java基础学习笔记——java8特性


虽然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. 构造流的几种常见方法:
        // 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();
  1. 数值流的构造:对于基本数值型,目前有三种对应的包装类型 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
  1. forEach:迭代流中的每个数据。是 terminal 操作。
  2. peek:功能和forEach相同,是intermediate操作。
  3. map:用于一对一映射每个元素到对应的结果。
  4. flatmap:一对多映射,可以将一个2维的集合映射成一维,相当于他映射的深度比map深了一层 。
  5. filter:设置条件过滤出元素。
  6. limit/skip:获取指定数量的元素。limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。
  7. sorted:对流进行排序。
  8. count:计数是一个terminal 操作,返回Stream中元素的个数,返回值类型是long。
  9. match:所有的匹配操作都是最终操作,并返回一个boolean类型的值。
  10. 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中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值