Java8新特性

www:what、where、how.

接口的默认方法

Default Methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces. They are interface methods that have an implementation and the default keyword at the beginning of the method signature. In addition, you can define static methods in interfaces.

翻译后3个意思:

  • 默认方法能使接口在添加新功能的同时,保证能兼容旧版本的代码(旧代码中无需实现新增的默认方法)。
  • 默认方法关键字 default
  • 可以在接口中定义静态方法

在java8之前

interface 接口 {
	静态常量;
    抽象方法;
}

java8之后

interface 接口 {
    静态常量;
    抽象方法;
    默认方法;
    静态方法;
}

如 java.util.List 类中的默认方法:

public interface List<E> extends Collection<E> {
	... 省略部分代码...
    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }

    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.ORDERED);
    }
}

一个类扩展一个包含默认方法的接口,你可以做以下3件事:

  • 不做任何事情,子类默认包含了接口中默认方法的默认实现
  • 在子类中重新定义默认方法,把他变成一个抽象方法,强制子类实现
  • 在子类中重写接口中的默认方法

测试代码

public interface DefaultMethodTest {
    void fun();

    default void fun1() {
        System.out.println("DefaultMethodTest..fun1");
    };

    static void fun2() {
        System.out.println("DefaultMethodTest static function fun2...");
    }
}

public interface DefaultMethodTest2 extends DefaultMethodTest{
    /**
     * 重新定义fun1 方法,子类必须实现这个接口
     */
    void fun1();
}


public class Main {
    public static void main(String[] args) {
        DefaultMethodTest test = new DefaultMethodTest() {
            @Override
            public void fun() {
                System.out.println("do....");
            }

            @Override
            public void fun1() {
                System.out.println("匿名内部类重写fun1");
                DefaultMethodTest.super.fun1();
            }
        };

        test.fun();
        test.fun1();

        DefaultMethodTest2 defaultMethodTest2 = new DefaultMethodTest2() {
            @Override
            public void fun() {
                System.out.println("defaultMethodTest2........fun...");
            }

            @Override
            public void fun1() {
                System.out.println("defaultMethodTest2........fun1...");
            }
        };
        defaultMethodTest2.fun();
        defaultMethodTest2.fun1();

        DefaultMethodTest.fun2();
    }
}

Lambda表达式

Lambda 表达式(lambda expression)是一个匿名函数。

使用Lambda表达式之前,我们一般都是使用匿名方法的方式:

public class Test01LambdaBefore {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        thread.start();
    }
}

使用Lambda表达式后,代码可简化如下:

public class Test02LambdaAfter {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
        thread.start();
    }
}

Lambda 表达式一般包含三部分:

(参数列表...) -> {
	方法体;
}
  • 参数列表
  • ->:箭头,连接参数和方法体,不可省略
  • 方法体

以下为不同参数个数、有无返回值等不同情况下的代码实例:

public class Test03Lambda {
    public static void main(String[] args) {
        // 没有入参,没有返回
        A a1 = () -> {
            System.out.println(Thread.currentThread().getName());
        };

        // 如果方法体只有1行代码,可省略大括号
        A a2 = () -> System.out.println(Thread.currentThread().getName());

        // 有1个参数,没有返回
        B b1 = (name) -> {
            System.out.println(name);
        };

        // 如果只有1个参数,可省略括号
        B b2 = name -> System.out.println(name);

        // 只有1个参数,并且只有1行代码,可进阶使用方法引用
        B b3 = System.out::println;

        // 有多个参数,没有返回
        C c1 = (name, age) -> {
            System.out.println(name + "===" + age);
        };

        // 只要参数超过1个,括号就不能省略,但是方法体只有1行代码,可以省略大括号
        C c2 = (name, age) -> System.out.println(name + "===" + age);

        // 没有入参,有返回值
        D d1 = () -> {
            return "1024";
        };

        // 方法体只有1行代码,可以省略大括号和return
        D d2 = () -> "1024";

        // 有1个参数,有返回值
        E e1 = (name) -> {
            return "hello " + name;
        };

        // 只有1个参数,可省略参数的括号;方法体只有1行代码,可以省略大括号和return
        E e2 = name -> "hello" + name;

        // 有多个参数,有返回值
        F f1 = (name, age) -> {
            return name + "===" + age;
        };

        // 只要参数超过1个,括号就不能省略,但是方法体只有1行代码,可以省略大括号和return
        F f2 = (name, age) -> name + "===" + age;
    }
}

// 没有入参,没有返回
interface A {
    void fun();
}

// 有1个参数,没有返回
interface B {
    void fun(String name);
}

// 有多个参数,没有返回
interface C {
    void fun(String name, int age);
}

// 没有入参,有返回值
interface D {
    String fun();
}

// 有1个参数,有返回值
interface E {
    String fun(String name);
}

// 有多个参数,有返回值
interface F {
    String fun(String name, int age);
}
  • 当且仅当只有1个参数的时候,可以省略参数的括号
  • 当前仅当方法体只有1行代码的时候,可以省略方法体的大括号;如果方法体有返回值,return也可以省略

函数式接口

Lambda表达式实现的接口不是普通的接口,他们都是函数式接口。这种接口有且只有一个抽象接口(可以包含多个静态方法或默认方法)。
可通过注解 @FunctionInteface 申明该接口为函数式接口。
如果在函数式接口中申明多个抽象方法,编译会报错。

Function

函数式接口,有一个输入,有一个输出

image.png
示例:

public class Test01Function {
    public static void main(String[] args) {
        Integer[] arrays = {12,21,18,33,100,343,22,39};
        Arrays.stream(arrays).map(x -> x*2).forEach(System.out::println);
        System.out.println("***************************");

        int result = calc(x -> x*x, x-> x+1024, 2);
        System.out.println(result);
        System.out.println("***************************");

        result = calc2(x -> x*x, x-> x+1024, 2);
        System.out.println(result);
    }

    static int calc(Function<Integer, Integer> f1, Function<Integer, Integer> f2,int x) {
        // 先执行f2,再执行f1
        return f1.compose(f2).apply(x);
    }

    static int calc2(Function<Integer, Integer> f1, Function<Integer, Integer> f2,int x) {
        // 先执行f1,再执行f2
        return f1.andThen(f2).apply(x);
    }
}

Supplier

供给型函数,没有输入,有一个输出

image.png
示例:

public class Test02Supplier {
    public static void main(String[] args) {
        Integer[] arrays = {12,21,18,33,100,343,22,39};
        oper(() -> {
            Arrays.sort(arrays);
            return arrays[arrays.length - 1];
        });

    }

    static void oper(Supplier<Integer> supplier) {
        Integer max = supplier.get();
        System.out.println("max = " + max);
    }
}

Consumer

消费型接口,有一个输入,没有输出

image.png
示例:

public class Test03Consumer {
    public static void main(String[] args) {
        Integer[] arrays = {12,21,18,33,100,343,22,39};
        Arrays.stream(arrays).forEach(System.out::println);
        System.out.println("*******************");

        // 1) 取前3个字符
        // 2) 全部转换成大写
        calc2(x -> {
            String sub = x.substring(0, 3);
            System.out.println(sub);
        }, x -> {
            String str = x.toUpperCase();
            System.out.println(str);
        }, "Hello World");

    }

    static void calc2(Consumer<String> f1,Consumer<String> f2, String str) {
        // 先执行f1,再执行f2
        f1.andThen(f2).accept(str);
    }
}

Predicate

断言型接口,有一个输入,返回 true 或 false

image.png
示例:

public class Test03Predicate {
    public static void main(String[] args) {
        Integer[] arrays = {12,21,18,33,100,343,22,39};
        Arrays.stream(arrays).filter(x -> x >=100).collect(Collectors.toList()).forEach(System.out::println);

        calc(str -> str.contains("Hello"), str -> str.contains("world"), "Hello World");
    }

    static void calc(Predicate<String> f1, Predicate<String> f2,String str) {
        // and:两个函数都返回true的时候才返回true,否则返回false
        boolean b1 = f1.and(f2).test(str);
        System.out.println(b1);

        // or:两个函数只要有1个返回true的时候就返回true
        boolean b2 = f1.or(f2).test(str);
        System.out.println(b2);
        
		// negate:取反,如果原来是true就返回false,如果是false就返回true
        boolean b3 = f1.negate().test(str);
        System.out.println(b3);
    }
}

方法引用

方法引用使用操作符 “:: ”,这个操作符把方法引用分为两部分,左边为类名或者对象,右边为方法名。

方法引用可分为4种类型:

类型语法案例
引用一个静态方法Class::staticMethodString::valueOf
Collections::sort
引用对象的实例方法instance::methodSystem.out::println
list::contains
引用一个指定类型的实例方法Class::methodString::length
ArrayList::add
引用构造函数Class::newString::new
HashMap::new

注意:被引用方法的参数列表和返回类型必须与函数式接口方法参数列表和方法返回值类型一致。

引用一个静态方法

语法:

<>::<类方法名称>

示例:

public class Test01StaticMethodRef {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(18, 15, 22, 30, 19, 25);

        // 使用lambda表达式实现排序
        list.sort(((o1, o2) -> o1 - o2));
        list.forEach(System.out::println);

        // 使用方法引用
        List<Integer> list2 = Arrays.asList(18, 15, 22, 30, 19, 25);
        list2.sort(Integer::compare);
        list2.forEach(System.out::println);
    }
}

引用对象的实例方法

语法:

<实例>::<实例方法名称>

示例:

public class Test02InstanceMethodRef {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("唐僧", "孙悟空", "天蓬元帅", "大自在天魔");

        // 使用lambda表达式实现排序
        list.stream().map(entry -> entry.length()).forEach(System.out::println);

        // 使用方法引用
        list.stream().map(String::length).forEach(System.out::println);
    }
}

引用一个指定类型的实例方法

语法:

<>::<类的实例方法>

示例:

public class Test03InstanceMethodRef {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "c", "b", "g", "f", "x");

        // 使用lambda表达式实现排序
        list.sort(((o1, o2) -> o1.compareTo(o2)));
        list.forEach(System.out::println);

        // 使用方法引用
        List<String> list2 = Arrays.asList("a", "c", "b", "g", "f", "x");
        list2.sort(String::compareTo);
        list2.forEach(System.out::println);
    }
}

引用构造函数

语法:

// 普通类::new

// 数组
类型[]::new

示例:

public class Test04Constructor {
    public static void main(String[] args) {
        List<String> nameList = Arrays.asList("张三","李四","王五");

        // 对象::new
        List<Person> personList = nameList.stream().map(Person::new)
        	.collect(Collectors.toList());
        System.out.println(personList);

        // 对象数组:  类型[]::new 
        Person[] peopleArray = nameList.stream().map(Person::new)
        	.toArray(Person[]::new);
        System.out.println(Arrays.toString(peopleArray));
    }

    static class Person {
        private String name;

        public Person(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

Stream

流式编程类似于车间里的流水线。

Stream流由三部分组成:

  • 数据源:可以是数据Array、集合Collection、Stream
  • 中间操作:可以是0-n个中间操作,如map、filter等
  • 最终操作:会产生一个结果,如果collect、foreach、count等

注意:Stream流是懒操作,数据源的计算操作只有触发最终操作的时候才会执行。

public class Test01Lazy {
    public static void main(String[] args) {
        List<String> nameList = Arrays.asList("张三","孙悟空","净坛使者");
        // 只有中间操作,没有最终操作,并不会触发中间操作
        Stream<Integer> stream = nameList.stream()
                .map(entry -> {
                    int length = entry.length();
                    System.out.println(entry + " length = " + length);
                    return length;
                }).filter(entry -> {
                    boolean flag = entry > 3;
                    if(flag) {
                        System.out.println("length 大于 3");
                    } else {
                        System.out.println("lenght 小于等于 3");
                    }
                    System.out.println("=========================");
                    return flag;
                });
        System.out.println(stream);

        // 只有最终操作才会触发中间操作,并且通过输出可以看出中间操作是对流中的数据一个元素一个元素的处理的
        stream.forEach(System.out::println);
    }
}

获取流

  1. 从集合中获取
// List转换成Stream
List<Integer> list = Arrays.asList(12, 34, 56);
Stream<Integer> stream1 = list.stream();
Stream<Integer> stream11 = list.parallelStream();// 获取一个并行流

// Set转换成Stream
Set<Integer> set = new HashSet<Integer>() {{
    add(1);
    add(12);
    add(13);
}};
Stream<Integer> stream2 = set.stream();
Stream<Integer> stream22 = set.parallelStream();// 获取一个并行流

// Vector 转换成 Stream
Vector<Integer> vector = new Vector<Integer>() {{
    add(1);
    add(2);
    add(3);
}};
Stream<Integer> stream3 = vector.stream();
Stream<Integer> stream33 = vector.parallelStream();// 获取一个并行流

// Map 转换成Map
Map<String, Integer> map = new HashMap<>();
map.put("张三", 18);
map.put("李四", 28);
Stream<String> stream4 = map.keySet().stream();
Stream<Integer> stream5 = map.values().stream();
  1. Stream.of、Stream.generate、Stream.iterate
// 通过Stream.of 生成 Stream
Stream<String> stream6 = Stream.of("zhangsan", "lisi", "wangwu");

// 通过Stream.generate 生成Stream
Stream<String> stream7 = Stream.generate(() -> UUID.randomUUID().toString()).limit(10);
stream7.forEach(System.out::println);

// Stream.iterate 生产Stream,给定一个种子seed,重复指定的一元操作
Stream.iterate(1, x -> x * 2).limit(10).forEach(System.out::println);
  1. IntStream、LongStream、DoubleStream
// IntStream 生成流, 类似的还有 LongStream、DoubleSrream
IntStream intStream = IntStream.of(1, 2, 3, 4);
// 生成1-100的流,不包含100
IntStream.range(1, 100).forEach(System.out::print);
System.out.println("========================");
// 生成1-100的流,包含100
IntStream.rangeClosed(1, 100).forEach(System.out::print);

中间操作

filter:过滤数据,只保留满足条件的数据

List<Integer> list1 = Arrays.asList(1, 100, 30, 88, 20, 60);
// 过滤掉所有小于等于50的数字
list1.stream().filter(x -> x > 50).forEach(System.out::println);

map:数据的映射/转换

// 把所有的元素都转换成大写
List<String> strList5 = Arrays.asList("abc", "def", "gh");
strList5.stream().map(String::toUpperCase).forEach(System.out::println);

mapToInt:把数据转换成Int

// 把字符串转换成int
List<String> strList = Arrays.asList("1", "100", "30", "88", "20", "60");
strList.stream().mapToInt(x -> Integer.valueOf(x)).forEach(System.out::println);

mapToLong:把数据转换成Long

List<String> strList2 = Arrays.asList("1", "100", "30", "88", "20", "60");
strList2.stream().mapToLong(x -> Long.valueOf(x)).forEach(System.out::println);

// 使用方法引用改写
strList2.stream().mapToLong(Long::valueOf).forEach(System.out::println);

mapToDouble:把数据转换成Double

List<String> strList2 = Arrays.asList("1", "100", "30", "88", "20", "60");
strList2.stream().mapToDouble(x -> Double.valueOf(x)).forEach(System.out::println);

// 使用方法引用改写
strList2.stream().mapToDouble(Double::valueOf).forEach(System.out::println);

flatMap:将流中的每个数据都换成另一个流,然后把所有流连接成一个流。

// flagMap:把流中的每一个数据转换成一个流,最后所有的流合并成一个流
List<String> list = Arrays.asList("hello", "world", "stream", "lambda");
list.stream().flatMap(entry -> {
    char[] chars = entry.toCharArray();
    Character[] cs = new Character[chars.length];
    for (int i = 0; i < chars.length; i++) {
        cs[i] = Character.valueOf(chars[i]);
    }
    return Arrays.stream(cs);
}).forEach(System.out::println);

flatMapToInt:将流中的每个数据都换成另一个流,然后把所有流连接成一个Int流。
flatMapToLong:将流中的每个数据都换成另一个流,然后把所有流连接成一个Long流。
flatMapToDouble:将流中的每个数据都换成另一个流,然后把所有流连接成一个Double流。

List<String> list3 = Arrays.asList("hello", "world", "stream", "lambda");
list3.stream().flatMapToInt(entry -> {
    char[] chars = entry.toCharArray();
    int[] cs = new int[chars.length];
    for (int i = 0; i < chars.length; i++) {
        cs[i] = chars[i];
    }
    return Arrays.stream(cs);
}).forEach(System.out::println);

distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素

// 去重
List<String> list4 = Arrays.asList("张三", "李四", "王五", "张三", "李四", "王五");
list4.stream().distinct().forEach(System.out::println);

sorted:根据自然顺序排序,可指定排序算法

// 按自然序排序
List<Integer> list1 = Arrays.asList(1, 100, 30, 88, 20, 60);
list1.stream().sorted().forEach(System.out::println);


// 按照指定的排序算法排序,比如倒序
list1.stream().sorted((x, y) -> y - x).forEach(System.out::println);

peek:偷看,整个流元素遍历一遍

// 偷看,全局浏览一遍整个流,虽然跟.forEach(System.out::println)效果一样,
// 但是peek完了以后流还是可以继续使用的,forEach是最终操作,执行后就不能继续执行其他的操作
// 通过结果可以看出,Stream流是一个数据一个数据的处理
List<Integer> list1 = Arrays.asList(1, 100, 30, 88, 20, 60);
list1.stream().peek(System.out::println).forEach(System.out::println);

limit:限制数据个数,只处理limit个数据

List<Integer> list1 = Arrays.asList(1, 100, 30, 88, 20, 60);
// 限制使用数据的个数
list1.stream().limit(3).forEach(System.out::println);

skip:跳过skip个数据

List<Integer> list1 = Arrays.asList(1, 100, 30, 88, 20, 60);
// 跳过数据
list1.stream().skip(3).forEach(System.out::println);

最终操作

forEach:对流中的每一个数据执行消费者函数

list.parallelStream().forEach(System.out::println);

forEachOrdered:跟forEach基本一致,差别主要是在parallelStream并行流中能始终保持跟初始流中的数据排序一致。

list.parallelStream().forEachOrdered(System.out::println);

toArray:把流中的数据抓换成Object数组,或者转换成流中数据类型的数组。

List<String> list2 = Arrays.asList("1", "11", "111", "1111");
// 转换成Object数组
Object[] objects = list2.stream().toArray();
System.out.println(Arrays.toString(objects));

// 转换成String数组,注意:只能转换成流中数据类型的数组
String[] strings = list2.stream().toArray(String[]::new);
System.out.println(Arrays.toString(strings));

reduce:指定一个数据作为基础数据,然后做递归计算,一般做汇总计算

// 计算0-99累积的值
int sum = IntStream.range(0, 100).reduce(0, (x, y) -> x + y);
System.out.println(sum);

// 计算0-99累积的值:使用方法引用
sum = IntStream.range(0, 100).reduce(0, Integer::sum);
System.out.println(sum);

// IntStream提供了计算总和的方法,底层实现就是reduce
sum = IntStream.range(0, 100).sum();
System.out.println(sum);

min:获取流中的最小数据

// 获取最小值
int min = IntStream.range(0, 100).reduce(0, (x, y) -> x < y ? x : y);
System.out.println(min);
// 使用方法引用获取最小值
min = IntStream.range(0, 100).reduce(0, Integer::min);
System.out.println(min);
// 使用IntStream的min方法
min = IntStream.range(0, 100).min().getAsInt();
System.out.println(min);

max:获取流中的最大数据

// 获取最大值
int max = IntStream.range(0, 100).reduce(0, (x, y) -> x >= y ? x : y);
System.out.println(max);
// 使用方法引用获取最大值
max = IntStream.range(0, 100).reduce(0, Integer::max);
System.out.println(max);
// 使用IntStream的max方法
max = IntStream.range(0, 100).max().getAsInt();
System.out.println(max);

count:获取留中数据总数

long count = IntStream.range(0, 100).filter(x -> x % 2 == 0).count();
System.out.println(count);

collect:收集数据

List<Integer> intList = Arrays.asList(1, 32, 23, 32, 11, 100, 99);
// 获取流中大于50的数据,并收集成一个list
List<Integer> collect = intList.stream().filter(x -> x > 50).collect(Collectors.toList());
collect.forEach(System.out::println);

System.out.println("********************************");
// 获取流中大于30的数据,并收集成一个Set
Set<Integer> collect1 = intList.stream().filter(x -> x > 30).collect(Collectors.toSet());
collect1.forEach(System.out::println);

// 把流中的数据转换成map,key为数字,value为数字出现的次数

// 当且仅当元素不会重复的时候使用当前方法
//        Map<Integer, Integer> map1 = intList.stream().collect(Collectors.toMap(Function.identity(), entry -> 1));
// 当流中有重复数据的时候需要对重复的key做汇总操作
Map<Integer, Integer> map = intList.stream().collect(Collectors.toMap(Function.identity(), entry -> 1, Integer::sum));
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<Integer, Integer> entry = iterator.next();
    System.out.println(entry.getKey() + " = " + entry.getValue());
}

anyMatch:只要任意一个数据符合条件,返回true,否则返回false,此操作是一个短路操作

// 只要任意一个大于50就返回true,否则返回false,短链操作
boolean flag = intList.stream().anyMatch(x -> {
    System.out.println(x);
    return x > 50;
});
System.out.println(flag);

allMatch:必须所有的数据都符合条件才返回true,否则返回false,此操作是一个短路操作

// 所有的都大于30的时候才返回true,否则返回false
boolean flag = intList.stream().allMatch(x -> x > 30);
System.out.println(flag);

noneMatch:必须所有的数据都不符合才返回ture,否则返回false,此操作是一个短路操作

// 所有的都不小于0才返回true
boolean flag = intList.stream().noneMatch(x -> x < 0);
System.out.println(flag);

findFirst:返回流中的第一个元素,如果流是空的,返回空

// 获取第一个大于30的元素
Integer first = intList.stream().filter(x -> x > 30).findFirst().get();
System.out.println(first);

findAny:返回流中的任一元素,如果流是空的,返回空;经常配合parallelStream一起使用

Integer any = intList.parallelStream().findAny().get();
Integer any2 = intList.parallelStream().findAny().get();
Integer any3 = intList.parallelStream().findAny().get();
System.out.println(any);
System.out.println(any2);
System.out.println(any3);

并行流

parallelStream其实就是一个并行执行的流,它通过默认的ForkJoinPool,可以提高多线程任务的速度。


任务窃取。
获取并行流有两种方式

  • 直接通过Collection接口中的parallelStream方法获取
  • 通过已有的串行流转换
public class Test04ParallelStream {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        // 通过Collection中的 接口 直接获取并行流
        list.parallelStream();

        // 将已有的串行流转换为并行流
        list.stream().parallel();

        // 证明并行流的底层实现为ForkJoin
        list.stream().parallel().peek(x -> {
            System.out.println(Thread.currentThread().getName() + " ===> " + x);
        }).count();
    }
}

并行流的实战,,计算0-10亿的累加和:

public class Test05CalcSum {
    public static void main(String[] args) {
        // 分别用串行流和并行流计算0-10亿的累加和
        long startTime = System.currentTimeMillis();
        long sum = LongStream.range(0, 10_0000_0000L).sum();
        System.out.println("sum = " + sum + " cost time = " + (System.currentTimeMillis() - startTime));

        startTime = System.currentTimeMillis();
        long sum1 = LongStream.range(0, 10_0000_0000L).parallel().sum();
        System.out.println("sum = " + sum1 + " cost time = " + (System.currentTimeMillis() - startTime));
    }
}

// 输出结果
// sum = 499999999500000000 cost time = 292
// sum = 499999999500000000 cost time = 54
// 通过输出结果可看出,并行流的效率远远大于串行流

Optional

A container object which may or may not contain a non-null value.

Optional就是一个要么包含一个空值,要么包含一个具体值的容器。它的主要作用就是为了避免null检查,防止NullPointException。

以一台电脑为例:

如果我们想获取到USB的版本信息,直接获取会有空指针的风险;如果每一层对象都判断非空,就会有一堆非空判断,代码比较繁琐。

public class Test01Befor {
    public static void main(String[] args) {
        // 看起来和整齐的代码,其实有很大的空指针风险
        Computor computor = new Computor();
        String version = computor.getSoundCard().getUsb().getVersion();

        // 为了防止空指针异常,我们不的不加入大量的非空判断
        if (computor != null) {
            SoundCard soundCard = computor.getSoundCard();
            if(soundCard != null) {
                USB usb = soundCard.getUsb();
                if(usb != null) {
                    String version1 = usb.getVersion();
                }
            }
        }

    }
}

class Computor {
    private SoundCard soundCard;

    public SoundCard getSoundCard() {
        return soundCard;
    }

    public void setSoundCard(SoundCard soundCard) {
        this.soundCard = soundCard;
    }
}

class SoundCard {
    USB usb;

    public USB getUsb() {
        return usb;
    }

    public void setUsb(USB usb) {
        this.usb = usb;
    }
}

class USB {
    String version;

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }
}


Optional是一个容器,这个容器要么装了对象,要没没装。
image.png
我们用Optional改造下上面的代码:

import java.util.Optional;

public class Test02After {
    public static void main(String[] args) {
        // 虽然看不到非空判断了,但是代码还是太繁琐,只是把非空判断变成了isPresent
        Computor2 computor = new Computor2();
        Optional<SoundCard2> soundCardOp = computor.getSoundCard();
        if(soundCardOp.isPresent()) {
            SoundCard2 soundCard = soundCardOp.get();
            Optional<USB2> usbOp = soundCard.getUsb();
            if (usbOp.isPresent()) {
                USB2 usb = usbOp.get();
                String version = usb.getVersion();
            }
        }
    }
}

class Computor2 {
    private Optional<SoundCard2> soundCard = Optional.empty();

    public Optional<SoundCard2> getSoundCard() {
        return soundCard;
    }

    public void setSoundCard(Optional<SoundCard2> soundCard) {
        this.soundCard = soundCard;
    }
}

class SoundCard2 {
    Optional<USB2> usb = Optional.empty();

    public Optional<USB2> getUsb() {
        return usb;
    }

    public void setUsb(Optional<USB2> usb) {
        this.usb = usb;
    }
}

class USB2 {
    String version;

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }
}

以上改写除了提醒开发者需要非空判断之外,貌似跟 if 判断没有任何区别。
所以,在最终改写之前,我们先了解下Optional包含的方法和使用方法:
Optional的创建

Optional.of:创建一个非空的Optional对象,如果传入的value为空就报错

image.png

Optional.ofNullable:创建一个可以为空的Optional对象,如果value为空就返回Optional.EMPTY

image.png

public class Test03OptionalCreate {
    public static void main(String[] args) throws Exception {
        String name = null;
        String str = "hello word";

        // 只能创建非空的Optional对象,NullPointerException
//        Optional<String> nameOptional = Optional.of(name);

        // 创建一个Optional对象,包含的对象内容为str的值
        Optional<String> stringOptional = Optional.of(str);

        // 创建一个可为空的Optional对象,传入的值可为null
        Optional<String> nameOptional2 = Optional.ofNullable(name);

        // 创建一个空Optional
        Optional<Object> empty = Optional.empty();
    }
}

其他方法:

isPresent:判断Optional是否包含value,有value返回true,否则返回false

String hello = null;
String world = "world";

Optional<String> helloOp = Optional.ofNullable(hello);
Optional<String> worldOp = Optional.ofNullable(world);

System.out.println("==================isPresent=====================");
System.out.println("helloOp isPresent = " + helloOp.isPresent());
System.out.println("worldOp isPresent = " + worldOp.isPresent());

get:返回Optional包含的value值,如果value为空抛异常NoSuchElementException

// NoSuchElementException: No value present
// System.out.println("helloOp get = " + helloOp.get());
System.out.println("worldOp get = " + worldOp.get());

ifPresent(Consumer<? super T> consumer):当Optional包含value 的时候,执行consumer函数

helloOp.ifPresent(x -> {
    // helloOp为空,所以不做任何操作
    System.out.println("helloOp:" + x.toUpperCase());
});

worldOp.ifPresent(x -> {
    System.out.println("worldOp:" + x.toUpperCase());
});

filter(Predicate<? super T> predicate):判断Optional包含的value是否符合predicate断言,如果符合断言返回Optional,如果不符合返回Optional.EMPTY

// 如果helloOp的元素包含w就打印,不包含就不做任何操作
helloOp.filter(x -> x.contains("w")).ifPresent(System.out::println);

// 如果worldOp的元素包含w就打印,不包含就不做任何操作
worldOp.filter(x -> x.contains("w")).ifPresent(System.out::println);

map(Function<? super T, ? extends U> mapper):

  • 当Optional包含value时候,执行mapper函数,执行结果如果为空返回Optional.EMPTY,不为空返回Optional.of(函数结果)
  • 当Optional不包含value,返回Optional.EMPTY
String helloIsNull = helloOp.map(String::toUpperCase).orElse("hello is null");
System.out.println(helloIsNull);

String worldIsNull = worldOp.map(String::toUpperCase).orElse("world is null");
System.out.println(worldIsNull);

flatMap(Function<? super T, Optional> mapper):跟map功能一直,区别就是入参必须是Optional

Computor2 computor = new Computor2();
Optional<SoundCard2> soundCardOp = computor.getSoundCard();
String versionNotExist = soundCardOp.flatMap(SoundCard2::getUsb).map(USB2::getVersion).orElse("version not exist");
System.out.println(versionNotExist);


上图为 map 和 flatMap的区别

orElse(T other):如果当前Optional为空,返回直接的值other

String helloIsNull = helloOp.map(String::toUpperCase).orElse("hello is null");
System.out.println(helloIsNull);

String worldIsNull = worldOp.map(String::toUpperCase).orElse("world is null");
System.out.println(worldIsNull);

orElseGet(Supplier<? extends T> other):如果当前Optional不为空返回value,为空执行other函数做为返回值

String s1 = helloOp.orElseGet(() -> new Random().nextLong() + "");
System.out.println(s1);
String s2 = worldOp.orElseGet(() -> new Random().nextLong() + "");
System.out.println(s2);

T orElseThrow(Supplier<? extends X> exceptionSupplier):如果当前Optional不为空返回value,为空抛出指定的异常

String s3 = worldOp.orElseThrow(() -> new Exception("world is null"));
System.out.println(s3);
String s4 = helloOp.orElseThrow(() -> new Exception("hello is null"));
System.out.println(s4);

基于以上Optional方法的了解,我们最终改写下实例中的代码:

public class Test04Upgrade {
    public static void main(String[] args) throws Exception {
        Optional<Computor3> optional = Optional.of(new Computor3());
        Optional<String> versionOp = optional.map(Computor3::getSoundCard)
        	.map(SoundCard3::getUsb).map(USB3::getVersion);
        String version = versionOp.orElse("version not exist");
        System.out.println(version);
    }
}

class Computor3 {
    private SoundCard3 soundCard;

    public SoundCard3 getSoundCard() {
        return soundCard;
    }

    public void setSoundCard(SoundCard3 soundCard) {
        this.soundCard = soundCard;
    }
}

class SoundCard3 {
    USB3 usb;

    public USB3 getUsb() {
        return usb;
    }

    public void setUsb(USB3 usb) {
        this.usb = usb;
    }
}

class USB3 {
    String version;

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }
}

时间

新的的时间API设计原则:

  • Clear:接口定义清晰,新的时间API不允许任何null的赋值,也不会返回null
  • Fluent:链式编程,LocalDate.now().minusYears(1).plusMonths(2);
  • Immutable:新的时间初始化后都是不可变的,所有通过方法产生的新的时间都会产生一个老时间的副本。在LocalDate和LocalTime中的属性year、month、day、hour、minute、second、nano都被final修饰,也就是只能被赋值一次。

image.png
image.png

  • Extensible:可扩展的,可以自己定义自己的日历系统,如MinguoDate、JapaneseDate。

常用方法:

now

获取当前时间。

// 获取当前的日期:yyyy-MM-dd
LocalDate now = LocalDate.now();
// 获取当前时间:HH:mm:ss
LocalTime now1 = LocalTime.now();
// 获取当前年月日时分秒:yyyy-MM-ddTHH:mm:ss.n
LocalDateTime now2 = LocalDateTime.now();

/**
 * 控制台输出:
 * 2024-01-09
 * 22:13:10.323
 * 2024-01-09T22:13:10.323
 */


of

根据指定的参数:年、月、日、时、分、秒、纳秒创建时间。

// 根据指定的年月日创建日期
LocalDate localDate = LocalDate.of(2024, 1, 9);
// 根据指定的时分秒创建时间
LocalTime localTime = LocalTime.of(23, 59, 59);
// 根据指定的年月日时分秒创建时间
LocalDateTime localDateTime = LocalDateTime.of(2024, 1, 9, 23, 59, 59);

/**
 * 控制台输出:
 *  2024-01-09
 *  23:59:59
 *  2024-01-09T23:59:59
 */

from

将一个 TemporalAccessor 对象转换成时间对象。比如将ZonedDateTime 转换成 LocalDateTime。

// America/Chicago  美国芝加哥 UTC/GMT-6(西六区)
ZonedDateTime chicagoDateTime = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("America/Chicago"));
System.out.println(chicagoDateTime);
LocalDate from = LocalDate.from(chicagoDateTime);
System.out.println(from);
LocalTime from1 = LocalTime.from(chicagoDateTime);
System.out.println(from1);
LocalDateTime from2 = LocalDateTime.from(chicagoDateTime);
System.out.println(from2);

/**
 * 控制台输出:
 *  2024-01-09T22:13:10.323-06:00[America/Chicago]
 *  2024-01-09
 *  22:13:10.323
 *  2024-01-09T22:13:10.323
 */

parse

根据指定的格式,把指定的字符串时间转成成时间对象。

// 把字符串格式的时间转换成LocalDate,字符串格式必须是:yyyy-MM-dd
LocalDate parse = LocalDate.parse("2024-01-09");
System.out.println(parse);
// 根据指定的格式把字符串转换成LocalDate
LocalDate parse3 = LocalDate.parse("2024/01/09", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
System.out.println(parse3);

// 把字符串格式的时间转换成LocalTime,字符串格式必须是:HH:mm:ss
LocalTime parse1 = LocalTime.parse("23:23:23");
System.out.println(parse1);
// 根据指定的格式把字符串转换成LocalTime
LocalTime parse4 = LocalTime.parse("22-22-22", DateTimeFormatter.ofPattern("HH-mm-ss"));
System.out.println(parse4);
// 把字符串格式的时间转换成LocalDateTime,字符串格式必须是:yyyy-MM-ddTHH:mm:ss
LocalDateTime parse2 = LocalDateTime.parse("2023-01-31T23:23:23");
System.out.println(parse2);

// 根据指定的格式把字符串换成成LocalDateTime
LocalDateTime parse5 = LocalDateTime.parse("2024-01-09T20:10:58.421",
        DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.n"));
System.out.println(parse5);

/*
控制台输出:
	2024-01-09
    2024-01-09
    23:23:23
    22:22:22
    2023-01-31T23:23:23
    2024-01-09T20:10:58.000000421
*/

format

把时间对象转换成指定格式的时间字符串。

// 把时间对象转换成指定格式的时间字符串
String format1 = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
System.out.println(format1);

String hHmmss = LocalTime.now().format(DateTimeFormatter.ofPattern("HHmmss"));
System.out.println(hHmmss);

String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
System.out.println(format);

/*
控制台输出:
	2024/01/09
    221310
    2024/01/09 22:13:10
*/

get

获取时间对象中的部分,比如获取年、月、日、时、分、秒、星期等。

// 年
int year = LocalDateTime.now().get(ChronoField.YEAR);
System.out.println(year);
// 月
int month = LocalDateTime.now().get(ChronoField.MONTH_OF_YEAR);
System.out.println(month);
// 日
int day = LocalDateTime.now().get(ChronoField.DAY_OF_MONTH);
System.out.println(day);
// 星期
int week = LocalDateTime.now().get(ChronoField.DAY_OF_WEEK);
System.out.println(week);

/*
控制台输出:
	2024
    1
    9
    2
*/

isXXX

  • isBefore:两个时间比较,对比当前时间是否是指定时间之前。
LocalDateTime of = LocalDateTime.of(2023, 2, 1, 23, 23, 23);
// 一个时间是否是另外一个时间之前
boolean before = LocalDateTime.now().isBefore(of);
System.out.println(before);

/*
控制台输出:
	false
*/
  • isAfter:两个时间比较,对比当前时间是否是指定时间之后。
LocalDateTime of = LocalDateTime.of(2023, 2, 1, 23, 23, 23);
// 一个时间是否是另外一个时间之后
boolean before = LocalDateTime.now().isAfter(of);
System.out.println(before);

/*
控制台输出:
	true
*/
  • isEqual:两个时间比较,对比两个时间是否相等。
// 两个时间是否一样
boolean equal = LocalDateTime.now().isEqual(LocalDateTime.now());
System.out.println(equal);

/*
控制台输出:
	true
*/
  • isSupported:检查是否支持指定的单位或字段。
// 时间对象是否支持指定的单位或字段
boolean supported = LocalDate.now().isSupported(ChronoField.HOUR_OF_DAY);
System.out.println(supported);

/*
控制台输出:
	false
*/

with
Returns a copy of this date-time with the specified field set to a new value.

返回一个当前时间对象的副本,并把指定字段设置成一个新值。

// 修改年
LocalDateTime with = LocalDateTime.now().with(ChronoField.YEAR, 2020);
System.out.println(with);

// 修改月
LocalDateTime localDateTime1 = LocalDateTime.now().withMonth(12);
System.out.println(localDateTime1);

// 修改月
LocalDateTime with1 = LocalDateTime.now().with(Month.JULY);
System.out.println(with1);
/*
控制台输出:
	2020-01-09T22:13:10.337
    2024-12-09T22:13:10.338
    2024-07-09T22:13:10.338
*/

plus

Returns a copy of this date-time with the specified amount added.
返回一个当前时间对象的副本,并把指定的字段增加指定的值。

// 当前时间往后1年
LocalDateTime plus = LocalDateTime.now().plus(1, ChronoUnit.YEARS);
System.out.println(plus);
LocalDateTime plus1 = LocalDateTime.now().plus(Period.ofYears(1));
System.out.println(plus1);
// 或者可直接+1年
LocalDateTime plus2 = LocalDateTime.now().plusYears(1);
System.out.println(plus2);

/*
控制台输出:
	2025-01-09T23:30:26.918
    2025-01-09T23:30:26.918
    2025-01-09T23:30:26.918
*/

minus

Returns a copy of this date-time with the specified amount subtracted.
返回一个当前时间对象的副本,并把指定的字段减去指定的值。

// 当前时间前1月
LocalDateTime minus = LocalDateTime.now().minus(1, ChronoUnit.MONTHS);
System.out.println(minus);
LocalDateTime minus1 = LocalDateTime.now().minus(Period.ofMonths(1));
System.out.println(minus1);
// 或者可直接-1月
LocalDateTime minus2 = LocalDateTime.now().minusMonths(1);
System.out.println(minus2);

/*
控制台输出:
	2023-12-09T23:33:49.483
    2023-12-09T23:33:49.483
    2023-12-09T23:33:49.483
*/

toXXX

  • toLocalDate:当前时间转换成日期
// 把指定的时间转成的日期对象
LocalDate localDate1 = LocalD2ateTime.now().toLocalDate();
  • toLocalTime:当前时间转换成时间
// 把指定的时间转成成时间对象
LocalTime localTime1 = LocalDateTime.now().toLocalTime();
  • toInstant:当前时间抓换成瞬时时间(时间戳)。往前推或者完后推的时间范围是18个小时以内(包含18个小时)
 // 当前默认时区-1
Instant instant = LocalDateTime.now().toInstant(ZoneOffset.of("-1"));
System.out.println(instant);
// 时间戳(毫秒)
System.out.println(instant.getEpochSecond());
// 时间戳(纳秒)
System.out.println(System.currentTimeMillis());

/**
*   {@code +h} : 往后推h个小时
*   {@code +hh}: 往后推hh个小时
*   {@code +hh:mm}: 往后推hh个小时,mm分钟
*   {@code -hh:mm}: 往前推hh个小时,mm分钟
*   {@code +hhmm}: 往后推hh个小时,mm分钟
*   {@code -hhmm}: 往前推hh个小时,mm分钟
*   {@code +hh:mm:ss}: 往后推hh个小时,mm分钟,ss秒
*   {@code -hh:mm:ss}: 往前推hh个小时,mm分钟,ss秒
*   {@code +hhmmss}: 往后推hh个小时,mm分钟,ss秒
*   {@code -hhmmss}: 往前推hh个小时,mm分钟,ss秒
*/
Instant z = LocalDateTime.now().toInstant(ZoneOffset.of("Z"));
System.out.println(z);
// +hh:mm: 指定的时间往前推hh小时,mm分钟
Instant instant1 = LocalDateTime.now().toInstant(ZoneOffset.of("+01:22"));
System.out.println(instant1);
Instant instant2 = LocalDateTime.now().toInstant(ZoneOffset.of("+0122"));
System.out.println(instant2);
  • toEpochSecond:转换成指定时区的时间戳
 // 转换成指定时区的时间戳
long toEpochSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+9"));
System.out.println(toEpochSecond);

atXXX

  • atOffset:把当前时间转换成指定时区的时间
// 把当前时间转换成指定时区的时间
OffsetDateTime offsetDateTime = LocalDateTime.now().atOffset(ZoneOffset.of("+10"));
System.out.println(offsetDateTime);

  • atZone:把当前时间转换成指定时区的时间
// 把当前时间转换成指定时区的时间
ZonedDateTime zonedDateTime = LocalDateTime.now().atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDateTime);

备注

https://www.oracle.com/java/technologies/javase/8-whats-new.html

  • 48
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值