Lambda从入门到精通(一篇搞懂)


​ 从JDK1.8开始为了简化使用者进行代码的开发,专门提供有Lambda表达式的支持,利用此操作可以实现函数式的编程,对于函数式编程比较著名的语言有:Haskell、Scala,利用函数式的编程可以避免掉面向对象编程之中一些繁琐的处理问题。

范例:观察传统开发中的问题

interface IMessage {
    public void send(String str);
}

public class JavaDemo {
    public static void main(String[] args) {
        IMessage i = new IMessage() {
            public void send(String str) { 
                System.out.println(str);
            }
        };
        i.send("www.baidu.com");
    }
}

在这样的程序里,实际上核心的功能只有一行语句System.out.println(str),但是为了这一行的核心语句,我们仍然需要按照完整的面向对象给出的结构进行开发。于是这些问题随着技术的发展也是越来越突出了。

范例:使用Lambda表达式实现与之前完全一样的功能

lambda 表达式的形式为 () -> {}; () 里面是函数式接口中抽象方法的形参, {} 中是抽象方法的实现。

interface IMessage {
    public void send(String str);
}

public class JavaDemo {
    public static void main(String[] args) {
        IMessage i = (str) -> {
            System.out.println(str);
        };
        i.send("www.baidu.com");
    }
}

现在在代码中就十分简洁了,只写了关键语句System.out.println(str) ,于是利用这种形式就避免了面向对象复杂的结构化要求。这便是Lambda表达式的基本处理形式。

Lambda表达式如果要想使用,那么必须要有一个重要的实现要求:SAM(Single Abstract Method),接口中只有一个抽象方法。以IMessage接口为例,这个接口里面只是提供有一个send()方法,除此之外没有任何其他方法定义,所以这样的接口就被称为函数式接口,而只有函数式接口才能被Lambda表达式所使用。

范例:错误的定义

interface IMessage {
    public void send(String str);
    public void say();
}

public class JavaDemo {
    public static void main(String[] args) {
        IMessage i = (str) -> {
            System.out.println(str);
        };
        i.send("www.baidu.com");
    }
}

在这里插入图片描述

所以很多时候为了明确的标注出你是一个函数式接口,往往会在接口上面增加一行注释@functionalInterface。但是,默认方法和静态方法不会破坏函数式接口的定义。

之所以在JDK1.8之后提供有默认和静态方法,也都是为函数式开发做准备。

Lambda表达式的几种格式

  • 方法没有参数: () -> {};

  • 方法有参数::(参数,…,参数) -> {};

下面看几个例子:

使用Lambda表达式(无参)

要创建接口 IMessage 的实现类,如果该类只是使用一次,我们可以使用匿名内部类的方式,但是匿名内部类写起来很麻烦。而 IMessage 接口中,只有一个抽象方法,是一个函数式接口,那么我们就可以使用 lambda 来代替匿名内部类。lambda 体就是接口的实现。

@FunctionalInterface
interface IMessage {
    public void send();
}

public class JavaDemo {
    public static void main(String[] args) {
        IMessage i = () -> {
            System.out.println("www.baidu.com");
        };
        i.send();
    }
}

上述写法还是有点麻烦,在 -> 右边的方法体中,如果只有一行语句,那么 可以省略大括号,直接一行搞定。

@FunctionalInterface
interface IMessage {
    public void send();
}

public class JavaDemo {
    public static void main(String[] args) {
        IMessage i = () -> System.out.println("www.baidu.com");
        i.send();
    }
}

注:抽象方法如果没有参数,则 lambda 表达式不能省略 ();

使用Lambda表达式(有参)

IMath 接口中只有一个抽象方法,该方法有返回值,且有两个参数。可以使用 lambda 进行简化。

@FunctionalInterface
interface IMath {
    public int add(int x, int y);
}

public class JavaDemo {
    public static void main(String[] args) {
    	// t1,t2是形参名,随便取,但是个数必须匹配形参
        IMath math = (t1, t2) -> { 
            return t1 + t2;
        };
        System.out.println(math.add(20, 30));
    }
}

以上的表达式之中你会发现只有一行语句“ return t1 + t2;”,这时候可以进一步简化。

使用Lambda表达式简化(再度简化Lambda表达式,把return语句也省略)

@FunctionalInterface
interface IMath{
	public int add(int x , int y);
}
public class Demo01 {
		IMath math = (n1,n2)-> n1 + n2;
		System.out.println(math.add(10,20));
	}
	
}

利用Lambda表达式确实可以使得代码更加简便。

Lambda 表达式使用总结

  • lambda 表达式形式为 ()->{},-> 左边是抽象方法的形参列表, -> 是抽象方法的实现体。
  • lambda 方法如果没有参数或有两个及以上的参数,则 小括号不能省略
  • lambda 方法如果只有一个参数,则小括号可以省略
  • lambda 方法体如果只有一行语句,则 大括号和return都可省略。
  • 省略了大括号,则必须省略 return,省略了 return ,则必须省略 {},这俩要么成对出现,要么都不出现。

lambda 的本质就是函数式接口的一个实现类。

练习

Java中Comparator 接口使得开发人员可以定制排序。在 Arrays 的 sort 方法中,就可以传入一个 Comparator 接口,进行定制排序。那么我们就可以使用 lambda 接口来简化 Comparator 的实现。

匿名内部类原始写法如下

	public void test5() {
        Comparator<Integer> com=new Comparator<Integer>(){
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };
    }

lambda 简化
compare 方法需要两个参数,返回一个 int 类型的值。那么 () 中两个参数,return Integer重写的比较方法即可。

	public void test5() {
        Comparator<Integer> com= (o1,o2)->{return Integer.compare(o1,o2);};
    }

还可以使用 方法引用 进一步简化:

	public void test5() {
        Comparator<Integer> com= Integer::compare;
    }

方法引用

方法引用的三种形式

  • 对象 :: 非静态方法

  • 类 :: 静态方法
  • 类 :: 非静态方法

注:方法引用规定,对象不能调用静态方法,这和面向对象的思想一致。但类可以调用非静态方法,这是面向对象中不允许的。

类引用静态方法

语法格式: 类名称::static方法名称

第一步,我们自定义一个接口,该接口中只有一个抽象方法,是一个函数式接口。

第二步,随便建立一个类,创建一个方法。这里要注意,创建的方法返回值类型和形参列表必须和函数式接口中的抽象方法相同。

第三步,创建函数式接口的实现类,我们可以使用方法引用。相当于实现类里的重写的方法,就是方法引用的方法。这样才能方法引用。

@FunctionalInterface
interface IMessage<T,P> {
    public T transfer(P p);
}

class Supplier{
    public static String getStr(Integer integer) {
        return String.valueOf(integer);
    }
}

public class JavaDemo {
    public static void main(String[] args) {
       IMessage<String, Integer> msg = Supplier::getStr;
        System.out.println(msg.transfer(31415926));
    }
}

对象引用非静态方法

语法格式: 实例化对象::普通方法;

有了类引用静态方法的基础,相信大家已经有了一点感觉。

对象引用非静态方法,和类引用静态方法一致。要求我们对象引用的方法,返回值和形参列表要和函数式接口中的抽象方法相同。

@FunctionalInterface
interface IMessage {
    public double get();
}

class Supplier{
    private Double salary;

    public Supplier() {
    }

    public Supplier(Double salary){
        this.salary = salary;
    }
    public Double getSalary() {
        return this.salary;
    }
}

public class JavaDemo {
    public static void main(String[] args) {
        Supplier supplier = new Supplier(9999.9);
        IMessage msg = supplier::getSalary;
        System.out.println(msg.get());
    }
}

类引用普通方法

语法格式: 类::普通方法

类引用普通方法就有点难以理解了。

当抽象方法中有两个参数,且第一个参数是调用者,第二个参数是形参,则可以使用类::实例方法。

@FunctionalInterface
interface IMessage<T, P> {
	// 要看成 T res = p1.compare(p2);
    public T compare(P p1, P p2);
}

@Data
class Person {
    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    private String name;
    private Integer age;

    public boolean equal(Person per) {
        return this.name.equals(per.getName()) && this.age.equals(per.getAge());
    }
}

public class JavaDemo {
    public static void main(String[] args) {
        Person person1 = new Person("张三", 22);
        Person person2 = new Person("张三", 23);
        // 符合T res = p1.compare(p2);
        IMessage<Boolean, Person> msg = Person::equal;
        System.out.println(msg.compare(person1, person2));

    }
}

再来一个例子:

@FunctionalInterface
interface IMessage<T, P, V> {
	// 看成 T res = p1.compare(p2);
	// public int compareTo(String anotherString){} 符合抽象方法的格式
	// int res = str1.compare(str2);
    public T compare(P p1, V p2);
}

public class JavaDemo {
    public static void main(String[] args) {
        IMessage<Integer,String,String> stringCompare = String::compareTo;
        Integer compare = stringCompare.compare("adc", "abd");
        System.out.println(compare);
    }
}

刚开始可能不适应,需要慢慢体会。

构造引用

语法格式: 类名称::new

@FunctionalInterface
interface IMessage<T, V> {
	//  public T create(P p1,V p2); 符合抽象方法的要求
    public T create(P p1, V p2);
}

@Data
class Person {
    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }
	// 符合 public T create(P p1,V p2); 要求,故可以构造引用
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    private String name;
    private Integer age;
}

public class JavaDemo {
    public static void main(String[] args) {
        IMessage<Person,Integer> msg = Person::new;
        Person person = msg.create("张三", 20);
        System.out.println(person);
    }
}

JDK8自带函数式接口

在JDK1.8之中,提供有Lambda表达式和方法引用,但是你会发现如果由开发者自己定义函数式的接口,往往都需要使用@FunctionalInterface来进行大量的声明,于是很多的情况下如果为了方便则可以引用系统中提供的函数式接口。

在系统之中专门提供有一个java.util.functional的开发包,里面可以直接使用函数式接口,在这个包下面一共有如下几个核心的接口供我们使用。

功能型函数式接口

接口定义 接口作用 接口使用
@FunctionalInterface
public interface Function<T,R>{ public R apply(T t); }
消费 T 类型参数,返回 R 类型结果 如下所示

function 相当于是给一个参数,然后返回一个结果。
如果是给两个参数,返回一个结果,那么就是 BiFunction。Bi 前缀即使 binary 的缩写。

import java.util.function.*;
/*
  @FunctionalInterface
  T是参数类型
  R是返回类型
  public interface Function<T,R>{
  	public R apply(T t);
  }
 */
class StringCompare {
	// 给一个 String 类型的参数,返回布尔类型,符合功能性函数式接口的抽象方法
    public static boolean test(String t) {
        return t == null;
    }
}
public class JavaDemo {
    public static void main(String[] args) {
    	// 直接静态引用
        Function<String,Boolean> func1 = StringCompare::test;
        System.out.println(func1.apply(null));
    }
}

消费型函数式接口

消费性函数式接口,只能进行数据的处理操作,而没有返回值

​ · 在进行系统输出的时候使用的是:System.out.println();这个操作只是进行数据的输出和消费,而不能返回,这就是消费性接口。

接口定义 接口作用 接口使用
@FunctionalInterface
public interface Consumer{ public void accept(T t); }
接收一个 T 类型参数,但是不返回任何东西,消费型接口 如下

其实最常见的消费型接口的实现,就是 System.out.println(xxx) 了。 我们只管往方法中输入参数,但是并没有返回任何值。
Consumer 相当于是有来无回,给一个参数,但是无返回。
而如果是两个参数,无返回,那么就是 BiConsumer。

public class JavaDemo {
    public static void main(String[] args) {
        Consumer<String> consumer = System.out::println;
        consumer.accept("Hello World!");
    }
}

当然我们也可以自定义消费性接口

class StringCompare {
	// 接收 StringBuilder ,但是不返回任何数据。
    public void fun(StringBuilder sb) {
        sb.append("World!");
    }
}

public class JavaDemo {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append("Hello ");
        Consumer<StringBuilder> consumer = new StringCompare()::fun;
        consumer.accept(sb);
        System.out.println(sb.toString());
    }
}

供给型函数式接口

接口定义 接口作用 接口使用
@FunctionalInterface
public interface Supplier{public T get();}
啥也不接受,但是却返回 T 类型数据,供给型接口 如下

Supplier 相当于是无中生有,什么也不传,但是返回一个结果。
像 String 类里的 toUpperCase() 方法,也是不接受参数,但是返回 String 类型,就可以看成这个供给型函数式接口的一个实现。

public String toUpperCase() {
        return toUpperCase(Locale.getDefault());
    }
import java.util.function.*;
public class Demo01 {
	public static void main(String[] args) {
		Supplier <String> sup = "WWW.BAIDU.COM" :: toLowerCase;
		System.out.println(sup.get());
	}
}

断言型函数式接口

接口定义 接口作用 接口使用
@FunctionalInterface
public interface Predicate{ public boolean test(T t);}
传入 T 类型参数,返回布尔类型,常常用于对入参进行判断 如下
class StringFilter {
	// 对集合中的数据进行过滤,传入断言型接口进行判断
    public static List<String> filter(List<String> list, Predicate<String> predicate) {
        List<String> stringList = new ArrayList<>();
        for (String str : list) {
            if (predicate.test(str)) {
                stringList.add(str);
            }
        }
        return stringList;
    }

}

public class JavaDemo {
    public static void main(String[] args) {
        List<String> stringList = Arrays.asList("好诗", "好文", "好评", "好汉", "坏蛋", "蛋清", "清风", "风间");
        List<String> filterList = StringFilter.filter(stringList, list -> list.contains("好"));
        System.out.println(filterList);
    }
}

如果JDK本身提供的函数式接口可以被我们所使用,那么就没必要重新去定义了。

总结

在开发中,使用 lambda 表达式能提高开发效率,写出更加 “高大上” 的代码。但 lambda 可读性较差,如果没接触过过的程序员不友好。

想要掌握 lambda 表达式,还是要多练。从手写一个接口的实现类,到匿名内部类,到 lambda。一开始不能直接写出 lambda ,可以先用匿名内部类的方式,然后一步步简化,多敲多练,最终就能游刃有余。

相关文章

可以认为OpenFeign是Feign的增强版,不同的是OpenFeign支持S...
为进一步规范小程序交易生态、提升用户购物体验、满足用户在...
云原生之使用Docker部署Dashdot服务器仪表盘
本文主要描述TensorFlow之回归模型的基本原理
1.漏洞描述Apache Druid 是一个集时间序列数据库、数据仓库和...
内部类(当作类中的一个普通成员变量,只不过此成员变量是cl...