java8新特性学习笔记

182 阅读10分钟

一、lambda表达式

例如:

Comparator<Integer> comparator=(x,y)->Integer.compare(x,y);

基础语法

  • -> 箭头运算符,将参数列表和方法体隔离开
  • 左侧: 参数列表
  • 右侧: 方法体、lambda体

口诀:

  • 写死小括号,拷贝右箭头,落地大括号
  • 左右遇一括号省
  • 左侧推断类型省

解释:

  1. 无论左边还是右边,如果参数列表、或者方法体中执行语句只有一个或者一条,可以省略掉小括号和大括号
  2. 左侧参数列表可以不写参数类型,因为JVM可以根据“类型推断”自动确认

语法格式

主要有以下语法格式

  1. 无参数无返回值
Runnable r1=new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
};

Runnable r2 = () -> {
    System.out.println("hello");
};

如上:因为run方法没有参数,所以左侧括号为空,箭头右侧是run方法的实现体

  1. 一个参数,无返回值
Consumer<String> con=(str)->{
    System.out.println(str);
};
con.accept("哈哈哈,我真是太帅了");
  1. 一个参数,无返回值 (小括号可以省略不写)
Consumer<String> con=str->System.out.println(str);
con.accept("哈哈哈,我真是太帅了");
  1. 两个及以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> comparator2 = (x, y) -> {
    System.out.println("hello");
    return Integer.compare(x, y);
};
  1. 两个及以上的参数,有返回值,并且 Lambda 体中只有1条语句 (大括号 与 return 都可以省略不写)
Comparator<Integer> comparator1 = (x, y) -> Integer.compare(x, y);

二、函数式接口

接口中只有一个抽象方法的接口 @FunctionalIterface,默认方法不算,必须只有一个抽象接口。个人觉得可能和类型推断等有关,否则无法确认Lamdba表达式实现的式哪一个方法

自定义函数式接口

//自定义一个接口,并标注FunctionalInterface注解,接口内有一个getVlaue抽象方法
@FunctionalInterface
public interface MyFun {
    Integer getValue(Integer num);
}


//新建测试类,并调用方法
@Test
public void test() {

    Integer num = operateMyFun(10, x -> x * x);
    System.out.println(num);

    MyFun myFun=x-> x+x;
    System.out.println(myFun.getValue(10));

}

private Integer operateMyFun(Integer num, MyFun myFun) {
    return myFun.getValue(num);
}

四大内置函数式接口

函数式接口参数类型返回类型用途
Consumer消费型接口Tvoid对类型为T的对象应用操作:void accept(T t)
Supplier提供型接口T返回类型为T的对象:T get()
Function<T, R>函数型接口TR对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t)
Predicate断言型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t)

消费型接口

Consumer<String> con=(str)->{
    System.out.println(str);
};
con.accept("哈哈哈,我真是太帅了");

提供型接口

Supplier<Integer> supplier = () -> (int) (Math.random() * 100);
System.out.println(supplier.get());

函数型接口

Function<String,String> function=str->str.toUpperCase(Locale.ROOT);
System.out.println(function.apply("wo zhen shi tai hao kan le"));

断言型接口

Predicate<String> predicate = str -> {
    System.out.println("开始进行判断......");
    return "666".equals(str);
};
System.out.println(predicate.test("666"));

其他接口

20200518225311406.png

三、引用

若 Lambda 表达式体中的内容已有方法实现,则我们可以使用“方法引用”

3.1 方法引用

对象::实例方法

Lambda 表达实体中调用方法的参数列表、返回类型必须和函数式接口中抽象方法保持一致
Consumer<String> con3 = s -> System.out.println(s);
Consumer<String> con4 = System.out::println;
con3.accept("我太帅了");
con4.accept("我太帅了");

//解释:调用的Println方法返回类型和Consumer中的accept方法一致为void,并且参数列表也一致

类::静态方法

和上述要求一致
//示例
Comparator<Integer> comparator1 = (x, y) -> Integer.compare(x, y);
Comparator<Integer> comparator2 = (x, y) -> {
    System.out.println("hello");
    return Integer.compare(x, y);
};
Comparator<Integer> comparator3=Integer::compare;

类::实例方法

Lambda 参数列表中的第一个参数是方法的调用者,第二个参数是方法的参数时,才能使用 ClassName :: Method
//示例
BiPredicate<String,String> b1=(x,y)->x.equals(y);
BiPredicate<String,String> b2=String::equals;

3.2 构造器引用

需要调用的构造器的参数列表要与函数时接口中抽象方法的参数列表保持一致
//示例
Supplier<ArrayList> sup1 = ArrayList::new;
Supplier<ArrayList> sup2 = () -> new ArrayList();

3.3 数组引用

算了.......

四、Stream API

4.1 定义

20200518225358689.png

20200518225430446.png

4.2 创建流

//1、集合流
Stream<String> listStream = new ArrayList<String>().stream();
//2、数组流
IntStream arrayStream = Arrays.stream(new int[]{6, 6, 6});
//静态方法
Stream<Integer> integerStream = Stream.of(6, 6, 6);
//3、无限流
//迭代
Stream<Integer> iterate = Stream.iterate(0, (i) -> i + 2);
iterate.limit(10).forEach(System.out::println);
//生成
Stream<Integer> generate = Stream.generate(() -> (int) (Math.random() * 100));
generate.limit(10).forEach(System.out::println);

筛选/切片

中间操作:

  • filter:接收 Lambda ,从流中排除某些元素
  • limit:截断流,使其元素不超过给定数量
  • skip(n):跳过元素,返回一个舍弃了前n个元素的流;若流中元素不足n个,则返回一个空流;与 limit(n) 互补
  • distinct:筛选,通过流所生成的 hashCode() 与 equals() 取除重复元素

List<Student> list = Arrays.asList(new Student("张三"),
        new Student("李四"),
        new Student("王二"),
        new Student("王二"),
        new Student("麻子"));

@Test
public void test1() {
    list.stream().filter(s -> !"麻子".equals(s.getName())).forEach(System.out::println);
    System.out.println("==============================");
    list.stream().skip(2).forEach(System.out::println);
    System.out.println("==============================");
    list.stream().limit(2).forEach(System.out::println);
    System.out.println("==============================");
    list.stream().distinct().forEach(System.out::println);
}


//输出结果
Student{name='张三'}
Student{name='李四'}
Student{name='王二'}
Student{name='王二'}
==============================
Student{name='王二'}
Student{name='王二'}
Student{name='麻子'}
==============================
Student{name='张三'}
Student{name='李四'}
==============================
Student{name='张三'}
Student{name='李四'}
Student{name='王二'}
Student{name='麻子'}

注:distinct去重时,记得重写Student类的equals和hashcode方法

映射

  • map:接收 Lambda ,将元素转换为其他形式或提取信息;接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
  • flatMap:接收一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流重新连接成一个流
 //1.map
 List<Student> list = Arrays.asList(new Student("张三", 18),
            new Student("李四", 19),
            new Student("王二", 16),
            new Student("王二", 16),
            new Student("麻子", 22));

    @Test
    public void test2() {
        list.stream().forEach(System.out::println);
        System.out.println("==============================================");
        list.stream().map(stu -> stu.getName()).forEach(System.out::println);
    }
    
    
   //输出结果
   Student{name='张三', age=18}
   Student{name='李四', age=19}
   Student{name='王二', age=16}
   Student{name='王二', age=16}
   Student{name='麻子', age=22}
   ==============================================
   张三
   李四
   王二
   王二
   麻子
   

 //2.flatMap
 同上类似,将方法换成函数即可

排序

  • sorted():自然排序
  • sorted(Comparator c):定制排序
@Test
public void sortTest() {
    Arrays.asList(6,5,4,3,2,1).stream().sorted().forEach(System.out::println);
    System.out.println("==============================================");
    list.stream().sorted(Comparator.comparingInt(Student::getAge)).forEach(System.out::println);
}

查找/匹配

终止操作:

  • allMatch:检查是否匹配所有元素
  • anyMatch:检查是否至少匹配一个元素
  • noneMatch:检查是否没有匹配所有元素
  • findFirst:返回第一个元素
  • findAny:返回当前流中的任意元素
  • count:返回流中元素的总个数
  • max:返回流中最大值
  • min:返回流中最小值
public enum Status {
    FREE, BUSY, VOCATION;
}

@Test
public void test(){
    List<Status> list = Arrays.asList(Status.FREE, Status.BUSY, Status.VOCATION);

    boolean flag1 = list.stream()
        .allMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag1);

    boolean flag2 = list.stream()
        .anyMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag2);

    boolean flag3 = list.stream()
        .noneMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag3);

    // 避免空指针异常
    Optional<Status> op1 = list.stream()
        .findFirst();
    // 如果Optional为空 找一个替代的对象
    Status s1 = op1.orElse(Status.BUSY);
    System.out.println(s1);

    Optional<Status> op2 = list.stream()
        .findAny();
    System.out.println(op2);

    long count = list.stream()
        .count();
    System.out.println(count);
}

归约/收集

  • 归约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中的数据反复结合起来,得到一个值
  • 收集:collect 将流转换成其他形式;接收一个 Collector 接口的实现,用于给流中元素做汇总的方法

reduce:

/**
* Java:
*  - reduce:需提供默认值(初始值)
* Kotlin:
*  - fold:不需要默认值(初始值)
*  - reduce:需提供默认值(初始值)
*/
@Test
public void test01(){
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Integer integer = list.stream()
        .reduce(0, (x, y) -> x + y);
    System.out.println(integer);
}

collect:


List<Employee> emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);

@Test
public void test02(){
    //放入List
    List<String> list = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toList()); 
    list.forEach(System.out::println);
    
	//放入Set
    Set<String> set = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toSet());
    set.forEach(System.out::println);

    //放入LinkedHashSet
    LinkedHashSet<String> linkedHashSet = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toCollection(LinkedHashSet::new));
    linkedHashSet.forEach(System.out::println);
}

@Test
public void test03(){
    //总数
    Long count = emps.stream()
        .collect(Collectors.counting());
    System.out.println(count);

    //平均值
    Double avg = emps.stream()
        .collect(Collectors.averagingDouble(Employee::getSalary));
    System.out.println(avg);

    //总和
    Double sum = emps.stream()
        .collect(Collectors.summingDouble(Employee::getSalary));
    System.out.println(sum);

    //最大值
    Optional<Employee> max = emps.stream()
        .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
    System.out.println(max.get());

    //最小值
    Optional<Double> min = emps.stream()
        .map(Employee::getSalary)
        .collect(Collectors.minBy(Double::compare));
    System.out.println(min.get());
}

@Test
public void test04(){
    //分组
    Map<Integer, List<Employee>> map = emps.stream()
        .collect(Collectors.groupingBy(Employee::getId));
    System.out.println(map);

    //多级分组
    Map<Integer, Map<String, List<Employee>>> mapMap = emps.stream()
        .collect(Collectors.groupingBy(Employee::getId, Collectors.groupingBy((e) -> {
            if (e.getAge() > 35) {
                return "开除";
            } else {
                return "继续加班";
            }
        })));
    System.out.println(mapMap);
    
    //分区
    Map<Boolean, List<Employee>> listMap = emps.stream()
        .collect(Collectors.partitioningBy((e) -> e.getSalary() > 4321));
    System.out.println(listMap);
}

@Test
public void test05(){
    //总结
    DoubleSummaryStatistics dss = emps.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
    System.out.println(dss.getMax());
    System.out.println(dss.getMin());
    System.out.println(dss.getSum());
    System.out.println(dss.getCount());
    System.out.println(dss.getAverage());
    
    //连接
    String str = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.joining("-")); //可传入分隔符
    System.out.println(str);
}

并行流

  • 并行流:就是把一个内容分成几个数据块,并用不同的线程分别处理每个数据块的流
  • Java 8 中将并行进行了优化,我们可以很容易的对数据进行操作;Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与串行流之间切换
@Test
public void test03(){
    //串行流(单线程):切换为并行流 parallel()
    //并行流:切换为串行流 sequential()
    LongStream.rangeClosed(0, 100000000L)
        .parallel() //底层:ForkJoin
        .reduce(0, Long::sum);

}

五、默认方法

5.1 默认方法

默认方法是java8中引入的一个新特性,希望借此以兼容的方式改变API,示例如下:
public interface PublicInterface {
    default void study(){
        System.out.println("我是大帅比,我正在学习......");
    }
}

//这样只要实现这个接口,就可以获得这个默认方法
//这样的好处之一是可以优雅的实现多继承
  • 那么抽象类和抽象接口之间的区别是什么呢?它们不都能包含抽象方法和包含方法体的 实现吗? 首先,一个类只能继承一个抽象类,但是一个类可以实现多个接口。 其次,一个抽象类可以通过实例变量(字段)保存一个通用状态,而接口是不能有实例变 量的
  • 接口冲突:如果一个父接口提供了一个默认方法,那么无论另外一个接口是否是默认方法,都要进行覆盖重写

5.2 静态方法

public interface PublicInterface {
    default void study(){
        System.out.println("我是大帅比,我正在学习......");
    }

    public static void say(){
        System.out.println("我在上课的时候大声说话...");
    }
}

六、optional

  • 定义:Optional 类 (java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在用 Optional 可以更好的表达这个概念;并且可以避免空指针异常

  • 作用:不用显示的进行空值检查 Optional类的Javadoc描述如下:这是一个可以为null的容器对象。 如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。 如果值不存在则isPresent()方法会返回false,调用get()方法会NPE。

常用方法:

  • Optional.of(T t):创建一个 Optional 实例
  • Optional.empty(T t):创建一个空的 Optional 实例
  • Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则空实例。区别就是可接受为null
  • isPresent():判断是否包含某值
  • orElse(T t):如果调用对象包含值,返回该值,否则返回 t
  • orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值
  • map(Function f):如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty()
  • flatmap(Function mapper):与 map 相似,要求返回值必须是 Optional
  • fliter():
@Test
public void test01(){
    Optional<Employee> op = Optional.of(new Employee());
    Employee employee = op.get();
}

Optional.empty(T t):

@Test
public void test02(){
    Optional<Employee> op = Optional.empty();
    Employee employee = op.get();
}

Optional.ofNullable(T t):

@Test
public void test03(){
    Optional<Employee> op = Optional.ofNullable(new Employee());
    Employee employee = op.get();
}

isPresent():

@Test
public void test03(){
    Optional<Employee> op = Optional.ofNullable(new Employee());
    if (op.isPresent()) {
        Employee employee = op.get();
    }
}




@Test
public void test() {
    Student stu = null;
    Optional<Student> op = Optional.ofNullable(stu);
    System.out.println(op.isPresent());
    Student student = op.isPresent() ? op.get() : new Student();
 }


@Test
public void test() {
    Student stu = null;
    Optional<Student> op = Optional.ofNullable(stu);
    Student student = op.isPresent() ? op.get() : new Student();
    student.setName("小帅哥");
    String name = Optional.of(student).map(Student::getName).orElse("大帅哥");
 }




七、CompletableFuture异步编程

7.1 future接口

future接口在java5中被引入,可以异步获取线程返回结果,示例如下:
@Test
public void test() {
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    Future<String> future = executorService.submit(() -> {
        //睡眠5秒
        Thread.sleep(5000);
        return "success";
    });
    try {
        String res = future.get();
        System.out.println(res);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

//一般情况下使用get的重载方法,等待一定时间后自动结束
String res = future.get(1, TimeUnit.SECONDS);

局限性:

future对于结果的获取不是很友好,只能通过阻塞或者轮询的方式获取结果。阻塞的方式和异步的设计理念相违背,轮询的话会耗费cpu资源。

7.2 CompletableFuture

后续更新......

八、新日期和时间API

主要是LocalDateTime、LocalDate、LocalTime,常用方法也很简单好理解,之间可以进行相互转换