Java8

JDK8新特性

参考地址

1. 函数式接口

概念: 只有一个抽象方法的接口称之为函数式接口。函数式接口可以使用注解@FunctionalInterface进行修饰

2. Lambda表达式

概念及语法格式

λ表达式可以理解为将代码或者方法作为参数进行传递去执行。

λ表达式可以拆分为两部分
左侧:参数列表
右侧:所需要执行的功能,及λ体

语法格式1:无参数,无返回值

@Test
public void test(){
    // () -> System.out.println("Hello");
    Runnable a = new Runnable(){
     @Override
     public void run(){
        System.out.println("Hello")
    	}
    };
    	
    //等同于
    Runnable a1 = () -> System.out.println("Hello");
    a1.run();
}

语法格式2:有一个参数,无返回值(若只有一个参数 小括号可以省略不写)

@Test
public void test(){
    //Consumer被注解@FunctionalInterface修饰的接口(函数式接口) 唯一抽象方法 void accept(T t);
    //左侧参数 -> 右侧执行体
    Consumer<String> con = (x) -> System.out.println(x);
                         // x -> System.out.println(x);
    con.accept("hahah");
}

语法格式3:有两个以上的参数,并且lambda体中有多条语句 (若lambda体中只有一条语句,return 和 大括号都可以省略不写)

@Test
public void test(){
    //Comparator被注解@FunctionalInterface的接口 举例抽象方法 int compare(T o1,T o2);
    Comparator<Integer> com = (x,y) -> {
      System.out.println("hhaha0");
      return (x < y) ? -1 : ((x == y) ? 0 : 1);
    };
    com.compare(1,2);
}

注意:lambda表达式的参数类型可以省略不写,因为jvm编译器可以从上下文推断出数据类型。即“类型推断”如果要在参数里面写数据类型,都要写上。

实例1

class Employee {
    private String name;
    private int age;
    private double salary;//省略 get and set and constructor}
interface MyPredicate<T> {
    boolean test(T t);
}
public class Test{
    static List<Employee> list = Arrays.asList(
            new Employee("张三",10,1),
            new Employee("里斯",20,1),
            new Employee("王五",16,1),
            new Employee("二三",30,1)
    );
    public static List<Employee> filterEmployee(List<Employee> list,MyPredicate<Employee> mp){
        List<Employee> emps = new ArrayList<>();
        for (Employee employee : list) {
            if(mp.test(employee)){
                emps.add(employee);
            }
        }
        return emps;
	 }
    @org.junit.Test
    public void test1(){
        //需要使用自定义的方法,过滤年龄等于等于15岁的员工
        List<Employee> list2 = filterEmployee(list,(e) -> e.getAge() >= 15);
        list2.stream().map(Employee::getName).forEach(System.out::println);
    }
    @org.junit.Test
    public void test2(){
        //可以使用stream进行list集合的过滤  不使用自定义接口
        List<Employee> list2 = list.stream().filter((e) -> e.getAge() >= 15).collect(Collectors.toList());
        list2.stream().map(Employee::getName).forEach(System.out::println);
    }
}

实例2
创建一个MyFun接口使用@FunctionalInterface注解,并创建一个抽象方法Integer getValue(Integer num);在Test类对变量进行某种操作。

@FunctionalInterface
interface MyFun{
    Integer getValue(Integer num);
}
public class Test{
    @org.junit.Test
    public void Test(){
        operation(100,num -> ++num);
    }
    /**
    * param1 num : 传入的整形数
    * param2 mf : 实现某种方式对 整形数 进行操作。
    **/
    public Integer operation(Integer num,MyFun mf){
        return mf.getValue(num);
    }
}

四大核心函数式接口

Consumer : 消费性接口 void accept(T t);

Supplier : 供给性接口 T get();

Function<T,R> : 函数性接口 T代表参数,R代表返回值 R apply(T t);

Predicate :断言性接口 boolean test(T t);

// 举例:消费型接口Consumer<T>的使用 
class Test{
    @org.junit.Test
    publilc void test(){
        happy(10000,(money)->System.out.println("happy消费"+money+"元"));
    }
    public void happy(double money,Consumer<double> con){
        con.accept(money);
    }
}

lambda方法引用

主要有三种语法格式:

对象::实例方法名
类::静态方法名
类::实例方法名

class Test{
    //对象::实例方法名
    @org.junit.Test
    public void test(){
        Consumer<String> con = (x) -> System.out.println(x);
        con.accept("haha");
        Consumer<String> con2 = System.out::println;
        con2.accept("haha");
    }
    //类::静态方法名
    @org.junit.Test
    public void test2(){
        Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
        Comparator<Integer> com2 = Integer::compare;
        com.compare(1,2);
        com2.compare(1,2);
    }
    //类::实例方法名
    @org.junit.Test(){
        BiPredicate<String,String> bp = (x,y) -> x.equals(y);
        bp.test("a","a");
        BiPredicate<String,String> bp2 = String::equals;
    }
}

lambda构造器引用

格式:
CalssName::new

class Test{
    @org.junit.Test
    public void test(){
        Supplier<String> sup = () -> new String();
        //这里的构造器引用取决于接口方法的参数的个数。 此处函数式接口 T get(); 为无参抽象方法所以String在实例化时 也是实例化无参的构造方法  其他类也适用
        Supplier<String> sup2 = String::new;
        String str = sup2.get();
    }
}

3.Stream API

Java8中有两大最为重要的改变。第一个是 Lambda表达式;另外一个则是 Stream API((java.util. stream.*)。

Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的査找、过滤和映射数据等操作。使用 Stream API对集合数据进行操作,就类似于使用SQL执行数据库查询。也可以使用 Stream API来并行执行操作。简而言之Stream API提供了一种高效且易于使用的处理数据的方式。

Stream流的特点

  • Stream自己不会存储数据,主要做的是对数据的计算和处理
  • Stream不会改变原对象(集合、数组)。相反它会返回一个持有结果的新的Stream
  • Stream操作是延迟操作。这意味着它会等到需要结果(终止操作)的时候才执行中间操作

Stream操作的三个步骤

  • 创建Stream。根据数据(集合、数组)源获取一个流
  • 中间操作。中间操作链,对数据源的数据进行处理
  • 终止操作(终端操作)。执行完中间操作进行终止操作产生结果

创建Stream流

方式1:通过Collection集合的Stream()方法或parallelStream()获取流

@Test
public void test(){
    ArrayList<String> list = new ArrayList<>();
    Stream<String> stream = list.stream();
}

方式2:通过Arrays中的静态方法Stream()获取数组流

Stream<String> stream1 = Arrays.stream(new String[]{"abc","def","ghi"});

方式3:通过Stream中的静态方法of(...)

Stream<String> stream2 = Stream.of("a", "b", "c");

方式4:使用Streamiterate()方法创建无限流,使用其generate()方法生成无限流

Stream<Integer> stream3 = Stream.iterate(0, x -> x + 2);
stream3.limit(10).forEach(System.out::println);// 0 2 4 6 8 10 12 ... 18
// 生成方式
Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);

Stream的中间操作

1.筛选与切片

  • filter 过滤操作
  • limit 分页操作
  • skip(n) 跳过前n个元素,若流中元素不足n个则返回一个空流
  • distinct 去重

示例

// 1.使用filter过滤年龄大雨35岁的员工并使用终止操作进行输出
List<com.company.Employee>  employees = Arrays.asList(
            new com.company.Employee("张三", 18, 9999.99),
            new com.company.Employee("李四", 58, 5555.55),
            new com.company.Employee("王五", 26, 3333.33),
            new com.company.Employee("赵六", 35, 6666.66),
            new com.company.Employee("田七", 12, 8888.88),
            new com.company.Employee("田七", 12, 8888.88)
    );
    
public void test11(){
        employees.stream().filter(e -> {
            System.out.println("调用了中间操作");
            return e.getAge()>=35;
        }).forEach(System.out::println);
    }
// 输出,可见只有执行终止了操作的时候才会执行中间操作
调用了中间操作
调用了中间操作
Employee{name='李四', age=58, price=5555.55}
调用了中间操作
调用了中间操作
Employee{name='赵六', age=35, price=6666.66}
调用了中间操作

// 2. 使用limit获取工资大于5000的前两位员工
employees.stream().filter(x ->x.getSalary()>5000).limit(2).forEach(System.out::println);

// 3.获取工资大于5000的员工后,使用skip操作跳过前两个员工获取符合条件的员工
employees.stream().filter(e -> e.getSalary()>5000).skip(2).forEach(System.out::println);

// 4.使用distinct对employees集合去重,注意对于去重的对象需要重写hashCode和equals方法,否则无法去重
employees.stream().distinct().forEach(System.out::println);

2.映射

  • map 接收lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
// 1.使用map获取employees集合中所有员工的名字并使用终止操作打印输出
employees.stream().map(e -> e.getName()).forEach(System.out::println);

3. 排序

  • sorted() 自然排序(按照Comparable排序)
  • sorted(Comparator com) 定制排序(按照Comparator排序)

示例

// 使用sorted定制排序,年龄一致按姓名排序,不一致按年龄排序
employees.stream().sorted( (e1, e2) -> {
            if (e1.getAge() == e2.getAge()){
                return e1.getName().compareTo(e2.getName());
            }else {
                return e1.getAge().compareTo(e2.getAge());
            }
        }).forEach(System.out::println);

Stream终止操作

1.查找与匹配

  • allMatch,检查是否匹配所有元素
  • anyMatch,检查是否至少匹配一个元素
  • noneMatch,检查是否没有匹配所有元素
  • findFirst,返回第一个元素
  • findAny,返回流中的任意元素
  • count,返回流中的元素总个数
  • max,返回流中的最大值
  • min,返回流中的最小值

示例

List<Employee>  employees = Arrays.asList(
            new Employee("张三", 18, 9999.99,Status.Bisy),
            new Employee("李四", 58, 5555.55,Status.Free),
            new Employee("王五", 26, 3333.33,Status.Vocation),
            new Employee("赵六", 35, 6666.66,Status.Bisy),
            new Employee("田七", 12, 8888.88,Status.Bisy),
            new Employee("田七", 12, 8888.88,Status.Bisy)
    );

// 1.使用allMatch操作匹配employees集合中的所有员工是否都处于Bisy状态
boolean allMatch = employees.stream().allMatch(e -> e.getStatus().equals(Status.Bisy));
System.out.println(allMatch);// false

// 2.employees集合按照工资倒序排序后获取第一个,返回一个可能为空的Optional对象,是为了避免出现空指针
Optional<Employee> optional = employees.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).findFirst();
System.out.println(optional.get());

// 3.使用max函数返回工资最大的员工
Optional<Employee> max = employees.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get());

// 4.获取employees集合中工资最少员工的工资
Optional<Double> min = employees.stream().map(Employee::getSalary).min(Double::compare);
System.out.println(min.get());// 3333.33

2. 规约与收集

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

示例:map-reduce模式,应用非常广泛

// 1.计算集合中所有元素的总和
// 计算过程,首先将初始值identity=0设置为x,从集合中一次取一个元素作为y,计算x+y,然后再将结果赋给x继续累加
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(reduce);// 55

// 2.计算employees所有员工的工资总和
// 这次reduce返回的结果为Optional和上面返回的结果为Integer不一样的原因是,这次结果可能为空,而上面因为设置了初始值0所以不可能为空
Optional<Double> optional = employees.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println(optional.get());// 43333.29

// 3.使用collect方法收集employees集合中所有员工的名字到一个set集合中实现去重
Set<String> set = employees.stream().map(Employee::getName).collect(Collectors.toSet());
set.forEach(System.out::println);

// 4.使用collect方法收集employees集合中所有员工的名字到一个特殊集合hashset中实现去重
HashSet<String> hashSet = employees.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
hashSet.forEach(System.out::println);

// 5.使用collect方法根据员工的状态分组,返回一个map
Map<Status, List<Employee>> statusListMap = employees.stream().collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(statusListMap);

// 6.collect多级分组,先按照状态分组,再按照年龄分组
Map<Status, Map<String, List<Employee>>> map = employees.stream().collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(x -> {
            if (x.getAge() <= 35) {
                return "青年";
            } else if (x.getAge() <= 50) {
                return "壮年";
            } else {
                return "老年";
            }
        })));
System.out.println(map);
//{Free={老年=[Employee{name='李四', age=58, salary=5555.55}]}, 
//Bisy={青年=[Employee{name='张三', age=18, salary=9999.99}, Employee{name='赵六', age=35, salary=6666.66}, Employee{name='田七', age=12, salary=8888.88}, Employee{name='田七', age=12, salary=8888.88}]}, 
//Vocation={青年=[Employee{name='王五', age=26, salary=3333.33}]}}

// 7.collect分区/分片,满足条件的一个区,不满足条件的一个区
 Map<Boolean, List<Employee>> map = employees.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
System.out.println(map);
//{false=[Employee{name='李四', age=58, salary=5555.55}, Employee{name='王五', age=26, salary=3333.33}, Employee{name='赵六', age=35, salary=6666.66}], 
//true=[Employee{name='张三', age=18, salary=9999.99}, Employee{name='田七', age=12, salary=8888.88}, Employee{name='田七', age=12, salary=8888.88}]}

// 8.collect统计汇总函数
DoubleSummaryStatistics statistics = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(statistics.getSum());// 总和
System.out.println(statistics.getAverage());// 平均值
System.out.println(statistics.getCount());// 个数
System.out.println(statistics.getMax());// 最大值
System.out.println(statistics.getMin());// 最小值

// 9.collect连接字符串
String collect = employees.stream().map(Employee::getName).collect(Collectors.joining(","));
System.out.println(collect);// 张三,李四,王五,赵六,田七,田七

常用的Collector接口还有:

  • Collectors.counting() 计数
  • Collectors.averagingInt() 求平均值
  • Collectors.summingInt() 求和
  • Collectors.maxBy() 求最大值
  • Collectors.minBy() 求最小值
  • Collectors.groupingBy() 分组
  • Collectors.partitioningBy() 分区
  • Collectors.summarizingDouble() 统计汇总
  • Collectors.joining() 连接字符串

4.并行流与串行流

Fork/Join框架: 就是在必要的情况下,将一个大任务拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个小任务的运行结果进行join汇总

Stream流就是使用了Fork/Join框架的原理

Stream流可以通过parallel()和sequential()进行并行流和串行流的切换jundong2

示例

// 使用并行流计算0~100000000000L的累加和,用时6118ms,运行过程中CPU利用率达到100%,16核都达到100%
Instant start = Instant.now();
        
long sum = LongStream.rangeClosed(0, 100000000000L).parallel().reduce(0, Long::sum);
System.out.println(sum);

Instant end = Instant.now();
System.out.println("耗时:" + Duration.between(start, end).toMillis());// 6118

5.Optional类

作用:Optional是一个容器类,可以避免空指针异常

常用方法:

  • Optional.of(T t)创建一个Optional实例
  • Optional.empty() 创建一个空的Optional实例
  • Optional.ofNullable(T t) 若t不为null,创建Optional实例,否则创建空实例
  • isPresent() 判断是否包含值
  • optional.orElse(T t) 如果调用对象包含值,返回该值,否则返回t
  • orElseGet(Supplier s) 如果调用对象包含值,返回该值,否则返回s获取的值
  • map(Function f) 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
  • flatMap(Function mapper) 与map类似,要求返回的值必须是Optional

示例

// flatMap使用示例
MyTestNormal myTestNormal1 = new MyTestNormal();
myTestNormal1.setName("张三");
myTestNormal1.setAge("23");
Optional<MyTestNormal> myTestNormal = Optional.ofNullable(myTestNormal1);
Optional<String> optional = myTestNormal.flatMap((x) -> Optional.of(x.getName()));
System.out.println(optional.get());

6.接口中的默认方法与静态方法

Java8之前接口中只能有全局静态常量和抽象方法;Java8中允许有实现的default方法和实现的static方法

接口默认方法的“类优先”原则:

  • 若一个接口中定义一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时,这时会执行父类中的方法,接口中的同名方法会被忽略

7.新时间日期API

常用时间API

LocalDate、LocalTime、LocalDateTime的实例是不可变对象、线程安全的,被人所熟悉的时间格式,分别表示ISO-8061日历系统的日期、时间、日期和时间。他们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息;

示例

//LocalDate、 LocalTime、LocalDateTime,以LocalDateTime为例演示
// 1.获取当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now); // 2021-10-26T15:29:20.519
// 2.指定设置一个时间
LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 24, 13, 06, 45);
System.out.println(localDateTime);// 2021-08-24T13:06:45
// 3.增加一个时间(年月日时分秒都行),增加一个月
System.out.println(now.plusMonths(1));// 2021-11-26T15:33:47.600
System.out.println(now.minusDays(6));// 2021-10-20T15:33:47.600
// 4.获取年月日时分秒
System.out.println("当前月份:" + now.getMonth()+ ", 当前小时:" + now.getHour());//当前月份:OCTOBER, 当前小时:15

Instant 时间戳(以Unix元年(1970年1月1日 00:00:00)到某个时间的毫秒值)

示例

 // 获取当前时间
Instant instant = Instant.now();
System.out.println(instant);// 2021-10-26T07:46:05.031Z,与当前时间相差8小时,是因为默认获取的是UTC时区
System.out.println(instant.atOffset(ZoneOffset.ofHours(8)));// 2021-10-26T015:46:05.031Z,增加8小时,偏移到当前时区
// 2.获取一个时间戳
System.out.println(instant.toEpochMilli());// 1635234594156

Duration 计算两个时间之间的间隔
Period 计算两个日期之间的间隔

示例

// 分别设置两个时间戳,在Unix元年增加1000和6000ms,计算二者之间的毫秒值查
Instant instant = Instant.ofEpochMilli(1000);
Instant instant1 = Instant.ofEpochMilli(6000);
System.out.println(Duration.between(instant, instant1).toMillis());// 5000

// Duration也支持计算两个LocalDateTime类型的时间间隔
LocalDateTime localDateTime = LocalDateTime.of(2021, 10, 26, 17, 54, 00);
LocalDateTime localDateTime1 = LocalDateTime.of(2021, 10, 27, 17, 54, 00);
System.out.println(Duration.between(localDateTime, localDateTime1).toMillis());// 86400000

// 使用Period计算两个日期的间隔
LocalDate localDate = LocalDate.now();// 2021-10-26
LocalDate localDate1 = LocalDate.of(2019, 8, 23);
Period period = Period.between(localDate, localDate1);
System.out.println("间隔年:" + period.getYears() + ",间隔月:" + period.getMonths() + ",间隔日:" + period.getDays());// 间隔年:-2,间隔月:-2,间隔日:-3

时间校正器 TemporalAdjuster

有时可能需要将日期调整到 “下个周日” 等操作,TemporalAdjusters该类通过静态方法提供了大量的常用的TemporalAdjuster的实现

示例

LocalDateTime now = LocalDateTime.now();
System.out.println("now:" + now);// 2021-10-28T14:50:38.122
// 使用with指定月份为8月
System.out.println("使用with指定月份为8月:" + now.withMonth(8));// 2021-08-28T14:50:38.122

// 使用时间校验器TemporalAdjuster获取下个周六
System.out.println("获取下个周六:" + now.with(TemporalAdjusters.next(DayOfWeek.SATURDAY)));// 2021-10-30T14:50:38.122 正确

// 使用with自定义一个方法,返回下个工作日的日期
LocalDateTime nexWeekDay = now.with((x) -> {
    LocalDateTime localDateTime = (LocalDateTime) x;
    DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();

    if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
        return localDateTime.plusDays(2);
    } else {
        return localDateTime.plusDays(1);
    }
});
System.out.println("自定义获取下个工作日:" + nexWeekDay);// 2021-10-29T14:50:38.122

// 输出:
now:2021-10-28T14:58:22.480
使用with指定月份为8:2021-08-28T14:58:22.480
获取下个周六:2021-10-30T14:58:22.480
自定义获取下个工作日:2021-10-29T14:58:22.480

日期格式化

LocalDateTime默认输出格式是:2021-10-29T14:58:22.480,即DateTimeFormatter.ISO_DATE_TIME

示例

// 1. 使用ISO_DATE_TIME格式化时间与不使用格式化时间效果一样,说明默认使用的ISO_DATE_TIME方式格式化
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);// 2021-10-31T19:08:22.123
System.out.println(ldt.format(DateTimeFormatter.ISO_DATE_TIME));// 2021-10-31T19:08:22.123
// 2. 只格式化日期或时间
System.out.println(ldt.format(DateTimeFormatter.ISO_DATE));// 2021-10-31
System.out.println(ldt.format(DateTimeFormatter.ISO_TIME));// 19:18:22.62
// 3. 自定义格式化
System.out.println(ldt.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss")));// 2021年10月31日 19:18:22
// 4. 字符串转日期对象
System.out.println(LocalDateTime.parse("2021-10-31 19:18:22", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));// 2021-10-31T19:18:22
    

带时区的处理

  • ZonedDate
  • ZonedTime
  • ZonedDateTime

每个时区都对应着ID,地区ID都为"{区域}/{城市}"的格式,例如:Asia/ShangHai
ZoneId该类包含了所有的时区信息,of(id):用指定的时区信息获取Zoned对象

示例

// 1.查看系统默认时区
System.out.println(ZoneId.systemDefault());// Asia/Shanghai
// 2.查看所有时区
ZoneId.getAvailableZoneIds().forEach(System.out::println);// Asia/Aden、 America/Cuiaba
// 3.查看某个时区下的当前时间
System.out.println(LocalDateTime.now(ZoneId.of("America/Cuiaba")));// 2021-10-31T07:55:00.906
// 4.获取当前系统带时区的时间ZonedDateTime,可见当前时区为Shanghai,相对于UTC偏移8个时区
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
System.out.println(zonedDateTime);// 2021-10-31T19:59:57.892+08:00[Asia/Shanghai]

Date/LocalDateTime/字符串相互转化

参考链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值