【问题标题】:Local Type Inference vs Instance本地类型推断与实例
【发布时间】:2018-03-02 13:46:09
【问题描述】:

我试图扫描JEP-286 了解本地类型推断。我看到这仅适用于局部变量 - 理解。所以这确实有效:

public class TestClass {
    public static void main(String [] args){
        var list = new ArrayList<>();
        list.add("1");
        System.out.println(list.get(0)); // 1
    }  
}

另一方面,我确实看到这无法编译:

public class TestClass {
    public var list = new ArrayList<>();
    public static void main(String [] args){

    }
}

很明显它没有,因为 JEP 是这么说的。现在我的问题:

声明为varpublic/protected 成员失败是完全合理的,至少在 IMO 中是这样。但是为什么即使是private 也编译不出来呢?我只能假设您仍然可以通过反射获得该变量(并且我无法获得这样的本地字段)......并且获得该变量将需要一个演员,嗯,可能是一个非常混乱的演员。

【问题讨论】:

  • 访问修饰符和范围最终被混合并质疑var 类型推断。你提出的另一个好问题。 :)

标签: java type-inference local-variables java-10


【解决方案1】:

禁止对字段和方法返回进行类型推断的动机是 API 应该是稳定的;字段访问和方法调用在运行时由描述符链接,因此如果对实现的更改导致推断类型发生变化(模擦除),导致推断类型发生细微变化的事情可能会导致现有编译客户端以可怕的方式中断。所以使用这是一个明智的指导原则,用于实现,但不适用于 API。

问“那么,私有字段和方法呢?”是合理的。事实上,我们很可能会选择这样做。像所有设计决策一样,这是一个权衡;它将使推理能够在更多地方使用,以换取用户模型的更多复杂性。 (我不太关心规范或编译器的复杂性;那是我们的问题。)推理“局部变量是,字段和方法否”比添加各种周转考虑因素(例如“但是,字段”)更容易推理如果它们是私有的,则方法是可以的”。在我们所做的地方画线也意味着将字段或方法从私有更改为非私有的兼容性后果不会与推理发生意外交互。

所以简短的回答是,这样做可以使语言更简单,而不会显着降低功能的用处。

【讨论】:

    【解决方案2】:

    各种原因:

    1. 可见性和类型是正交的 - 一个不应影响另一个。如果私有变量可以用 var 初始化,那么在将它们设为受保护或公开时,您必须更改它。

    2. 因为var 使用右侧推断类型,所以总是需要立即初始化此类私有字段。如果将初始化移到构造函数中,则必须明确类型。

    3. 使用var,编译器可以推断出您目前无法用Java 表达的类型(例如,像Comparable &amp; Serializable 这样的交集类型)。当然,您最终可能会依赖这些特定类型,并且当您出于某种原因不得不在某个时候停止使用 var 时,您可能需要进行大量重构以保持代码正常工作。

    【讨论】:

    • 您是否暗示 IFF 将在适当位置声明一个实例私有变量,它可以用作var?在这种情况下,暗示(也)可以这样声明final 字段似乎是合理的——它们要么在构造函数中初始化,要么就地初始化。
    • 顺便说一句,如果是这种情况......在方法(本地)var x; 内部不会编译,那么为什么不对实例变量做同样的事情呢?我认为我们可能在这里遗漏了一些东西,认为我们暂时忽略了变量的可见性
    • 我不太明白你的问题。澄清一下,var 字段在 Java 10 中在任何情况下都是不可能的。在 2. 我想说 if 是可能的(并且类型推断会像现在这样工作),这些字段必须立即初始化,因为 var x; 不起作用(它现在在局部变量中不起作用)。
    【解决方案3】:

    将这些变量转换为可以通过反射检查的字段并不是完全不可能的。例如,你可以这样做

    var l = new ArrayList<String>();
    l.add("text");
    System.out.println(l);
    System.out.println(
      new Object(){ { var x = l; } }.getClass().getDeclaredFields()[0].getGenericType()
    );
    

    在当前版本中,它只是打印ArrayList,因此实际的泛型类型并没有存储在匿名内部类的类文件中,这不太可能改变,因为支持这种内省并不是实际的目标。这也只是一个特例,类型可表示为ArrayList&lt;String&gt;。为了说明不同的情况:

    var acs = true? new StringBuilder(): CharBuffer.allocate(10);
    acs.append("text");
    acs.subSequence(1, 2);
    System.out.println(
      new Object(){ { var x = acs; } }.getClass().getDeclaredFields()[0].getGenericType()
    );
    

    acs 的类型是AppendableCharSequence 的交集类型,通过在其上调用任一接口的方法来证明,但由于未指定编译器推断是#1 extends Appendable&amp;CharSequence 还是@987654329 @,未指定代码是打印java.lang.Appendable还是java.lang.CharSequence

    我不认为这是合成字段的问题,但对于显式声明的字段,它可能是。

    但是,我怀疑专家组是否详细考虑了这些影响。相反,从一开始就决定不支持字段声明(因此跳过冗长的含义思考),因为局部变量始终是该功能的预期目标。局部变量的数量远高于字段声明的数量,因此减少局部变量声明的样板文件具有最大的影响。

    【讨论】:

    • 真的喜欢这个,它回答了我的问题,但很难击败“建筑师”。谢谢
    【解决方案4】:

    详细阐述Nicolai's answer(特别是他的#2 原因),JLS 10 的拟议草案指出var e;var g = null; 对于局部变量都是非法的,并且有充分的理由;从右侧(或缺少右侧)不清楚要推断 var 的类型。

    目前,非最终实例变量会根据它们的类型自动初始化(0false 的原语,以及对null 的引用,我相信你已经知道了)。实例变量的推断类型仍不清楚,除非它在声明时或在其各自类的构造函数中初始化。

    因此,我支持仅当变量同时为privatefinal 时才允许使用var,这样我们可以确保在创建类时初始化它。不过,我不能说实现起来有多么困难。

    【讨论】:

      【解决方案5】:

      允许 var 用于私有字段 (IMO) 是一个合理的决定。但是省略它会使功能更简单。

      在对仅本地类型推断有更多经验之后,它还可以在未来的某个版本中添加,而删除功能则要困难得多。

      【讨论】:

      • 在这种情况下反射呢?假设这是可能的并且你得到了var,它仍然是你需要投射的Object吗?我不知道...
      • 该字段将具有初始化程序给定的类型,因此var x = new Object() 将是Objectvar x = "" 将是String,就像使用局部变量一样。反射总是给你一个Object,那里什么都不会改变。 field.getType() 将返回推断的类型。
      • +1,不幸的是,我仍然认为这不是我想要的东西。在 var x; 方法内部由于显而易见的原因不会编译,而 var x = "" 会。因此,如果您将类型(右侧)定义为可以推断的 something,我认为这对实例变量不起作用。我真的认为这是更深层次的东西
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-29
      • 1970-01-01
      • 2023-03-05
      • 1970-01-01
      • 1970-01-01
      • 2017-03-12
      • 2019-06-27
      相关资源
      最近更新 更多