Java 8

Lambda表达式

在Java 8中,为了能够将行为参数化而引入了Lambda表达式。

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

语法

Lambda表达式在Java语言中引入了->操作符,->操作符被称为Lambda表达式的操作符或者箭头操作符,它将Lambda表达式分为两部分:

  • 左侧部分指定了Lambda表达式需要的所有参数,Lambda表达式本质上是对接口的实现,Lambda表达式的参数列表本质上对应着接口中方法的参数列表。

  • 右侧部分指定了Lambda体,即Lambda表达式要执行的功能,Lambda体本质上就是接口方法具体实现的功能。

Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器能够通过上下文推断出数据类型,这就是类型推断

  • 左侧部分只有一个参数时,小括号可以省略
  • Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器能够通过上下文推断出数据类型,这就是“类型推断”
public class LambdaTest {
    public void completedLeft() {
        // 类型String可以省略,由上下文进行类型推断;因为只有一个参数,小括号可以省略
        Consumer<String> consumer = (String e) -> println(e.substring(3, 6));
    }
    
    public void singleLeft() {
        Consumer<String> consumer = e -> println(e.substring(3, 6));
    }
}
  • 右侧部分只有一行语句时,大括号和return关键字可以省略
public class LambdaTest {
    public String completeRight() {
        Supplier<String> supplier = () -> {
            String result = new Random().nextInt(100) + "";
            return result.substring(1);
        };
        return supplier.get();
    }

    public String simpleRight() {
        Supplier<String> supplier = () -> new Random().nextInt(100) + "";
        return supplier.get();
    }
}
  • 无参无返回值
public interface INoArgsAndNoReturn {
    void execute();
}
public class LambdaTest {
    public void noArgsAndNoReturn() {
        INoArgsAndNoReturn lambda = () -> println("execute lambda");
        execute(lambda);
    }

    private void execute(INoArgsAndNoReturn lambda) {
        lambda.execute();
    }
}
  • 无参有返回值
public interface INoArgsAndHasReturn {
    String execute();
}
public class LambdaTest {
    public void noArgsAndHasReturn() {
        INoArgsAndHasReturn lambda = () -> "return lambda result";
        execute(lambda);
    }

    private void execute(INoArgsAndHasReturn lambda) {
        println(lambda.execute());
    }
}
  • 有参无返回值
public interface IHasArgsAndNoReturn {
    void execute(Integer one, Integer two);
}
public class LambdaTest {
    public void hasArgsAndNoReturn() {
        IHasArgsAndNoReturn lambda = (a, b) -> println(a + b);
        execute(lambda);
    }

    private void execute(IHasArgsAndNoReturn lambda) {
        lambda.execute(1, 2);
    }
}
  • 有参有返回值
public interface IHasArgsAndHasReturn {
    String execute(Integer one, Integer two);
}
public class LambdaTest {
    public void hasArgsAndHasReturn() {
        execute((x, y) -> x + " " + y);
    }

    private void execute(IHasArgsAndHasReturn argsReturn) {
        println(argsReturn.execute(1, 2));
    }
}

使用局部变量

Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为
final,或事实上是 final 。

对局部变量的限制

第一,实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。
如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分
配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它
的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。

第二,这一限制不鼓励你使用改变外部变量的典型命令式编程模式,这种模式会阻碍很容易做到的并行处理。

经验总结

  • lambda表达式通过传递代码,来使代码根据条件被延迟执行或不执行。
  • 多参数lambda通过创建匹配的函数式接口来实现。

接口默认方法

Java 8中接口添加了一个默认方法的新特性,为的是提供一个扩充接口而不会破坏现有代码的方式。

接口中的默认方法用 default 来表示,如下所示:

java.util.Collection

public interface Collection<E> extends Iterable<E> {
    // Collection 接口新增了获取并行流的默认方法,使得所有实现了 Collection 接口的类不在需要实现该方法即可调用
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

函数式接口

函数式接口定义及应用

只包含一个抽象方法的接口,允许有默认实现方法和静态实现方法的接口称为函数式接口。

可以在函数式接口上使用 @FunctionalInterface 注解,标注它是一个函数式接口,同时javadoc也会包
含一条声明,说明这个接口是一个函数式接口。

如果被 @FunctionalInterface 标注的接口不是一个函数式接口,编译器将返回一个提示原因的错误。

@FunctionalInterface
public interface ICalculator<M, N, R> {
    /**
     * 函数式接口只有一个抽象方法
     */
    R calculate(M m, N n);

    /**
     * 函数式接口可以有实现的默认方法
     */
    default void defaultPrint(R result){
        PrintUtil.println("默认方法输出结果:" + result);
    }

    /**
     * 函数式接口可以有实现的静态方法
     */
    static void staticPrint(String result){
        PrintUtil.println("静态方法输出结果:" + result);
    }
}
public class CalculatorDemo {
    public static void main(String[] args) {
        ICalculator<Integer, Integer, Integer> calculator = (x, y) -> x * 10 + y;
        // 执行函数式接口ICalculator的calculate方法
        Integer result = calculator.calculate(1, 2);
        // 执行函数式接口的默认方法
        calculator.defaultPrint(result);
        // 执行函数式接口的静态方法
        ICalculator.staticPrint(result.toString());
    }
}

核心函数式接口

函数式接口 参数类型 返回类型 描述
Consumer T void 对类型为T的对象应用操作。
Supplier T 返回类型为T的对象。
Function T R 对类型为T的对象应用操作,并R类型的返回结果。
Predicate T boolean 确定类型为T的对象是否满足约束条件,并返回boolean类型的数据。

Consumer接口

Consumer接口源码

java.util.function.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); };
    }
}

Consumer接口用例

public class ConsumerDemo {
    public static void main(String[] args) {
        String product = "产品";
        Consumer<String> consumer1 = str -> PrintUtil.println(str.concat("被消费了"));
        consumer1.accept(product);

        Consumer<String> consumer2 = str -> PrintUtil.print(str.concat("已售空,无法消费"));
        // 先执行对象consumer1的accept方法,在执行consumer2的accept方法
        consumer1.andThen(consumer2).accept(product);
    }
}

Supplier接口

Supplier接口源码

java.util.function.Supplier

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Supplier接口用例

public class SupplierDemo {
    public static void main(String[] args) {
        Supplier<Integer> supplier = () -> new Random().nextInt(100);
        PrintUtil.println("生成100以内随机数:" + supplier.get());
    }
}

Function接口

Function接口源码

java.util.function.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;
    }
}

Function接口用例

public class FunctionDemo {
    public static void main(String[] args) {
        Function<Integer, String> function = x -> "生成指定范围内的随机数:" + new Random().nextInt(x);
        PrintUtil.println(function.apply(10));  
    }
}

Predicate接口

Predicate接口源码

java.util.function.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
                : targetRef::equals;
    }
}

Predicate接口用例


原始类型特化函数式接口

Java 8在现有的函数式接口的基础上还提供了几种原始类型特化的函数式接口。这类函数式接口在输入输出时都是原始类型,避免了自动装箱的操作,从而提高了性能。

下表是一些常用函数式接口及其原始类型特化后的函数式接口对应表。

函数式接口 函数描述符 原始类型特化
Predicate<T> T->boolean IntPredicate,
LongPredicate,
DoublePredicate
Consumer<T> T->void IntConsumer,
LongConsumer,
DoubleConsumer
Function<T,R> T->R IntFunction<R>,
IntToDoubleFunction,
IntToLongFunction,
LongFunction<R>,
LongToDoubleFunction,
LongToIntFunction,
DoubleFunction<R>,
ToIntFunction<T>,
ToDoubleFunction<T>,
ToLongFunction<T>
Supplier<T> ()->T BooleanSupplier,
IntSupplier,
LongSupplier,
DoubleSupplier
UnaryOperator<T> T->T IntUnaryOperator,
LongUnaryOperator,
DoubleUnaryOperator
BinaryOperator<T> (T,T)->T IntBinaryOperator,
LongBinaryOperator,
DoubleBinaryOperator
BiPredicate<L,R> (L,R)->boolean
BiConsumer<T,U> (T,U)->void ObjIntConsumer<T>,
ObjLongConsumer<T>,
ObjDoubleConsumer<T>
BiFunction<T,U,R> (T,U)->R ToIntBiFunction<T,U>,
ToLongBiFunction<T,U>,
ToDoubleBiFunction<T,U>

Optional<T>取代null

新的时间和日期API

Stream流

构建流

由值创建流

由数组创建流

由文件生成流

无限流

处理数据

筛选和切片

映射

查找和匹配

归约

收集数据

归约和汇总

分组

分区

方法引用

需要使用方法引用时,目标引用放在分隔符 :: 前,方法的名称放在后面。

  • 指向静态方法的方法引用
@Test
public void methodReferences(){
    String str = "methodReferences";
    // String的length方法是一个静态方法,可以直接引用
    Optional.ofNullable(str).map(String::length);
}
  • 指向任意类型实例方法的方法引用

指向任意类型实例方法的方法引用的思想就是在引用一个对象的方法,而这个对象本身就是Lambda的一个参数。

@Test
public void methodReferences(){
    String str = "methodReferences";
    // 对象 str 本身就是Lambda的参数,可以指向任意类型实例方法的方法引用替换
    Optional.ofNullable(str).map(s -> s.toUpperCase());
    Optional.ofNullable(str).map(String::toUpperCase);
}
  • 指向现有对象的实例方法的方法引用

构造方法引用

构造函数可以利用它的名称和关键字new来创建它的引用:ClassName::new 。功能与指向静态方法的引用类似。

@Test
public void constructorMethodReferences(){
    // ArrayList就可以通过构造器来建立方法引用 ArrayList::new
    Supplier<List<String>> supplier = ArrayList::new;
    List<String> list = supplier.get();
}
posted @ 2021-10-20 16:18  94许芒芒  阅读(100)  评论(0)    收藏  举报