JDK8新特性
目录1、总览
- 接口的默认方法和静态方法
- 函数式接口
- Lambda表达式
- 方法和构造器的引用
- Stream类
- Optional类
- 新的日期API
- 注解相关的改变
- 使用Base64
代码地址:https://gitee.com/kouao/jdk8-features
2、接口中的默认方法和静态方法(Default Methods for Interfaces)
Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。
默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求,就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个default关键字即可实现默认方法。
为什么要有这个特性?
以前当需要修改接口的时候,需要修改全部实现该接口的类。而引进的默认方法的目的是为了解决接口的修改与现有的实现不兼容的问题。
示例
-
接口IFormula
/** * @author kouhao */ public interface IFormula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }
-
类Client
/** * @author kouhao */ public class Client { public static void main(String[] args) { IFormula formula = new IFormula() { @Override public double calculate(int a) { return a * a; } }; System.out.println(formula.calculate(2)); } }
-
输出
4.0
3、函数式接口
什么是函数式接口?
- 只包含一个抽象方法的接口,称为函数式接口。
- 你可以通过 Lambda 表达式来创建该接口的对象。
- 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
- 在java.util.function包下定义了Java 8 的丰富的函数式接口
- 函数式接口配合Lambda 表达式使用
示例
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
.....
}
四大内置核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Function<T,R> 函数型接口 |
T | R | 对类型为T的对象应用操作,并返回结果。 结果是R类型的对象。 包含方法:R apply(T t) |
Supplier 供给型接口 |
无 | T | 返回类型为T的对象, 包含方法:T get() |
Consumer 消费型接口 |
T | 无 | 对类型为T的对象应用操作 包含方法:void accept(T t) |
Predicate 断定型接口 |
T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean值。 包含方法:boolean test(T t) |
自定义函数式接口
-
自定义函数式接口
/** * 自定义函数式接口 * * @author kouhao */ @FunctionalInterface public interface IConverter<T, R> { R convert(T from); }
@FunctionalInterface注解主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口的定义时,编译器会报错。
-
Client类
/** * @author kouhao */ public class Client { public static void main(String[] args) { IConverter<Integer, String> converter = from -> String.valueOf(from); System.out.println(converter.convert(2)); } }
4、Lambda表达式
概述
Lambda 表达式也可称为闭包,是推动 Java 8 发布的最重要新特性。
Lambda表达式实质上是一个匿名方法,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)或者把代码看成数据。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
在最简单的形式中,一个Lambda表达式可以由:用逗号分隔的参数列表、->符号、函数体三部分组成,在某些情况下lambda的函数体会更加复杂,这时可以把函数体放到在一对花括号中,就像在Java中定义普通函数一样,Lambda可以引用类的成员变量与局部变量(如果这些变量不是final的话,它们会被隐含的转为final,这样效率更高)。
Lambda可能会返回一个值,返回值的类型也是由编译器推测出来的,如果lambda的函数体只有一行的话,那么没有必要显式使用return语句。
lamdba表达式只能针对函数式接口进行操作,进行简化。只包含一个抽象方法的接口,称为函数式接口。
lambda表达式具体语法格式
-
语法格式一:无参,无返回值。
Runnable r1 = () ->
-
语法格式二:Lambda需要一个参数,但是没有返回值。
Consumer
con = (String str) -> -
语法格式三:数据类型可以省略,因为可由编译器推断出,成为类型推断。
Consumer
con = (String str) -> -
语法格式四:Lambda需要一个参数,但是没有返回值。
Consumer
con = str -> -
语法格式五:Lambda需要两个或者以上的参数,多条执行语句,并且可以有返回值。
Comparator
com = (x,y) -> { System.out.println("实现函数式接口方法");
return Integer.compare(x,y);
}
-
语法格式六:Lambda只有一条语句时,return与大括号若有,都可以省略。
Consumer
con = (x,y) -> return Integer.compare(x,y);
类型推断
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。
Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。
Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
5、方法引用
概述
- 当要传递给Lambda体的操作,已经有实现的方法,可以使用方法引用!
- 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
- 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
- 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
- 如下三种主要使用情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
举例
-
对象::实例方法名
package org.kouhao.jdk8.features.method_references; import lombok.AllArgsConstructor; import lombok.Data; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.util.function.Supplier; /** * 对象::实例方法名 使用例子 */ public class ObjectInstanceMethodNameDemoTest { static User user; @BeforeAll public static void before() { user = new User(1, "test", "123456"); } /** * 对象::实例方法名 正常使用匿名对象 */ @Test public void test_object_instance_method_name_normal() { Supplier<String> supplier = new Supplier<String>() { @Override public String get() { return user.getName(); } }; System.out.println(supplier.get()); } /** * 对象::实例方法名 使用lambda */ @Test public void test_object_instance_method_name_lambda() { Supplier<String> supplier = () -> user.getName(); System.out.println(supplier.get()); } /** * 对象::实例方法名 使用方法引用 */ @Test public void test_object_instance_method_name_methodReference() { Supplier<String> supplier = user::getName; System.out.println(supplier.get()); } } /** * User */ @Data @AllArgsConstructor class User { int id; String name; String adr; }
-
类::静态方法名
package org.kouhao.jdk8.features.method_references; import org.junit.jupiter.api.Test; import java.util.function.Function; /** * 类::静态方法名 * * @author kouhao */ public class ClassStaticMethodNameDemoTest { /** * 正常使用匿名对象 */ @Test public void test_class_static_method_name_normal() { Function<Double,Long> function = new Function<Double,Long>() { @Override public Long apply(Double dd) { return Math.round(dd); } }; System.out.println(function.apply(20.32)); } /** * 使用lambda */ @Test public void test_class_static_method_name_lambda() { Function<Double,Long> function = dd -> Math.round(dd); System.out.println(function.apply(20.32)); } /** * 使用方法引用 */ @Test public void test_class_static_method_name_method_reference() { Function<Double,Long> function = Math::round; System.out.println(function.apply(20.32)); } }
-
类::实例方法名
package org.kouhao.jdk8.features.method_references; import lombok.AllArgsConstructor; import lombok.Data; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.util.function.Function; /** * 类::实例方法名 * * @author kouhao */ public class ClassInstanceMethodNameTest { static User2 user; @BeforeAll public static void before() { user = new User2(1, "test", "123456"); } /** * 正常使用匿名对象 */ @Test public void test_class_instance_method_name_normal() { Function<User2, String> function = new Function<>() { @Override public String apply(User2 user) { return user.getName(); } }; System.out.println(function.apply(user)); } /** * 使用lambda */ @Test public void test_class_static_method_name_lambda() { Function<User2, String> function = user -> user.getName(); System.out.println(function.apply(user)); } /** * 使用方法引用 */ @Test public void test_class_static_method_name_method_reference() { Function<User2, String> function = User2::getName; System.out.println(function.apply(user)); } } /** * User */ @Data @AllArgsConstructor class User2 { int id; String name; String adr; }
6、构造器引用
概述
与函数式接口相结合,自动与函数式接口中方法兼容。 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
示例
package org.kouhao.jdk8.features.constructor.reference;
import org.junit.jupiter.api.Test;
/**
* 构造器引用
*
* @author kouhao
*/
public class ConstructorReferenceTest {
/**
* 匿名类方式实现
*/
@Test
public void test_normal() {
PersonFactory<Person> personFactory = new PersonFactory<>() {
@Override
public Person create(String firstName, String lastName) {
return new Person(firstName, lastName);
}
};
System.out.println(personFactory.create("kou", "hao").firstName);
}
/**
* lambda表达式
*/
@Test
public void test_lambda() {
PersonFactory<Person> personFactory = (firstName, lastName) -> new Person(firstName, lastName);
System.out.println(personFactory.create("kou", "hao").firstName);
}
/**
* 构造方法引用
*/
@Test
public void test_constructor_reference() {
PersonFactory<Person> personFactory = Person::new;
System.out.println(personFactory.create("kou", "hao").firstName);
}
}
interface PersonFactory<R extends Person> {
R create(String firstName, String lastName);
}
class Person {
String firstName;
String lastName;
Person() {
}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
7、Stream类
概述
- Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
- Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。 也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
Stream操作的三个步骤
- 创建 Stream :一个数据源(如:集合、数组),获取一个流
- 中间操作 :一个中间操作链,对数据源的数据进行处理
- 终止操作(终端操作) :一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
创建 Stream
-
通过集合方法创建Stream
public interface Collection<E> extends Iterable<E> { // 创建串行流 default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } // 创建并行流 default Stream<E> parallelStream() { return StreamSupport.stream(spliterator(), true); } }
-
通过数组工具类方法创建Stream
public class Arrays {
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}
public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
}
}
-
Stream.of() 方法创建Stream
Stream<Integer> stream = Stream.of(1,2,3);
-
创建无限流
public interface Stream<T> extends BaseStream<T, Stream<T>> { // 迭代 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f); // 生成 public static<T> Stream<T> generate(Supplier<? extends T> s); }
-
示例
package org.kouhao.jdk8.features.stream.create; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.function.Supplier; import java.util.stream.Stream; /** * 创建流 * * @author kouhao */ public class CreateStreamTest { /** * 通过集合创建stream */ @Test public void collection_stream_test() { ArrayList<String> list = new ArrayList<>() {{ add("1"); add("2"); add("3"); }}; System.out.println(list.stream().findFirst().orElse(null)); } /** * 通过数组工具创建stream */ @Test public void array_stream_test() { Stream<String> stream = Arrays.stream(new String[] {"1", "2", "3"}); System.out.println(stream.findFirst().orElse(null)); } /** * 通过Stream.of() */ @Test public void stream_of_test() { Stream<String> stream = Stream.of("1", "2", "3"); System.out.println(stream.findFirst().orElse(null)); } /** * 通过Stream.iterate() */ @Test public void stream_iterate_test() { Stream<String> stream = Stream.iterate("1", s -> s.length() != 10, s -> { s = s + "1"; System.out.println(s); return s; }); System.out.println(stream.count()); } /** * 通过Stream.generate() */ @Test public void stream_generate_test() { Stream<String> stream = Stream.generate(new Supplier<String>() { @Override public String get() { return "null"; } }).limit(10); System.out.println(stream.count()); } }
Stream中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
-
筛选与切片
方法 描述 filter(Predicate p) 接收 Lambda ,从流中排除某些元素 distinct() 筛选,通过流所生成元素的 hashCode()
和 equals() 去除重复元素limit(long maxSize) 截断流,使其元素不超过给定数量 skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回空 -
映射
方法 描述 map(Function f) 接收一个函数作为参数,该函数会被应用到每个元 素上,
并将其映射成一个新的元素。mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,
产生一个新的DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,
产生一个新的IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,
产生一个新的 LongStream。flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,
然后把所有流连接成一个流 -
排序
方法名 描述 sorted() 产生一个新流,其中按自然顺序排序 sorted(Comparator com) 产生一个新流,其中按比较器顺序排序 -
示例
package org.kouhao.jdk8.features.stream.intermediate.operation; import org.junit.jupiter.api.Test; import java.util.Comparator; import java.util.stream.IntStream; import java.util.stream.Stream; /** * 中间操作测试 * * @author kouhao */ public class IntermediateOperationTest { /** * 切片操作测试 */ @Test public void sectioning_test() { Stream<String> stream = Stream.of("1", "2", "3"); Integer data = stream.filter("1"::equals) .flatMapToInt(s -> IntStream.of(Integer.parseInt(s))) .findFirst() .orElse(0); System.out.println(data); } /** * Stream map操作 */ @Test public void map_test() { Stream<String> stream = Stream.of("1", "2", "3"); Stream<String> map = stream.map(s -> { if ("2".equals(s)) { return s; } else { return "0"; } }).distinct().sorted(Comparator.reverseOrder()); map.forEach(System.out::println); } }
Stream的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
注意:流进行了终止操作后,不能再次使用
-
匹配与查找
方法 描述 allMatch(Predicate p) 检查是否匹配所有元素 anyMatch(Predicate p) 检查是否至少匹配一个元素 noneMatch(Predicate p) 检查是否没有匹配所有元素 findFirst() 返回第一个元素 count() 返回流中元素总数 max(Comparator c) 返回流中最大值 min(Comparator c) 返回流中最小值 forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。
相反,Stream API 使用内部迭代——它帮你把迭代做了) -
规约
方法 描述 reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional -
收集
方法 描述 collect(Collector c ) 将流转换为其他形式。接收一个Collector接口的实现,
用于给Stream中元素做汇总的方法 -
示例
package org.kouhao.jdk8.features.stream.terminating.operations; import org.junit.jupiter.api.Test; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Stream 终结操作 * * @author kouhao */ public class TerminatingOperationsTest { /** * reduce 操作 */ @Test public void reduce_test() { Stream<String> stream = Stream.of("1", "2", "3"); Integer data = stream.parallel().map(Integer::parseInt).reduce(Integer::sum).get(); System.out.println(data); } /** * collect 操作 */ @Test public void collect_test() { Stream<Integer> stream = Stream.of(1, 2, 3, 10, 20); List<Integer> list = stream.filter(integer -> integer >= 3).collect(Collectors.toList()); System.out.println(list); } }
8、Optional类
概述
1、到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。
2、Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不 存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
3、Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在 则isPresent()方法会返回true,调用get()方法会返回该对象。
常用方法
- Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
- 创建Optional类对象的方法:
- Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
- Optional.empty() : 创建一个空的 Optional 实例
- Optional.ofNullable(T t):t可以为null
- 判断Optional容器中是否包含对象:
- boolean isPresent() : 判断是否包含对象
- void ifPresent(Consumer<? super T> consumer) :如果有值,就执行Consumer 接口的实现代码,并且该值会作为参数传给它。
- 获取Optional容器的对象:
- T get(): 如果调用对象包含值,返回该值,否则抛异常
- T orElse(T other) :如果有值则将其返回,否则返回指定的other对象。
- T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由 Supplier接口实现提供的对象。
- T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
9、新的日期API
概述
- JDK8新日期API之前问题
- java.util.Date是线程不安全的,所有的日志类都是可变的,这是Java日期类最大的问题之一。
- 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
- 新API类
- Instant——它代表的是时间戳
- LocalDate——不包含具体时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。
- LocalTime——它代表的是不含日期的时间
- LocalDateTime——它包含了日期及时间,不过还是没有偏移信息或者说时区。
- ZonedDateTime——这是一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。
- DateTimeFormatter ----日期的解析及格式化
- 方法API
- of: 静态工厂方法。
- parse: 静态工厂方法,关注于解析。
- get: 获取某些东西的值。
- is: 检查某些东西的是否是true。
- with: 不可变的setter等价物。
- plus: 加一些量到某个对象。
- minus: 从某个对象减去一些量。
- to: 转换到另一个类型。
- at: 把这个对象与另一个对象组合起来,例如: date.atTime(time)
示例
-
Clock示例
@Test public void testClock() { // 获取一个时钟,该时钟使用最佳可用系统时钟返回当前瞬间,并使用默认时区转换为日期和时间。 // 此时钟基于最佳可用系统时钟。这可以使用System.currentTimeMillis(),或者更高分辨率的时钟(如果有的话)。 // 使用此方法将对默认时区的依赖项硬编码到应用程序中。建议避免这种情况,并尽可能使用特定的时区。 // 当您需要当前时刻而不需要日期或时间时,应该使用UTC时钟。 Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); System.out.println(new Date(millis)); try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } // 时间戳 Instant instant = clock.instant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); }
-
TimeZone示例
@Test public void testTimeZone() { Set<String> set = ZoneId.getAvailableZoneIds(); System.out.println(Arrays.toString(set.toArray())); ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules());// ZoneRules[currentStandardOffset=+01:00] System.out.println(zone2.getRules());// ZoneRules[currentStandardOffset=-03:00] }
-
LocalTime示例
/** * LocalTime */ @Test public void testLocalTime() { // 德国柏林 ZoneId zone1 = ZoneId.of("Europe/Berlin"); LocalTime now1 = LocalTime.now(zone1); ZoneId zone2 = ZoneId.of("Brazil/East"); LocalTime now2 = LocalTime.now(zone2); long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239 LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime time = LocalTime.parse("13:37", germanFormatter); System.out.println(time); // 13:37 }
-
LocalDate
/** * LocalDate */ @Test public void testLocalDate() { LocalDate today = LocalDate.now(); // 加 LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); System.out.println(tomorrow);// 2023-11-06 // 减 LocalDate yesterday = tomorrow.minusDays(2); System.out.println(yesterday);// 2023-11-04 LocalDate independenceDay = LocalDate.of(2023, Month.NOVEMBER, 5); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); System.out.println(dayOfWeek); // SUNDAY DateTimeFormatter germanFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24 }
-
LocalDateTime测试
/** * LocalDateTime测试 */ @Test public void testLocalDateTime() { LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER // 一天的分钟 long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439 // 设置时区信息 Instant instant = sylvester.atZone(ZoneId.systemDefault()).toInstant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CST 2014 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM dd, yyyy - HH:mm"); LocalDateTime parsed = LocalDateTime.parse("11 03, 2014 - 07:13", formatter); System.out.println(parsed);// 2014-11-03T07:13 String string = formatter.format(parsed); System.out.println(string); // 11 03, 2014 - 07:13 }
10、注解相关的改变
Java 8 中的注解允许重复。
11、并发相关
CompletableFuture
CompletableFuture实现接口
CompletionStage
与Future
。StampedLock
在JDK1.8中,提供StampedLock来实现读多写少的业务场景,比ReadWriteLock更加有性能。