Java8最全介绍(持续更新)

Lambda表达式

Lambda表达式可以理解为简洁地表示可传递的匿名函数的一种方式: 它没有名称,但它

有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

举个例子:

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

// Java8,使用了Lambda表达式
Comparator byWeight = (Apple a1, Apple a2) -> 		       a1.getWeight().compareTo(a2.getWeight());

如何使用Lambda表达式?

  1. 参数列表:这里采用了Comparator中compare方法的参数,两个 Apple 。
  2. 箭头:箭头 -> 把参数列表与Lambda主体分隔开。
  3. Lambda主体:比较两个 Apple 的重量。表达式就是Lambda的返回值了。

注意: 可以不需要写参数类型,单条语句不需要写return,多条语句需要使用{ }括号括起来。

在哪里使用Lambda表达式?

在函数式接口上使用Lambda表达式。

函数式接口

函数式接口就是只定义一个抽象方法的接口。

// 下面哪些是函数式接口?
public interface Adder {
    int add(int a, int b);
}
public interface SmartAdder extends Adder{
    int add(double a, double b);
}
public interface Nothing{
    
}

答案: 只有Adder是函数式接口。SmartAdder不是函数式接口,因为它继承了一个Adder又自己定义了一个。Nothing则没有声明抽象方法。

函数式接口能干什么?

Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。 具体来说就是函数式接口一个具体实现的实例。

其实,使用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后直接内联将它实例化。比如下面的Runnable的run方法。

// 使用匿名内部类的方式
Runnable r = new Runnabler(){
    @Override
    public void run(){
        System.out.println("Hello World!");
    }
}

// 使用Lambda
Runnable r = () -> System.out.println("Hello World!");

使用函数式接口

  1. Predicate
    java.util.function.Predicate接口定义了一个名叫test的抽象方法,他接受泛型T对象,并返回一个boolean。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。从下面Predicate的源码中也可以看到其他的默认实现方法,这些将会在后面进行解答。

       // 底层源码
       @FunctionalInterface
       public interface Predicate<T>{
       	   boolean test(T t);
           
           default Predicate<T> and(Predicate<? super T> other) {
               Objects.requireNonNull(other);
               return (t) -> test(t) && other.test(t);
           }
           
           default Predicate<T> negate() {
               return (t) -> !test(t);
           }
           
           default Predicate<T> or(Predicate<? super T> other) {
               Objects.requireNonNull(other);
               return (t) -> test(t) || other.test(t);
           }
           
           static <T> Predicate<T> isEqual(Object targetRef) {
               return (null == targetRef)
                       ? Objects::isNull
                       : object -> targetRef.equals(object);
           }
       }
    

    先看一个栗子:

     public static <T> List<T> filter(List<T> list, Predicate<T> p) {
        	List<T> results = new ArrayList<>();
        	for(T s: list){
        		if(p.test(s)){
        			results.add(s);
        		}
        	}
        	return results;
        }
    
    // 利用Lambda实现一个判断字符串是否为空的方法
    Predicate nonEmptyStringPredicate = (String s) -> !s.isEmpty();
    // 调用该过滤器
    List nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
    

    上述的做法将使程序变得非常灵活,比如之后要判断List中是否包含长度大于5的等等,则只需要利用Predicate进行相应的实现即可。
    // 扩展
    Predicate nonEmptyStringPredicate1 = (String s) -> s.length() > 5;

  2. Consumer
    java.util.function.Consumer 定义了一个名叫 accept 的抽象方法,它接受泛型 T的对象,没有返回,如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。
    比如创建一个forEach方法,接受一个Integers的列表,并对其中每个元素执行操作。

     // 底层源码
        @FunctionalInterface
        public interface Consumer<T> {
            void accept(T t);
        
            default Consumer<T> andThen(Consumer<? super T> after) {
                Objects.requireNonNull(after);
                return (T t) -> { accept(t); after.accept(t); };
            }
        }
        public static <T> void forEach(List<T> list, Consumer<T> c) {
            for (T i : list) {
                c.accept(i);
            }
        }
        
        forEach(Arrays.asList(1,2,3,4,5),
               	(Integer i) -> System.out.println(i));
    
  3. Function
    java.util.function.Function 接口定义了一个叫作 apply 的方法,它接受一个泛型 T 的对象,并返回一个泛型 R 的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。

     // 底层源码
       @FunctionalInterface
       public interface Function<T, R> {
       
           R apply(T t);
       
           default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
               Objects.requireNonNull(before);
               return (V v) -> apply(before.apply(v));
           }
       
           default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
               Objects.requireNonNull(after);
               return (T t) -> after.apply(apply(t));
           }
       
           static <T> Function<T, T> identity() {
               return t -> t;
           }
       }
    

    例如: 创建一个map方法,以将一个String列表映射到包含每个String长度的Integer列表。

     public static <T,R> List<R> map(List<T> list, Function<T,R> f) {
         List<R> result = new ArrayList<>();
         for (T s: list) {
             result.add(f.apply(s));
         }
         return result;
     }
    
    // [7, 2, 6]
    List l = map(Arrays.asList("lambdas","in","action"),
    					(String s) -> s.length());
    

    注意:任何函数式接口都不允许抛出受检异常,如果你需要在Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个try/catch块中。

方法引用

方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。

先看一个例子: 

// 之前
inventory.sort((Apple a1, Apple a2)
				-> a1.getWeight().compareTo(a2.getWeight()));

// 现在(使用方法引用和 java.util.Comparator.comparing)
inventory.sort(comparing(Apple::getWeight));

基本思想:如果一个Lambda表达的只是"直接调用这个方法", 最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。

// 方法引用就是Lambda的快捷写法
Apple::getWeight
| | 等价于
(Apple a) -> a.getWeight()
Lambda 等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s) System.out::println

如何创建方法引用

方法引用主要有三类
  1. 指向静态方法的方法引用(例如 Integer 的 parseInt 方法,写作 Integer::parseInt )。
  2. 指向任意类型实例方法的方法引用( 例 如 String 的 length 方 法 , 写 作String::length )。
  3. 指向现有对象的实例方法的方法引用(假设你有一个局部变量 expensiveTransaction用于存放 Transaction 类型的对象,它支持实例方法 getValue ,那么你就可以写 expensiveTransaction::getValue )。

流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个		  实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。
 public static void main(String[] args) {
            Apple a1 = new Apple("red", 500);
            Apple a2 = new Apple("black", 300);
            Apple a3 = new Apple("green", 420);
            // Java7
            List<Apple> appleList = Arrays.asList(a1, a2, a3);
            List<Apple> list = new ArrayList<>();
            for (Apple a : appleList) {
                if (a.getWeight() > 400) {
                    list.add(a);
                }
            }
    
            Collections.sort(list, new Comparator<Apple>() {
                @Override
                public int compare(Apple o1, Apple o2) {
                    return Integer.compare(o1.getWeight(), o2.getWeight());
                }
            });
    
            List<String> colorList = new ArrayList<>();
            for(Apple a: list){
                colorList.add(a.getColor());
            }
            System.out.println(colorList);
    
            // Java8
            List<String> colorList2 =
                        appleList.stream()
                            .filter(a -> a.getWeight() > 400)
                            .sorted(Comparator.comparing(Apple::getWeight).reversed())
                            .map(Apple::getColor)
                            .collect(Collectors.toList());
            System.out.println(colorList2);
    
            // 多核架构并行处理
            List<String> colorList3 =
                    appleList.parallelStream()
                            .filter(a -> a.getWeight() > 400)
                            .sorted(Comparator.comparing(Apple::getWeight).reversed())
                            .map(Apple::getColor)
                            .collect(Collectors.toList());
            System.out.println(colorList3);
        }

Java8中的Stream API可以让你写出这样的代码: 声明性:更简洁,更易读。可复合:更灵活。可并行:性能更好。

流到底是什么?

简单来说就是支持数据处理操作的源生成的元素序列。

  1. 元素序列:可以访问特定元素类型的一组有序值。
  2. 源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
  3. 数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以顺序执行,也可并行执行。
  4. 流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。
  5. 内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

使用流

  1. 一个数据源(如集合)来执行一个查询。
  2. 一个中间操作链,形成一条流的流水线。
  3. 一个终端操作,执行流水线,并能生成结果。

中间操作

操作 类型 返回类型 操作参数 函数描述符
filter 中间 Stream Predicate T -> boolean
map 中间 Stream Function T -> R
sorted 中间 Stream Comparator (T, T) -> int
distinct 中间 Stream Predicate T -> boolean

终端操作

操作 类型 目的
forEach 终端 消费流中的每个元素,并对其应用Lambda。这一操作返回void
count 终端 返回流中的个数。这一操作返回long
collect 终端 把流归约成一个集合,比如List,Map甚至是Integer

小结

* 流是“从支持数据处理操作的源生成的一些列元素”。
* 流利用内部迭代:通过filter,map,sorted等操作被抽象掉了。
* 流操作有两类:中间操作和终端操作。
* filter和map等中间操作会返回一个流,并可以连接在一起。可以用他们来设置一条流水线,但不会生成任何结果。
* forEach和count等终端操作会返回一个非流的值,并处理流水线以返回结果。
* 流中的元素是按需计算的。

使用谓词筛选

	Stream接口支持filter方法,该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包含所有符合谓词的元素的流。

筛选各异的元素

	流还支持一个叫做distinct的方法, 会返回一个元素各异(根据流所生成元素hashCode和equals方法实现)的流。

e.g. 下面代码会筛选出所有偶数,并确保没有重复。

List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
		.filter(i -> i % 2 == 0)
		.distinct()
		.forEach(System.out::println);

截短流

流还支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。

List dishes = menu.stream()
				.filter(d -> d.getCalories() > 300)
				.limit(3)
				.collect(toList());

跳过元素

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中能够元素不足n个,则返回一个空流。注意:limit(n)和skip(n)是互补的!

List dishes = menu.stream()
					.filter(d -> d.getCalories() > 300)
					.skip(2)
					.collect(toList());

对流中每一个元素应用函数

流还支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”,而不是去“修改”)。

	e.g. 下面的代码把方法引用Dish::getName传给了map方法,来提取流中菜肴的名称。

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

	如果你要找出每道菜的名称并判断名称有多长,怎么做?

List dishNameLengths = menu.stream()
    							.map(Dish::getName)
    							.mdistinctap(String::length)
    							.collect(toList);

流的扁平化

对于一张单词表,如何返回一张列表,列出里面各不相同的字符呢?e.g. 给定单词列表[“Hello”,“World”],你想要返回列表[“H”,“e”,“l”, “o”,“W”,“r”,“d”]。

你可能想到的第一个版本是这样的

words.stream()
	.map(word -> word.split(""))
	.distinct()
	.collect(toList());

	这个方法的问题在于:传递给map方法的Lambda为每个单词返回了一个String[]。因此,map返回的流实际上是Stream类型的。

检查谓词是否至少匹配一个元素

	anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。

	e.g.  检查菜单中是否有素食

if (menu.stream().anyMatch(Dish::isVegetarian)) {
    System.out.println("The menu is (somewhat) vegetarian frinedly!!!")
}

	anyMatch方法返回一个boolean,因此是一个终端操作。

检查谓词是否匹配所有元素

	allMatch方法的工作原理和anyMatch类似,它会看看流中的元素是否都能匹配给定的谓词。

	e.g. 查看菜品是否有利健康(即所有菜的热量都低于1000卡路里)

boolean isHealthy = menu.stream()
		 			.allMatch(d -> d.getCalories() < 1000);

与之对应的是noneMatch,他可以确保流中没有任何元素与给定的谓词匹配。

boolean isHealthy = menu.stream()
   					.noneMatch(d -> d.getCalories() >= 1000);

anyMatch,allMatch和noneMatch这三个操作都用到了所谓的短路,这就是Java中&&和||运算符短路在流中的版本。

查找元素

	findAny方法将返回当前流中的任意元素。可以与其他流操作结合使用。

	e.g. 想找一道素食菜肴,可以结合使用filter和findAny来查询。

Optional dish = 
    	menu.stream()
    	.filter(Dish::isVegetarian)
    	.findAny();

Optional

	Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的例子中,findAny可能什么元素都没找到,而Optional的引入可以避免出现null的问题。

Optional中有几种可以迫使你显式地检查值是否存在或处理值不存在的方法。

	isPresent():将在Optional包含值的时候返回true,否则返回false。

	ifPresent(Consumer block)会在值存在的时候执行给定的代码块。

	T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。

	T orElse(T other)会在值存在时返回值,否则返回一个默认值。

menu.stream()
	.filter(Dish::isVegetarian)
	.findAny()
	.ifPresent(d -> System.out.println(d.getName());

查找第一个元素

List someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional firstSquareDivisibleByThree =
	someNumbers.stream()
				.map(x -> x * x)
				.filter(x -> x % 3 == 0)
				.findFirst(); // 9

何时使用findFirst和findAny

	答案是并行。找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少。

归约

	如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为归约操作(将流归约成一个值)。

你可能感兴趣的:(Java8)