1

I have some troubles with generics and, despite having found a workaround, I don't understand what prevents my code from compiling.

I have a JavaFX project which displays a TreeTableView:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication7.FXMLController">
   <children>
      <TreeTableView fx:id="tree" layoutX="43.0" layoutY="30.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <columns>
          <TreeTableColumn id="date" prefWidth="399.0" text="Date" />
          <TreeTableColumn id="dateType" minWidth="0.0" prefWidth="200.0" text="Type" />
        </columns>
      </TreeTableView>
   </children>
</AnchorPane>

The tree simply displays an array of objects "Date", the first column with strings and the second with images:

public enum DateType {
    WORKDAY("work.jpg"),
    HOLIDAY("holiday.jpg");

    private Image image;

    DateType(String imgPath) {
        image = new Image(FXMLController.class.getResourceAsStream(imgPath));
    }

    public Image getImage() {
        return image;
    }
}

public class Date {
    public LocalDate date;
    public DateType dayType;

    public String getDate() {
        return date.toString();
    }

    public ImageView getDateType() {
        return new ImageView(dayType.getImage());
    }
}

I have a custom Consumer class that do all the TreeTableView configuration:

import java.util.Comparator;
import java.util.function.Consumer;
import javafx.scene.Node;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;

public class TreeTableColumnConfigurator<S, E extends Enum<E>, N extends Node> implements Consumer<TreeTableColumn<S, N>> {

    @Override
    public void accept(TreeTableColumn<S, N> t) {
        t.setCellValueFactory(new TreeItemPropertyValueFactory(t.getId()));
        t.setCellFactory(column -> {
            TreeTableCell cell = TreeTableColumn.DEFAULT_CELL_FACTORY.call(column);
            return cell;
        });
        if ("dateType".equals(t.getId())) {
            t.setComparator(Comparator.<N, E>comparing(
                    node -> (E) node.getUserData()
            ));
        }
    }

}

The compilation error is raised by the controller associated to the FXML:

import java.net.URL;
import java.time.LocalDate;
import java.util.ResourceBundle;
import java.util.function.Consumer;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TreeTableView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

public class FXMLController implements Initializable {

    @FXML
    private TreeTableView<Date> tree;

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        /* working workaround */
        Consumer consumer = new TreeTableColumnConfigurator<Date, DateType, ImageView>();
        tree.getColumns().stream().forEach(consumer);
        /* not compiling code */
        tree.getColumns().stream().forEach(new TreeTableColumnConfigurator<Date, DateType, ImageView>());
    }    

}

The last line of the method "initialize" raises the following error:

FXMLController.java:32: error: incompatible types: TreeTableColumnConfigurator<Date,DateType,ImageView> cannot be converted to Consumer<? super TreeTableColumn<Date,?>>
        tree.getColumns().stream().forEach(new TreeTableColumnConfigurator<Date, DateType, ImageView>());

I don't understand why the 2 first lines of the same method don't raise anything at all, functionally they do the same thing?

OOEngineer
  • 447
  • 3
  • 12

1 Answers1

1

Your Consumer instance uses a raw type When you use raw types, the compiler ignores generic type checking, which is why it won't throw an error on this line:

Consumer consumer = new TreeTableColumnConfigurator<Date, DateType, ImageView>();

However, if you changed the above line to this...

Consumer<TreeTableColumn<Date, ImageView>> consumer = new TreeTableColumnConfigurator<Date, DateType, ImageView>();

...and tried to compile it that way, the same error that you got with the second line will appear since the Consumer instance has been given type parameters.

Why does the error appear? tree.getColumns().stream() returns a Stream<TreeTableColumn<Date, ?>>, and forEach expects a Consumer<TreeTableColumn<Date, ?>>. It's important to note, however, that TreeTableColumn<Date, ?> and TreeTableColumn<Date, ImageView> are not the same, just like how List<Object> and List<?> are not the same; this is why, as the compiler mentions, Consumer<TreeTableColumn<Date, ImageView>> cannot be converted to Consumer<TreeTableColumn<Date, ?>>.

While using raw types works in this case, you should come up with a generic solution that is suitable for your needs (preserving the wildcard, for example), if one exists.

Community
  • 1
  • 1
TNT
  • 2,900
  • 3
  • 23
  • 34
  • Thanks for the explanations about raw types. It seems like TreeTableColumn isn't compatible with TreeTableColumn because generics in Java aren't covariant while generics in dotNet can be covariant. But more importantly, I don't see what I can do to only use parametrised types and preserve the wildcard... I can partially do it by changing the configurator class as "class TreeTableColumnConfigurator> implements Consumer>" but then I cannot implement the Comparator for the dateType column with generics anymore... – OOEngineer Jul 28 '15 at 22:54
  • After some practice, I understand that Java can kind of do covariant generics but, if I have to modify some wildcard generics, I'll be forced to use the equivalent raw types at some point else the compiler will always complain somewhere. Everything is fine only when handling wildcard generics in a read-only way. – OOEngineer Jul 31 '15 at 23:40