天天看点

【Java知识点】Java8新特性之Lambda表达式(适合初学者)

【Java知识点】Java8新特性之Lambda表达式(适合初学者)

目录

  • ​​🍇  Lambda表达式​​
  • ​​Lambda表达式介绍​​
  • ​​🥝  接口的默认方法和静态方法​​
  • ​​接口的默认方法​​
  • ​​接口的静态方法​​
  • ​​方法引用​​
  • ​​使用方式​​
  • ​​引用方法​​
  • ​​🍒nbsp; Optional​​
  • ​​创建方式​​
  • ​​常用的API​​
  • ​​Optioanl总结​​
  • ​​

🍇  Lambda表达式

在​

​JDK8​

​​之前,一个方法可以接受的参数都是变量,例如:​

​Object.method(Object obj);​

​​ 试想,如果需要传入的是一个动作呢?例如回调函数

参数传入的是一个动作随机就会想到Java中的匿名内部类,但是匿名内部类是需要依赖接口的,所以首先需要定义一个接口

自定义接口
@FunctionalInterface
public interface CustomCallback {
    void callback(Person person);
}      
自定义Person类
public class Person {
    private int id;
    private String name;

    public Person(int id,String name){
        this.id = id;
        this.name = name;
    }
    //创建一个回调方法
    public static void create(Integer id, String name, CustomCallback customCallback){
        Person person = new Person(id,name);
        customCallback.callback(person);
    }
}      
测试自定义回调函数
public class PersonCallbackMain {
    public static void main(String[] args) {
        Person.create(1, "张三", new CustomCallback() {
            @Override
            public void callback(Person person) {
                System.out.println("我是张三");
            }
        });

        Person.create(2, "李四", new CustomCallback() {
            @Override
            public void callback(Person person) {
                System.out.println("我是李四");
            }
        });
    }
}      

由上述示例中的CustomCallback其实就是一种动作,但是我们只需要真正关心的只有callback()里面的逻辑即可,如果使用Lambda表达式可以将示例中的代码进行优化:

Person.create(1, "张三", person -> {System.out.println("我是张三")});
      ...      

一经优化后可以很清楚的看出代码就会变得非常简单,一行就可以解决。但是在从示例中发现问题,示例中的​

​Person.create()​

​​明明接收的是自定义的​

​CustomCallback​

​​这个接口,但是现在却传入的是一个​

​Lambda​

​​表达式,难道是这个表达式实现了这个自定义接口吗?首先带着问题去了解一下什么是​

​Lambda​

​表达式

Lambda表达式介绍

​Lambda​

​​表达式允许把函数以一个参数的形式传入到方法中,而​

​Lambda​

​​表达式是由三部分组合而成的

● 由逗号分隔的参数列表

● ​​

​->​

​​ 符号

● 函数体

Person.create(1, "张三", person -> {System.out.println("我是张三")});
解释:
    其中的 person 为Lambda表达式的入参,如果有多个参数即将参数使用括号进行包括起来(param1,param2,...)
    {System.out.println("我是张三")} 由大括号包裹起来的就是函数体内容,如果函数体中只有一条语句时
    也可以将大括号省略不写即=> person -> System.out.println("我是张三")      

当实现一个接口的时候,实现接口中的方法是毋庸置疑的,那么使用​

​Lambda​

​​表达式亦如此需要遵守这样的一个基本原则,那么​

​Lambda​

​​表达式它实现了接口中的什么方法呢?

一个​​

​Lambda​

​表达式实现了接口中有且仅有的唯一一个抽象方法,那么对于这种接口就叫做函数式接口

函数式接口

用​​

​@FunctionalInterface ​

​​修饰的接口叫做函数式接口,函数式接口就是一个只具有抽象方法的普通接口,​

​@FunctionalInterface​

​ 可以起到校验的作用

使用@FunctionalInterface标注的接口类中只有一个抽象方法可以编译正确
@FunctionalInterface
public interface TestFunctionalInterface {
    void method1();
}      
使用@FunctionalInterface标注的接口类中只有多个抽象方法编译出现错误
@FunctionalInterface
public interface TestFunctionalInterface {
    void method1();
    void method2();
    ...
}      

其实函数式接口在JDK1.7时就已经存在,比如创建多线程时需要实现的​

​Runnable、Callable​

​​接口

在​​

​JDK1.8​

​​也增加了很多函数式接口,例如​

​java.util.function​

​包下

Supplier 无参数,返回一个结果
【Java知识点】Java8新特性之Lambda表达式(适合初学者)
Function 接收一个参数,返回一个结果
【Java知识点】Java8新特性之Lambda表达式(适合初学者)
Consumer 接收一个输入参数,无返回结果
【Java知识点】Java8新特性之Lambda表达式(适合初学者)
Predicate 接收一个输入参数,返回一个布尔值结果
【Java知识点】Java8新特性之Lambda表达式(适合初学者)
一个Lambda表达式可以理解为一个函数式接口的实现者,但是作为表达式,它的写法是多种多样的

● ​

​() -> {return 0; } ​

​​ 没有传入参数,有返回值

● ​​

​(int i ) -> {return 0; }​

​​ 传入一个参数,有返回值

● ​​

​(int i) -> {System.out.println(i);}​

​​ 传入一个​

​int​

​​类型的参数,但是没有返回值

●​​

​(int i,int j) -> {System.out.println(i);}​

​​传入两个​

​int​

​​类型的参数,但是没有返回值

● ​​

​(int i,int j) -> {return i+j;} ​

​​传入两个​

​int​

​​类型的参数,返回一个​

​int​

​​值

● ​​

​(int i,int j) -> {return i>j;}​

​​ 传入两个​

​int​

​​类型的参数,返回一个​

​boolean​

​​ ● ​

​ . . . . .​

​等等

总结:如果没有函数式接口,就不能编写​

​Lambda​

​表达式

🥝  接口的默认方法和静态方法

在​

​JDK1.7​

​​中,如果想对​

​Collection​

​​接口新增一个方法,则需要修改它所有的实现类源码

那么在​​

​JDK1.8​

​为了解决这个问题也就是使用抽象类,示例如下:

现在有一个接口CustomInterface,里面有一个抽象方法
public interface CustomInterface(){
    void Car();
}      
有三个实现类
public class BMW implements CustomInterface{
    @Override
    public void Car() {
        System.out.println("BMW");
    }
}

public class DZ implements CustomInterface{
    @Override
    public void Car() {
        System.out.println("DZ");
    }
}

public class BC implements CustomInterface{
    @Override
    public void Car() {
        System.out.println("BC");
    }
}      

如果我需要对​

​CustomInterface​

​​接口中新增一个方法时,那么示例中的三个实现类都必须做出相应的改变才能编译通过,会增加许多冗余的实现和操作。解决办法则可以增加一个抽象类​

​CustomAbstract​

​​,示例中的三个实现类只需要改成继承这个抽象类即可,这样如果后期想要新增一些方法时,只需要修改​

​CustomAbstract​

​类即可,其他的类就不需要改动了。

public abstract class CustomAbstract implements CustomInterface{
    @Override
    public void Car() {
        System.out.println("do something ...");
    }
}      

那么在​

​JDK1.8​

​​中支持直接在接口中添加已经实现了的方法,一种是​

​Default​

​​方法(默认方法),一种是​

​static​

​静态方法

接口的默认方法

在接口中用​

​default​

​修饰的方法称为 默认方法

接口中的默认方法一定要有默认实现(方法体),接口实现者可以继承它,也可以覆盖它

default void testDefault(){
    System.out.println("I am default");
}      

接口的静态方法

在接口中用​

​static​

​修饰的方法称为 静态方法

static void testStatic(){
    System.out.println("I am static");
}      
调用方式
CustomInterface.testStatic();      

因为有了默认方法和静态方法,所以不用去修改它的实现类,可以直接调用即可!

方法引用

有个函数接口​

​Consumer​

​​,里面有个抽象方法​

​accept()​

​​能够接收一个参数但是没有返回值,这时候如果想实现​

​accept()​

​​并使用这个功能打印接收的参数,使用​

​Lambda​

​表达式怎么去编写:

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

但是这个打印功能​

​(System.out.println)​

​​在​

​PrintStream​

​类中已经实现了,打印的这一步还可以再简单点

Consumer<String> consumer = System.out::println;
consumer.accept("pdx");      

这就是方法引用,方法引用方法的参数列表必须和函数式接口的抽象方法的参数列表保持一致,返回值不作要求。

使用方式

● 引用方法

● 引用构造函数

● 引用数组

引用方法

● 实例对象 :: 实例方法名

● 类名 :: 静态方法名

● 类名 :: 实例方法名

实例对象 :: 实例方法名
Consumer<String> consumer = System.out::println;
consumer.accept("pdx");      
类名 :: 静态方法名
//Function<Long,Long> f = aLong -> Math.abs(aLong);
Function<Long,Long> function = Math::abs;
function.apply(-3L);      

​Math​

​​是一个类而​

​abs​

​​是该类的静态方法,​

​Function​

​​中唯一抽象方法​

​apply()​

​​参数列表与​

​abs()​

​​的参数列表相同,都接收一个​

​Long​

​类型参数。

类名 :: 实例方法名

如果​

​Lambda​

​表达式的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,就可以使用这种方法

//BiPredicate<String,String> predicate = (x,y) ->x.equals(y);
BiPredicate<String,String> pre = String::equals;
boolean isEqual = pre.test("a", "b");      
引用构造器

在引用构造器时,构造器的参数列表要与接口中抽象方法的参数列表一致,格式为​

​类名::new​

//Function<Integer,StringBuffer> fun = n -> new StringBuffer();
Function<Integer,StringBuffer> fun = StringBuffer::new;
StringBuffer buffer = fun.apply(10);      

​Function​

​​接口的​

​apply()​

​​接收一个参数,并且有返回值,在这里接收的参数是​

​Integer​

​​类型,与​

​StringBuffer​

​​类的一个构造方法​

​StringBuffer(int capacity)​

​​对应,而返回值就是​

​StingBuffer​

​​类型

​​

​Function​

​​实例,并把它​

​apply()​

​​实现为创建一个指定初始大小的​

​StringBuffer​

​​对象

【Java知识点】Java8新特性之Lambda表达式(适合初学者)
引用数组

引用数组和引用构造器很像,格式为​

​类型[] :: new​

​其中类型可以为基本类型可以是类

//Function<Integer,Integer[]> fun = n -> new Integer[n];
Function<Integer,Integer[]> fun = Integer[]::new;
Integer[] arr = fun.apply(10);      

🍒nbsp; Optional

​空指针异常​

​​是导致​

​Java​

​​应用程序失败的最常见原因,为了解决空指针异常,引入了​

​Optional​

​​类,通过使用检查空值的方式来防止代码污染。​

​Optional​

​​实际上是一个容器:它可以保存类型​

​T​

​​的值,或者仅仅保存​

​null​

​​,​

​Optional​

​提供很多有用的方法,这样就不用显式进行空值校验。

创建方式

创建​

​Optional​

​​对象的几个方法:

● ​​

​Optional.of(T val) ​

​​ 返回一个​

​Optional​

​​对象,​

​val​

​​不能为空,否则出现空指针校验

【Java知识点】Java8新特性之Lambda表达式(适合初学者)

● ​

​Optional.ofNullable(T val) ​

​​返回一个​

​Optional​

​​对象,​

​val​

​​可以为空

【Java知识点】Java8新特性之Lambda表达式(适合初学者)

●​

​ Optional.empty()​

​代表空

【Java知识点】Java8新特性之Lambda表达式(适合初学者)

常用的API

● ​

​Optional.isPresent() ​

​​ 是否存在值(不为空)

● ​​

​Optional.ifPresent(Consumer<?super T>consumer) ​

​​如果存在值则执行​

​consumer​

​​ ● ​

​Optional.get() ​

​获取​

​value​

​ ● ​

​Optional.orElse(T other) ​

​如果没值则返回​

​other​

​ ● ​

​Optional.orElseGet(Supplier<-?extends T:>other) ​

​如果没值则执行​

​other​

​并返回

● ​

​Optional..orElseThrow(Supplier<-?extends X>exceptionSupplier)​

​ 如果没值则执行

​exceptionSupplier​

​并抛出异常

对比之前防止空指针和使用Optional的区别
public class Product {
    String name;
    
    public String getProductName(Product product){
        if (product == null){
            return null;
        }
        return product.name;
    }
}

//使用Optional进行改写
public class Product {
    String name;

    public String getProductName(Product product){
        Optional<Product> productOptional = Optional.ofNullable(product);
        if (!productOptional.isPresent()){
            return null;
        }
        return productOptional.get().name;
    }
}      

从上述示例中的更改其实并没有实质性的分别,反而代码量增加了不少,事实上示例中的​

​isPresent()​

​​与​

​obj != null ​

​​并无区别,并且在使用get()之前最好都使用​

​isPresent()​

​​,不然在​

​IDEA​

​​ 中会出现提示信息

【Java知识点】Java8新特性之Lambda表达式(适合初学者)

对于上述对比代码块中使用Optional还可以优化为一行代码:

public class Product {
    String name;    
    public String getProductName(Product product){
//        Optional<Product> productOptional = Optional.ofNullable(product);
//        if (!productOptional.isPresent()){
//            return null;
//        }
        return Optional.ofNullable(product).map(pro -> pro.name).orElse(null);
    }
}      

Optioanl总结