Java 8可谓是自Java 5以来最具革命性的版本了,她在语言、编译器、类库、开发工具以及Java虚拟机等方面都带来了不少新特性。
我们来一一回顾一下这些特性。
一、Lambda表达式
Lambda表达式可以说是Java 8最大的卖点,她将函数式编程引入了Java。Lambda允许把函数作为一个方法的参数,或者把代码看成数据。Lambda 是一个匿名函数。
一个Lambda表达式可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:
例子1
需求: 比较TreeSet
中数据,按小到大输出
使用匿名内部类实现一个排序功能
|
我们不难发现上面的代码存在一个问题:其实关键代码只有第七行,其他代码都是冗余的
使用Lambda表达式实现同样功能
|
代码瞬间就变得很简短,你可能觉得这个有什么,没什么感觉。那么我们在进入第二个例子
例子2
需求:
1.获取公司中年龄小于 35 的员工信息
2.获取公司中工资大于 5000 的员工信息
。。。。。。
前期准备
实现一个Employee类,有四个属性
|
初始化一个List:
|
常规方法实现
实现两个方法,然后传入需要过滤的源数据,返回过滤后的结果集
我们不难发现上面的代码存在一个问题:那就是两个方法除了比较部分不同,其他逻辑是一样的,存在大量冗余,假设有新的需求(例如求得求得名字姓王的员工)那么就需要再创建一个 filterEmployee**方法对应新的需求。
使用策略设计模式实现
提供父借口 和 两个 实现类(两个需求对应的逻辑实现类)
|
测试代码
|
使用策略模式比上一个的好处是:代码很清晰,便于维护,新的需求我们只需要再实现对应的需求实现类即可,然后传入
我们不难发现上面的代码存在一个问题:跟例子1一样,存在大量的冗余。
Lambda 表达式实现
前期准备
|
|
我们不难发现上面的代码存在一个问题:这个代码,是不是已经非常简短了,感觉已经是终极的最简代码。但是实际上还有更简短的代码(使用stream api)
缺点:太过依赖 MyPredicate
终极实现方式:Stream API
|
输出
|
我们不难发现上面的代码存在一个问题:这个代码,是非常潇洒,舒服的,不依赖我们上面所说的接口。
函数式接口
为了使现有函数更好的支持Lambda表达式,Java 8引入了函数式接口的概念。函数式接口就是只有一个方法的普通接口。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的例子。为此,Java 8增加了一种特殊的注解@FunctionalInterface:
–也就是说:这个接口里面只能够存在一个接口方法,多个就会报错
例子:
认识Lambda表达式
概念
一、Lambda 表达式的基础语法:Java8中引入了一个新的操作符 “->” 该操作符称为箭头操作符或 Lambda 操作符
箭头操作符将 Lambda 表达式拆分成两部分:
左侧:Lambda 表达式的参数列表
右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体
上面的例子:List
list = filterEmployee(emps, (e) -> e.getAge() <= 35); 第二个参数他会去找
MyPredicate<T>
接口里面的 public boolean test(T t);
test方法,lambda表达式左边的(e) 对应的是test方法的入参, ambda表达式右边的e.getAge() <= 35 对应得是test方法的实现
那么你可能会有疑问,假设MyPredicate接口里面有很多个接口方法,那么他会去调用那个呢?他怎么知道去找test方法呢? 引入了:@FunctionalInterface这个函数式接口的概念,解决了这个问题。
|
|
|
|
|
|
类型推断 : jdk1.8后,添加了这个功能
String[] strs = {“aaa”, “bbb”, “ccc”} ; 它自动会转换里面的数据为String类型的数据
改为:String[] strs;strs = {"aaa", "bbb", "ccc"};//会报错--因为这样无法进行类型推断
类型推断例子2
|
热身例子一
|
热身例子二
|
看到这里可能会有疑惑?我靠,使用lambda表达式还得声明一个函数接口,这么麻烦。实际上,java内部已经帮我们实现了很多个接口供我们使用,不需要重新自己定义,除非有特别操作。
java8内置四大函数式接口
为了解决接口需要自定义问题
|
例子
消费型接口
|
Supplier 供给型接口
|
Function 函数型接口
|
Predicate 断言型接口
|
四大内置函数衍生的子函数
二、接口的默认方法与静态方法
我们可以在接口中定义默认方法,使用default关键字,并提供默认的实现。所有实现这个接口的类都会接受默认方法的实现,除非子类提供的自己的实现。例如:
public interface DefaultFunctionInterface {default String defaultFunction() {return "default function";}}
我们还可以在接口中定义静态方法,使用static关键字,也可以提供实现。例如:
接口的默认方法和静态方法的引入,其实可以认为引入了C++中抽象类的理念,以后我们再也不用在每个实现类中都写重复的代码了。
三、方法引用
通常与Lambda表达式联合使用,可以直接引用已有Java类或对象的方法。一般有四种不同的方法引用:
构造器引用
- 构造器引用。语法是Class::new,构造器的参数列表,需要与函数式接口中参数列表保持一致!也就是说,决定Class::new调用那一个构造器得是:接口函数的方法的参数
|
对象静态方法引用(类名::静态方法)
- 静态方法引用。语法是Class::static_method,要求接受一个Class类型的参数;
|
对象实例方法引用(对象引用::实例方法名)
- 特定类的任意对象方法引用。它的语法是Class::method。要求方法是没有参数的;
|
类名实例方法引用(类名::实例方法名)
- 我们知道一般是有对象才能够引用实例方法,但是有种特殊情况是可以直接使用类名引用实例方法
若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName
|
注意:
①方法体所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式:ClassName::MethodName (针对于第四种方法引用)
数组引用(类型[] :: new)
|
四、重复注解
在Java 5中使用注解有一个限制,即相同的注解在同一位置只能声明一次。Java 8引入重复注解,这样相同的注解在同一地方也可以声明多次。重复注解机制本身需要用@Repeatable注解。Java 8在编译器层做了优化,相同注解会以集合的方式保存,因此底层的原理并没有变化。
五、扩展注解的支持
Java 8扩展了注解的上下文,几乎可以为任何东西添加注解,包括局部变量、泛型类、父类与接口的实现,连方法的异常也能添加注解。
六、Optional
Java 8引入Optional类来防止空指针异常,Optional类最先是由Google的Guava项目引入的。Optional类实际上是个容器:它可以保存类型T的值,或者保存null。使用Optional类我们就不用显式进行空指针检查了。
七、Stream
前言
Stream API是把真正的函数式编程风格引入到Java中。其实简单来说可以把Stream理解为MapReduce,当然Google的MapReduce的灵感也是来自函数式编程。她其实是一连串支持连续、并行聚集操作的元素。从语法上看,也很像linux的管道、或者链式编程,代码写起来简洁明了,非常酷帅!
八、Date/Time API (JSR 310)
Java 8新的Date-Time API (JSR 310)受Joda-Time的影响,提供了新的java.time包,可以用来替代 java.util.Date和java.util.Calendar。一般会用到Clock、LocaleDate、LocalTime、LocaleDateTime、ZonedDateTime、Duration这些类,对于时间日期的改进还是非常不错的。
九、JavaScript引擎Nashorn
Nashorn允许在JVM上开发运行JavaScript应用,允许Java与JavaScript相互调用。
十、Base64
在Java 8中,Base64编码成为了Java类库的标准。Base64类同时还提供了对URL、MIME友好的编码器与解码器。
除了这十大新特性之外,还有另外的一些新特性:
更好的类型推测机制:Java 8在类型推测方面有了很大的提高,这就使代码更整洁,不需要太多的强制类型转换了。
编译器优化:Java 8将方法的参数名加入了字节码中,这样在运行时通过反射就能获取到参数名,只需要在编译时使用-parameters参数。
并行(parallel)数组:支持对数组进行并行处理,主要是parallelSort()方法,它可以在多核机器上极大提高数组排序的速度。
并发(Concurrency):在新增Stream机制与Lambda的基础之上,加入了一些新方法来支持聚集操作。
Nashorn引擎jjs:基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。
类依赖分析器jdeps:可以显示Java类的包级别或类级别的依赖。
JVM的PermGen空间被移除:取代它的是Metaspace(JEP 122),元空间直接采用的是物理空间,也即是我们电脑的内存,电脑内存多大,元空间就有多大。