Java Lambda

Posted 流星<。)#)))≦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Lambda相关的知识,希望对你有一定的参考价值。

  • lambda从Java8开始

基础语法

expression = (variable) -> action
  • variable: 这是一个变量,一个占位符。像x,y,z,可以是多个变量;
  • action: 这里我称它为action, 这是我们实现的代码逻辑部分,它可以是一行代码也可以是一个代码片段。

函数式接口

  • 我们把这些只拥有一个方法的接口称为函数式接口。(之前它们被称为SAM类型,即 单抽象方法类型(Single Abstract Method))
  • 注解@FunctionalInterface标识一个接口为函数式接口,被标识的接口只能有一个抽象方法(除了已经在Object中定义的方法),否则会报错
  • 不标识@FunctionalInterface的接口,但事实上也是函数式接口的,依然可以使用lambda表达式
  • JDK中的函数式接口位置:java.util.function,包含了常用的函数式接口:

Predicate——接收T并返回boolean
Consumer——接收T,不返回值
Function<T, R>——接收T,返回R
Supplier——提供T对象(例如工厂),不接收值
UnaryOperator——接收T对象,返回T
BinaryOperator——接收两个T,返回T

  • Java SE 7 中已经存在的函数式接口
java.lang.Runnable
java.util.concurrent.Callable
java.util.Comparator
java.io.FileFilter

  • 除了上面的这些基本的函数式接口,还提供了一些针对原始类型(Primitive type)的特化(Specialization)函数式接口,例如IntSupplierLongBinaryOperator,还有很多在java.util.function包中,应该每一个都有所了解

四大核心函数式接口:

image

消费型接口示例

public static void donation(Integer money, Consumer<Integer> consumer){
    consumer.accept(money);  
}
public static void main(String[] args) {
    donation(1000, money -> System.out.println("好心的麦乐迪为Blade捐赠了"+money+"元")) ;
}

供给型接口示例

public static List<Integer> supply(Integer num, Supplier<Integer> supplier){
       List<Integer> resultList = new ArrayList<Integer>()   ;
       for(int x=0;x<num;x++)  
           resultList.add(supplier.get());
       return resultList ;
}
public static void main(String[] args) {
    List<Integer> list = supply(10,() -> (int)(Math.random()*100));
    list.forEach(System.out::println);
}

函数型接口示例

public static Integer convert(String str, Function<String, Integer> function) {
    return function.apply(str);
}
public static void main(String[] args) {
    Integer value = convert("28", x -> Integer.parseInt(x));
}

断言型接口示例

public static List<String> filter(List<String> fruit, Predicate<String> predicate){
    List<String> f = new ArrayList<>();
    for (String s : fruit) {
        if(predicate.test(s)){
            f.add(s);
        }
    }
    return f;
}
public static void main(String[] args) {
    List<String> fruit = Arrays.asList("香蕉", "哈密瓜", "榴莲", "火龙果", "水蜜桃");
    List<String> newFruit = filter(fruit, (f) -> f.length() == 2);
    System.out.println(newFruit);
}

自定义函数式接口

  • 实现简单加法
public class Test2 {
	/*
	一个接口,如果只有一个显式声明的抽象方法,
	那么它就是一个函数接口。
	一般用@FunctionalInterface标注出来(也可以不标)
	*/
	interface Inteface1 {
		// 可以不用abstract修饰
		public abstract void test(int x, int y);

		// public void test1();//会报错,不能有两个方法,尽管没有使用abstract修饰
		public boolean equals(Object o);// equals属于Object的方法,所以不会报错
	}

	public static void main(String args[]) {
		Inteface1 f1 = (x, y) -> {
			System.out.println(x + y);
		};
		f1.test(3, 4);
		Inteface1 f2 = (int x, int y) -> {
			System.out.println("Hello Lambda!\\t the result is " + (x + y));
		};
		f2.test(3, 4);
	}
}

lambda表达式

  • lambda 表达式的语法由参数列表、箭头符号->和函数体组成。函数体既可以是一个表达式,也可以是一个语句块
  • 出现在语句中的lambda表达式
FileFilter java = (File f) -> f.getName().endsWith("*.java");
String user = doPrivileged(() -> System.getProperty("user.name"));
new Thread(() -> {
  connectToService();
  sendNotification();
}).start();

目标类型(Target typing)

  • 同样的 lambda 表达式在不同上下文里可以拥有不同的类型
Callable<String> c = () -> "done";
PrivilegedAction<String> a = () -> "done";

第一个 lambda 表达式 () -> "done" 是Callable的实例,而第二个 lambda 表达式则是PrivilegedAction的实例。
编译器负责推导 lambda 表达式类型。
lambda 表达式的参数类型可以从目标类型中得出。
当 lambda 的参数只有一个而且它的类型可以被推导得知时,该参数列表外面的括号可以被省略:

FileFilter java = f -> f.getName().endsWith(".java");
button.addActionListener(e -> ui.dazzle(e.getModifiers()));

目标类型的上下文(Contexts for target typing)

  • lambda 表达式本身也可以为它自己的函数体提供目标类型,也就是说 lambda 表达式可以通过外部目标类型推导出其内部的返回类型,这意味着我们可以方便的编写一个返回函数的函数:
Supplier<Runnable> c = () -> () -> { System.out.println("hi"); };
  • 条件表达式可以把目标类型“分发”给其子表达式:
Callable<Integer> c = flag ? (() -> 23) : (() -> 42);
  • 转型表达式(Cast expression)可以显式提供 lambda 表达式的类型,这个特性在无法确认目标类型时非常有用:
// Object o = () -> { System.out.println("hi"); }; 这段代码是非法的
Object o = (Runnable) () -> { System.out.println("hi"); };

词法作用域(Lexical scoping)

  • lambda 表达式函数体里面的变量和它外部环境的变量具有相同的语义,不同于内部类
public class Hello {
	Runnable r1 = () -> {
		System.out.println(this);
	};

	Runnable r2 = new Runnable() {
		public void run() {
			System.out.println(this);
		}
	};

	public String toString() {
		return "Hello, world";
	}

	public static void main(String... args) {
		new Hello().r1.run();	// Hello, world
		new Hello().r2.run();	// lambda.Hello$1@1fb3ebeb
	}
}

变量捕获(Variable capture)

  • 类似于在匿名类内部引用函数局部变量,必须将其声明为final
public static Supplier<Integer> testClosure() {
	int i = 1;
	// i++; // 会报错
	return () -> {
		return i;
	};
}

方法引用(Method references)

  • 不需要为方法引用提供方法体,可以直接通过方法名称引用已有方法
Comparator<Person> byName = Comparator.comparing(p -> p.getName());
Comparator<Person> byName = Comparator.comparing(Person::getName);  // 缩写形式
  • 因为函数式接口的方法参数对应于隐式方法调用时的参数,所以被引用方法签名可以通过放宽类型,装箱以及组织到参数数组中的方式对其参数进行操作,就像在调用实际方法一样:
Consumer<Integer> b1 = System::exit; // void exit(int status)
Consumer<String[]> b2 = Arrays::sort; // void sort(Object[] a)
Consumer<String> b3 = MyProgram::main; // void main(String... args)
Runnable r = Myprogram::mapToInt // void mapToInt()

方法引用的种类(Kinds of method references)

  • 静态方法引用:ClassName::methodName
  • 实例上的实例方法引用:instanceReference::methodName
  • 超类上的实例方法引用:super::methodName
  • 类型上的实例方法引用:ClassName::methodName
  • 构造方法引用:Class::new
  • 数组构造方法引用:TypeName[]::new

默认方法和静态接口方法(Default and static interface methods)

  • 默认方法 利用面向对象的方式向接口增加新的行为。它是一种新的方法:接口方法可以是 抽象的 或是 默认的。默认方法拥有其默认实现,实现接口的类型通过继承得到该默认实现(如果类型没有覆盖该默认实现)。此外,默认方法不是抽象方法,所以我们可以放心的向函数式接口里增加默认方法,而不用担心函数式接口的单抽象方法限制。
  • 除了默认方法,Java SE 8 还在允许在接口中定义 静态 方法。这使得我们可以从接口直接调用和它相关的辅助方法(Helper method),而不是从其它的类中调用(之前这样的类往往以对应接口的复数命名,例如 Collections)
  • 理解这两种方法的一个实例:java.util.Comparator<T>

使用实例

Collections.sort(list, new Comparator<Person>() {
	public int compare(Person x, Person y) {
		return x.getLastName().compareTo(y.getLastName());
	}
});

Collections.sort(list, (Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));

Collections.sort(list, Comparator.comparing((Person p) -> p.getLastName()));

Collections.sort(list, Comparator.comparing(p -> p.getLastName()));

Collections.sort(list, Comparator.comparing(Person::getLastName));

list.sort(Comparator.comparing(Person::getLastName));

参考资料

以上是关于Java Lambda的主要内容,如果未能解决你的问题,请参考以下文章

Android开发太难了:Java Lambda ≠ Android Lambda (上)

Android开发太难了:Java Lambda ≠ Android Lambda (上)

JAVA由一个将JSONArray转成Map的需求引发的lambda语法的学习

Java -- 每日一问:有人说“Lambda能让Java程序慢30倍”,你怎么看?

Java 8 新特性总结

函数式编程