Advertisement
  1. Code
  2. Java 8
Code

适用于Android开发的Java 8:默认和静态方法

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Java 8 for Android Development.
Java 8 for Android: Cleaner Code With Lambda Expressions
Java 8 for Android Development: Stream API and Date & Time Libraries

Chinese (Simplified) (中文(简体)) translation by Zhang Xiang Liang (you can also view the original English article)

Java 8是编程语言向前迈进的一大步,现在随着Android Studio 3.0的发布,Android开发人员终于可以获得对Java 8最重要特性的内置支持。

在这个由三部分组成的系列中,我们已经探索了Java 8的功能,你可以开始使用你的Android项目今天。在Cleaner代码中使用Lambda表达式,在深入研究lambda表达式之前,我们将开发设置为使用Android默认工具链提供的Java 8支持。

在这篇文章中,我们将介绍两种不同的方式,您可以在接口中声明非抽象方法(这在早期版本的Java中是不可行的)。我们还将回答现在接口可以实现方法的问题,抽象类和接口之间究竟有什么区别?

我们还将介绍Java 8功能,该功能可让您自由地在同一位置使用相同的注释,并保持向后兼容早期版本的Android。

但首先,我们来看一下Java 8特性,该特性旨在与我们在前一篇文章中看到的lambda表达式结合使用  。

使用方法参考编写Cleaner Lambda表达式

在上一篇文章中,您看到了如何使用lambda表达式从Android应用程序中删除大量样板代码。但是,当lambda表达式仅调用一个已有名称的单个方法时,可以使用方法引用来减少项目中的更多代码。

例如,这个lambda表达式实际上只是将工作重定向到现有handleViewClick方法:

在这种情况下,我们可以使用::方法引用操作符通过名称引用此方法。您可以使用以下格式创建这种方法参考:

在我们的Floating Action Button示例中,我们可以使用方法引用作为lambda表达式的主体:

请注意,被引用的方法必须采用与接口相同的参数 - 在这种情况下,就是View

您可以使用方法引用运算符(::)来引用以下任何一项:

静态方法

如果你有一个调用静态方法的lambda表达式:

然后你可以把它变成一个方法参考:

例如,如果你PrintMessage在一个MyClass类中有一个静态方法,那么你的方法引用看起来像这样:

一个特定对象的实例方法

这是一个事先知道的对象的实例方法,允许您替换:

附:

所以,如果你有以下lambda表达式:

然后引入一个方法引用会给你以下内容:

一种特定类型任意对象的实例方法

这是稍后将提供的任意对象的实例方法,并以以下格式写入:

构造函数引用

除了使用关键字new来调用构造函数外,构造函数的引用与方法引用类似。例如,Button::new是该类的构造函数引用Button,但调用的确切构造函数取决于上下文。

使用构造函数引用,你可以打开:

成:

例如,如果您有以下MyInterface界面:

然后你可以使用构造函数引用来创建新的Student实例:

也可以为数组类型创建构造函数引用。例如,一个ints 数组的构造函数引用  是int[]::new

将默认方法添加到您的接口

在Java 8之前,只能在接口中包含抽象方法(即没有正文的方法),这使得难以在接口发布后发布。

每次向接口定义添加方法时,实现此接口的任何类都会突然丢失实现。例如,如果你有一个MyInterface被使用过的接口(MyClass),然后添加一个方法来MyInterface破坏兼容性MyClass

在最好的情况下,你负责使用少量的类MyInterface,这种行为会令人讨厌但易于管理 - 你只需要留出一些时间来用新的实现更新你的类。然而,事情可能会变得很大,如果实施了大量的类更复杂MyInterface,或者如果界面中,你是不负责的班级使用。

虽然这个问题有很多解决方法,但都不是理想的。例如,您可以在抽象类中包含新方法,但这仍然需要每个人都更新其代码以扩展此抽象类; 而且,尽管可以用新界面扩展原始界面,但任何想要使用这些新方法的人都需要重写所有现有界面引用。

通过在Java 8中引入默认方法,现在可以在接口中声明非抽象方法(即带有主体的方法),以便最终为您的方法创建默认实现。

当您将方法作为默认方法添加到接口时,实现此接口的任何类都不一定需要提供它自己的实现,这使您可以在不中断兼容性的情况下更新接口。如果您将新方法作为默认方法添加到接口中,那么使用此接口但不提供其自己实现的每个类都将简单地继承该方法的默认实现。由于类不缺少实现,它将继续正常工作。 实际上,默认方法的引入是Oracle能够在Java 8中对Collections API进行如此大量的添加的原因。

实际上,默认方法的引入是Oracle能够在Java 8中对Collections API进行如此大量的添加的原因。

Collection是一个在许多不同类中使用的通用接口,因此向该接口添加新方法有可能破坏无数代码行。与其向Collection接口添加新方法并打破从此接口派生的每个类,Oracle创建了默认方法功能,然后将这些新方法作为默认方法添加。如果你看看新的Collection.Stream()方法(我们将在第三部分详细探讨),你会发现它被添加为默认方法: 如果你看看新的Collection.Stream()方法(我们将在第三部分详细探讨),你会发现它被添加为默认方法:

创建一个默认方法很简单 - 只需将default修饰符添加到方法签名中即可:

现在,如果MyClass使用MyInterface但不提供它自己的实现defaultMethod,它只会继承由提供的默认实现MyInterface。例如,以下类仍将编译:

实现类可以覆盖接口提供的默认实现,所以类仍然完全控制它们的实现。

虽然缺省方法对于API设计者来说是一个受欢迎的补充,但它们偶尔会为试图在同一个类中使用多个接口的开发人员造成问题。 

想象一下,除此之外MyInterface,你还有以下几点:

双方MyInterface并SecondInterface包含一个默认的方法完全相同的签名(defaultMethod)。现在想象你尝试在同一个类中使用这两个接口:

在这一点上,你有两个相互冲突的实现defaultMethod,编译器不知道应该使用哪种方法,所以你会遇到编译器错误。

解决此问题的一种方法是使用您自己的实现覆盖冲突的方法:

另一种解决方案是defaultMethod使用以下格式来指定要执行的版本:

所以如果你想调用MyInterface#defaultMethod()实现,那么你会使用以下内容:

在Java 8界面中使用静态方法

与默认方法类似,静态接口方法为您提供了一种在接口内定义方法的方法。但是,与默认方法不同,实现类不能覆盖接口的静态方法。

如果你有一个特定于接口的静态方法,那么Java 8的静态接口方法为你提供了一种将这些方法放入相应接口的方法,而不必将它们存储在一个单独的类中。

您可以通过将关键字static放在方法签名的开头创建静态方法,例如:

当您实现包含静态接口方法的接口时,该静态方法仍然是接口的一部分,并且不会由实现它的类继承,因此您需要使用接口名称为该方法添加前缀,例如:

这也意味着类和接口可以具有相同签名的静态方法。例如,在同一个类中使用MyClass.staticMethod和MyInterface.staticMethod不会导致编译时错误。

那么,接口本质上只是抽象类?

静态接口方法和默认方法的增加导致一些开发人员质疑Java接口是否变得更像抽象类。但是,即使添加了默认和静态接口方法,接口和抽象类仍然存在一些显着差异:

  • 抽象类可以有最终的,非最终的,静态的和非静态的变量,而一个接口只能有静态变量和最终变量。
  • 抽象类允许你声明非静态和最终的字段,而接口的字段本质上是静态的和最终的。
  • 在接口中,您声明或定义为默认方法的所有方法都是天生公开的,而在抽象类中,您可以定义公共,受保护和私有的具体方法。
  • 抽象类是类,因此可以具有状态; 接口不能有与之相关的状态。
  • 您可以在抽象类中定义构造函数,这在Java接口内是不可能的。
  • Java只允许你扩展一个类(不管它是否抽象),但是你可以自由地实现尽可能多的接口。这意味着当你需要多重继承时,接口通常具有优势,尽管你需要小心死亡的致命钻石

根据需要多次应用相同的注释

传统上,Java注释的局限之一在于,您不能在同一位置多次应用相同的注释。尝试多次使用相同的注释,并且您将遇到编译时错误。

但是,随着Java 8重复注释的引入,您现在可以在同一位置随意多次使用相同的注释。

为了确保您的代码与早期版本的Java保持兼容,您需要将重复注释存储在容器注释中。

您可以通过完成以下步骤来告诉编译器生成此容器:

  • @Repeatable meta-annotation(用于注释注释的注释)标记问题中的注释。例如,如果你想使@ToDo注解可重复,你可以使用: 例如,如果你想使@ToDo注解可重复,你可以使用:@Repeatable(ToDos.class)。括号中的值是编译器最终生成的容器注释的类型。
  • 声明包含注释类型。这必须具有重复注释类型数组的属性,例如:

尝试多次应用相同的注释而不首先声明它是可重复的将在编译时导致错误。但是,一旦您指定这是可重复注释,您可以在使用标准注释的任何位置多次使用此注释。

结论

在我们的Java 8系列的第二部分中,我们看到了如何通过将lambda表达式与方法引用相结合,以及如何使用默认方法和静态方法来增强接口,从而减少Android项目中更多的样板代码。

在第三部分也是最后一部分中,我们将看到一个新的Java 8 API,它允许您以更高效的声明方式处理大量数据,而不必担心并发和线程管理。我们还将通过探索函数接口在lambda表达式,静态接口方法,默认方法等中扮演的角色,将我们在本系列中讨论的一些不同功能捆绑在一起。

最后,即使我们仍然在等待Java 8的新日期和时间API正式到达Android,我将展示如何在今天的Android项目中开始使用这个新的API,并在第三方的帮助下库。

同时,查看一些关于Java和Android应用程序开发的其他文章!

Envato qr branded
关注我们的公众号
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.