java8 新特性

来源:互联网 发布:java怎么调试错误 编辑:程序博客网 时间:2024/04/27 05:42

1.简介

毫无疑问,Java 8是自Java 5(2004年)发布以来Java语言最大的一次版本升级,如果不学习,你会怀疑自己面前的代码是不是Java。Java 8带来了很多的新特性,比如编译器、类库、开发工具和JVM(Java虚拟机),但最最主要的还是函数式编程。接下来将会结合代码去展示Java 8的新特性。

下面主要介绍Java 8的最显著特性:

接口增强

Lambda编程

函数式接口

Stream

Annotations

接口增强

Java 8允许我们在方法前使用default关键字在接口中加入一个具体的方法。

interface Formula {    double calculate(int a);        default double sqrt(int a) {        return Math.sqrt(a);    }    static int positive(int a) {        return a > 0 ? a : 0;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在实现Formula接口的时候只需实现calculate即可(当然也可以重写sqrt方法)

Formula formula = new Formula() {    @Override    public double calculate(int a) {        return sqrt(a * 100);    }};formula.calculate(100);     // 100.0formula.sqrt(16);           // 4.0Formula.positive(-4);        // 0.0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

关键字default定义的方法不可以直接通过接口类名调用

Formula.sqrt(100);
  • 1
  • 2

必须通过实例化的接口对象调用。接口在加入这个特性后非常像抽象类,也就是说我们可以在接口中实现模板模式。

与此同时,Java 8还允许我们在接口中添加一个静态方法,如上例的positive方法。

Lambda 表达式

Java 8中最大的特性应该就是加入了函数式编程,那么函数式编程语言是什么?函数式编程语言的核心是它以处理数据的方式处理代码

我们看一个Java的简单例子

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");Collections.sort(names, new Comparator<String>() {    @Override    public int compare(String a, String b) {        return b.compareTo(a);    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我们经常像这个代码一样写一些匿名内部类。在Java 8你可以通过Lambda来代替这种编程

Collections.sort(names, (String a, String b) -> b.compareTo(a));
  • 1
  • 2

Lambda的基本形式是()->->左面是入参,可以为空,->右面是具体的执行,可以写多行,但需要使用{},这样我们就把一段代码写入了函数的参数

我们以后就可以这样开一个Thread了

Thread t = new Thread(    () -> System.out.println("Hello"););
  • 1
  • 2
  • 3

Lambda函数的作用域

既然我们上面说到了Lambda表达式和匿名内部类很像,那么使用参数的作用域也是如此,在Lambda中可以使用类的成员函数或者static变量,有点稍稍不同的是,如果使用方法中的变量在内部类中必须使用定义为final的变量,Lambda没有明确要求变量必须使用final定义,但该变量不能更改(其实只是不需要finalfinal),所以下面最后一行代码会导致出错。

int num = 1;Converter<Integer, String> stringConverter =        (from) -> String.valueOf(from + num);num = 3;
  • 1
  • 2
  • 3
  • 4
  • 5

函数式接口

函数式接口的目的是为了使Lambda表达式更好的融入Java,那我们先看看什么是函数式接口,如果一个接口只包含一个抽象的方法,那么该接口成为函数式接口,同时提供一个新的注解@FunctionalInterface,这个注解不是必须的,如果满足条件不加@FunctionalInterface也会被虚拟机翻译为函数式接口,若增加@FunctionalInterface将会增加编译时的检查,如果接口不满足将会在编译时报错。

@FunctionalInterfaceinterface Converter<F, T> {    T convert(F from);}Converter<String, Integer> converter = (from) -> Integer.valueOf(from);Integer converted = converter.convert("123");System.out.println(converted);    // 123
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

default方法不能使用Lambda表达式。

同时Java 8自带了很多函数式接口,例如ComparatorRunnable,同时还加入了很多很好用的函数式接口

Predicates

Predicate提供一个单参数返回值为Boolean的函数式接口,返回值定义为其泛型

Predicate<String> predicate = (s) -> s.length() > 0;predicate.test("foo");              // truepredicate.negate().test("foo");     // falsePredicate<Boolean> nonNull = Objects::nonNull;Predicate<Boolean> isNull = Objects::isNull;Predicate<String> isEmpty = String::isEmpty;Predicate<String> isNotEmpty = isEmpty.negate();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Functions

Function

Suppliers

Suppliers提供一个无参的方法,返回值为定义的泛型

Supplier<Person> personSupplier = Person::new;personSupplier.get();   // new Person
  • 1
  • 2
  • 3

Consumers

而Consumers则是提供一个一个参数的void函数

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);greeter.accept(new Person("Luke", "Skywalker"));
  • 1
  • 2
  • 3

Comparators

在以往的Java中这个借口还是很常见的,现在的Comparator增加了很多的default和static方法。

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);             // > 0comparator.reversed().compare(p1, p2);  // < 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Optionals

Optionals不是函数式接口但是是一个很实用的final类,Optional主要针对函数时而返回值时而返回null的时候,比如我们在Map.get的时候或者调用网络接口都是这样的使用场景

Optional<String> optional = Optional.of("bam");optional.isPresent();   // trueoptional.get(); // "bam"optional.orElse("fallback");// "bam"optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

方法或构造函数引用

方法引用

Java 8引入::关键字使得我们对类中的方法和构造函数的使用增加了一种方式。上面的例子可以这么改写

Converter<String, Integer> converter = Integer::valueOf;Integer converted = converter.convert("123");System.out.println(converted);   // 123
  • 1
  • 2
  • 3
  • 4

上面我们通过::引用了Integer的静态工厂方法valueOf。那么非静态方法的使用是如何呢?

class Something {    String startsWith(String s) {        return String.valueOf(s.charAt(0));    }}Something something = new Something();Converter<String, String> converter = something::startsWith;String converted = converter.convert("Java");System.out.println(converted);    // "J"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

构造方法引用

我们来看构造方法如何使用

class Person {    String firstName;    String lastName;    Person() {}    Person(String firstName, String lastName) {        this.firstName = firstName;        this.lastName = lastName;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

再创建一个工厂类用于创建

interface PersonFactory<P extends Person> {    P create(String firstName, String lastName);}
  • 1
  • 2
  • 3
  • 4

我们不需要实现该工厂,只需要传递引用

PersonFactory<Person> personFactory = Person::new;Person person = personFactory.create("Peter", "Parker");
  • 1
  • 2
  • 3

而且我们不需要指定参数,Java编译器会根据构造函数的方法签名去匹配正确的构造函数。

Stream

Steam适用在集合数据处理,Java 8在java.util.Collection中的ListSetMap并不直接提供)。

Steam存在两组重要的概念中间操作和终止操作并行和串行

中间操作和终止操作是指根据对Stream流操作的结果而言,如果是中间操作那么将会返回Stream,以便接下来继续操作,而终止操作则是返回最终的操作结果,不能再对Stream操作。可以看到,有了Stream我们可以轻松对集合进行一系列的复杂操作,而且我们只需要写一行代码。类似于pipeline功能。

串行和并行

先创建一个List

List<String> stringCollection = new ArrayList<>();stringCollection.add("ddd2");stringCollection.add("aaa2");stringCollection.add("bbb1");stringCollection.add("aaa1");stringCollection.add("bbb3");stringCollection.add("ccc");stringCollection.add("bbb2");stringCollection.add("ddd1");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Collections在Java 8提供了创建Stream的方法,Collection.stream()-串行流和Collection.parallelStream()-并行流

中间操作和终止操作

  • Filter

顾名思义,Filter可以为我们提供过滤功能,这是一个中间操作,我们可以设置我们需要的过滤规则。

  • ForEach

还是顾名思义,也就是循环遍历Stream中的元素,显然这是一个终止操作。

stringCollection.stream().filter((s) -> s.startsWith("a")).forEach(System.out::println);// "aaa2", "aaa1"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • Sorted

Sorted是一个中间操作,可以接受Comparator参数,如果不传参将会默认使用字典顺序。

stringCollection.stream().sorted().filter((s) -> s.startsWith("a")).forEach(System.out::println);// "aaa1", "aaa2"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

同时中间操作都不会改变原集合,也就是Java 8为中间操作重新分配了内存。

System.out.println(stringCollection);// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
  • 1
  • 2
  • 3

并行的优势

上面的例子我们都是串行,我们通过一个例子来看一下并行操作能带来的性能提升

int max = 1000000;List<String> values = new ArrayList<>(max);for (int i = 0; i < max; i++) {    UUID uuid = UUID.randomUUID();    values.add(uuid.toString());}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

串行操作

long t0 = System.nanoTime();long count = values.stream().sorted().count();System.out.println(count);long t1 = System.nanoTime();long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);System.out.println(String.format("sequential sort took: %d ms", millis));// sequential sort took: 899 ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

并行操作

long t0 = System.nanoTime();long count = values.parallelStream().sorted().count();System.out.println(count);long t1 = System.nanoTime();long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);System.out.println(String.format("parallel sort took: %d ms", millis));// parallel sort took: 472 ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

当然,具体性能的提升跟你的电脑性能有关,但是很明显并行给我们带来的不仅仅是性能的提升,如果我们在Java 8之前操作,我们必然要考虑多线程执行时候的同步问题吧,就拿上例来说,我们如果使用3个线程操作,那么三个线程需要从集合中取不同的数据,然后对中间结果sum的使用的时候我们需要保证操作的原子性。现在Java 8只需要一个方法,迅速摆脱烦恼啊!

Map

Map虽然没有直接提供Stream方法,但是我们可以创建Map中keyvalueentrySet的Stream

同时Map提供了很多非常使用的方法

  • forEach

顾名思义是用来遍历的

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));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • putIfAbsent & computeIfPresent

顾名思义第一个如果该keynull则插入。第二个是如果存在就计算

map.computeIfPresent(3, (num, val) -> val + num);map.get(3);             // val33map.computeIfPresent(9, (num, val) -> null);map.containsKey(9);     // falsemap.computeIfAbsent(23, num -> "val" + num);map.containsKey(23);    // truemap.computeIfAbsent(3, num -> "bam");map.get(3);             // val33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • getOrDefault

顾名思义,根据key获取,如果不存在则使用默认值

map.getOrDefault(42, "not found");  // not found
  • 1
  • 2

Annotations

注解在Java 8是可以重复的。只需要使用注解@Repeatable

@Retention(RetentionPolicy.RUNTIME) @interface Annots {    Annot[] value();} @Retention(RetentionPolicy.RUNTIME) @Repeatable(Annots.class)@interface Annot {    String value();}@Annot("a1")@Annot("a2")public class Test {    public static void main(String[] args) {        Annots annots1 = Test.class.getAnnotation(Annots.class);        System.out.println(annots1.value()[0]+","+annots1.value()[1]);         // @Annot(value=a1),@Annot(value=a2)        Annot[] annots2 = Test.class.getAnnotationsByType(Annot.class);        System.out.println(annots2[0]+","+annots2[1]);         // @Annot(value=a1),@Annot(value=a2)        Annot annot1 = Test.class.getAnnotation(Annot.class);        System.out.println(annots1);        //null     }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

注释 Annot 被 @Repeatable( Annots.class ) 注解。Annots 只是一个容器,它包含 Annot 数组, 编译器尽力向程序员隐藏它的存在。通过这样的方式,Test 类可以被 Annot 注解两次。重复注释的类型可以通过 getAnnotationsByType() 方法来返回。

Java 8剩余的特性

除了上述Java 8带来的很显著的变化。Java 8当然还有一些其他的特性:

  • Date

API 如果你被Date类恶心过,那么你一定很期待Java 8给你带来的全新感受

  • JVM

元空间(Metaspace):一个新的内存空间的诞生

  • Concurrency

Java 8

concurrentForkJoinPool类添加了新的方法来支持通用线程池操作。同时Java 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)