Java 8 新特性
一、Lambda 表达式
1、什么是 Lambda 表达式?
Lambda 本质是一个匿名函数,可以理解为一段可以传递的代码,像数据一样传输。
使用 Lambda 可以简化代码、使代码更紧凑、更灵活。
使用 Lambda 表达式前提 需要函数式接口支持。
注:
函数式接口:指的是 接口中只有一个抽象方法的接口。可以使用 @FunctionalInterface 注解去判断、检查是否为函数式接口。
@FunctionalInterface public interface Test { public void test(); // public void test2(); }
2、基础语法:
JDK 8 引入一个箭头操作符 “->”,或者叫 Lambda 操作符。
【格式:】 (参数列表) -> {Lambda 体}; 注: 参数列表指的是 方法的参数,即需要在 Lambda 体中执行的变量。 Lambda 体指的是 方法体,定义了需要执行的功能。 【语法:】 若出现一个参数的情况,可以将()省略。 若出现一行代码的情况,可以将{}省略。 对于多个参数的情况,可以省略参数类型(JVM 类型推断),但()不能省略。 若{}中只有一行代码,且为return语句,则可省略return 和 {}。
3、举例:
如下例,使用匿名函数 与 Lambda 的比较。
Lambda 可以省去很多代码,代码结构紧凑、简洁。
使用 Lambda 与使用 匿名函数类似, Lambda 体中即为 匿名函数重写的方法的方法体,参数列表为 匿名函数重写的方法的参数列表,使用时,直接调用方法名即可。
【举例:】 /** * 测试 Lambda 表达式用法 */ public class TestLambda { public static void main(String[] args) { String str = "helloworld"; // 使用 匿名函数 时 test(str, new TestLambdaFunction() { @Override public void test(String str) { System.out.println(str); } }); // 使用 Lambda 表达式,此方法等价于上面匿名函数的写法 // 只有一个参数,() 可省略,参数类型可省略(JVM自动进行类型推断), Lambda 体只有一行代码,{} 可省略。 test(str, x -> System.out.println(x)); } public static void test(String str, TestLambdaFunction testLambdaFunction) { testLambdaFunction.test(str); } } @FunctionalInterface interface TestLambdaFunction { void test(String str); }
4、核心函数式接口
使用 Lambda 表达式前提是为函数式接口,但是 每次都自定义函数式接口,很繁琐、蛋疼,所以 Java 8 内置了函数式接口,使用时套用即可。
【四大核心函数式接口:(其余函数式接口用法类似)】 Consumer<T> 消费型接口,有参数 T,没有返回值。 抽象方法: void accept(T t); Supplier<T> 供给型接口,没有参数,有返回值 T。 抽象方法: T get(); Function<T, R> 函数型接口,有参数 T,有返回值 R。 抽象方法:R apply(T var1); Predicate<T> 断言型接口,有参数 T,返回值为 boolean 型。 抽象方法: boolean test(T var1); 【举例:】 import java.util.Date; import java.util.function.Consumer; import java.util.function.Supplier; /** * 测试 Lambda 表达式用法 */ public class TestLambda { public static void main(String[] args) { String str = "helloworld"; // 测试 Consumer<T> 函数式接口,此处定义 Consumer<T> 函数式接口抽象方法的具体实现 testConsumer(str, x -> System.out.println(x)); // 测试 Supplier<T> 函数式接口,此处定义 Supplier<T> 函数式接口抽象方法的具体实现 testSupplier(() -> new Date()); } public static void testConsumer(String str, Consumer<String> consumer) { // 此处为 Consumer<T> 函数式接口抽象方法具体调用 consumer.accept(str); } public static void testSupplier(Supplier<Date> supplier) { // 此处为 Supplier<T> 函数式接口抽象方法具体调用 System.out.println(supplier.get()); } }
5、方法引用
若 Lambda 体中的内容在其他方法中已经实现,可以通过 方法引用 去引用相关代码,从而减少代码的冗余。方法引用可以理解为 Lambda 的另一种表现形式。
【格式:】 对象名::实例方法名 类名::静态方法名 类名::实例方法名 注: 方法引用 的方法与函数式接口中的方法 返回值类型 以及 参数类型要一致。 当 Lambda 参数列表中 第一个参数 是实例方法的调用者,第二个参数是 实例方法的参数时,才可以使用 类::实例方法名。 比如:String::equals. 【举例:】 import java.io.PrintStream; import java.util.function.Consumer; /** * 测试 Lambda 表达式用法 */ public class TestLambda { public static void main(String[] args) { Consumer<String> consumer0 = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; consumer0.accept("匿名函数"); // 一般 Lambda 表达式写法 Consumer<String> consumer1 = x -> System.out.println(x); consumer1.accept("hello"); PrintStream printStream = System.out; Consumer<String> consumer2 = x -> printStream.println(x); consumer2.accept("hello wrold"); // 使用 对象名::实例方法名 去改写 Lambda 表达式,本质与 Lambda 表达式一致 Consumer<String> consumer3 = printStream::println; consumer3.accept("world"); } }
6、构造器引用
与方法引用类似,此处是对构造器的引用,是 Lambda 针对构造器的一种写法。
【格式:】 类名::new 注: 构造器引用 的参数列表 与 函数式接口中方法的参数列表 要一致。 即通过方法参数去确认调用哪一个构造方法。 【举例:】 import java.util.function.Function; /** * 测试 Lambda 表达式用法 */ public class TestLambda { public static void main(String[] args) { // 普通 Lambda 表达式 Function<String, String> function = (x) -> new String(x).trim(); System.out.println(function.apply(" ddd hello ")); // Lambda 表达式 -- 构造器引用 Function<String, String> function1 = String::new; System.out.println(function1.apply(" hello ")); // 普通 Lambda 表达式,并创建一个对象实例,等价于 上面的构造器引用 Function<String, String> function2 = (x) -> new String(x); System.out.println(function2.apply(" hello ")); } }
7、数组引用
与方法引用类似,此处是对数组引用,是 Lambda 针对数组的一种写法。
【格式:】 数组类型[]::new 【举例:】 import java.util.function.Function; /** * 测试 Lambda 表达式用法 */ public class TestLambda { public static void main(String[] args) { // 一般 Lambda 创建一个数组的写法 Function<Integer, String[]> function = x -> new String[x]; System.out.println(function.apply(10).length); // 使用数组引用创建一个 数组的写法 Function<Integer, String[]> function1 = String[]::new; System.out.println(function1.apply(20).length); } }
二、Stream API
1、什么是 Stream API?
Stream API 是 JDK 8 中处理集合的抽象概念,可以对指定的集合进行复杂的操作,比如查找、过滤、映射数据等。
简单地理解:Stream API 提供了一种高效且易于使用的处理数据的方式。Stream 就是一个数据通道,一个数据 经过 该通道(一系列流水线操作)后得到一个新的数据。
Stream 不会存储数据、不会改变源对象(会创建一个新 Stream)、延迟操作。
2、Stream 操作步骤
Step1:创建流。
Step2:流水线操作(对流进行操作)。
Step3:终止流。
注:
第二步是不会立即执行的,等待第三步终止发生时,才开始执行。
第二步可以形成链式调用,其每个操作返回的都是一个流。
3、创建流的方式
要使用流,就得先创建一个流。
【方式一:】 通过 Collection 集合的 stream() 或者 parallelStream() 方法去创建集合流。 default Stream<E> stream() // 创建一个串行流(顺序流) default Stream<E> parallelStream() // 创建一个并行流 比如:(对 List 集合创建一个集合流) List<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); 【方式二:】 通过 Arrays 工具类的 stream() 方法去创建数组流。 public static <T> Stream<T> stream(T[] var0) 比如:(对 String 数组创建一个数组流) String[] strings = new String[10]; Stream<String> stream = Arrays.stream(strings); 【方式三:】 通过 Stream 类中的静态方法 of() 去创建。 static <T> Stream<T> of(T var0) 比如:(创建一个数组流) String[] strings = new String[10]; Stream<String> stream = Stream.of(strings); 【方式四:】 通过 Stream 类中的静态方法 iterate() 迭代创建无限流。 static <T> Stream<T> iterate(final T var0, final UnaryOperator<T> var1) 比如:(创建一个无限流,不断加 2) Stream<Integer> stream = Stream.iterate(0, x -> x + 2); 【方式五:】 通过 Stream 类中的静态方法 generate() 生成创建无限流。 static <T> Stream<T> generate(Supplier<T> var0) 比如:(创建一个无限流,随机生成 0 - 9 内的数) Stream<Integer> stream = Stream.generate(() -> (int)(Math.random() * 10)); 【举例:】 import java.util.Arrays; import java.util.List; public class TestStream { public static void main(String[] args) { // 创建一个集合流,Arrays.stream() 是创建流操作, forEach 是终止操作 String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; Arrays.stream(strings).forEach(System.out::println); // 创建一个集合流,list.stream() 是创建流操作 List<String> list = Arrays.asList(strings); list.stream().forEach(System.out::println); } }
4、流水线操作(操作流)
流创建之后,就需要对其进行操作。而多个操作可以连接起来,形成一个流水线。
每个操作结束,仍然返回一个 Stream 流对象,所以可以链式调用。
注:
除非流水线上触发了终止操作,否则流水线上不做任何处理,只有终止操作触发后,才开始流水线处理,即惰性求值。
筛选与切片操作:
【filter:】 通过 Stream 类中 filter() 方法进行过滤操作。根据 Lambda 表达式过滤掉部分数据。 Stream<T> filter(Predicate<? super T> var1); 比如:(对流处理,过滤出元素长度大于等于 3 的元素) String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; Arrays.stream(strings).filter(x -> x.length() >= 3 ); 【limit:】 通过 Stream 类中 limit(n) 方法进行截取 n 个元素。 Stream<T> limit(long var1); 比如:(对流处理,截取 2 个元素) String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; Arrays.stream(strings).limit(2); 【skip:】 通过 Stream 类中 skip(n) 方法进行跳过 n 个元素。 Stream<T> skip(long var1); 比如:(对流处理,跳过 2 个元素,与 limit 互补) String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; Arrays.stream(strings).skip(2); 【distinct:】 通过 Stream 类中 distinct() 方法进行元素去重。 Stream<T> distinct(); 注: 根据元素的 hashcode() 以及 equals() 进行判断,自定义类型需要进行 重写两个方法,否则可能不生效。 比如:(对流处理,对元素进行去重) String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; Arrays.stream(strings).distinct(); 【举例:】 import java.util.Arrays; public class TestStream { public static void main(String[] args) { // 创建一个集合流,Arrays.stream() 是创建流操作, forEach 是终止操作。 // filter()、distinct()、limit(n)、skip(n) 都是中间操作,可以链式调用 String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; System.out.println("==========过滤出元素长度大于 3 的元素=========="); Arrays.stream(strings).filter(x -> x.length() > 3).forEach(System.out::println); System.out.println("==========对元素进行去重,需要根据 hashcode、 equals 判断==========="); Arrays.stream(strings).distinct().forEach(System.out::println); System.out.println("==========截取前两个元素=============="); Arrays.stream(strings).limit(2).forEach(System.out::println); System.out.println("==========跳过前两个元素=============="); Arrays.stream(strings).skip(2).forEach(System.out::println); } }
映射操作:
【map:】 Stream 类中 map 方法接收一个 Lambda 表达式,并将其应用到每个元素上。 <R> Stream<R> map(Function<? super T, ? extends R> var1); 比如:(分别对每个元素进行处理,将元素转为大写) String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; Arrays.stream(strings).map(x -> x.toUpperCase()); 【flatMap:】 Stream 类中 flatMap 方法接受一个 Lambda 表达式,将其应用在每个元素上(每个元素都为流),最后将所有元素(流)组成一个流返回。 <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> var1); 比如:(将流中每个元素转为 字符,并将其转为流,最后再将流合并成一个大流) String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; Arrays.stream(strings).flatMap(str -> { List<Character> list = new ArrayList<>(); for(Character character : str.toCharArray()) { list.add(character); } return list.stream(); }); 【举例:】 import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class TestStream { public static void main(String[] args) { // 创建一个集合流,Arrays.stream() 是创建流操作, forEach 是终止操作。 // map()、flatMap() 都是中间操作,可以链式调用 String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; System.out.println("==========map 接收一个函数,并应用在每个元素上============="); Arrays.stream(strings).map(x -> x.toUpperCase()).forEach(System.out::println); System.out.println("==========flatMap 与 map 类似,但是其每处理一个元素都会让其转为 Stream 流,最后合并成一个 Stream =========="); Arrays.stream(strings).flatMap(x -> { List<String> list = new ArrayList<>(); list.add(x.toUpperCase()); return list.stream(); }).forEach(System.out::println); } }
排序操作:
【sorted:(自然排序)】 Stream 类中的 sorted 方法可以使用自然排序(实现 Comparable 接口)。 Stream<T> sorted(); 比如:(对数组进行自然排序) String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; Arrays.stream(strings).sorted(); 【sorted:(自定义排序)】 使用 Comparator 接口自定义排序规则。 Stream<T> sorted(Comparator<? super T> var1); 比如:(根据元素长度进行排序) String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; Arrays.stream(strings).sorted((x, y) -> x.length() - y.length()); 【举例:】 import java.util.Arrays; public class TestStream { public static void main(String[] args) { // 创建一个集合流,Arrays.stream() 是创建流操作, forEach 是终止操作。 // sorted() 是中间操作,可以链式调用 String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; System.out.println("=======自然排序,根据 Comparable 接口的 compareTo() 方法进行排序======="); Arrays.stream(strings).sorted().forEach(System.out::println); // 自定义排序,按元素长度降序排序 System.out.println("=======自定义排序,根据 Comparator 接口的 compare() 方法进行排序======="); Arrays.stream(strings).sorted((x, y) -> y.length() - x.length()).forEach(System.out::println); } }
5、终止操作
终止流水线操作。
查找与匹配操作:
【allMatch:】 检查是否匹配所有元素,全部匹配则返回 true。 boolean allMatch(Predicate<? super T> predicate); 【anyMatch:】 检查是否匹配至少一个元素,全不匹配则返回 false。 boolean anyMatch(Predicate<? super T> predicate); 【noneMatch:】 检查所有元素是否均不匹配,均不匹配则返回 true。 boolean noneMatch(Predicate<? super T> predicate); 【findFirst:】 返回第一个元素。 Optional<T> findFirst(); 【findAny:】 返回任意一个元素。 Optional<T> findAny(); 【count:】 返回流中元素的总个数。 long count(); 【max:】 根据自定义比较规则,找到流中最大值。 Optional<T> max(Comparator<? super T> comparator); 【min:】 根据自定义比较规则,找到流中最小值。 Optional<T> min(Comparator<? super T> comparator); 【forEach:】 遍历流中每个元素 void forEach(Consumer<? super T> action); 【举例:】 import java.util.Arrays; public class TestStream { public static void main(String[] args) { String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; System.out.println("=======判断流中每个元素长度是否大于 10,全都大于 10,则输出 true,否则为 false ========"); System.out.println(Arrays.stream(strings).allMatch(x -> x.length() > 10)); System.out.println("=======noneMatch 与 allMatch 相反,全都不大于 10,则输出 true======="); System.out.println(Arrays.stream(strings).noneMatch(x -> x.length() > 10)); System.out.println("=======输出第一个值==========="); System.out.println(Arrays.stream(strings).findFirst().get()); System.out.println("=======输出当前流中元素大于等于 3 的元素个数====================="); System.out.println(Arrays.stream(strings).filter(x -> x.length() >= 3).count()); }
归约操作:
【reduce:】 用于将流中的元素反复结合,并进行处理。 Optional<T> reduce(BinaryOperator<T> accumulator); T reduce(T identity, BinaryOperator<T> accumulator); 【举例:】 import java.util.Arrays; public class TestStream { public static void main(String[] args) { String[] strings = new String[]{"aa", "bbb", "ccccc", "aa", "ddd"}; System.out.println("======反复处理流中的元素,依次拼接元素======"); System.out.println(Arrays.stream(strings).reduce("", (x, y) -> x + y)); Integer[] integers = new Integer[]{10, 20, 10, 30}; System.out.println("======反复处理流中的元素,元素依次相加======"); System.out.println(Arrays.stream(integers).reduce((x, y) -> { System.out.println("====== x ==" + x + "======== y ==" + y + "======="); return x + y; }).get()); } }
收集操作:
【collect:】 收集流元素,通过 Collector 接口实现,将流元素转为其他形式。 <R, A> R collect(Collector<? super T, A, R> collector); 注: 通过 Collector 接口中的方法决定了如何对流进行收集操作(转为 set、list 等)。 Collectors 工具类提供了很多静态方法,去返回相应的 Collector 类型(可以很方便的收集数据)。 【举例:】 import java.util.*; import java.util.stream.Collectors; public class TestStream { public static void main(String[] args) { String[] strings = new String[]{"aa", "bbb", "ccccc"}; System.out.println("=====收集流中元素并转为 List 集合======"); List<String> list = Arrays.stream(strings).collect(Collectors.toList()); System.out.println(list); System.out.println("=====收集流中元素并转为 map ========"); // Map<Integer, String> map = Arrays.stream(strings).collect(Collectors.toMap(x -> x.length(), y -> y)); Map<Integer, String> map = Arrays.stream(strings).collect(Collectors.toMap(String::length, String::trim)); map.forEach((x, y) -> System.out.println("=== x == " + x + " === y ==" + y)); } }
三、日期
jdk8 提供了一系列线程安全的日期操作 API,这些 API 实例都是不可变对象(类似于 String)。
注:
String 不可变性,使用 final 修饰 类名,内部使用 final 修饰一个 char 数组用于保存字符串。
尽管 final 修饰引用类型,其数据可以修改,但是 String 内部并没有提供 修改 char 数组的方法,其方法 比如 trim()、replace() 等方法都是返回一个新的 String 对象(修改引用地址,不修改引用具体内容),从而保证对象的不可变性。
1、LocalDate、LocalTime、LocalDateTime(设置、获取日期)
这三个 API 用法类似。此处简单举个例,方法太多,知晓大概就行。
LocalDate 用于设置、获取日期。
LocalTime 用于设置、获取时间。
LocalDateTime 用于设置、获取日期和时间。
【举例:】 import java.time.LocalDate; import java.time.LocalDateTime; public class Test { public static void main(String[] args) { // 使用 now 方法可以获取当前系统时间 LocalDate localDate = LocalDate.now(); // 使用 of 方法可以自定义时间 LocalDateTime localDateTime = LocalDateTime.of(2020, 4, 24, 23, 00, 00); System.out.println(localDate); System.out.println(localDateTime); // 可以使用 plus 相关方法增加日期,使用 minus 相关方法可以减少日期(返回一个新的日期对象) localDateTime = localDateTime.plusHours(10); localDateTime = localDateTime.minusHours(5); System.out.println(localDateTime); // 可以使用 get 相关方法获取相应的年、月、日等 System.out.println(localDateTime.getYear() + " ===== " + localDateTime.getMonth()); } }
2、Instant(设置、获取时间戳)
与 LocalDate 等 API 区别为, Instant 可以用来操作时间戳。
Instant 默认使用 UTC 时区,即与北京时间相差 8 小时。
【举例:】 import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; public class Test { public static void main(String[] args) { // 使用 now 方法可以获取时间戳,默认为 UTC 时区,即与 北京时间 相差 8 小时 Instant instant = Instant.now(); System.out.println(instant); // 使用 atOffset 方法可以设置时间偏移量 OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8)); System.out.println(offsetDateTime); // 使用 toEpochMilli 可以获取时间戳(精确到毫秒) System.out.println(instant.toEpochMilli()); // 使用 getEpochSecond 可以获取时间戳(精确到秒) System.out.println(instant.getEpochSecond()); } }
3、Duration、Period(用于计算日期)
Duration 用于计算 时间 之间的间隔。
Period 用于计算 日期 之间的间隔。
【举例:】 import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.Period; public class Test { public static void main(String[] args) { LocalDateTime localDateTime = LocalDateTime.now(); LocalDateTime localDateTime2 = localDateTime.plusHours(5); // Duration 用于计算 时间 之间的间隔 Duration duration = Duration.between(localDateTime, localDateTime2); System.out.println(duration); LocalDate localDate = LocalDate.now(); LocalDate localDate2 = localDate.plusDays(3); // Period 用于计算 日期 之间的间隔 Period period = Period.between(localDate, localDate2); System.out.println(period); } }
4、TemporalAdjuster(时间矫正器)
通过 TemporalAdjuster 可以获取想要的时间。
一般可以通过 TemporalAdjusters 工具类进行操作。
【举例:】 import java.time.DayOfWeek; import java.time.LocalDateTime; import java.time.temporal.TemporalAdjusters; public class Test { public static void main(String[] args) { // 获取当前系统时间 LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime); // 可以通过 with 相关函数获取指定的日期,比如获取当前年中的第二天 LocalDateTime localDateTime2 = localDateTime.withDayOfYear(2); System.out.println(localDateTime2); // 可以使用 TemporalAdjusters 工具类进行时间矫正,比如获取当前系统日期的下一个星期六的日期 LocalDateTime localDateTime3 = localDateTime.with(TemporalAdjusters.next(DayOfWeek.SATURDAY)); System.out.println(localDateTime3); } }
5、DateTimeFormatter(日期格式化)
通过 DateTimeFormatter 可以方便的进行 字符串 与 日期 之间的转换。
format 用于 日期 转 字符串。
parse 用于 字符串 转 日期。
【举例:】 import java.time.DayOfWeek; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class Test { public static void main(String[] args) { // 获取当前系统时间 LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime); // 可以使用 DateTimeFormatter 提供的一些日期格式化常量进行格式化 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE; System.out.println(dateTimeFormatter.format(localDateTime)); // 使用 ofPattern 可以指定模板,使用 模板.format(时间) 可以将时间按照模板转为字符串。 // 使用 时间.parse(字符串, 模板) 可以将字符串按照模板转为时间 DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss"); System.out.println(dateTimeFormatter2.format(localDateTime)); System.out.println(LocalDateTime.parse(dateTimeFormatter2.format(localDateTime), dateTimeFormatter2)); } }
6、ZonedDateTime(获取带时区的时间)
【举例:】 import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; public class Test { public static void main(String[] args) { // 获取当前系统时间 LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime); // 获取所有时区 // Set<String> set = ZoneId.getAvailableZoneIds(); // set.forEach(System.out::println); // 获取 Asia/Pontianak 时区的时间 LocalDateTime localDateTime2 = LocalDateTime.now(ZoneId.of("Asia/Pontianak")); System.out.println(localDateTime2); ZonedDateTime zonedDateTime = localDateTime2.atZone(ZoneId.of("Asia/Pontianak")); System.out.println(zonedDateTime); } }
四、其余特性
1、Optional<T>
(1)什么是 Optional?
指的是 java.util.Optional,是一个容器类,代表一个值的存在或不存在。
当一个值可能为 null 时,可以使用 Optional 包装一下,从而尽量避免空指针异常。
(2)常用方法:
【get:】 获取 Optional 的值。 public T get() 【of:】 创建一个 Optional 实例,参数为 null 时会报 空指针异常。 public static <T> Optional<T> of(T var0) 【empty:】 创建一个 Optional 空实例,参数为 null 不会报 空指针异常。 public static <T> Optional<T> empty() 【ofNullable:】 创建一个 Optional 实例,参数为 null 则调用 empty(), 不为 null 则调用 of() public static <T> Optional<T> ofNullable(T var0) 【isPresent:】 判断 Optional 实例中是否存在值。 public boolean isPresent() 【orElse:】 如果 Optional 不为 null,则返回该值,若为 null,则返回 var1. public T orElse(T var1) 【orElseGet:】 与 orElse 类似,其可以传入 Lambda。 public T orElseGet(Supplier<? extends T> var1) 【map:】 对 Optional 值进行处理,若不存在 Optional 值,则返回 Optional.empty() public <U> Optional<U> map(Function<? super T, ? extends U> var1) 【flatMap:】 与 map 类似,但是处理结果类型必须为 Optional public <U> Optional<U> flatMap(Function<? super T, Optional<U>> var1) 【举例:】 import java.util.Optional; public class Test { public static void main(String[] args) { Optional<String> optional = Optional.of(new String("hello")); if (optional.isPresent()) { System.out.println(optional.get()); } optional.orElse(new String("hello world")); System.out.println(optional.orElse(new String("hello world"))); Optional<String> optional2 = Optional.empty(); System.out.println(optional2.orElse(new String("hello world"))); } }
2、接口中允许存在默认方法、静态方法
(1)简介
jdk 8 对接口中可以存在的方法进行了调整。
jdk 8 之前,接口中只能存在 抽象方法。
jdk 8,接口中可以存在 实现方法,可以分为 默认方法 和 静态方法。
其中:
默认方法使用 default 关键字修饰。
静态方法使用 static 关键字修饰。
(2)冲突
接口方法多了,冲突就可能随之而来了。
冲突一:
一个类继承了一个父类、实现了一个接口,且父类、接口中存在同名方法,那子类会使用哪个方法嘞?
答案:
子类会优先使用 父类 中实现的方法。
【举例:】 public class Test extends A implements B{ public static void main(String[] args) { Test test = new Test(); test.show(); test.show2(); } } interface B { default void show() { System.out.println("Hello Java"); } static void show2() { System.out.println("Hello Java NB"); } } class A { public void show() { System.out.println("Hello JavaScript"); } public static void show2() { System.out.println("Hello JavaScript NB"); } }
冲突二:
一个类实现了两个接口,且接口中存在同名方法,那子类会使用哪个方法嘞?
答案:
子类需要重写某一个接口的方法,自定义方法实现逻辑。
【举例:】 public class Test implements B,A{ public static void main(String[] args) { Test test = new Test(); test.show(); } @Override public void show() { A.super.show(); } } interface B { default void show() { System.out.println("Hello Java"); } } interface A { default void show() { System.out.println("Hello JavaScript"); } }