毫无疑问,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
方法。
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中可以使用类的成员函数或者static变量,有点稍稍不同的是,如果使用方法中的变量在内部类中必须使用定义为final
的变量,Lambda没有明确要求变量必须使用final定义,但该变量不能更改(其实只是不需要final
的final
),所以下面最后一行代码会导致出错。
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自带了很多函数式接口,例如Comparator
和Runnable
,同时还加入了很多很好用的函数式接口
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();
Function
Suppliers提供一个无参的方法,返回值为定义的泛型
Supplier personSupplier = Person::new;
personSupplier.get(); // new Person
而Consumers则是提供一个一个参数的void函数
Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
在以往的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不是函数式接口但是是一个很实用的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编译器会根据构造函数的方法签名去匹配正确的构造函数。
Steam适用在集合数据处理,Java 8在java.util.Collection
中的List
和Set
(Map
并不直接提供)。
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可以为我们提供过滤功能,这是一个中间操作,我们可以设置我们需要的过滤规则。
还是顾名思义,也就是循环遍历Stream中的元素,显然这是一个终止操作。
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa2", "aaa1"
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虽然没有直接提供Stream
方法,但是我们可以创建Map中key
,value
,entrySet
的Stream
同时Map提供了很多非常使用的方法
顾名思义是用来遍历的
Map map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));
顾名思义第一个如果该key
为null
则插入。第二个是如果存在就计算
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
顾名思义,根据key
获取,如果不存在则使用默认值
map.getOrDefault(42, "not found"); // not found
注解在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当然还有一些其他的特性:
API 如果你被Date类恶心过,那么你一定很期待Java 8给你带来的全新感受
元空间(Metaspace):一个新的内存空间的诞生
Java 8
concurrentForkJoinPool
类添加了新的方法来支持通用线程池操作。同时Java 8还添加了新的java.util.concurrent.locks.StampedLock
类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)