文章目录
- 一、接口特性
- 1.函数式接口
- 2.默认方法和静态方法
- 二、Lambda表达式
- 三、方法引用
- 四、Optional 接口
- 五、时间相关类
- 1. LocalDate / LocalTime
- 2. LocalDateTime
- 3. ZoneDateTime
- 4. Clock
- 5. Duration
- 六、Stream
- 七、Map
虽然java8早在14年就发布了,但是很多书本和视频资料还停留在5或者6版本的时候。鄙人所在的公司大多数项目还用着jdk1.7,个别项目虽用了1.8,但是也几乎完全没有用到新特性。为了个人成长,查找了写的比较好的博客,边写练习代码,边总结这篇笔记,供以后复习,这里的特性并不全。欢迎读者指教。
一、接口特性
1.函数式接口
函数式接口(FunctionalInterface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口用@FunctionalInterface
修饰,可以起到校验是否满足函数式接口的作用。可以被隐式转换为lambda表达式。
常用接口:
示例:(此处先不用lambda)
class User {
String name;
public User(String name) {
this.name = name;
}
public User() {
}
}
@FunctionalInterface
interface myFunction<T> {
T operate(T a, T b);
}
public class Test2 {
static Supplier<User> userSupplier = new Supplier<User>() {
@Override
public User get() {
return new User();
}
};
static Function<String, User> userFunction = new Function<String, User>() {
@Override
public User apply(String s) {
return new User(s);
}
};
static Consumer<User> userConsumer = new Consumer<User>() {
@Override
public void accept(User user) {
System.out.println(user.name);
}
};
static Predicate<User> userPredicate = new Predicate<User>() {
@Override
public boolean test(User user) {
return user.name.equals("zhangsan");
}
};
static myFunction<Integer> myFunction = new myFunction<Integer>() {
@Override
public Integer operate(Integer a, Integer b) {
return a + b;
}
};
public static void main(String[] args) {
User user = userSupplier.get(); //调用无参构造
User user2 = userFunction.apply("zhangsan"); //调用一个入参的构造
userConsumer.accept(user2); //输出zhangsan
// userPredicate.test(user); // 空指针
boolean test = userPredicate.test(user2); // true
Integer result = myFunction.operate(3, 5); // 3+5=8
}
}
2.默认方法和静态方法
Java 8允许给接口添加一个非抽象的方法实现,默认方法只需要使用 default关键字即可。接口实现者可以继承它,也可以覆盖它。在接口中用static修饰的方法称为静态方法。静态方法不能被覆盖。
@FunctionalInterface
interface MyFunction<T> {
T operate(T a, T b);
default void myDefault(){
System.out.println("这是默认方法");
}
static void myStatic(){
System.out.println("这是静态方法");
}
}
特别地,如果一个类实现两个接口A、B,这两个接口有相同签名的默认方法test()
,则必须要覆盖该方法。
- 可以选择改写成自己的逻辑。
- 如果要使用接口的方法,使用如
A.super.test();
二、Lambda表达式
Lambda 允许把函数作为一个方法的参数,可以使代码变的更加简洁紧凑。
语法格式:(parameters) ->{statements; }
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
在java8 环境下,上面的老式写法会报警告,所以推荐大家使用lambda。
将上面的代码改写:
static Supplier<User> userSupplier = () -> new User(); //省略return和大括号
static Function<String, User> userFunction = s -> new User(s); //省略参数类型
static Consumer<User> userConsumer = user -> System.out.println(user.name);
static Predicate<User> userPredicate = user -> user.name.equals("zhangsan");
static MyFunction<Integer> myFunction = (a, b) -> a + b;
lambda 表达式内只能引用标记了 final 的外层局部变量,可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有final 的语义)。而 lambda 内部对于实例的字段以及静态变量是即可读又可写。
int num = 1;
Consumer<Integer> consumer = i -> System.out.println(num + i);
consumer.accept(2);
num = 5;
这段代码会报错
另外,在构建接口实例的 lambda 表达式中是不能访问自己的默认方法的。
@FunctionalInterface
interface MyFunction<T> {
T operate(T a, T b);
default void myDefault(){
System.out.println("这是默认方法");
}
static void myStatic(){
System.out.println("这是静态方法");
}
}
// MyFunction<Integer> myFunction = num -> myDefault(); 这句是错误的
三、方法引用
Java 8 允许你使用 ::
关键字来传递方法或者构造函数引用。
可分为:
- 类名::静态方法名
- 类名::实例方法名
- 实例对象::实例方法名
在上面的 lambda 代码中仍然有报警,这里用方法引用会更加简洁。
将上面代码改写:
static Supplier<User> userSupplier = User::new; //构造方法引用
//写法和上面一样,编译器会自动识别该调用哪个构造方法
static Function<String, User> userFunction = User::new;
static Consumer<User> userConsumer = user -> System.out.println(user.name);
//新增了一个lambda表达式
//原本写法 user -> System.out.println(user);
//实例方法引用,System.out是PrintStream 的实例
static Consumer<User> userConsumer2 = System.out::println;
static Predicate<User> userPredicate = user -> user.name.equals("zhangsan");
static MyFunction<Integer> myFunction = Integer::sum; //静态方法引用
四、Optional 接口
Optional 是用来防止 NullPointerException 异常的辅助类型,是一个可以为 null 的容器对象。方便我们不用显式进行空值检测。
创建Optional对象的几个方法:
- Optional.of(T value), 返回一个Optional对象,value不能为空,否则会出空指针异常
- Optional.ofNullable(T value), 返回一个Optional对象,value可以为空
- Optional.empty(),代表空
API:
- isPresent(),是否存在值(不为空)
- ifPresent(Consumer<? super T> consumer), 如果存在值则执行consumer
- get(),获取value
- orElse(T other),如果为null 则返回other
- orElseGet(Supplier<? extends T> other),如果为null 则执行other并返回
- orElseThrow(Supplier<? extends X> exceptionSupplier),如果为null 则执行exceptionSupplier,并抛出异常
- map(Function<? super T, ? extends U> mapper),返回映射值的Optional,可以继续使用Optional的API
- flatMap(Function<? super T, Optional< U > > mapper),同map类似,区别在于返回值为value的类型
- filter(Predicate<? super T> predicate),过滤,不符合规则则返回empty,可以继续使用Optional的API
这两段代码实现相同功能:
public static String getUserName(User user) {
return Optional.ofNullable(user).map(User::getName).orElse(null);
}
public static String getUserNameOld(User user) {
if (user == null) {
return null;
}
return user.getName();
}
五、时间相关类
java 8 中提供了很多时间相关类,这些类都是不可变的,所以是线程安全的。
1. LocalDate / LocalTime
LocalDate 表示一个没有时区信息的日期,LocalTime 表示一个没有时区信息的时间。
public static void testLocaleDate() {
LocalDate date = LocalDate.now(); // 格式如:2020-01-21
date = date.plusDays(1); // 增加一天
date = date.plusMonths(1); // 减少一个月
date = date.minusDays(1); // 减少一天
date = date.minusMonths(1); // 减少一个月
System.out.println(date);
}
public static void testLocaleTime() {
LocalTime time = LocalTime.now(); // 格式如:17:16:40.380
time = time.plusMinutes(1); // 增加一分钟
time = time.plusSeconds(1); // 增加一秒
time = time.minusMinutes(1); // 减少一分钟
time = time.minusSeconds(1); // 减少1秒
System.out.println(time);
}
2. LocalDateTime
LocalDateTime 同时表示了时间和日期。
public static void testLocalDateTime() {
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // UTC格式:2020-01-21T17:16:40.380
System.out.println(localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); // 自定义格式:2020-01-21 17:16:40
}
3. ZoneDateTime
表示带有时区信息的时间和日期。
public static void testZonedDateTime() {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
//2020-01-21T17:19:14.529+08:00[Asia/Shanghai]
System.out.println(zonedDateTime);
ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
//2020-01-21T01:19:14.530-08:00[America/Los_Angeles]
System.out.println(zonedDatetimeFromZone);
ZoneId zoneId = ZoneId.systemDefault();
//Asia/Shanghai
System.out.println(zoneId);
}
4. Clock
public static void testClock() {
Clock utc = Clock.systemUTC(); // 世界标准时间
//2020-01-22T01:21:40.200
System.out.println(LocalDateTime.now(utc));
Clock shanghai = Clock.system(ZoneId.of("Asia/Shanghai")); // 上海时间
//2020-01-22T09:21:40.245
System.out.println(LocalDateTime.now(shanghai));
}
5. Duration
帮助我们轻松的计算时间差。
public static void testDuration() {
final LocalDateTime from = LocalDateTime.parse("2019-07-15 18:50:50", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
final LocalDateTime to = LocalDateTime.parse("2019-07-16 19:50:50", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
final Duration duration = Duration.between(from, to);
System.out.println("Duration in days: " + duration.toDays()); // 1
System.out.println("Duration in hours: " + duration.toHours()); // 25
}
六、Stream
Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;
Stream,用户只要给出需要对其包含的元素执行什么操作,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个Iterator,单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
Stream 可以并行化操作,Iterator只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架来拆分任务和加速处理过程。开启并行流方式:parallelStream
stream绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。
所有 Stream 的操作必须以 lambda 表达式为参数
Stream的操作类型:
- 中间操作(Intermediate Operation):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
- 终止操作(Terminal Operation):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。
Intermediate Operation又可以分为两种类型:
- 无状态操作(Stateless Operation):操作是无状态的,不需要知道集合中其他元素的状态,每个元素之间是相互独立的,比如map()、filter()等操作。
- 有状态操作(Stateful Operation):有状态操作,操作是需要知道集合中其他元素的状态才能进行的,比如sort()、distinct()。
Stream的使用:
- 构造流的几种常见方法:
// 1> Individual values
Stream stream = Stream.of("a", "b", "c");
// 2> Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3> Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
- 数值流的构造:对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。
IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println); //1 2 3
IntStream.range(1, 3).forEach(System.out::println); //1 2
IntStream.rangeClosed(1, 3).forEach(System.out::println); //1 2 3
- forEach:迭代流中的每个数据。是 terminal 操作。
- peek:功能和forEach相同,是intermediate操作。
- map:用于一对一映射每个元素到对应的结果。
- flatmap:一对多映射,可以将一个2维的集合映射成一维,相当于他映射的深度比map深了一层 。
- filter:设置条件过滤出元素。
- limit/skip:获取指定数量的元素。limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。
- sorted:对流进行排序。
- count:计数是一个terminal 操作,返回Stream中元素的个数,返回值类型是long。
- match:所有的匹配操作都是最终操作,并返回一个boolean类型的值。
- reduce:主要作用是把 Stream 元素组合起来,字符串拼接、数值的 sum、min、max、average等。
// 1> map/flatMap
// map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素
Stream<String> stream4 = Stream.of(new String[]{"a", "b", "c"});
stream4.map(String::toUpperCase).forEach(System.out::println);
// 还有一些场景,是一对多映射关系的,这时需要 flatMap
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
// Stream<Integer> mapStream = inputStream.map(List::size);
// mapStream.forEach(System.out::println);
Stream<Integer> flatMapStream = inputStream.flatMap(Collection::stream);
flatMapStream.forEach(System.out::println);
// 2> filter
// filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream
Integer[] nums = new Integer[]{1,2,3,4,5,6};
Arrays.stream(nums).filter(n -> n<3).forEach(System.out::println);
// 3> forEach
// forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,无法对一个 Stream 进行两次terminal 运算
Stream stream13 = Arrays.stream(nums);
stream13.forEach(System.out::print);
// stream13.forEach(System.out::print); // 上面forEach已经消费掉了,不能再调用
System.out.println();
// 具有相似功能的 intermediate 操作 peek 可以达到上述目的
Stream stream14 = Arrays.stream(nums);
stream14
.peek(System.out::print)
.peek(System.out::print)
.collect(Collectors.toList());
System.out.println();
// 4> reduce 主要作用是把 Stream 元素组合起来,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce
// Stream 的 sum 就相当于:
Integer sum = Arrays.stream(nums).reduce(0, (integer, integer2) -> integer + integer2);
System.out.println(sum);
// 有初始值
Integer sum1 = Arrays.stream(nums).reduce(0, Integer::sum);
// 无初始值
Integer sum2 = Arrays.stream(nums).reduce(Integer::sum).get();
// 5> limit/skip
// limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。
Arrays.stream(nums).limit(3).forEach(System.out::print);
System.out.println();
Arrays.stream(nums).skip(2).forEach(System.out::print);
System.out.println();
// 6> sorted
// 对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、
// limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
Arrays.stream(nums).sorted((i1, i2) -> i2.compareTo(i1)).limit(3).forEach(System.out::print);
System.out.println();
Arrays.stream(nums).sorted((i1, i2) -> i2.compareTo(i1)).skip(2).forEach(System.out::print);
System.out.println();
// 7> min/max/distinct
System.out.println(Arrays.stream(nums).min(Comparator.naturalOrder()).get());
System.out.println(Arrays.stream(nums).max(Comparator.naturalOrder()).get());
Arrays.stream(nums).distinct().forEach(System.out::print);
System.out.println();
// 8> Match
// Stream 有三个 match 方法,从语义上说:
// allMatch:Stream 中全部元素符合传入的 predicate,返回 true
// anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
// noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
// 它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。
Integer[] nums1 = new Integer[]{1, 2, 2, 3, 4, 5, 5, 6};
System.out.println(Arrays.stream(nums1).allMatch(integer -> integer < 7));
System.out.println(Arrays.stream(nums1).anyMatch(integer -> integer < 2));
System.out.println(Arrays.stream(nums1).noneMatch(integer -> integer < 0));
//统计
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
七、Map
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
map.getOrDefault(42, "not found"); // not found
map.merge(9, "val9", (oldValue, value) -> oldValue.concat(value));
String s = map.get(9);// val9
map.merge(9, "concat", String::concat);
String s1 = map.get(9);// val9concat
//Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。