Java 8新特性

Lambda表达式

所谓Lambda表达式,也就是Dart中的闭包,作为传统Java开发者,不一定能很快理解此概念,但作为Dart开发者,应当是相当熟悉了。它允许我们将一个函数当作方法的参数进行传递,或者说把代码当作数据传递。

基本语法结构

(参数列表)-> 表达式;

// 或者带有花括号
(参数列表)-> {表达式;}

实例

    // 声明lambda 表达式
    ToLongBiFunction<Integer,Long> func = (Integer x, Long y) -> x + y;

    // 创建匿名内部类对象
    Runnable run1 = new Runnable() {  
        @Override  
        public void run() {  
            System.out.println("Hello world !");  
        }  
    };  

    // 使用lambda表达式简化匿名内部类
    Runnable run2 = () -> System.out.println("Hello world !");    

   // 一行代码也可以使用花括号包裹
   // (String name) -> {System.out.println(name);}

总的来说,Java中的Lambda表达式与Dart中的闭包相比,最多只能算半成品,最大的意义就是在某种情况下可以少写几行代码。使用Java的Lambda表达式有许多要注意的地方

  • Lambda表达式中可以访问局部变量,但不能修改

    Runnable get(String name){
        String hello = "hello";
        return () -> System.out.println(hello + "," + name);
    }
    
  • 用于方法参数的传递,不能直接执行Lambda表达式

  • 不能完全和匿名内部类等同,只在特定情况下可以替换匿名内部类

  • 主要场景用于为函数式接口的抽象方法提供实现

什么是函数式接口

有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数接口可以隐式地转换成lambda表达式。

// 函数式接口
public interface MyInterface {
    void work(String data);
}

使用Lambda表达式为函数式接口提供实现时,必须保证接口中的抽象方法的签名与Lambda表达式签名一致。

函数式接口存在一定缺陷,只要有人在接口里多添加一个方法,那么这个接口就不是函数接口了,就会导致编译失败。Java 8提供了一个特殊的注解@FunctionalInterface来克服此脆弱性,并且显示地表明函数接口的目的。

@FunctionalInterface
public interface MyInterface {
    void work(String data);
}

Java 8的java.util.function 包已经为我们提供了一些函数式接口

接口 描述
BiConsumer<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果
BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果
BinaryOperator<T> 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
BiPredicate<T,U> 代表了一个两个参数的boolean值方法
BooleanSupplier 代表了boolean值结果的提供方
Consumer<T> 代表了接受一个输入参数并且无返回的操作
DoubleBinaryOperator 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
DoubleConsumer 代表一个接受double值参数的操作,并且不返回结果。
DoubleFunction<R> 代表接受一个double值参数的方法,并且返回结果
DoublePredicate 代表一个拥有double值参数的boolean值方法
DoubleSupplier 代表一个double值结构的提供方
DoubleToIntFunction 接受一个double类型输入,返回一个int类型结果。
DoubleToLongFunction 接受一个double类型输入,返回一个long类型结果
DoubleUnaryOperator 接受一个参数同为类型double,返回值类型也为double 。
Function<T,R> 接受一个输入参数,返回一个结果。
IntBinaryOperator 接受两个参数同为类型int,返回值类型也为int 。
IntConsumer 接受一个int类型的输入参数,无返回值 。
IntFunction<R> 接受一个int类型输入参数,返回一个结果 。
IntPredicate 接受一个int输入参数,返回一个布尔值的结果。
IntSupplier 无参数,返回一个int类型结果。
IntToDoubleFunction 接受一个int类型输入,返回一个double类型结果 。
IntToLongFunction 接受一个int类型输入,返回一个long类型结果。
IntUnaryOperator 接受一个参数同为类型int,返回值类型也为int 。
LongBinaryOperator 接受两个参数同为类型long,返回值类型也为long。
LongConsumer 接受一个long类型的输入参数,无返回值。
LongFunction<R> 接受一个long类型输入参数,返回一个结果。
LongPredicate 接受一个long输入参数,返回一个布尔值类型结果。
LongSupplier 无参数,返回一个结果long类型的值。
LongToDoubleFunction 接受一个long类型输入,返回一个double类型结果。
LongToIntFunction 接受一个long类型输入,返回一个int类型结果。
LongUnaryOperator 接受一个参数同为类型long,返回值类型也为long。
ObjDoubleConsumer<T> 接受一个object类型和一个double类型的输入参数,无返回值。
bjIntConsumer<T> 接受一个object类型和一个int类型的输入参数,无返回值。
ObjLongConsumer<T> 接受一个object类型和一个long类型的输入参数,无返回值。
Predicate<T> 接受一个输入参数,返回一个布尔值结果。
Supplier<T> 无参数,返回一个结果。
ToDoubleBiFunction<T,U> 接受两个输入参数,返回一个double类型结果
ToDoubleFunction<T> 接受一个输入参数,返回一个double类型结果
ToIntBiFunction<T,U> 接受两个输入参数,返回一个int类型结果。
ToIntFunction<T> 接受一个输入参数,返回一个int类型结果。
ToLongBiFunction<T,U> 接受两个输入参数,返回一个long类型结果。
ToLongFunction<T> 接受一个输入参数,返回一个long类型结果。
UnaryOperator<T> 接受一个参数为类型T,返回值类型也为T。

默认方法

Java 8允许在接口内声明静态方法或者声明默认方法。也就是说现在Java接口也能提供方法的具体实现。并且实现接口的类如果不覆写该方法,就会自动继承默认的实现。

Java中,通常工具类定义了与接口实例协作的很多静态方法,现在静态方法可以存在于接口内部,那么代码中的那些辅助类就没有了存在的必要,就可以把这些静态方法转移到接口内部。

interface MyInterface {
    void work(String data);

    // 声明默认方法
    default void print(Object data){
        System.out.println(data);
    }
}

默认方法的意义

传统上,Java程序的接口是将相关方法按照约定组合到一起的方式。实现接口的类必须为接口中定义的每个方法提供一个实现,或者从父类中继承它的实现。但是,一旦类库的设计者需要更新接口,向其中加入新的方法,这种方式就会出现问题,那些接口实现类为了适配新接口约定都需要进行修改,现在有了默认方法,接口实现类会自动继承它,这样就可以使你平滑地进行接口的优化和演进。

方法引用

所谓方法引用,类似Dart中的将方法作为参数传递。即把Java中已经存在的方法作为参数传递。Java 8用Lambda表达式来实现匿名方法用方法引用来传递已经存在的方法,注意两者的区别。

主要有四种方法引用,参见官方文档-方法引用

类型 语法 对应的Lambda表达式
静态方法引用 类名::staticMethod (args) -> 类名.staticMethod(args)
特定对象的实例方法引用 expr::instMethod (args) -> expr.instMethod(args)
特定类型的任意对象的实例方法引用 类型名::instMethod (inst,args) -> inst.instMethod(args)
构造方法引用 类名::new (args) -> new 类名(args)

声明一个类,定义一些方法用于测试

public class Car {
    private String name;

    public Car(){}
    public Car(String name){
        this.name = name;
    }

    // 静态方法
    public static void print() {
        System.out.println("this is a static method");
    }

    // (实例方法)成员方法
    public void drive() {
        System.out.println("drive "+this.name);
    }

    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
}

声明一个测试类

public class Test{

    // Car 类构造方法做参数
    public Car testCreate(final Function<String,Car> func){
        // 调用了Car有参数的那个构造方法
        return func.apply("特斯拉");
    }

    // 特定对象的方法做参数
    public void testDrive(final Runnable runnable){
        runnable.run();
    }

    // 特定类型任意对象方法做参数
    public  void testRepair(final Consumer<Car> action){
        action.accept(new Car());
    }

    // 静态方法做参数
    public void testPrint(final Runnable runnable){
        runnable.run();
    }
}

验证

Test test = new Test();
// 构造方法引用
Car car = test.testCreate(Car::new);
// 特定对象方法引用
test.testDrive(car::drive);
// 特定类型任意对象方法引用
test.testRepair(Car::repair);
// 静态方法引用
test.testPrint(Car::print);

Optional

Optional 是一个容器,它可以保存一些类型的值或者null,它可以用来处理Java中棘手的空指针异常,但它更重大的意义是应用于函数式编程中。

举例:

String name = user.getAddress().getCountry().getName().toUpperCase();

如果其中有一环返回null值,将引发NullPointerException空指针异常。因此应当使用防御式编程:

String name = null;
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String name = country.getName();
            if (name != null) {
                name = name.toUpperCase();
            }
        }
    }
}

由此代码变得冗长臃肿,可读性差,难以维护。Dart中,在语法层面为我们提供了?.操作符避免此类问题,而Java中一直没有好的方式处理,直到Java 8增加了Optional

使用Optional改写上例:

String name = Optional.ofNullable(user)
                .map(u -> u.getAddress())
                .map(addr -> addr.getCountry())
                .map(country -> country.getName())
                .map(n -> n.toUpperCase())
                .orElse(null);
System.out.println(name);

Optional 三种构造方式:

  1. 声明一个空的Optional

    Optional<User> optUser = Optional.empty();
    
  2. 通过一个非空值创建Optional(如果是空值,则会报NullPointException

    Optional<User> user = Optional.of(new User());
    
  3. 可以接受null值的Optional

    Optional<User> optUser = Optional.ofNullable(user);
    

其中,OptionalisPresent方法可以用于判断保存的值是否为空,该方法返回一个布尔值;get方法可用于取出保存的值,但如果值为空,则会报NoSuchElementException异常。于是有人将isPresent方法和get方法结合起来使用,但这是不推荐的,如果这样组合,就与上述的加if语句判空差不多了。

推荐的使用方式:

  • ifPrensent:检查是否有值,值存在则调用传入的 Lambda 表达式

    Optional.ofNullable(person).ifPresent(p -> p.setAge(21));
    
  • orElse:如果有值则返回该值,否则返回传递给它的参数值

    name = Optional.ofNullable(name).orElse("");
    
  • orElseGet:与orElse类似,但传入的参数是一个Lambda 表达式

    name = Optional.ofNullable(name).orElseGet(()->"");
    
  • map:如果值存在,则将提供的映射函数应用于该值。其作用就是从Optional对象中提取和转换值。注意,该方法的返回值是将传入的 Lambda 表达式的返回值包装到一个新的Optional中返回,因此map方法可以无限级联。

Optional还有一个orElseThrow()方法,在有值时直接返回, 无值时抛出想要的异常。

Optional 的使用需要注意:

  • 尽量不要使用isPresentget方法
  • 尽量不要作为类或实例属性使用
  • 尽量不要作为方法参数使用

公众号“编程之路从0到1”

20190301102949549

Copyright © Arcticfox 2021 all right reserved,powered by Gitbook文档修订于: 2022-05-01 12:20:20

results matching ""

    No results matching ""