2020-04-08 (Java8实战)第一部分

一、第一章

1、了解概念

  a、Stream API

  b、向方法传递代码的技巧。

  c、接口中的默认方法。

1.1、

在java.util.stream中添加了一个Stream API;Stream就是一 系列T类型的项目。可以把它看成一种比较花哨的迭代器。Stream API的很多方法可以链 接起来形成一个复杂的流水线,推动这种做法的关键在于,现在你可以在一个更高的抽象层次上写Java 8程序了:思路变成 了把这样的流变成那样的流(就像写数据库查询语句时的那种思路),而不是一次只处理一个项 目。

另一个好处是,Java 8可以透明地把输入的不相关部分拿到几个CPU内核上去分别执行你的 Stream操作流水线——这是几乎免费的并行,用不着去费劲搞Thread了。

1.2、

并行与共享的可变数据

写代码时不能访问共享的可变数据。这些函数有时被称为“纯 函数”或“无副作用函数”或“无状态函数”,如果要写入的是一个共享变 量或对象,这就行不通了:如果两个进程需要同时修改这个共享变量怎么办? Java 8的流实现并行比Java现有的线程API更容易,因此,尽管可以使用synchronized来打 破“不能有共享的可变数据”这一规则,但这相当于是在和整个体系作对,因为它使所有围绕这 一规则做出的优化都失去意义了。在多个处理器内核之间使用synchronized,其代价往往比你 预期的要大得多,因为同步迫使代码按照顺序执行,而这与并行处理的宗旨相悖。 这两个要点(没有共享的可变数据,将方法和函数即代码传递给其他方法的能力)是我们平 常所说的函数式编程范式的基石。在命令式编程 范式中,写的程序则是一系列改变状态的指令。“不能有共享的可变数据”的要求意味着,一 个方法是可以通过它将参数值转换为结果的方式完全描述的;换句话说,它的行为就像一个数学 函数,没有可见的副作用。

1.3、方法作为值传递

Java 8的第一个新功能是方法引用。比方说,你想要筛选一个目录中的所有隐藏 文件。你需要编写一个方法,然后给它一个File,它就会告诉你文件是不是隐藏的。

幸好,File 类里面有一个叫作isHidden的方法。我们可以把它看作一个函数,接受一个File,返回一个布 尔值。但要用它做筛选,你需要把它包在一个FileFilter对象里,然后传递给File.listFiles 方法,如下所示:

File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
     public boolean accept(File file) { 
     return file.isHidden(); 
    } 
}); 

 如今在Java 8里,你可以把代码重写成这个样子:  

File[] hiddenFiles = new File(".").listFiles(File::isHidden);

已经有了函数isHidden,因此只需用Java 8的方法引用::语法(即“把这 个方法作为值”)将其传给listFiles方法;开始用函数代表方法了。一个好处是,代码现在读起来更接近问题的陈述了。方法不再是二等值了。与用对象引用传递对象类似(对象引用是用new创建的),在Java 8里写下 File::isHidden的时候,就创建了一个方法引用,同样可以传递它。

 

 

 Lambda——匿名函数

除了允许(命名)函数成为一等值外,Java 8还体现了更广义的将函数作为值的思想,包括 Lambda(或匿名函数)。比如,可以写(int x) -> x + 1,表示“调用时给定参数x, 就返回x + 1值的函数”。可能会想这有什么必要呢?因为可以在MyMathsUtils类里面定义 一个add1方法,然后写MyMathsUtils::add1嘛!确实是可以,但要是没有方便的方法和类可用,新的Lambda语法更简洁。这句话的意思是“编写把函数作为一等值来传递的程序”。

1.4、默认方法

jdk1.8之后,接口可以写方法的实现,如List<E>这个接口,实现了默认方法,需要用default关键字来声明

default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

加入默认方法主要是为了支持库设计师,让他们能够写出更容易改进的接口,多用于程序的改进。

 

二、第二章

使用lambda表达式实现代码实例,跟以往用类、匿名内部类的对比。

public class FilteringApples {
    public static void main(String[] args) {
        List<Apple> inventory= Arrays.asList(new Apple(80,"green"),
                new Apple(155,"green"),new Apple(120,"red"));

        //[Apple{weight=80, color='green'}, Apple{weight=155, color='green'}]
        List<Apple> apples0 = FilteringApples.fileterApples(inventory, FilteringApples::isGreenApple);
        System.out.println(apples0);

        //[Apple{weight=80, color='green'}, Apple{weight=155, color='green'}]
        List<Apple> apples1 = FilteringApples.fileterApples(inventory, (Apple a) -> "green".equals(a.getColor()));
        System.out.println(apples1);

        //[Apple{weight=155, color='green'}, Apple{weight=120, color='red'}]
        List<Apple> apples2 = FilteringApples.fileterApples(inventory,
                (Apple a) -> "red".equals(a.getColor()) || a.getWeight()>150);
        System.out.println(apples2);
    }
    public static List<Apple> fileterApples(List<Apple> inventory, Predicate<Apple> p){
        List<Apple> result=new ArrayList<>();
        for(Apple apple:inventory){
            if(p.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }
    public static boolean isGreenApple(Apple apple){
        return "green".equals(apple.getColor());
    }
    public static List<Apple> filterGreenApples(List<Apple> inventory){
        List<Apple> result=new ArrayList<>();
        for(Apple apple:inventory){
            if("green".equals(apple.getColor())){
                result.add(apple);
            }
        }
        return result;
    }
    static class Apple{
        private int weight=0;
        private String color="";
        Apple(int i,String color){
            weight=i;
            this.color=color;
        }

        public int getWeight() {
            return weight;
        }

        public void setWeight(int weight) {
            this.weight = weight;
        }

        public String getColor() {
            return color;
        }

        public void setColor(String color) {
            this.color = color;
        }

        @Override
        public String toString() {
            return "Apple{" +
                    "weight=" + weight +
                    ", color='" + color + '\'' +
                    '}';
        }
    }
}

以前比较繁琐的方式:

public class FilteringApples {
    public static void main(String[] args) {
        List<Apple> inventory= Arrays.asList(new Apple(80,"green"),
                new Apple(155,"green"),new Apple(120,"red"));

        //[Apple{weight=80, color='green'}, Apple{weight=155, color='green'}]
        List<Apple> list1 = FilteringApples.filter(inventory, new ColorApplePredicate());
        System.out.println(list1);

        //[Apple{weight=155, color='green'}]
        List<Apple> list2 = FilteringApples.filter(inventory, new WeightApplePredicate());
        System.out.println(list2);

        //[Apple{weight=155, color='green'}, Apple{weight=120, color='red'}]
        //使用匿名类
        List<Apple> list3 = FilteringApples.filter(inventory, new ApplePredicate(){
            @Override
            public boolean check(Apple apple) {
                return apple.getWeight()>150 || "red".equals(apple.getColor());
            }
        });
        System.out.println(list3);

    }
    //体现了策略模式,想根据什么筛选就传递什么实现类
    public static List<Apple> filter(List<Apple> inventory ,ApplePredicate applePredicate){
        List<Apple> result=new ArrayList<>();
        for (Apple apple:inventory){
            if(applePredicate.check(apple)){
                result.add(apple);
            }
        }
        return result;
    }

    interface ApplePredicate{
        boolean check(Apple apple);
    }
    static class ColorApplePredicate implements ApplePredicate{
        @Override
        public boolean check(Apple apple) {
            return "green".equals(apple.getColor());
        }
    }
    static class WeightApplePredicate implements ApplePredicate{
        @Override
        public boolean check(Apple apple) {
            return apple.getWeight()>150;
        }
    }
    static class Apple{
        private int weight=0;
        private String color="";
        Apple(int i,String color){
            weight=i;
            this.color=color;
        }

        public int getWeight() {
            return weight;
        }

        public void setWeight(int weight) {
            this.weight = weight;
        }

        public String getColor() {
            return color;
        }

        public void setColor(String color) {
            this.color = color;
        }

        @Override
        public String toString() {
            return "Apple{" +
                    "weight=" + weight +
                    ", color='" + color + '\'' +
                    '}';
        }
    }
}

 

三、第三章

1、lambda表达式有三部分

a、参数列表——这里它采用了Comparator中compare方法的参数,两个Apple。

b、箭头——箭头->把参数列表与Lambda主体分隔开。

c、Lambda主体——比较两个Apple的重量。表达式就是Lambda的返回值了。

 

 

 

2、Java8中有效的Lambda表达式

 

3、测验,判断下面Lambda语法是否正确

根据上述语法规则,以下哪个不是有效的Lambda表达式?

(1) () -> {}

(2) () -> "Raoul"

(3) () -> {return "Mario";}

(4) (Integer i) -> return "Alan" + i;

(5) (String s) -> {"IronMan";}

答案:只有4和5是无效的Lambda。

(1) 这个Lambda没有参数,并返回void。它类似于主体为空的方法:public void run() {}。

(2) 这个Lambda没有参数,并返回String作为表达式。

(3) 这个Lambda没有参数,并返回String(利用显式返回语句)。

(4) return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示: (Integer i) -> {return "Alan" + i;}。 (5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号 和分号,如下所示:(String s) -> "Iron Man"。或者如果你喜欢,可以使用显式返回语 句,如下所示:(String s)->{return "IronMan";}。

 

4、Lambda示例

 

 

 

5、函数式接口

函数式接口就是只定义一个抽象方法的接口。哪怕接口有很多默认方法,只要接口只定义一个抽象方法,它仍然是一个函数式接口。现函数式接口带有@FunctionalInterface的标注。

作用:Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例
具体说来,是函数式接口一个具体实现的实例。
用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,
然后再直接内联将它实例化。下面的代码是有效的,因为Runnable是一个只定义了一个抽象方法run的函数式接口:

 

 

 

6、jdk 中Predicate、Consumer、Function三个函数式接口

a、Predicate

java.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受泛型 T对象,并返回一个boolean。在需要 表示一个涉及类型T的布尔表达式时,就可以使用这个接口。比如,你可以定义一个接受String 对象的Lambda表达式,如下所示。

@FunctionalInterface
public interface Predicate<T>{
 boolean test(T t);
}
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;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate); 

b、Consumer

java.util.function.Consumer定义了一个名叫accept的抽象方法,它接受泛型T 的对象,没有返回(void)。需要访问类型T的对象,并对其执行某些操作,就可以使用 这个接口。

@FunctionalInterface
public interface Consumer<T>{
 void accept(T 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)
 ); 

c、Function

java.util.function.Function<t, r="">接口定义了一个叫作apply的方法,它接受一个 泛型T的对象,并返回一个泛型R的对象。如果需要定义一个Lambda,将输入对象的信息映射 到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。在下面的代码中,展示如何利用它来创建一个map方法,以将一个String列表映射到包含每个 String长度的Integer列表。

@FunctionalInterface
public interface Function<T, R>{
 R apply(T t);
}
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<Integer> l = map(
 Arrays.asList("lambdas","in","action"),
 (String s) -> s.length()
 ); 

其他一些函数式接口:

 

 

 

 

 

 

 

 最主要的一点是:如果需要什么样的类型,输出什么样的类型,都可以自己设计一个函数式接口来用。

 

7、方法引用

显式地指明方法的名称,代码的可读性会更好。它是如何工作的呢? 当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。

 

 方法引用主要有三类:

(1) 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。

(2) 指 向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作 String::length)。

(3) 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction 用于存放Transaction类型的对对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。

第二种和第三种方法引用可能乍看起来有点儿晕。

类似于String::length的第二种方法引 用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。

例如,Lambda 表达式(String s) -> s.toUppeCase()可以写作String::toUpperCase。

但第三种方法引用 指的是,你在Lambda中调用一个已经存在的外部对象中的方法。例如,Lambda表达式 ()->expensiveTransaction.getValue()可以写作expensiveTransaction::getValue。

依照一些简单的方子,我们就可以将Lambda表达式重构为等价的方法引用,如图3-5所示。

 

 看下面一个排序的例子,怎么一步步演化成简单的写法

// 1
        List<Apple> inventory = new ArrayList<>();
        inventory.addAll(Arrays.asList(new Apple(80,"green"), new Apple(155, "green"), new Apple(120, "red")));

        // [Apple{color='green', weight=80}, Apple{color='red', weight=120}, Apple{color='green', weight=155}]
        inventory.sort(new AppleComparator());
        System.out.println(inventory);

        // 改一下第2位置的元素
        inventory.set(1, new Apple(30, "green"));
        
        // 2
        // [Apple{color='green', weight=30}, Apple{color='green', weight=80}, Apple{color='green', weight=155}]
        inventory.sort(new Comparator<Apple>() {
            public int compare(Apple a1, Apple a2){
                return a1.getWeight().compareTo(a2.getWeight()); 
        }});
        System.out.println(inventory);

        // 改一下第2位置的元素
        inventory.set(1, new Apple(20, "red"));
        
        // 3
        // [Apple{color='red', weight=20}, Apple{color='green', weight=30}, Apple{color='green', weight=155}]
        inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
        System.out.println(inventory);
        
        // 改一下第2位置的元素
        inventory.set(1, new Apple(10, "red"));
        
        // 4
        // [Apple{color='red', weight=10}, Apple{color='red', weight=20}, Apple{color='green', weight=155}]
        inventory.sort(comparing(Apple::getWeight));
     //inventory.sort(comparing(Apple::getWeight).reversed());  //逆序
     System.out.println(inventory);

Comparator具有一个叫作comparing的静态辅助方法, 它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象。

最终的代码读起来的意思就是:对库存进行排序,比较苹果的重量。

 

posted @ 2020-04-08 14:29  math_lin  阅读(217)  评论(0)    收藏  举报