Java8新特性

Java8新特性

 

一、Lambda

(一)介绍

Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据

(二)作用

1.用作替代匿名内部类

(1)资料

λ表达式主要用于替换以前广泛使用的内部匿名类,各种回调,比如事件响应器、传入Thread类的Runnable等。看下面的例子:

    Thread oldSchool = new Thread( new Runnable () {
        @Override
        public void run() {
            System.out.println("This is from an anonymous class.");
        }
    } );
    
    Thread gaoDuanDaQiShangDangCi = new Thread( () -> {
        System.out.println("This is from an anonymous method (lambda exp).");
    } );

注意第二个线程里的λ表达式,你并不需要显式地把它转成一个Runnable,因为Java能根据上下文自动推断出来:一个Thread的构造函数接受一个Runnable参数,而传入的λ表达式正好符合其run()函数,所以Java编译器推断它为Runnable。

2.集合类的迭代

(1)资料

集合类的批处理操作。这是Java8的另一个重要特性,它与λ表达式的配合使用乃是Java8的最主要特性。Java8之前集合类的迭代(Iteration)都是外部的,即客户代码。而内部迭代意味着改由Java类库来进行迭代,而不是客户代码。例如:

    for(Object o: list) { // 外部迭代
        System.out.println(o);
    }

可以写成:

    list.forEach(o -> {System.out.println(o);}); //forEach函数实现内部迭代

集合类(包括List)现在都有一个forEach方法,对元素进行迭代(遍历),所以我们不需要再写for循环了。forEach方法接受一个函数接口Consumer做参数,所以可以使用λ表达式。

3.流(stream)

(1)资料

Java8为集合类引入了另一个重要概念:流(stream)。一个流通常以一个集合类实例为其数据源,然后在其上定义各种操作。流的API设计使用了管道(pipelines)模式。对流的一次操作会返回另一个流。如同IO的API或者StringBuffer的append方法那样,从而多个不同的操作可以在一个语句里串起来。看下面的例子:

    List<Shape> shapes = shapes.stream()
      .filter(s -> s.getColor() == BLUE)
      .forEach(s -> s.setColor(RED));

首先调用stream方法,以集合类对象shapes里面的元素为数据源,生成一个流。然后在这个流上调用filter方法,挑出蓝色的,返回另一个流。最后调用forEach方法将这些蓝色的物体喷成红色。(forEach方法不再返回流,而是一个终端方法,类似于StringBuffer在调用若干append之后的那个toString)。filter方法的参数是Predicate类型,forEach方法的参数是Consumer类型,它们都是函数接口,所以可以使用λ表达式。

来看更多的例子。下面是典型的大数据处理方法,Filter-Map-Reduce:

    //给出一个String类型的数组,找出其中所有不重复的素数
    public void distinctPrimary(String... numbers) {
        List<String> l = Arrays.asList(numbers);
        List<Integer> r = l.stream()
                .map(e -> new Integer(e))
                .filter(e -> Primes.isPrime(e))
                .distinct()
                .collect(Collectors.toList());
        System.out.println("distinctPrimary result is: " + r);
    }

第一步:传入一系列String(假设都是合法的数字),转成一个List,然后调用stream()方法生成流。

第二步:调用流的map方法把每个元素由String转成Integer,得到一个新的流。map方法接受一个Function类型的参数,上面介绍了,Function是个函数接口,所以这里用λ表达式。

第三步:调用流的filter方法,过滤那些不是素数的数字,并得到一个新流。filter方法接受一个Predicate类型的参数,上面介绍了,Predicate是个函数接口,所以这里用λ表达式。

第四步:调用流的distinct方法,去掉重复,并得到一个新流。这本质上是另一个filter操作。

第五步:用collect方法将最终结果收集到一个List里面去。collect方法接受一个Collector类型的参数,这个参数指明如何收集最终结果。在这个例子中,结果简单地收集到一个List中。我们也可以用Collectors.toMap(e->e, e->e)把结果收集到一个Map中,它的意思是:把结果收到一个Map,用这些素数自身既作为键又作为值。toMap方法接受两个Function类型的参数,分别用以生成键和值,Function是个函数接口,所以这里都用λ表达式。

在对于一个Stream进行多次转换操作,每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是一个for循环里把所有操作都做掉的N(转换的次数)倍啊。其实不是这样的,转换操作都是lazy的,多个转换操作只会在汇聚操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。

(三)Lambda表达式类型

(1)资料

λ表达式可以被当做是一个Object(注意措辞)。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数接口(functional interface)”,这是Java8新引入的概念。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来(也可以不标)

可以用一个λ表达式为一个函数接口赋值:
 
    Runnable r1 = () -> {System.out.println("Hello Lambda!");};
    
然后再赋值给一个Object:

    Object obj = r1;
    
但却不能这样干:

Object obj = () -> {System.out.println("Hello Lambda!");}; 

必须显式的转型成一个函数接口才可以:

    Object o = (Runnable) () -> { System.out.println("hi"); }; // correct
    
一个λ表达式只有在转型成一个函数接口后才能被当做Object使用。所以下面这句也不能编译:

    System.out.println( () -> {} ); //错误! 目标类型不明
    
必须先转型:

System.out.println( (Runnable)() -> {} ); // 正确

 

JDK预定义了很多函数接口以避免用户重复定义。最典型的是Function:

    @FunctionalInterface
    public interface Function<T, R> {  
        R apply(T t);
    }

这个接口代表一个函数,接受一个T类型的参数,并返回一个R类型的返回值。   

另一个预定义函数接口叫做Consumer,跟Function的唯一不同是它没有返回值。

    @FunctionalInterface
    public interface Consumer<T> {
        void accept(T t);
    }

还有一个Predicate,用来判断某项条件是否满足。经常用来进行筛滤操作:
    
    @FunctionalInterface
    public interface Predicate<T> {
        boolean test(T t);
    }
    
综上所述,一个λ表达式其实就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。

(四)demo

E:\source\_2\src\main\java\exercise\LambdaTry.java

二、接口的默认方法与静态方法

(一)接口的默认方法

1.介绍

与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。

默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。

2.例子

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

Private interface Defaulable {

    // Interfaces now allow default methods, the implementer may or

    // may not implement (override) them.

    Default String notRequired() {

        return"Default implementation";

    }       

}

         

Private static class DefaultableImpl implements Defaulable {

}

     

Private static class OverridableImpl implements Defaulable {

    @Override

    Public String notRequired() {

        return"Overridden implementation";

    }

}

 

Defaulable接口用关键字default声明了一个默认方法notRequired()Defaulable接口的实现者之一DefaultableImpl实现了这个接口,并且让默认方法保持原样。Defaulable接口的另一个实现者OverridableImpl用自己的方法覆盖了默认方法。

(二)接口的静态方法

1.例子

接口可以声明(并且可以提供实现)静态方法。例如:

1

2

3

4

5

6

Private interface DefaulableFactory {

    // Interfaces now allow static methods

    Static Defaulable create( Supplier< Defaulable > supplier ) {

        Return supplier.get();

    }

}

 

下面的一小段代码片段把上面的默认方法与静态方法黏合到一起。

1

2

3

4

5

6

7

Public static void main( String[] args ) {

    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new);

    System.out.println( defaulable.notRequired() );

         

    defaulable = DefaulableFactory.create( OverridableImpl::new);

    System.out.println( defaulable.notRequired() );

}

 

这个程序的控制台输出如下:

1

2

Default implementation

Overridden implementation

 

JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream()parallelStream()forEach()removeIf(),……

尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。

三、方法引用

(一)资料

可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

在学习lambda表达式之后,我们通常使用lambda表达式来创建匿名方法。然而,有时候我们仅仅是调用了一个已存在的方法。如下:

 Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));

Java8中,我们可以直接通过方法引用来简写lambda表达式中已经存在的方法。

Arrays.sort(stringsArray, String::compareToIgnoreCase);

这种特性就叫做方法引用(Method Reference)

一共有四种类型的方法引用:

类型

示例

类静态方法引用

ContainingClass::staticMethodName

某个对象的方法引用

ContainingObject::instanceMethodName

特定类的任意对象的方法引用

ContainingType::methodName

构造方法引用

ClassName::new

 

(二)例子

定义了4个方法的Car这个类作为例子,区分Java中支持的4种不同的方法引用。

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

Public static class Car {

    Public static Car create(final Supplier< Car > supplier ) {

        Return supplier.get();

    }             

         

    Public static void collide(final Car car ) {

        System.out.println("Collided "+ car.toString() );

    }

         

    Public void follow(final Car another ) {

        System.out.println("Following the "+ another.toString() );

    }

         

    Public void repair() {  

        System.out.println("Repaired "+this.toString() );

    }

}

 

第一种方法引用是构造器引用,它的语法是Class::new,或者更一般的Class< T > new。请注意构造器没有参数。

1

2

final Car car = Car.create( Car::new);

Final List< Car > cars = Arrays.asList( car );

 

第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数。

1

cars.forEach( Car::collide );

 

第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method。请注意,这个方法没有参数。

1

cars.forEach( Car::repair );

 

最后,第四种方法引用是特定对象的方法引用,它的语法是instance::method。请注意,这个方法接受一个Car类型的参数

1

2

Final Car police = Car.create( Car::new);

cars.forEach( police::follow );

 

运行上面的Java程序在控制台上会有下面的输出(Car的实例可能不一样):

1

2

3

Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

 

四、Optional

(一)资料

1.介绍

为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存nullOptional提供很多有用的方法,这样我们就不用显式进行空值检测。

2.例子

我们下面用两个小例子来演示如何使用Optional类:一个允许为空值,一个不允许为空值。

1

2

3

4

Optional< String > fullName = Optional.ofNullable(null);

System.out.println("Full Name is set? "+ fullName.isPresent() );       

System.out.println("Full Name: "+ fullName.orElseGet( () ->"[none]") );

System.out.println( fullName.map( s ->"Hey "+ s +"!").orElse("Hey Stranger!") );

 

如果Optional类的实例为非空值的话,isPresent()返回true,否从返回false。为了防止Optional为空值,orElseGet()方法通过回调函数来产生一个默认值。map()函数对当前Optional的值进行转化,然后返回一个新的Optional实例。orElse()方法和orElseGet()方法类似,但是orElse接受一个默认值而不是一个回调函数。

下面是这个程序的输出:

1

2

3

Full Name isset?false

Full Name: [none]

Hey Stranger!

 

让我们来看看另一个例子:

1

2

3

4

5

Optional< String > firstName = Optional.of("Tom");

System.out.println("First Name is set? "+ firstName.isPresent() );       

System.out.println("First Name: "+ firstName.orElseGet( () ->"[none]") );

System.out.println( firstName.map( s ->"Hey "+ s +"!").orElse("Hey Stranger!") );

System.out.println();

 

下面是程序的输出:

1

2

3

First Name isset?true

First Name: Tom

Hey Tom!

 

 

posted @ 2017-08-02 16:12  roren  阅读(215)  评论(0)    收藏  举报