Java16都快上线了,你该不会连Java8的特性都不会用吧?

Posted Java鱼仔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java16都快上线了,你该不会连Java8的特性都不会用吧?相关的知识,希望对你有一定的参考价值。

(一)前言

2020年,Java16有了新的消息,预计将在2021年的3月16日正式发布。但是这一消息对于百分之九十的国内程序员来说都只是一个新闻而已,因为国内的绝大部分公司依然使用着Java8。这款发布于2014年的JDK版本深受各大公司的喜爱,最大的原因取决于它的稳定性。

即使如此,依然有一半以上的程序员对于Java8的特性不了解,于是我用一个周末的时间把JDK8的”新“特性肝了一遍,希望对大家有所帮助。

(二)Lambda表达式

Lambda是一个匿名函数,它允许你通过表达式来代替功能接口,使用lambda可以让代码变得更加简洁。简单来讲,lambda使用更简洁的方式给接口添加实现方法。

Lambda只能在函数式接口中使用,函数式接口就是那些只有一个抽象方法的接口,可以用注解@FunctionalInterface修饰,如果一个接口中有多个方法,就不能再使用Lambda了。

我们常用的Runnable、Comparator都是函数式接口:

lambda的语法格式如下:

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

2.1 无参数、无返回值

lambda实现的是对功能接口代码的简洁化,比如下面这一段:

Runnable runnable=new Runnable() {
    @Override
    public void run() {
        System.out.println("run");
    }
};

我们在创建一个Runnable对象的时候需要通过匿名内部类实现接口中定义的方法,而使用lambda表达式,上面的代码一行就可以搞定:

Runnable runnable2= () -> System.out.println("run");

2.2 有参数,无返回值

我们先写一个这样的接口:

public interface Student {
    public void getAge(int age);
}

在以前的代码中,我们需要首先写一个类去继承这个接口,或者是写一个匿名内部类,如:

Student student=new Student() {
    @Override
    public void getAge(int age) {
        System.out.println(age);
    }
};

现在就变得简单了:

Student student2=(age) -> { System.out.println(age); };

如果只有一个参数,小括号可以不写:

Student student3=age -> { System.out.println(age); };

2.3 有参数,有返回值

写一个接口:

public interface Student {
    public int getAge(int age);
}

直接写lambda表达式了:

Student student5=age -> { return age; };
System.out.println(student5.getAge(1));

(三)内置函数式接口

在前面已经介绍了什么是函数式接口,函数式接口就是那些只有一个抽象方法的接口,Java8中内置了四种函数式接口:

Consumer<T> : void accept(T t);
Supplier<T> : T get();
Function<T,R> : R apply(T t);
Predicate<T> : boolean test(T t)

这四种接口和我们平常写的没有什么区别,可以在Java8中直接调用:

3.1 Consumer

消费型接口,提供了一个参数、无返回值的接口方法,就和消费一样,花出去就没有了。

public static void main(String[] args) {
    consume(100,money -> {
        System.out.println("消费了"+money+"元");
    });
}
public static void consume(double money, Consumer<Double> consumer){
    consumer.accept(money);
}

3.2 Supplier

供给型接口,提供了无参数,有返回值的接口方法,主要起到供给数据的作用,比如实现一个生成随机数的功能:

public static void main(String[] args) {
    System.out.println(getRandomNum(()->(int)(Math.random()*100)));
}
public static int getRandomNum(Supplier<Integer> supplier){
    return supplier.get();
}

3.3 Function

函数型接口,提供带参数和返回值的接口方法,可用来对数据进行处理后再返回,比如实现一个替换字符串给功能:

public static void main(String[] args) {
   //去掉前后空格
   System.out.println(handlerString("\\t\\tJava鱼仔\\t\\t",(str)->str.trim()));
}
public static String handlerString(String string, Function<String,String> function){
    return function.apply(string);
}

3.4 Predicate

断言型接口,提供带参数和返回值的接口方法,只不过返回值是boolean类型,可用于进行判断,比如实现一个判断某个字符串是否是整数的需求

public static void main(String[] args) {
     Pattern pattern = Pattern.compile("^[-\\\\+]?[\\\\d]*$");
     if (isDigit("111",s -> {
         return pattern.matcher(s).matches();
     })){
         System.out.println("is Digit");
     }
}
public static String handlerString(String string, Function<String,String> function){
    return function.apply(string);
}

(四)Stream API

Java8中两个最重大的改变,一个是Lambda表达式,另外一个就是Stream API

Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。

使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

Stream的执行过程分为三步

1、创建stream

2、操作stream(查找、筛选、过滤等等)

3、执行stream

stream的操作属于惰性求值,即所有的操作是在执行stream时一次性执行。

4.1 创建stream

创建流的方式有四种,这里直接把创建方式的注释写进代码里

public static void main(String[] args) {
    //创建Stream
    //1、通过Collection系列集合提供的stream()或parallelStream()
    List<String> list=new ArrayList<>();
    Stream<String> stream = list.stream();

    //2、通过Arrays中的静态方法stream()获取
    Integer[] integers=new Integer[10];
    Stream<Integer> stream1 = Arrays.stream(integers);

    //3、通过Stream类中的静态方法of()
    Stream<Integer> stream2 = Stream.of(integers);

    //4、迭代创建
    Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2);
}

4.2 操作stream

stream有多种操作集合的方法,下面一一进行讲解:

为了更方便的演示,新建一个实体类User:

public class User {
    private String name;
    private int age;
    private String sex;
    //这里省略构造方法、get、set、toString方法
}

filter过滤--通过某种条件过滤元素:

filter通过Predicate接口过滤元素,下面这段代码将年龄超过30岁的过滤出来

public static void main(String[] args) {
    List<User> list= Arrays.asList(
            new User("javayz",23,"male"),
            new User("张三",25,"male"),
            new User("李四",30,"female"),
            new User("王五",33,"female")
            new User("王五",33,"female")
    );
    //过滤出年龄大于30岁的人
    Stream<User> userStream = list.stream().filter((e) -> e.getAge() >= 30);
   userStream.forEach((e)-> System.out.println(e));
}

limit截断--使元素不超过给定的数量

//对取出来的数据只展示一条
Stream<User> userStream = list.stream()
        .filter((e) -> e.getAge() >= 30)
        .limit(1);
userStream.forEach((e)-> System.out.println(e));

skip--跳过集合中的前n个数

可以和limit联合使用取出指定的范围的数

Stream<User> userStream = list.stream().skip(2);

distinct--去重,通过元素的hashcode()和equals()去除重复元素

去重是根据hashcode()和equals()方法判断重复元素的,因此实体类了需要重写hashcode()和equals()方法

Stream<User> userStream = list.stream().distinct();

map--通过Function接口对stream中的每个数据处理后返回新的数据

比如我想把所有的英文改为大写,就可以这样

List<String> list1=Arrays.asList("aa","bb","cc");
Stream<String> stringStream = list1.stream().map((str) -> str.toUpperCase());
stringStream.forEach((e)-> System.out.println(e));

flatmap--通过Function接口把stream的每一个值都换成另外个stream,最后再把所有stream连接成一个stream

这个方法的使用可能会比较难懂,通过一个例子,把包含三个字符串的集合中的每个字符串都分成一个个字符提取出来。比如把一个集合{"aa","bb","cc"}变成{\'a\',\'a\',\'b\',\'b\',\'c\',\'c\'},使用map就需要这样:

List<String> list1=Arrays.asList("aa","bb","cc");
Stream<Stream<Character>> streamStream = list1.stream().map((str) -> {
    List<Character> characters = new ArrayList<>();
    for (Character ch : str.toCharArray()) {
        characters.add(ch);
    }
    return characters.stream();
});
streamStream.forEach((stream)->{
    stream.forEach((character -> System.out.println(character)));
});

它第一个stream的返回值是stream中套一个字符流,输出的时候也需要先遍历最外层的stream再遍历内层的stream,比较麻烦,于是就可以使用flatmap,他会把多个stream合并为一个。

Stream<Character> characterStream = list1.stream().flatMap((str) -> {
    List<Character> characters = new ArrayList<>();
    for (Character ch : str.toCharArray()) {
        characters.add(ch);
    }
    return characters.stream();
});
characterStream.forEach((e)-> System.out.println(e));

sorted--自然排序,按照Comparable方式排序

Stream<String> stringStream = list.stream().sorted();

sorted(Comparator comparator)--定制排序,自己写一个Comparator 实现排序

List<User> list= Arrays.asList(
        new User("javayz",23,"male"),
        new User("张三",25,"male"),
        new User("李四",30,"female"),
        new User("王五",33,"female")
);

Stream<User> sorted = list.stream().sorted((e1, e2) -> {
    return e1.getAge() == e2.getAge() ? 0 : e1.getAge() > e2.getAge() ? 1 : -1;
});
sorted.forEach((e)-> System.out.println(e));

4.3 执行stream

如果只是操作stream,流的数据是不会变化的,接下来介绍执行stream的一系列方法,首先把接下来会用的数据创建进来:

List<User> list= Arrays.asList(
        new User("javayz",23,"male"),
        new User("张三",25,"male"),
        new User("李四",30,"female"),
        new User("王五",33,"female")
);

allMatch--检查是否匹配所有元素

//判断所有的元素的sex是否都是male,这里返回false
boolean male = list.stream().allMatch((e) -> e.getSex().equals("male"));
System.out.println(male);

anyMatch--检查是否至少匹配一个元素

boolean male = list.stream().anyMatch((e) -> e.getSex().equals("male"));
System.out.println(male);//这里返回true

noneMatch--检查是不是没有元素能够匹配指定的规则

boolean male = list.stream().noneMatch((e) -> e.getSex().equals("male"));
System.out.println(male);//这里返回false

findFirst--返回第一个元素

Optional<User> first = list.stream().findFirst();
System.out.println(first.get());

findAny--返回当前流中的任意一个元素

Optional<User> first = list.stream().findAny();
System.out.println(first.get());

count--返回当前流中的元素总个数

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

max--返回当前流中的最大值

返回最大值和最小值依旧需要实现comparator接口方法

Optional<User> max = list.stream().max((e1, e2) -> {
    return Integer.compare(e1.getAge(), e2.getAge());
});
System.out.println(max.get());

min--返回当前流中的最小值

Optional<User> max = list.stream().min((e1, e2) -> {
    return Integer.compare(e1.getAge(), e2.getAge());
});
System.out.println(max.get());

reduce--规约,将流中的元素反复结合起来,得到一个值

List<Integer> list=Arrays.asList(1,2,3,4,5,6,7,8,9);
//从第0个元素开始,对list求和
Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(reduce);

collection--收集,将流中的数据接收成其他的形式

比如我们把上面User数据的名字收集到List数组中:

List<String> collect = list.stream().map((e) -> e.getName()).collect(Collectors.toList());
System.out.println(collect);

Collectors提供了大量的转化为其他方式的实现,这里不做过多介绍。

(五)接口中的默认方法和静态方法

在1.8之前,接口中的方法只能声明无法实现,在Java8中,接口中可以添加默认方法和静态方法了。

首先介绍默认方法,通过default修饰的方法可以在接口中增加实现。

public interface MyInterface {
    default void defaultMethod(){
        System.out.println("hello");
    }
}

另外是接口中的静态方法:

public interface MyInterface {
    public static void staticMethod(){
        System.out.println("hello");
    }
}

(六)新时间日期API

JAVA8中增加了新的时间日期API,通过代码来混个眼熟:

6.1 LocalDate、LocalTime、LocalDateTime

@Test
public void test1(){
    //获取当前时间
    LocalDateTime localDateTime=LocalDateTime.now();
    System.out.println(localDateTime);
    //增加两年
    System.out.println(localDateTime.plusYears(2));
    //减少两年
    System.out.println(localDateTime.minusYears(2));
    //获取年、月、日、小时、分钟、秒
    System.out.println(localDateTime.getYear());
    System.out.println(localDateTime.getMonthValue());
    System.out.println(localDateTime.getDayOfMonth());
    System.out.println(localDateTime.getHour());
    System.out.println(localDateTime.getMinute());
    System.out.println(localDateTime.getSecond());
}

6.2 Instant :时间戳

@Test
public void test2(){
    Instant instant=Instant.now();
    //获取当前时间戳
    System.out.println(instant.toEpochMilli());
}

6.3 Duration :计算两个时间间隔

@Test
public void test3(){
    Instant instant1=Instant.now();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Instant instant2=Instant.now();
    Duration between = Duration.between(instant1, instant2);
    //计算出两个日期相差的毫秒、分钟、小时等数据
    System.out.println(between.toMillis());
    System.out.println(between.toMinutes());
    System.out.println(between.toHours());
}

6.4 Period:计算两个日期间隔

@Test
public void test4(){
    LocalDate localDate1=LocalDate.of(2020,11,1);
    LocalDate localDate2=LocalDate.now();
    Period between = Period.between(localDate1, localDate2);
    //计算相差几年几个月零几天
    System.out.println(between.getYears());
    System.out.println(between.getMonths());
    System.out.println(between.getDays());
}

6.5 TemporalAdjuster:时间矫正器

TemporalAdjuster用于对时间进行操作,我们可以通过TemporalAdjusters这个类调用大量对时间操作的方法,比如下个周日等等。

@Test
public void test5(){
    LocalDateTime localDateTime=LocalDateTime.now();
    LocalDateTime with = localDateTime.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    System.out.println(with);
}

6.6 格式化时间

时间格式化类在JAVA8之前用的最多的就是SimpleDateFormat,现在多了一个叫做DateTimeFormatter的格式化类:

@Test
public void test6(){
    DateTimeFormatter dtf= DateTimeFormatter.ofPattern("yyyy-MM-dd");
    LocalDateTime localDateTime=LocalDateTime.now();
    System.out.println(localDateTime.format(dtf));
}

(七)总结

总的来讲,Java8的新东西还是有很多的,虽然现在很多程序员都不习惯使用新的语法,但对这些新语法也不要抗拒,毕竟现在最新的Java版本已经到16了呢!

以上是关于Java16都快上线了,你该不会连Java8的特性都不会用吧?的主要内容,如果未能解决你的问题,请参考以下文章

2020了你还不会Java8新特性?Java 8新特性介绍

300 秒快速了解 Java 9 - 16 新特性

JDK都出到16了,8还没有掌握吗?一篇文章打包带走Java8新特性

jdk都出到16了8还没掌握吗?挽周带你玩转java8新特性(建议收藏)

2020了你还不会Java8新特性?收集器比较器用法详解及源码剖析

2020了你还不会Java8新特性?时间日期API&&Java8总结