Java8 新特性 —— Lambda 表达式、函数接口以及方法引用详解


一、Lambda表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java的语言表达能力得到了提升。

在以往的开发中,我们使用方法传递主要是通过接口使用匿名内部类的方式:

TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return Integer.compare(o1.length(), o2.length());
    }
});

当使用 Lambda 表达式时,则可以使用以下方式:

TreeSet<String> t2 = new TreeSet<>((o1, o2) -> Integer.compare(o1.length(), o2.length()));

Lambda 表达式的本质:作为接口的实例。

语法

(parameters) -> expression

(parameters) -> { statements; }

Lambda 表达式在 Java 语言中引入了一个新的语法元素和操作符。这个操作符为 -> , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为 两个部分:

  • 左侧:指定了 Lambda 形参列表(其实就是接口中的抽象方法的形参列表)
  • 右侧:指定了 Lambda 体(其实就是重写的抽象方法的方法体)

Lambda 表达式语法分为以下六类:

//语法格式一:无参,无返回值,Lambda体只需一条语句
Runnable l1 = () -> System.out.println("Hello Lambda");

//语法格式二:Lambda需要一个参数
Consumer<String> l2 = (x) -> System.out.println(x);

//语法格式三:Lambda只需要一个参数时,参数的小括号可以省略
Consumer<String> l3 = x -> System.out.println(x);

//语法格式四:Lambda需要两个参数,并且有返回值
BinaryOperator<Long> l4 = (x, y) ->{
    System.out.println("Lambda体有两条语句");
    return x + y;
};

//语法格式五:当Lambda体只有一条语句时,return与大括号可以省略
BinaryOperator<Long> l5 = (x, y) -> x + y;

//语法格式六:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
BinaryOperator<Long> l6 = (Long x, Long y) ->{
    System.out.println("类型推断");
    return x + y;
};

优势

  • 简洁性: Lambda 表达式大大简化了代码的编写方式,尤其是在处理匿名内部类和函数式接口时。它避免了大量的样板代码,使得代码更加简洁易读。
  • 灵活性: 它提供了一种更加灵活的方式来传递代码逻辑。我们可以根据需要随时创建和使用 Lambda 表达式,而不需要像传统方式那样定义一个专门的类来实现接口。
  • 与函数式编程的融合: Lambda 表达式是 Java 向函数式编程迈进的重要一步。它使得 Java 开发者能够更好地利用函数式编程的思想和技术,提高代码的质量和可维护性。

二、函数式接口

想要对Lambda表达式有一个深入的理解,我们需要去认识另外一个知识点,那就是函数式接口。在上面我们的举得例子中比如Consumer或者是BinaryOperator为什么能够使用Lambda呢?就是因为实函数式接口,那么什么是函数式接口?

  • 只包含一个抽象方法的接口,称为函数式接口。

  • 我们可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口。

@FunctionalInterface
public interface MyInterface<T> {
    public T getValue(T t);
}

如果不是抽象接口,@FunctionalInterface则会保错

在这里插入图片描述

四种内置核心函数

简而言之,接口要想使用Lambda表达式,就必须是函数式接口,为了方便日常开发需求,java也内置了许多函数式接口,下面介绍四种核心的函数式接口:

函数式接口参数类型返回类型用途
Consumer<T> 消费型接口Tvoid对类型为T的对象应用作,包含方法: void accept(T t)
Supplier<T> 供给型接口T返回类型为T的对象,包含方法:T get( );
Function<T, R> 函数型接口TR对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t);
Predicate<T> 断定型接口Tboolean确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法 boolean test(T t);

1、Consumer<T>消费型接口

// Consumer<T> 消费型接口:
@Test
public void test1(){
    happy(100, (m) -> System.out.printf("今天消费:" + m + " 元"));
}

private void happy(double money, Consumer<Double> consumer){
    consumer.accept(money);
}

2、Supplier<T> 供给型接口

// Supplier<T> 供给型接口
@Test
public void test2(){
    List<Integer> n1 = getNumList1(10, () -> (int) (Math.random() * 100));
    System.out.println(n1);
    List<Integer> n2 = getNumList2(10, () -> (int) (Math.random() * 100));
    System.out.println(n2);
}

//差生指定个数的整数, 并放入集合中
public List<Integer> getNumList1(int num, Supplier<Integer> supplier){
    List<Integer> list = new ArrayList<>();

    for (int i = 0; i < num; i++) {
        list.add(supplier.get());
    }
    return list;
}

//在后续我们会学习Stream类,先来混个脸熟
public List<Integer> getNumList2(int num, Supplier<Integer> supplier){
    return IntStream.range(0, num)
        .mapToObj(i -> supplier.get())
        .collect(Collectors.toList());
}

3、Function<T, R> 函数型接口

// Function<T, R> 函数型接口
@Test
public void test3(){
    String t1 = transform("hello function", (str) -> str.toUpperCase());
    System.out.println(t1);
    String t2 = transform("hello function", (str) -> str.toLowerCase());
    System.out.println(t2);
}

// 用于处理字符串
public String transform(String str, Function<String, String> function){
    return function.apply(str);
}

4、Predicate<T> 断言型接口

// Predicate<T> 断言型接口
@Test
public void test4(){
    List<String> list = Arrays.asList("Hello", "Java", "Lambda", "Function", "Stream");
    List<String> stringList1 = filterStr1(list, s -> s.length() > 4);
    System.out.println(stringList1);
    List<String> stringList2 = filterStr2(list, s -> s.length() > 4);
    System.out.println(stringList2);
}

//将满足条件的字符串放入集合
public List<String> filterStr1(List<String> list, Predicate<String> predicate){
    List<String> strList = new ArrayList<>();

    for (String s : list) {
        if (predicate.test(s)){
            strList.add(s);
        }
    }
    return strList;
}

//在后续我们会学习Stream类,先来混个脸熟
public List<String> filterStr2(List<String> list, Predicate<String> predicate){
    return list.stream()
            .filter(x -> predicate.test(x))
            .collect(Collectors.toList());
}

其他接口

除了以上四种核心接口,java还内置了其他的一些函数接口 :

函数式接口参数类型返回类型用途
BiFunction<T, U, R>T, UR对类型为T, U参数应用操作,返回R类型的结果。包含方法为 Rapply(T t,U u);
UnaryOperator<T> (Function子 接口)TT对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为 Tapply(T t);
BinaryOperator<T, U> (BiFunction子接口)T, TT对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为 Tapply(T t1,T t2);
BiConsumer<T, U>T, Uvoid对类型为T, U参数应用操作。包含方法为 void accept(T t, U u)
ToIntFunction<T>
ToLongFunction<T>
ToDoubleFunction<T>
Tint
long
double
对类型为T, U参数应用操作。包含方法为 void accept(T t, U u)
IntFunction<R>
LongFunction<R>
DoubleFunction<R>
int
long
double
R参数分别为int、long、 double类型的函数

三、方法引用和构造器引用

方法引用

若 Lambda 体中的内容有方法已经实现了, 我们可以使用“方法引用”,(可以理解为方法引用是 Lambda 表达式的另一种表现形式)

方法引用的语法有三种:

  • 对象 : : 实例方法
  • 类 : : 静态方法
  • 类 : : 实例方法

1、对象 : : 实例方法

// 对象::实例方法 
@Test 
public void test1(){
   Employee employee = new Employee("Lambda表达式...", "方法引用...");
   // 原写法
   Supplier<String> s1 = () -> employee.getS1();
   System.out.println(s1.get());
   // 对象::实例方法
   Supplier<String> s2 = employee::getS2;
   System.out.println(s2.get()); 
}

class Employee{
   private String s1;
   private String s2;

   public Employee(String s1, String s2) {
       this.s1 = s1;
       this.s2 = s2;
   }

   public String getS1() {
       return s1;
   }
   public String getS2() {
       return s2;
   }
}

2、类 : : 静态方法

// 类::静态方法 
@Test 
public void test2(){
	// 原写法
	Comparator<Integer> c1 = (x, y) -> Integer.compare(x, y);
	// 类::静态方法
	Comparator<Integer> c2 = Integer::compare; 
} 

Integer.compare( ) 原码:

public static int compare(int x, int y) {
   return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

3、类 : : 实例方法

// 类::实例方法 
@Test 
public void test3(){
    // 原写法
    BiPredicate<String, String> p1 = (x, y) -> x.equals(y);
    // 类::实例方法
    BiPredicate<String, String> p2 = String::equals; 
}

String 的equals( ) 原码:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
	return (anObject instanceof String aString)
    		&& (!COMPACT_STRINGS || this.coder == aString.coder)
    		&& StringLatin1.equals(value, aString.value); 
 }

注意:

  1. Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值一致;

  2. Lambda 参数列表的第一个参数是实例方法的调用者, 第二个方法是实例方法的参数时可以使用 “类 : : 实例方法”

构造器引用

与函数式接口相结合,自动与函数式接口中方法兼容。 可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!

格式:ClassName : : new

// 构造器引用 ClassName::new
@Test
public void test5(){
    // 构造器的使用取决于 方法的参数与构造方法的参数相匹配
    Supplier<Student> s1 = () -> new Student();
    Supplier<Student> s2 = Student::new;
    System.out.println(s2.get().name);

    Function<String, Student> f1 = (x) -> new Student(x);
    Function<String, Student> f2 = Student::new;
    System.out.println(f2.apply("张三").name);
}

class Student{
    private String name;

    public Student() {
    }

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

四、总结

Lambda 表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。
优点:

  • 代码简洁,开发迅速
  • 方便函数式编程
  • 非常容易进行并行计算
  • Java 引入 Lambda,改善了集合操作

缺点:

  • 代码可读性变差
  • 在非并行计算中,很多计算未必有传统的 for 性能要高
  • 不容易进行调试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值