本地类型推断与实例

8

我尝试阅读关于本地类型推断的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的规定,它不支持这样做。现在我的问题是:如果一个声明为var的public/protected成员失败,至少在我看来是有道理的。但是即使它是private,为什么它也不能编译呢?我只能假设你仍然可以通过反射获取该变量(而我无法获取像这样的局部字段)...并且获取该变量需要进行转换,嗯,可能是非常困惑的转换。

2
访问修饰符和作用域最终被混合并质疑var类型推断。又是一个来自您的好问题。 :) - Naman
5个回答

21
禁止对字段和方法返回值进行类型推断的动机是为了保持API稳定性;在运行时,字段访问和方法调用与描述符相关联,因此如果某些导致推断类型微妙变化的事情导致实现中的更改,可能会导致现有编译客户端出现可怕的错误,而这种情况可以通过擦除来避免。因此,在实现中使用这个特性很有意义,但不适用于API,这是一个明智的指导原则。
有人合理地提出了问题:“那么私有字段和方法呢?”确实,我们也可以选择这样做。像所有的设计决策一样,这是一个权衡;它将使推断可以在更多的地方使用,以换取用户模型中的更多复杂性。(我不太关心规范或编译器中的复杂性;那是我们的问题。)对“本地变量是否推断”进行推理比添加各种附加考虑如“但是,如果它们是私有的,那么字段和方法是可以的”要容易得多。我们所画的线还意味着将字段或方法从私有更改为非私有的兼容性后果不会意外地与推断发生交互。
因此,简化语言而不显著降低功能的方式是这样做的答案。

6

各种原因:

  1. 可见性和类型是正交的 - 一个不应该影响另一个。如果私有变量可以用var初始化,那么当将它们改为受保护或公共时,你就需要更改它。

  2. 因为var使用右侧来推断类型,所以这样的私有字段总是需要立即初始化。如果将初始化移动到构造函数中,则必须显式指定类型。

  3. 使用var编译器可以推断出您当前无法在Java中表达的类型(例如交集类型,如Comparable&Serializable)。当然,您可能最终会依赖于这些特定类型,当您由于任何原因不得不停止使用var时,您可能需要重构很多代码以使其正常工作。


你是在暗示如果一个实例私有变量被声明在某个地方,它就可以像 var 一样使用吗?在这种情况下,似乎合理暗示 final 字段也可以这样声明 - 它们要么在构造函数中初始化,要么在原地初始化。 - Eugene
顺便说一下,如果在方法内部(本地)使用“var x;”将无法编译,那么为什么不对实例变量做同样的事情呢?我认为我们可能忽略了某些东西,暂时忽略变量的可见性。 - Eugene
1
我无法完全理解你的问题。为了澄清,在Java 10中绝对不可能存在var字段。在第二点中,我想说的是,如果这种情况是可能的(并且类型推断的工作方式与现在相同),那么这些字段必须立即初始化,因为var x;是不起作用的(在局部变量中现在也不起作用)。 - Nicolai Parlog

6

并不是完全不可能将这些变量转换为可以通过反射检查的字段。例如,您可以执行以下操作:

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<String>这样可以表示。为了说明不同的情况:
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&CharSequence还是#1 extends CharSequence&Appendable,因此不确定代码将打印java.lang.Appendable还是java.lang.CharSequence
我认为这对于合成字段并不是问题,但对于明确声明的字段可能会有影响。
然而,我怀疑专家组没有详细考虑这种影响。相反,从一开始就决定不支持字段声明(因此跳过了对其影响的深思熟虑),因为局部变量始终是该功能的目标。局部变量的数量比字段声明的数量高得多,因此减少局部变量声明的样板文件具有最大的影响。

我*真的很喜欢这个,它回答了我的问题,但是很难超越“架构师”。谢谢。 - Eugene

5
详细说明了Nicolai's answer(特别是他的第二个原因),JLS 10的草案指出,对于局部变量,var e;var g = null;都是非法的,这是有充分理由的;从右侧(或缺乏)无法清楚地推断出var的类型。

目前,非最终实例变量会根据其类型自动初始化(原语为0false,引用为null,我相信您已经知道)。除非在声明时或在其各自类的构造函数中初始化,否则实例变量的推断类型仍将不明确。

因此,我支持只有当变量既是private又是final时才允许使用var,以确保它在创建类时被初始化。但是,我无法说这将有多难实现。


4

在我看来,允许使用var作为私有字段是一个合理的决定。但是省略它会使该特性更简单。

此外,在本地类型推断积累更多经验之后,可以在未来的某个发布版本中添加它,而删除特性则要困难得多。


在这种情况下,反射怎么样?假设这是可能的,并且您获得了那个“var”,它仍然是一个需要转换的“Object”吗?我不知道... - Eugene
2
该字段将具有初始化程序给定的类型,因此 var x = new Object() 将是一个 Objectvar x = "" 将是一个 String,就像本地变量一样。反射总是会给您返回一个 Object,这不会改变。field.getType() 将返回推断出的类型。 - Alexey Romanov
+1,不幸的是,我仍然认为这不是我正在寻找的东西。在方法“var x;”中,由于明显的原因而无法编译,而“var x =”将会编译。因此,如果您将类型(右侧)定义为可以推断的某些内容,我认为实例变量也可以使用此方法。我真的认为这里有更深层次的问题。 - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接