javajavafxjavafx-tableview

ClassCasting Error on my JavaFX Application and Issue with Values showing up on tableView


Everything was working fine but then I added generics and now this ClassCastException keeps popping up and I have no idea why.

****Important****
So I tried removing it as well but here's where another issue came up. All the values printed up until the Traction Control Column. After this, all the values just don't show up event though besides the data type of some of them, everything is the exact same.

The Error

Caused by: java.lang.ClassCastException: app.westminster.car.rentalSystem.Car cannot be cast to app.westminster.car.rentalSystem.Motorbike at app.westminster.car.rentalSystem.GUI.MainGUI.lambda$start$5(MainGUI.java:139) at javafx.scene.control.TableColumn.getCellObservableValue(TableColumn.java:578) at javafx.scene.control.TableColumn.getCellObservableValue(TableColumn.java:563) at javafx.scene.control.TableCell.updateItem(TableCell.java:644) at javafx.scene.control.TableCell.indexChanged(TableCell.java:468) at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116) at com.sun.javafx.scene.control.skin.TableRowSkinBase.updateCells(TableRowSkinBase.java:533) at com.sun.javafx.scene.control.skin.TableRowSkinBase.init(TableRowSkinBase.java:147) at com.sun.javafx.scene.control.skin.TableRowSkin.(TableRowSkin.java:64) at javafx.scene.control.TableRow.createDefaultSkin(TableRow.java:212) at javafx.scene.control.Control.impl_processCSS(Control.java:872) at javafx.scene.Node.processCSS(Node.java:9056) at javafx.scene.Node.applyCss(Node.java:9153) at com.sun.javafx.scene.control.skin.VirtualFlow.setCellIndex(VirtualFlow.java:1964) at com.sun.javafx.scene.control.skin.VirtualFlow.getCell(VirtualFlow.java:1797) at com.sun.javafx.scene.control.skin.VirtualFlow.getCellLength(VirtualFlow.java:1879) at com.sun.javafx.scene.control.skin.VirtualFlow.computeViewportOffset(VirtualFlow.java:2528) at com.sun.javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1189) at javafx.scene.Parent.layout(Parent.java:1087) at javafx.scene.Parent.layout(Parent.java:1093) at javafx.scene.Parent.layout(Parent.java:1093) at javafx.scene.Scene.doLayoutPass(Scene.java:552) at javafx.scene.Scene.preferredSize(Scene.java:1646) at javafx.scene.Scene.impl_preferredSize(Scene.java:1720) at javafx.stage.Window$9.invalidated(Window.java:864) at javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:109) at javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:144) at javafx.stage.Window.setShowing(Window.java:940) at javafx.stage.Window.show(Window.java:955) at javafx.stage.Stage.show(Stage.java:259) at app.westminster.car.rentalSystem.GUI.MainGUI.start(MainGUI.java:154) at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863) at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326) at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295) at java.security.AccessController.doPrivileged(Native Method) at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294) at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95) at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177) ... 1 more

The GUI Class

public class MainGUI extends Application {

private static Stage stage;

@Override
public void start(Stage primaryStage) throws Exception {

    stage = primaryStage;
    Platform.setImplicitExit(false);

    stage.setTitle("Westminster Rentals");
    VBox mainLayout = new VBox();

    Scene scene = new Scene(mainLayout, 1655,800);

    Label heading1 = new Label("Welcome to Westminster Rentals");
    heading1.setStyle("-fx-font-size: 32px; -fx-font-family: -apple-system, sans-serif;");
    heading1.setPadding(new Insets(20,20,20,20));


    //_____________________Search Tab__________________________________//

    // search Tab will hold search bar, search button and rent button

    HBox searchTab = new HBox();
    searchTab.setStyle("-fx-spacing: 10px; -fx-padding: 20px");

    JFXButton searchButton = new JFXButton("Search");
    searchButton.setAlignment(Pos.CENTER);
    searchButton.setStyle("-fx-padding: 20px;-fx-background-color: #6143d5; -fx-text-fill: #fff;");

    JFXTextField searchBar = new JFXTextField();
    searchBar.setPrefWidth(500);
    searchBar.setStyle("-fx-padding: 20px;");
    searchBar.promptTextProperty().setValue("Search....");

    HBox rentTab = new HBox();
    JFXButton rent = new JFXButton("Rent a car");
    rent.setOnAction(e->{
        RentGUI.showRentPage();
    });
    rent.setStyle("-fx-padding: 20px;-fx-background-color: #1ad541; -fx-text-fill: #fff;");


    rentTab.getChildren().add(rent);
    searchTab.getChildren().addAll(searchBar,searchButton, rentTab);

    //_____________________Search Tab --END--_____________________________//


    //Main Heading will hold a vertical alignment for heading1 and the searchTab Hbox to create embedded layout
    VBox mainHeading =  new VBox();
    mainHeading.getChildren().addAll(heading1,searchTab);

    //Table view and Table columns are used to build the table attribute of the app
    // This needs to be done as below unless FXML is used to build the application
    TableView<Vehicle> tableView = new TableView<>();
    tableView.setEditable(false);

    TableColumn<Vehicle, String> makeColumn = new TableColumn<>("Make");
    makeColumn.setPrefWidth(150);
    makeColumn.setCellValueFactory(data->  new SimpleStringProperty(data.getValue().getMake()));

    TableColumn<Vehicle,String> modelColumn = new TableColumn<>("Model");
    modelColumn.setPrefWidth(200);
    modelColumn.setCellValueFactory(new PropertyValueFactory<>("model"));

    TableColumn<Vehicle,String> plateNumColumn = new TableColumn<>("Plate Number");
    plateNumColumn.setPrefWidth(200);
    plateNumColumn.setCellValueFactory(new PropertyValueFactory<>("plateNumber"));

    TableColumn<Vehicle,String> fuelTypeColumn = new TableColumn<>("Fuel Type");
    fuelTypeColumn.setPrefWidth(200);
    fuelTypeColumn.setCellValueFactory(new PropertyValueFactory<>("fuelType"));

    TableColumn<Vehicle, BigDecimal> priceColumn = new TableColumn<>("Price Per Hour (USD)");
    priceColumn.setPrefWidth(200);
    priceColumn.setCellValueFactory(new PropertyValueFactory<>("pricePerHour"));

    TableColumn<Vehicle,String> transmissionColumn = new TableColumn<>("Transmission");
    transmissionColumn.setPrefWidth(200);
    transmissionColumn.setCellValueFactory(new PropertyValueFactory<>("transmission"));

    TableColumn carColumn = new TableColumn("Car");
    carColumn.setPrefWidth(200);
    carColumn.setCellValueFactory(new PropertyValueFactory("car"));

    TableColumn<Car,Boolean> tractionControlColumn = new TableColumn<>("Traction Control?");
    tractionControlColumn.setPrefWidth(200);
    tractionControlColumn.setCellValueFactory(new PropertyValueFactory<>("tractionControl"));

    TableColumn<Car,Boolean> appleCarPlayColumn = new TableColumn<>("Apple Car Play?");
    appleCarPlayColumn.setPrefWidth(200);
    appleCarPlayColumn.setCellValueFactory(data-> new SimpleBooleanProperty(data.getValue().hasAppleCarPlay()));

    TableColumn <Car,String>parkingAssistanceColumn = new TableColumn<>("Parking Assistance?");
    parkingAssistanceColumn.setPrefWidth(200);
    parkingAssistanceColumn.setCellValueFactory(data-> new SimpleStringProperty(String.valueOf(data.getValue().hasParkingAssistant())));

    TableColumn<Car,String> blindSpotAwarenessColumn = new TableColumn<>("Blind spot awareness?");
    blindSpotAwarenessColumn.setPrefWidth(200);
    blindSpotAwarenessColumn.setCellValueFactory(data-> new SimpleStringProperty(String.valueOf(data.getValue().hasBlindSpotAwareness())));

    TableColumn bikeColumn = new TableColumn("Motorbikes");
    bikeColumn.setPrefWidth(200);
    bikeColumn.setCellValueFactory(new PropertyValueFactory("motorbike"));

    TableColumn<Motorbike, Boolean> basketColumn = new TableColumn<>("Basket?");
    basketColumn.setPrefWidth(200);
    basketColumn.setCellValueFactory(data-> new SimpleBooleanProperty(data.getValue().hasBasket()));

    TableColumn<Motorbike, Boolean> gyroColumn = new TableColumn<>("Gyroscopic Balance?");
    gyroColumn.setPrefWidth(200);
    gyroColumn.setCellValueFactory(data-> new SimpleBooleanProperty(data.getValue().hasGyroscopicBalance()));


    carColumn.getColumns().addAll(tractionControlColumn,appleCarPlayColumn,parkingAssistanceColumn,blindSpotAwarenessColumn);
    bikeColumn.getColumns().addAll(basketColumn,gyroColumn);
    tableView.getColumns().addAll(makeColumn,modelColumn,plateNumColumn,fuelTypeColumn,priceColumn,transmissionColumn,carColumn,bikeColumn);

    mainLayout.getChildren().addAll(mainHeading, tableView);
    tableView.setItems(addTableItems());

    stage.setScene(scene);
    stage.show();

}


//addTableItems will create an observable list required to enter values into the table.

private ObservableList<Vehicle> addTableItems() throws UnknownHostException {
    ObservableList<Vehicle> musicItem = FXCollections.observableArrayList();
    for (Vehicle item: Database.getDatastore().find(Vehicle.class).asList()){
        musicItem.addAll(item);
    }
    return musicItem;
}

public static void main(){
    launch();
}

public static void showStage(){
    stage.show();
}
}

The Main Class with Main Method

public class Main extends Thread{

private static Scanner scanner = new Scanner(System.in);
private static Stage stage;

public static void main(String[] args) {
    int userResponse;
    RentalVehicleManager rentalVehicleManager = new WestminsterRentalVehicleManager();
    Thread guiControlThread = new Thread(MainGUI::main);
    guiControlThread.start();
}

}

Let's say I remove the lambdas and put in new PropertyValueFactory. Then the error disappears, the GUI works

BUT

The values after traction Control dont show up

Result


Solution

  • There's a discrepancy between the type parameter of the TableView (Vehicle) and the first type parameter you use for some of your columns. Those 2 type parameters are supposed to match. Otherwise things like this can happen.

    Since Vehicle is the type parameter of TableView the compiler allows you to add everything that is assignable to Vehicle to the items list of that TableView.

    Declaring basketColumn as TableColumn<Motorbike, Boolean> basketColumn "tells" the compiler that all the items in the table are Motorbikes. The following code snippet would produce the same result in the compiled code for this reason:

    TableColumn basketColumn = new TableColumn("Basket?"); // note the use of the raw type here
    basketColumn.setPrefWidth(200);
    
    // note the hidden cast inserted here by the compiler based on the type parameter in the declaration
    // this is the cause of the ClassCastException
    basketColumn.setCellValueFactory(data -> new SimpleBooleanProperty(((Motorbike) data.getValue()).hasBasket()));
    

    Since you want to put both Cars and Motorbikes into the TableView, you need to return a result based on the real type in your cellValueFactorys:

    TableColumn<Vehicle, Boolean> basketColumn = new TableColumn<>("Basket?");
    basketColumn.setPrefWidth(200);
    basketColumn.setCellValueFactory(data -> {
        Vehicle item = data.getValue();
        if (item instanceof Motorbike) {
            return new SimpleBooleanProperty(((Motorbike) item).hasBasket());
        } else {
            return null; // put something else here, if you don't want empty cells for non Motorbikes
        }
    });
    

    There are multiple other places in your code where changes like this one are necessary.