java8新特性的实际应用

Java8自从14年发布之后增加了很多新特性,其中好多特性在实际应用中都可以用到,不仅简化了我们代码,还弥补了旧版本里的一些不足。这里只列举出一些实际开发中可能用得到的特性,加以说明。

函数式接口

函数式接口就是有且仅有一个抽象函数的接口,使用@FunctionalInterface注解修饰的类,编译器会检测该类是否只有一个抽象方法或接口,否则,会报错。在java8之前就有一些函数是接口,比如java.util.Comparator,以前我们用到这种接口的时候,一般使用匿名内部类来进行实现,如下,我们定义一个Person类:

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
    private Integer id;
    private String name;
    private Integer age;

    public static List<Person> getList() {
        return Arrays.asList(
                new Person(1, "1号", 19),
                new Person(2, "2号", 16),
                new Person(3, "3号", 20),
                new Person(4, "4号", 19),
                new Person(5, "5号", 14));
    }
}

例如我们要实现按照年龄排序,就要实现一个Comparator类,重写compare方法:

public static void main(String[] args) {
    List<Person> list = Person.getList();
    list.sort(new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.getAge() - o2.getAge();
        }
    });
    list.forEach(System.out::println);
}

运行结果如下:
普通排序的运行结果
在java8之后就可以使用Lambda表达式来表示该接口的一个实现,如下:

public static void main(String[] args) {
    List<Person> list = Person.getList();
    list.sort((o1, o2) -> o1.getAge() - o2.getAge());
    list.forEach(System.out::println);
}

个人的一些看法:面向对象编程其实就是对数据的封装,函数式编程其实就是对函数的封装,每个函数式接口其实就是一类函数的抽象,这和每个类都代表一组有相同特征的对象是一个道理。

java8也提供了一些函数式接口,位于java.util.function包下,例如下面的几个(圈起来的为接口中的抽象方法):
函数式接口
Predicate:接收一个参数,返回值为布尔类型,泛型表示参数类型,可用于判断真假

public static void main(String[] args) {
    Predicate<String> strNullOrEmpty = str -> str == null || "".equals(str);
    System.out.println(strNullOrEmpty.test(""));
}

Consumer:接收一个参数,无返回值,泛型代表参数类型

public static void main(String[] args) {
    Consumer<String> print = str -> System.out.println("[" + str + "]");
    print.accept("你好");
}

Function:接收一个参数,自定义返回值,泛型的第一个参数为参数类型,第二个参数为返回值类型

public static void main(String[] args) {
    Function<String, Integer> function = str -> str.length();
    System.out.println(function.apply("abcd"));
}

这里举个例子,来说明函数式接口的作用:
例如:我们的冒泡排序,我们想从从小到大排序,于是我们写出了以下代码:
冒泡排序
但是,如果这个时候我们又需要从大到小排序,我们就需要在增加一个方法:
冒泡排序2
观察后发现我们两个函数中的大部分逻辑都是相同的,只有判断条件不同,这时我们就可以使用函数是接口来进行代码的优化。

public static void bubbleSort(int[] arr, BiPredicate<Integer, Integer> biPredicate) {
    boolean flag = true;
    for (int i = 0; i < arr.length && flag; i++) {
        flag = false;
        for (int j = arr.length - 1; j > i; j--) {
            if (biPredicate.test(arr[i], arr[j])) {
                int temp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = temp;
                flag = true;
            }
        }
    }
}

public static void main(String[] args) {
    int[] arr = {2, 4, 3, 5, 1};
    bubbleSort(arr, (x, y) -> x > y); // 从小到大排序
    System.out.println(Arrays.toString(arr));
    bubbleSort(arr, (x, y) -> x < y); // 从大到小排序
    System.out.println(Arrays.toString(arr)); 
}

这里的BiPredicate是java8中自带的函数式接口,可以用返回值为布尔类型,参数为两个的值的Lambda表达式来表示

方法引用

方法引用通过方法的名字来指向一个方法,可以使语言的构造更紧凑简洁,减少冗余代码。
例如上面代码中的list.forEach(System.out::println);就引用了println方法,继续优化上面的代码,我们可以使用方法引用来对集合进行排序:

public static void main(String[] args) {
    List<Person> list = Person.getList();
    list.sort(Comparator.comparingInt(Person::getAge));
    list.forEach(System.out::println);
}

stream

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

生成流的几种方法:
  • Collection.stream(); 从集合获取流。
  • Collection.parallelStream(); 从集合获取并行流。
  • Arrays.stream(T array) or Stream.of(); 从数组获取流。
  • BufferedReader.lines(); 从输入流中获取流。
  • IntStream.of() ; 从静态方法中获取流。
  • Stream.generate(); 自己生成流
List<String> nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter");
String[] nameArr = {"Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"};
// 集合获取 Stream 流
Stream<String> nameListStream = nameList.stream();
// 集合获取并行 Stream 流
Stream<String> nameListStream2 = nameList.parallelStream();
// 数组获取 Stream 流
Stream<String> nameArrStream = Stream.of(nameArr);
// 数组获取 Stream 流
Stream<String> nameArrStream1 = Arrays.stream(nameArr);
// 文件流获取 Stream 流
BufferedReader bufferedReader = new BufferedReader(new FileReader("E:/User/Desktop/today.log"));
Stream<String> linesStream = bufferedReader.lines();
linesStream.forEach(System.out::println);
// 从静态方法获取流操作
IntStream rangeStream = IntStream.range(1, 10);
rangeStream.limit(10).forEach(num -> System.out.print(num+","));
System.out.println();
IntStream intStream = IntStream.of(1, 2, 3, 3, 4);
intStream.forEach(num -> System.out.print(num+","));

我们可以使用BufferedReader.lines();来使用简单的代码获取文本文件的行数

常用方法:
findFirst

返回流中第一个元素,为Optional类型

public static void main(String[] args) {
    Stream.of(1, 2, 3, 4, 5)
            .findFirst()
            .ifPresent(System.out::println);
}

运行结果:
运行结果

filter

filter接受一个返回值为布尔类型的函数作为参数,该函数可以用Lambda表达式表示。顾名思义,filter可用于过滤出集合中满足条件元素。
例如我们要在刚才定义的集合中找到年龄为19岁的:

public static void main(String[] args) {
    List<Person> list = Person.getList();
    list.stream().filter(person -> person.getAge() == 19).forEach(System.out::println);
}

运行结果:
运行结果

map

map就是将对应的元素按照给定的方法进行转换,参数为Lambda表达式形式或者是方法引用。
例如我们要获得所有person对象的name:

public static void main(String[] args) {
    List<Person> list = Person.getList();
    list.stream().map(person -> person.getName()).forEach(System.out::println);
}

或者使用函数引用:

public class Test02 {
    public static void main(String[] args) {
        List<Person> list = Person.getList();
        list.stream().map(Person::getName).forEach(System.out::println);
    }
}

运行结果:
运行结果

flatMap

和map像似,都是将对应元素按照对应的方法进行转换,但是区别在于map只能一对一,而flatMap可以一对多转换。
例如我们要获取文章集合中的所有标签,多个标签用逗号隔开。
文章类的定义如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
class Article {
    private String title;
    private Integer id;
    private String tags;

    public static List<Article> getList() {
        return Arrays.asList(
                new Article("如何学习SpringBoot?", 1, "java,Spring"),
                new Article("python学习方法总结", 2, "python,学习经验"),
                new Article("sql优化", 3, "sql,数据库"),
                new Article("jvm面试必考要点?", 4, "java,jvm,面试")
        );
    }
}

使用flatMap实现:

public static void main(String[] args) {
    List<Article> list = Article.getList();
    list.stream().flatMap(article -> Arrays.stream(article.getTags().split(","))).forEach(System.out::println);
}

运行结果:
运行结果
但是假如使用map:

public static void main(String[] args) {
    List<Article> list = Article.getList();
    list.stream().map(article -> Arrays.stream(article.getTags().split(","))).forEach(System.out::println);
}

运行结果为:
运行结果
可见map是能实现一对一,上面的代码只是将集合中的各个元素转换为stream类。

distinct

顾名思义,distinct可以实现去重。
例如,还是上面的标签问题,刚才打印出来的结果中存在标签重复,我们可以对其进行去重操作:

public static void main(String[] args) {
    List<Article> list = Article.getList();
    list.stream()
            .flatMap(article -> Arrays.stream(article.getTags().split(",")))
            .distinct()
            .forEach(System.out::println);
}

运行结果:
运行结果

sorted

使用soured可以对流中元素进行排序。
例如对person进行按照年龄排序:

public static void main(String[] args) {
    List<Person> list = Person.getList();
    list.stream().sorted(Comparator.comparingInt(Person::getAge)).forEach(System.out::println);
}

运行结果:
运行结果

limit

限制返回的个数,例如我们只要前三个person的信息:

public static void main(String[] args) {
    List<Person> list = Person.getList();
    list.stream().limit(3).forEach(System.out::println);
}

运行结果:
运行结果

skip

与limit相反,skip的作用是跳过前面指定个数元素,例如只显示后三个person的信息:

public static void main(String[] args) {
    List<Person> list = Person.getList();
    list.stream().skip(list.size() - 3).forEach(System.out::println);
}

运行结果:
运行结果

min/max

求最大值或者最小值:

public static void main(String[] args) {
    List<Person> list = Person.getList();
    list.stream().min(Comparator.comparingInt(Person::getAge)).ifPresent(System.out::println);
}

这里的ifPresent是Optional类里面的方法,下面会谈到。
运行结果:
运行结果

Statistics

数学统计功能,求一组数组的最大值、最小值、个数、数据和、平均数等。

public static void main(String[] args) {
    IntSummaryStatistics stats = IntStream.of(1, 2, 3, 4, 5, 6).summaryStatistics();
    System.out.println("最小值:" + stats.getMin());
    System.out.println("最大值:" + stats.getMax());
    System.out.println("个数:" + stats.getCount());
    System.out.println("和:" + stats.getSum());
    System.out.println("平均数:" + stats.getAverage());
}

运行结果:
运行结果

anyMatch/allMatch/noneMatch

返回一个boolean类型的值,其中

  • anyMatch:Stream 中任意一个元素符合传入的 predicate,返回 true
  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true
  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
public static void main(String[] args) {
    List<Person> list = Person.getList();
    if (list.stream().noneMatch(person -> person.getAge() > 20)) {
        System.out.println("没有人年龄大于20岁");
    }
    if (list.stream().anyMatch(person -> person.getAge() > 15)) {
        System.out.println("存在年龄大于15岁的人");
    }
    if (list.stream().allMatch(person -> person.getAge() > 10)) {
        System.out.println("所有人年龄都大于10岁");
    }
}
collect

collect在流中生成列表,map,等常用的数据结构
collect的用法分组

toArray

将流转化为数组:

public static void main(String[] args) {
    Integer[] integers = Stream.of(1, 2, 3, 4, 5)
            .toArray(Integer[]::new);
    System.out.println(Arrays.toString(integers));
}

运行结果:
运行结果

自定义流生成器

这种操作类似python里面的生成器,用来保存生成元素的逻辑,当需要生成元素的时候才来调用这个逻辑生成我们需要的元素。

public static void main(String[] args) {
    Stream<String> stream = Stream.generate(Test::myGenerator);
    System.out.println("-----------stream创建之后------------");
    stream.limit(2).forEach(System.out::println);
}

private static String myGenerator() {
    System.out.println("开始执行myGenerator");
    return UUID.randomUUID().toString();
}

运行结果:
运行结果
可以看出,当我们创建流生成器的时候不会立马执行生成器里面的内容,当我们调用forEach函数的时候才开始执行

Optional

Optional类的出现是为了解决返回值为null的问题,以Spring Data Jpa为例,函数findById()的返回值就为一个Optional类的对象。

isPresent/get

isPresent()可以用来判断Optional对象里面的值是否为null,get()可以获取里面的值:
isPresemt/get
但是这样似乎并没有比原始的方法if(xxx==null){}好用,其实Optional主要与Lambda表达式结合才能发挥出作用。

存在就执行操作:
Person p = new Person();
Optional<Person> p1 = Optional.ofNullable(p);
p1.ifPresent(person -> {
    System.out.println(person);
});
存在就返回,否则返回其他内容

(例如一个返回数组的函数,如果没有数据的话一般返回一个长度为0的数组,而不是null)

private static int[] test() {
    int[] ints = {1, 2, 3, 4};
    Optional<int[]> optional = Optional.ofNullable(ints);
    return optional.orElse(new int[0]);
}
  • 也可以由函数产生:
private static int[] test() {
    int[] ints = {1, 2, 3, 4};
    Optional<int[]> optional = Optional.ofNullable(ints);
    return optional.orElseGet(() -> {
        // 一系列操作
        return new int[0];
    });
}
存在就返回,否则就抛出异常
private static int[] test() {
    int[] ints = {1, 2, 3, 4};
    Optional<int[]> optional = Optional.ofNullable(ints);
    return optional.orElseThrow(() -> new RuntimeException());
}
链式操作

Optional类还可以进行链式操作,下面为java7的写法:

private static String getUpperName() {
    Person p = new Person();
    if (p != null) {
        if (p.getName() != null) {
            return p.getName().toUpperCase();
        } else {
            return null;
        }
    } else {
        return null;
    }
}

使用java8可以大量简化操作:

private static String getUpperName() {
    Person p = new Person();
    return Optional.ofNullable(p)
            .map(person -> person.getName())
            .map(name -> name.toUpperCase())
            .orElse(null);
}

Java 8 日期时间 API

旧版API中的问题

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
java8中新的类

java8中引入了很多新的类,使我们更加方便的操作时间与日期

  • LocalDate:不包含时间的日期,比如2019-10-14。可以用来存储生日,周年纪念日,入职日期等。
  • LocalTime:与LocalDate想对照,它是不包含日期的时间。
  • LocalDateTime:包含了日期及时间,没有偏移信息(时区)。
  • ZonedDateTime:包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。
  • Instant:时间戳,与System.currentTimeMillis()类似。
  • Duration:表示一个时间段。
  • Period:用来表示以年月日来衡量一个时间段。
  • DateTimeFormatter:日期格式化类
常用操作:
毫秒数转LocalDateTime

实现需求:为了方便我们经常在数据库中存入毫秒数,从数据库中读取出毫秒数之后,我们可以通过以下方法转换为LocalDateTime

Instant instant = Instant.ofEpochMilli(System.currentTimeMillis());
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
格式化LocalDateTime

实现需求:我们可以将时间转换为相应格式,返回给前端

LocalDateTime localDateTime = LocalDateTime.now();
String format = localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
String format1 = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

需要注意LocalDate、LocalTime、LocalDateTime、Instant为不可变对象,修改这些对象对象会返回一个副本

参考:(DateTimeFormatter自带的时间格式)
时间格式

参考文章:
https://www.runoob.com/java/java8-new-features.html
https://mp.weixin.qq.com/s/QQbkpjqxunHPN1RVx5EIDg
https://mp.weixin.qq.com/s/sz47–bXx-yjCy84uL0LbQ
https://juejin.im/post/5da3fd81f265da5b81794e71
https://mp.weixin.qq.com/s/gqCYNh123u4Xvp4KNvNdhA
https://mp.weixin.qq.com/s/JWsNJXtQgb3H9phcBHYCqQ

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值