JDK8是官方发布的一个大版本, 提供了很多新特性功能给开发者使用, 包含语言、编译器、库、工具和JVM
等方面的十多个新特性。 本文将介绍编码过程中常用的一些新特性。
一、Lambda 表达式
1.优点
- 简化匿名内部类的写法, 允许你以简洁的方式表示可传递给方法或存储在变量中的代码块 ,用更加简洁和表达性的语法来编写匿名函数,从而简化了对函数式接口的实现,使代码更加简洁紧凑。
- 提高了代码的可读性和可维护性,尤其是在处理集合、函数式编程等场景下。
当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法,代码如下:
public class Demo01LambdaIntro {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程任务执行!");
}
}).start();
}
}
Lambda表达式写法,代码如下:
借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到相同的效果。
public class Demo01LambdaIntro {
public static void main(String[] args) {
new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程
}
}
2.Lambda省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
(int a) -> {
return new Person();
}
//省略后
a -> new Person()
3.使用Lambda的前提条件
Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
- 方法的参数或局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法
4.实现原理
匿名内部类在编译的时候会有一个class文件
Lambda在程序运行的时候形成一个类
- 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
- 还会形成一个匿名内部类,实现接口,重写抽象方法
- 在接口的重写方法中会调用新生成的方法.
二、函数式接口
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
- 是只有一个抽象方法的接口,可以用 Lambda 表达式或方法引用来实现,通常与 Lambda 表达式配合使用, 被 @FunctionalInterface 注解标记,以明确标识函数式接口 。
例如,java.util.function.Consumer接口就是一个函数式接口,可以接受一个参数并执行某些操作,没有返回值。 - JDK 8 提供了一些内置的函数式接口,如 Predicate、Function、Consumer 等,方便进行函数式编程,提高代码的灵活性。
三、方法引用
1.是 Lambda 表达式的一种简化形式, 它可以直接引用已有方法来创建函数式接口的实例 ,进一步简化代码。使代码更加直观,易于理解。
例如,可以使用 “类名::方法名” 的形式来引用静态方法,或者 “对象::方法名” 的形式来引用实例方法。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);
2.有不同的引用类型 , 如静态方法引用、实例方法引用、特定类型的实例方法引用和构造方法引用等。
常见引用方式
方法引用在JDK 8中使用方式相当灵活,有以下几种形式:
- instanceName::methodName 对象::方法名
- ClassName::staticMethodName 类名::静态方法
- ClassName::methodName 类名::普通方法
- ClassName::new 类名::new 调用的构造器
- TypeName[]::new String[]::new 调用数组的构造器
四、Stream流
1.概述
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工
处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
2.优点
1.提供了一种对集合数据进行高效操作的方式,可以进行过滤、映射、排序、聚合等操作, 可以更简洁、高效地处理数据;Stream API 的引入让集合操作变得更加简洁和易于并行处理 。
//例如:计算一个整数列表中所有偶数的平方和:
List<Integer> numbers = Arrays.asList(8, 7, 12, 24, 15, 33);
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.reduce(0, Integer::sum);
2.可以实现并行处理,提高处理大数据集的效率;支持链式调用,将多个操作连接在一起,形成一个数据处理管道 。
3.获取Stream流的两种方式
有以下几种常用的方式:
- 所有的 Collection 集合都可以通过 stream 默认方法获取流;
- Stream 接口的静态方法 of 可以获取数组对应的流。
1) 根据Collection获取流
首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
// 集合获取流
// Collection接口中的方法: default Stream<E> stream() 获取流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Vector<String> vector = new Vector<>();
Stream<String> stream3 = vector.stream();
java.util.Map 接口不是 Collection 的子接口,所以获取对应的流需要分key、value或entry等情况
// Map获取流
Map<String, String> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
2) Stream中的静态方法of获取流
由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单
// Stream中的静态方法: static Stream of(T... values)
Stream<String> stream6 = Stream.of("aa", "bb", "cc");
String[] arr = {"aa", "bb", "cc"};
Stream<String> stream7 = Stream.of(arr);
Integer[] arr2 = {11, 22, 33};
Stream<Integer> stream8 = Stream.of(arr2);
// 注意:基本数据类型的数组不行
int[] arr3 = {11, 22, 33};
Stream<int[]> stream9 = Stream.of(arr3);
备注: of 方法的参数其实是一个可变参数,所以支持数组
4.Stream常用方法
Stream流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
- 中间操作,如 filter(过滤)、map(映射)、sorted(排序)等,这些操作会返回一个新的 Stream,允许进行进一步的操作。
- 终端操作,如 forEach(遍历)、count(计数)、reduce(归约)等,这些操作会触发 Stream 的执行,并返回一个结果。
方法名 | 方法作用 | 返回值类型 | 方法种类 |
---|---|---|---|
filter | 过滤 | Stream | 中间操作 |
limit | 取用前几个 | Stream | 中间操作 |
skip | 跳过前几个 | Stream | 中间操作 |
map | 映射 | Stream | 中间操作 |
concat | 组合 | Stream | 中间操作 |
count | 计数 | long | 终端操作 |
forEach | 遍历 | void | 终端操作 |
Stream注意事项:
- Stream只能操作一次
- Stream方法返回的是新的流
- Stream不调用终结方法,中间的操作不会执行
收集Stream流中的结果
- 到集合中: Collectors.toList()/Collectors.toSet()/Collectors.toCollection()
- 到数组中: toArray()/toArray(int[]::new)
- 聚合计算:Collectors.maxBy/Collectors.minBy/Collectors.counting/Collectors.summingInt/Collectors.averagingInt
- 分组: Collectors.groupingBy
- 分区: Collectors.partitionBy
- 拼接: Collectors.joinging
五、新的日期和时间 API
1.旧版日期时间 API 存在的问题
- 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此外用于格式化和解析的类在java.text包中定义。
- 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
- 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
2.新的日期和时间API
1)日期和时间类
LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用 ISO-8601 日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
// LocalDate:获取日期时间的信息。格式为 2019-10-16
// 创建指定日期
LocalDate fj = LocalDate.of(1985, 9, 23);
System.out.println("fj = " + fj); // 1985-09-23
// 得到当前日期
LocalDate nowDate = LocalDate.now();
System.out.println("nowDate = " + nowDate); // 2019-10-16
// 获取日期信息
System.out.println("年: " + nowDate.getYear());
System.out.println("月: " + nowDate.getMonthValue());
System.out.println("日: " + nowDate.getDayOfMonth());
System.out.println("星期: " + nowDate.getDayOfWeek());
// LocalTime类: 获取时间信息。格式为 16:38:54.158549300
// 得到指定的时间
LocalTime time = LocalTime.of(12,15, 28, 129_900_000);
System.out.println("time = " + time);
// 得到当前时间
LocalTime nowTime = LocalTime.now();
System.out.println("nowTime = " + nowTime);
// 获取时间信息
System.out.println("小时: " + nowTime.getHour());
System.out.println("分钟: " + nowTime.getMinute());
System.out.println("秒: " + nowTime.getSecond());
System.out.println("纳秒: " + nowTime.getNano());
// LocalDateTime类: 获取日期时间信息。格式为 2018-09-06T15:33:56.750
LocalDateTime fj = LocalDateTime.of(1985, 9, 23, 9, 10, 20); System.out.println("fj = " + fj); // 1985-09-23T09:10:20
// 得到当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // 2019-10-16T16:42:24.497896800
System.out.println(now.getYear());
System.out.println(now.getMonthValue());
System.out.println(now.getDayOfMonth());
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
System.out.println(now.getNano());
对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。
// LocalDateTime类: 对日期时间的修改
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
// 修改日期时间
LocalDateTime setYear = now.withYear(2078);
System.out.println("修改年份: " + setYear);
System.out.println("修改月份: " + now.withMonth(6));
System.out.println("修改小时: " + now.withHour(9));
System.out.println("修改分钟: " + now.withMinute(11));
// 再当前对象的基础上加上或减去指定的时间
LocalDateTime localDateTime = now.plusDays(5);
System.out.println("5天后: " + localDateTime);
System.out.println("10年后: " + now.plusYears(10));
System.out.println("20月后: " + now.plusMonths(20));
System.out.println("20年前: " + now.minusYears(20));
System.out.println("5月前: " + now.minusMonths(5));
System.out.println("100天前: " + now.minusDays(100));
日期时间的比较
// 日期时间的比较
// 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2018, 8, 8);
System.out.println(now.isBefore(date)); // false
System.out.println(now.isAfter(date)); // true
2)时间格式化与解析
通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。
// 日期格式化
// 得到当前日期时间
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 将日期时间格式化为字符串
String format = now.format(formatter);
System.out.println("format = " + format);
// 将字符串解析为日期时间
LocalDateTime parse = LocalDateTime.parse("1985-09-23 10:12:22", formatter); System.out.println("parse = " + parse);
3)Instant 类
Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
// 时间戳
Instant now = Instant.now(); System.out.println("当前时间戳 = " + now);
// 获取从1970年1月1日 00:00:00的秒
System.out.println(now.getNano());
System.out.println(now.getEpochSecond());
System.out.println(now.toEpochMilli()); System.out.println(System.currentTimeMillis());
Instant instant = Instant.ofEpochSecond(5);
System.out.println(instant);
4)计算日期时间差类
Duration/Period类: 计算日期时间差。
- Duration:用于计算2个时间(LocalTime,时分秒)的距离
- Period:用于计算2个日期(LocalDate,年月日)的距离
// Duration/Period类: 计算日期时间差
// Duration计算时间的距离
LocalTime now = LocalTime.now();
LocalTime time = LocalTime.of(14, 15, 20);
Duration duration = Duration.between(time, now);
System.out.println("相差的天数:" + duration.toDays());
System.out.println("相差的小时数:" + duration.toHours());
System.out.println("相差的分钟数:" + duration.toMinutes());
System.out.println("相差的秒数:" + duration.toSeconds());
// Period计算日期的距离
LocalDate nowDate = LocalDate.now();
LocalDate date = LocalDate.of(1998, 8, 8);
// 让后面的时间减去前面的时间
Period period = Period.between(date, nowDate);
System.out.println("相差的年:" + period.getYears());
System.out.println("相差的月:" + period.getMonths());
System.out.println("相差的天:" + period.getDays());
5)时间校正器
有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
- TemporalAdjuster : 时间校正器。
- TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
// TemporalAdjuster类:自定义调整时间
LocalDateTime now = LocalDateTime.now();
// 得到下一个月的第一天
TemporalAdjuster firsWeekDayOfNextMonth = temporal -> {
LocalDateTime dateTime = (LocalDateTime) temporal;
LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
System.out.println("nextMonth = " + nextMonth);
return nextMonth;
};
LocalDateTime nextMonth = now.with(firsWeekDayOfNextMonth); System.out.println("nextMonth = " + nextMonth);
6)设置日期时间的时区
Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。
其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。
ZoneId:该类中包含了所有的时区信息。
// 设置日期时间的时区
// 1.获取所有的时区ID
ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 不带时间,获取计算机的当前时间
LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时
System.out.println("now = " + now);
// 2.操作带时区的类
// now(Clock.systemUTC()): 创建世界标准时间
ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
System.out.println("bz = " + bz);
// now(): 使用计算机的默认的时区,创建日期时间
ZonedDateTime now1 = ZonedDateTime.now();
System.out.println("now1 = " + now1); // 2019-10-19T16:19:44.007153500+08:00[Asia/Shanghai]
// 使用指定的时区创建日期时间
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver")); System.out.println("now2 = " + now2); // 2019-10-19T01:21:44.248794200-07:00[America/Vancouver]
3.优点
- 新版的日期和时间API中,日期和时间对象是不可变的。操纵的日期不会影响老值,而是新生成一个实例。
- 新的API提供了两种不同的时间表示方式,有效地区分了人和机器的不同需求。
- TemporalAdjuster可以更精确的操纵日期,还可以自定义日期调整器。
- 是线程安全的。
- 提供了更好的时区支持和日期时间计算功能, 可以轻松地在不同时区之间进行转换 。
六、 接口的默认方法和静态方法
JDK 8以前的接口:
interface 接口名 { 静态常量;
抽象方法;
}
JDK 8对接口的增强,接口还可以有默认方法和静态方法
JDK 8的接口:
interface 接口名 { 静态常量;
抽象方法;
默认方法;
静态方法;
}
在接口中,JDK 8 允许定义默认方法和静态方法,实现了接口的类可以选择继承或重写这些方法,这使得在不破坏现有代码的情况下可以向接口中添加新的方法,方便进行库的升级和扩展。
默认方法可以在接口中提供实现,而不必在实现类中进行实现,这对于接口的向后兼容性非常重要。
1.默认方法
默认方法:接口中可以定义带有实现的方法,称为默认方法(Default Methods)。这使得在不破坏现有实现的情况下,可以向接口中添加新的方法。子类可以选择继承默认方法的实现,或者覆盖默认方法。
接口默认方法的定义格式:
interface 接口名 {
修饰符 default 返回值类型 方法名() {
代码;
}
}
2.静态方法
静态方法:接口中可以定义静态方法,类似于类中的静态方法,可以直接通过接口名调用。
接口静态方法的定义格式:
interface 接口名 {
修饰符 static 返回值类型 方法名() {
代码;
}
}
3.接口默认方法和静态方法的区别
- 默认方法通过实例调用,静态方法通过接口名调用。
- 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
- 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用。
//定义一个接口和其默认方法:
interface MyInterface {
void method();
default void defaultMethod() {
System.out.println("This is a default method.");
}
static void staticMethod() {
System.out.println("This is a static method.");
}
}
class MyClass implements MyInterface {
// 继承了 defaultMethod() 的实现
}
MyClass obj = new MyClass();
obj.defaultMethod(); // 输出: Default method
MyInterface.staticMethod(); // 输出: Static method
七、Optional 类
以前对null的处理方式
String userName = "凤姐";
// String userName = null;
if (userName != null) {
System.out.println("用户名为:" + userName);
} else {
System.out.println("用户名不存在");
}
1.Optional类介绍
Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。
提供了orElse,ifPresent,ifPresentOrElse,map等方法避免对null的判断,写出更加优雅的代码。
2.Optional的基本使用
Optional类的创建方式:
Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
Optional类的常用方法:
isPresent() : 判断是否包含值,包含值返回true,不包含值返回false
get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException
orElse(T t) : 如果调用对象包含值,返回该值,否则返回参数t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
// Optional<String> userNameO = Optional.of("凤姐");
// Optional<String> userNameO = Optional.of(null);
// Optional<String> userNameO = Optional.ofNullable(null);
Optional<String> userNameO = Optional.empty();
// isPresent() : 判断是否包含值,包含值返回true,不包含值返回false。
if (userNameO.isPresent()) {
// get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException。
String userName = userNameO.get();
System.out.println("用户名为:" + userName);
} else {
System.out.println("用户名不存在");
}
八、重复注解与类型注解
1.重复注解的使用
自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解。
重复注解的使用步骤:
1.定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface
MyTests {
MyTest[] value();
}
2.定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface
MyTest {
String value();
}
3.配置多个重复的注解
@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")
public class Demo01 {
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
}
}
4.解析得到指定注解
@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")
public class Demo01 {
@Test
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
// 4.解析得到类上的指定注解
MyTest[] tests = Demo01.class.getAnnotationsByType(MyTest.class);
for (MyTest test : tests) {
System.out.println(test.value());
}
// 得到方法上的指定注解
Annotation[] tests1 = Demo01.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (Annotation annotation : tests1) {
System.out.println("annotation = " + annotation);
}
}
}
2.类型注解的使用
JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。
TYPE_USE :表示注解可以在任何用到类型的地方使用。
通过@Repeatable元注解可以定义可重复注解,TYPE_PARAMETER 可以让注解放在泛型上, TYPE_USE 可以让注解放在类型的前面
1) TYPE_PARAMETER的使用
@Target(ElementType.TYPE_PARAMETER)
@interface
TyptParam {
}
public class Demo02<@TyptParam T> {
public static void main( String[] args) {
}
public <@TyptParam E> void test( String a) {
}
}
2) TYPE_USE的使用
@Target(ElementType.TYPE_USE)
@interface
NotNull {
}
public class Demo02<@TyptParam T extends String> {
private @NotNull int a = 10;
public static void main(@NotNull String[] args) {
@NotNull int x = 1;
@NotNull String s = new @NotNull String();
}
public <@TyptParam E> void test( String a) {
}
}
九、Nashorn JavaScript 引擎
JDK 8 引入了 Nashorn,这是一个新的 JavaScript 引擎,用来替代旧的 Rhino 引擎。Nashorn 提供了更好的性能,并且与 JavaScript 语言的最新标准兼容。
import javax.script.*;
public class NashornExample {
public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello from Nashorn')");
}
}
十、并行数组操作
JDK 8 通过 Arrays.parallelSort() 方法引入了并行数组排序,这利用了多核处理器来加快排序过程。
int[] numbers = {9, 4, 8, 55, 22,46,72};
Arrays.parallelSort(numbers);
System.out.println(Arrays.toString(numbers));//输出 [4, 8, 9, 22, 46, 55, 72]
十一、Base64 编码和解码
Java 8的java.util套件中,新增了Base64的类别,可以用来处理Base64的编码与解码,用法如下:
final Base64.Decoder decoder = Base64.getDecoder();
final Base64.Encoder encoder = Base64.getEncoder();
final String text = "字串文字";
final byte[] textByte = text.getBytes("UTF-8");
//编码
final String encodedText = encoder.encodeToString(textByte); System.out.println(encodedText);
//解码
System.out.println(new String(decoder.decode(encodedText), "UTF-8"));