首发于JDK知识

JAVA8 Lambda 不再害怕

本文就是要搞透彻 java8的Lambda ,后面自己code代码时尽量要用。否则以后看新的框架代码都很难看懂。


函数式接口

接口中 有且 只有一个抽象方法叫做函数式接口。@FunctionalInterface 修饰一个函数式接口,编译器会检测 @FunctionalInterface 修饰的接口,如果不是函数式接口就会报错。

@FunctionalInterface
interface A {
    public void a();
}

如果是下面

Invalid '@FunctionalInterface' annotation; Lambada.A is not a functional interface

注意,当你不用 FunctionalInterface 修饰时,接口并不代表就不是函数式接口了,@FunctionalInterface 只是一个规范接口而已。


Lambda

延迟加载功能

下面这段代码, 那个a.split 方法简直就是白执行了。

private static void log(int a , String[] str){
	if(a % 2 == 0){
		System.out.println(str);
	}
}

public static void main(String[] args) {
	String a = "a,b,c,d";
	log(1, a.split(","));
}

如果换成Lambda 表达式:

@FunctionalInterface
interface Call {
	public Object call();
}

private static void log(int a, Call call) {
	if (a % 2 == 0) {
		System.out.println(call.call());
	}
}

public static void main(String[] args) {
	String a = "a,b,c,d";
	log(2, () -> a.split(","));
}

当我们传入2时,call被回调,lambda表达式被执行,split才会被有效执行。lambda表达式 必须是函数式接口


lambda 与函数式接口对应关系

----------------------无参数无返回值-----------
@FunctionalInterface
public A {
    public void call();
}
()->{
;;  // 代码块
}

----------------------有参数无返回值-----------
@FunctionalInterface
public A {
    public void call(String a , String b);
}
(a,b)-{}

----------------------有参数有返回值,无代码块-----------
@FunctionalInterface
public A {
    public String call(String a);
}
 // 一个参数 参数可以省略()  , 没有代码块,返回值retuen可省略
a->{"我是返回值"}  

----------------------有参数有返回值有代码块-----------
@FunctionalInterface
public A {
    public String call(String a);
}
a->{
 ; ;  // 代码块
 return "我是返回值"
}


Java8内置的几个重要的函数接口

Predicate 代表条件, 用法:

// 定义一个条件 : 是否是女士  (字符串里面有女这个词)
Predicate<String> pre = name -> name.contains("女"); // 定义一个lamdba接口
// 造人
List<String> persons = Arrays.asList("小明-男", "花花-女", "郑明桥-男", "姑姑-男");

// 赛选出 女士
List<String> womans = persons.stream().filter(pre).collect(Collectors.toList());
System.out.println(womans); // [花花-]

集合的stream + filter 方法 可以 处理集合,传入一个Predicate ,最后 collect 就会把集合中元素 包含 "女" 的元素 返回出来。


Predicate.negate() 取反

// 定义一个条件 : 是否是女士  (字符串里面有女这个词)
Predicate<String> pre = name -> name.contains("女"); // 定义一个lamdba接口
// 造人
List<String> persons = Arrays.asList("小明-男", "花花-女", "郑明桥-男", "姑姑-男");
// 取反 ,赛选出男生
List<String> mans = persons.stream().filter(pre.negate()).collect(Collectors.toList());
System.out.println(mans); // [小明-, 郑明桥-, 姑姑-]

Predicate.or() 取并集

// 定义一个条件 : 是否是女士  (字符串里面有女这个词)
Predicate<String> pre = name -> name.contains("女"); // 定义一个lamdba接口
// 造人
List<String> persons = Arrays.asList("小明-男", "花花-女", "郑明桥-男", "姑姑-男");
// 取反 ,赛选出男生
List<String> mans = persons.stream().filter(pre.or(pre.negate())).collect(Collectors.toList());
System.out.println(mans); // [小明-, 郑明桥-, 姑姑-]

pre.or(pre.negate()) : 女生条件 or 男生条件 , 取所有。


Consumer 代表消费, 用法:

List<String> persons = Arrays.asList("小明-男", "花花-女", "郑明桥-男", "姑姑-男");
// 遍历输出
persons.forEach(name -> System.out.println(name));
//遍历输出  consumer 作为 方法引用
persons.forEach(System.out::println);

forEach 参数就是一个consumer接口,直接消费拿出集合的每个元素。


Function 代表功能 , 用法:

List<String> persons = Arrays.asList("小明-男", "花花-女", "郑明桥-男", "姑姑-男");
List<Integer> lens = persons.stream().map(name -> name.length()).collect(Collectors.toList());
lens.forEach(System.out::println); // 4 , 4 ,5 ,4

map 参数 接受一个Function , 我们把原始的List<String> 数据结构 转换成了 List<Integer> 。 Function 接口是一个功能型接口,它的一个作用就是转换作用,将输入数据转换成另一种形式的输出数据。


lambda 原理

比如有如下代码:

public class Lambada {

	@FunctionalInterface
	interface Call {
		public Object call();
	}

	private static void log(int a, Call call) {
		if (a % 2 == 0) {
			System.out.println(call.call());
		}
	}

	public static void main(String[] args) {
		log(12, () -> "我是日志内容");
	}
}

我们用 javap -p Lambada.class 反编译出里面的方法,发现:

E:\debug_jdk\debug_jdk8\bin\debug_jdk8>javap -p Lambada.class
Compiled from "Lambada.java"
public class debug_jdk8.Lambada {
  public debug_jdk8.Lambada();
  private static void log(int, debug_jdk8.Lambada$Call);
  public static void main(java.lang.String[]);
  private static java.lang.Object lambda$0();
}

多了一个 lambda$0 方法,Lambada其实很简单,编译成class的时候,多加了个静态的 lambda$0 方法。运行遇到lambada时,就会直接调用 lambda$0 方法。

我们搞个事情:

public class Lambada {
	@FunctionalInterface
	interface Call {
		public Object call();
	}
	private static void log(int a, Call call) {
		if (a % 2 == 0) {
			System.out.println(call.call());
		}
	}
	public static void main(String[] args) {

		log(12, () -> "我是日志内容");
	}
	private static java.lang.Object lambda$0() {
		return "我是日志内容";
	}
}

直接在代码中定义了 lambda$0 方法,运行自然会报错:

Exception in thread "main" java.lang.ClassFormatError: Duplicate method name "lambda$0" with signature "()Ljava.lang.Object;" in class file debug_jdk8/Lambada
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)


推荐一个Java架构师博客,带你一起写架构:

发布于 2021-04-07 19:12