Java - Lambda表达式 / 方法引用 / 构造器引用 / 数组引用(多种情况举例说明)

目录

  • Lambda表达式语法
    • 无参数,无返回值
    • 有一个参数,无返回值
    • 有多个参数,无返回值
    • 无参数,有返回值
    • 有一个参数,有返回值
    • 有多个参数,有返回值
  • 函数式接口
  • 方法引用
    • 对象 :: 实例(非静态)方法名
    • 类 :: 静态方法名
    • 类 :: 实例(非静态)方法名
  • 构造器引用
  • 数组引用

    Java 8中有两个最为重要的改变:第一个是Lambda表达式,另一个则是Stream API。使用Stream配合Lambda表达式、方法引用会让你的代码更简洁。

list.stream().map(good -> good.getName())
			 .map(string -> string.toUpperCase())
			 .forEach(string -> System.out.println(string));
list.stream().map(Goods :: getName).map(String :: toUpperCase).forEach(System.out :: println);

Java - Stream流 - 让你的代码更简洁(创建流、中间操作(筛选、映射、排序)、终止操作(匹配、约简、收集))


    Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更加简洁的 Java 代码。
    Lambda 表达式是一个可传递的代码块, 可以在以后执行一次或多次。
    关键:Lambda 表达式在Java中是函数式接口的一个对象(实例)

Lambda表达式语法

    Lambda表达式分为三部分:

  1. ->’:该操作符被称为Lambda操作符箭头操作符。它将Lambda表达式分为两部分。
  2. ‘->’左侧:指定了Lambda表达式需要的参数列表,即接口中的抽象方法的参数列表
  3. ‘->’右侧:指定了Lambda体,是抽象方法的实现逻辑,即Lambda表达式要执行的功能

    下面将Lambda表达式分为6种情况:

无参数,无返回值

    我们以Runnable接口举例说明:创建一个线程:

package java.lang;
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

    Java 8之前的写法是这样的:

Runnable runnable = new Runnable() {
	@Override
	public void run() {
		System.out.println("线程运行中");
	}
};
runnable.run();
  1. 一个非抽象类实现一个接口就必须实现里面所有的抽象方法。由于Runnable接口中只有一个抽象方法 run(),显然方法名是可以推断出来的,所以方法名可以省略。
  2. 可以由接口中方法的定义推出来,权限修饰符是 public,没有返回值,所以 public 和 void 也可以省略。
  3. 由于Lambda表达式在Java中是函数式接口的一个对象(实例),所以 new Runnable() 也可以省略了。

    使用Lambda表达式,只留参数列表(这里没有参数,但必须留括号),‘->’ 和方法体:

Runnable runnable = () -> {
	System.out.println("线程运行中");
};
runnable.run();

    只有一条语句且不是return语句时,方法体的花括号可以省略:

Runnable runnable = () -> System.out.println("线程运行中");
runnable.run();

有一个参数,无返回值

    我们以Consumer接口举例说明:判断一个数是大于0,小于0或等于0:

package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

    Java 8之前的写法是这样的:

Consumer<Integer> consumer = new Consumer<Integer>() {
	@Override
	public void accept(Integer t) {
		if (t > 0) {
			System.out.println("大于0");
		} else if (t < 0) {
			System.out.println("小于0");
		} else {
			System.out.println("等于0");
		}
	}
};
consumer.accept(0);
  1. 由于Consumer接口中只有一个抽象方法 accept()(另外一个是默认方法,Java 8可以在接口中定义默认方法),显然方法名是可以推断出来的,所以方法名可以省略。显然 public 和 void 也可以省略。
  2. 由于Lambda表达式在Java中是函数式接口的一个对象(实例),所以 new Consumer() 也可以省略了。

    使用Lambda表达式,只留参数列表(这里的参数是t),‘->’ 和方法体(这里有多条语句,花括号不能省略):

Consumer<Integer> consumer = (Integer t) -> {
	if (t > 0) {
		System.out.println("大于0");
	} else if (t < 0) {
		System.out.println("小于0");
	} else {
		System.out.println("等于0");
	}
};
consumer.accept(-10);

    由于参数 t 的类型可以由 Consumer 推出来(类型推断,注意接口的定义中的泛型T),所以参数的类型可以省略。

Consumer<Integer> consumer = (t) -> {
	if (t > 0) {
		System.out.println("大于0");
	} else if (t < 0) {
		System.out.println("小于0");
	} else {
		System.out.println("等于0");
	}
};
consumer.accept(-10);

    当参数列表中的参数只有一个时,参数旁边的 () 可以省略。

Consumer<Integer> consumer = t -> {
	if (t > 0) {
		System.out.println("大于0");
	} else if (t < 0) {
		System.out.println("小于0");
	} else {
		System.out.println("等于0");
	}
};
consumer.accept(-10);

有多个参数,无返回值

    我们自己写一个接口进行说明:

@FunctionalInterface
interface MultiParamNoReturn<T> {
	void method(T a, T b);
}

    Java 8之前的写法是这样的:

MultiParamNoReturn<Integer> m = new MultiParamNoReturn<Integer>() {
	@Override
	public void method(Integer a, Integer b) {
		System.out.printf("%d + %d = %d\n", a, b, (a + b));
	}
}; 
m.method(10, 11);

    使用Lambda表达式,只留参数列表(这里的参数是a,b),‘->’ 和方法体:

MultiParamNoReturn<Integer> m = (a, b) -> 
	System.out.printf("%d + %d = %d\n", a, b, (a + b));
m.method(5, 6);

无参数,有返回值

    我们以Supplier接口举例说明:

package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

    Java 8之前的写法是这样的:

Supplier<Double> supplier = new Supplier<Double>() {
	@Override
	public Double get() {
		return (Double)Math.random()*10000;
	}
};
System.out.printf("今天又双叒叕丢了%.2f块钱",supplier.get());

    使用Lambda表达式,只留参数列表(这里没有参数),‘->’ 和方法体:

Supplier<Double> supplier = () -> {
	return (Double)Math.random()*10000;
};
System.out.printf("今天又双叒叕丢了%.2f块钱\n",supplier.get());

    当Lambda表达式的方法体只有一条语句,且是return语句,那么return和花括号都可以省略。

Supplier<Double> supplier = () -> (Double)Math.random()*10000;
System.out.printf("今天又双叒叕丢了%.2f块钱\n",supplier.get());

有一个参数,有返回值

    我们以Predicate接口举例说明:判断一个字符串是否相等:

package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    ...
}

    Java 8之前的写法是这样的:

public static boolean search(List<String> list, Predicate<String> predicate) {
	for (String string : list) {
		if (predicate.test(string))
			return true;
	}
	return false;
}
	
public static void main(String[] args) {
	List<String> list = Arrays.asList("Lanbda","Lamdba","Landba","Lembda","Lambde");
	boolean result = search(list, new Predicate<String>() {
		@Override
		public boolean test(String t) {
			return t.equals("Lambda");
		}
	});
	System.out.println(result);
}
  1. public、boolean、方法名可以由Predicate接口中唯一的抽象方法 test(T t) 推出来,所以可以省略。
  2. new Predicate() 和 test(T t) 中的参数类型可以由 search(List list, Predicate predicate) 推出,所以可以省略。

    使用Lambda表达式:

public static boolean search(List<String> list, Predicate<String> predicate) {
	for (String string : list) {
		if (predicate.test(string))
			return true;
	}
	return false;
}
	
public static void main(String[] args) {
	List<String> list = Arrays.asList("Lanbda","Lamdba","Landba","Lembda","Lambde");
	boolean result = search(list, t -> t.equals("Lambda"));
	System.out.println(result);
}

有多个参数,有返回值

    我们以Comparator接口举例说明:Comparator自然排序:

package java.util;
...
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);
	...
}

    Java 8之前的写法是这样的:

List<Integer> list = Arrays.asList(30,10,62,47,84,7,55);
Collections.sort(list, new Comparator<Integer>() {
	public int compare(Integer o1, Integer o2) {
		return Integer.compare(o1,o2);
	}
});
System.out.println(list);

    这里有一个问题:为什么Comparator有两个抽象方法compare和equals,但是必须要重写compare,而不必重写equals?

戳我☞☞☞因为重写了超类Object类中任意一个public方法的方法并不算接口中的抽象方法,任何接口的实现都会从其父类Object或其它地方获得这些方法的实现。

    使用Lambda表达式:

List<Integer> list = Arrays.asList(30,10,62,47,84,7,55);
Collections.sort(list,(o1, o2) -> Integer.compare(o1, o2));
System.out.println(list);

函数式接口

    函数式接口(Functional Interface)是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。我们可以通过Lambda表达式来创建该接口的对象。
    我们可以在一个接口上使用 @FunctionalInterface 注解,这样可以检查它是否是一个函数式接口。
    在java.util.function包下定义了丰富的函数式接口。
Java - Lambda表达式 / 方法引用 / 构造器引用 / 数组引用(多种情况举例说明)_第1张图片

方法引用

    方法引用可以看做是lambda表达式深层次的表达。换句话说,方法引用就是lambda表达式,也就是函数式接口的一个对象(实例)。
    使用场景:当要传递给Lambda体的操作,已经有实现的方法了,那么就可以使用方法引用。
    要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致(相同)。
    格式:使用操作符 “::” 将类(或对象)与方法名分割开来。(是已经实现好的方法名及其所在的类或对象)。(可以看成左边的类或对象是调用者,右边的方法是被调用者

对象 :: 实例(非静态)方法名

    Consumer接口中的方法:void accept(T t)
    PrintStream类中的方法:void println(T t)
    匿名内部类方式:

Consumer<String> consumer = new Consumer<String>() {
	@Override
	public void accept(String t) {
		System.out.println(t);
	}
};
consumer.accept("hello world");

    Lambda表达式方式:

Consumer<String> consumer = string -> System.out.println(string);
consumer.accept("hello world");

    方法引用方式:

  1. void accept(T t) 和 void println(T t) 的返回值类型都是void,没有返回值;参数列表参数类型都一样,为泛型T。返回值类型和参数类型都可以由两个方法推出来。
  2. println()方法在PrintStream类中已经实现好了,PrintStream对象调用println()方法。

    从Lambda表达式看,可以看成 ‘->’ 左边的参数列表和右边的参数列表相同,且右边的方法已经实现好了,那么用 ‘::’ 将右边的调用者和方法分割开来。

PrintStream printStream = System.out;
Consumer<String> consumer = printStream :: println;
consumer.accept("hello world");

    Supplier接口中的方法:T get()
    Person类中的方法:String getName()

public class Person{
	public String name;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	...
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	...
}

    匿名内部类方式:

Person person = new Person("Tom",10);
Supplier<String> supplier = new Supplier<String>() {
	@Override
	public String get() {
		return person.getName();
	}
};
System.out.println(supplier.get());

    Lambda表达式方式:

Supplier<String> supplier = () -> person.getName();
System.out.println(supplier.get());

    方法引用方式:

  1. T get() 和 String getName() 的返回值类型都是String;都没有参数。返回值类型和参数类型都可以由两个方法推出来。
  2. getName()方法在Person类中已经实现好了,Person对象调用getName()方法。
Supplier<String> supplier = person :: getName;
System.out.println(supplier.get());

类 :: 静态方法名

    Comparator接口中的方法:int compare(T t1, T t2)
    Integer类中的方法:int compare(T t1, T t2)
    匿名内部类方式:

Comparator<Integer> comparator = new Comparator<Integer>() {
	@Override
	public int compare(Integer o1, Integer o2) {
		return Integer.compare(o1, o2);
	}
};
System.out.println(comparator.compare(10, 5));

    Lambda表达式方式:

Comparator<Integer> comparator = (o1, o2) -> Integer.compare(o1, o2);
System.out.println(comparator.compare(1, 5));

    方法引用方式:

  1. 显然,两个方法的返回值类型都是int;参数都一样。返回值类型和参数类型都可以由两个方法推出来。
  2. compare()方法在Integer类中已经实现好了,Integer类调用compare()方法。
Comparator<Integer> comparator = Integer :: compare;
System.out.println(comparator.compare(5, 5));

    Function接口中的方法:R apply(T t)
    Math类中的方法:Long round(Double d)
    匿名内部类方式:

Function<Double, Long> function = new Function<Double, Long>() {
	@Override
	public Long apply(Double d) {
		return Math.round(d);
	}
};
System.out.println(function.apply(123.7));

    Lambda表达式方式:

Function<Double, Long> function = d -> Math.round(d);
System.out.println(function.apply(111.8));

    方法引用方式:

Function<Double, Long> function = Math :: round;
System.out.println(function.apply(100.4));

类 :: 实例(非静态)方法名

    Comparator接口中的方法:int compare(T t1, T t2)
    String类中的方法:int t1.compare(T t2)
    匿名内部类方式:

Comparator<String> comparator = new Comparator<String>() {
	@Override
	public int compare(String o1, String o2) {
		return o1.compareTo(o2);
	}
};
System.out.println(comparator.compare("c", "a"));

    Lambda表达式方式:

Comparator<String> comparator = (o1, o2) -> o1.compareTo(o2);
System.out.println(comparator.compare("z", "a"));

    方法引用方式:

  1. 返回值类型都是int,参数的类型都是String。虽然它的参数列表不一样,但是String的compare()方法实际上也是 t1,t2 两个参数进行操作,只是它的写法看上去不一样(依然可以看成 compare(T t1, T t2))。其中一个 t1 是调用者,它的方法compare()在String类中已经实现好了。(可以看成t1作为调用者前移了,所以参数列表少了一个参数)
Comparator<String> comparator = String :: compareTo;
System.out.println(comparator.compare("a", "z"));

    BiPredicate接口中的方法:boolean test(T t, U u)
    String类中的方法:boolean t.equals(U u)
    匿名内部类方式:

BiPredicate<String, String> biPredicate = new BiPredicate<String, String>() {
	@Override
	public boolean test(String t, String u) {
		return t.equals(u);
	}
};
System.out.println(biPredicate.test("Lambda", "Lamdba"));

    Lambda表达式方式:

BiPredicate<String, String> biPredicate = (t, u) -> t.equals(u);
System.out.println(biPredicate.test("Lambda", "Lamdba"));

    方法引用方式:

  1. 返回值类型都是boolean,参数的类型都是String。虽然它的参数列表不一样,但是String的equals()方法实际上可以看成 equals(T t, U u)。其中一个 t 是调用者,它的equals()方法在String类中已经实现好了。
BiPredicate<String, String> biPredicate = String :: equals;
System.out.println(biPredicate.test("Lambda", "Lambda"));

    Function接口中的方法:R apply(T t)
    Person类中的方法:String getName()
    匿名内部类方式:

Person person = new Person("Jerry",20);
Function<Person, String> function = new Function<Person, String>() {
	@Override
	public String apply(Person t) {
		return t.getName();
	}
};
System.out.println(function.apply(person));

    Lambda表达式方式:

Person person = new Person("Jerry",20);
Function<Person, String> function = t -> t.getName();
System.out.println(function.apply(person));

    方法引用方式:

  1. 返回值类型都是String,虽然Person类的getName()方法没有参数,但是我们可以把Function接口的 String apply(Person t) 看成是将参数 t 前移作为调用者,这样参数列表就没参数了,与getName()方法一样。而getName()在Person类中已经实现好了。
Person person = new Person("Jerry",20);
Function<Person, String> function = Person::getName;
System.out.println(function.apply(person));

    类似的还有

(x, y) -> x.compareToIgnoreCase(y);
//等价于
String :: compareToIgnoreCase;

构造器引用

    构造器引用与方法引用类似,其要求是函数式接口的抽象方法的参数列表与构造器的参数列表相同。抽象方法的返回值类型即为构造器所属类的类型。
    格式类名::new


    Supplier接口中的方法:T get()
    Person类中的构造器:Person()
    匿名内部类方式:

Supplier<Person> supplier = new Supplier<Person>() {
	@Override
	public Person get() {
		return new Person();
	}
};
System.out.println(supplier.get());

    Lambda表达式方式:

Supplier<Person> supplier = () -> new Person();
System.out.println(supplier.get());

    构造器引用方式:

Supplier<Person> supplier = Person :: new;
System.out.println(supplier.get());

    BiFunction接口中的方法:R apply(T t, U,u)
    Person类中的构造器:Person(String name, int age)
    匿名内部类方式:

BiFunction<String, Integer, Person> b = new BiFunction<String, Integer, Person>() {
	@Override
	public Person apply(String name, Integer age) {
		return new Person(name, age);
	}
};
System.out.println(b.apply("Jack", 18));

    Lambda表达式方式:

BiFunction<String, Integer, Person> b = (name, age) -> new Person(name, age);
System.out.println(b.apply("Jack", 18));

    构造器引用方式:

BiFunction<String, Integer, Person> b = Person :: new;
System.out.println(b.apply("Jack", 18));

数组引用

    数组引用与构造器引用类似。


    Function接口中的方法:R apply(T t)
    匿名内部类方式:

Function<Integer, String[]> function = new Function<Integer, String[]>() {
	@Override
	public String[] apply(Integer t) {
		return new String[t];
	}
};
String[] array = function.apply(5);
System.out.println(Arrays.toString(array));

    Lambda表达式方式:

Function<Integer, String[]> function = length -> new String[length];
String[] array = function.apply(5);
System.out.println(Arrays.toString(array));

    数组引用方式:

Function<Integer, String[]> function = String[] :: new;
String[] array = function.apply(5);
System.out.println(Arrays.toString(array));

如有错误请大家指出了(ง •̀_•́)ง (*•̀ㅂ•́)و

你可能感兴趣的:(Java学习)