java8新特性

2022-03-31  本文已影响0人  轻轻敲醒沉睡的心灵

介绍Java8之前,先回想一下基础知识:Java中的接口和抽象类 - 简书 (jianshu.com)

1. 函数式接口

先举一个例子,我们经常使用到的:Java中在用到线程的时候,经常是new一个类实现Runnable接口重写run(),然后将这个类的实例放到Thread()中。我们看一下Runnable接口

1648538659(1).png
我们使用这个接口时,也可以用匿名内部类(叫这个名字,我感觉是因为,平时我们用接口都会new一个类去实现接口,然后用这个类的实例,这里没有new类,所以没有名字,但是像类一样,重写了方法,也有实例了)的方法:
new Runnable() {
            
    @Override
    public void run() {
        System.out.println("新线程处理业务");
    }
};

这是以前经常使用的代码样例。为了简化代码,Java8引入了Lambda表达式,而Lambda表达式的基础就是函数式接口。
函数式接口有以下特点:

1.1 Java8内置的函数式接口

Java8内置了很多函数式接口,在java.util.function包中,平时用的最多的主要是以下几个,其他的可以ctrl+shift+t自己看

函数式接口 入参 返回 用法
Conusmer<T> T 消费型接口,内含方法:
void accept(T t)
接收入参,无返回值
Supplier<T> T 供给型接口,内含方法:
T get()
无入参,有返回值
Function<T ,R> T R 函数型接口,内含方法:
R apply(T t)
有入参,有返回值
Predicate<T> T boolean 断言型接口,内含方法:
boolean test(T t)
接收入参,返回boolean值

2. Lambda表达式

2.1 语法结构

2.2 例子

都已Java8内置的函数式接口为例。

Runnable r = () -> {
    System.out.println("无参无返回接口");
};
r.run();
// 直接调用
((Runnable)() -> {
    System.out.println("无参无返回接口");
}).run();
Consumer<Integer> c = (n) -> {
    System.out.println("有参无返回");
};
c.accept(9);
((Consumer<Integer>)(n) -> {
    System.out.println("有参无返回");
}).accept(3);
Supplier<String> s = () -> {
    return "无参有返回";
};
s.get();
((Supplier<String>)() -> {
    return "无参有返回";
}).get();

规则1:若lambda表达式有且只有1个参数,可以省略()
规则2:若lambda表达式方法体只有一条语句,可以省略{},也可以省略return关键字
所以,上面几个简写:

((Runnable)() -> System.out.println("无参无返回接口")).run();
        
((Consumer<Integer>)n -> System.out.println("有参无返回")).accept(3);
        
((Supplier<String>)() -> "无参有返回").get();

2.4 实用

Lambda表达式在代码中很实用的,我在处理集合的时候,有些API中经常用到,典型的:

List<User> list = userDao.selectList(null);
// 排序
list.sort((o1, o2) -> o2.getId() - o1.getId()); 
// 轮循
list.forEach(o1 -> System.out.println(o1.toString()));

2.5 方法引用

什么是方法引用呢?当我们需要一个函数式接口的实例对象,于是我们使用Lambda表达式来创建,lambda的方法体需要我们自己实现,这个时候如果有另外的方法,正好满足我们的需要,我们就不用写了,直接把那个方法拿过来用就行了,但是我们需要的是lambda表达式创建的接口的实例对象,此时 lambda表达式可以引用已有的方法,称为方法引用
比如,我想要Consumer<String>的实例对象,方法体就打印传入的参数,用lambda表达式效果如下面1所示。但是打印传参这个功能呢,在PrintStream这个类中已经有了,我可以直接拿过来用,作为我lambda表达式要实现的方法。

java自带的打印的方法
这个时候就用到了方法引用,用::来表示,如下2所示。
// 1. lambda表达式
Consumer<String> con = text -> System.out.println(text);
con.accept("测试方法引用");
// 2. 方法引用      
Consumer<String> con1 = System.out::println;
con1.accept("test success");

方法引用使用条件:被引用方法的参数列表返回类型必须要和函数接口中方法的参数列表返回类型保持一致。
几种常见的方法引用:

前2种方法比较正常,如下面代码中的1和2,我的理解是:双冒号前面的 能点出来 冒号后面的方法;
对象方法:是指函数式接口中方法的第1个参数 类型是 要引入的方法所在的类的类型,比较特殊,所以有简便写法,也可以按前2种来写,简便写法认识就行;
构造方法引入,下面代码中的3,class :: new

// 1
Consumer<String> con1 = System.out::println;
con1.accept("test success");
// 2    
Consumer<String> con2 = new User()::setName;
con2.accept("张三");
// 3. 构造方法引用        
Supplier<User> su = User::new;

3. Stream Api

Stream 是JDK1.8 中处理集合的关键抽象概念,Lambda 和 Stream 是JDK1.8新增的函数式编程最有亮点的特性了,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL执行的数据库查询。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
特点:

  1. 创建Stream流,(用集合或者数组等作为原数据)
  2. 中间操作(过滤、相加、分组等)
  3. 终止操作

3.1 创建Stream流

Stream流分为2种:

public static void main(String[] args) {
    // 1. 通过集合的stream()方法
    List<String> list = new ArrayList<String>();
    Stream<String> s1 = list.stream();
    // 2. 通过Arrays的静态方法stream()
    User[] userArr = new User[10];  
    Stream<User> s2 = Arrays.stream(userArr);
    // 3. 通过Stream的静态方法of()
    Stream<String> s3 = Stream.of("a", "b", "c");
    // 4. 创建无限流
    Stream<Integer> s4 = Stream.iterate(1, x -> x+2);
    s4.limit(10).forEach(System.out::println);
    Stream<Double> s5 = Stream.generate(() -> Math.random());
    s5.limit(10).forEach(System.out::println);
}

3.2 Stream流操作

List<User> list = userDao.selectList(null);
list.stream()
    .filter(u -> u.getAge() == 22)
    .forEach(System.out::println);
// foreach是终止操作,有终止操作,才有结果
List<User> list = userDao.selectList(null);
list.stream()
    .limit(3)
    .forEach(System.out::println);
List<User> list = userDao.selectList(null);
list.stream()
    .skip(2)
    .forEach(System.out::println);
List<User> list = userDao.selectList(null);
list.stream()
    .distinct()
    .forEach(System.out::println);
List<User> list = userDao.selectList(null);
list.stream()
    .map(User::getName)   // 拿到每个元素的名字
    .forEach(System.out::println);
List<User> list = userDao.selectList(null);
list.stream()
    .sorted((u1, u2) -> u1.getName().compareTo(u2.getName()))
    .forEach(System.out::println);
List<User> list = userDao.selectList(null);
boolean b1 = list.stream()
    .allMatch((u) -> u.getAge() > 20);
boolean b2 = list.stream()
    .anyMatch((u) -> u.getAge() > 20);
boolean b3 = list.stream()
    .noneMatch((u) -> u.getAge() > 20);
System.out.println(b1 + "---" + b2 + "----" + b3);
Optional<User> f = list.stream()
    .findFirst();
if (f.isPresent()) {
    System.out.println(f.get());
}
long count = list.stream()
    .count();
list.stream()
    .max((u1, u2) -> Integer.compare(u1.getAge(), u2.getAge()));
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream()
    .reduce(0, (x, y) -> x + y); // 求和
List<User> list1 = userDao.selectList(null);
Optional<Integer> sum1 = list1.stream()
    .map(User::getAge)
    .reduce(Integer::sum); // 求和
List<User> list = userDao.selectList(null);
List<String> nameList = list.stream()
    .map(User::getName)
    .collect(Collectors.toList());
Map<Integer, User> userMap = list.stream()
    .collect(Collectors.toMap(user -> user.getId(), user -> user));
Long count = list.stream()
    .collect(Collectors.counting());
Double average = list.stream()
    .collect(Collectors.averagingInt(User::getAge));

4. Optional类

Optional 是个容器,它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 常用方法:

5.

串行流:单线程的方式操作; 数据量比较少的时候。
并行流:多线程方式操作;数据量比较大的时候,原理:
Fork join 将一个大的任务拆分n多个小的子任务并行执行,
最后在统计结果,有可能会非常消耗cpu的资源,确实可以
提高效率。

6. 新日期时间Api

  1. 时间戳
Instant now = Instant.now();
System.out.println(now);
System.out.println(now.atOffset(ZoneOffset.ofHours(8)));
System.out.println(now.atOffset(ZoneOffset.ofHours(8)).toEpochSecond());
  1. 时间 LocalDate LocalTime LocalDateTime
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
LocalDateTime ldt1 = LocalDateTime.of(2022, 3, 31, 11, 50, 20);
System.out.println(ldt1);
System.out.println(ldt.plusDays(1)); 
System.out.println(ldt.minusMonths(1));
System.out.println(ldt.getDayOfMonth());
  1. 时间差
Duration dur = Duration.between(ldt1, ldt);
System.out.println(dur.getSeconds());
System.out.println(dur.toMillis());
  1. 日期差
LocalDate ld1 = LocalDate.of(2020, 5, 31);
LocalDate ld2 = LocalDate.now();
Period pe = Period.between(ld1, ld2);
System.out.println(pe.getYears());
System.out.println(pe.getMonths());
System.out.println(pe.getDays());
  1. 时间日期修改更正
System.out.println(ldt.withDayOfMonth(1));
System.out.println(ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));
  1. 格式化
ldt.format(DateTimeFormatter.ISO_DATE);
ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); System.out.println(ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
  1. 时区 ZoneDate ZoneTime ZoneDateTime
// 所有时区
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
zoneIds.forEach(System.out::println);
System.out.println(ldt.atOffset(ZoneOffset.ofHours(8)));
System.out.println(ldt.atZone(ZoneId.of("Asia/Shanghai")));
上一篇 下一篇

猜你喜欢

热点阅读