AS3.0中的J8新特性

澳门新葡亰平台游戏网站 1

本文由码农网 –
civic5216原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

1.简述

在Java8之前,Java程序接口是将相关方法按照约定组合到一起的方式。实现接口的类必须为接口中定义的每个方法提供一个实现,或者从父类中继承它的实现。但是,一旦类库的设计者需要更新接口,向其中加入新的方法,这种方式就会出现问题。现实情况是,现存的实体类往往不在接口设计者的控制范围之内,这些实体类为了适配新的接口约定也需要进行修改。由于Java8的API在现存的接口上引入了非常多的新方法,这种变化带来的问题也愈加严重。

在Java8中为了解决这个问题引入了一种新的机制。Java8中的接口现在支持在声明方法的同时提供实现。有两种方式可以完成这种操作。其一,Java8允许在接口内声明静态方法。其二,Java8引入了一个新功能,叫默认方法。通过默认方法,即使实现接口的方法也可以自动继承默认的实现,你可以让你的接口可以平滑地进行接口的进化和演进。比如我们的List接口中的sort方法是java8中全新的方法,定义如下:

default void sort(Comparator<? super E> c){
    Collections.sort(this, c);
}

在方法有个default修饰符用来表示这是默认方法。

今天google官方放出了期待已久的AS3.0正式版,加入了很多新特性,支持kotlin,java8,编译速度更快等等,想必很多kotlin小迷弟,小迷妹们已经欢呼雀跃。然鹅,菜鸡如我,不会使用kotlin。只能研究一下作为android开发者在AS3.0中可以使用到的java8新特性。

这篇文章我们将要探讨Java 8中接口里的默认方法特性。Java8指出“默认方法使得新功能被添加到库中的接口里面,同时又能保证与这些接口老版本代码的二进制兼容性。”

2.进化的API

为了理解为什么一旦API发布之后,它的演进就变得非常困难,我们假设你是一个流行Java绘图库的设计者(为了说明本节的内容,我们做了这样的假想)。你的库中包含了一个Resizable接口,它定义了一个简单的可缩放形状必须支持的很多方法,比如:setHeight、
setWidth、getHeight、getWidth以及setAbsoluteSize。此外,你还提供了几个额外的实现(out-of-boximplementation),如正方形、长方形。由于你的库非常流行,你的一些用户使用Resizable接口创建了他们自己感兴趣的实现,比如椭圆。

发布API几个月之后,你突然意识到Resizable接口遗漏了一些功能。比如,如果接口提供一个setRelativeSize方法,可以接受参数实现对形状的大小进行调整,那么接口的易用性会更好。你会说这看起来很容易啊:为Resizable接口添加setRelativeSize方法,再更新Square和Rectangle的实现就好了。不过,事情并非如此简单!你要考虑已经使用了你接口的用户,他们已经按照自身的需求实现了Resizable接口,他们该如何应对这样的变更呢?非常不幸,你无法访问,也无法改动他们实现了Resizable接口的类。这也是Java库的设计者需要改进JavaAPI时面对的问题。让我们以一个具体的实例为例,深入探讨修改一个已发布接口的种种后果。

官方对于AS中J8新特性的页面如下Use Java 8 Language
Features(请自备红杏)

这些年Java进化升级了很多,在Java库中引入的接口需要添加新的功能。在没有默认方法特性时,当你往接口中添加新方法时,接口内部所有实现的类都要历经一些修改。这将导致上千行的代码修改工作量。为了避免这点,Java
8引入了默认对象方法。亦即,如果你想要往现存的接口中添加任何功能,你只需添加默认方法特性而不会影响接口实现形式。

2.1初始化版本的API

Resizable最开始的版本如下:

public interface Resizable{
    int getWidth();
    int getHeight();
    void setWidth(int width);
    void setHeight(int height);
    void setAbsoluteSize(int width, int height);
}

这时候有一位用户实现了你的Resizable接口,创建了Ellipse类:

public class Ellipse implements Resizable {
    @Override
    public int getWidth() {
        return 0;
    }

    @Override
    public int getHeight() {
        return 0;
    }

    @Override
    public void setWidth(int width) {

    }

    @Override
    public void setHeight(int height) {

    }

    @Override
    public void setAbsoluteSize(int width, int height) {

    }
}

附上gradle-4.1-all.zip地址

让我们看一些例子来更好的理解它。例如,我声明了一个具有打开和读取功能的接口“BookIterface”。接口的类需要实现打开和读取方法。

2.2第二版本API

库上线使用几个月之后,你收到很多请求,要求你更新Resizable的实现,所以你更新了一个方法。

public interface Resizable{
    int getWidth();
    int getHeight();
    void setWidth(int width);
    void setHeight(int height);
    void setAbsoluteSize(int width, int height);
    void setRelativeSize(int wFactor, int hFactor);//第二版本API
}

接下来用户便会面临很多问题。首先,接口现在要求它所有的实现类添加setRelativeSize方法的实现。但我们刚才的用户最初实现的Ellipse类并未包含setRelativeSize方法。向接口添加新方法是二进制兼容的,这意味着如果不重新编译该类,即使不实现新的方法,现有类的实现依旧可以运行。但是这种情况少之又少,基本项目每次发布时都会重新编译,所以必定会报错。

最后,更新已发布API会导致后向兼容性问题。这就是为什么对现存API的演进,比如官方发布的Java.Collection.API,会给用户带来麻烦。当然,还有其他方式能够实现对API的改进,但是都不是明智的选择。比如,你可以为你的API创建不同的发布版本,同时维护老版本和新版本,但这是非常费时费力的,原因如下。其一,这增加了你作为类库的设计者维护类库的复杂度。其次,类库的用户不得不同时使用一套代码的两个版本,而这会增大内存的消耗,延长程序的载入时间,因为这种方式下项目使用的类文件数量更多了。

这就是我们默认方法所要做的工作。它让我们的类库设计者放心地改进应用程序接口,无需担忧对遗留代码的影响。

配置Java8

package org.smarttechie;
/**
* The interface is intended to open and read. The implementors should implement the methods to open and read.
* @author Siva Prasad Rao Janapati
*
*/
public interface BookInterface {
/**
* The method opens the book
*/
public void openTheBook();
/**
* The method reads the book
*/
public void readTheBook();
}

3.详解默认方法

经过前述的介绍,我们已经了解了向已发布的API添加方法,会对我们现存的代码会造成多大的危害。默认方法是Java8中引入的一个新特性,依靠他我们可以在实现类中不用提供实现。
我们要使用我们的默认方法非常简单,只需要在我们要实现的方法签名前面添加default修饰符进行修饰,并像类中声明的其他方法一样包含方法体。如下面的接口一样:

public interface Sized {
    int size();
    default boolean isEmpty(){
        return size() == 0;
    }
}

这样任何一个实现了Sized接口的类都会自动继承isEmpty的实现。

首先需要抛弃之前的第三方插件

Android Studio为使用某些Java
8语言功能和使用它们的第三方库提供内置支持。,默认工具链通过desugar在javac编译器的输出上执行字节码转换来实现新的语言特性。

默认工具链

在3.0之前,相信很多小伙伴为了尝试java8的新特性,会使用第三方的插件来使用lambda,如:Jack,
Retrolambda, 或
DexGuard,AS3.0将对其不再提供支持,如果as检测到你继续使用这些插件,as将继续使用第三方插件,而不使用默认的工具链。

澳门新葡亰平台游戏网站,现在,我们提供上面接口的实现代码。

3.1默认方法的使用模式

删除Jack支持

jackOptionsbuild.gradle删除

  android {
      ...
      defaultConfig {
      ...
    // 删除此代码块
        jackOptions {
            enabled true
              ...
        }
}
package org.smarttechie;
/**
* The JavaBookImpl is the implementation of BookInterface
* @author Siva Prasad Rao Janapati
*
*/
public class JavaBookImpl implements BookInterface {
/**
* This opens the book
*/
@Override
public void openTheBook() {
System.out.println("The Java book is opened");
}

/**
* This reads the book
*/
@Override
public void readTheBook() {
System.out.println("Reading the Java book");
 }
}

3.1.1可选方法

你有时候会碰到这种情况,类实现了接口,不过却可以将一些方法的实现留白。比如我们Iterator接口,我们一般不会去实现remove方法,经常实现都会留白,在Java8中为了解决这种办法回味我们的remove方法添加默认的实现,如下:

public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

通过这种方式,我们可以减少无效的模板代码。实现Iterator接口的每一个类都不需要再次实现remove的模板方法了。

删除Retrolambda

请从项目级build.gradle文件中删除Retrolambda依赖关系

buildscript {
  ...
   dependencies {
      // 删除此依赖
      classpath 'me.tatarka:gradle-retrolambda:<version_number>'
   }
}

并删除每个moudle下的build.gradle文件中Retrolambda引入

//删除
apply plugin: 'me.tatarka.retrolambda'
//删除(如果有的话)
retrolambda {
    jvmArgs '-Xmx2048m'
}

现在,我们想要给接口提供一个关闭功能。如果你直接添加关闭功能到book接口中,现存的实现类需要历经一些修改。有了默认方法特性后,我们能给book接口直接添加关闭功能。默认方法对所有实现都可用。

3.1.2多继承

默认方法让之前的Java是不支持多继承,但是默认方法的出现让多继承在java中变得可能了。

Java的类只能继承单一的类,但是一个类可以实现多接口。要确认也很简单,下面是Java
API中对ArrayList类的定义:

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable,
Serializable, Iterable<E>, Collection<E> {
}

设置Java8

build.gradle文件下android节点添加如下

android {
  ...
  //将项目的源和目标兼容性值设置为Java 8
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}
package org.smarttechie;
/**
* The interface is intended to open and read. The implementors should implement the methods to open and read.
* @author Siva Prasad Rao Janapati
*
*/
public interface BookInterface {
/**
* The method opens the book
*/
public void openTheBook();
/**
* The method reads the book
*/
public void readTheBook();
/**
* The default method implementation
*/
public default void closeTheBook() {
System.out.println("Closting the book");
 }
}

package org.smarttechie;
/**
 * The JavaBookImpl is the implementation of BookInterface
 * @author Siva Prasad Rao Janapati
 *
 */
public class JavaBookImpl implements BookInterface {
 /**
 * This opens the book
 */
 @Override
 public void openTheBook() {
 System.out.println("The Java book is opened");
 }
 /**
 * This reads the book
 */
 @Override
 public void readTheBook() {
 System.out.println("Reading the Java book");
 }
 public static void main (String[] args) {
 BookInterface bookInter = new JavaBookImpl();
 //Call the default method declared in BookInterface
 bookInter.closeTheBook();
 JavaBookImpl book = new JavaBookImpl();
 book.closeTheBook();
 }
}

3.1.3冲突问题

我们知道Java语言中一个类只能继承一个父类,但是一个类可以实现多个接口。随着默认方法在Java8中引入,有可能出现一个类继承了多个方法而它们使用的却是同样的函数签名。这种情况下,类会选择使用哪一个函数?在实际情况中,虽然这样的冲突很难发生,但是一旦发生,就必须要规定一套约定来处理这些冲突。这一节中,我们会介绍Java编译器如何解决这种潜在的冲突。

public interface A {
    default void hello(){
        System.out.println("i am A");
    }
}
interface B extends A{
    default void hello(){
        System.out.println("i am B");
    }
}
class C implements A,B{
    public static void main(String[] args) {
        new C().hello();
    }
}

上面的代码会输出i am B。为什么呢?我们下面有三个规则:

  1. 类中的方法优先级最高。类或父类中的声明的方法的优先级高于任何声明为默认方法的优先级。
  2. 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口。如果B继承了A,那么B就比A的更具体。
  3. 最后,如果还是无法判断,继承了多个接口的类必须通过显示覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。

接下来举几个例子

public interface A {
    default void hello(){
        System.out.println("i am A");
    }
}
interface B extends A{
    default void hello(){
        System.out.println("i am B");
    }
}
class D implements A{
    public void hello(){
        System.out.println("i am D");
    }
}
class C extends D implements A,B{
    public static void main(String[] args) {
        new C().hello();
    }
}

上面会输出D,遵循我们的第一条原则,类中的方法优先级最高。

public interface A {
    default void hello(){
        System.out.println("i am A");
    }
}
interface B {
    default void hello(){
        System.out.println("i am B");
    }
}

class C implements A,B{
    public static void main(String[] args) {
        new C().hello();
    }
}

上面代码会出现编译错误:Error:(19, 1) java: 类 java8.C从类型 java8.A 和
java8.B 中继承了hello()
的不相关默认值,这个时候必须利用第三条,显式得去调用父类的接口:

class C implements A,B{
    public void hello(){
        B.super.hello();
    }
    public static void main(String[] args) {
        new C().hello();
    }
}

AS中的Java8新特性

放一张官方的资料图:

image.png

可以看到新版AS中指支持部分Java8的新特性,且有一部分还有minSdkVersion限制。接下来,我们对其比较常用的进行讲解:

下面给出了上述调用方法的字节码。从字节码中,我们可以认为默认方法是一种“虚方法”。

Lambda

相信大家已经lambda已经非常熟悉,这里我们不讲语法,讲点别的需要注意的地方。

澳门新葡亰平台游戏网站 1

与匿名内部类的区别

lambda大家看作是一种匿名内部类,但实际却与匿名内部类是有区别的,

  • lambda编译完成之后,使用的是J7新增的指令invokedynamic来表示,具体的实现不体现在字节码文件中,由虚拟机进行翻译lambda的具体执行策略,以便于后续JDK版本优化此语法特性的策略,同时可以很大程度上减少字节码的文件的大小和数量,分配方式类似于java.lang.invokeMethodHandle中的动态调用方法(具体请百度)。目前lambda的具体执行方式还是与匿名内部类差不多的策略,据说oracle在以后的版本更新中会优化分配测罗,选用MethodHandle
  • 而匿名内部类方式生成新类,新建一个标有$xx.class的文件。

如果你想,你可以重载实现类中的默认方法:

分类

lambda表达式的类型是一种特殊的接口,这种接口只有一个抽象方法,成为函数接口。

在J8中,lambda表达式倾向于函数式编程,根据其输入与返回类型不同,分为几种类型的函数接口,下面是系统提供的一组函数式接口类型,并以Rxjava的操作符举例:

接口 参数 返回类型 说明
Predicate<T> T boolean 判断型函数,filter
Consumer<T> T void 消费型函数,subcribe
Function<T,R> T R 转换型函数,map
Supplier<T> None T 生产型函数,create
UnaryOperator<T> T T Function的特殊情况
BinaryOperator<T> (T,T) T (二元)多元运算,zip

我们可以同过自定义接口进行使用,如android的View.OnclickListener极为消费性函数。

注意,在自定义函数接口时,站在用途的角度来考虑,为了区分普通接口与函数接口的区别,建议自定的函数接口都应该加上@FunctionalInterface注解。该直接会强制编译器检查一个接口是否符合函数接口的标准。如果该注释添加给一个枚举类型,类或者接口包含不止一个抽象方法,编译器就会报错。这对我们规范化代码有很大的帮助。

package org.smarttechie;
/**
 * The JavaBookImpl is the implementation of BookInterface
 * @author Siva Prasad Rao Janapati
 *
 */
public class JavaBookImpl implements BookInterface {
 /**
 * This opens the book
 */
 @Override
 public void openTheBook() {
 System.out.println("The Java book is opened");
 }
 /**
 * This reads the book
 */
 @Override
 public void readTheBook() {
 System.out.println("Reading the Java book");
 }
 /*
 * This closes the book
 */
 public void closeTheBook() {
 System.out.println("Closing the JAVA book");
 }
 public static void main (String[] args) {
 BookInterface book = new JavaBookImpl();
 book.closeTheBook();
 }
}

重载解析

,在运行过程中,会根据,java中可以重载方法,造成多个方法会有相同的方法名,但签名却不一样。这在推断类型是会带来问题,因为系统可能会推断出多种方法类型。当lambda表达式作为参数时,当出现重载情况时,这时,

java会挑出最具体的类型。

看一个例子:

static interface IntergerBifunciton extends BinaryOperator<Integer>{
    }
    public  void overloadedMethoe(BinaryOperator<Integer> l){
    }
    public  void overloadedMethoe(IntergerBifunciton l){
    }

接口IntergerBifuncitonBinaryOperator的子接口,在调用overloadedMethoe((x,y)->x+y)的时候,虚拟机会选用最具体的类型

再看一个无法推断哪个更具体类型的例子:

    static interface Intergerfunciton{
        public boolean test(int x);
    }
    public  void overloadedMethoe(Predicate<Integer> l){
    }
    public  void overloadedMethoe(Intergerfunciton l){
    }

我们使用overloadedMethoe((x)->true);调用的时候,会出现错误:

模糊调用

这是因为,编译器无法推断出哪种类型更具体
更适合方法的参数,故报错。我们在日常开发中应该避免出现这种情况。

到这会儿,你可能有一个疑问,如果我们实现了两个具有同样默认方法签名的接口会怎样?在那种情况下,调用实现会得到下面的编译错误提示。

Default and static interface methods(默认方法和静态接口方法)

  • 默认方法
    java8相比之前最大的改动无疑是对集合类的改动和添加StreamAPI(minSdkVersion为24可用),用于在集合类中更方便的进行函数是编程,大家可以把StreamAPI看成类似Rxjava的类库,用以方便的对数据进行变换。(同样的,Stream也有很多类似Rxjava操作符的方法,比如filter,map,toList,flatmap等等,使用方法大同小异)。

java为Collection接口添加了stream方法(见下图),但是问题出现了,这意味着java1-java7中,实现了Collection接口的全部类都需要实现stream方法,否则,那些类将无法在java8里面通过编译。java为了保持向下兼容性,java8引入了默认方法,用关键字default表示。形象化说法就是:Collection告诉他的子类,如果你没有实现stream方法,就用我的吧。

stream方法

我们可以看到,在j8中,很多地方都使用了默认方法,如Iterable接口中的forEachspliterator

子接口中或者实现类中实现覆盖默认方法,需要Override注解。

看一下默认方法在复杂的继承关系中的一种特殊情况:

image.png

如图,OverrideChild既实现了Parent接口,又继承了实现了Parent接口的类,那么调用时,应该调用哪个方法呢?此例子打印出来的是from ParentImpl,原因在于,与接口中定义的默认方法相比,类中重写的方法更具体

调用优先级为:类重写方法>子接口重写方法>父接口方法

接口允许多继承,因此,有了默认方法,子类就可以继承多个接口中的默认实现。这在某种形式上实现了多重继承的功能。(注意:如果在继承的多个接口中,有多个签名相同的默认方法,编译器将报错)

  • 接口的静态方法

在以往的jdk版本中,接口是不允许存在静态方法的,j8发布后,为了使Stream.of静态方法,加入了静态方法,使得同一接口的子类可以共享这一静态方法。

“用参数()和()复制名为closedTheBook的默认方法是继承于TheBookInterface和BookInterface的类型。”

try-with-resources

AS3.0在所有android api层面对try-with-resources进行支持。

try-with-resources声明一个或多个资源的 try
语句。一个资源作为一个对象,必须在程序结束之后随之关闭。
try-with-resources语句确保在语句的最后每个资源都被关闭 。

任何实现了 java.lang.AutoCloseable的对象, 包括所有实现了
java.io.Closeable 的对象, 都可以用作一个资源。

先来一段代码:

image.png

可以看到, try-with-resources 语句声明的资源是一个
BufferedReader。声明语句在紧跟在 try
关键字的括号里面。在j8之前,如果我要是有一个资源,在使用之后,需要显示的效用其close方法将其关闭,但j8新的语法糖出现后,我们只需要将其声明在try中,在资源使用完毕之后,将自动关闭,不需要手动调用。上面的语句相当于:

image.png

我们可以在try后声明多个资源,见下图:

image.png

我们也可以在try-with-resources
语句后添加自己需要捕获的异常或添加finally语句块,如下图:

image.png

注意,在try-with-resources 语句中, 任意的 catch 或者 finally
块都是在声明的资源被关闭以后才运行。

如果try-with-resources语句中资源的Close方法和try代码块中都抛出了异常,Close
方法抛出的异常被抑制,try代码块中的异常会被抛出。
Java7之后,可以使用Throwable.getSuppressed方法获得被抑制的异常。

package org.smarttechie;
public interface TechBookInterface {
/**
* The default method implementation
*/
public default void closeTheBook() {
System.out.println("Closing the book");
 }
}

package org.smarttechie;
/**
* The JavaBookImpl is the implementation of BookInterface
* @author Siva Prasad Rao Janapati
*
*/
public class JavaBookImpl implements BookInterface, TechBookInterface {
/**
* This opens the book
*/
@Override
public void openTheBook() {
System.out.println("The Java book is opened");
}
/**
* This reads the book
*/
@Override
public void readTheBook() {
System.out.println("Reading the Java book");
}
public static void main (String[] args) {
BookInterface book = new JavaBookImpl();
book.closeTheBook();
 }
}

为了避免这个编译错误,我们需要在实现类中显式地定义那个具有同样签名的方法。

package org.smarttechie;
/**
* The JavaBookImpl is the implementation of BookInterface
* @author Siva Prasad Rao Janapati
*
*/
public class JavaBookImpl implements BookInterface, TechBookInterface {
/**
* This opens the book
*/
@Override
public void openTheBook() {
System.out.println("The Java book is opened");
}
/**
* This reads the book
*/
@Override
public void readTheBook() {
System.out.println("Reading the Java book");
}
public void closeTheBook() {
System.out.println("Closing the JAVA book");
}
public static void main (String[] args) {
BookInterface book = new JavaBookImpl();
book.closeTheBook();
 }
}

更深入的了解阅读,可以参考下面的链接:

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图