从实用的角度出发,总结一下经常能用到的一些特性!
文章目录
1、Lambda 表达式
网上解释众说纷纭,以下也是我师傅的个人理解,是理解这个表达式来的过程。
1.1 是什么?
Lambda表达式是一个 有且只有一个实例抽象方法的接口 的 匿名实现类 的 简洁替身。
其中 有且只有一个实例抽象方法的接口 就是所谓的 “函数式接口”
什么是函数式接口?
1、接口中有且仅有一个 实例抽象方法
2、允许定义default实例抽象方法与静态非抽象方法
3、允许重写Object中的public方法
4、@FunctionInterface注解不是必须的,它的存在只为了编译器更好的检查,含有该注解,但是不符合上述要求时会编译错误
简化一下就是:
Lambda表达式是一个函数式接口 的 匿名实现类 的 简洁替身
还是拗口是吗?不要急,下面有个例子就能理解这个“定义”的来由了
场景是这样,有一个学生类,需要根据名字,年龄,分数进行一个排序
可以看到我们需要实现自定义的排序规则,需要,定义类,最简洁也得定义一个匿名内部类,来重写compare方法来实现我们的需求。然后去调用我们自己重写的方法
在这里我默认大家是知晓Lambda表达式的基本语法!当引入 Lambda表达式的概念以后,就可以根据其语法来进行 简化 我们的上述操作。对于下面的run方法,我们直接使用Lambda表达式来将上面具体的实现(函数)进行“传递”(Lambda 允许把函数作为一个方法的参数)
这样,回头再看,这个概念
Lambda表达式是一个函数式接口 的 匿名实现类 的 简洁替身
是不是清晰一点?我们暂时不需要明白它为什么可以这么做,只要明白,这么做,不需要定义匿名内部类,直接传递,这样可以少些一些代码。尽管这样理解有一点浅薄,但确是一个深入理解>Lambda表达式,不错的出发方向。
1.2 Lambda表达式基本格式 / 语法
(parameters) -> expression
或 (parameters) ->{ statements; }
也就是说,形如以上规则,将参数列表,通过特定符号->
传入一个表格式亦或是代码块,这样的一个表达式,我们就称其为 Lambda表达式。
同理,我们也不需要知道为什么是这样,只需要知道,这样可以用,Java8认识它就好。
其他:
-
类型可推断时,可以省略类型
Test<String> t = (String s) -> {System.out.println(s);}
=>
Test<String> t = (s) -> {System.out.println(s);}
-
有且只有一个参数时,无需定义圆括号,但无参和多个参数需要定义圆括号
Test<String> t = (String s) -> {System.out.println(s);}
=>
Test<String> t = s -> {System.out.println(s);}
-
如果主体只包含一个语句,就不需要使用大括号,包括语句后的分号。
Test<String> t = (String s) -> {System.out.println(s);}
=>
Test<String> t = s -> System.out.println(s)
-
如果主体只包含一个带返回值的表达式,则可以省略return关键字
s -> rerutn func(s)
=>
s -> func(s)
2、方法引用
双冒号语法
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
-
构造器引用:它的语法是
Class::new
,或者更一般的Class< T >::new
需要注意的是,构造方法必须是函数式接口的参数
-
静态方法引用:它的语法是
Class::static_method
-
特定类的任意对象的方法引用:它的语法是
Class::method
-
特定对象的方法引用:它的语法是
instance::method
public class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
public static void main(String[] args) {
Car car = Car.create(Car::new);
List< Car > cars = Arrays.asList(car);
cars.forEach(Car::collide);
//...
}
}
通过方法引用,可以将方法的引用赋值给一个变量,说明方法引用也是一种函数式接口的实现
3、默认方法
默认方法就是一个在接口里面有了一个有实现的方法。
1.8之前接口中的方法是不能有方法实现的,在1.8及以后打破了这个规则,一个接口,可以拥有默认方法,也可以拥有静态方法,并拥有他们的实现
类或者接口,去实现接口时,可以重写默认方法,这样做的原因说白了,其实也是为了Lambda表达式的使用
4、Stream API
新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
何谓 函数式编程风格 ?
即
指令性语言 => 描述性语言
从手动处理数据 => 给我需要的数据
很直白的对比就是,java语言本身就是指令性语言,每一步操作,你都需要告诉虚拟机,虚拟机在和机器去做 “交涉”!而SQL语言是典型的描述性语言,sql他不需要关注这些数据的来源,以及怎么一步步“细化”处理(分组,条件过滤),我们写的sql本身表达的意思就是,我要一个什么条件的数据,然后,执行这个sql,数据就会按照你的条件返回。
而Stream API 就是描述性语言的体现,下面看个例子~
情景:获取班级男生中,分数最高前十名的平均年龄!
普通实现:
1、找出男生,filter(man)
2、分数倒排,sort(desc)
3、取前十名,limit(1-10)
4、获取年龄,get(age)
5、计算平均值,average()
以上步骤,我们是不是得写代码一步步的去实现?这就是所谓的指令性语言
而有了 Stream API 之后,我们可以这样做
//伪代码如下
List<Student> list = new ArrayList<Student>();
double ave = Arrays.stream(list)
.filter()
.sorted()
.limit(10)
.mapToDouble()
.average().orElse(0);
是不是很像sql,我们需要是告诉一个“数据集合”我要什么样的数据,最后,他会把你想要的东西返回给你~
Stream和普通集合的区别?
- stream不存储元素,只是提供了操作集合的各种方法
- 适配函数式接口
- 延迟计算
- 链式调用,上面的各个链式调用方法,我们称为一个操作的话,在Steam中就有“中间操作”,和“终止操作”
我们找到源码,发现,前面的方法返回都还是一个Stream,也就是可以继续 点
下去,而average返回就已经不是Steam了
为什么不推荐用Stream<T>
操作原生数据类型?
- 产生装箱,拆箱,影响性能
Stream<T>
不提供数值类型的特殊方法
常用方法
- 遍历:forEach()、peek()、map(),三个方法都是遍历所有元素,中途无法优雅中止。(除非抛出异常,宕机等)
- 去重:distinct(),去重后,保持 顺序稳定
- 数据类型转换
Stream<Student> stream = Arrays.stream(stu); List<Student> list = stream.collect(Collectors.toList()); Set<Student> set = stream.collect(Collectors.toSet()); Map<String, Student> map = stream.collect(Collectors.toMap(t -> t.getName(), t -> t)); Student[] arr = stream.toArray(t -> new Student[t]); Student[] arr = stream.toArray(Student[]::new);
5、Date Time API
加强对日期与时间的处理。
Clock,LocalDate,LocalTime
6、Optional 类
Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
Optional是个容器,提供各种方法,获取内部是否有值,或执行各种快捷操作。
Optional.empty()返回一个空的Optional对象,对其执行的各种操作均为空操作,无任何效果
消除箭头代码的实例
Optional常用方法
7、并发集合,并行数组
8、JVM的改变
使用Metaspace
(JEP 122)代替持久代(PermGen space
)。
在JVM参数方面:
使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize
代替原来的-XX:PermSize和-XX:MaxPermSize
。