如何解决在JavaFX中设置TableView的列时出现的“Unchecked generics”警告?

huangapple go评论85阅读模式
英文:

How to resolve "Unchecked generics" warning when setting columns on a TableView in JavaFX

问题

在这个常见情况下,我遇到了一个编译器警告,即在JavaFX应用程序中向表视图控件添加列。

为了演示,这是我修改过的代码示例,该示例在JavaFX/OpenJFXTableView的Javadoc中可见。请注意getColumns行。

public class HelloApplication extends Application
{
    @Override
    public void start ( Stage stage )
    {
        TableView < Person > tableViewPersons = new TableView <> ( );
        ObservableList < Person > persons = FXCollections.observableArrayList ( this.fetchPersons ( ) );
        tableViewPersons.setItems ( persons );

        TableColumn < Person, String > firstNameCol = new TableColumn <> ( "First Name" );
        firstNameCol.setCellValueFactory ( new PropertyValueFactory <> ( persons.get ( 0 ).firstNameProperty ( ).getName ( ) ) );

        TableColumn < Person, String > lastNameCol = new TableColumn <> ( "Last Name" );
        lastNameCol.setCellValueFactory ( new PropertyValueFactory <> ( persons.get ( 0 ).lastNameProperty ( ).getName ( ) ) );

        tableViewPersons.getColumns ( ).setAll ( firstNameCol , lastNameCol );  // <---- 警告:为可变参数创建未经检查的泛型数组。

        Scene scene = new Scene ( tableViewPersons , 320 , 240 );
        stage.setTitle ( "JavaFX Example" );
        stage.setScene ( scene );
        stage.show ( );
    }

这行代码:

tableViewPersons.getColumns ( ).setAll ( firstNameCol , lastNameCol );

…会生成以下警告:

警告:为可变参数创建未经检查的泛型数组

根据Java unchecked: unchecked generic array creation for varargs parameter上的这个答案这篇文章,我了解到问题涉及传递参数的类型不明确。

如果我的目标是解决这个编译器警告,那么一种解决方案是明确传递给ObservableList#setAllTableColumn对象集合的参数化类型。因此,我们可以显式声明一个TableColumn对象的List

        List < TableColumn < Person, ? > > columns = List.of ( firstNameCol , lastNameCol );  // <--- 添加这行代码以明确`TableColumn`的参数化类型,以解决“Unchecked generics”警告。
        tableViewPersons.getColumns ( ).setAll ( columns );

在完整的上下文中查看新代码:

public class HelloApplication extends Application
{
    @Override
    public void start ( Stage stage )
    {
        TableView < Person > tableViewPersons = new TableView <> ( );
        ObservableList < Person > persons = FXCollections.observableArrayList ( this.fetchPersons ( ) );
        tableViewPersons.setItems ( persons );

        TableColumn < Person, String > firstNameCol = new TableColumn <> ( "First Name" );
        firstNameCol.setCellValueFactory ( new PropertyValueFactory <> ( persons.get ( 0 ).firstNameProperty ( ).getName ( ) ) );

        TableColumn < Person, String > lastNameCol = new TableColumn <> ( "Last Name" );
        lastNameCol.setCellValueFactory ( new PropertyValueFactory <> ( persons.get ( 0 ).lastNameProperty ( ).getName ( ) ) );

        List < TableColumn < Person, ? > > columns = List.of ( firstNameCol , lastNameCol );  // <--- 添加这行代码以明确`TableColumn`的参数化类型,以解决“Unchecked generics”警告。
        tableViewPersons.getColumns ( ).setAll ( columns );

        Scene scene = new Scene ( tableViewPersons , 320 , 240 );
        stage.setTitle ( "JavaFX Example" );
        stage.setScene ( scene );
        stage.show ( );
    }

我的问题:

  • 这是解决“Unchecked generics”警告的有效完整解决方案吗?

  • 是否有其他更简单的方法来解决“Unchecked generics”警告?(我不想抑制警告。)

英文:

I encountered a compiler warning in this common situation of adding columns to a table view control in a JavaFX app.

To demonstrate, here is my modified version of the code example seen in the Javadoc for the JavaFX/OpenJFX class TableView. Note the getColumns line.

public class HelloApplication extends Application
{
    @Override
    public void start ( Stage stage )
    {
        TableView &lt; Person &gt; tableViewPersons = new TableView &lt;&gt; ( );
        ObservableList &lt; Person &gt; persons = FXCollections.observableArrayList ( this.fetchPersons ( ) );
        tableViewPersons.setItems ( persons );

        TableColumn &lt; Person, String &gt; firstNameCol = new TableColumn &lt;&gt; ( &quot;First Name&quot; );
        firstNameCol.setCellValueFactory ( new PropertyValueFactory &lt;&gt; ( persons.get ( 0 ).firstNameProperty ( ).getName ( ) ) );

        TableColumn &lt; Person, String &gt; lastNameCol = new TableColumn &lt;&gt; ( &quot;Last Name&quot; );
        lastNameCol.setCellValueFactory ( new PropertyValueFactory &lt;&gt; ( persons.get ( 0 ).lastNameProperty ( ).getName ( ) ) );

        tableViewPersons.getColumns ( ).setAll ( firstNameCol , lastNameCol );  // &lt;---- Warning: Unchecked generics array creation for varargs parameter.

        Scene scene = new Scene ( tableViewPersons , 320 , 240 );
        stage.setTitle ( &quot;JavaFX Example&quot; );
        stage.setScene ( scene );
        stage.show ( );
    }

The line:

tableViewPersons.getColumns ( ).setAll ( firstNameCol , lastNameCol );

… generates a warning for:

> Warning: Unchecked generics array creation for varargs parameter

I understand from this Answer on Java unchecked: unchecked generic array creation for varargs parameter, and from this post, that the issue involves ambiguity about the type of the arguments being passed.

If my goal is to resolve this compiler warning, then one solution is to make explicit the parameterized type of the collection of TableColumn objects be passed to ObservableList#setAll. So we can explicitly declare a List of the TableColumn objects.

        List &lt; TableColumn &lt; Person, ? &gt; &gt; columns = List.of ( firstNameCol , lastNameCol );  // &lt;--- Adding this line to make explicit the parameterized type of `TableColumn` to resolve the &quot;Unchecked generics&quot; warning.
        tableViewPersons.getColumns ( ).setAll ( columns );

See that new code in the full context:

public class HelloApplication extends Application
{
    @Override
    public void start ( Stage stage )
    {
        TableView &lt; Person &gt; tableViewPersons = new TableView &lt;&gt; ( );
        ObservableList &lt; Person &gt; persons = FXCollections.observableArrayList ( this.fetchPersons ( ) );
        tableViewPersons.setItems ( persons );

        TableColumn &lt; Person, String &gt; firstNameCol = new TableColumn &lt;&gt; ( &quot;First Name&quot; );
        firstNameCol.setCellValueFactory ( new PropertyValueFactory &lt;&gt; ( persons.get ( 0 ).firstNameProperty ( ).getName ( ) ) );

        TableColumn &lt; Person, String &gt; lastNameCol = new TableColumn &lt;&gt; ( &quot;Last Name&quot; );
        lastNameCol.setCellValueFactory ( new PropertyValueFactory &lt;&gt; ( persons.get ( 0 ).lastNameProperty ( ).getName ( ) ) );

        List &lt; TableColumn &lt; Person, ? &gt; &gt; columns = List.of ( firstNameCol , lastNameCol );  // &lt;--- Adding this line to make explicit the parameterized type of `TableColumn` to resolve the &quot;Unchecked generics&quot; warning.
        tableViewPersons.getColumns ( ).setAll ( columns );

        Scene scene = new Scene ( tableViewPersons , 320 , 240 );
        stage.setTitle ( &quot;JavaFX Example&quot; );
        stage.setScene ( scene );
        stage.show ( );
    }

My question:

  • Is this a valid, complete solution to resolving the the "Unchecked generics" warning?
  • Is there any other simpler way to resolve the "Unchecked generics" warning? (I would rather not suppress the warning.)

答案1

得分: 6

使用不带可变参数的替代API

处理未经检查的泛型警告的一种方法(并非在所有情况下都适用)是重写代码,使用一个不太容易出现此类问题的不同API。

例如,可以不调用setAll,而是在列表上调用clear,然后逐个调用add方法添加每个项目。可变参数类型的setAll主要是为了方便进行此操作。

这两种选项之间有一些微小的差别。因为列表是可观察的,所以每个原子操作后都会触发更改。setAll使调用的所有更改都是原子的,从而减少了触发的更改次数,但我认为在这种情况下这不会成为问题。

如果有很多列,逐个调用add会更冗长,但可以避免任何类型错误和警告的潜在问题(我不是在提倡这种方法,只是让你知道这种可能性)。

你可以将以下代码替换为:

tableViewPersons.getColumns().setAll(firstNameCol, lastNameCol);  // <---- 警告:未经检查的泛型数组创建,用于可变参数。

使用不带可变参数的替代API调用:

tableViewPersons.getColumns().clear();
tableViewPersons.getColumns().add(firstNameCol);
tableViewPersons.getColumns().add(lastNameCol);

你的解决方案是正确的(也使用了不带可变参数的替代API)

你解决这个问题的方法是在上面的部分中描述的相同方法,你使用了一个不同的API而不是可变参数的API。

List<TableColumn<Person, ?>> columns = List.of(firstNameCol, lastNameCol);  // <--- 添加此行以明确解析“未经检查的泛型”警告的参数化类型。
tableViewPersons.getColumns().setAll(columns);

ObservableList有重载的setAll方法:

你将可变参数的API调用替换为单参数的变体。这是一个完全有效的解决方案。对于不提供类似重载接口的不同API,这种方法在一般情况下不起作用,但在这种情况下可以使用。

在你的情况下,如果所有列都映射到String属性,我不会使用通配符类型?,而是会使用:

List<TableColumn<Person, String>> columns = List.of(firstNameCol, lastNameCol);

只有当每列映射到不同类型时才使用通配符类型。

使用SafeVarargs

处理此问题的另一种方法是创建一个帮助方法,并使用SafeVarargs注解来确保安全使用可变参数。

//...
addColumns(tableViewPersons, firstNameCol, lastNameCol);
//...

@SafeVarargs
private static void addColumns(
    TableView<Person> tableViewPersons, 
    TableColumn<Person, String>... columns) 
{
    tableViewPersons.getColumns().setAll(columns);
}

关于抑制警告的个人意见

我知道你不想抑制警告,正如问题中提到的那样(这就是为什么我提供了其他选项)。但是我认为,在这种情况下,抑制警告是可以接受的。

我认为在许多情况下,未经检查的内容只是一种烦恼。泛型最初是作为一种妥协设计添加到Java中的,即使是设计者也会这样说,因为该语言最初并不支持泛型。在像这种情况下出现的一些未经检查的泛型警告周围的内容是妥协设计的一部分。

在TableView定义、后备列表等地方使用主要类型是好的(也很重要)。但是,当你开始使用通配符类型或方法级别的类型说明符来使用已经有类型的类和方法,或者试图寻找方法使用上已经知道是安全的警告时,这会比有益更加分散注意力。在这种情况下,我建议有选择性地抑制警告

你可以使用IDE来帮助抑制不必要的警告,例如在Idea中抑制警告。在这样做之前,请确保它确实不会导致重大的错误潜在问题,但如果不会(像许多未经检查的泛型警告一样),那么我认为抑制它们是可以接受的。

例如,在Idea IDE中抑制特定行的警告,可以使用以下注释(这是我通常使用的方法):

//noinspection unchecked
tableViewPersons.getColumns().setAll(firstNameCol, lastNameCol);

不幸的是,在一般情况下,Java只允许在类、字段或方法级别上抑制警告。

因此,在方法级别上以可移植的方式执行此操作是使用SuppressWarnings注解,将有限的相关代码放在一个小方法中:

@SuppressWarnings("unchecked")
private TableView<Person> createPersonTableView(ObservableList<Person> people) {
    TableView<Person> tableViewPersons = new TableView<>(people);

    TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
    firstNameCol.setCellValueFactory(data -> data.getValue().firstNameProperty());

    TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
    firstNameCol.setCellValueFactory(data -> data.getValue().lastNameProperty());
    
    tableViewPersons.getColumns().setAll(firstNameCol, lastNameCol);
    
    return tableViewPersons;
}

使用Lambda表达式而不是PropertyValueFactory

顺便提一下,与泛型问题无关,上面的示例代码还说明了使用Lambda表达式设置单元格值工厂而不是依赖于PropertyValueFactory的“正确”方法,如下所述:

在我看来,使用Lambda表达式而不是PropertyValueFactory比在TableView方法调用中抑制可变参数类型检查的问题更重要,因为使用PropertyValueFactory时更容易创建运行时错误,如果出现错误,调试起来更困难。

英文:

Using alternate API without varargs

One way to handle the unchecked generics warnings (which won't work in all cases) is to rewrite your code to use a different API which is less susceptible to such issues.

For example, instead of calling setAll, you can call clear on the list and then call the add method individually for each item. The varargs typed setAll is mostly a convenience method for doing this.

There is a slight difference in meaning between the two options. Because the list is observable, changes are fired after each atomic operation. setAll makes all of the changes for the call atomic, resulting in fewer changes fired, but I don't think that will be an issue in this case.

It is more wordy to call add for each column individually if you have many columns, but will avoid any potential for type errors and warnings. (I'm not advocating that approach, just letting you know the possibility).

You can replace the call:

tableViewPersons.getColumns().setAll(firstNameCol, lastNameCol);  // &lt;---- Warning: Unchecked generics array creation for varargs parameter.

With alternate API calls which do not use a varargs parameter:

tableViewPersons.getColumns().clear();
tableViewPersons.getColumns().add(firstNameCol);
tableViewPersons.getColumns().add(lastNameCol);

Your solution is OK (and also uses alternate API without varargs)

Your solution to this issue where you create a List of a given type up front and provide that to the TableView is really the same approach as described in the above section, you are using a different API instead of the varargs API.

> List < TableColumn < Person, ? > > columns = List.of ( firstNameCol , lastNameCol ); // <--- Adding this line to make explicit the parameterized type of TableColumn to resolve the "Unchecked generics" warning.
> tableViewPersons.getColumns ( ).setAll ( columns );

ObservableList has overloaded setAll methods:

You are replacing the varargs API call with the single argument variant. It is a perfectly valid solution. It won't work in the general case with different APIs that don't provide an overloaded interface like this, but it will work in this instance.

In your case, where all columns are mapping String properties, I would not use the wildcard type ?, instead I would use:

List &lt; TableColumn &lt; Person, String &gt; &gt; columns = List.of ( firstNameCol , lastNameCol );

and only use the wild card type if each column mapped to a different type.

Using SafeVarargs

Another way to handle this is to create a helper method, annotated SafeVarargs to assert you are using the variable arguments safely.

//...
addColumns(tableViewPersons, firstNameCol, lastNameCol);
//...
@SafeVarargs
private static void addColumns(
TableView&lt;Person&gt; tableViewPersons, 
TableColumn&lt;Person, String&gt;... columns) 
{
tableViewPersons.getColumns().setAll(columns);
}

Unsolicited opinion on suppressing warnings

I know you don't want to suppress the warnings as mentioned in the question (which is why I have provided other options). But I think, in this case, suppression is OK.

My opinion is that the unchecked stuff is just an annoyance in many cases. Generics were added to Java in what I think even the designers would say was a compromise design, because the language was not built for generics originally. The stuff that shows up around some of the unchecked generics warnings in cases like this is a part of the compromise design.

Having the main type on the TableView definition, backing list, etc, is good (and important). But when you start having to do stuff like use wildcard types or method level type specifiers when using classes and methods that are already typed, or trying to hunt around to remove a warning on a method usage that you already know is safe, it becomes more distracting than beneficial. In such cases, I advise suppressing the warnings on a selective basis.

Potentially you can use your IDE to assist in suppressing unnecessary warnings, for example suppressing in Idea. Before doing so, check that it isn't really a significant potential for error, but if it is not (like a lot of the unchecked generics warnings IMO), then I think it is OK to suppress them.

For example to suppress the warning in the Idea IDE for a specific line, the following comment can be used (this is what I usually do):

//noinspection unchecked
tableViewPersons.getColumns().setAll(firstNameCol, lastNameCol);

Unfortunately, in a general case Java only allows suppressing the warnings on a class, field or method level.

So a portable way to do this at the method level is using the SupressWarnings annotation where limited, relevant code is placed in a small method:

@SuppressWarnings(&quot;unchecked&quot;)
private TableView&lt;Person&gt; createPersonTableView(ObservableList&lt;Person&gt; people) {
TableView&lt;Person&gt; tableViewPersons = new TableView&lt;&gt;(people);
TableColumn&lt;Person, String&gt; firstNameCol = new TableColumn&lt;&gt;(&quot;First Name&quot;);
firstNameCol.setCellValueFactory(data -&gt; data.getValue().firstNameProperty());
TableColumn&lt;Person, String&gt; lastNameCol = new TableColumn&lt;&gt;(&quot;Last Name&quot;);
firstNameCol.setCellValueFactory(data -&gt; data.getValue().lastNameProperty());
tableViewPersons.getColumns().setAll(firstNameCol, lastNameCol);
return tableViewPersons;
}

Using lambda's instead of PropertyValueFactory

Incidentally, but not related to the generics issue, the example snippet above also illustrates the "correct" use of lambdas to set the cell value factories rather than relying on PropertyValueFactory, as discussed in:

In my opinion, using lambdas instead of PropertyValueFactory is more important than the issues around suppression of the varargs type checks on the TableView method calls, because it is far easier to create runtime errors when using a PropertyValueFactory and more difficult to debug them if they do arise.

huangapple
  • 本文由 发表于 2023年8月9日 06:26:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76863532.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定