翻译于 http://winterbe.com/posts/2014/03/16/java-8-tutorial/
(接上篇)
流(Stream)
java.util.Stream
表示可以执行一个或多个操作的元素序列。 流操作是中间或终端。 当终端操作返回某种类型的结果时,中间操作返回流本身,以便可以连续地连接多个方法调用。 流是在源上创建的,例如 一个java.util.Collection如列表或集(不支持映射)。 流操作可以顺序还是并行执行。
我们先来看看顺序流如何工作。 首先,我们以字符串列表的形式创建一个示例源:
1 2 3 4 5 6 7 8 9
| 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");
|
扩展Java 8中的集合,因此您可以通过调用Collection.stream()或Collection.parallelStream()简单地创建流。 以下部分介绍最常见的流操作。
过滤(Filter)
Filter接受一个谓词来过滤流的所有元素。 这个操作是中间的,这使我们能够调用另一个流操作(forEach)来获得结果。 ForEach接受对过滤流中的每个元素执行的消费者。 ForEach是一个终端操作。 它是void
,所以我们不能调用另一个流操作。
1 2 3 4 5 6 7
| stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println);
|
排序(Sorted)
Sorted是一个中间操作,返回流的排序视图。 元素按照自然顺序进行排序,除非您传递自定义的比较器。
1 2 3 4 5 6 7
| stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println);
|
要注意,排序的仅创建流的排序视图,是不会改变后端集合的排序。 stringCollection的顺序是不变的:
1 2
| System.out.println(stringCollection);
|
映射(Map)
中间操作图通过给定的功能将每个元素转换成另一个对象。 以下示例将每个字符串转换为上一个字符串。 但是您也可以使用map将每个对象转换成另一种类型。 结果流的通用类型取决于传递给映射的函数的通用类型。
1 2 3 4 5 6 7
| stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println);
|
匹配(Match)
可以使用各种匹配操作来检查某个谓词是否与流匹配。 所有这些操作都是终止的,并返回一个布尔结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA);
boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA);
boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ);
|
计数(Count)
1 2 3 4 5 6 7 8
| long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count();
System.out.println(startsWithB);
|
还原(Reduce)
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)
如上所述,流可以是顺序还是并行。 在单个线程上执行顺序流的操作,同时在多个线程上并行执行对并行流的操作。
以下示例说明通过使用并行流来增加性能是多么容易。
首先我们创建一个很大的独特元素列表:
1 2 3 4 5 6 7
| 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));
|
并行排序
1 2 3 4 5 6 7 8 9 10 11 12
| 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));
|
您可以看到两个代码段几乎相同,但是并行排序速度大约提高了50%。所有你需要做的是将stream()更改为parallelStream()。
映射(Map)
1 2 3 4 5 6 7 8
| 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));
|
1 2 3 4 5 6 7 8 9 10 11
| map.computeIfPresent(3, (num, val) -> val + num); map.get(3);
map.computeIfPresent(9, (num, val) -> null); map.containsKey(9);
map.computeIfAbsent(23, num -> "val" + num); map.containsKey(23);
map.computeIfAbsent(3, num -> "bam"); map.get(3);
|
1 2 3 4 5
| map.remove(3, "val3"); map.get(3);
map.remove(3, "val33"); map.get(3);
|
1 2
| map.getOrDefault(42, "not found");
|
1 2 3 4 5
| map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); map.get(9);
map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); map.get(9);
|
日期API(Date API)
Java 8包含java.time包下的全新的日期和时间API。 新的Date API与Joda-Time库是差不多的,但不完全一样。 以下示例涵盖了此新API最重要的部分。
时钟(Clock)
Clock 可以访问当前的日期和时间。Clock 是与时区相关的,也可以用于替代System.currentTimeMillis()
来检索当前的毫秒数。 时间线上的这样一个瞬时点也由类Instant
表示。 可以使用Instants
来创建旧的java.util.Date对象。
1 2 3 4 5
| Clock clock = Clock.systemDefaultZone() long millis = clock.millis()
Instant instant = clock.instant() Date legacyDate = Date.from(instant)
|
时区(Timezones)
时区由ZoneId
表示。它们可以通过静态工厂方法轻松访问。时区定义了在时刻和本地日期和时间之间转换很重要的偏移量。
1 2 3 4 5 6 7 8 9 10
| System.out.println(ZoneId.getAvailableZoneIds());
ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules());
|
本地时间(LocalTime)
LocalTime表示没有时区的时间,例如 晚上10点或17点30分15分。 以下示例为上面定义的时区创建两个本地时间。 然后我们比较两次,并计算两次之间的小时和分钟的差异。
1 2 3 4 5 6 7 8 9 10
| LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2));
long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); System.out.println(minutesBetween);
|
LocalTime具有各种工厂方法,以简化新实例的创建,包括解析时间字符串。
1 2 3 4 5 6 7 8 9 10
| LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late);
DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime);
|
本地日期(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);
|
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);
|
本地日期时间(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);
Month month = sylvester.getMonth(); System.out.println(month);
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay);
|
1 2 3 4 5 6
| Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant();
Date legacyDate = Date.from(instant); System.out.println(legacyDate);
|
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);
|
与java.text.NumberFormat不同,新的DateTimeFormatter是不可变且线程安全的。
注解(Annotations)
Java 8中的注释是可重复的。 我们直接进入一个例子来弄清楚。
首先,我们定义一个包含实际注释数组的包装器注释:
1 2 3 4 5 6 7 8 9
| @interface Hints { Hint[] value(); }
@Repeatable(Hints.class) @interface Hint { String value(); }
|
Java 8使我们能够通过声明@Repeatable
注解来使用相同类型的多个注解.
变式1:使用容器注解
1 2
| @Hints({@Hint("hint1"), @Hint("hint2")}) class Person {}
|
变式2:使用可重复的注解
1 2 3 4
| @Hint("hint1") @Hint("hint2") class Person {}
|
使用变式2,java编译器会隐式设置@Hints
,对于像反反射这样的注解是非常重要的。
1 2 3 4 5 6 7 8
| Hint hint = Person.class.getAnnotation(Hint.class); System.out.println(hint);
Hints hints1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length);
Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); System.out.println(hints2.length);
|
虽然我们从未在Person类上声明@Hints
注解,但它仍然可以通过getAnnotation(Hints.class)
来读取。 然而getAnnotationsByType
更方便的方法是直接访问所有@Hint注解。
此外,Java 8中的注解的使用扩展到两个新的目标:
1 2
| @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}
|
(翻译完)
自己的看法
流的部分还是比较实用的
map部分,感觉变化不大
时间api方面,用得不多,要使用的再看
注解部分就是简化了一点