Java8新特性(一) Lambda表达式与函数式接口
一. 基本概念
Lambda表达式是Java 8中引入的一个重要的新特性,该表达式提出了一种新的语法规则,用于对某些(函数式接口)匿名内部类的书写方式进行简化。除此之外,Lambda表达式是函数式编程思想的一个重要体现,它允许我们通过表达式的形式来定义和传递功能,并且更加关注数据本身。以线程创建为例,Lambda表达式的基本语法规则如下:
(参数列表)->{ 方法体(代码); }
//1. Runnable匿名内部类
new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Thread running...");}
}).start();
//2. Runnable Lambda表达式
new Thread(()->{System.out.println("Thread running...");
}).start();
二. 语法特性
1. 限制条件
(1)Lambda表达式只能用于简化函数式接口的匿名内部类的实现,即只含有一个抽象方法的接口(可以包含默认default方法),或者是声明了 @FunctionalInterface 注解的接口。注意:Lambda表达式不能够简化类或者抽象类的创建,如果试图使用Lambda表达式去创建一个类或者抽象类将会报错如下 ”Target type of a lambda conversion must be an interface“;
(2)Lambda表达式可以看作是一种特殊的匿名内部类的实现,也被称为匿名函数,匿名内部类的所有使用限制都会对其生效;
2. 省略规则
(1)参数列表中的参数类型可以省略,JVM编译器可以通过上下文自动做"类型推断",但是所有参数类型是否省略必须统一;
参数列表中若只有一个参数,则可以省略参数列表的小括号(同时必须省略参数类型);
参数列表中若没有参数(即无参抽象方法),则参数列表的小括号不可省略;
(2)若方法体中只有一句代码,则方法体的大括号可以省略(包括结尾分号);若该代码是return语句,则在省略大括号时必须进一步去掉return关键字;
(3)若方法体由多句代码组成,则方法体格式必须完整;
// 1.保留参数类型
(int x,int y) -> {int res = x + y;return res;
}// 2.省略参数类型
(x,y) -> { int res = x + y; return res;
}// 3.只有一个参数(无返回值)
x -> {int res = x * x;System.out.print(res);
}// 4.无参数
() -> {System.out.print("无参表达式");}// 5.单行方法体-返回其2倍的值
x -> 2 * x// 6.单行方法体-返回他们的差值
(x, y) -> x – y // 7.单行方法体-无返回值
(String str) -> System.out.print(str)
3.变量捕获
类似于匿名内部类,Lambda表达式同样可以捕获外部作用域中的变量数据,并可以直接使用这些变量而无需声明。Lambda表达式可以捕获的变量数据类型如下:
实例变量(Instance Variables):若Lambda表达式定义在非静态域中,则可以捕获并访问包含它的实例的成员实例变量,并可以使用OuterClass.this关键字来指明外部类的引用;
静态变量(Static Variables):Lambda表达式定义在静态/非静态域中都可以随时捕获并访问包含它的类的成员静态变量;
方法参数(Method Parameters):Lambda表达式可以捕获并访问包含它的方法的参数,本质上与本地局部变量一致;
本地变量(Local Variables):Lambda表达式可以捕获并访问声明为final的本地局部变量。从Java 8开始,final关键字可以省略(自动添加),但该变量的值实际上不可修改(该表达式域内外均不可修改);
在Lambda表达式中捕获的成员变量(实例变量、静态变量)与其在外部作用域中发生的改变将会同步;但方法参数和本地局部变量经由Lambda表达式访问之后,就会添加final修饰,此时变量的值在表达式域的内外均已不可修改。
public class LambdaDemo01 {private int instanceVariable = 10;private static int staticVariable = 20;public static void staticMethod(int methodVariable){int localVar = 30;// 静态域中 Lambda表达式捕获外部变量Runnable runnable = () -> {staticVariable += 1; //修改外部类静态成员// methodVariable += 1; //error: Variable used in lambda expression should be final or effectively final// localVar += 1; //error: Variable used in lambda expression should be final or effectively finalSystem.out.println("Static variable: " + staticVariable);System.out.println("Method variable: " + methodVariable);System.out.println("Local variable: " + localVar);};runnable.run();}public void instanceMethod(int methodVariable){int localVar = 30;// 非静态域中 Lambda表达式捕获外部变量Runnable runnable = () -> {instanceVariable += 1; //修改外部类实例成员System.out.println("Instance variable: " + LambdaDemo01.this.instanceVariable); //LambdaDemo01.this外部类引用System.out.println("Static variable: " + staticVariable);System.out.println("Method variable: " + methodVariable);System.out.println("Local variable: " + localVar);};instanceVariable += 2; //synchronous changerunnable.run();}public static void main(String[] args) {LambdaDemo01.staticMethod(40);LambdaDemo01 lambdaDemo01 = new LambdaDemo01();lambdaDemo01.instanceMethod(40);}
}
4.应用场景
4.1 简化函数式接口
Lambda表达式提供了一种更简洁、更便捷的方式来声明匿名函数(函数式接口的实例),这使得Java语言更加灵活,是Java函数式编程的实现基础,另一方面它也使得代码更为紧凑、简洁,并提高了可读性、减少了代码冗余。
4.2 配合Stream API
Java 8新特性中的Stream API(java.util.stream) 是一种抽象流式接口,其是一个来自数据源的元素队列并支持一系列聚合操作,比如过滤、筛选、映射、遍历等;Stream可以以一种声明的方式处理集合与数据,是函数式编程模式的重要体现。Lambda表达式常用于配合Stream API实现各种数据处理操作,使得数据处理更加简洁、直观。
public interface Stream<T> extends BaseStream<T, Stream<T>> {/*** Returns a stream consisting of the elements of this stream that match* the given predicate.** <p>This is an <a href="package-summary.html#StreamOps">intermediate* operation</a>.** @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,* <a href="package-summary.html#Statelessness">stateless</a>* predicate to apply to each element to determine if it* should be included* @return the new stream*/Stream<T> filter(Predicate<? super T> predicate);/*** Returns a stream consisting of the results of applying the given* function to the elements of this stream.** <p>This is an <a href="package-summary.html#StreamOps">intermediate* operation</a>.** @param <R> The element type of the new stream* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,* <a href="package-summary.html#Statelessness">stateless</a>* function to apply to each element* @return the new stream*/<R> Stream<R> map(Function<? super T, ? extends R> mapper);//...
}
4.3 简化集合操作
在Java 8及以上的版本中,为了能够让Lambda表达式和Java的集合类集更好的配合使用,集合Collection
接口新增了一些默认方法,例如forEach()
、removeIf()
和stream()
等,这使得使用Lambda表达式更加方便、数据处理更加高效。此处以集合遍历forEach()
操作为例,介绍如下:
(1)List
//1.List forEach 源码
default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}
}
//2.example
List<String> names = Arrays.asList("Alice", "Bob", "Tom","Krito");
// 参数不固定为t,其他参数名称也可以(name)
names.forEach(name -> {int len = name.length();System.out.println(name + " with length = " + len);
});
(2)Map
//1.Map forEach 源码
default void forEach(BiConsumer<? super K, ? super V> action) {Objects.requireNonNull(action);for (Map.Entry<K, V> entry : entrySet()) {K k;V v;try {k = entry.getKey();v = entry.getValue();} catch(IllegalStateException ise) {// this usually means the entry is no longer in the map.throw new ConcurrentModificationException(ise);}action.accept(k, v);}
}
//2.example
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 22);
ages.put("Tom", 27);
ages.put("Krito",25);
// 双参数
ages.forEach((key, value) -> System.out.println(key + " : " + value));