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

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

🏡  博客首页:派 大 星

⛳️  欢迎关注  ❤️ 点赞  🎒 收藏  ✏️ 留言

🎢  本文由派大星原创编撰

🚧  系列专栏:Java知识点

🎈  本系列记录学习Java的相关知识与深入思考历程,如有描述有误的地方还望诸佬不吝赐教
🏝  本系列内容会记录在派大星的语雀,后续同步到CSDN平台


在这里插入图片描述


目录

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

🍇  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 无参数,返回一个结果

在这里插入图片描述

Function 接收一个参数,返回一个结果

在这里插入图片描述

Consumer 接收一个输入参数,无返回结果

在这里插入图片描述

Predicate 接收一个输入参数,返回一个布尔值结果

在这里插入图片描述

一个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 f = aLong -> Math.abs(aLong);
Function<Long,Long> function = Math::abs;
function.apply(-3L);

Math是一个类而abs是该类的静态方法,Function中唯一抽象方法apply()参数列表与abs()的参数列表相同,都接收一个Long类型参数。

类名 :: 实例方法名

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

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

引用构造器

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

//Function 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对象
在这里插入图片描述

引用数组

引用数组和引用构造器很像,格式为类型[] :: new其中类型可以为基本类型可以是类

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

🍒nbsp; Optional

空指针异常是导致Java应用程序失败的最常见原因,为了解决空指针异常,引入了Optional类,通过使用检查空值的方式来防止代码污染。Optional实际上是一个容器:它可以保存类型T的值,或者仅仅保存nullOptional提供很多有用的方法,这样就不用显式进行空值校验。

创建方式

创建Optional对象的几个方法:
Optional.of(T val) 返回一个Optional对象,val不能为空,否则出现空指针校验
在这里插入图片描述

Optional.ofNullable(T val) 返回一个Optional对象,val可以为空
在这里插入图片描述

Optional.empty()代表空

在这里插入图片描述

常用的API

Optional.isPresent() 是否存在值(不为空)
Optional.ifPresent(Consumerconsumer) 如果存在值则执行consumer
Optional.get() 获取value
Optional.orElse(T other) 如果没值则返回other
Optional.orElseGet(Supplierother) 如果没值则执行other并返回
Optional..orElseThrow(SupplierexceptionSupplier) 如果没值则执行
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 中会出现提示信息
在这里插入图片描述

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

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

Optioanl总结

使用Optioanal时尽量不要直接调用Optional.get()方法,Optional.isPresent()更应该被视为一个私有方法,应依赖于其他像Optional.orElse(),Optional.orElseGet(),Optional.map()等这样的方法。

🔔 🔔 🔔 E n d i n g 🔔 🔔 🔔 🔔 🔔 🔔 Ending 🔔 🔔 🔔 🔔🔔🔔Ending🔔🔔🔔

在这里插入图片描述