JDK1.8 新特性

Alt text

接口的默认方法

Java8允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征被称作扩展方法。

首先我们定义一个接口,该接口有一个抽象方法print和一个具体方法defaultPrint();

1
2
3
4
5
6
public interface InterfaceEnhancement {
void print();
default void defaultPrint(String string) {
System.out.println( string + "接口可以添加default修饰的非抽象方法了");
}
}

实现了该接口一个类如下,并对其进行调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
class InterfaceEnhancementDemo implements InterfaceEnhancement {
@Override
public void print() {
System.out.println("实现类实现了InterfaceEnhancement中的print方法");
}
public static void main(String[] args) {
InterfaceEnhancementDemo IED = new InterfaceEnhancementDemo();
IED.print();
IED.defaultPrint("传给接口中的方法的字符串");
}
}

运行结果如下:

1
2
实现类实现了InterfaceEnhancement中的print方法
传给接口中的方法的字符串接口可以添加default修饰的非抽象方法了

考虑到Java中只有单继承,如果需要给一个类赋予新特性,通常是使用接口来实现。允许接口中写具体的实现方法,实现对接口功能的扩展,避免了单继承的弊端。

Lambda表达式

Java中对字符串进行排序,可以通过传入一个List对象和一个比较器来制定顺序排列。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LambdaDemo {
public static void main(String[] args) {
List<String> strings = Arrays.asList("1","2","3","11","12","5");
System.out.println(strings.toString());
Collections.sort(strings, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
System.out.println(strings.toString());
}
}

Lambda表达式提供了更加简洁的写法:

1
2
3
Collections.sort(strings, (o1, o2) -> o1.compareTo(o2));
//或者
Collections.sort(strings, String::compareTo);

函数式接口

每一个Lambda表达式对应着一个类型,通常是接口类型。函数式接口指的是仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法中,该接口也可以添加默认方法。

因此实际上,我们可以把Lambda当成任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,只需要给接口添加@FunctionalInterface注解,编译时会检查接口中是否只存在一个抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface
public interface FunctionalInterfaceDemo<K,V> {
V get(K key);
}
public class FITest {
public static void main(String[] args) {
FunctionalInterfaceDemo<String, String> functionalInterfaceDemo = key -> key + "12";
String result = functionalInterfaceDemo.get("functionInterface");
System.out.println(result);
}
}

实际上不添加注解,程序也是可以正确执行的。而将Lambda表达式映射到一个但方法的接口上,在其他语言中已经有实现了。单一方法的接口使得Lambda表达式更加的简洁。

方法与构造函数引用

Java中允许你使用::关键字来传递方法或者构造函数的引用,下面有一个实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Person {
String name;
int age;
Person(){
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public interface PersonFactory<P extends Person> {
P create(String name, int age);
}
public class PersonTest {
public static void main(String[] args) {
PersonFactory<Person> personPersonFactory = Person::new;
Person person = personPersonFactory.create("Peter", 18);
}
}

可以看出Person::new获取Person类的构造函数的引用,Java编译器会根据create方法的签名来选取合适的构造方法。

Lambda作用域

Lambda表达式中访问外层作用域和匿名方法中的方式类似,可以直接访问final修饰的外层局部变量,实例的字段及静态变量。

访问局部变量

1
2
3
4
5
6
7
public class LocalDemo {
public static void main(String[] args) {
final int num = 123;
FunctionalInterfaceDemo<String, String> functionalInterfaceDemo = key -> key + num;
System.out.println(functionalInterfaceDemo.get("123"));
}
}
  • final不强制要求添加
  • 不添加final,num也不能被后面的程序修改,隐式的final。

访问字段和静态变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FieldAndStaticFieldDemo {
static int staticNum;
int normalNum;
void testScope() {
FunctionalInterfaceDemo<Integer, Integer> f = (key) -> {
normalNum = 513;
return key +12;
};
FunctionalInterfaceDemo<Integer, Integer> f1 = (key) -> {
staticNum = 1234455;
return key + 12;
};
}
}

访问接口的默认方法

JDK1.8中包含了很多内建的函数式接口,如之前常用的Comparator或者Runnable接口,这些接口都增加了@FunctionInterface注解以便可以使用Lambda表达式。同样提供了很多全新的Lambda接口。

  • Predicate接口
1
2
3
4
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}

该接口只有一个参数,返回boolean类型的值。改接口包含多种默认方式来将Predicate组合成其他复杂的逻辑:

1
2
3
4
5
6
Predicate<String> predicate = s -> s.length() > 0;
System.out.println(predicate.test("foo"));
System.out.println(predicate.negate().test("foo"));
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
  • Function接口
1
2
3
4
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}

只有一个参数并且返回一个结果,附带了一些组合方法。

1
2
Function<String, Integer> function = Integer::valueOf;
Function<String, String> function1 = s -> s + 11;
  • Supplier接口
1
2
3
4
@FunctionalInterface
public interface Supplier<T> {
T get();
}

返回一个任意范式的值。

  • Consumer接口
1
2
3
4
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
  • Comparator接口
1
2
3
4
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
  • Optional接口

不是函数式接口,是用来放置空指针异常的辅助类型。

  • Stream接口

表示能应用中哎一组元素上一次执行的操作序列。Stream操作分为中间操作或者最终操作两种,最终操作返回以特定类型的计算结果,而中间操作返回Stream本身。可以将多个操作穿起来。Stream创建过程中需要指定一个数据源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class StreamFuture {
public static void main(String[] args) {
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");
System.out.println(stringCollection.toString());
//Filter过滤
stringCollection.stream().filter(s -> s.startsWith("b")).forEach(System.out::println);
//Sort排序
stringCollection.stream().sorted().filter(s -> s.startsWith("b")).forEach(System.out::println);
// map操作
stringCollection.stream().map(String::toUpperCase).sorted().forEach(System.out::println);
//Match 匹配操作
System.out.println(stringCollection.stream().anyMatch(s -> s.startsWith("a")));
System.out.println(stringCollection.stream().allMatch(s -> s.length() > 0));
System.out.println(stringCollection.stream().noneMatch(s -> s.startsWith("h")));
// count计数
long count = stringCollection.stream().filter(s -> s.startsWith("b")).count();
System.out.println(count);
// reduce规约
Optional<String> reduce = stringCollection.stream().sorted().reduce((s1,s2) -> s1 + "##" + s2);
reduce.ifPresent(System.out::println);
}
}
* Filter过滤:通过一个Predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以在过滤后可以将结果来应用其他Stream操作。forEach需要一个函数来对过滤后的 元素依次执行。forEach时一个最终操作,不能在其之后再执行Stream操作。

* Sort排序:排序是一个中间操作。

* Map映射:中间操作map,会将元素根据指定的Function接口来依次将元素转换成另外的对象,如将小写转换成大写

* Match匹配:检测指定的Predicate是否匹配整个Stream,最终操作。

* Count计数,最终操作,返回Stream中元素的个数,返回值为long

* Reduce规约,将stream中的多个元素规约为一个元素,通过Optional接口来接收结果

上述的输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1]
bbb1
bbb3
bbb2
bbb1
bbb2
bbb3
AAA1
AAA2
BBB1
BBB2
BBB3
CCC
DDD1
DDD2
true
true
true
3
aaa1##aaa2##bbb1##bbb2##bbb3##ccc##ddd1##ddd2

并行Stream

多个线程并行计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MultiStreams {
public static void main(String[] args) {
List<String> strings = new ArrayList<>(1000000);
for (int i = 0 ; i < strings.size(); i++) {
UUID uuid = UUID.randomUUID();
strings.add(uuid.toString());
}
//串行计算
long time1 = System.nanoTime();
strings.stream().sorted();
long end1 = System.nanoTime();
long mills = TimeUnit.NANOSECONDS.toMillis(end1 - time1);
System.out.println("串行Stream排序1000万个数的时间为" + mills + "ms");
//并行计算
long time2 = System.nanoTime();
strings.stream().sorted();
long end2 = System.nanoTime();
long mill2 = TimeUnit.NANOSECONDS.toMillis(end2 - time2);
System.out.println("串行Stream排序1000万个数的时间为" + mill2 + "ms");
}
}
串行Stream排序1000万个数的时间为1ms
串行Stream排序1000万个数的时间为0ms

实际上,并行计算不一定会快于串行运算,影响的因素很多:

  • 数据大小输入数据的大小会影响并行化处理对性能的提升。将问题分解之后并行化处理, 再将结果合并会带来额外的开销。 因此只有数据足够大、 每个数据处理管道花费的时间足够多
    时, 并行化处理才有意义。
  • 源数据结构,每个管道的操作都基于一些初始数据源,通常是集合。将不同的数据源分割相对容易,这里的开销影响了在管道中并行处理数据时到底能带来多少性能上的提升。
  • 装箱,处理基本类型比处理装箱类型要快。
  • 核的数量,极端情况下,只有一个核,因此完全没必要并行化。显然,拥有的核越多,获得潜在性能提升的幅度就越大。在实践中,核的数量不单指你的机器上有多少核,更是指运行时你的机器能使用多少核。这也就是说同时运行的其他进程,或者线程关联性(强制线程在某些核或CPU上运行)会影响性能。
  • 单元处理开销,比如数据大小,这是一场并行执行花费时间和分解合并操作开销之间的战争。花在流中每个元素身上的时间越长,并行操作带来的性能提升越明显。

Date API

Java8中包含了一组全新的时间日期API,简单介绍下:

  • Clock时钟:访问当前日期和时间的方法,对时区敏感。
  • Timezones时区:ZoneID来标志时区。
  • LocalTime本地时间:没有时区信息的时间
  • LocalDate本地日期
  • LocalDateTime:本地日期时间

Annotation注解

  • Java8中吃吃了多重注解。
1
2
3
4
5
6
7
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}
  • Java8中允许一个类型的注解使用多次

使用包装类当容器来存多个注解

1
2
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}

使用多重注解

1
2
3
@Hint("hint1")
@Hint("hint2")
class Person {}
Donate comment here