Java8新特性学习笔记(1)----Lamda表达式

1. Lambda表达式

1.1 示例

Thread thread = new Thread(() -> {
    System.out.println("HelloWorld");
});
thread.start();
复制代码

1.2 定义

Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它 有参数列表函数主体返回类型,可能还有一个可以抛出的异常列表”(参考Java8实战)

Lambda表达式包含3部分

  • 参数列表:函数参数列表
  • 箭头:分割参数列表和主体
  • Lambda主体:函数主体

1.2.1 参数列表

参数列表使用括号()来表示,其中参数可以声明参数类型,也可以不声明参数类型(编译器会根据上下文推断)。

1.2.2 函数体(Lambda主体)

Lambda表达式的函数体,既可以是代码块,也可以是表达式代码块:和普通方法的方法主体一致; 表达式表达式会被执行并返回结果,其本质上是一种return语句的简写(省略了return和{})

1.2.3 特殊表达

Lambda不仅可以省略参数类型用表达式表示方法体,其实还可以直接使用方法引用来替代Lambda表达式,例如如下三种方式是等价的:

Consumer<String> consumer1 = (String str) -> {
    System.out.println(str);
};
Consumer<String> consumer2 = (str) -> {
    System.out.println(str);
};
Consumer<String> consumer3 = System.out::println;
复制代码
  • 暂时还是没太理解第三种的本质。。

1.3 特点

Lambda表达式拥有如下特点:

  • 匿名:和普通方法相比,没有明确的方法名
  • 函数:Lambda函数和方法区别是不属于某个特定的类
  • 传递:Lambda表达式可以作为参数传递给方法,或者存储在变量中。
  • 简洁:无需匿名内部类那样写很多复杂的函数。

1.4 Lambda表达式和匿名内部类

Lambda表达式用来为某个抽象方法提供实现,个人可以粗略地讲其理解为一种匿名内部类更加通俗易懂,但两者并不完全相同。

相同点

两者有如下相同点:

  • 都没有表现出来的类名(匿名内部类会生成.class文件,Lambda表达式好像会在第一次被使用时动态生成一个类?);
  • 都可以访问外部的变量和方法。
  • 只能访问外部final变量,不能修改外部变量。
final String word = "Hello";
Runnable runnable = () -> {
    // 不可以在Lambda表达式中修改外部变量,否则会提示Variable used in lambda expression should be final or effectively final
    // word = "HelloWorld";
    function1();
    System.out.println(word);
};
runnable.run();

Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        function1();
        // 不可以在内部类中修改外部变量,否则会提示Variable is accessed within inner class. Needs to be final or effectively final
        // word = "HelloWorld";
        System.out.println(word);
    }
};
runnable1.run();
复制代码

不同点

  • 实现父类(或接口)不同。Lambda表达式只可以实现函数式接口,而匿名内部类可以实现拥有多个抽象方法的接口、抽象类和普通类。
  • 匿名内部类可以调用接口中其它方法,但Lambda表达式不可以。(本质上应该是下一个不同点的第2小点)
  • 两者作用域不同。(1)Lamda表达式中this指的是外部类的对象,而内部类中this指的是内部类对象。见作用域示例1。 (2)Lamda表达式中调用的方法也都是外部类的,但内部类中调用的方法是优先内部的。
作用域示例1
public class HelloLambda {

    Runnable r1 = () -> {
        System.out.println("Lambda, 1: " + this);
        System.out.println("Lambda, 2: " + toString());
    };

    Runnable r2 = new Runnable() {
        @Override
        public void run() {
            System.out.println("Inner class, 1: " + this);
            System.out.println("Inner class, 2: " + toString());
        }
    };

    @Override
    public String toString() {
        return "Hello, lambda!";
    }

    public static void main(String[] args) {
        new HelloLambda().r1.run();
        new HelloLambda().r2.run();
    }
}
复制代码

输出:

作用域示例2

Lambda表达式r1由于外部类中没有run()方法,所以报错;但r2由于内部类中有run()方法,所有可以通过编译,实际上就是循环调用了。

2. 函数式接口

函数式接口就是有且仅有一个抽象方法的接口
Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。(其实可以将Lambda表达式看做匿名内部类,该匿名内部类实现了函数式接口)。

特殊说明:

  • 函数式接口中除了特定的抽象方法外,还可以有非抽象方法
  • 如果在接口中有重写了Obejct的public方法的抽象方法,那么该方法也不影响函数式接口抽象方法的计数。

接下来介绍几种常见函数式接口。

2.1 Runnable和Callable接口

Runnable

Runnable接口源码

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
复制代码

观察Runnable源码,可以发现该接口中只有一个抽象方法run(),故可以使用Lambda表达式来作为该函数式接口的实例,并提供抽象方法的实现。

Runnable runnable = () -> {
    System.out.println("Hello world!");
};
复制代码

Callable

Callable接口源码:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
复制代码

Callable接口的Lambda表达式示例:

Callable<String> callable = () -> {
    return "HelloWorld";
};
复制代码

Callable接口有返回值,Lambda主体可以直接使用表达式替代return语句,如下:

Callable<String> callable = () -> "HelloWorld";
复制代码

2.2 Comparator接口

源码(部分)

@FunctionalInterface
public interface Comparator<T> {
    // 抽象方法
    int compare(T o1, T o2);
    // 重写Obejct类public方法的抽象方法,不进行计数
    boolean equals(Object obj);
    // 非抽象方法
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
}
复制代码

观察源码,可以发现Comparator实现类:(1)需要声明泛型;(2)需要实现compare方法,且参数列表有两个,参数类型与泛型类上声明一致。

Lambda表达式示例

简单示例

Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2);
复制代码

列表倒序排序示例:

List<Integer> numbers = Lists.newArrayList(1, 2, 3);
numbers.sort((x1, x2) ->  x2.compareTo(x1));
numbers.forEach((x) -> {
    System.out.println(x);
});
复制代码

输出:

按照对象属性排序

按user年龄倒序排序:

List<User> users = Lists.newArrayList(new User(18), new User(22), new User(24));
users.sort((u1, u2) -> u2.age.compareTo(u1.age));
users.forEach((u) -> {
    System.out.println(u.age);
});
复制代码

输出:

2.3 Consumer

Consumer接口是一种有参无返回值的消费型接口

源码

@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); };
    }
}
复制代码

Consume接口可以理解为一种通用消费型接口可以接收参数,但不能返回),通过lambda表达式定义好要执行的动作,通过调用accept方法执行。

简单示例

Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello world1!");
复制代码

拿其与Runable接口比较更好理解,Runnable接口也可以直接执行run方法(就没有线程特性了),但是无法接受传入的参数。比较

Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello world1!");

Runnable runnable = () -> {
    System.out.println("Hello world2!");
};
runnable.run();
复制代码

回调示例

个人理解,Consumer接口最常见的用途是作为回调
如下所示,function1接收参数list、以及回调函数consumer,function1本身不关心如何处理、直接执行consumer.accept(),具体的处理由传入的consumer变量(lambda表达式、回调函数)决定,可以通过修改传入的consumer变量来修改函数的实现。

public static void function1(List<Integer> list, Consumer<List<Integer>> consumer) {
    consumer.accept(list);
}

public static void main(String[] args) {
    // 存储lambda表达式
    Consumer<List<Integer>> consumer1 = list -> {
        list.sort((x1, x2) -> x2.compareTo(x1));
    };
    Consumer<List<Integer>> consumer2 = list -> {
        list.sort((x1, x2) -> x1.compareTo(x2));
    };
    
    
    List<Integer> numbers = Lists.newArrayList(1, 2, 3);
    // 传递lambda表达式
    function1(numbers, consumer1);
    numbers.forEach((x) -> {
        System.out.println(x);
    });
    
    function1(numbers, consumer2);
    numbers.forEach((x) -> {
        System.out.println(x);
    });
}
复制代码

BiConsumer

BiConsumer接口则是Consumer接口升级版,可以接受两个参数:

BiConsumer<String, Integer> biConsumer = (str, num) -> {
    System.out.println(str + num);
};
biConsumer.accept("Hello, ", 123);
复制代码

还有其他消费型接口,比如IntConsumer。

2.4 Supplier接口

Supplier接口是一种无参有返回值的供给型接口。可以理解为一个容器,可以生成、存储对象,供其它方法调用其中get()方法获取对象。

源码

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
复制代码

简单示例

Supplier<User> supplier = () -> {
    return new User(123);
};
User user = supplier.get();
System.out.println(user.age);
复制代码

回调示例

如下所示,getId方法用于返回一个int型整数,但不同的地方可能使用不同的生成策略,所以使用Supplier接口作为参数,将id生成策略回调出去。

public static Integer getId(Supplier<Integer> supplier) {
    return supplier.get();
}

public static void main(String[] args) {
    Supplier<Integer> supplier1 = () -> (new Random()).nextInt(10);
    Supplier<Integer> supplier2 = () -> (new Random()).nextInt(100);
    System.out.println(getId(supplier1));
    System.out.println(getId(supplier2));
}
复制代码

但是个人理解上Supplier接口意义很低,不能接受参数、直接return,即便作为回调也难以有太多实际使用场景。

2.5 Predicate接口

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);
    }
}
复制代码

观察源码可以发现,需要实现test()方法,并且还支持and/or联合判断条件。

使用示例

Predicate<Integer> predicate = (x) -> x < 100;
Predicate<Integer> predicate1 = (x) -> x > 0;
System.out.println(predicate.test(1));
System.out.println(predicate.and(predicate1).test(-1));
复制代码

输出结果:

2.6 Function接口

源码

@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;
    }
}
复制代码

观察源码得到需要实现其中apply()方法,而lambda表达式的参数<T, R>,其中T代表函数的参数类型,R代表返回类型(第一个是参数,第二个是返回值)。
Function接口使用的场景较多Consumer/Predicate/Supplier接口能实现的功能用Function接口也都可以实现。

简单示例

Function<Integer, String> function = (x) -> {
    if (x < 0) {
        return "小于0";
    } else {
        return "大于等于0";
    }
};
System.out.println(function.apply(3));
复制代码

BiFunction

Function接口同样也有用来接收两个参数的BiFunction接口。

BiFunction<Integer, Integer, String> function = (x1, x2) -> {
    return new StringBuilder("Receive x1=").append(x1).append(", x2=").append(x2).toString();
};
复制代码

3. 流

Java8新增了一个接口java.util.Stream,可以将Collection、List、Set、Map等使用流进行处理,编码更加方便快捷。Stream接口依赖于Lambda表达式。
详见:。。。。。。。。。

猜你喜欢

转载自juejin.im/post/5e4ab0b0f265da572a0cf227