13Lambda、函数式接口、Stream
1、Lambda表达式
Lambda表达式的介绍与使用
匿名内部类的格式:
new 父类或接口() {
//重写父类或接口的方法
}
匿名内部类很多地方是冗余的。
比如使用匿名内部类的方式实现多线程。
因为Thread构造方法参数需要一个Runnable类型的数据,所以我们不得不写new Runnable。
因为要重写Runnable中的run方法,所以又不得不写了run方法等声明
整个匿名内部类中最关键内容是方法,方法最重要的东西有前中后三点。
前: 参数。
中: 方法体
后: 返回值
最好的情况是我们只关注匿名内部类中这些最核心的东西(方法的参数,方法体,返回值)。
Lambda表达式只关注方法的参数,方法体,以及返回值。
Lambda表达式是匿名内部类的简化写法。
Lambda表达式是属于函数式的编程思想
面向对象思想: 怎么做
函数式编程思想: 做什么
匿名内部类的好处是可以省略单独创建.java文件的操作。但是匿名内部类也有缺点,就是语法有些冗余。匿名内部类中最核心的东西是方法的参数,方法体,返回值,其他的内容都是为了满足格式不得不写的一些东西。匿名内部类的简化写法是lambda表达式,lambda表达式只需要关注方法的参数,方法体,返回值。
- Lambda的标椎格式:
(参数类型 参数名)->{
方法体;
return 返回值;
}
格式解释:
(1)小括号中的参数和之前的方法的参数一样,多个参数之间要使用逗号隔开。
(2)->箭头是一个运算符,表示指向性的动作。
(3)大括号中的内容和之前的方法大括号中的内容是一样的。
Lambda表达式可以省略面向对象中的一些条条框框,让我们只关注最核心的内容。
使用了函数式编程的思想:可推导,可以省略。
因为Thread构造方法中需要Runnable接口类型的参数,所以可以省略new Runnable。同时因为Runnable中只有一个 抽象方法run,所以重写的必然是这个抽象方法,那么也可以省略run方法的声明部分。
使用比较器排序,对保存学生对象的集合根据年龄升序排序。
public static void main(String[] args) {
//创建集合
List<Student> list = new ArrayList<>();
//添加学生对象
list.add(new Student("嫐", 20));
list.add(new Student("嬲", 18));
list.add(new Student("挊", 22));
//使用比较器排序,对集合中的学生对象根据年龄升序排序。
//Collections.sort(list, new Rule());
//进行比较器排序,使用匿名内部类的方式
/*
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
});
*/
//进行比较器排序,使用Lambda表达式
/*
Collections.sort(list, (Student o1, Student o2) -> {
return o1.getAge() - o2.getAge();
});
*/
Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge());
//输出集合
System.out.println(list);
}
- Lambda表达式的标准格式:
(参数类型 参数名)->{
方法体;
return返回值;
}
省略规则:小括号中的参数类型可以省略;
如果小括号中只有一个参数,那么可以省略小括号。
如果大括号中只有一条语句,那么无论该方法有没有返回值,都可以省略大括号。 - 接口:
package cn.itcast.demo01_lambda;
public interface MyInterface {
//提供抽象方法
void printStr(String str);
}
- 测试类
public class Test {
public static void main(String[] args) {
method(new MyInterface() {
@Override
public void printr(String string) {
System.out.println(string);
}
});
method((String str) ->{
System.out.println(str);
});
method(str -> System.out.println(str));
}
public static void method(MyInterface myInterface) {
myInterface.printr("hello");
}
}
Lambda表达式的使用前提
(1)必须要有接口(不能是抽象类),接口中必须有且只有一个需要被重写的抽象方法。
(2)必须支持上下文推导。要能够推导出来这个Lambda表达式表示的是哪个接口中的内容。常用的一种上下文推导方式是使用Lambda表达式当做方法参数(传递给一个接口类型)也可以将Lambda表达式赋值给一个接口类型的变量。
public static void method(MyInterface m) {
m.printStr("hello");
}
public static void main(String[] args) {
//调用method方法
//method(str -> System.out.println(str));
//使用匿名内部类方式创建对象
/*
MyInterface m = new MyInterface() {
@Override
public void printStr(String str) {
System.out.println(str);
}
};
*/
//将Lambda表达式赋值给接口类型。
MyInterface m = str -> System.out.println(str);
m.printStr("HELLO");
}
函数是接口
函数式接口的介绍
如果一个接口中有且仅有一个需要被重写的抽象方法,那么这个接口就是一个函数式接口。函数式接口可以当做Lambda表达式的使用前提。
有一个注解叫做@FunctionalInterface可以验证一个接口是否是函数式接口, 如果在接口上面加上@FunctionalInterface注解报错,那么就说明该接口不是函数式接口,如果在接口上面加上@FunctionalInterface注解不报错,那么就说明该接口是一个函数式接口。
@FunctionalInterface仅仅用来验证一个接口是否是函数式接口,如果不使用该注解,只要接口符合函数式接口的规则,那么他就是一个函数式接口。
在jdk8的时候,提供了java.util.function,这个包下面有大量的函数式接口。
其中有一个函数式接口叫做Consumer,可以把它看成一个消费者,可以去消费(使用)一个数据。
- 抽象方法:void accept(T t):用来消费(使用)一个数据。
public class Demo01Consumer {
//定义方法,使用函数式接口Consumer当做参数
public static void method(Consumer<String> c) {
//通过c调用accept方法,使用字符串
c.accept("hello");
}
public static void main(String[] args) {
//调用method方法,传递匿名内部类对象。
/*
method(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
*/
//调用method方法,传递Lambda表达式
method(s -> System.out.println(s));
}
}
- 在Java中还有一个函数式接口叫做Predicate,这个函数式接口可以判断一个数据是否符合要求。
抽象方法:boolean test(T t): 判断一个数据t是否符合要求。
public class Demo02Predicate {
//定义方法,使用函数式接口Predicate当做方法参数
public static void method(Predicate<String> p) {//p= s -> s.length() == 5
//调用方法,判断hello这个字符串是否符合要求
boolean flag = p.test("hello");
System.out.println(flag);
}
public static void main(String[] args) {
//调用method方法
method(s -> s.length() == 5);
}
}
Stream流
Stream流的获取
Stream是一个接口,这个接口表示的就是流。
获取Stream流的两种方法:
(1)通过Collection单列集合获取。
(2)通过数组获取流。
- 通过Collection单列集合获取流:
在Collection中有一个方法,叫做stream,可以得到集合对应的流。
Stream stream():获取集合对应的流。
public class Demo02CollectionGetStream {
public static void main(String[] args) {
//创建List集合
List<String> list = new ArrayList<>();
//添加元素
list.add("aa");
list.add("bb");
list.add("cc");
//调用集合的stream方法,获取流
Stream<String> stream = list.stream();
//调用stream的toArray将流转成数字,然后再使用数组的工具类将内容输出
System.out.println(Arrays.toString(stream.toArray()));
}
}
- 通过数组获取流:
(1)使用Stream中的静态方法of获取。
static Stream of(T… values): 根据数组获取流,参数是可变参数,可以传递多个数据,也可以传递数组。
(3)使用Arrays工具类中的stream方法获取。
public class Demo03ArrayGetStream {
public static void main(String[] args) {
//1. 使用Stream中的静态方法of获取
String[] strArr = {"aa", "bb", "cc", "dd"};
//static Stream of(T... values): 根据数组获取流
//Stream<String> stream = Stream.of(strArr);
Stream<String> stream = Stream.of("hello", "world", "java");
//输出Stream流中的内容
System.out.println(Arrays.toString(stream.toArray()));
//2. 使用Arrays工具类中的stream方法获取
Stream<String> stream2 = Arrays.stream(strArr);
System.out.println(Arrays.toString(stream2.toArray()));
}
}
Stream流的常用方法
- (1)forEach:在Stream中,有一个方法叫做forEach,可以对流中的元素进行逐一处理。
void forEach(Consumer action):参数需要传递Consumer类型,Consumer表示消费者。
forEach方法的参数定义的就是处理的规则(如何进行处理)
forEach方法参数是函数式接口Consumer类型, 那么我们可以传递Lambda表达式, 这个Lambda表达式表示的含义是如何处理。 - (2)在Stream中,有一个方法叫做filter,可以对流中的元素进行过滤(筛选)
Stream filter(Predicate predicate):对流中的元素进行过滤(筛选)
filter方法参数需要一个Predicate类型,Predicate这个函数式接口的作用是用来判断一个数据是否符合要求。
我们要在参数Predicate中定义过滤的规则,如果判断成立,那么表示该数据留下,如果判断不成立表示该数据过滤掉。
filter方法参数是Predicate函数式接口,那么我们可以传递Lambda表达式, 该Lambda表达式表示的是Predicate中
唯一的抽象方法test的内容(重写后), 我们要在Lambda表达式中定义过滤规则,如果条件成立,那么数据不会过滤掉,
如果条件不成立,那么数据会被过滤掉。 - (3)在Stream中,有一个方法叫叫做count,可以获取流中元素的个数
long count(): 获取流中元素的个数 - (4)在Stream中,有一个方法叫做limit,可以获取流中的前几个元素
Stream limit(long n): 获取流中的前n个元素。 - (5)在Stream中,有一个方法叫做skip,可以跳过流中的前几个元素。
Stream skip(long n):跳过流中的前n个元素 - (6)在Stream中,有一个静态方法叫做concat,可以对两个流进行合并。
static Stream concat(Stream a, Stream b): 对参数a和b这两个流进行合并,合并成新的流。 - 注意:流的注意事项:
1. 调用Stream的非终结方法会返回Stream流对象,但是返回的Stream是一个新的流。(forEach、count为终结方法,不能连接操作)
2. 流只能一次性使用。