java8新特性

本文链接:https://github.com/wmyskxz/MoreThanJava/blob/master/java-base/java8.md

1.接口中的默认方法和静态方法

默认方法可以为接口添加一些默认的实现,实现接口时可以直接使用默认方法。

public interface TestInterface1 {
    default void sameMethod() { System.out.println("Invoke TestInterface1 method!"); }
}

静态方法:如果实现接口时,没有理由再额外提供一个带有实用方法的工具类

public interface StaticInterface {
    static void method() {
        System.out.println("这是Java8接口中的静态方法!");
    }
}

2.lambda表达式

Lambda 允许把函数作为一个方法的参数,即 行为参数化,函数作为参数传递进方法中。函数作为参数传递进方法中。

3.函数式接口

一个接口函数需要被实现的接口类型,我们叫它「函数式接口」。

例如

Comparator(比较器接口)

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

 Consumer的作用

给定义一个参数,对其进行(消费)处理,处理的方式可以是任意操作.

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

4.方法引用

可以直接通过方法引用来简写 Lambda 表达式中已经存在的方法或构造函数,这种特性就叫做方法引用(Method Reference)。

Arrays.sort(strArray, (s1, s2) -> s1.compareToIgnoreCase(s2));
Arrays.sort(strArray, String::compareToIgnoreCase);

 方法引用是一个 Lambda 表达式,其中方法引用的操作符是双冒号::

方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号)

有以下四种形式的方法引用:

  • 引用静态方法: ContainingClass::staticMethodName
  • 引用某个对象的实例方法: containingObject::instanceMethodName
  • 引用某个类型的任意对象的实例方法:ContainingType::methodName
  • 引用构造方法: ClassName::new

5.stream流式操作

特点一:内部迭代

 

foreach采用外部迭代,流采用内部迭代

采用内部迭代,项目可以透明地并行处理,或者用优化的顺序进行处理,要是使用 Java 过去的外部迭代方法,这些优化都是很困难的。

特点二:只能遍历一次

和迭代器一样,流只能遍历一次。当流遍历完之后,我们就说这个流已经被消费掉了,你可以从原始数据那里重新获得一条新的流,但是却不允许消费已消费掉的流。

特点三:并行处理

只需要加上 .parallel() 就行了!例如我们使用下面程序来说明一下多线程流操作的方便和快捷,并且与单线程做了一下对比:

public class StreamParallelDemo {

    /** 总数 */
    private static int total = 100_000_000;

    public static void main(String[] args) {
        System.out.println(String.format("本计算机的核数:%d", Runtime.getRuntime().availableProcessors()));

        // 产生1000w个随机数(1 ~ 100),组成列表
        Random random = new Random();
        List<Integer> list = new ArrayList<>(total);

        for (int i = 0; i < total; i++) {
            list.add(random.nextInt(100));
        }

        long prevTime = getCurrentTime();
        list.stream().reduce((a, b) -> a + b).ifPresent(System.out::println);
        System.out.println(String.format("单线程计算耗时:%d", getCurrentTime() - prevTime));

        prevTime = getCurrentTime();
        // 只需要加上 .parallel() 就行了
        list.stream().parallel().reduce((a, b) -> a + b).ifPresent(System.out::println);
        System.out.println(String.format("多线程计算耗时:%d", getCurrentTime() - prevTime));

    }

    private static long getCurrentTime() {
        return System.currentTimeMillis();
    }
}
本计算机的核数:8
655028378
单线程计算耗时:4159
655028378
多线程计算耗时:540

并行流的内部使用了默认的 ForkJoinPool 分支/合并框架,它的默认线程数量就是你的处理器数量,这个值是由 Runtime.getRuntime().availableProcessors() 得到的(当然我们也可以全局设置这个值)。我们也不再去过度的操心加锁线程安全等一系列问题。 

一些示例

Filter 过滤

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

Sort 排序

stringCollection
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

Map 映射

stringCollection
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

Match 匹配

boolean anyStartsWithA = stringCollection
        .stream()
        .anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA);      // true

boolean allStartsWithA = stringCollection
        .stream()
        .allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA);      // false

boolean noneStartsWithZ = stringCollection
        .stream()
        .noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ);      // true

Count 计数

long startsWithB = stringCollection
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();
System.out.println(startsWithB);    // 3

Reduce 归约

这是一个最终操作,允许通过指定的函数来将 stream 中的多个元素规约为一个元素,规越后的结果是通过 Optional 接口表示的。代码如下:

Optional<String> reduced = stringCollection
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);

6.optional

检查空值

Optional<String> fullName = Optional.ofNullable(null);
System.out.println("Full Name is set? " + fullName.isPresent());
System.out.println("Full Name: " + fullName.orElseGet(() -> "[none]"));
System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!"));

如果 Optional 类的实例为非空值的话,isPresent() 返回 true,否从返回 false

为了防止 Optional 为空值,orElseGet() 方法通过回调函数来产生一个默认值。

map() 函数对当前 Optional 的值进行转化,然后返回一个新的 Optional 实例。

orElse() 方法和 orElseGet() 方法类似,但是 orElse 接受一个默认值而不是一个回调函数。下面是这个程序的输出:

 

 

只有当 Optional<T> 结合 Lambda 一起使用的时候,才能发挥出其真正的威力!

我们现在就来对比一下下面四种常见的 null 处理中,Java 8 的 Lambda + Optional<T> 和传统 Java 两者之间对于 null 的处理差异。

情况一:存在则继续

 

 

情况二:存在则返回,无则返回不存在

 

情况三:存在则返回,无则由函数产生

 

情况四:夺命连环 null 检查

 7.date/time API

LocalTime 本地时间,LocalData 本地日期,LocalDateTime 本地日期时间,线程安全

8.重复注解

相同的注解可以在同一地方声明多次

重复注解机制本身必须用 @Repeatable 注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。让我们看一个快速入门的例子:

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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());
        }
    }

}

正如我们看到的,这里有个使用 @Repeatable(Filters.class) 注解的注解类 FilterFilters 仅仅是 Filter 注解的数组,但Java编译器并不想让程序员意识到 Filters 的存在。这样,接口 Filterable 就拥有了两次 Filter(并没有提到Filter)注解。

同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型(请注意Filterable.class.getAnnotation(Filters.class)`经编译器处理后将会返回Filters的实例)。

9.扩展注解支持

Java 8 扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。下面演示几个例子:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;

public class Annotations {

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
    public @interface NonEmpty {
    }

    public static class Holder<@NonEmpty T> extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {
        }
    }

    @SuppressWarnings("unused")
    public static void main(String[] args) {
        final Holder<String> holder = new @NonEmpty Holder<String>();
        @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
    }

}

10.base64

在 Java 8 中,Base64 编码已经成为 Java 类库的标准。它的使用十分简单,下面让我们看一个例子:

 

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {

    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";

        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);
    }

}

程序在控制台上输出了编码后的字符与解码后的字符:

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

Base64 类同时还提供了对 URL、MIME 友好的编码器与解码器(Base64.getUrlEncoder() / Base64.getUrlDecoder()Base64.getMimeEncoder() / Base64.getMimeDecoder())。

11.其他性能提升

(1)HashMap引入红黑树,改用尾插法,ConcurrentHashMap修改分段锁为synchronized

(2)NIO

主要包括:改进了java.nio.charset.Charset的实现,使编码和解码的效率得以提升,也精简了jre/lib/charsets.jar包;

优化了String(byte[], *)构造方法和String.getBytes()方法的性能;

增加了一些新的IO/NIO方法,使用这些方法可以从文件或者输入流中获取流(java.util.stream.Stream),通过对流的操作,可以简化文本行处理、目录遍历和文件查找。

新增的 API 如下:

  • BufferedReader.line(): 返回文本行的流Stream<String>
  • File.lines(Path, Charset): 返回文本行的流Stream<String>
  • File.list(Path): 遍历当前目录下的文件和目录
  • File.walk(Path, int, FileVisitOption): 遍历某一个目录下的所有文件和指定深度的子目录
  • File.find(Path, int, BiPredicate, FileVisitOption...): 查找相应的文件

下面就是用流式操作列出当前目录下的所有文件和目录:

Files.list(new File(".").toPath()).forEach(System.out::println);

(3)JVM 的 PermGen 空间被移除

PermGen空间被移除了,取而代之的是Metaspace(JEP 122)。JVM 选项-XX:PermSize-XX:MaxPermSize分别被-XX:MetaSpaceSize-XX:MaxMetaspaceSize所代替。

区别:

  1. 元空间并不在虚拟机中,而是使用本地内存
  2. 默认情况下,元空间的大小仅受本地内存限制
  3. 也可以通过-XX:MetaspaceSize指定元空间大小

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值