Java 10如何改变您使用匿名内部类的方式

正如您已经注意到的,Friends在6月底将启动许多新的小组,其中包括下一个大家都喜欢的Java开发人员课程。 目前,我们正在与您分享为该课程的学生准备的新翻译。



在向编程语言添加新功能时,语言开发人员通常会注意与该语言当前功能的冲突,导致与先前版本不兼容的更改,错误以及可能导致未定义或意外行为的任何情况。

但是,通常他们对新的实用代码编写技术中的微小变化没有给予足够的重视。 这些更改通常是新功能的副作用。 严格来说,这种变化不是新功能。 这些是由于其他功能或其组合而出现的细微变化。

匿名内部类


在Java中,内部类是定义为类成员的类。 内部类有四种:

  • 静态嵌套
  • 内部的
  • 本地(本地方法)
  • 匿名的

匿名内部类是未命名的类,可提供现有类的实现。 此类广泛用于处理面向事件的编程中的事件。 通常,匿名内部类提供抽象类的即时实现。 但是,这不是必需的。 可以从非抽象类创建匿名内部类。

我认为,对于匿名内部类而言,有一点还没有完全理解。 事实是程序员实际上创建了源类的子类 。 该子类的名称为Class$X ,其中Class是外部类,而X是表示在外部类中创建内部类实例的顺序的数字。 例如, AnonDemo$3是在AnonDemo创建的第三个内部类。 您不能以通常的方式调用这些类。 而且,与其他类型的内部类不同,匿名内部类始终隐式地是基于其创建类型的子类(除了使用var ,我们将在后面介绍)。
让我们来看一个例子。

 /* AnonDemo.java */ class Anon { }; public class AnonDemo { public static void main (String[] args) { Anon anonInner = new Anon () { public String toString() { return "Overriden"; }; public void doSomething() { System.out.println("Blah"); }; }; System.out.println(anonInner.toString()); anonInner.doSomething(); //  ! }; }; 

在此示例中,我们基于Anon类实例化了一个匿名内部类。 本质上,我们创建了特定类的未命名子类 。 在Java 10之前,匿名内部类几乎总是隐式多态的。 我说“几乎”是因为这样的非多态代码当然会执行。

 new Anon() { public void foo() { System.out.println("Woah"); } }.foo(); 

但是,如果我们想将创建匿名内部类的实例的结果分配给原始类型,那么这种操作本质上将是多态的。 这样做的原因在于,我们隐式地创建了一个类的子类,该类被指定为匿名内部类的源,而我们将无法访问特定的对象类型( Class$X )以在源代码中对其进行指定。

多态和匿名内部类,实际结果


您注意到上面的代码了吗? 由于我们使用对子类对象的基类引用,因此根据多态性定律,我们只能引用1)由基类定义的方法或2)子类中重写的虚方法。

因此,在前面的代码片段中,为匿名内部类对象调用toString()将为我们提供重写的“ Overridden”值,但是,调用doSomething()会导致编译错误。 是什么原因

引用基类类型的子类对象无法通过对该基类的引用来访问子类的成员。 该规则的唯一例外是子类是否覆盖基类方法。 在这种情况下,Java具有其多态性,因此在运行时使用动态方法分派来选择虚拟子类方法的版本。

如果您还不知道,则虚拟方法是可以覆盖的方法。 在Java中,默认情况下,所有非最终,非私有和非静态方法都是虚拟的。 我默认情况下而不是暗中讲,因为不同的jvm可以执行优化来更改此设置。

Java 10与它有什么关系?


一个小的功能称为类型推断。 看下面的例子:

 /* AnonDemo.java */ class Anon { }; public class AnonDemo { public static void main(String[] args) { var anonInner = new Anon() { public void hello() { System.out.println( "New method here, and you can easily access me in Java 10!\n" + "The class is: " + this.getClass() ); }; }; anonInner.hello(); // !! } } 

它有效,我们可以调用hello() ! 细节在于魔鬼。 如果您熟悉var ,那么您已经了解这里发生了什么。 使用类型为var的保留名称, Java能够确定匿名内部类的确切类型。 因此,我们不再局限于引用基类来访问子类对象。

当需要引用子类时,在Java 10之前我们做了什么?


在遥远但未被遗忘的过去,关于内层阶级的激烈辩论已不是什么秘密。 如果这是一个秘密,那么这就是世界上最糟糕的秘密之一。 毫无疑问,许多人都不赞成您需要精确引用匿名内部类这一事实,因为这样做的目的是避免在类中添加垃圾。 对于在逻辑上与另一个类相关的操作,例如用于处理事件,应使用匿名类即时执行合同。 但是,也许好奇的芭芭拉并没有从她的鼻子上撕下来,我敢打赌,大多数开发人员都很好奇。 也许有损常识!

在Java 10之前,我们可以使用反射实现类似的效果,如下所示:

 Anon anonInner2 = new Anon() { public void hello() { System.out.println("Woah! "); }; }; anonInner2.getClass().getMethod("hello").invoke(anonInner2); 

全文


可以在这里

 public class VarAnonInner { public static void main (String[] args) throws Exception { var anonInner = new Anon() { public void hello() { System.out.println("New method here, and you can easily access me in Java 10!\n" + "The class is: " + this.getClass() ); }; }; anonInner.hello(); Anon anonInner2 = new Anon() { public void hello() { System.out.println("Woah! "); }; }; anonInner2.getClass().getMethod("hello").invoke(anonInner2); new Anon() { public void hello() { System.out.println("Woah!!! "); }; }.hello(); // VarAnonInner$1 vw = anonInner; /* Anon anonInner4 = new Anon() { public void hello() { System.out.println("New method here!\n" + "The class is: " + this.getClass() ); }; }; anonInner4.hello(); */ } } class Anon { }; 

我们正在等待您的评论,并回顾今天将在20.00举行免费的网络研讨会

Source: https://habr.com/ru/post/zh-CN456010/


All Articles