JAVA8新特性详解

一、接口的默认方法 

1、概念

  Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法。

  1. 接口提供一个默认实现的方法,并且不强制实现类重写此方法。

  2. 默认方法使用default关键字来修饰。

2、引入背景

  1. 当一个接口添加新方法时,需要所有的实现类都重写新方法,影响到了已有的实现类,可能导致应用崩溃。

  2. 默认方法可以不强制重写,也不会影响到已有的实现类。

  3. 例如Iterable接口的foreach方法,Java8的List接口新增了sort方法。都是默认方法。

举例:Java8中List接口新增了sort方法,其源码如下:

public interface List<E> extends Collection<E> {
    // ...其他成员
	default void sort(Comparator<? super E> c) {
      ...
    }
}

  

  可以看到,这个新增的sort方法有方法体,由default修饰符修饰,这就是接口的默认方法。

3、使用

  1. 当一个实现类实现了多个接口,多个接口里都有相同的默认方法时,实现类必须重写该默认方法,否则编译错误。

    a. 实现类自己重写逻辑。

    b. 实现类使用super关键字指定使用哪个接口的默认方法。

  2. 接口静态方法

    接口中支持定义静态方法,将默认方法的default关键字换成static即可。

  3. 继承类可以直接使用接口中的static方法,也可以创建对象后使用接口中的default方法。

二、Lambda 表达式

  Lambda 表达式,也可称为闭包,Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

  语法

    (parameters) -> expression(parameters) ->{ statements; }

  特性

    1、可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。   

    2、可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。  

      3、可选的大括号:如果主体包含了一个语句,就不需要使用大括号。   

        4、可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

  举例一:

// 1. 不需要参数,返回值为 5  
() -> 5  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

  

  举例二  老版本的Java中是如何排列字符串的:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

  需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。

  在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

  对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

  Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。

Collections.sort(names, (a, b) -> b.compareTo(a));

  

  举例三

  lambda表达式替换了原来匿名内部类的写法,没有了匿名内部类繁杂的代码实现,而是突出了,真正的处理代码。最好的示例就是 实现Runnable 的线程实现方式了: 用() -> {}代码块替代了整个匿名类

@Test
    public void test(){
        //old
        new Thread((new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类 实现线程");
            }
        })).start();
        //lambda
        new Thread( () -> System.out.println("java8 lambda实现线程")).start();
    }

  

  举例四 

  以下demo 使用的 Person 实体 以及 person list:

@Data
@ToString
@Accessors(chain = true)
public class Person {
    private String name;
    private int age;
    private String sex;
}
private List<Person> getPersonList(){
        Person p1 = new Person().setName("liu").setAge(22).setSex("male");
        Person p2 = new Person().setName("zhao").setAge(21).setSex("male");
        Person p3 = new Person().setName("li").setAge(18).setSex("female");
        Person p4 = new Person().setName("wang").setAge(21).setSex("female");
        List<Person> list = Lists.newArrayList();
        list.add(p1);
        list.add(p2);
        list.add(p3);
        list.add(p4);
        return list;
    }

  

   1、使用 forEach方法,直接通过一行代码即可完成对集合的遍历:

@Test
    public void test1(){
        List<Person> list = getPersonList();
        list.forEach(person -> System.out.println(person.toString()));
    }
结果
    Person(name=liu, age=22, sex=male)
    Person(name=zhao, age=21, sex=male)
    Person(name=li, age=18, sex=female)
    Person(name=wang, age=21, sex=female)

  2、双冒号::  表示方法引用,可以引用其他方法

@Test
    public void test2(){
        List<Person> list = getPersonList();
        Consumer<Person> changeAge = e -> e.setAge(e.getAge() + 3);
        list.forEach(changeAge);
        list.forEach(System.out::println);
    }
结果:
    Person(name=liu, age=25, sex=male)
    Person(name=zhao, age=24, sex=male)
    Person(name=li, age=21, sex=female)
    Person(name=wang, age=24, sex=female)

  3、filter 对集合进行过滤, filter 可以根据传入的 Predicate 对象,对集合进行过滤操作,Predicate 实质就是描述了过滤的条件:

 @Test
    public void test3(){
        List<Person> list = getPersonList();
        list.stream().filter(e -> e.getAge() > 20)
                     .forEach(e -> System.out.println(e.toString()));
    }
结果:
    Person(name=liu, age=22, sex=male)
    Person(name=zhao, age=21, sex=male)
    Person(name=wang, age=21, sex=female)

  当需要通过 多个过滤条件对集合进行过滤时,可以采取两种方式:

         a、可以通过调用多次filter 通过传入不同的 Predicate对象来进行过滤

         b、也可以通过 Predicate 对象的 and  or 方法,对多个Predicate 对象进行 且 或 操作

@Test
    public void test4(){
        List<Person> list = getPersonList();
        Predicate<Person> ageFilter = e -> e.getAge() > 20;
        Predicate<Person> sexFilter = e -> e.getSex().equals("male");
        //多条件过滤
        list.stream().filter(ageFilter)
                     .filter(sexFilter)
                     .forEach(e -> System.out.println(e.toString()));
        System.out.println("----------------------------");
        // Predicate : and or
        list.stream().filter(ageFilter.and(sexFilter))
                     .forEach(e -> System.out.println(e.toString()));
    }
结果:
    Person(name=liu, age=22, sex=male)
    Person(name=zhao, age=21, sex=male)
    ----------------------------
    Person(name=liu, age=22, sex=male)
    Person(name=zhao, age=21, sex=male)

  4、limit 限制结果集的数据量, limit 可以控制 结果集返回的数据条数:返回三条数据,返回年龄>20的前两条数据。

@Test
    public void  test5(){
        List<Person> list = getPersonList();
        list.stream().limit(3).forEach(e -> System.out.println(e.toString()));
 
        System.out.println("----------------------------");
        list.stream().limit(2).filter(e -> e.getAge() > 20)
                     .forEach(e -> System.out.println(e.toString()));
    }
结果:
    Person(name=liu, age=22, sex=male)
    Person(name=zhao, age=21, sex=male)
    Person(name=li, age=18, sex=female)
    ----------------------------
    Person(name=liu, age=22, sex=male)
    Person(name=zhao, age=21, sex=male)

  5、sorted 排序:通过sorted,可以按自定义的规则,对数据进行排序,可以用两种写法,分别按 年龄 和 姓名排序。

@Test
    public void test6(){
        List<Person> list = getPersonList();
        //年龄排序
        list.stream().sorted((p1,p2) -> (p1.getAge() - p2.getAge()))
                     .forEach(e -> System.out.println(e.toString()));
        //姓名排序
        System.out.println("----------------------------");
        list.stream().sorted(Comparator.comparing(Person::getName))
                     .forEach(e -> System.out.println(e.toString()));
    }
结果:
    Person(name=li, age=18, sex=female)
    Person(name=zhao, age=21, sex=male)
    Person(name=wang, age=21, sex=female)
    Person(name=liu, age=22, sex=male)
    ----------------------------
    Person(name=li, age=18, sex=female)
    Person(name=liu, age=22, sex=male)
    Person(name=wang, age=21, sex=female)
    Person(name=zhao, age=21, sex=male)

  6、max min 获取结果中 某个值最大最小的的对象。

@Test
    public void test7(){
        List<Person> list = getPersonList();
        // 如果 最大最小值 对应的对象有多个 只会返回第一个
        Person oldest = list.stream().max(Comparator.comparing(Person::getAge)).get();
        System.out.println(oldest.toString());
    }
结果:
    Person(name=liu, age=22, sex=male)

  7、map:对集合中的每个元素进行遍历,并且可以对其进行操作,转化为其他对象。如将集合中的每个人的年龄增加3岁。

@Test
    public void test8(){
        List<Person> list = getPersonList();
        //将 每人的年龄 +3
        System.out.println("修改前:");
        list.forEach(e -> System.out.println(e.toString()));
        System.out.println("修改后:");
        list.stream().map(e -> e.setAge(e.getAge() + 3 ))
                     .forEach(e -> System.out.println(e.toString()));
 
    }
结果:
 修改前:
    Person(name=liu, age=22, sex=male)
    Person(name=zhao, age=21, sex=male)
    Person(name=li, age=18, sex=female)
    Person(name=wang, age=21, sex=female)
 修改后:
    Person(name=liu, age=25, sex=male)
    Person(name=zhao, age=24, sex=male)
    Person(name=li, age=21, sex=female)
    Person(name=wang, age=24, sex=female)

  8、reduce:也是对所有值进行操作,但它是将所有值,按照传入的处理逻辑,将结果处理合并为一个。

    如:将集合中的所有整数相加,并返回其总和

@Test
    public void test9(){
        //第一个参数是上次函数执行的返回值(也称为中间结果),第二个参数是stream中的元素,
        // 这个函数把这两个值相加,得到的和会被赋值给下次执行这个函数的第一个参数。
        //要注意的是:第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素
 
        //将所有人的年龄加起来 求和
        List<Integer> ages = Arrays.asList(2,5,3,4,7);
        int totalAge = ages.stream().reduce((sum,age) -> sum + age).get();
        System.out.println(totalAge);
        //带 初始值的计算, 如果list没有元素 即stream为null 则直接返回初始值
        int totalAge1 = ages.stream().reduce(0,(sum,age) -> sum+age);
        List<Integer> initList = Lists.newArrayList();
        int initTotalAge = initList.stream().reduce(0,(sum,age) -> sum+age);
        System.out.println("totalAge1: "+ totalAge1 + " initTotalAge: " + initTotalAge);
    }
结果:
    21
    totalAge1: 21 initTotalAge: 0

  9、collect方法以集合中的元素为基础,生成新的对象。

   在实际中,我们经常会以集合中的元素为基础,取其中的数据,来生成新的结果集,例如 按照过滤条件,返回新的List,或者将集合转化为 Set 或Map等操作,通过collect方法实现是十分简便的:

@Test
    public void test10(){
        List<Person> list = getPersonList();
        //排序过滤等一系列操作之后的元素 放入新的list
        List<Person> filterList = list.stream().filter(e -> e.getAge() >20).collect(Collectors.toList());
        filterList.forEach(e -> System.out.println(e.toString()));
 
        //将 name 属性用" , ",连接拼接成一个字符串
        String nameStr = list.stream().map(Person::getName).collect(Collectors.joining(","));
        System.out.println(nameStr);
        //将name 放入到新的 set 集合中
        Set<String> nameSet = list.stream().map(person -> person.getName()).collect(Collectors.toSet());
        nameSet.forEach(e -> System.out.print(e + ","));
 
        System.out.println();
        System.out.println("map--------");
        Map<String,Person> personMap = list.stream().collect(Collectors.toMap(Person::getName,person -> person));
        personMap.forEach((key,val) -> System.out.println(key + ":" + val.toString()));
    }
结果:
    Person(name=liu, age=22, sex=male)
    Person(name=zhao, age=21, sex=male)
    Person(name=wang, age=21, sex=female)
    liu,zhao,li,wang
    wang,zhao,liu,li,
    map--------
    wang:Person(name=wang, age=21, sex=female)
    zhao:Person(name=zhao, age=21, sex=male)
    liu:Person(name=liu, age=22, sex=male)
    li:Person(name=li, age=18, sex=female)

  10、summaryStatistics 计算集合元素的最大、最小、平均等值。

    IntStream、LongStream 和 DoubleStream 等流的类中,有个非常有用的方法叫做 summaryStatistics(),可以返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistics,描述流中元素的各种摘要数据。

@Test
    public void test11(){
        List<Integer> ages = Arrays.asList(2,5,3,4,7);
        IntSummaryStatistics statistics = ages.stream().mapToInt(e -> e).summaryStatistics();
        System.out.println("最大值: " + statistics.getMax());
        System.out.println("最小值: " + statistics.getMin());
        System.out.println("平均值: " + statistics.getAverage());
        System.out.println("总和: " + statistics.getSum());
        System.out.println("个数: " + statistics.getCount());
    }
结果:
    最大值: 7
    最小值: 2
    平均值: 4.2
    总和: 21
    个数: 5

  

三、函数式接口

  1、什么是函数式接口:有且仅有一个抽象方法的接口

(注:一般会出现一个名词叫做“语法糖”,即使用更加方便而原理不变的代码语法,如Lambda可以认为是匿名内部类的语法糖)

  2、定义

    其实就是在一个接口中有一个抽象方法的接口,称为函数式接口,当然接口可以包含其他的方法(默认、静态、私有)

  示例代码:

@FunctionalInterface
// 定义一个接口,只包含一个抽象方法
public interface MyfuInter {
    public abstract void method();
}

  在这儿注意到有一个注解@FunctionalInterface:可以检测接口是否为一个函数接口。也就是说编译时候确保只有一个抽象方法接口。与我们@Override一样,不加时候人工保证重写一致,加了编译时候免除人工检查。

  3、函数式接口的使用

public class Demo {
    // 定义一个方法,参数部分使用的函数式接口
    public static void show(MyfuInter myfuInter) {
        myfuInter.method();
    }
    public static void main(String[] args) {
        //方式1,调用show方法,方法的参数是一个接口,可以传递接口的实现对象MyfuInterImpl()
        show(new MyfuInterImpl());
        //方式2,调用show方法,方法的参数是一个接口,可以传递接口的匿名内部类
        show(new MyfuInter() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类方法重写接口中的抽象方法");
            }
        });
        // 方式3,调用show方法,方法的参数是一个接口,可以使用Lambda表达式
        show(() -> System.out.println("使用lamba表达式"));
    }
}

  

四、方法与构造函数引用

  1、方法引用

    如果 Lambda 体中的内容有方法已经实现了,可以使用 "方法引用"。(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。可以将方法引用理解为 Lambda 表达式的另外一种表现形式。

  注意事项:

    a、方法引用中的参数和返回值类型,要与接口中的抽象方法的参数和返回值类型保持一致。     

    b、如果 Lambda 参数列表中第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用 ClassName::method。

如下三种主要使用情况:

  • 对象引用::实例方法
  • 类::静态方法
  • 类::实例方法
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class AcceptMethod {
    public static void printValur(String str) {
        System.out.println("print value : " + str);
    }
    public static void main(String[] args) {
        //方法1、普通for循环
        List<String> al = Arrays.asList("a", "b", "c", "d");
        for (String a : al) {
            AcceptMethod.printValur(a);
        }
        //方法2、forEach循环
        al.forEach(x -> AcceptMethod.printValur(x));
        //方法3、使用JDK双冒号表示
        al.forEach(AcceptMethod::printValur);
        //方法4、下面的方法和上面等价的
        Consumer<String> methodParam = AcceptMethod::printValur; //方法参数
        al.forEach(e -> methodParam.accept(e));//方法执行accept
        //结果都是循环打印输出集合里面的参数,打印四遍。
//        print value : a
//        print value : b
//        print value : c
//        print value : d
    }
}

  在JDK8中,接口Iterable 8中默认实现了forEach方法,调用了 JDK8中增加的接口Consumer内的accept方法,执行传入的方法参数。JDK源码如下:

default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

  2、构造器引用

       格式: ClassName::new

   需要注意,需要调用的构造器参数列表要和函数式接口中抽象方法的参数列表保持一致。

@Test
public void test() {
	Supplier<Employee> sup = ()->new Employee();
	System.out.println(sup.get());
       //调用的是无参构造器 :因为Supplier方法的参数名为空
	Supplier<Employee> sup1 = Employee::new;
	System.out.println(sup1.get());
 
	Function<String,Employee> fun = (name)-> new Employee(name);
	System.out.println(fun.apply("张三"));
        //调用的是一个参数构造器:因为Function方法的参数名是一个参数
	Function<String, Employee> fun1 = Employee::new;
	System.out.println(fun1.apply("李四"));
}    

  3、数组引用

      格式:类型[] :: new

@Test
public void test() {
	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);
}

  

  

参考文献:https://blog.csdn.net/qq_37176126/article/details/81273195

       https://www.jianshu.com/p/0bf8fe0f153b

原文地址:https://www.cnblogs.com/gshao/p/13403735.html