java8新特性介绍
个人感觉Swift比Java 8的新特性还要强大一些。2016年秋季Android N就要支持Java 8了,期待中
欢迎阅读我的java8新特性介绍教程。本教程将一步一步的引领你浏览全部新的语言特性。伴随着一些简单且简短的代码演示样例。你将学习怎样使用默认接口方法。lambda表示式。方法引用和可反复的annotation。 在阅读完本篇文章,你将对新加入的和更新的API有着更具体的了解。
这些API包括流、功能接口、扩展的map以及新的Date API。
接口的默认方法
以下是我们的第一个样例:
- interface Formula {
- double calculate(int a);
- default double sqrt(int a) {
- return Math.sqrt(a);
- }
- }
如:
- Formula formula = new Formula() {
- @Override
- public double calculate(int a) {
- return sqrt(a * 100);
- }
- };
- formula.calculate(100); // 100.0
- formula.sqrt(16); // 4.0
我们将在以下部分看到一种用java8实现的更加简洁的办法。
Lambda表达式
- 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);
- }
- });
- Collections.sort(names, (String a, String b) -> {
- return b.compareTo(a);
- });
可是,实际上,它能够变得更加简短:
- Collections.sort(names, (String a, String b) -> b.compareTo(a));
- Collections.sort(names, (a, b) -> b.compareTo(a));
功能性接口
编译器能够检測到该注解并推断你的接口是否满足条件。假设 你定义的接口包括多个抽象方法时,编译器便会报错。
- @FunctionalInterface
- interface 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
方法和构造函数引用
前部分的演示样例在使用静态方法引用的情况下能够被进一步的简化:- Converter<String, Integer> converter = Integer::valueOf;
- Integer converted = converter.convert("123");
- System.out.println(converted); // 123
上面的演示样例展示了怎样引用一个静态方法。我们相同也能够引用对象方法。
- 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"
- class Person {
- String firstName;
- String lastName;
- Person() {}
- Person(String firstName, String lastName) {
- this.firstName = firstName;
- this.lastName = lastName;
- }
- }
接下来我们定义一个用来创建类person的工厂接口:
- </pre><pre code_snippet_id="265470" snippet_file_name="blog_20140330_13_2612710" name="code" class="java">interface PersonFactory<P extends Person> {
- P create(String firstName, String lastName);
- }
- PersonFactory<Person> personFactory = Person::new;
- Person person = personFactory.create("Peter", "Parker");
Lambda范围
在lambda表达式里訪问外部变量和匿名类的方式是十分类似的。你能够在lambda中訪问外部的final变量,訪问实例字段和静态变量的方法也是如此。訪问本地变量
- final int num = 1;
- Converter<Integer, String> stringConverter =
- (from) -> String.valueOf(from + num);
- stringConverter.convert(2); // 3
可是和匿名变量不同的是变量num不必强制的被声明为final。以下的代码依旧是合法的:
- int num = 1;
- Converter<Integer, String> stringConverter =
- (from) -> String.valueOf(from + num);
- stringConverter.convert(2); // 3
- int num = 1;
- Converter<Integer, String> stringConverter =
- (from) -> String.valueOf(from + num);
- num = 3;
訪问对象字段和静态变量
- 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);
- };
- }
- }
訪问接口默认方法
它们中的一些是老版本号中被熟知的接口,比如Comparator和Runnable。
这些已存在的接口已经通过@FunctionalInterface注解扩展为支持Lambda表达式。
一些新的接口是来自非常出名的Google Guava库。即使你已经对这库十分熟悉了,你也应当留意下这些接口是怎样被扩展的。
断言接口(Predicates)
- 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)
默认方法能够用于将多个函数链接在一起。
- Function<String, Integer> toInteger = Integer::valueOf;
- Function<String, String> backToString = toInteger.andThen(String::valueOf);
- backToString.apply("123"); // "123"
供应接口(Suppliers)
- Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
- greeter.accept(new Person("Luke", "Skywalker"));
比較接口(Comparators)
- 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
选项接口(Optionals)
Optionals并非功能性接口,反而它是一种特殊的工具用来阻止NullPointerException。我们首先高速的浏览Optionals是怎样工作的。由于它在下一章节是十分重要的概念。考虑到方法能够返回non-null结果,偶尔也可能不论什么都不返回。在Java8中,你能够返回Optional而不是返回null。
- 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"
流接口(Streams)
java.util.Stream代表着一串你能够在其上进行多种操作的元素。流操作既能够是连续的也能够是中断的。
中断操作返回操作结果。而连续操作返回流本身,这样你就能够在该行上继续操作。流是创建在数据源上的。比如:java.util.Collection、list集合和set集合。流操作既能够顺序运行也能够并行运行。
- 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
这个操作是连续的,它能够让我们在结果上继续调用另外一个流操作forEach。ForEach接受一个consumer。它被用来对过滤流中的每一个元素运行操作。ForEach是一个中断操作,因此我们不能在ForEach后调用其它流操作。
- stringCollection
- .stream()
- .filter((s) -> s.startsWith("a"))
- .forEach(System.out::println);
- // "aaa2", "aaa1"
Sorted
- stringCollection
- .stream()
- .sorted()
- .filter((s) -> s.startsWith("a"))
- .forEach(System.out::println);
- // "aaa1", "aaa2"
- stringCollection
- .stream()
- .map(String::toUpperCase)
- .sorted((a, b) -> b.compareTo(a))
- .forEach(System.out::println);
- // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match
- boolean anyStartsWithA =
- stringCollection
- .stream()
- .anyMatch((s) -> s.startsWith("a"));
- System.out.println(anyStartsWithA); // true
- boolean allStartsWithA =
- stringCollection
- .stream()
- .allMatch((s) -> s.startsWith("a"));
- System.out.println(allStartsWithA); // false
- boolean noneStartsWithZ =
- stringCollection
- .stream()
- .noneMatch((s) -> s.startsWith("z"));
- System.out.println(noneStartsWithZ); // true
Count
- long startsWithB =
- stringCollection
- .stream()
- .filter((s) -> s.startsWith("b"))
- .count();
- System.out.println(startsWithB); // 3
Reduce
- 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
顺序流的操作是在单线程上运行的。而并行流的操作是在多线程上并发运行的。
- int max = 1000000;
- List<String> 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
Map
- 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));
- </pre>上述的代码应该非常清晰了:putIfAbsent使得我们不用写是否为null值的检測语句;forEach使用consumer来对map中的每一个元素进行操作。</div><div></div><div>以下的样例向我们展示使用功能性函数在map里运行代码:</div><div><pre code_snippet_id="265470" snippet_file_name="blog_20140330_37_9422552" name="code" class="java">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
接下来,我们将学习怎样删除给定键所相应的元素。删除操作还须要满足给定的值须要和map中的值想等:
- map.remove(3, "val3");
- map.get(3); // val33
- map.remove(3, "val33");
- map.get(3); // null
- map.getOrDefault(42, "not found"); // not found
合并map中的实体是十分easy的:
- map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
- map.get(9); // val9
- map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
- map.get(9); // val9concat
Date API
Java8在包java.time以下包括了一款新的date和time的API。新的Date API和Joda-Time库是相兼容的。可是它们不是一样的。以下的演示样例覆盖了新API中的重要部分。Clock
同一时候,时间轴上的时间点是能够用类Instant来表示的。Instants能够被用来创建遗留的java.util.Date对象。
- Clock clock = Clock.systemDefaultZone();
- long millis = clock.millis();
- Instant instant = clock.instant();
- Date legacyDate = Date.from(instant); // legacy java.util.Date
TimeZones
- System.out.println(ZoneId.getAvailableZoneIds());
- // prints all available timezone ids
- 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 now1 = LocalTime.now(zone1);
- LocalTime now2 = LocalTime.now(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
- LocalTime late = LocalTime.of(23, 59, 59);
- 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
它是不变的同一时候工作原理类似于LocalTime。以下的样例描绘了通过加减年,月。日来计算出一个新的日期。须要注意的是这每一个操作都返回一个新的实例。
- 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
- 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是不可变的而且它的工作原理和LocalTime和LocalDate十分类似。
- 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
Instants能够被easy的转换为遗留的java.util.Date类型。
- Instant instant = sylvester
- .atZone(ZoneId.systemDefault())
- .toInstant();
- Date legacyDate = Date.from(instant);
- System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
- 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
Annotations
- @interface Hints {
- Hint[] value();
- }
- @Repeatable(Hints.class)
- @interface Hint {
- String value();
- }
Java8能够使同一个注解类型同一时候使用多次,仅仅要我们在注解声明时使用@Repeatable。
- @Hints({@Hint("hint1"), @Hint("hint2")})
- class Person {}
情景2:使用可反复注解
- @Hint("hint1")
- @Hint("hint2")
- class Person {}
- Hint hint = Person.class.getAnnotation(Hint.class);
- System.out.println(hint); // null
- Hints hints1 = Person.class.getAnnotation(Hints.class);
- System.out.println(hints1.value().length); // 2
- Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
- System.out.println(hints2.length); // 2
- @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
- @interface MyAnnotation {}
总结