java 8新特性

今天介绍一下java8主要的新特性,分别是默认接口方法、lambda表达式、方法引用和可重复注解等,结尾还会介绍到最新的Api,如streams、函数式接口和新的Date api。

Interfaces的默认方法

java 8可以使用default关键字来给接口添加非抽象方法,如下:

1
2
3
4
5
6
7
interface Formula {
double calculate(int a);

default double sqrt(int a) { //非抽象方法
return Math.sqrt(a);
}
}
1
2
3
4
5
6
7
8
9
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);//sqrt方法可在内部调用
}
};

formula.calculate(100); // 100.0
formula.sqrt(16); // 4.0,sqrt方法也可在外部调用

lambda表达式

java 8终于可以使用简洁的代码语法:lambda表达式,有多简洁,下面说明。

以前写一个排序的程序,需要这么写:

1
2
3
4
5
6
7
8
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);
}
});

使用lambda表达式后,可以修改为:

1
2
3
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});

因为对比的逻辑比较简单,可将大括号和return关键字去掉,如下:

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

java编译器强大的推算能力,甚至可将参数类型去掉,如下:

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

函数式接口

java中使用lambda表达式,其根本原理是匿名内部类的语法糖,编译器根据类型上下文推断lambda语句转为对应匿名内部类的字节码,所有就需要对接口做一些限制:接口必须有且只有一个抽象方法,即后续给lambda表达式实现的方法,这种接口就叫函数式接口。

函数式接口可以添加@FunctionalInterface注解来声明自己为函数式接口,编译器编译时,如果检查到带有@FunctionalInterface注解但不满足函数式接口限制的,会编译异常报警,相当于增加多了一层检查。

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

方法引用

java 8使用::标识符来传递方法引用,如静态方法引用:

1
2
3
Converter<String, Integer> converter = Integer::valueOf;//Integer的静态方法valueOf
Integer converted = converter.convert("123");
System.out.println(converted); // 123

对象实例方法引用:

1
2
3
4
5
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
1
2
3
4
Something something = new Something();
Converter<String, String> converter = something::startsWith;//实例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
interface PersonFactory<P extends Person> {//函数式接口
P create(String firstName, String lastName);
}
1
2
PersonFactory<Person> personFactory = Person::new;//构造方法引用
Person person = personFactory.create("Peter", "Parker");

java的方法引用其实也是语法糖,并没有改变原来底层的那一套东西,也就是说底层不存在方法的实例化,这里的方法引用只是接口实例的引用,编译器做了一层转化而已。

lambda表达式的访问范围

lambda表达式底层本身就是匿名内部类,所以它的访问范围和匿名内部类是一样的,只能访问外部不可变的变量。如下:

局部变量

访问final声明的变量:

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

stringConverter.convert(2); // 3

不同于匿名内部类,如果编译器可以推断某个变量在上下文中是不会改变的,可以免去final的声明,如下:

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

stringConverter.convert(2); // 3

但是如果变量在上下文中会变的,则会编译错误:

1
2
3
4
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);//lambda中使用了num
num = 3;//改变了num,会编译不通过

成员变量和静态变量

与匿名内部类相似,lambda表达式可以读写成员变量和静态变量,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Lambda4 {
static int outerStaticNum;
int outerNum;

void testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};

Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}

范围默认接口方法

前面有个例子是,在匿名对象中可以在接口实现中调用默认方法:

1
2
3
4
5
6
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);//sqrt方法可在内部调用
}
};

如果转化成lambda表达式,是编译不通过的:

1
Formula formula = (a) -> sqrt( a * 100);//编译错误

内置的函数式接口

Java 8内置了很多函数式接口,比如老版本的ComparatorRunnable,同时也新增了很多很好用的函数式接口。

Predicates

Predicates是一个接收一个参数并返回true或false的方法接口,也内置了一些好用的默认接口方法,如:

1
2
3
4
5
6
7
8
9
10
Predicate<String> predicate = (s) -> s.length() > 0;

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

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

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

Functions

Functions是一个接收一个参数并生成一个结果的方法接口,可以使用默认方法将多个Functions串在一起,如:

1
2
3
4
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123"); // "123"

Suppliers

Suppliers是一个不接收参数并生成一个结果的方法接口:

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

Consumers

Consumers是一个只接收参数不返回结果的方法接口:

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

Comparators

Comparators接口就不用多讲了:

1
2
3
4
5
6
7
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

Optional

Optional不是一个函数式接口,而是用来防止出现空指针的工具类,非常好用:

1
2
3
4
5
6
7
Optional<String> optional = Optional.of("bam");

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

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

其他语言如groovy会使用?.代替.来防止方法调用空指针,而已java 8之后可以使用Optional的map方法,如:

1
2
3
// company可能为null.  
company?.address?.street?.name //groovy的写法
Optional.ofNullable(company).map(o -> o.address).map(o -> o.street).map(o -> o.name).orElse("不存在"); //java的写法

如果用习惯了,发现会回不去了,篇幅有限,后面可以另起一篇文章详细讲解。

Streams

java 8另一个新增的超级好用的类Streams,它可以执行一个或多个操作序列元素,Streams的操作元素可以分为中间操作和最终操作,下面简单讲解一些常用操作。(同样篇幅有限,后续另起篇幅来讲解里面的底层原理)

1
2
3
4
5
6
7
8
9
10
//创建一个string类型的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");

Filter

Filter是一个中间操作元素,用来过滤数据,如:

1
2
3
4
5
6
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))//只保留以a开头的字符串数据
.forEach(System.out::println);//forEach是一个最终操作元素,迭代处理输入的数据

// "aaa2", "aaa1"

Sorted

Sorted是一个中间操作元素,对数据列表进行排序,如:

1
2
3
4
5
6
7
stringCollection
.stream()
.sorted() //数据流排序
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);

// "aaa1", "aaa2"

Sorted操作只是修改了流的顺序,并不会影响到原数据列表:

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

Map

Map是一个中间操作,将原数据映射到另一种数据,如:

1
2
3
4
5
6
7
stringCollection
.stream()
.map(String::toUpperCase) //将字符串转为全部大写
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

Match

Match有一系列的最终操作,any、all、none,表示匹配的判断,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a")); //列表有只要有以a开头的数据,则返回true

System.out.println(anyStartsWithA); // true

boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a")); //列表中的所有数据都以a开头,才返回true

System.out.println(allStartsWithA); // false

boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z")); //列表中所有数据都不是以z开头,才返回true

System.out.println(noneStartsWithZ); // true

Count

Count是一个最终操作,用来统计数据流大小:

1
2
3
4
5
6
7
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();

System.out.println(startsWithB); // 3

Reduce

Reduce是一个最终操作,返回的Optional

1
2
3
4
5
6
7
8
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Parallel Streams

Parallel Streams能使Streams在多线程上并发运行,从而大幅提升操作性能。

1
2
3
4
5
6
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
8
9
10
11
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));

// 串行排序耗时: 899 ms
1
2
3
4
5
6
7
8
9
10
11
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));

// 并行排序耗时: 472 ms

Map

Map是不支持转化为Streams的,但java 8版本新增了一些好用的方法,如:

1
2
3
4
5
6
7
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)); //迭代处理key value
1
2
3
4
5
6
7
8
9
10
11
map.computeIfPresent(3, (num, val) -> val + num);//如果存在则修改value
map.get(3); // val33

map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false

map.computeIfAbsent(23, num -> "val" + num);//如果不存在则添加,比putIfAbsent方法多了key的参数输入
map.containsKey(23); // true

map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
1
2
3
4
5
map.remove(3, "val3"); //如果key为3,value为"val3",则删除
map.get(3); // val33

map.remove(3, "val33");
map.get(3); // null
1
2
//如果value为null,则返回默认值
map.getOrDefault(42, "not found"); // not found
1
2
3
4
5
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));//如果key不存在,则添加
map.get(9); // val9

map.merge(9, "concat", (value, newValue) -> value.concat(newValue));//如果key存在,则按合并规则合并value
map.get(9); // val9concat

Date API

Java 8出了个类似于Joda-Time的Date API。

Clock

1
2
3
4
5
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // 转化为 java.util.Date

时区

1
2
3
4
5
6
7
8
9
10
System.out.println(ZoneId.getAvailableZoneIds());
// 打印所有合法的时区id

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]

LocalTime

LocalTime只有时分秒数据,没有日期:

1
2
3
4
5
6
7
8
9
10
LocalTime now1 = LocalTime.now(zone1);//zone1时区的时分秒
LocalTime now2 = LocalTime.now(zone2);//zone2时区的时分秒

System.out.println(now1.isBefore(now2)); // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);//两个时区的时差
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);//两个时区的分差

System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239
1
2
3
4
5
6
7
8
9
10
LocalTime late = LocalTime.of(23, 59, 59);//of构造方法
System.out.println(late); // 23:59:59

DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);//字符解析构造方法
System.out.println(leetTime); // 13:37

LocalDate

LocalDate只包含日期,不包括时分秒:

1
2
3
4
5
6
7
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // FRIDAY
1
2
3
4
5
6
7
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24

LocalDateTime

LocalDateTime包含日期和时分秒:

1
2
3
4
5
6
7
8
9
10
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY

Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
1
2
3
4
5
6
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();

Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
1
2
3
4
5
6
7
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");//这个类是线程安全的

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13

可重复注解

java 8 后可以通过@Repeatable来声明注解的重复性,如:

1
2
3
4
5
6
7
8
@interface Hints {
Hint[] value();
}

@Repeatable(Hints.class)
@interface Hint {
String value();
}
1
2
@Hints({@Hint("hint1"), @Hint("hint2")})//以前的写法
class Person {}
1
2
3
@Hint("hint1")
@Hint("hint2")//新的写法,编译器会自动转换成上面那种写法
class Person {}
1
2
3
4
5
6
7
8
Hint hint = Person.class.getAnnotation(Hint.class);//这种方式是获取不到重复的Hint注解的
System.out.println(hint); // null

Hints hints1 = Person.class.getAnnotation(Hints.class);//编译器隐式创建了Hints注解
System.out.println(hints1.value().length); // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);//获取Hint数组
System.out.println(hints2.length); // 2

此外JAVA8中的annotations新增了两个目标:

1
2
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

以上就是java 8新添加的特性及使用说明,有问题欢迎留言。

  • 本文作者:二当家的
  • 本文链接: 2019/02/17/java-8新特性/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!
  • 彩蛋: 左边Overview微信公众号二维码,扫描它获取更多技术信息