第二章:Lambda表达式


本章内容:

  Lambda管中窥豹

  在哪里以及如何使用Lambda表达式

  函数式接口,类型推断

  方法引用

  Lambda复合

2.1 Lambda管中窥豹

  可以把Lambda表达式理解为简洁的表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、方法主体、返回类型等。

  匿名:

    它不像普通方法那样有一个明确的名称,写得少做得多

  函数:

    Lambda函数不像方法那样属于某个特定的类,但它和方法一样有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表

  传递:

    Lambda表达式可以作为参数传递给方法

  简洁:

    不需要像匿名类那样写很多的代码模板

参数列表:这里采用了Comparator中compare方法的参数,两个Apple

箭头:箭头 -> 将参数列表与Lambda主体分隔开

Lambda主体:比较两个Apple的重量,表达式就是Lambda的返回值

 

  测试:

  下面哪个不是有效的Lambda表达式:

    1. () -> {}

    2. () -> "Hello"

    3. () -> {return "Hello";}

    4. (Integer i) -> return "Hello" + i;

    5. (String s) -> {"Hello";}

  答案:

    4和5不是有效的Lambda表达式,4:(Integer i) -> {return "Hello" + i;};5:(String s) -> {return "Hello";}

Lambda示例:

 

2.2 在哪里以及如何使用Lambda

2.2.1 函数式接口:只定义了一个抽象方法的接口

  例如之前定义的Predicate<T>就是一个函数式接口,Comparator和Runnable也是函数式接口。

  那使用函数式接口能干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。

public class LambdaDemo {
    
    public static void main(String[] args) {
        
        Runnable r1 = () -> System.out.println("Hello World1");
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World2");
            }
        };

        process(r1);
        process(r2);
        process(() -> System.out.println("Hello World3"));
        
    }
    
    public static void process(Runnable r) {
        r.run();
    }
    
}

2.2.2 函数描述符

  函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫做函数描述符。

2.3 使用函数式接口

2.3.1 Predicate

  java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受一个泛型T对象,并返回一个boolean

public static <T> List<T> filter(List<T> list, Predicate<T> p){
        List<T> result = new ArrayList<T>();
        for (T t : list) {
            if(p.test(t)) {
                result.add(t);
            }
        }
        return result;
}


// 测试

// 使用Predicate
System.out.println("使用Predicate:");
List<String> list = new ArrayList<String>();
list.add(null);
list.add("Hello World");
list.add("");
Predicate<String> p = (String s) -> s != null && !s.isEmpty();
System.out.println(filter(list , p));

2.3.2 Consumer

  java.util.function.Consumer<T>定义了一个叫accept的抽象方法,它接受一个泛型T的对象,并没有返回(void)

public static <T> void forEach(List<T> list, Consumer<T> c) {
        for(T t : list) {
            c.accept(t);
        }
}

// 测试

// 使用Consumer
System.out.println("// 使用Consumer:");
forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i));

2.3.3 Function

  java.util.function.Function<T, R>定义了一个叫apply的抽象方法,它接受一个泛型T的对象,并返回一个泛型R对象

public static <T, R> List<R> map(List<T> list, Function<T, R> f){
        List<R> result = new ArrayList<R>();
        for (T t : list) {
            result.add(f.apply(t));
        }
        return result;
}

// 测试

// 使用Function
System.out.println("// 使用Function:");
List<Integer> l = map(Arrays.asList("lambda", "in", "action"), (String s) -> s.length());
System.out.println(l);

2.4 类型检查

  Lambda的类型是从使用Lambda的上下文推断出来的,下面描述了Lambda表达式的类型检查过程

2.5 类型推断

  你还可以进一步简化你的代码。java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来获取。这样就可以在Lambda语法中省去参数类型的声明。

List<Apple> heavirThan150 = filter(inventory, a -> a.getWeight() > 150);

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

  参数没有显式类型。有时候显式写出参数类型更易读,这个取决于程序员的选择。

2.6 方法的引用

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

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(Comparator.comparing((Apple a) -> a.getWeight())); inventory.sort(Comparator.comparing(Apple::getWeight));//你的第一个方法的引用

2.6.1 管中窥豹

  当你需要使用方法的引用时,目标引用放在分隔符 :: 前,方法的名称放在后面。例如 Apple::getWeight 就是引用了Apple类中的getWeight 方法。

Lambda 及其等效方法引用的例子:

Lambda  等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s); System.out::println

 

 

 

 

 

BiFunction<String, Integer, String> bf = (String str, Integer i) -> str.substring(i); 
bf
= String::substring; String methadQuote = methadQuote("Hello", 2, bf); System.out.println("方法的引用:"); System.out.println(methadQuote); Consumer<String> consumer = (String str) -> System.out.println(str); consumer = System.out::println; methadQuote1("Hello World", consumer);

// 方法的引用
public static String methadQuote(String str, Integer i, BiFunction<String, Integer, String> bf) {
return bf.apply(str, i);
}
public static void methadQuote1(String str, Consumer<String> c) {
c.accept(str);
}

 

  如何构建方法的引用:

    方法引用主要有三类:

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

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

      3. 指向现有对象的实例方法的方法引用(例如Apple对象a有getWeight方法,写成Apple::getWeight)

    解释一下第二种:类似于String::length这种引用思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。(这个对象必须放在泛型参数的第一位--不确定)

    

2.6.2 构造函数引用

  对于一个现有的构造函数,你可以利用它的名称和关键字new来创建它的一个引用:ClassName::new。它适合Supplier的签名 () -> T

/**
* 构造函数引用
*/
// 无参
Supplier<Apple> s = Apple::new;
// 一个参数
Function<String, Apple> f = Apple::new;
// 两个参数
BiFunction<Integer, String, Apple> biFunction = Apple::new;

   如果想要为更多参数的构造函数使用方法引用怎么办呢?

  答案:

    自己创建这样的函数式接口:

private String name;
private Integer age;
private String address;

public interface TriFunction<T, U, V, R> {

    R apple(T t, U u, V v);
    
}

// 自定义的函数式接口
TriFunction<String, Integer, String, Student> tri = Student::new;

2.7 复合Lambda表达式的有用方法

  即将讲到的方法为默认方法,后面章节会细讲

2.7.1 比较器复合

Comparator<Apple> com = Comparator.comparing(Apple::getWeight);

  1. 逆序

inventory.sort(Comparator.comparing(Apple::getWeight).reversed());

  2. 比较器链

inventory.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));

2.7.2 谓词复合

  谓词接口包含三个方法:negate,and,or

Predicate<Apple> redApple = (Apple a) -> "red".equals(a.getColor());
Predicate<Apple> notRedApple = redApple.negate();// 产生现有对象redApple的非
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150)
                                                   .or(a -> "green".equals(a.getColor()));

  注意:and和or在表达式中按照从左到右确定优先级。因此,a.or(b).and(c) 为 (a || b) && c

2.7.3 函数复合:

  你还可以把Function接口所代表的的Lambda表达式复合起来。Function接口为此配了andThen和compose两个默认方法,他们都返回一个Function的实例。

Function<Integer, Integer> g = x -> x + 1;
Function<Integer, Integer> h = y -> y * 2;
Function<Integer, Integer> j = g.andThen(h);// 相当于数学中的 h(g(x))
Integer apply = j.apply(1);
System.out.println("andThen函数复合:" + apply);// 4
j = g.compose(h);// 相当于数学中的 g(h(x))
apply = j.apply(1);
System.out.println("compose函数复合:" + apply);// 3

 

  

备注:

摘自文献:《Java8实战》(中文版)《Java8 in Action》(英文版)

 

代码(GitHub地址): https://github.com/changlezhong/java8InAction

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM