关注我更多精彩文章第一时间推送给你
JDK 新特性
Oracle 对 Java 8 的官方支持时间持续到 2020 年 12 月,以后将再也不为我的桌面用户提供 Oracle JDK 8 的修复更新。java
不过,还会有不少第三方会经过 openjdk8 继续维护 jdk8.python
Java 11 仅将提供长期支持服务(LTS, Long-Term-Support),还将做为 Java 平台的默认支持版本,而且会提供技术支持直至 2023 年 9 月,对应的补丁和安全警告等支持将持续至 2026 年。程序员
目前 Oracle 官方最近版本是 JDK15,也就是不用注册 Oracle 帐户就能下载的最新版,固然这是一个短时间版本,下一个长期 LTS 版本将是今年 9 月份将要发布的 JDK17。sql
虽然官方已经不在修复更新 JDK8,可是 JDK8 仍然是具备里程碑意义的一个重要的 JDK 版本,也是多数人仍在使用的一个版本,因此我要讲的内容是JDK八、JDK11的新特性。编程
JDK8 新特性
1. Lambda表达式&函数式接口
Lambda 表达式,也可称为闭包,它是推进 Java 8 发布的最重要新特性。数组
Lambda 容许把函数做为一个方法的参数(函数做为参数传递进方法中)。安全
使用Lambda 表达式可使代码变的更加简洁紧凑。多线程
什么是函数式接口?
Lambda表达式须要函数式接口的支持。闭包
函数式接口是指只有一个抽象方法的接口。app
JDK8提供了注解@FunctionInterface在编译时校验函数式接口。
JDK内置的函数式接口在 java.util.function;
例如:Runnable 接口就是一个函数式接口:
/** * @author yx * @since 2021/2/2 15:18 */ public class JDKRunnable { public static void main(String[] args) { // 线程 1 Runnable r1 = new Runnable() { @Override public void run() { System.out.println("JDK8以前的写法"); } }; // 线程 2 Runnable r2 = () -> System.out.println("JDK8以后的写法"); new Thread(r1).start(); new Thread(r2).start(); } }
/** * 实现 treeSet 的比较器排序 */ private void comparator() { // TreeSet 是一个有序的集合,它的做用是提供有序的Set集合。这是使用比较器排序。 TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.length() - o2.length(); } }); // Lambda表达式写法 TreeSet<String> treeSet1 = new TreeSet<>((m, n) -> m.length() - n.length()); // 方法引用写法 TreeSet<String> treeSet2 = new TreeSet<>(Comparator.comparingInt(String::length)); }
方法引用介绍
方法引用经过方法的名字来指向一个方法。
方法引用可使语言的构造更紧凑简洁,减小冗余代码。
方法引用使用一对冒号 ::
在咱们使用Lambda表达式的时候,->
右边部分是要执行的代码,即要完成的功能,能够把这部分称做Lambda体。有时候,当咱们想要实现一个函数式接口的那个抽象方法,可是已经有类实现了咱们想要的功能,这个时候咱们就能够用方法引用来直接使用现有类的功能去实现。
引用方法
- 对象引用 :: 实例方法名
- 类名 :: 静态方法名
- 类名 :: 实例方法名
-
对象引用 :: 实例方法名
private static void method() { Consumer<String> consumer = System.out::println; consumer.accept("方法引用 1 之对象引用::实例方法名"); }
System.out
就是一个PrintStream
类型的对象引用,而println
则是一个实例方法名,须要注意的是没有括号的哟。其中Consumer
是Java内置函数式接口,下面的例子用到的都是Java内置函数式接口。Consumer
中的惟一抽象方法accept
方法参数列表与println
方法的参数列表相同,都是接收一个String
类型参数。 -
类名::静态方法名
private static void method() { Function<Integer, Integer> f = Math::abs; final Integer apply = f.apply(-3); System.out.println(apply); }
Math
是一个类而abs
为该类的静态方法。Function
中的惟一抽象方法apply
方法参数列表与abs
方法的参数列表相同,都是接收一个Integer
类型参数。 -
类名::实例方法名
private static void method() { BiPredicate<String, String> n = String::equals; final boolean test = n.test("aaa", "bbb"); System.out.println(test); }
String
是一个类而equals
为该类的定义的实例方法。BiPredicate
中的惟一抽象方法test
方法参数列表与equals
方法的参数列表相同,都是接收两个String
类型参数。
引用构造器
private static void method() { Function<Integer, StringBuffer> is = StringBuffer::new; final StringBuffer sb = is.apply(10); System.out.println(sb.capacity()); }
Function
接口的apply
方法接收一个参数,而且有返回值。在这里接收的参数是Integer
类型,与StringBuffer
类的一个构造方法StringBuffer(int capacity)
对应,而返回值就是StringBuffer
类型。上面这段代码的功能就是建立一个Function
实例,并把它apply
方法实现为建立一个指定初始大小的StringBuffer
对象。
引用数组
private static void method() { Function<Integer, int[]> fii = int[]::new; final int[] apply1 = fii.apply(20); System.out.println(apply1.length); Function<Integer, Double[]> fid = Double[]::new; final int[] apply2 = fii.apply(30); System.out.println(apply2.length); }
引用数组和引用构造器很像,格式为 类型[]::new,其中类型能够为基本类型也能够是类。
2. Stream流式编程
Stream 流是 java8 中处理数组、集合的抽象概念,他能够指定你但愿对集合进行的操做,能够执行很是复杂的查找、过滤和映射数据等操做。
一个Stream表面上与一个集合很相似,可是集合中保存的是数据,而流设置的是对数据的操做。
Stream流的特色:
- Stream 本身不会存储元素
- Stream 不会改变源对象,相反,他们会返回一个持有结果的新的Stream
- Stream 操做是延迟执行的,这意味着他们会等到须要结果的时候才去执行
- Stream 遵循
作什么,而不是怎么作
的原则,只须要描述作什么,而不用考虑程序是怎么实现的
private static void stream() { int[] arr = {4, 1, 2, 5, 0, 8, 6, 5}; // 获取最大值 final int max = Arrays.stream(arr).max().getAsInt(); System.out.println(max); // 数组中大于3的元素的数量 final long count = Arrays.stream(arr).filter(e -> e > 3).count(); System.out.println(count); List<Student> list = Arrays.asList( new Student(1, "花木兰", 25, 66.0), new Student(2, "李白", 21, 90.0), new Student(3, "诸葛亮", 21, 80.0), new Student(4, "公孙离", 18, 100d), new Student(5, "不知火舞", 21, 90d), new Student(5, "不知火舞", 21, 90d) ); list.stream().filter(e -> e.getScore() >= 90) .findFirst() .ifPresent(System.out::println); Console.log("-----------filter--------------"); list.stream().skip(1).limit(2).forEach(System.out::println); Console.log("------------limit-------------"); list.stream().skip(3).distinct().forEach(System.out::println); Console.log("-------------distinct------------"); list.stream().mapToInt(Student::getAge).min().ifPresent(System.out::println); Console.log("-------------map------------"); final Set<String> collect = list.stream().map(Student::getName) .collect(Collectors.toSet()); System.out.println(collect); Console.log("-------------collect------------"); final List<Student> collect1 = list.stream() .sorted(Comparator.comparingDouble(Student::getScore).reversed() .thenComparing(Student::getAge) .thenComparing(Student::getId)) .collect(Collectors.toList()); System.out.println(collect1); Console.log("----------sort---------------"); final boolean b = list.stream().allMatch(e -> e.getAge() < 25); Assert.isFalse(b); // 正则匹配 final boolean b1 = list.stream() .anyMatch(e -> ReUtil.isMatch("^[1-9]\\d*$", e.getId().toString())); Assert.isTrue(b1); final boolean b2 = list.stream().noneMatch(e -> e.getScore() == 100d); Assert.isFalse(b2); Console.log("----------------match------------------"); list.stream().mapToInt(Student::getAge) .reduce(Integer::sum) .ifPresent(System.out::println); Console.log("----------------reduce------------------"); final DoubleSummaryStatistics collect2 = list.stream() .collect(Collectors.summarizingDouble(Student::getScore)); Console.log(collect2); }
3. 接口默认方法
Java 8 新增了接口的默认方法。
简单说,默认方法就是接口能够有实现方法,并且不须要实现类去实现其方法。
只需在方法名前面加个 default 关键字便可实现默认方法。
为何要有这个特性?
首先,以前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当须要修改接口时候,须要修改所有实现该接口的类,目前的 java 8 以前的集合框架没有 foreach 方法,一般能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是无法在给接口添加新方法的同时不影响已有的实现。因此引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
interface Cat { /** * 接口默认方法 */ default void eat() { System.out.println("一只小猫爱吃鱼"); } /** * Java 8 的另外一个特性是接口能够声明(而且能够提供实现)静态方法。 */ static void voice() { System.out.println("一只小猫喵喵叫"); } } interface Dog { default void eat() { System.out.println("一只小狗啃骨头"); } } class Animal implements Cat, Dog{ @Override public void eat() { // 第一种使用默认实现 Dog.super.eat(); // 第二种本身实现 System.out.println("我是一只新动物"); // 调用接口的静态方法 Cat.voice(); } }
4.日期时间处理
Java 8经过发布新的Date-Time API (JSR 310)来进一步增强对日期与时间的处理。
在旧版的 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类,但他们一样存在上述全部的问题。
新的日期/时间API的一些设计原则是:
- 不变性:新的日期/时间API中,全部的类都是不可变的,这对多线程有好处。
- 关注点分离:新的API将人可读的日期/时间和机器的日期/时间明确分离,它为日期Date、时间Time、日期时间DateTime、时间戳unix timestamp以及时区定义了不一样的类。
- 清晰:在全部的类中,方法都被明肯定义 用以完成相同的行为。举个例子,在全部的类中都定义了now()方法、format()方法、parse()方法,而不是像之前那样专门有一个独立的类。为了更好的处理问题,全部的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其余类协同工做并不困难。
- 实用操做:全部新的日期 时间 API 类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期时间中提取单独部分,等等。
- 可扩展性:新的日期/时间API是工做在ISO-801日历系统上的,但咱们也能够将其应用在非ISO的日历上。
LocalDate、LocalTime、LocalDateTime
private static void dateTime() { LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.now(); LocalDateTime localDateTime = LocalDateTime.now(); Console.log("当前日期是{}, 时间是{}, 日期时间{}", localDate, localTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")), localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); // 自定义 LocalDate date = LocalDate.of(2021, Month.AUGUST, 1); Console.log("自定义日期{}", date); // 设置地区 LocalDate seoulDate = LocalDate.now(ZoneId.of("Asia/Seoul")); LocalTime seoulTime = LocalTime.now(ZoneId.of("Asia/Seoul")); LocalDateTime seoul = LocalDateTime.now(ZoneOffset.of("+9")); Console.log("首尔日期{}, 首尔时间{}, 首尔日期时间{}", seoulDate, seoulTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")), seoul.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); // 时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现 在的总秒数。 Console.log("当前时间转毫秒数{}", localDateTime .toInstant(ZoneOffset.of("+8")).toEpochMilli()); Console.log("格林威治时间纳秒{}, 北京时间{}", Instant.now(), localDateTime); /** * 时间差值 */ Duration duration = Duration.between(localDateTime, seoul); System.out.println(duration); /** * 计算日期间隔 */ Period period = Period.between(date, seoulDate); System.out.println(period); }
5.Optional类
从 Java 8 引入的一个颇有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException)这个异常就很少说了,确定是每一个 Java 程序员都很是了解的异常。Optional 的完整路径是 java.util.Optional,使用它是为了不代码中的 if (obj != null) { } 这样范式的代码,能够采用链式编程的风格。并且经过 Optional 中提供的 filter 方法能够判断对象是否符合条件,在符合条件的状况下才会返回,map 方法能够在返回对象前修改对象中的属性。
Optional的用处
本质上,Optional是一个包含有可选值的包装类,这意味着 Optional 类既能够含有对象也能够为空。咱们要知道,Optional 是 Java 实现函数式编程的强劲一步,而且帮助在范式中实现。可是 Optional 的意义显然不止于此。咱们知道,任何访问对象方法或属性的调用均可能致使 NullPointerException,在这里,我举个简单的例子来讲明一下:
String result = test.getName().getTime().getNum().getAnswer();
在上面的这个代码中,若是咱们须要确保不触发异常,就得在访问每个值以前对其进行明确地检查,就是使用if else对test等值进行判断是否为null,这很容易就变得冗长,难以维护。为了简化这个过程,Google公司著名的Guava项目引入了Optional类,Guava经过使用检查空值的方式来防止代码污染,并鼓励程序员写更干净的代码。Optional其实是个容器:它能够保存类型T的值,或者仅仅保存null。Optional提供不少有用的方法,这样咱们就不用显式进行空值检测。
Optional 的构造函数
Optional 的三种构造方式:Optional.of(obj), Optional.ofNullable(obj) 和明确的 Optional.empty()
- Optional.of(obj):它要求传入的 obj 不能是 null 值的, 不然直接报NullPointerException 异常。
- Optional.ofNullable(obj):它以一种智能的,宽容的方式来构造一个 Optional 实例。来者不拒,传 null 进到就获得 Optional.empty(),非 null 就调用 Optional.of(obj).
- Optional.empty():返回一个空的 Optional 对象
Optional 的经常使用函数
- isPresent:若是值存在返回true,不然返回false。
- ifPresent:若是Optional实例有值则为其调用consumer,不然不作处理
- get:若是Optional有值则将其返回,不然抛出NoSuchElementException。所以也不常常用。
- orElse:若是有值则将其返回,不然返回指定的其它值。
- orElseGet:orElseGet与orElse方法相似,区别在于获得的默认值。orElse方法将传入的字符串做为默认值,orElseGet方法能够接受Supplier接口的实现用来生成默认值
- orElseThrow:若是有值则将其返回,不然抛出supplier接口建立的异常。
- filter:若是有值而且知足断言条件返回包含该值的Optional,不然返回空Optional。
- map:若是有值,则对其执行调用mapping函数获得返回值。若是返回值不为null,则建立包含mapping返回值的Optional做为map方法返回值,不然返回空Optional。
- flatMap:若是有值,为其执行mapping函数返回Optional类型返回值,不然返回空Optional。
Optional 应该怎样用
在使用 Optional 的时候须要考虑一些事情,以决定何时怎样使用它。重要的一点是 Optional 不是 Serializable。所以,它不该该用做类的字段。Optional 类能够将其与流或其它返回 Optional 的方法结合,以构建流畅的API。咱们来看一个示例,咱们不使用Optional写代码是这样的:
public String getName(User user) { if (user == null) { return ""; } return user.getName(); }
接着咱们来改造一下上面的代码,使用Optional来改造,咱们先来举一个Optional滥用,没有达到流畅的链式API,反而复杂的例子,以下:
public String getName(User user) { Optional<User> u = Optional.ofNullable(user); if(!u.isPresent()) { return ""; } return u.get().getName(); }
这样改写非但不简洁,并且其操做仍是和第一段代码同样。无非就是用isPresent方法来替代原先user==null。这样的改写并非Optional正确的用法,咱们再来改写一次。
public String getName(User user) { return Optional.ofNullable(user) .map(u -> u.getName()) .orElse(""); }
这样才是正确使用Optional的姿式。那么按照这种思路,咱们能够安心的进行链式调用,而不是一层层判断了。
6.Base64
在Java8中,Base64编码已经成为Java类库的标准。
Java 8 内置了 Base64 编码的编码器和解码器。
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
· 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
· URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
· MIME:输出隐射到MIME友好格式。输出每行不超过76字符,而且使用'\r'并跟随'\n'做为分割。编码输出最后没有行分割。
public static void main(String[] args) { //编码,加密getEncoder() String str = Base64.getEncoder() .encodeToString("java8_Base64?".getBytes(StandardCharsets.UTF_8)); Console.log("标准加密以后的字符串是 {}", str); //解码,解密getDecoder() byte[] decode = Base64.getDecoder().decode(str); Console.log("标准解码 {}", new String(decode, StandardCharsets.UTF_8)); //URL编码 str = Base64.getUrlEncoder() .encodeToString("dksiofdo+/d,s;".getBytes(StandardCharsets.UTF_8)); Console.log("加密后字符串是:{}", str); //URL解码 byte[] decode1 = Base64.getUrlDecoder().decode(str); Console.log("URL解码后 {}", new String(decode1, StandardCharsets.UTF_8)); //Mime编码 str = Base64.getMimeEncoder() .encodeToString("dksiofdo+/d,s;ddd".getBytes(StandardCharsets.UTF_8)); Console.log("加密后字符串是:{}", str); //Mime解码 byte[] decode2 = Base64.getMimeDecoder().decode(str); Console.log("Mime解码后 {}", new String(decode2, StandardCharsets.UTF_8)); /* Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator): 返回具备给定lineLength的已修改MIME变体的编码器 (向下舍入到最接近的4的倍数 - 输出在lineLength<= 0 时不分红行)和lineSeparator。 当lineSeparator包含RFC 2045的表1中列出的任何Base64字母字符时,它会抛出 java.lang.IllegalArgumentException。 至关于用lineSeparator隔开加密后的字符串,每lineLength(4的倍数向下取整)隔开 */ String s = Base64.getMimeEncoder(6, ".?--".getBytes()) .encodeToString("jimidssafsaa".getBytes(StandardCharsets.UTF_8)); Console.log("加密后 {}", s); byte[] decode3 = Base64.getMimeDecoder().decode(s); Console.log("Mime解码后 {}", new String(decode3, StandardCharsets.UTF_8)); }
JDK11新特性
1.类型推断
private static void jdk11() { var s = "world"; var list = new ArrayList<String>(); list.add(s); list.add("java"); list.add("python"); list.stream().map(e -> "Hello, " + e) .forEach(System.out::println); }
局部变量类型推断就是左边的类型直接使用 var 定义,而不用写具体的类型,编译器能根据右边的表达式自动推断类型。
2. 字符串增强
String新增了strip()方法,和trim()相比,strip()能够去掉Unicode空格,例如,中文空格:
// 判断字符串是否为空白 " ".isBlank(); // true // 去除首尾空格 " Javastack ".strip(); // "Javastack" // 去除尾部空格 " Javastack ".stripTrailing(); // " Javastack" // 去除首部空格 " Javastack ".stripLeading(); // "Javastack " // 复制字符串 "Java".repeat(3);// "JavaJavaJava" // 行数统计 "A\nB\nC".lines().count(); // 3
3. 集合增强
自 Java 9 开始,Jdk 里面为集合(List/ Set/ Map)都添加了 of 和 copyOf 方法,它们两个都用来建立不可变的集合,来看下它们的使用和区别。
var list = List.of("Java", "Python", "C"); var copy = List.copyOf(list); System.out.println(list == copy); // true
var list = new ArrayList<String>(); var copy = List.copyOf(list); System.out.println(list == copy); // false
static <E> List<E> of(E... elements) { switch (elements.length) { // implicit null check of elements case 0: return ImmutableCollections.emptyList(); case 1: return new ImmutableCollections.List12<>(elements[0]); case 2: return new ImmutableCollections.List12<>(elements[0], elements[1]); default: return new ImmutableCollections.ListN<>(elements); } } static <E> List<E> copyOf(Collection<? extends E> coll) { return ImmutableCollections.listCopy(coll); } static <E> List<E> listCopy(Collection<? extends E> coll) { if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) { return (List<E>)coll; } else { return (List<E>)List.of(coll.toArray()); } }
能够看出 copyOf 方法会先判断来源集合是否是 AbstractImmutableList 类型的,若是是,就直接返回,若是不是,则调用 of 建立一个新的集合。
示例2由于用的 new 建立的集合,不属于不可变 AbstractImmutableList 类的子类,因此 copyOf 方法又建立了一个新的实例,因此为false.
注意:使用of和copyOf建立的集合为不可变集合,不能进行添加、删除、替换、排序等操做,否则会报 java.lang.UnsupportedOperationException 异常。
上面演示了 List 的 of 和 copyOf 方法,Set 和 Map 接口都有。
4.Stream流处理增强
private static void jdk11() { var s = "world"; var list = new ArrayList<String>(); list.add(s); list.add("java"); list.add("python"); list.add("go"); list.stream().map(e -> "Hello, " + e) .forEach(System.out::println); /** * lambda表达式体为 true 打印,遇到 false则再也不继续 */ list.stream().takeWhile(e -> !StrUtil.startWith(e, "p")) .forEach(System.out::println); System.out.println("--------------------------------"); /** * lambda表达式体为true不打印,一直到遇到false开始打印 */ list.stream().dropWhile(e -> !StrUtil.startWith(e, "p")) .forEach(System.out::println); }
参考:https://www.jianshu.com/p/84a6050c5391
参考:https://blog.csdn.net/csdnnews/article/details/105236653