1. Lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
语法:
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符 或箭头操作符。它将 Lambda 分为两个部分:
左侧:指定了 Lambda 表达式需要的参数列表 (其实就是接口中的抽象方法的形参列表)
右侧:指定了 Lambda 体,是抽象方法的实现逻辑,(其实就是重写的抽象方法的方法体)
问题:在Runnable中打印一串文字
标准写法(不使用Lambda):
@Test
public void standard(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("标准模式");
}
};
runnable.run();
}
Lambda:
@Test
public void lambda1(){
Runnable runnable = ()-> system.out.println("Lambda1");
runnable.run();
}
问题:设置View的点击事件
标准写法(不使用Lambda):
@Test
public void clickStandard(Context context){
View view = new View(context);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("点击事件");
}
});
}
Lambda:
@Test
public void clickLambda(Context context){
View view = new View(context);
view.setOnClickListener(v -> System.out.println("点击事件"));
}
问题:匿名内部类的输出两数相加
标准写法(不使用Lambda):
@Test
public void addStandard(){
Consumer<Integer> consumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer + 30);
}
};
consumer.accept(20);
}
Lambda:
@Test
public void addLambda(){
Consumer<Integer> consumer = s -> System.out.println(s + 30);
consumer.accept(20);
}
问题:匿名内部类比较两数并返回结果
标准写法(不使用Lambda):
@Test
public void compaResultStandard(){
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(comparator.compare(80, 50));
}
Lambda:
@Test
public void compaResultLambda(){
Comparator<Integer> comparator = (s1, s2) -> {
return s1.compareTo(s2);
};
System.out.println(comparator.compare(80, 50));
}
总结:
->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字
函数式接口(Functional Interface)
Lambda表达式的本质:作为函数式接口的实例
如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
这种类型的接口也称为SAM接口,即Single Abstract Method interfaces。
以前用匿名实现类表示的现在都可以用Lambda表达式来写。
1、该注解只能标记在"有且仅有一个抽象方法"的接口上。
2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么 也不算抽象方法。
注:该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
类型推断
在Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
接口Api
Java 内置四大核心函数式接口
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:
1 BiConsumer<T,U>
代表了一个接受两个输入参数的操作,并且不返回任何结果
2 BiFunction<T,U,R>
代表了一个接受两个输入参数的方法,并且返回一个结果
3 BinaryOperator<T>
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4 BiPredicate<T,U>
代表了一个两个参数的boolean值方法
5 BooleanSupplier
代表了boolean值结果的提供方
6 Consumer<T>
代表了接受一个输入参数并且无返回的操作
7 DoubleBinaryOperator
代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8 DoubleConsumer
代表一个接受double值参数的操作,并且不返回结果。
9 DoubleFunction<R>
代表接受一个double值参数的方法,并且返回结果
10 DoublePredicate
代表一个拥有double值参数的boolean值方法
11 DoubleSupplier
代表一个double值结构的提供方
12 DoubleToIntFunction
接受一个double类型输入,返回一个int类型结果。
13 DoubleToLongFunction
接受一个double类型输入,返回一个long类型结果
14 DoubleUnaryOperator
接受一个参数同为类型double,返回值类型也为double 。
15 Function<T,R>
接受一个输入参数,返回一个结果。
16 IntBinaryOperator
接受两个参数同为类型int,返回值类型也为int 。
17 IntConsumer
接受一个int类型的输入参数,无返回值 。
18 IntFunction<R>
接受一个int类型输入参数,返回一个结果 。
19 IntPredicate
:接受一个int输入参数,返回一个布尔值的结果。
20 IntSupplier
无参数,返回一个int类型结果。
21 IntToDoubleFunction
接受一个int类型输入,返回一个double类型结果 。
22 IntToLongFunction
接受一个int类型输入,返回一个long类型结果。
23 IntUnaryOperator
接受一个参数同为类型int,返回值类型也为int 。
24 LongBinaryOperator
接受两个参数同为类型long,返回值类型也为long。
25 LongConsumer
接受一个long类型的输入参数,无返回值。
26 LongFunction<R>
接受一个long类型输入参数,返回一个结果。
27 LongPredicate
R接受一个long输入参数,返回一个布尔值类型结果。
28 LongSupplier
无参数,返回一个结果long类型的值。
29 LongToDoubleFunction
接受一个long类型输入,返回一个double类型结果。
30 LongToIntFunction
接受一个long类型输入,返回一个int类型结果。
31 LongUnaryOperator
接受一个参数同为类型long,返回值类型也为long。
32 ObjDoubleConsumer<T>
接受一个object类型和一个double类型的输入参数,无返回值。
33 ObjIntConsumer<T>
接受一个object类型和一个int类型的输入参数,无返回值。
34 ObjLongConsumer<T>
接受一个object类型和一个long类型的输入参数,无返回值。
35 Predicate<T>
接受一个输入参数,返回一个布尔值结果。
36 Supplier<T>
无参数,返回一个结果。
37 ToDoubleBiFunction<T,U>
接受两个输入参数,返回一个double类型结果
38 ToDoubleFunction<T>
接受一个输入参数,返回一个double类型结果
39 ToIntBiFunction<T,U>
接受两个输入参数,返回一个int类型结果。
40 ToIntFunction<T>
接受一个输入参数,返回一个int类型结果。
41 ToLongBiFunction<T,U>
接受两个输入参数,返回一个long类型结果。
42 ToLongFunction<T>
接受一个输入参数,返回一个long类型结果。
43 UnaryOperator<T>
接受一个参数为类型T,返回值类型也为T。
方法引用
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
如下三种主要使用情况:
对象::实例方法名
类::静态方法名
类::实例方法名
@Test
public void methodQuote(){
Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "FUCK";
}
};
System.out.println(supplier.get());
User user = new User("川普",80,150);
//对象::实例方法名
Supplier<String> supplierL = user::getName;
System.out.println(supplierL.get());
//类::静态方法名
Comparator<String> comparator = String::compareTo;
System.out.println(comparator.compare("aa","bb"));
//类::实例方法名
Function<User,String> function = User::getName;
System.out.println(function.apply(user));
Function<User,String> fun = new Function<User, String>() {
@Override
public String apply(User user) {
return user.name;
}
};
}
构造器引用
ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象
@Test
public void MethodConstructor(){
//构造器
Function<String, User> function = User::new;
System.out.println(function.apply("老毕登"));
Function<String,User> fun = new Function<String, User>() {
@Override
public User apply(String s) {
return new User(s);
}
};
}
数组引用
可以把数组看做是一个特殊的类,则写法与构造器引用一致。
@Test
public void MethodArray(){
//数组引用
Function<Integer, Integer[]> function = Integer[]::new;
System.out.println(Arrays.toString(function.apply(20)));
}
2. Stream API
什么是 Stream
Stream到底是什么呢?是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,Stream讲的是计算!”
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他 们会等到需要结果的时候才执行
流程:
创建Stream -> 中间操作 -> 终止操作
创建Stream:
1、Collection方式:
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
default Stream stream() : 返回一个顺序流
default Stream parallelStream() : 返回一个并行流
@Test
public void ColletcionsStream(){
List<User> users = new ArrayList<User>();
users.stream();
users.parallelStream();
}
2、Arrays方式:
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流
@Test
public void arraysStream(){
User[] users = new User[20];
Stream<User> userStream = Arrays.stream(users);
}
3、Stream.of()方式:
可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
@Test
public void streamOfStream(){
User user1 = new User("普京");
User user2 = new User("梅德韦杰夫");
Stream<User> userStream = Stream.of(user1,user2);
}
4、无限流方式
可以使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流。
@Test
public void infiniteStreamIterate(){
//迭代创建
Stream.iterate(1, t -> t *2).limit(10).forEach(System.out::println);
Stream s = Stream.iterate(1, new UnaryOperator<Integer>() {
@Override
public Integer apply(Integer integer) {
return integer * 2;
}
});
s = s.limit(20);
s.forEach(System.out::println);
}
@Test
public void infiniteStreamGenerate(){
//生成创建
Stream.generate(Math::random).limit(20).forEach(System.out::println);
}
5、BufferedReader.lines() 每行内容转成流方式
@Test
public void bufferStream() throws FileNotFoundException {
BufferedReader reader = new BufferedReader(new FileReader("F:\\test\\test.txt"));
reader.lines().forEach(System.out::println);
}
6、Pattern.splitAsStream() 将字符串分隔成流
@Test
public void patternStream(){
Pattern pattern = Pattern.compile(",");
pattern.splitAsStream("adfadfa,bbbb,cccccc").forEach(System.out::println);
}
流的中间操作
Stream是惰性流,中间操作只是将lambda表达式记录下来,返回一个新的Stream,只有终止操作被调用时,才会触发计算。这样可以保证数据被尽量少的遍历,这也是Stream高效的原因之一。中间操作分为无状态操作和有状态操作,无状态操作不会存储元素的中间状态,有状态操作会保存元素中间状态。
无状态操作: filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek() unordered()
有状态操作: distinct() sorted() limit() skip()
无状态操作是指元素的处理不受之前元素的影响;有状态操作是指该操作只有拿到所有元素之后才能继续下去.
1、筛选与切片
// 1 filter:过滤流中的某些元素
public void filter(List<User> list){
list.stream().filter(item -> item.age >= 60).forEach(item -> System.out.println(item.name));
}
// 2 limit(n):获取n个元素,限制获取元素的个数
public void limit(List<User> list){
list.stream().limit(5).forEach(item -> System.out.println(item.name));
}
// 3 skip(n):跳过n元素,配合limit(n)可实现分页
public void skip(List<User> list){
list.stream().skip(1).forEach(aa -> System.out.println(aa.name));
}
// 4 distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
public void distinct(List<User> list){
list.stream().distinct().forEach(bb -> System.out.println(bb.name));
}
2、映射
// map:接收一个函数作为参数,(常为lambda表达式)该函数会被应用到每个元素上,并将其映射成一个新的元素。
public void map(List<String> list){
list.stream().map(cc -> cc.split(",")).forEach( dd -> Arrays.stream(dd).forEach(System.out::println));
}
// flatMap:各个数组并不是分别映射一个流,而是映射成流的内容。
public void flatmap(List<String> list){
list.stream().flatMap(cc -> {
String[] res = cc.split(",");
return Arrays.stream(res);
}).forEach(System.out::println);
}
3、排序
//排序
public void sort(List<User> list){
list.stream().sorted((aa, bb) -> aa.age - bb.age).forEach(item -> System.out.println(item.name));
}
4、消费
// peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。
public void peek(List<User> list) {
list.stream().peek(aa -> aa.setName("大哥大")).forEach( item -> System.out.println(item.name));
}
流的终止操作
匹配、聚合操作
allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
findFirst:返回流中第一个元素
findAny:返回流中的任意元素
count:返回流中元素的总个数
max:返回流中元素最大值
min:返回流中元素最小值
public void over(List<User> list){
//allMatch
Boolean b = list.stream().allMatch(aa -> aa.name.length() > 10);
System.out.println(b);
//noneMatch
Boolean c = list.stream().noneMatch(aa -> aa.name.length() > 10);
System.out.println(c);
//findFirst
User user = list.stream().findFirst().get();
System.out.println(user.name);
}
规约操作
Stream中的Reduce方法:根据一定的规则将Stream中的元素进行计算后返回一个唯一的值,它有三个变种,输入参数分别是一个参数、二个参数以及三个参数。
/**
* 一个参数
*/
@Test
public void reduce(){
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5, 6);
Integer sum = s.reduce(Integer::sum).get();
System.out.println(sum);
}
/**
* 两个参数
*/
@Test
public void reduce2(){
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
System.out.println(s.reduce("丢你螺母", String::concat));
}
/**
* 三个参数
* identity: 一个初始化的值;这个初始化的值其类型是泛型U,与Reduce方法返回的类型一致;
* 注意此时Stream中元素的类型是T,与U可以不一样也可以一样,这样的话操作空间就大了;
* 不管Stream中存储的元素是什么类型,U都可以是任何类型,如U可以是一些基本数据类型的包装类型Integer、Long等;
* 或者是String,又或者是一些集合类型ArrayList等(这个和之前的两参数和单参数就不一样了)
* accumulator: 其类型是BiFunction,输入是U与T两个类型的数据,
* 而返回的是U类型;也就是说返回的类型与输入的第一个参数类型是一样的,
* 而输入的第二个参数类型与Stream中元素类型是一样的。
* combiner: 其类型是BinaryOperator,支持的是对U类型的对象进行操作;
*/
@Test
public void reduce3(){
Stream<String> s1 = Stream.of("aa", "ab", "c", "ad");
System.out.println(s1.reduce(new ArrayList<String>(), (r, t) -> {r.add(t); return r; }, (r1, r2) -> r1));
}
注:当Stream是并行时,第三个参数就有意义了,它会将不同线程计算的结果调用combiner做汇总后返回。
收集操作
collect:接收一个Collector实例,将流中元素收集成另外一个数据结构。
User p1 = new User("aaaa",26,180);
User p2 = new User("bbbb",22,182);
User p3 = new User("cccc",23,183);
User p4 = new User("ddddd",23,183);
User p5 = new User("eeee",26,183);
List<User> list = Arrays.asList(p1,p2,p3,p4,p5);
//装成list
List<Integer> ageList = list.stream().map(User::getAge).collect(Collectors.toList());//[26,22,22]
//转成set
Set<Integer> ageSet = list.stream().map(User::getAge).collect(Collectors.toSet());//[26,22]
//转成map,注:key不能相同,否则报错
Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(User::getName, User::getAge));
//字符串分隔符连接
String joinName = list.stream().map(User::getName).collect(Collectors.joining(",", "(", ")"));
//聚合操作
//1.总数
Long count = list.stream().collect(Collectors.counting());
//2.最大年龄 (最小的minBy同理)
Integer maxAge = list.stream().map(User::getAge).collect(Collectors.maxBy(Integer::compare)).get();
//3.所有人的年龄求和
Integer sumAge = list.stream().collect(Collectors.summingInt(User::getAge));
//4.平均年龄
Double averageAge = list.stream().collect(Collectors.averagingDouble(User::getAge));
// 带上以上所有方法
DoubleSummaryStatistics statistics = list.stream().collect(Collectors.summarizingDouble(User::getAge));
System.out.println("count:" + statistics.getCount() + ",max:" + statistics.getMax() + ",sum:" + statistics.getSum() + ",average:" + statistics.getAverage());
//分组 按年龄分组
Map<Integer, List<User>> ageMap = list.stream().collect(Collectors.groupingBy(User::getAge));
//分区
//分成两部分,一部分大于10岁,一部分小于等于10岁
Map<Boolean, List<User>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));
//规约
Integer allAge = list.stream().map(User::getAge).collect(Collectors.reducing(Integer::sum)).get();
System.out.println();
System.out.println();
System.out.println(allAge);
3.接口增强
在java8以前的版本中,定义一个接口时,所有的方法必须是抽象方法,不能有具体实现,这是java语法规定的。但是在java8中定义一个接口时,在满足特定的前提下,可以有方法的具体实现。这样一个接口中可以有属性,可以有抽象方法,也可以有具体的方法,这跟java8以前的接口比,明显接口的功能变得强大了。
- 1.新增 : 默认方法 default 关键字修饰
default 关键字修饰方法,必须有方法体
实现类可以重写该方法
调用方式 : 通过实现类的对象进行调用 - 2.新增 :静态方法
static 关键字修饰方法,必须有方法体
实现类不可以重写该方法
调用方式 : 只能通过 【接口名.静态方法名】 进行调用
4.Optional
传统的写代码方式经常会遇到NullPointerException,这就需要我们在代码中经常判空。而判空的写法又会显得很累赘,这里就可以用到Optional来简化代码.
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 类的引入很好的解决空指针异常
构建Optional
构建一个Optional对象;方法有:empty( )、of( )、ofNullable( )
相关API
- isPresent(): 持有非空值,返回true;否则false;
- ifPresent(): 如果 Optional 中有值,则对该值调用consumer.accept,否则什么也不做。
- orElse: 参数是一个值,如果 Optional 中有值则将其返回,否则返回 orElse 方法传入的参数。
- orElseGet: 传入的参数为一个 Supplier 接口的实现。
- orElseThrow: 没有值的时候会抛出异常,抛出的异常由传入的 exceptionSupplier 提供。
- map:如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional。
- flatMap: map 方法参数中的函数 mapper 输出的是值,然后 map 方法会使用 Optional.ofNullable 将其包装为 Optional;而 flatMap 要求参数中的函数 mapper 输出的就是 Optional。
- filter: 接受一个 Predicate 来对 Optional 中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional;否则返回 Optional.empty。