java8新特性

Java 8可谓是自Java 5以来最具革命性的版本了,她在语言、编译器、类库、开发工具以及Java虚拟机等方面都带来了不少新特性。
我们来一一回顾一下这些特性。

java8新特性

一、Lambda表达式

Lambda表达式可以说是Java 8最大的卖点,她将函数式编程引入了Java。Lambda允许把函数作为一个方法的参数,或者把代码看成数据。Lambda 是一个匿名函数。

一个Lambda表达式可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:

例子1

需求: 比较TreeSet中数据,按小到大输出

使用匿名内部类实现一个排序功能

//采用匿名内部类的方式-实现比较器
Comparator<Integer> comparator = new Comparator<Integer>()
{
@Override
public int compare(Integer o1, Integer o2)
{
return Integer.compare(o1, o2);//关键代码
}
};
//传入比较器
TreeSet<Integer> tree2 = new TreeSet<>(comparator );
tree2.add(12);
tree2.add(-12);
tree2.add(100);
System.out.println(tree2)
//输出
-12
12
100

我们不难发现上面的代码存在一个问题:其实关键代码只有第七行,其他代码都是冗余的

使用Lambda表达式实现同样功能

//使用Lambda表达式,抽取关键代码,减少代码量
Comparator<Integer> comparator2 = (x, y) -> Integer.compare(x, y); //关键代码
TreeSet<Integer> tree = new TreeSet<>(comparator2 );
tree.add(12);
tree.add(-12);
tree.add(100);
tree.forEach(System.out::println);//代替System.out.println

代码瞬间就变得很简短,你可能觉得这个有什么,没什么感觉。那么我们在进入第二个例子

例子2

需求:
1.获取公司中年龄小于 35 的员工信息
2.获取公司中工资大于 5000 的员工信息
。。。。。。

前期准备

实现一个Employee类,有四个属性

private int id;
private String name;
private int age;
private double salary;
忽略get/set方法和构造器

初始化一个List:

List<Employee> emps = Arrays.asList(
new Employee(101, "张三", 18, 9999.99),
new Employee(102, "李四", 59, 6666.66),
new Employee(103, "王五", 28, 3333.33),
new Employee(104, "赵六", 8, 7777.77),
new Employee(105, "田七", 38, 5555.55)
);

常规方法实现

实现两个方法,然后传入需要过滤的源数据,返回过滤后的结果集

//需求:获取公司中年龄小于 35 的员工信息
public List<Employee> filterEmployeeAge(List<Employee> emps){
List<Employee> list = new ArrayList<>();
for (Employee emp : emps) {
if(emp.getAge() <= 35){//比较代码
list.add(emp);
}
}
return list;
}
//需求:获取公司中工资大于 5000 的员工信息
public List<Employee> filterEmployeeSalary(List<Employee> emps){
List<Employee> list = new ArrayList<>();
for (Employee emp : emps) {
if(emp.getSalary() >= 5000){//比较代码
list.add(emp);
}
}
return list;
}

我们不难发现上面的代码存在一个问题:那就是两个方法除了比较部分不同,其他逻辑是一样的,存在大量冗余,假设有新的需求(例如求得求得名字姓王的员工)那么就需要再创建一个 filterEmployee**方法对应新的需求。

使用策略设计模式实现

提供父借口 和 两个 实现类(两个需求对应的逻辑实现类)

// 父接口
@FunctionalInterface
public interface MyPredicate<T> {
public boolean test(T t);
}
//需求1 实现类-年龄小于35
public class FilterEmployeeForAge implements MyPredicate<Employee>{
@Override
public boolean test(Employee t) {
return t.getAge() <= 35;
}
}
//需求1 实现类-工资大于5000
public class FilterEmployeeForSalary implements MyPredicate<Employee> {
@Override
public boolean test(Employee t) {
return t.getSalary() >= 5000;
}
}

测试代码

// 通用过滤方法
public List<Employee> filterEmployee(List<Employee> emps, MyPredicate<Employee> mp){
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if(mp.test(employee)){
list.add(employee);
}
}
return list;
}
@Test
public void test4(){
//传入实现年龄过滤的实现类
List<Employee> list =
filterEmployee(emps, new FilterEmployeeForAge());
for (Employee employee : list) {
System.out.println(employee);
}
System.out.println("------------------------------------------");
List<Employee> list2 = filterEmployee(emps, new FilterEmployeeForSalary());
for (Employee employee : list2) {
System.out.println(employee);
}
}

使用策略模式比上一个的好处是:代码很清晰,便于维护,新的需求我们只需要再实现对应的需求实现类即可,然后传入

MyPredicate```接口即可。
缺点是:需要实现对应的需求类然后实现``` MyPredicate<T>```接口
### **匿名内部类**
这种方法类似于例子1中的 Comparator这个接口的实现
```JAVA
//直接使用 MyPredicate<Employee>接口,不去实现对应的需求类(上面的FilterEmployeeForSalary 和 FilterEmployeeForAge )
@Test
public void test5(){
List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
@Override
public boolean test(Employee t) {
return t.getId() <= 103;
}
});
for (Employee employee : list) {
System.out.println(employee);
}
}

我们不难发现上面的代码存在一个问题:跟例子1一样,存在大量的冗余。

Lambda 表达式实现

前期准备

public List<Employee> filterEmployee(List<Employee> emps, MyPredicate<Employee> mp){
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if(mp.test(employee)){
list.add(employee);
}
}
return list;
}
@Test
public void test6(){
List<Employee> list = filterEmployee(emps, (e) -> e.getAge() <= 35);
list.forEach(System.out::println);
System.out.println("------------------------------------------");
List<Employee> list2 = filterEmployee(emps, (e) -> e.getSalary() >= 5000);
list2.forEach(System.out::println);
}

我们不难发现上面的代码存在一个问题:这个代码,是不是已经非常简短了,感觉已经是终极的最简代码。但是实际上还有更简短的代码(使用stream api)
缺点:太过依赖 MyPredicate 这个接口,假设这个接口不存在,该怎么办呢?(我们这里仅仅是做个假设)

终极实现方式:Stream API

@Test
public void test7(){
emps.stream()
.filter((e) -> e.getAge() <= 35)
.forEach(System.out::println);
System.out.println("----------------------------------------------");
emps.stream()
.filter((e) -> e.getSalary() >= 5000)
.forEach(System.out::println);
System.out.println("----------------------------------------------");
// 可以使用map 指定输出那个属性的值,代替普通的便利输出
emps.stream()
.map(Employee::getName)
.limit(3)// 输出前三个
.sorted()//排序
.forEach(System.out::println);
}

输出

Employee [id=101, name=张三, age=18, salary=9999.99]
Employee [id=103, name=王五, age=28, salary=3333.33]
Employee [id=104, name=赵六, age=8, salary=7777.77]
----------------------------------------------
Employee [id=101, name=张三, age=18, salary=9999.99]
Employee [id=102, name=李四, age=59, salary=6666.66]
Employee [id=104, name=赵六, age=8, salary=7777.77]
Employee [id=105, name=田七, age=38, salary=5555.55]
----------------------------------------------
张三
李四
王五

我们不难发现上面的代码存在一个问题:这个代码,是非常潇洒,舒服的,不依赖我们上面所说的接口。


函数式接口

为了使现有函数更好的支持Lambda表达式,Java 8引入了函数式接口的概念。函数式接口就是只有一个方法的普通接口。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的例子。为此,Java 8增加了一种特殊的注解@FunctionalInterface:

–也就是说:这个接口里面只能够存在一个接口方法,多个就会报错

例子:

@FunctionalInterface
public interface Functional {
void method();
}

认识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这个函数式接口的概念,解决了这个问题。

* 语法格式一:无参数,无返回值
* () -> System.out.println("Hello Lambda!");
> 例如 Runnable接口的 run方法就是无参数无返回值:
@Test
public void test1(){
int num = 0;//jdk 1.7 前,我们知道匿名内部引用局部变量必须声明为final
//但jdk1.8,它默认给我们添加了final,不用显示声明。
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!" + num);
//这里如果改为 num++是会报错的,因为他本质上是一个final
}
};
r.run();
System.out.println("-------------------------------");
Runnable r1 = () -> System.out.println("Hello Lambda!");
r1.run();
}
这两个是等效的

*
* 语法格式二:有一个参数,并且无返回值
* (x) -> System.out.println(x)
*
例子:
Consumer这个类jdk自带--有参数无返回值
@Test
public void test2(){
Consumer<String> con = x -> System.out.println(x);
con.accept("我是你泽精哥!");
}
* 语法格式三:若只有一个参数,小括号可以省略不写
* x -> System.out.println(x)
*
* 语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
* Comparator<Integer> com = (x, y) -> {
* System.out.println("函数式接口");
* return Integer.compare(x, y);
* };
* 语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
* Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
*
* 语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
* (Integer x, Integer y) -> Integer.compare(x, y);

类型推断 : jdk1.8后,添加了这个功能
String[] strs = {“aaa”, “bbb”, “ccc”} ; 它自动会转换里面的数据为String类型的数据

改为:
String[] strs;
strs = {"aaa", "bbb", "ccc"};
//会报错--因为这样无法进行类型推断


类型推断例子2

public void show(Map<String, Integer> map){}//方法
show(new HashMap<>());//调用方法
我们发现在调用方法的时候入参我们并没有明确声明类型,但是在jdk1.8中是可以编译通过的。这里也是运用了类型推断(注意:jdk1.7中编译会失败)

热身例子一


//函数是接口
@FunctionalInterface
public interface MyFun {
public Integer getValue(Integer num);
}
//测试
//需求:对一个数进行运算
@Test
public void test6(){
Integer num = operation(100, (x) -> x * x);
System.out.println(num);
System.out.println(operation(200, (y) -> y + 200));
}
public Integer operation(Integer num, MyFun mf){
return mf.getValue(num);
}

热身例子二

//函数接口
@FunctionalInterface //约束当前接口只能有一个方法
public interface CalcLong<K,T>
{
// public K getMultiply(T t, T tt);
K getMultiply(T t, T tt);
}
//需求:求得两个数的和
String result = getMuyl(10L,10L,(e,ee)->{
System.out.println(e+ " " + ee);
return e+ee+"";
});
System.out.println(result);
public String getMuyl(Long l,Long ll,CalcLong<String,Long> mf){
return mf.getMultiply(l, ll);
}

看到这里可能会有疑惑?我靠,使用lambda表达式还得声明一个函数接口,这么麻烦。实际上,java内部已经帮我们实现了很多个接口供我们使用,不需要重新自己定义,除非有特别操作。


java8内置四大函数式接口

为了解决接口需要自定义问题

内置四大函数接口.png

/*
* Java8 内置的四大核心函数式接口
*
* Consumer<T> : 消费型接口
* void accept(T t);
*
* Supplier<T> : 供给型接口
* T get();
*
* Function<T, R> : 函数型接口
* R apply(T t);
*
* Predicate<T> : 断言型接口
* boolean test(T t);
*
*/

例子

消费型接口
//Consumer<T> 消费型接口 :
@Test
public void test1(){
String p;
happy(10000, (m) -> System.out.println("桑拿,每次消费:" + m + "元"));
}
public void happy(double money, Consumer<Double> con){
con.accept(money);
}

Supplier 供给型接口

//Supplier<T> 供给型接口 :
@Test
public void test2(){
List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
for (Integer num : numList) {
System.out.println(num);
}
}
//需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}

Function 函数型接口

//Function<T, R> 函数型接口:
@Test
public void test3(){
String newStr = strHandler("\t\t\t 去除前后空格 ", (str) -> str.trim());
System.out.println(newStr);
String subStr = strHandler("截取字符串你知不知道", (str) -> str.substring(2, 5));
System.out.println(subStr);
}
//需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun){
return fun.apply(str);
}

Predicate 断言型接口

//Predicate<T> 断言型接口:
@Test
public void test4(){
List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
List<String> strList = filterStr(list, (s) -> s.length() > 3);
for (String str : strList) {
System.out.println(str);
}
}
//需求:将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strList = new ArrayList<>();
for (String str : list) {
if(pre.test(str)){
strList.add(str);
}
}
return strList;
}

四大内置函数衍生的子函数

四大内置函数接口的子接口.png


二、接口的默认方法与静态方法

我们可以在接口中定义默认方法,使用default关键字,并提供默认的实现。所有实现这个接口的类都会接受默认方法的实现,除非子类提供的自己的实现。例如:

public interface DefaultFunctionInterface {
default String defaultFunction() {
return "default function";
}
}


我们还可以在接口中定义静态方法,使用static关键字,也可以提供实现。例如:

public interface StaticFunctionInterface {
static String staticFunction() {
return "static function";
}
}

接口的默认方法和静态方法的引入,其实可以认为引入了C++中抽象类的理念,以后我们再也不用在每个实现类中都写重复的代码了。


三、方法引用

通常与Lambda表达式联合使用,可以直接引用已有Java类或对象的方法。一般有四种不同的方法引用:

构造器引用


  • 构造器引用。语法是Class::new,构造器的参数列表,需要与函数式接口中参数列表保持一致!也就是说,决定Class::new调用那一个构造器得是:接口函数的方法的参数
//构造器引用
@Test
public void test7(){
// Supplier 的接口方法 T get(); --所以调用无参构造器
Supplier<Employee> fun0 = Employee::new;
//Function 的接口方法 R apply(T t);-调用一个参数构造器
Function<String, Employee> fun = Employee::new;
//BiFunction 的接口方法 R apply(T t, U u); -调用二参构造器
BiFunction<String, Integer, Employee> fun2 = Employee::new;
}

对象静态方法引用(类名::静态方法)


  • 静态方法引用。语法是Class::static_method,要求接受一个Class类型的参数;
//类名 :: 静态方法名
//max和compare 都是静态方法
@Test
public void test4(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
System.out.println("-------------------------------------");
Comparator<Integer> com2 = Integer::compare;
BiFunction<Double, Double, Double> fun = (x, y) -> Math.max(x, y);
System.out.println(fun.apply(1.5, 22.2));
System.out.println("------------------------------------");
BiFunction<Double, Double, Double> fun2 = Math::max;
System.out.println(fun2.apply(1.2, 1.5));
}

对象实例方法引用(对象引用::实例方法名)


  • 特定类的任意对象方法引用。它的语法是Class::method。要求方法是没有参数的;

//对象的引用 :: 实例方法名
@Test
public void test2(){
Employee emp = new Employee(101, "张三", 18, 9999.99);
Supplier<String> sup = () -> emp.getName();
System.out.println(sup.get());
System.out.println("----------------------------------");
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}

类名实例方法引用(类名::实例方法名)


  • 我们知道一般是有对象才能够引用实例方法,但是有种特殊情况是可以直接使用类名引用实例方法
    若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName

//类名 :: 实例方法名
//按照常规是String st = new String("123"); st::equals,
//对象调用实例方法,但是下面因为符合第四种引用的规则,
//所以可以使用类名调用实例方法
@Test
public void test5(){
//第一个参数为实例方法调用者,第二个参数为为实例方法参数
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("abcde", "abcde"));
System.out.println("-------------------------------------");
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abc", "abc"));
System.out.println("---------------------------------------");
//第一个参数为实例方法调用者,第二个参数为空
Function<Employee, String> fun = (e) -> e.show();
System.out.println(fun.apply(new Employee()));
System.out.println("--------------------------------------");
Function<Employee, String> fun2 = Employee::show;
System.out.println(fun2.apply(new Employee()));
}

注意:

①方法体所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!

②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式:ClassName::MethodName (针对于第四种方法引用)


数组引用(类型[] :: new)

//数组引用
@Test
public void test8(){
Function<Integer, String[]> fun = (args) -> new String[args];
String[] strs = fun.apply(10);
System.out.println(strs.length);
System.out.println("--------------------------");
Function<Integer, Employee[]> fun2 = Employee[] :: new;
Employee[] emps = fun2.apply(20);
System.out.println(emps.length);
}

四、重复注解

在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的管道、或者链式编程,代码写起来简洁明了,非常酷帅!


stream前言.png


stream概念一.png


stream概念二.png


八、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),元空间直接采用的是物理空间,也即是我们电脑的内存,电脑内存多大,元空间就有多大。

如果你感觉文章对你又些许感悟,你可以支持我!!
-------------本文结束感谢您的阅读-------------