write:2022-5-19
文章目录
1. Lambda表达式的基本用法
eg:
import java.util.*;
public class SimpleTester {
public static void main(String[] args) {
String[] data = {"Tom", "Mike","Mary","Linda","Jack"};
List<String> names = Arrays.asList(data);
// 方式一:传统的遍历集合的方式
for(String name : names) {
System.out.println(name);
}
//方式二:使用 Lambda 表达式
names.forEach((name) -> System.out.println(name));
//方式三:使用 Lambda 表达式
names.forEach(System.out::println);
}
}
Lambda表达式的基本语法为:
(Type1 param1, Type2 param2, …, TypeN paramN) -> {
statment1;
statment2;
//…
return statmentM;
}
从Lambda表达式的基本语法可以看出,Lambda表达式可以理解为一段带有输入参数的可执行语句块,这种语法表达方式也可称之为函数式表达。
刚才代码中的Lambda表达式:names.forEach((name) -> System.out.println(name)); 它所表达的Lambda表达式的完整语法应该是:
(String name) ->{
System.out.println(name);
return; //return语句没有返回值,因此可以省略
}
name的类型简写是因为可以从上下文推断出name的类型;
2. Lambda表达式的简化形式
2.1 参数类型可以省略
绝大多数情况下,编译器都可以从上下文环境中聪明地推断出Lambda表达式的参数类型,例如对于以上Lambda表达式,编译器能推断出name变量的类型为String,因此Lambda表达式可以简化为:
(name) -> {
System.out.println(name + “,”);
return;
}
2.2 当Lambda表达式的参数个数只有一个,可以省略小括号
以上Lambda表达式可以简化为:
name -> {
System.out.println(name + “,”);
return;
}
2.3 当Lambda表达式只包含一条语句时,可以省略大括号、语句结尾的分号
此外,当return语句没有返回值时也可以省略。以上Lambda表达式可以简化为:
name -> System.out.println(name + “,”)
2.4 Lambda表达式中符号“->”后面也可以仅包含一个普通的表达式
语法为:
(Type1 param1, Type2 param2, …, TypeN paramN) -> (expression)
例如:
(int a,int b)->(a*b+2)
从以上可以看出,Lambda表达式非常的灵活也非常的简洁;
3. Lambda表达式的用途
3.1 用Lambda表达式代替内部类
eg:
import java.util.*;
public class InnerTester {
public static void main(String[] args) {
//方式一:使用匿名内部类
new Thread(new Runnable() {
public void run() {
System.out.println("Hello world !");
}
}).start();
//方式二:使用Lambda表达式
new Thread(()-> System.out.println("Hello world !")).start();
//方式三:使用Lambda表达式
Runnable race = () -> System.out.println("Hello world !");
new Thread(race).start();
}
}
3.2 Lambda表达式和集合的forEach()方法
从JDK5开始,Java集合都实现了java.util.Iterable接口,它的forEach()方法能够遍历集合中的每个元素。forEach()方法的完整定义如下:default void forEach(Consumer<? super T> action)。
forEach()方法有一个Consumer 接口类型的action参数,它包含了对集合中每个元素的具体操作行为。action参数所引用的Consumer 实例必须实现Consumer接口的accept(T t)方法,在该方法中指定对参数t所执行的具体操作。
例如以下forEach()方法中的Lambda表达式相当于是Consumer类型的匿名对象,它指定对每个元素的操作为打印这个元素:
names.forEach((name) -> System.out.println(name + “,”));
Person类:
public class Person{
private String name; //姓名
private int age; //年龄
public Person(String name,int age){
this.name=name;
this.age=age;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
…… //setAge(int age)和getAge()方法
}
EachTester类:
import java.util.*;
public class EachTester {
public static void main(String[] args) {
List<Person> persons = new ArrayList<Person>(){
{ //匿名类初始化代码
add(new Person("Tom",21));
add(new Person("Mike",32));
add(new Person("Linda",19));
}
};
//Lambda表达式,相当于是Consumer类型的匿名对象
persons.forEach( (Person p) ->{//指定对每个元素的具体操作,相当于实现Consumer接口的accept(T t)方法
p.setAge(p.getAge()+1);
System.out.println(p.getName()+":"+p.getAge());
}
);
}
}
3.3 用Lambda表达式对集合进行排序
import java.util.*;
public class SortTester {
public static void main(String[] args) {
String[] data = {"Tom", "Mike","Mary","Linda","Jack"};
List<String> names = Arrays.asList(data);
//方式一:通过创建匿名的Comparator实例来排序
Comparator<String> cp=new Comparator<String>() { //普通方法:实现Comparator接口的compare方法
public int compare(String s1, String s2) {
return (s1.compareTo(s2));
}
};
Collections.sort(names,cp);
//方式二:用Lambda表达式来排序
Comparator<String> sortByName=(String s1,String s2)->(s1.compareTo(s2));
Collections.sort(names, sortByName);
//方式三:用Lambda表达式来排序
Collections.sort(names,(String s1,String s2)->(s1.compareTo(s2)));
names.forEach(System.out::println);
}
}
3.4 Lambda表达式与Stream API联合使用
Stream接口提供了一组功能强大的操纵集合的方法:
filter(Predicate<? super T> predicate):对集合中的元素进行过滤,返回包含符合条件的元素的流。
forEach(Consumer<? super T> action):遍历集合中的元素。
limit(long maxSize):返回参数maxSize所指定个数的元素。
max(Comparator<? super T> comparator):根据参数指定的比较规则,返回集合中最大的的元素。
min(Comparator<? super T> comparator):根据参数指定的比较规则,返回集合中最小的的元素。
sorted():对集合中的元素自然排序。
sorted(Comparator<? super T> comparator):根据参数指定的比较规则,对集合中的元素排序。
mapToInt(ToIntFunction<? super T> mapper):把当前的流映射为int类型的流,返回一个IntStream对象。ToIntFunction接口类型的参数指定映射方式。ToIntFunction接口有一个返回值为int类型的applyAsInt(T value)方法,该方法指定把参数value映射为int类型的方式。
怎么样得到Stream对象:
Collection接口的stream()方法返回一个Stream对象。程序可以通过这个Stream对象操纵集合中的元素。
eg:
import java.util.*;
public class ColTester {
public static void main(String[] args) {
List<Person> persons = new ArrayList<Person>(){
{ //匿名类初始化代码
add(new Person("Tom",21));
add(new Person("Mike",32));
add(new Person("Linda",19));
add(new Person("Mary",29));
}
};
persons.stream() //stream()方法得到流对象
.filter(p -> p.getAge()>20) //过滤条件为年龄大于20
.forEach(p -> System.out.println(p.getName()+":"+p.getAge()));
persons.stream()
.sorted( (p1, p2) -> (p1.getAge() - p2.getAge()) ) //按照年龄排序
.limit(3) //取出三个元素
.forEach(p -> System.out.println(p.getName()+":"+p.getAge()));
int maxAge = persons.parallelStream() //获得并行流
//把包含Person对象的流映射为保存其的age属性的int类型流
.mapToInt(p -> p.getAge())
.max()
.getAsInt();
System.out.println("Max Age:"+maxAge);
}
}
4. Lambda表达式中的方法引用
以下两种Lambda表达式是等价的:
names.forEach((name) -> System.out.println(name)); //有参数name的引用
或者:
names.forEach(System.out::println); //无参数的引用
在编译器能根据上下文来推断Lambda表达式的参数的场合,可以在Lambda表达式中省略参数,直接通过“::”符号来引用方法。方法引用的语法格式有以下三种:
第一种方式: objectName::instanceMethod //引用实例方法
第二种方式: ClassName::staticMethod //引用静态方法
第三种方式: ClassName::instanceMethod //引用实例方法
eg:
x->System.out.println(x)
等同于: System.out::println //引用实例方法
(x, y)->Math.max(x,y)
等同于: Math::max //引用静态方法
x->x.toLowerCase()
等同于: String::toLowerCase //引用实例方法
5. 函数式接口(FunctionalInterface)
(1)在JDK8中,定义了一个Annotation标注类型的函数式接口FunctionalInterface:
public @interface FunctionalInterface
(2)Lambda表达式只能赋值给声明为函数式接口的Java类型的变量。
(3)Consumer、Runnable、Comparator、Predicate接口都标注为函数式接口,因此可以接受Lambda表达式。
(4)以下代码把Lambda表达式赋值给一个Runnable类型的变量,这是合法的:
Runnable race = () -> System.out.println(“Hello world !”);
(5)String类没有标注为函数式接口。以下代码试图把Lambda表达式赋值给一个String类型的变量,这是非法的,会导致编译错误:
String str=()->{return “hello”.toUpperCase();};
(6)只要查阅Java API文档,就能了解一个Java类是否被标注为函数式接口。例如在Java API文档中,Runnable接口的声明如下,由此可以看出它被标注为函数式接口:@FunctionalInterface public interface Runnable
6. 课堂小结
- Lambda表达式本质只是一颗让编程人员更加得心应手的“语法糖”,它只存在于Java源代码中,由编译器把它转换为常规的Java类代码。
- Lambda表达式有点类似于方法,由参数列表和一个使用这些参数的主体(可以是一个表达式或一个代码块)组成。
- Lambda表达式与Stream API联合使用,可以方便地操纵集合,完成对集合中元素的排序和过滤等操作。
7. 练习题
- 仿照ColTester类,对persons集合过滤,过滤条件为姓名字符串的长度大于3,接着按照姓名排序,再把集合中前三个Person对象的信息打印出来。
/* 思考题1的答案 */
import java.util.*;
public class ColTester1 {
public static void main(String[] args) {
List<Person> persons = new ArrayList<Person>(){
{ //匿名类初始化代码
add(new Person("Tom",21));
add(new Person("Mike",32));
add(new Person("Linda",19));
add(new Person("Mary",29));
}
};
persons.stream()
.filter(p -> p.getName().length()>3) //姓名字符串的长度大于3
.sorted( (p1, p2) -> (p1.getName().compareTo(p2.getName())) )
.limit(3) //取出三个元素
.forEach(p -> System.out.println(p.getName()+":"+p.getAge()));
}
}
- 以下程序代码是否能编译通过?
Integer i=()->(new Integer(var));
[答案]编译出错,提示:不兼容的类型: Integer 不是函数式接口
- 把以下程序代码改为使用Lambda表达式:
new Thread(new Runnable() {
int var=0;
public void run() {
for(int i=0;i<10;i++)
System.out.println(i);
}
}).start();
[答案]
new Thread(()->{
for(int i=0;i<10;i++)
System.out.println(i);
}).start();