Java 8 再不知道就老了

Java 8 特性介绍

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

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

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0
Formula.positive(-4);        // 0.0

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

Formula.sqrt(100);

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

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

Lambda 表达式

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

我们看一个Java的简单例子

List names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

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

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

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

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

Thread t = new Thread(
    () -> System.out.println("Hello"););

Lambda函数的作用域

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

int num = 1;
Converter stringConverter =
        (from) -> String.valueOf(from + num);
num = 3;

函数式接口

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

@FunctionalInterface
interface Converter {
    T convert(F from);
}
Converter converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

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

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

Predicates

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

Predicate predicate = (s) -> s.length() > 0;

predicate.test("foo");              // true
predicate.negate().test("foo");     // false

Predicate nonNull = Objects::nonNull;
Predicate isNull = Objects::isNull;

Predicate isEmpty = String::isEmpty;
Predicate isNotEmpty = isEmpty.negate();

Functions

Function

Suppliers

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

Supplier personSupplier = Person::new;
personSupplier.get();   // new Person

Consumers

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

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

Comparators

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

Comparator 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

Optionals

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

Optional optional = Optional.of("bam");

optional.isPresent();   // true
optional.get(); // "bam"
optional.orElse("fallback");// "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"

方法或构造函数引用

方法引用

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

Converter converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123

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

class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}

Something something = new Something();
Converter converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

构造方法引用

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

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

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

interface PersonFactory

{ P create(String firstName, String lastName); }

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

PersonFactory personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

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

Stream

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

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

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

串行和并行

先创建一个List

List 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");

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"
  • Sorted

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

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

// "aaa1", "aaa2"

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

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

并行的优势

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

int max = 1000000;
List values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();
    values.add(uuid.toString());
}

串行操作

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

并行操作

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

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

Map

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

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

  • forEach

顾名思义是用来遍历的

Map map = new HashMap<>();

for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);
}

map.forEach((id, val) -> System.out.println(val));
  • putIfAbsent & computeIfPresent

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

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
  • getOrDefault

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

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

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

注释 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的替代者)

你可能感兴趣的:(Java)