JDK8新特性之Lambda表达式
1 Java语言发展史
1991 年, SUN 公司中 James Goslin 带领团队启动”Green”项目.开发 了一种称为”Oak”语言
1995 年, SUN 公司把 Oak 语言改名为 Java.
2004 年 9 月, 发布 J2SE1.5. 为了表示该版本的重要性,把 J2SE1.5 更名为 JavaSE 5.0 2014 年, 发布了 Java8 正式版
2018 年 9 月发布了 Java11.
Oracle 支持 Java8 到 2025 年, 支持 Java11 到 2026 年.
2 什么是Lambda表达式
Lambda 是数学中的一个函数. Java 中使用方法来代替函数,方法总 是作为类或对象的一部分存在的. 可以把 Lambda 看作是一个匿名方法, 拥有更简洁的语法。
3 Lambda表达式语法
语法: (参数列表) -> {语句;}
Lambda 表达式由参数列表和一个 Lambda 体组成, 通过箭头连接
说明:
1) 当只有一个参数时, 参数列表的小括弧可以省略 x -> { System.out.println(x); }
2) 参数列表中参数的数据类型可以省略 ( x,y ) -> {x.compareTo(y);}
3) 如果 Lambda 体只有一条语句,大括弧也可以省略 x -> { return x + 2 ; }
4) 如果Lambda 体中只有一条return 语句, return 关键字可以省略 (x,y) -> x+y
通过举例说明(以下都是有效的Llambda表达式语法):
(String s ) -> s.length()
(Student stu) -> stu.getAge() > 18
(int x, int y ) -> {
System.out.print(“result”);
System.out.println( x + y );
}
() -> {}
() -> “hehe”
但是注意:
(String s) -> { “hello” } //不合法,因为“hello”不是一个表达式
如果想要合法,可以改为以下写法:
(String s) -> { return “hello” ; }
(String s) -> “hello”
4 什么情况下使用Lambda表达式
①布尔表达式, 判断参数接收的 list 集合是否为空
( List list) -> list.is mpty()
②创建对象,并返回
() -> new Student()
③消费(使用)一个对象, 把参数接收学生对象的姓名打印出来
(Student stu ) -> { System.out.println( stu.name) ; }
④从 一个对象中选择, 返回参数对象的成绩
(Student stu) -> stu.getScore()
⑤组合两个值 (int a, int b) -> a*b 比较两个对象
(Student stu1, Student stu2) -> stu1.getScore() - stu2.getScore()
5 函数式接口
在 JDK8 中, 引用了函数式接口. 就是只定义一个抽象方法的接口. 如: Comparator 接口 , Runnable 接口
@FunctionalInterface //注解,声明接口为函数式接口
public interface Adder{
int add(int x, int y);
}
//当前ByteAdder接口不是函数式接口, 从Adder接口中继承了一个抽象方法,在本接口中又定义了一个抽象方法
public interface ByteAdder extends Adder{
byte add( byte b1, byte b2);
}
//当前 Nothing 接口也不函数式接口,因为没有抽象方法
public inteface Nothing{ }
函数式接口就是为 Lambda 表达式准备的,或者说 Lambda 表达式 必须实现一个函数式接口
java.util.function 包中定义了一些基本的函数式接口,如 Predicate, Consumer, Function,Supplier 等
5.1 Predicate接口
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
Predicate接口中定义一个抽象方法 test(T ),接收一个 T 类型的 对象参数,返回一个布尔值 当需要一个涉及类型 T 的布尔表达式时,可以使用这个接口
public class Test01 {
public static void main(String[] args) {
List<String> list = Arrays.asList("lisi", "zhangsan", "wangwu", "chenqi", "maliu");
List<String> result = filter(list, x -> x.length() > 6);
System.out.println(result);
}//定义方法, 方法可以把 List 列表中符合条件的元素存储到一个新的 List 列表中返回
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
//遍历 list 参数列表,把符合 predicate 条件的元素存储到 result 中
for (T t : list) {
if (predicate.test(t)) {
result.add(t);
}
}
return result;
}
}
5.2 Consumer 接口
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); };
}
}
Consumer接口定义了一个 accept(T)抽象方法,可以接收一个 T 类型的对象,没有返回值. 如果需要访问类型 T 的对象,对该对象做一 些操作,就可以使用这个接口.
在 Collection 集合和 Map 集合中都有 forEach(Consumer)方法
public class Test02 {
public static void main(String[] args) {
List<String> list = Arrays.asList("lisi", "gg", "jj", "tuantuan", "daimeir", "timo", "XDD");
list.forEach(s -> System.out.println(s));
Map<String, Integer> map = new HashMap<>();
map.put("lisi", 22);
map.put("feifei", 28);
map.put("zhangxiaosan", 20);
map.put("chenqi", 30);
map.forEach((k, v) -> System.out.println(k + "->" + v));
}
}
5.3 Function接口
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
Function<T,R>接口中定义了accept( T )方法,接收一个T类型的参数对象,返回一个 R 类型的数据. 如果需要定义一个 Lambda,将一个输入 对象的信息加工后映射到输出,就可以使用该接口
public class Test03 {
public static void main(String[] args) {
List<String> list = Arrays.asList("lisi", "gg", "jj", "tuantuan", "daimeir", "timo", "XDD");
//把 list 集合中存储字符串的长度映射出来
List<Integer> result = map(list, x -> x.length());
System.out.println(result);
}
// 把一个 List 列表映射到另外一个 List 列表中
public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
List<R> result = new ArrayList<>();
//存储映射后的数据
for (T t : list) {
// 把 apply 对 t 对象的操作结果保存 到 result 集合中
result.add(function.apply(t));
}
return result;
}
}
5.4 对原始类型的处理
泛型只能绑定引用类型,不能使用基本类型 Java8 中函数式接口为基本类型也提供了对应的接口,可以避免在 进行输入输出原始数据时频繁进行装箱/拆箱操作. 一般来说,在针对专门的基本类型数据的函数式接口名称前面加 上 了 对应的原始类型前缀
如 : IntPredicate, IntConsumer,IntFunction 等.
public class Test04 {
public static void main(String[] args) {
IntPredicate evenNumbers = (int x ) -> x % 2 == 0 ;
boolean test = evenNumbers.test(10);
System.out.println(test);
}
}
6 捕获Lambda
Lambda 表达式可以使用外层作用域中定义的变量,如成员变量,局 部变量,称为捕获 Lambda.
public class Test05 {
int xx = 123; //实例变量
static int yy = 456; //静态变量
public static void main(String[] args) {
IntUnaryOperator operator = i -> {
return i + yy; //把参数接收的数据与静态变量的值相加并返回
};
System.out.println( operator.applyAsInt(10));
//在 Lambda 表达式中使用局部变量,局部变量必须是 final 修饰,或者是事实上的 final
int zz = 789; //局部变量
final int ff = 147; //final 修饰的局部变量
IntUnaryOperator operator2 = i->{
return i + ff;
};
System.out.println(operator2.applyAsInt(10));
//zz 虽然没有使用 final 修饰,如果它是事实的 final,后面没有 修改 zz 值的代码
operator2 = i ->{
return i + zz;
};
System.out.println( operator2.applyAsInt(10));
// zz = 258; //如果再对 zz 重新赋值,则上面的 lambda 表达式语法错误
}
}
main方法中只能使用静态变量,在实例方法中也可以使用成员变量 xx.
7 方法引用
7.1 什么是方法引用
方法引用可以让你重复使用现有的方法定义, 并像 Lambda 一样 传递它们. 如:
list.forEach( x -> System.out.println(x) );
使用方法引用可以这样:
list.forEach( System.out :: println );
方法引用可以看作是仅仅调用特定方法的 Lambda 表达式的一种 快捷写法. 需要使用方法引用时, 目标引用放在分隔符 ::前面, 方法名放在:: 的后面, 注意,只需要方法名不需要小括弧
(Stude t stu) -> stu getScore()
改为方法引用 :
Stude t :: getScore ()
Thread.currentThread().dumpStack()
改 为 方 法 引 用 :
Thread.currentThread() :: dumpStack
(Str, i) -> Str.substring(i)
改为方法引用:
String :: substring
a -> System.out.println(a)
改为方法引用:
System.out :: prinltn
7.2 如何构建方法引用
方法引用主要有三类:
1)指向静态方法的方法引用
Integer[] data ={65,23,87,2,34,99};
Arrays.sort( data, Integer::compare );
Arrays.sort()方法,需要传入一个Comparator比较器,Comparator是一个函数式接口,函数式接口可以赋值Lambda表达式
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
Integer接口的compareTo()方法和compare()方法。
compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
2) 指向任意类型的实例方法的引用
Lambda 表达式 (args0, args1) -> args0.instanceMethod(args1)
args0 是 Classname 类型的一个对象
方法引用: Classname :: instanceMethod
3) 指向现有对象的实例方法的引用
Lambda 表达式: (args) -> obj.instanceMethod(args)
方法引用: obj::instanceMethod
public class Test06 {
public static void main(String[] args) {
Integer[] data ={65,23,87,2,34,99};
//引用静态方法
Arrays.sort( data, Integer::compare );
System.out.println(Arrays.toString(data));
List<String> list = Arrays.asList("a","b");
//之前只能传入匿名内部类对象
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
//现在可以传入Lambda表达式 因为 Comparator 是一个函数式接口
list.sort((a,b)->{return a.compareTo(b);});
//传入方法引用
//引用实例方法
list.sort(String::compareTo);
//对 System.out 这个实例方法的引用
list.forEach(System.out::println);
}
}
7.3 构造方法引用
对于一个现有的构造方法,可以使用类名和关键字 new 来创建 一个构造方法的引用: Classname::new
public class Test07 {
public static void main(String[] args) {
//1)引用无参构造方法
Supplier<Person> supplier = Person::new;
Person p1 = supplier.get();
System.out.println(p1);
//2)引用有一个参数的构造方法
Function<String, Person> function = Person::new;
Person p2 = function.apply("lambda");
System.out.println(p2);
//3)引用有两个参数的构造方法
/BiFunction<String, Integer, Person> biFunction = Person::new;
Person p3 = biFunction.apply("lambda", 28);
System.out.println(p3);
//4)如果引用有三个参数及三个以上参数的构造方法,需要自定义匹配的函数式接口
TriFunction<String, Integer, String, Person> triFunction = Person::new;
Person p4 = triFunction.apply("lambda", 35, "男");
System.out.println(p4);
}
}
//@FunctionalInterface不加也可以,apply方法名可随意定义
@FunctionalInterface
interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
class Person {
String name;
int age;
String gender;
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this.name = name;
}
public Person() {
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + '}';
}
}
8 Lambda表达式练习
public class Test08 {
public static void main(String[] args) {
//定义 List 集合存储
List<Student> list = new ArrayList<>();
list.add(new Student("lisi", 80));
list.add(new Student("zhangsan", 30));
list.add(new Student("wangwu", 90));
list.add(new Student("feifei", 60));
list.add(new Student("mingge", 100));
System.out.println(list);
//使用匿名内部类排序
list.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
});
System.out.println(list);
//使用 Lambda 表达式
list.sort((p1, p2) -> p2.name.compareTo(p1.name));
System.out.println(list);
//Comparator 接口中有一个 comparing 静态方法返回 Comparator 比较器
list.sort(Comparator.comparing((stu) -> stu.name));
System.out.println(list);
//方法引用,Student 类中 getScore 方法返回成绩
list.sort(Comparator.comparing(Student::getScore));
System.out.println(list);
}
}
class Student {
String name;
int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", score=" + score + '}';
}
public int getScore() {
return score;
}
}