Java 8特性学习笔记

Lambda表达式

Arrays.asList( "a", "b", "d" ).forEach(e -> System.out.println( e ) );

在#forEach中的“e -> System.out.println( e )”,即为Lambda表达式。

上代码也可写为:

Arrays.asList( "a", "b", "d" ).forEach((String e)->{
    System.out.println(e);
});

以“->”为界,之前的“e”为参数,参数的类型会自动匹配。

之后的为逻辑运算等操作。

之后的运算可以当做一个方法来写。

例如:

历遍一个String数组,将其中的值赋值到另一个数组“newArrStr”中。

ArrayList<String> newArrStr = new ArrayList<>();
Arrays.asList("a","b","c","d").forEach(e ->newArrStr.add(e));
newArrStr.forEach(e-> System.out.println(e));

接口添加静态方法和默认方法

默认方法

在接口中使用“default”关键字标注的方法为默认方法。

在其它类实现某个接口时,其中的默认方法可以直接调用,也可以重写。

例如:

新建一个接口,其中包含一个默认方法。

public interface DefaultAble {
    default String notRequired(){
        return "默认方法";
    }
}

新建一个类直接实现此接口。

public class useDefaultImpl implements DefaultAble{

    public static void main(String[] args) {
        useDefaultImpl useDefault = new useDefaultImpl();
        System.out.println(useDefault.notRequired());
    }

}
/*
out:
默认方法
 */

再写一个类,实现此接口并重写其中的默认方法。

public class overrideDefaultImpl implements DefaultAble {

    @Override
    public String notRequired(){
        return "重写默认方法";
    }

    public static void main(String[] args) {
        overrideDefaultImpl overrideDefault = new overrideDefaultImpl();
        System.out.println(overrideDefault.notRequired());
    }

}
/*
out:
重写默认方法
 */

默认方法不强制要求被实现。

但是值得注意的是默认方法可能会引起歧义或者编译错误。

静态方法

存在于接口中的静态方法,可以直接调用。

例如:

创建一个接口,其中有一个静态方法。

public interface DefaultAbleFactory {

    /**
     * Supplier类用于返回T类型的一个对象
     * 调用一次,返回一次。
     * @param supplier
     * @return
     */
    static DefaultAble create(Supplier<DefaultAble> supplier){
        return supplier.get();
    }

}

然后,创建一个类来调用此接口中的静态方法。

public class StaticMethodsExample {

    public static void main(String[] args) {
        DefaultAble defaultAble = DefaultAbleFactory.create(useDefaultImpl::new);
        System.out.println(defaultAble.notRequired());
        defaultAble = DefaultAbleFactory.create(overrideDefaultImpl::new);
        System.out.println(defaultAble.notRequired());
    }

}
/*
out:
默认方法
重写默认方法
 */

方法引用

在部分时候可以代替“.”来引用方法。

使用方法及注意点如下:

public class MethodReferences {

    public static void main(String[] args) {

        //引用无参构造器
        Apple apple = Apple.create(Apple::new);
        List<Apple> apples = Arrays.asList(apple);
        //引用静态方法
        apples.forEach(Apple::pick);
        //此处的静态方法必须带参
        //apples.forEach(Apple::recover);
        //引用成员方法
        apples.forEach(Apple::buy);
        //应用实例对象的成员方法(必须带参)
        Apple seller = Apple.create(Apple::new);
        apples.forEach(seller::sell);
    }

    //内部类如果有静态方法,则必须为静态内部类
    private static class Apple{

        public static Apple create(final Supplier<Apple> supplier){
            return  supplier.get();
        }

        public static void pick(final Apple apple){
            System.out.println("苹果从树上摘了下来"+apple.toString());
        }

        public static void recover(){
            System.out.println("苹果又被按回了树上");
        }

        public void sell(final Apple apple){
            System.out.println("苹果在商店被出售"+apple.toString());
        }

        public void buy(){
            System.out.println("苹果被卖出");
        }
    }

}
/*
out:
苹果从树上摘了下来Java8.MethodReferences$Apple@548c4f57
苹果被卖出
苹果在商店被出售Java8.MethodReferences$Apple@548c4f57
 */

重复注解

在引入重复注解技术之前,在很多地方同一个注解只可以使用一次。

以下是重复注解的例子:

public class RepeatingAnnotations {

    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }

    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    }

    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {
    }

    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}
/*
out:
filter1
filter2
 */

在例子中可以看到“@Filter”注解被使用了两次。

引入Optional避免空指针

简单的使用方法和使用注意如下。

代码样例:

public class OptionalExample {

    public static void main(String[] args) {

        //#ofNullable的参数如果是空则会返回一个Empty的对象,如果不是空则会调用Optional#Of来返回一个新的对象
        Optional<String> fullName = Optional.ofNullable(null);

        //如果当前对象不为空则会返回true
        System.out.println("fullName 不为空吗?" + fullName.isPresent());

        //如果当前对象为空则返回true
        System.out.println("fullName 是否为空?" + fullName.isEmpty());

        //map可以接收Lambda表达式进行其它的逻辑,#orElse所属对象如果为空则会调用“other”(即参数),不为空时则会调用当前对象
        System.out.println(fullName.map(s->"Hey" + s + "!").orElse("Hey Stranger!"));

        //Option#of会返回新的赋值为参数的Optional对象
        Optional<String> firstName = Optional.of("Tom");
        //Optional<String> firstName = Optional.ofNullable(null);

        System.out.println("firstName 不为空吗?" + firstName.isPresent());

        System.out.println("firstName 是否为空?" + firstName.isEmpty());

        //如果目标对象为空则调用参数,参数可为Lambda表达式
        //Optional#orElseGet的实现是用Supplier#Get来实现的,也就是说如果目标为空则直接获取参数对象
        System.out.println("firstName: "+firstName.orElseGet(()->"[none]"));

        System.out.println(firstName.map(s->"Hey " + s + "!").orElse("Hey Stranger!"));

        //使用样例
        String[] testArr = new String[3];
        testArr[0] = "aaa";
        testArr[1] = null;
        testArr[2] = "bbb";
        for (String temp :
                testArr) {
            System.out.println("当前元素为空吗?" + Optional.ofNullable(temp).isEmpty());
        }
    }

}
/*
out:
fullName 不为空吗?false
fullName 是否为空?true
Hey Stranger!
firstName 不为空吗?true
firstName 是否为空?false
firstName: Tom
Hey Tom!
 */

引入Streams相关API

在Java8之前,如果要遍历一个集合中的数据,可以使用迭代器或者for或者while循环来进行。

Java8中则对StreamsAPI进行了优化使得其更加容易使用。

以下为简单的介绍和示例:

public class StreamExample {

    public static void main(String[] args) {

        //创建一个集合
        final Collection<Streams.Task> tasks = Arrays.asList(
                new Streams.Task(Streams.Status.OPEN, 7),
                new Streams.Task(Streams.Status.OPEN, 13),
                new Streams.Task(Streams.Status.CLOSED, 8)
        );

        //计算处于某种状态的某个属性总和
        final long totalPonitsOfOpenTasks = tasks
                //调用stream
                .stream()
                //设置条件
                .filter(task -> task.getStatus() == Streams.Status.OPEN)
                //获取数值(Int型)
                .mapToInt(Streams.Task::getPoints)
                //算加和或者其他
                .sum();
        System.out.println("总共处于OPEN状态的点:" + totalPonitsOfOpenTasks);

        //计算某个属性的总和
        final long totalPoints = tasks
                .stream()
                .parallel()
                .map(task -> task.getPoints())
                .reduce(0, Integer::sum);
        System.out.println("全部点数之和:" + totalPoints);

        //按照某个属性分组
        final Map< Streams.Status, List<Streams.Task>> groupByStatus = tasks
                .stream()
                .collect(Collectors.groupingBy(Streams.Task::getStatus));
        System.out.println("按照Status分组结果:" + groupByStatus);

        //计算某一属性(数字)占总体的比例
        final Collection<String> result = tasks
                .stream()
                .mapToInt(Streams.Task::getPoints)
                //转换类型
                .asLongStream()
                .mapToDouble(points->{
                    //不可直接使用points和totalPoints进行运算
                    //因为两个变量均为Long类型,Long即为大整数类型,所以结果和int类型相除类似,没有小数位
                    double tempPointVal = points;
                    double tempTotalVal = totalPoints;
                    return tempPointVal/tempTotalVal;
                })
                //对类型进行装箱操作,即,转换为包装类
                .boxed()
                .mapToLong(weight->(long)(weight*100))
                .mapToObj(percentage->percentage+"%")
                .collect(Collectors.toList());
        System.out.println("各元素所占比例:" + result);

        //又一个示例
        ArrayList<String> testArrInt = new ArrayList<>();
        testArrInt.add("1");
        testArrInt.add("2");
        testArrInt.add("3");
        testArrInt.add("4");
        testArrInt.add("5");
        testArrInt.add("10");
        long sumArr = testArrInt
                .stream()
                .mapToInt(Integer::parseInt)
                .sum();
        System.out.println("数组加和:" + sumArr);
    }

}

补充

并行流

在遍历某个数组时,for循环是最常见也是最普通的,但是相对于流操作来说速度也是要慢很多的。

在字面上看,似乎并行流也应该与并行数组一样,比串行处理快很多,但是从实验结果看并非如此。

串行流快还是并行流快是分场合的。

以下是示例代码:

public class StreamExampleTwo {

    public static int aimInt = 0;

    public static void doSome(){
        aimInt++;
    }

    public static void main(String[] args) {

        //运行次数
        int runTimes = 1000;
        //每次每个方法遍历的次数
        int arrMaxSize = 10000;
        //错误值
        int lostTImes = 0;

        double aveFTime = 0;
        double aveFSTime = 0;
        double avePSOTime = 0;
        double avePSTime = 0;

        boolean isFCorrect = true;
        boolean isSCorrect = true;
        boolean isPSOCorrect = true;
        boolean isPSCorrect = true;

        //检测指标
        aimInt = 0;
        //指标正确的值
        int correctAimInt = 4*runTimes*arrMaxSize;
        Integer[] arrInt = new Integer[arrMaxSize];

        double startTime;
        double endTime;

        //测试数组赋值
        startTime = System.currentTimeMillis();
        Arrays.parallelSetAll(
                arrInt,
                index -> ThreadLocalRandom.current().nextInt(100)
        );
        endTime = System.currentTimeMillis();
        System.out.println("parallelSetAll执行时间:" + (endTime - startTime) + "ms");

        List<Integer> integerList = Arrays.asList(arrInt);

        for (int i = 0; i < runTimes;i++){
            System.out.println("第" + (i + 1) + "执行开始!");

            //普通for循环遍历
            startTime = System.currentTimeMillis();
            for (Integer coursor : integerList){
                doSome();
            }
            endTime = System.currentTimeMillis();
            System.out.println("for循环遍历执行时间:" + (endTime - startTime) + "ms");
            aveFTime += (endTime - startTime);
            if((i * 4 * arrMaxSize + arrMaxSize - lostTImes) != aimInt){
                isFCorrect = false;
                lostTImes = (i * 4 * arrMaxSize + arrMaxSize) - aimInt;
                System.out.println("for循环出错,No." + (i+1) + ",错误值:" + aimInt);
            }
            System.out.println("for循环后的目标Integer为:" + aimInt + ",本次耗时:" + (endTime - startTime) + "ms");

            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //普通流遍历
            startTime = System.currentTimeMillis();
            integerList.stream().forEach(coursor ->doSome());
            endTime = System.currentTimeMillis();
            System.out.println("普通流遍历执行时间:" + (endTime - startTime) + "ms");
            aveFSTime += (endTime - startTime);
            if(i * 4 * arrMaxSize + arrMaxSize * 2 - lostTImes!= aimInt){
                isSCorrect = false;
                lostTImes = (i * 4 * arrMaxSize + arrMaxSize * 2) - aimInt;
                System.out.println("普通流出错,No." + (i+1) + ",错误值:" + aimInt);
            }
            System.out.println("普通流遍历执行后的目标Integer为:" + aimInt + ",本次耗时:" + (endTime - startTime) + "ms");

            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //并行流遍历
            startTime = System.currentTimeMillis();
            integerList.parallelStream().forEachOrdered(coursor ->doSome());
            endTime = System.currentTimeMillis();
            System.out.println("并行流顺序遍历执行时间:" + (endTime - startTime) + "ms");
            avePSOTime += (endTime - startTime);
            if(i * 4 * arrMaxSize + arrMaxSize * 3 - lostTImes!= aimInt){
                isPSOCorrect = false;
                lostTImes = (i * 4 * arrMaxSize + arrMaxSize * 3) - aimInt;
                System.out.println("并行流顺序出错,No." + (i+1) + ",错误值:" + aimInt);
            }
            System.out.println("并行流顺序遍历执行后的目标Integer为:" + aimInt + ",本次耗时:" + (endTime - startTime) + "ms");

            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            startTime = System.currentTimeMillis();
            integerList.parallelStream().forEach(coursor ->doSome());
            endTime = System.currentTimeMillis();
            System.out.println("并行流非顺序遍历执行时间:" + (endTime - startTime) + "ms");
            avePSTime += (endTime - startTime);
            if(i * 4 * arrMaxSize + arrMaxSize * 4 - lostTImes!= aimInt){
                isPSCorrect = false;
                lostTImes = (i * 4 * arrMaxSize + arrMaxSize * 4) - aimInt;
                System.out.println("并行流非顺序出错,No." + (i+1) + ",错误值:" + aimInt);
            }
            System.out.println("并行流非顺序遍历执行的目标Integer为:" + aimInt + ",本次耗时:" + (endTime - startTime) + "ms");

            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("第" + (i + 1) + "执行结束!");
            System.out.println();
        }

        System.out.println("普通for循环遍历 " + runTimes + " 次,平均用时为:" + aveFTime/runTimes + "ms" + ",总耗时为:" + aveFTime + "ms");
        System.out.println("普通流循环遍历 " + runTimes + " 次,平均用时为:" + aveFSTime/runTimes + "ms" + ",总耗时为:" + aveFSTime + "ms");
        System.out.println("并行流顺序循环遍历 " + runTimes + " 次,平均用时为:" + avePSOTime/runTimes + "ms" + ",总耗时为:" + avePSOTime + "ms");
        System.out.println("并行流非顺序循环遍历 " + runTimes + " 次,平均用时为:" + avePSTime/runTimes + "ms" + ",总耗时为:" + avePSTime + "ms");
        System.out.println("最终应为Integer为:" + correctAimInt);
        System.out.println("最终实际Integer为:" + aimInt);
        System.out.println("是否有错误?" + (aimInt == correctAimInt ? "没有错误":"有错误,错误次数:" + (correctAimInt - aimInt)));
        if(!isFCorrect){
            System.out.println("普通for循环出错");
        }
        if(!isSCorrect){
            System.out.println("普通流出错");
        }
        if(!isPSOCorrect){
            System.out.println("并行流顺序遍历出错");
        }
        if(!isPSCorrect){
            System.out.println("并行流非顺序遍历出错");
        }

    }

}

以下是执行3次,每次循环10次的结果:

普通for循环遍历 3 次,平均用时为:0.3333333333333333ms,总耗时为:1.0ms
普通流循环遍历 3 次,平均用时为:0.0ms,总耗时为:0.0ms
并行流顺序循环遍历 3 次,平均用时为:1.3333333333333333ms,总耗时为:4.0ms
并行流非顺序循环遍历 3 次,平均用时为:0.3333333333333333ms,总耗时为:1.0ms
最终应为Integer为:120
最终实际Integer为:120
是否有错误?没有错误

可以看到差别并不明显。

以下是执行10次,每次循环10000次的结果:

普通for循环遍历 10 次,平均用时为:0.1ms,总耗时为:1.0ms
普通流循环遍历 10 次,平均用时为:0.1ms,总耗时为:1.0ms
并行流顺序循环遍历 10 次,平均用时为:0.6ms,总耗时为:6.0ms
并行流非顺序循环遍历 10 次,平均用时为:0.1ms,总耗时为:1.0ms
最终应为Integer为:400000
最终实际Integer为:378000
是否有错误?有错误,错误次数:22000
并行流非顺序遍历出错

可以看到,这种情况下并行流开始出现错误,但是在是在时间上开始出现。

当执行次数为1000次,每次循环次数为10000次的结果:

普通for循环遍历 1000 次,平均用时为:0.003ms,总耗时为:3.0ms
普通流循环遍历 1000 次,平均用时为:0.003ms,总耗时为:3.0ms
并行流顺序循环遍历 1000 次,平均用时为:0.017ms,总耗时为:17.0ms
并行流非顺序循环遍历 1000 次,平均用时为:0.004ms,总耗时为:4.0ms
最终应为Integer为:40000000
最终实际Integer为:37033984
是否有错误?有错误,错误次数:2966016
并行流非顺序遍历出错

此时的并行流的执行速度总体不如串行流或者 for循环。

但是当循环次数为1,数组循环次数为100,000,000时的结果为:

普通for循环遍历 1 次,平均用时为:183.0ms,总耗时为:183.0ms
普通流循环遍历 1 次,平均用时为:92.0ms,总耗时为:92.0ms
并行流顺序循环遍历 1 次,平均用时为:1654.0ms,总耗时为:1654.0ms
并行流非顺序循环遍历 1 次,平均用时为:865.0ms,总耗时为:865.0ms
最终应为Integer为:400000000
最终实际Integer为:335420276
是否有错误?有错误,错误次数:64579724
并行流非顺序遍历出错

此时的耗时出现了比较大的差异,普通串行流耗时最小,其次for循环,最慢且容易出错的为并行流顺序访问。

但是,这就说明并行流就一定不好了吗?

这次,将要做的任务为求一个数组值的和。

这次的任务更适合划分为小任务来做。

以下是另一段代码示例:

public class StreamExampleThree {

    public static void main(String[] args) {

        int arrMaxSize = 1000000000;
        Integer[] arrInt = new Integer[arrMaxSize];

        double startTime;
        double endTime;

        //测试数组赋值
        startTime = System.currentTimeMillis();
        Arrays.parallelSetAll(
                arrInt,
                index -> ThreadLocalRandom.current().nextInt(3)
        );
        endTime = System.currentTimeMillis();
        System.out.println("parallelSetAll执行时间:" + (endTime - startTime) + "ms");

        List<Integer> integerList = Arrays.asList(arrInt);

        //for循环
        long sumOne = 0;
        startTime = System.currentTimeMillis();
        for (Integer coursor : integerList){
            sumOne += coursor;
        }
        endTime = System.currentTimeMillis();
        System.out.println("for循环计算结果:" + sumOne);
        System.out.println("for循环执行时间:" + (endTime - startTime) + "ms");

        //串行流
        long sumTwo = 0;
        startTime = System.currentTimeMillis();
        sumTwo = integerList
                .stream()
                .mapToInt(coursor -> coursor)
                .sum();
        endTime = System.currentTimeMillis();
        System.out.println("串行流计算结果:" + sumTwo);
        System.out.println("串行流执行时间:" + (endTime - startTime) + "ms");

        //并行流
        long sumThree = 0;
        startTime = System.currentTimeMillis();
        sumThree = integerList
                .parallelStream()
                .mapToInt(coursor -> coursor)
                .sum();
        endTime = System.currentTimeMillis();
        System.out.println("并行流计算结果:" + sumThree);
        System.out.println("并行流执行时间:" + (endTime - startTime) + "ms");

    }

}

当要求和数组大小为10时,实验结果:

parallelSetAll执行时间:10.0ms
for循环计算结果:9
for循环执行时间:0.0ms
串行流计算结果:9
串行流执行时间:1.0ms
并行流计算结果:9
并行流执行时间:1.0ms

当要求和数组大小为10000时,实验结果:

parallelSetAll执行时间:9.0ms
for循环计算结果:10114
for循环执行时间:1.0ms
串行流计算结果:10114
串行流执行时间:3.0ms
并行流计算结果:10114
并行流执行时间:1.0ms

当要求和数组大小为10000000时,实验结果:

parallelSetAll执行时间:96.0ms
for循环计算结果:10001226
for循环执行时间:24.0ms
串行流计算结果:10001226
串行流执行时间:17.0ms
并行流计算结果:10001226
并行流执行时间:17.0ms

当要求和数组大小为1000000000时,实验结果:

parallelSetAll执行时间:6130.0ms
for循环计算结果:999995720
for循环执行时间:1407.0ms
串行流计算结果:999995720
串行流执行时间:868.0ms
并行流计算结果:999995720
并行流执行时间:288.0ms

从这四组实验可以看出,当面对超大数组求和时,并行流的处理效率是非常高的。

第一次实验选择的任务,是对并行处理极不友好的,即求递增数值,这种任务不容易划分为多个小任务。

而第二次实验,是对并行处理比较友好的,各个小任务之间没有直接的联系,最后的目标只需要将每个小任务的结果相加即可。

在真正的开发时,应该注意选择。

引入新的Date/Time相关API

处理时间和日期一直是最令Java开发者头疼的问题。

在Java8中引入了新的Date-Time API(JSR 310)来改进时间和日期的处理。

以下是示例代码和一部分说明:

public class TimeAndDateExample {

    public static void main(String[] args) {

        //获取系统世界统一时间
        final Clock clock = Clock.systemUTC();

        System.out.println("clock#instant:" + clock.instant());
        System.out.println("clock#millis:" + clock.millis());

        //获取本地日期
        final LocalDate date = LocalDate.now();
        final LocalDate dateFromClock = LocalDate.now(clock);

        System.out.println("LocalDate#now:" + date);
        System.out.println("LocalDate#now(clock):" + dateFromClock);

        //获取本地时间
        final LocalTime time = LocalTime.now();
        final LocalTime timeFromClock = LocalTime.now(clock);

        System.out.println("LocalTime#now:" + time);
        System.out.println("LocalTime#now(clock):" + timeFromClock);

        //同时获取本地日期和时间
        final LocalDateTime dateTime = LocalDateTime.now();
        final LocalDateTime dateTimeFromClock = LocalDateTime.now(clock);

        System.out.println("LocalDateTime#now:" + dateTime);
        System.out.println("LocalDateTime#now(clock):" + dateTimeFromClock);

        //获取特定时区的时间和日期
        final ZonedDateTime zonedDateTime = ZonedDateTime.now();
        final ZonedDateTime zonedDateTimeFromClock = ZonedDateTime.now(clock);
        final ZonedDateTime zonedDateTimeFromZone = ZonedDateTime.now(ZoneId.of("America/Chicago"));

        System.out.println("ZonedDateTime#now;" + zonedDateTime);
        System.out.println("ZonedDateTime#now(clock):" + zonedDateTimeFromClock);
        System.out.println("ZonedDateTime#now(ZoneId#of);" + zonedDateTimeFromZone);

        //计算两日期之间相差天数
        final LocalDateTime from = LocalDateTime.of(1999, Month.JULY, 18, 16, 0,0);
        //可以直接指定目标或者起始时间
        //final LocalDateTime to = LocalDateTime.of(2019, Month.AUGUST, 18, 16, 0,0);
        //也可以直接从系统中获取
        final LocalDateTime to = LocalDateTime.now();

        final Duration duration = Duration.between(from, to);

        System.out.println("相差天数:" + duration.toDays());
        System.out.println("相差小时数:" + duration.toHours());
    }

}

Nashorn JavaScript引擎

Java8提供了新的Nashorn JavaScript引擎,这允许我们在JVM开发并运行JS应用。

这使得Java可以与JavaScript进行交互。

以下是示例代码和一部分说明:

public class NashornJavaScriptEngineExample {

        public static void main(String[] args) {

        //新建实例对象
        ScriptEngineManager manager = new ScriptEngineManager();
        //获取JavaScript引擎
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        System.out.println("获取的Class名称:" + engine.getClass().getName());
        String scriptStr = "" +
                "function f(){" +
                "   return 20" +
                "};" +
                "f() + 20";
        try {
            System.out.println("简单的运行结果:" + engine.eval(scriptStr));
        } catch (ScriptException e) {
            e.printStackTrace();
        }

    }

}
/*
out:
Warning: Nashorn engine is planned to be removed from a future JDK release
获取的Class名称:jdk.nashorn.api.scripting.NashornScriptEngine
简单的运行结果:40.0
 */

在JDK 12.0.2版本中可以看到这样的警告:

Warning: Nashorn engine is planned to be removed from a future JDK.

意为在未来的JDK的发行版本Nashorn JavaScript引擎将会被移除。

新增Base64 API

什么是Base64编码?

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。

Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。采用Base64编码具有不可读性,需要解码后才能阅读。

在Java8中开发者可以直接进行Base64编码,而不需要第三方库。

以下是示例代码:

public class Base64Example {

    public static void main(String[] args) {

        final String text = "Base64 in Java 8~~~~";
        System.out.println("初始字符串:" + text);

        //获取编码器,并设置要转换字符串的字符集,最后将目标字符串转换为Byte数组
        final String encoded = Base64
                .getEncoder()
                .encodeToString(text.getBytes(StandardCharsets.UTF_8));
        System.out.println("编码之后的字符串:" + encoded);

        //获取解码器,调用解码方法解码目标字符串,设置解码之后字符串的字符集
        final String decoded = new String(
                Base64.getDecoder().decode(encoded),
                StandardCharsets.UTF_8
        );
        System.out.println("解码后的字符串:" + decoded);
    }

}

值得注意的是:BASE64不是用来加密的,是BASE64编码后的字符串,全部都是由标准键盘上面的常规字符组成,这样编码后的字符串在网关之间传递不会产生UNICODE字符串不能识别或者丢失的现象。

并行数组

Java8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。

以下为示例代码:

public class ParabllelArrayExample {

    public static void main(String[] args) {

        int arrMaxSize = 400000;
        int[] longArr = new int[arrMaxSize];
        int[] comparisonArr = new int[arrMaxSize];

        long startTime;
        long endTime;
        MergeSort mergeSort = new MergeSort();

        startTime = System.currentTimeMillis();
        Arrays.parallelSetAll(
                longArr,
                index -> ThreadLocalRandom.current().nextInt(1000000)
        );
        endTime = System.currentTimeMillis();
        System.out.println("parallelSetAll执行时间:" + (endTime - startTime) + "ms");

        startTime = System.currentTimeMillis();
        for (int i=0;i<arrMaxSize;i++){
            comparisonArr[i] = ThreadLocalRandom.current().nextInt(1000000);
        }
        endTime = System.currentTimeMillis();
        System.out.println("使用for循环SetAll执行时间:" + (endTime - startTime) + "ms");

        startTime = System.currentTimeMillis();
        Arrays.parallelSort(longArr);
        endTime = System.currentTimeMillis();
        System.out.println("parallelSort执行时间:" + (endTime - startTime) + "ms");

        //复杂度为nlogn的归并排序作为对比
        startTime = System.currentTimeMillis();
        mergeSort.mergeSort(comparisonArr, 0, comparisonArr.length-1);
        endTime = System.currentTimeMillis();
        System.out.println("归并排序执行时间:" + (endTime - startTime) + "ms");
    }

}
/*
out:
parallelSetAll执行时间:24ms
使用for循环SetAll执行时间:10ms
parallelSort执行时间:104ms
归并排序执行时间:61174ms
 */

可以通过控制台输出的数据看到,虽然在赋值的时候使用Arrays#parallelSetAll可能比普通的for循环还要慢,但是在排序时与相对较低复杂度的归并算法比较,Arrays#parallelSort比归并算法快近600倍的。

补充

归并算法代码来自:

https://blog.csdn.net/qq_36442947/article/details/81612870

代码:

public class MergeSort {
    /**
     * 两路归并算法,两个排好序的子序列合并为一个子序列
     * @param a
     * @param left
     * @param mid
     * @param right
     */
    public void merge(int []a,int left,int mid,int right){
        //辅助数组
        int []tmp=new int[a.length];
        //p1、p2是检测指针,k是存放指针
        int p1=left,p2=mid+1,k=left;

        while(p1<=mid && p2<=right){
            if(a[p1]<=a[p2]){
                tmp[k++]=a[p1++];
            }
            else{
                tmp[k++]=a[p2++];
            }
        }

        while(p1<=mid){
            //如果第一个序列未检测完,直接将后面所有元素加到合并的序列中
            tmp[k++]=a[p1++];
        }

        while(p2<=right){
            //同上
            tmp[k++]=a[p2++];
        }

        //复制回原素组
        for (int i = left; i <=right; i++){
            a[i]=tmp[i];
        }
    }

    public void mergeSort(int [] a,int start,int end){
        //当子序列中只有一个元素时结束递归
        if(start<end){
            //划分子序列
            int mid=(start+end)/2;
            //对左侧子序列进行递归排序
            mergeSort(a, start, mid);
            //对右侧子序列进行递归排序
            mergeSort(a, mid+1, end);
            //合并
            merge(a, start, mid, end);
        }
    }

}

Array转List

数组转成List集合,最常见的有四种方法,其中第四种方法需要在Java 9或者更高版本使用。
以下是示例代码:

public class ArrayToListExample {

    public static void main(String[] args) {

        int arrMaxSize = 400000;
        Integer[] arrInt = new Integer[arrMaxSize];

        long startTime;
        long endTime;

        //测试数组赋值
        startTime = System.currentTimeMillis();
        Arrays.parallelSetAll(
                arrInt,
                index -> ThreadLocalRandom.current().nextInt(1000000)
        );
        endTime = System.currentTimeMillis();
        System.out.println("parallelSetAll执行时间:" + (endTime - startTime) + "ms");

        //第一种转换方法
        List<Integer> aimIntListOne = new ArrayList<>(arrInt.length);
        startTime = System.currentTimeMillis();
        for (int temp : arrInt){
            aimIntListOne.add(temp);
        }
        endTime = System.currentTimeMillis();
        System.out.println("使用for循环将数组转换为List执行时间:" + (endTime - startTime) + "ms");

        //第二种转换方法
        startTime = System.currentTimeMillis();
        //注意类型,即使是Integer和int的关系也不可以,都要用包装类
        List<Integer> aimIntListTwo = Arrays.asList(arrInt);
        endTime = System.currentTimeMillis();
        System.out.println("使用Arrays#asList将数组转换为List执行时间:" + (endTime - startTime) + "ms");
        System.out.println("List是否为空?" + (aimIntListTwo.isEmpty()? "空" : "否,大小:" + aimIntListTwo.size()));

        //第三种转换方式
        List<Integer> aimIntListThree = new ArrayList<>(arrInt.length);
        startTime = System.currentTimeMillis();
        Collections.addAll( aimIntListThree, arrInt);
        endTime = System.currentTimeMillis();
        System.out.println("使用Collections#addAll将数组转换为List执行时间:" + (endTime - startTime) + "ms");

        //第四种方式,Java版本要求大于等于9
        startTime = System.currentTimeMillis();
        List<Integer> aimIntListFour = List.of(arrInt);
        endTime = System.currentTimeMillis();
        System.out.println("使用List#of将数组转换为List执行时间:" + (endTime - startTime) + "ms");

    }

}
/*
out:
parallelSetAll执行时间:33ms
使用for循环将数组转换为List执行时间:33ms
使用Array#asList将数组转换为List执行时间:0ms
List是否为空?否,大小:400000
使用Collections#addAll将数组转换为List执行时间:10ms
使用List#of将数组转换为List执行时间:9ms
 */

List->Array

将List集合转换为数组,最常见的方式只有一种。

此种方法在使用时需要注意。

示例代码如下:

public class ListToArrayExample {

    public static void main(String[] args) {

        int arrMaxSize = 400000;
        Integer[] arrInt = new Integer[arrMaxSize];

        long startTime;
        long endTime;

        //测试数组赋值
        startTime = System.currentTimeMillis();
        Arrays.parallelSetAll(
                arrInt,
                index -> ThreadLocalRandom.current().nextInt(1000000)
        );
        endTime = System.currentTimeMillis();
        List<Integer> integerList = Arrays.asList(arrInt);
        System.out.println("parallelSetAll执行时间:" + (endTime - startTime) + "ms");

        //List转数组的第一种方法
        startTime = System.currentTimeMillis();
        //List#toArray返回的默认类型是Object类型,因此需要进行类型转换
        //这样可能会抛出java.lang.ClassCastException
        //Integer[] aimArrIntOne = (Integer[]) integerList.toArray();
        //更推荐这样使用
        Integer[] aimArrIntOne = integerList.toArray(new Integer[integerList.size()]);
        endTime = System.currentTimeMillis();
        System.out.println("使用List#toArray时List转数组执行时间:" + (endTime - startTime) + "ms");
    }

}
/*
out:
parallelSetAll执行时间:44ms
使用List#toArray时List转数组执行时间:1ms
 */
``
发布了24 篇原创文章 · 获赞 8 · 访问量 1872

猜你喜欢

转载自blog.csdn.net/qq_40462579/article/details/98384806