Java 8 实战 (一)

@[笔记, Java8, Java]

[TOC]

第一章 Java 8的更新

  1. 流处理

    流是一系列数据项,一次只生成一项。

  2. 通过 API来传递代码

  3. 并行与共享的可变数据

    对传给流方法的行为写法,必须能够同时对不同的输入安全地执行,这就意味着,代码不能访问共享的可变数据。


第二章 通过行为参数化传递代码

行为参数化,指让一个方法接受多种行为(或策略)作为参数,并在内部使用,来完成不同的行为。

处理行为参数化的问题,灵活性上定义接口实现类、使用匿名类、使用 Lambda 差不多。但是简洁程度上 Lambda 胜。


第三章 Lambda 表达式

Lambda 简介

Lambda 表达式为可传递的匿名函数的一种方式,它没有名称,但是它有参数列表、函数主题、返回类型,还有一个可以抛出的异常列表。

它可以被赋给一个变量,或者传递给一个接受函数式接口作为参数的方法。但是需要校验 参数列表返回类型 是否跟该变量、函数式接口需求的一样。

Java7的表达

Comparator byWeight = new Comparator(){
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
};

Lambda 表达式

Comparator byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

Lambda的基本表达式为

(parameters)-> expression

(parameters) -> {statements;}

   (Integer i) -> return "Alan" + i; //无效
   (Integer i) -> { return "Alan" + i;} //有效
   
   (String s) -> {"Ironman";} // 无效
   (String s) -> "Ironman"; // 有效

怎样使用 Lambda

  1. 函数式接口

    简单的说,就是只定义了一个 抽象方法 的接口。因为现在接口有 默认方法,即在类没有对方法进行实现时,其主体为方法提供默认实现的方式。
    即使一个接口有很多的 默认方法 ,只要有一个 抽象方法,仍然是一个函数式接口。(继承来的抽象方法也认为是本接口的抽象方法。 )

    如:

    public interface Comparator{
        int compare( T o1, To2 );
    }
    
    public interface Runnable{
        void run();
    }
    
  2. 函数描述符
    函数式接口的抽象方法的签名基本上就是需匹配的 Lambda 表达式的签名。我们将这种抽象方法叫做 函数描述符

  3. @FunctionalInterface 可以用来标记一个函数式接口,若本接口不是,编译器会报错。非强制。

Lambda 类型检查

  1. 类型检查过程

    a) 通过被传递的函数声明或者变量来找到类型
    b) 找到该类型的抽象方法 (故,一个 Lambda 表达式不能赋值给一个 Object 对象,因 Object 不是一个函数式接口)
    c) 找到该抽象方法的参数和返回值,进行类型检查。(对 void 类型有兼容。即一个有返回的 Lambda 表达式,可以赋值给一个返回 Void的函数式接口实例。)
    d) 若本 Lambda 表达式会抛出异常,需跟抽象方法的 throws 匹配。

  2. 类型推断

Comparator b1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

// b2同 b1的效果相同,编译器在这里会自动去匹配 a1和 a2的实际类型
Comparator b2 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
  1. 使用变量

a) Lambda 可以没有限制地使用实例变量和静态变量
b) Lambda 使用的局部变量,必须是 final 的,或者实际上 final 的。

究其本质原因,实际上就是静态变量和实例变量是在堆上的,而局部变量是在栈里的。
当拥有该变量的栈和 Lambda 所属于的栈为两个线程中时,会有线程安全问题。

方法引用

Lambda 表达式,是通过声明参数、方法体(针对参数列表的方法调用)、返回值的方式来声明一个方法。
而方法引用,就是避免参数调用其声明方法,而是直接指明类内方法的方式来创建 Lambda 表达式的方法。当需要使用一个方法引用时,目标引用放在分隔符前,方法名称放在后面。如:Apple::getWeight。(注意: 此处不应该带(),因为这里是一个对函数声明的引用,而不是对函数本身的调用。)

方法引用的普通类型

  1. 指向静态方法的方法引用, 如: Integer::parseInteger
  2. 指向实例方法的方法引用, 如: String::length
  3. 指向先用对象的实例方法的方法引用, 如:parameter::Function

构造方法引用

使用类名和关键字new的方法引用。
注意:方法引用是要赋值给一个函数式接口声明,同样需要进行返回值和类型检查。那么该接口声明的签名需跟当前类的某一个构造函数一直。
实例如下

// Supplier 的签名 () -> T
// 对应 Apple 的默认构造函数
// 等价于
// Supplier c1 = () ->new Apple();
Supplier c1 = Apple::new;
Apple a1 = c1.get();

// Function 的签名 (T) -> R
// 对应 Apple 的构造函数 Apple(int) 
// 等价于
// Function c2 = (Integer weight) -> new Apple(weight);
Function c2 = Apple::new;
Apple a2 = c2.apply(100);

// BitFunction 的签名 (T, E) -> R
// 对应 Apple 的构造函数 Apple(String, int)
// 等价于
// BitFunction c3 = (String color, Integer weight) -> new Apple( color, weight);
BitFunction c3 = Apple::new;
Apple a3 = c3.apply("green", 100);

Lambda 带来的变化

以对一个 List 排序的例子,来汇总同样做 sort的几种做法。
void sort(Comparator c)

  1. 代码传递
// 构造一个 Comparator 的实现类,并且实例化后传给 sort 方法。
public class AppleComparaotr implements Comparator{
    public int compare(Apple a1, Apple a2 )
    {
        return a1.getWeight().compareTo(a2.getWeight()); 
    }
}

// 那么排序时
listInstance.sort( new AppleComparator() );
  1. 匿名类
listInstance.sort( new Comparator{
        public int compare( Apple a1, Apple a2){
            return a1.getWeight().compareTo(a2.getWeight()); 
        }
    }
);
  1. Lambda
import static java.util.Comparator.comparing;
// 普通 Lambda
listInstance.sort( (Apple a1, Apple a2) ->  a1.getWeight().compareTo(a2.getWeight()) );

// 因为 Java 编译器有根据上下文判断 Lambda 参数类型的方法,那么实际上可简写为
listInstance.sort( (a1, a2) ->  a1.getWeight().compareTo(a2.getWeight()) );

// 使用现有接口 Comparator,可进一步简化为
listInstance.sort( Comparator.comparing( (Apple a) -> a.getWeight()) );
// 或者
listInstance.sort( Comparator.comparing( (a) -> a.getWeight()) )
  1. 方法引用
import static java.util.Comparator.comparing;

listInstance.sort( comparing(Apple::getWeight) );

复合 Lambda 表达式的有用方法

Java 8的几个接口(如 Comparator,Function, Predicate)都提供了将 Lambda 进行关联的方法。

  1. Compparator.reversed(), thenComparring();
  2. Predicate.negate(), Predicate.and(), Predicate.or();
  3. Function.andThen(), Function.compose();

第四章 流

流允许你以生命方式处理数据集合,通过查询语句来表达,而不是临时编写一个实现。它们可以被看做遍历数据集合的迭代器。此外,它们还可以透明地并行处理。

流可以进行筛选、切片、查找、匹配、映射、归约。

流与集合

流与集合的差别基本上就在于什么时候进行计算。
集合是内存中的一个数据结构,它包含了这个数据结构中的所有数据,每一个元素都是计算出结果后放入集合中。
流式一个固定的数据结构(不能增加或者删除),其元素都是按需计算的。就像是一个延迟创建的集合,只有消费者要求的时候才会计算值。

  1. 流跟迭代器一样,只能遍历一次。遍历完后,就说这个流已经被消费掉了。
Stream s = menu.stream();

s.forEach(System.out::println); // 正常执行
s.forEach(System.out::println); // 报错 stream has already been operated upon or closed
  1. 对于集合的迭代操作,是外部迭代。而 Stream 类库使用内部迭代,开发者不需要关心迭代的具体实施方法,而只需要给出函数说明要做什么。
    外部迭代
List names = new ArrayList();

// 外部迭代一
for( Dish d : menu ){
    names.add(d.getName());
}

// 外部迭代二
Iterator iterator = menu.iterator();
while( iterator.hasNext() )
{
    Dish d = iterator.next();
    names.add(d.getName());
}

内部迭代

List names = menu.stream()
                    .map(Dish::getName)
                    .collect(toList());

流操作

  1. 中间操作

可以连接起来的流操作被称为中间操作,它们通常返回另外一个流。在碰到一个终端操作前,中间操作实际上并不会执行任何数据处理。

  1. 终端操作

关闭流的操作被称作终端操作,其返回结果可能是任何不是流的值。

第五章 使用流

筛选和切片


TODO

  • [ ]Scala 和 Groovy
  • [ ]访客模式
  • [ ]策略模式
  • [ ]构建器模式
  • [ ]Java FX API,一种现代的 Java UI 平台
  • [ ]Java7中的 try 语句,表示不需要显式的关闭资源。P41
  • [ ]Java7中的菱形表达式,用法。
  • [ ]java.util.Comparator.comparing
  • [ ]Guava, Apache Commons Collections, LambdaJ

你可能感兴趣的:(Java 8 实战 (一))