【问题标题】:Which Java generic should be used in ambiguous cases?在模棱两可的情况下应该使用哪个 Java 泛型?
【发布时间】:2010-12-21 21:19:50
【问题描述】:

我在 Wicket 1.3 -> Wicket 1.4 迁移时遇到了一些问题,但这个问题也可以应用于 Java 泛型。迁移导致数百个警告突然出现——对于那些不熟悉 Wicket 的人来说,许多 Wicket 类都派生自一个共同的祖先,在 v1.4 中被泛型化——我不确定要应用哪些参数在某些情况下,主要是各种表格和表格。我认为他们可以使用 <?><Object><Void>,但我不确定是哪一个。

<?> 似乎最适合我,但有很多地方我不能使用通配符。 <Object> 在所有情况下都有效,但这让我感到不安,因为它基本上是在不使用通配符的情况下编写通配符,这对我的部分大脑来说是天生错误的。在Wicket migration guide 中建议使用<Void>

那么在这种情况下应该怎么做呢?


编辑 2:我认为我的第一次编辑(现在在问题的底部)使人们看起来像是在询问字符串的集合,从而使人们感到困惑。以下是其他示例及其警告:

public class DocumentProcessor extends Form implements DocumentManagement { ...

Form 是原始类型。对泛型类型 Form 的引用应该被参数化

AjaxFallbackDefaultDataTable theTable = new AjaxFallbackDefaultDataTable("theTable", cols, dataProvider, recPerPg);

此行有多个标记
- 类型安全:构造函数 AjaxFallbackDefaultDataTable(String, List, ISortableDataProvider, int) 属于原始类型 AjaxFallbackDefaultDataTable。应参数化对泛型类型 AjaxFallbackDefaultDataTable 的引用
- AjaxFallbackDefaultDataTable 是原始类型。应参数化对泛型类型 AjaxFallbackDefaultDataTable 的引用
- AjaxFallbackDefaultDataTable 是原始类型。对泛型 AjaxFallbackDefaultDataTable 的引用应该被参数化


编辑:我希望这个问题如此广泛以至于不需要示例代码,但这里有一些。

List<IColumn> columns = new ArrayList<IColumn>();
columns.add(new PropertyColumn(new Model<String>("Number"), "revisionID"));

会生成以下警告:

[第一行] 处有多个标记
- IColumn 是原始类型。对泛型类型 IColumn 的引用应参数化
- IColumn 是原始类型。对泛型 IColumn 的引用应该被参数化

在 [第二] 行有多个标记
- 类型安全:构造函数 PropertyColumn(IModel, String) 属于原始类型 PropertyColumn。应参数化对泛型类型 PropertyColumn 的引用
- PropertyColumn 是原始类型。对泛型 PropertyColumn 的引用应该被参数化

没有错误。

【问题讨论】:

    标签: java generics wicket


    【解决方案1】:

    如果您不打算使用组件的底层模型对象,请使用 Void

    从语义上讲,它更合理并且更好地传达了模型对象不是可以是任何东西,而是它在语义上什么都不是并且永远不会被使用的想法。在这种情况下,大多使用 Void 关键字作为常规解决方案。

    如果您要使用模型对象并且不在乎,我认为这不是您的意思,**尽可能使用通配符**不能(构造函数参数等),无论是 Void、Object 还是其他一些“包含”类,都不能根据组件的特定语义和所需的泛型类型行为(例如,在构造函数的情况下)做出决定对于组件变量,你会考虑你的构造函数将如何处理 Void 或 Object 类型)。

    当然,那是从“良好的编程实践”理论来讲,在实践中你不必太在意,虽然这种想法可以帮助你的队友维护你的代码,并且可以帮助你更好地理解它,也许甚至可以预测错误。

    到处使用通配符在检票口用户中相当普遍,可能比我建议的决定更常见,但这并不是因为通配符是惯例,而是很可能仅仅是由于大多数代码示例在首选通配符的搜索引擎中弹出。尽管如此,正如迁移指南对 Void 的建议所示,通配符不仅在语义上不太连贯,而且它们似乎也不是一个绝对的约定,甚至似乎受到 wicket 开发人员的挑战,我们最好假设,对他们的类型的内部运作有足够的了解,他们的建议才会被认真对待。

    【讨论】:

    • +1 用于解释Void 何时是正确选项。 (因为这是 Wicket 特有的。)
    • 非常感谢您的回答,很高兴看到老问题不会总是被遗忘。你有 Wicket 开发者挑战的约定的来源吗?
    • Lord Torgamus:除了你自己提到的来源,迁移指南,他们认为 Void 应该适用于大多数以前没有类型的组件。我所做的只是详细说明什么时候去 Void 是有意义的
    【解决方案2】:

    您想使用与您的组件关联的模型类型。也就是说,使用调用 getModelObject() 返回的类型。因此,使用迁移指南中的示例:

    ListView<Person> peopleListView = new ListView<Person>("people", people) {
        protected void populateItem(ListItem<Person> item) {
            item.add(new Link<Person>("editPerson", item.getModel()){
                public void onClick() {
                    Person p = getModelObject();
                    setResponsePage(new EditPersonPage(p));
                }
            });
        }
    };
    

    使用泛型很容易看出这是一个人员列表,并带有指向使用人员作为模型的编辑页面的链接。不幸的是,在检票口中,您的组件通常没有与之关联的模型。在这种情况下,getModel() 将返回 null,因此要使用的正确类型是 &lt;Void&gt;,它本质上是 null 的占位符。

    文档处理器

    public class DocumentProcessor extends Form implements DocumentManagement { ...
    

    如果您没有为 DocumentProcessor 设置模型,它看起来像这样:

    public class DocumentProcessor extends Form<Void> implements DocumentManagement {
        public DocumentProcessor(String id) {
            super(id);
            ....
    

    但使用模型 DocumentProcessor 看起来像这样:

    public class DocumentProcessor extends Form<Document> implements DocumentManagement {
        public DocumentProcessor(String id, Document doc) {
            super(id, doc);
    

    AjaxFallbackDefaultDataTable

    从它的构造函数来看,AjaxFallbackDefaultDataTable 可能会在它的模型中存储 IColumn[] 或 List,但是对于您的实现,您不知道或不关心所以 &lt;?&gt; 是合适的,这和 DocumentProcessor 之间的区别在于您重新扩展 Form,因此知道并关心它是如何使用它的模型的。

    I列

    对于 IColumn/PropertyColumn 示例,我将假设 revisionID 字段是 Long,然后我会这样写:

    List<PropertyColumn> columns = new ArrayList<PropertyColumn>();
    columns.add(new PropertyColumn<Long>(new Model<String>("Number"), "revisionID"));
    

    你可以看看

    More 1.4 Migration info

    Void type parameter

    【讨论】:

      【解决方案3】:

      在语义上,使用&lt;?&gt; 意味着“我不知道类型,实际上我根本不在乎。使用其他任何东西都会对预期内容的形式设定期望。 实际上,&lt;Object&gt; 的作用相同,但声明您将使用使用参数类型的泛型属性。

      所以经验法则应该是:

      • 如果您只处理遗传对象而不处理其参数化内容,请使用&lt;?&gt;,这样您一眼就知道该参数与行为无关。
      • 在任何其他情况下,请使用包含您的方法设计使用的所有类型的最具体的参数。极端情况为&lt;Object&gt;,其他包括&lt;? extends SomeTopLevelType&gt;

      【讨论】:

        【解决方案4】:

        可用的替代方案是:

        1. 要只使用原始类型,就像您在示例代码中所做的那样,只需忽略警告
        2. 使用通配符/对象泛型
        3. 使用扩展泛型

        我从你的问题中假设 #1 对你来说不是一个可行的选择。

        #2 示例(通配符/对象)

        List<IColumn<?>> columns = new ArrayList<IColumn<?>>();
        

        List<IColumn<Object>> columns = new ArrayList<IColumn<Object>>();
        

        IMO 我认为您选择 ? 还是 Object 并不重要,而且两者都不比另一个更正确,至少在功能上是这样。
        如果您不关心泛型是什么,并且您从不访问它,那么它的后果很严重;尽管请仔细考虑,如果确实有可能您将来会在这里使用泛型。只有在您的预迁移代码中,您发现自己不必从 IColumn 对象中进行任何类型转换,才会出现这种情况。

        #3 示例(扩展通用)

        IColumn 类型的所有可能的泛型创建一个超类型或公共接口。哪里
        T extends MyType:

        List<IColumn<T>> columns = new ArrayList<IColumn<T>>();
        

        我会根据IColumn 可能的通用属性实际上是什么来决定在第 2 和第 3 方法之间进行选择。

        • 如果它们是您自己的类并且您实际上想要访问泛型类型的对象,我会选择第三种方法,
        • 否则,例如使用String 或诸如Integer 之类的盒装原语,或者如果您不使用泛型类型的对象,我会选择方法2。

        HTH

        【讨论】:

          【解决方案5】:

          我没有使用检票口,沃达丰阻止我查看 API 文档。但是,您似乎缺少许多通用参数,并且想要类似:

          List<IColumn<String>> columns = new ArrayList<IColumn<String>>();
          columns.add(new PropertyColumn<String>(new Model<String>("Number"), "revisionID"));
          

          如果你想用不相关的泛型参数添加其他IColumns,你需要类似的东西;

          List<IColumn<?>> columns = new ArrayList<IColumn<?>>();
          columns.add(new PropertyColumn<String>(new Model<String>("Number"), "revisionID"));
          

          或者,如果您需要获取列的属性,可能类似于:

          List<IColumn<String>> strColumns = new ArrayList<IColumn<String>>();
          List<IColumn<?>> columns = new ArrayList<IColumn<?>>();
          PropertyColumn<String> column =
              new PropertyColumn<String>(new Model<String>("Number"), "revisionID");
          strColumns.add(column);
          columns.add(column);
          

          【讨论】:

            【解决方案6】:

            警告说 IColumn 接口和 PropertyColumn 类是参数化类型,因此您只需要为它们定义类型参数即可。

            考虑以下示例:

            List<Set> list = new ArrayList<Set>();
            

            ListArrayList 是参数化类型,并定义了它们的类型参数。但是,Set 也是参数化类型,但它的原始版本用作类型参数,因此,编译器会在这种情况下生成警告。

            我们可以通过显式指定所有类型参数来修复我们的示例,例如

            List<Set<Integer>> list1 = new ArrayList<Set<Integer>>();
            List<Set<String>> list1 = new ArrayList<Set<String>>();
            

            你需要为你的泛型类和接口做同样的事情。

            【讨论】:

            • 我知道我需要参数,但我不清楚应用哪一个。您能否在通配符、Object 和 Void 之间提出建议,或者解释一下为什么这些都不正确?
            • 这取决于您的应用程序。 IE。你的问题看起来像'我有 List。我应该为 Set 使用什么类型参数?答案是“您应该使用“正确”参数。 “正确”的定义取决于具体情况。'
            猜你喜欢
            • 2017-04-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多