1.Java7新特性速览
1.1 语法变化
1.1.1 switch增强
switch-case语法支持String类型了.在此之前只能用于byte、short、char、int以及它们的包装类型还有枚举类型.
1 | String name = "jjzi"; |
1.1.2 二进制字面量
1 | int binary = 0b1000; |
1.1.3 数字分隔符
较长的数字可以用下划线分隔,便于代码的阅读.
1 | long number = 1211_2313_2313L; |
1.1.4 try-catch增强
try-catch语法的变化主要有两点:
- try后面可以加括号,里面可以初始化对象,但这些对象要实现AutoClosable或Closeable接口.这些对象在语句try-catch结束之后,会自动关闭,不需要显示调用它们的
close()方法. - catch可以一次捕获多种异常.
在此之前,一段读取文件内容并打印的代码,需要这样写:
1 | BufferedReader br=null; |
在新版本中只要这么写:
1 | try (FileReader fr=new FileReader(new File("1.txt")); |
1.1.5 菱形语法
在7之前,声明一个带泛型的集合需要这样写:
1 | List<String> list = new ArrayList<String>(); |
现在只需要这么写:
1 | List<String> list = new ArrayList<>(); |
因为编译器可以从前面的变量声明中推断出类型参数,所以后面的ArrayList可以不用写泛型参数,只用一对空着的尖括号就可以。
1.2 新的API
1.2.1 文件操作API
新的文件操作API提供了以下方便的功能:
- 获取系统根目录
- 获取文件的目录层级
- 获取某个文件路径的子路径
- 根据相对路径得到绝对路径
- 计算两个文件的相对路径
- 目录下文件的过滤
- 目录的深层遍历
- 文件属性的操作,如文件权限,所属用户和组
- 移动和复制文件
- 软链接(symbol link)的支持
- 更简便的获取缓冲流
- 文件监控
1.2.2 AIO
提供了异步网络通道和异步文件通道,实现了真正的异步非阻塞IO.
1.2.3 Fork/Join并发框架
这个框架是为了实现线程池中任务的自动调度.把一个大的任务分解成一个个小任务,并发地执行它们,最后把它们的结果进行合并.类似MapReduce.
1.2.4 更简单的反射API
Java7为间接调用方法引入了新的API,简化了方法的查找和调用.
1.2.5 Objects工具类
这个工具类提供了很多可以让重写equals,hashCode的非常简便的方法.
2 Java8
2.1 语法变化
2.1.1 lambda表达式
lambda表达式是Java8最重要的特性.它在很多编程语言中都已经存在,如JS,Python,Haskell.它可以被看作是匿名函数的简洁表示.它没有名称,但有参数列表,有函数体和返回值.
对一个字符串数组按照字符串长度排序,在Java8之前需要这样写:
1 | class LengthComparator implements Comparator<String>{ |
或者这样写:
1 | String[] strings = {"a", "bcd", "ef"}; |
使用lambda可以这样写:
1 | String[] strings = {"a", "bcd", "ef"}; |
其中,圆括号内的是参数,如果参数类型可以被推导出来,可以省略.实践中,大多数情况都不需要写.如果参数只有一个,可以省略圆括号.
1 | EventHandler<ActionEvent> listener = event -> System.out.println("Click"); |
如果lambda不需要参数,那么直接写一对空的括号.如果其中要写的代码不止一行,需要使用花括号.
1 | new Thread(() -> { |
任何的只包含一个abstract方法的接口,我们都可以使用lambda来代替.这种接口被称为函数接口,比如Runnable和Comparator.它们的作用只是为了封装代码,因为Java中方法必须包装在类中.在Java8中,引入了@FunctionalInterface这个注解来标明这个接口是函数接口.
1 |
|
也可以不用这个注解,只要符合只包含一个抽象方法这个条件,都被认为是函数接口.实践中最好还是加上,这样编译器会检查这个接口是否符合作为函数接口的条件.
lambda表达式实际还是接口的对象,任意一个lambda表达式都可以等价转换成现在所使用的API中对应的函数式接口.
1 | Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length()); |
2.1.2 方法引用
有时候,我们要写的表达式其实已经有方法实现了,例如,你只想在按钮点击时打印event对象,使用刚刚学到的知识,代码这么写:
1 | Arrays.stream(new int[]{1,2,3}).forEach(i-> System.out.println(i)); |
这样写当然也很简洁,但其实我们直接把函数传递给forEach方法.
1 | Arrays.stream(new int[]{1,2,3}).forEach(System.out::println); |
其中,System.out::println是一个方法引用,等同于x->System.out.println(x).再举一个例子,假如你想不区分大小写地对字符串进行排序,那么可以这样写:
1 | Arrays.sort(new String[]{"a","dc","be"},String::compareToIgnoreCase); |
方法引用有三种形式:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
前两种情况,方法引用等同于接收参数并用这些参数去调用这个方法的lambda表达式,System.out::println等同于x->System.out.println(x),Math::pow等同于(x,y)->Math.pow(x,y).
第三种情况,传入的第一个参数会成为执行方法的对象,余下的参数传递给方法,例如String::compareToIgnoreCase,等价于(x,y)->x.compareToIgnoreCase(y).
和lambda表达式一样,方法引用也不会独立存在,它们经常被用于转换成函数式接口的实例.
2.1.3 构造器引用
它其实是方法引用的变种,语法和方法引用一样,只是方法名固定是new.如果有多个构造器.那么选哪个呢?和方法引用遇到的问题的答案一样,这取决于上下文.
1 | List<String> labels = new ArrayList<>(); |
存在多个Button构造器,但是编译器会选择有一个String参数的构造器,因为它从上下文推测出调用构造器使用一个String.
你可以使用数组类型来建立构造器引用.比如int[]::new是带一个参数的构造器引用,这个参数就是数组长度.它等价于x->new int[x].
使用数组构造器引用可以很好的克服一个Java的限制.我们知道Java有类型擦除机制,所以在设计库的时候,new T[]这样的语法是错误的,类型擦除会把它变成new Object[].所以,Stream的一个方法toArray,它返回的类型是Object数组:
1 | Object[] btns = buttons.stream().toArray(); |
但这样并不尽如人意,用户肯定是想要一个Button数组.通过数组构造器引用,Stream库解决了这个问题.
1 | Button[] btns= buttons.stream().toArray(Button[]::new); |
2.1.4 默认方法
很多语言都将函数表达式应用到了集合库中.Java8同样如此.但是,Java的集合库是很早就有的,如果要给Collection接口添加新的方法,如forEach,那么所有实现了Collection接口的类都必须实现这个方法,这令人无法接受也没有必要.Java的设计者通过允许接口带有具体实现的方法来一劳永逸的解决这个问题,这些方法就叫做默认方法.下面是一个例子:
1 | interface Person{ |
默认方法完全可以取代模板方法模式.
2.1.5 接口中的静态方法
Java8中,接口是可以添加静态方法的.技术上,这是完全没问题的,但这看起来违反了接口作为一个抽象定义的原则.至今,我们经常在相互一起使用的类中使用静态方法,如标准库中的Collection/Collections或者Path/Paths这样成对的接口和类.以Paths为例子,它有一些工程方法,用来产生Path.在Java8中,你可以把这些方法移到Path接口中,这样Paths就没必要存在了.
Java8中,很多接口已经添加了静态方法.例如,Comparator接口提供了一个很实用的比较方法,它接受一个键提取函数,并返回一个用来比较提取出的键的比较强.比如,要根据名称对Person对象进行比较,可以使用函数Comparator.comparing(Person::getName).之前我们写过这样的代码:
1 | Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length()); |
其实下面这样写更简单:
1 | Comparator<String> comp = Comparator.comparingInt(String::length) |
2.2 Stream
以前,我们要处理一个集合,需要使用for循环或Iterator来遍历这个集合,迭代的时候每次取一个元素,然后对这个元素做一些需要的操作.比如,计算字符串数组中长度大于10的字符串数量:
1 | int count=0; |
这样的代码有两个问题,一个是代码比较臃肿,还有一个是很难并行.Java8引入Stream就是解决这两个问题.下面是用Stream来实现同样的功能:
1 | long count = words.stream().filter(w -> w.length() > 10).count(); |
Stream看着和集合很像,但其实它们有着下面几大区别:
- Stream并不存储元素,它们可能是存储在底层的集合中或者根据需要生成.
- Stream并不改变它们的数据源,相反.它们返回持有结果的新的Stream.
- Stream操作可能是延迟执行的.这意味着它们会等到需要结果的时候才会执行.例如,如果你只需要前五个长度大于10的单词,那么filter方法会在第五次匹配之后停止过滤.因此,你甚至可以拥有无限的流.
使用Stream一般分三个步骤:
- 创建一个Stream.
- 在一个或多个步骤中,指定将初始的Stream转换成另一个Stream的中间操作.
- 使用一个终止操作产生结果.这个操作强制它之前的延迟操作立即执行,在这之后,Stream不能再被使用.
2.2.1 生成Stream
我们刚刚已经看到可以使用Java8加入Collection接口的stream方法把任何集合转变成Stream.如果你想要把数组转成Stream,可以使用静态方法Stream.of:
1 | Stream<String> words = Stream.of(content.split("[\\P{L}]+")); |
of还有一个可变的参数的版本,所以你可以用任意数量的参数来构造Stream:
1 | Stream<String> songs = Stream.of("gently", "down", "the", "stream"); |
创建一个空的Stream:
1 | Stream<String> silence = Stream.empty(); |
Stream接口有两个静态方法用来创建无限Stream.generate方法接受一个无参数的函数(或者,技术上说,一个Supplier
1 | Stream<String> echos = Stream.generate(() -> "Echo"); |
而要产生像”0 1 2 3 …“这样的序列,使用iterate方法.它接受一个初始值和一个函数(技术上来说,一个UnaryOperation
1 | Stream<Integer> integers = Stream.iterate(0, i->i+1); |
静态方法Files.lines返回包含文件所有行的Stream.Stream接口有一个AutoCloseable父接口.当Stream的close方法被调用的时候,底层的文件也会被关闭.
1 | try(Stream<String> lines=Files.lines(Paths.get("gradlew"))) { |
如果要得到可以并行的Stream,可以调用Collection接口的parallelStream()方法.并行Stream一般来说性能比一般的Stream高,特别是Stream的元素较多的时候.
1 | Stream<Integer> parallelIntegers = Arrays.asList(1,2,3).parallelStream(); |
调用Stream的limit(n)方法,返回含有n个元素的新的流(或者返回原来的流如果原来流的大小比n小).这个方法对于把无限的流切成一定大小是很有用的.
1 | Stream<Double> randoms = Stream.generate(Math::random).limit(100); |
Stream的skip(n)方法和它正好相反,它会丢弃前n个元素.
1 | Stream<Integer> integerStream = Stream.of(0,1, 2, 3).skip(1); |
还可以用Stream类的静态方法concat把两个流连接起来.
1 | Stream<Character> combined = Stream.concat(Stream.of("Hello"), Stream.of("World")); |
还有一个peek方法.它生成的流中的元素和原来的流完全一样,但是每次在其中取一个元素都会调用一个函数,这个方法对于debug是很方便.
1 | Object[] powers = Stream.iterate(1, integer -> integer * 2).peek(e -> System.out.println("Fetching:" + e)).limit(20).toArray(); |
当某个元素真正被访问了,才会打印出来一个消息。这可以验证对流的处理是lazy的。
2.2.2 转换Stream
主要介绍filter,map,forEach,flatMap,distinct,reverse这几个常用的转换方法.filter这个转换方法,它会生成一个新的流,其中包含了满足特定条件的所有元素.filter的参数是一个Predicate<T>,这是一个从T到boolean的函数.
1 | List<String> wordsList = new ArrayList<>(); |
我们通常会想对一个流中的值进行某种形式的转换,这就需要使用map方法,传递给它一个执行转换的函数.
1 | Stream<String> lowcaseWords = words.map(String::toLowerCase); |
如果map使用的转换函数返回的是一个Stream,例如:
1 | public static Stream<Character> characterStream(String s |
那么map返回的是一个包含流的流,如果需要的是包含字符的流,那么需要使用flatMap而不是map.
1 | Stream<Character> characterStream = words.flatMap(TransformMethod::characterStream); |
目前介绍的这几个方法都是无状态的转换,即当你从一个已过滤的或已映射过的流中获取一个元素的时候,它的结果并不依赖于之前获取的元素.除此之前,也有一些有状态的转换方法.例如distinct方法,它根据原来的流中的元素返回一个剔除了重复的元素的流.这显然是必须记住已经读取的元素的.
1 | Stream<String> uniqueWords = Stream.of("merrily", "merrily", "merrily", "gently").distinct(); |
sorted方法必须遍历整个流,并在产生任何元素之前就对其进行排序,毕竟,最小的元素可以位于最后.它不能应用于一个无限长的流.
1 | Stream<String> longestString=words.sorted(Comparator.comparingInt(String::length).reversed()); |
2.2.3 Optional
我们接下来要介绍的是流的聚合方法,但是在此之前,需要了解Java8另一个重要的类:Optional,因为很多聚合方法返回的类型就是Optional<T>.Optional<T>是一种更好的表明方法的返回值可能是null的方式,这很好的避免了抛出NullPointerException,但你需要正确的使用它.
先看一个错误的例子:
1 | Optional<T> optionalValue = ...; |
其中,get()方法是取出其中分装的对象.它并不比下面的代码安全:
1 | T value = ...; |
再看一个:
1 | if (optionalValue.isPresent()) optionalValue.get().someMethod(); |
其中,isPresent测试是否分装了值.它并不比下面的代码更简洁:
1 | if (value != null) value.someMethod(); |
高效的使用Optional的关键是,使用一个接收正确的值或者产生替代值的方法.
方法ifPresent接收一个函数,如果存在可选值,那么它会被传递给这个函数,如果没有,什么都不会发生.
1 | optionalValue.ifPresent(System.out::println); |
ifPresent没有返回值,如果你希望对结果进行处理,用map方法:
1 | List<String> results = new ArrayList<>(); |
added可能有三种值:封装着true或false的Optional对象如果optionalValue中有值的话,否则的话是一个空的Optional对象.这个map方法和流的map方法是类似的,你可以把Optional看成是一个大小是0或1的流.但如果传入的方法返回值类型是Optional<U>,那么map返回的类型就是Optional<Optional<U>>,而你只想得到Optional<U>,那么使用flatMap,这也是和流类似的.
1 | public static Optional<Double> square(Double x){ |
另一个使用可选值的策略是当没有值存在时产生一个替代值.通常,当没有匹配项时,你会想要使用一个默认值,例如空字符串.
1 | String result = optionalString.orElse(""); |
你也可以调用函数来计算默认值:
1 | String result=optionalString.orElseGet(() -> System.getProperty("user.dir")) |
或者,你想在没有值的时候抛出异常:
1 | String error = optionalString.orElseThrow(NoSuchElementException::new); |
2.2.4 聚合方法
我们之前已经见过一个聚合方法count,还有简单的聚合方法如min和max,它们返回类型是Optional<T>.
1 | Optional<String> largest = sortedWords.max(String::compareToIgnoreCase); |
findFirst方法返回集合中的第一个值,返回的也是Optional<T>.它通常和filter方法结合使用.比如,返回第一个以Q开头的单词:
1 | Optional<String> startWithQ = words.filter(s -> s.startsWith("Q")).findFirst(); |
如果你不在乎是否是第一个,只想要任意的匹配项,可以使用findAny,它在你并行地处理流的时候非常高效,因为在任意片段中找到了第一个匹配项就会结束整个计算了.
1 | Optional<String> startWithQ = words.parallel().filter(s -> s.startsWith("Q")).findAny(); |
如果你只想知道流中是否有匹配的元素,使用anyMatch:
1 | boolean aWordStartsWithQ = words.parallel().anyMatch(s -> s.startsWith("Q")); |
还有两个方法allMatch和noneMatch,它们分别在所有元素或没有元素匹配的时候返回true.
最重要的聚合方法是reduce.它有三种形式.
第一种接收一个二元函数,并不断应用它.第一次的参数是是流的前两个元素,然后是第一次调用的结果和流的第三个元素,依次类推,直到流的最后.这个方法返回的是一个Optional,因为流可能是空的.下面是一个求流的元素的和的例子:
1 | Stream<Integer> values = Stream.of(1, 2, 3, 4, 5); |
这个函数需要是满足结合律的,也即是说这个操作和你组合元素的顺序无关,(x op y) op z = x op (y op z).这样就允许使用并行流进行高效的聚合.在实践中,有很多有用的结合操作,例如和,积,字符串连接,最大值和最小值,并集和交集.
第二种形式,相比第一个形态多了一个参数,作为初始值.
1 | Integer sum = values.reduce(0, Integer::sum); |
现在假设你有一个包含多个对象的流,你想要对对象们的某个属性求和,比如流中字符串的总长.你不能使用简单形式的reduce方法,因为它需要的一个函数(T,T)->T,其中参数类型和返回值类型都必须是一样的,而现在的情况下,这两种类型是不同的,流的元素的类型是字符串,而累积结果的类型是整型.幸好有另一种形式的reduce可以处理这个情况.下面是它的参数列表:
1 | <U> U reduce(U , BiFunction<U, ? super T, U> , BinaryOperator<U> combiner); |
第一个参数是初始值,第二个参数是一个累加器函数,最后一个函数是为了在并行计算合并多个子结果的时候使用的.下面是计算流中字符串总长的代码:
1 | words.reduce(0, (s, word) -> s + word.length(), (total, subtotal) -> total + subtotal) |
但实践中,不会大量使用reduce方法.就拿上面这个例子来说,更好的方法是先把字符流映射到数字流,然后求和.
1 | words.mapToInt(String::length).sum() |
2.2.5 收集结果
当你对一个流进行了处理,你通常想要看到结果而不是把它们聚合成一个值.你可以调用iterator方法,生成一个旧式的迭代器用来访问元素.或者调用toArray获取流元素组成的一个数组,就像之前看到.
但如果你想要把结果放到一个List或者Set中,那么你要用collect方法.它有三个参数:
- 一个生成目标对象实例的生产者,比如,一个HashSet的构造器.
- 一个把元素添加到目标对象的累加器.例如,add方法.
- 一个把两个对象合成一个的组合器,如addAll方法.
注意,目标对象不一定是集合,也可以是StringBuilder或一个跟踪数量和总和的对象.下面是产生一个HashSet的collect的写法:
1 | Stream<Integer> nums=Stream.of(1,2,4,7,6,2,9); |
实践中,你不需要这样写,因为有一个便利的Collector接口代替这三个函数,并且有包含了很多常用的Collector的类Collectors.
1 | nums.collect(Collectors.toSet()); |
如果想要控制获得的set类型,可以像下面这样调用:
1 | nums.collect(Collectors.toCollection(TreeSet::new)); |
如果想要通过拼接把流中的所有字符串收集起来,你可以调用:
1 | Stream<String> words=Stream.of("what", "which", "where","when"); |
如果你想要对流进行聚合求和,平均值,最大值v,最小值,可以使用Collectors中的summarizing方法.这些方法接收一个可以把流中的对象映射到数字的函数,返回一个类型是(Int|Long|Double)SummaryStatistics的对象,它们带有获取和,平均值,最大值,最小值的方法.
1 | IntSummaryStatistics statistics =words.collect(Collectors.summarizingInt(String::length)); |
如果你只是想要打印流中的元素或者把它们放入数据库,可以使用forEach:
1 | stream.forEach(System.out::println); |
你传入的这个函数会被应用到流中的每个元素上.在一个并行流中,你需要保证这个函数式可以被并发地执行的.并行流中的元素可能会被以任意的顺序遍历,如果你想按照原始的顺序执行,调用forEachOrdered.当然,这样你就放弃了并行带来的好处.forEach和forEachOrdered方法都是终结操作,调用它们之后,你无法再使用流了.如果你想继续使用流,使用之前提到的peek.
2.2.6 将结果收集到Map
假设你有一个Person的流,你想把元素收集到一个Map以便可以使用它们的ID来进行查找.你可以使用Colletors.toMap方法,它有两个函数参数,分别用来生成Map的键和值.例如:
1 |
|
但一般情况下,Map的值应该是实际的对象,所以使用Function.identity()作为第二个参数,这个函数接受一个参数,然后直接返回它:
1 | Map<Integer, Person> idToPerson = people.collect(Collectors.toMap(Person::getId, Function.identity())); |
如果多个元素有相同的键,那么收集器会抛出IllegalStateException异常.但你可以通过提供第三个函数参数重写这个行为.这个函数的参数是已有值和新的值,你可以返回它们中的一个或者根据它们另外产生一个值来返回.这里,我们构造一个map,对于系统的支持的所有语言,以其默认语言环境中的名字作为键,以其本地化的名字作为值.
1 | Stream<Locale> locales = Stream.of(Locale.getAvailableLocales()); |
但是,如果我们希望知道指定国家的所有语言,那么我们需要得到一个Map<String,Set<String>>对象.首先,我们把每种语言存到单个集中,当发现指定国家的新的语言时,我们就将已有值和新值组合成一个新的集合.
1 | Map<String, Set<String>> countryLanguageSets = locales |
如果你想要的是一个TreeMap,那么你需要提供一个构造器引用作为第四个参数:
1 | Stream<Locale> locales = Stream.of(Locale.getAvailableLocales()); |
对于上面的三种toMap方法,都有对应的toConcurrentMap方法,用来产生一个ConcurrentMap.
2.2.7 分组和分片
对有共同特性的值进行分组是是很常见的需求,groupingBy方法就是专门用于分组的.还是对国家语言分组的问题,使用groupingBy方法的写法:
1 | Map<String, List<Locale>> country2Locales= locales.collect(Collectors.groupingBy(Locale::getCountry)); |
当分类函数是一个Predicate函数时,流元素会被分为两组,一组是函数会返回true的元素,另一组是返回false的元素.这种情况下,使用partitionBy方法.例如,把所有语言环境分为两组,一组是使用英语一组使用的是其他语言:
1 | Map<Boolean, List<Locale>> englishAndOtherLocales = locales.collect(partitioningBy(l -> l.getLanguage().equals("en"))); |
groupingBy方法生成的map的值是list,如果你想要使用某种方式处理这些list的时候,你要提供一个downstream收集器.例如,你想要set而不是list,你可以使用Collectors.toSet.
1 | Map<String, Set<Locale>> countryToLocaleSet = locales.collect(groupingBy(Locale::getCountry, toSet())); |
还有若干其他的收集器可以用于分好组的元素的处理:
counting产生被收集的元素的个数,例如:
1 | Map<String, Long> countryToLocaleCounts = locales.collect(groupingBy(Locale::getCountry, counting())); |
- summing(Int|Long|Double)接受一个函数参数,并应用这个函数到各个分组的元素,生成它们的合,例如:
1 | Map<String, Integer> stateToPopulation = cities.collect(groupingBy(City::getState, summingInt(City::getPopulation))); |
- maxBy和minBy接收一个比较器并产生各个分组的元素的最大值和最小值.例如:
1 | Map<String, Optional<City>> stateToLargestCity = cities |
mapping应用一个函数到各个分组,它还需要另一个收集器处理之前的结果.还记得上一个章节中的收集国家的所有语言到一个set的问题吗?mapping方法可以提供一个更加漂亮的解决方案:
1 | Map<String, Set<String>> countryToLanguages = locales |
- 最后,
reducing方法可以对downst的元素应用一个聚合函数.它有三种形式,这其实和之前的reduce的三种形式是对应的.下面的代码可以获取每个州以及州的所有城市名拼接成的字符串的Map:
1 | Map<String, String> stateToCityNames = cities.collect(groupingBy(City::getState, reducing("", |
和reduce一样,reducing用的并不多.这个例子中,我们可以更自然的达到目的:
1 | stateToCityNames = cities.collect(groupingBy(City::getState, mapping(City::getName, joining(",")))); |
显然,使用downstream收集器会产生非常复杂的代码.所以,你应该只在处理groupingBy或partitionBy产生的分组的时候使用它们,其他情况,只需要使用诸如map,reduce,count,max或min这些简单的方法即可.
2.2.8 原生类型流
原生类型流
目前为止,我们都是把整数收集到一个Stream<Integer>,尽管把每个整形放入包装对象显然是低效的做法.其他原始类型也是一样的.为此,提供了特化类型IntStream,LongStream和DoubleStream,它们直接存储原始类型值,不需要包装.下面是对应关系:
- 存放short、char、byte、int和boolean类型的值,使用
IntStream - 存放float和double类型的值,使用
DoubleStream. - 存放long类型的值,使用
LongStream.
要创建一个IntStream,可以调用IntStream.of和Arrays.stream方法:
1 | IntStream stream = IntStream.of(1, 2, 3, 4); |
和对象流一样,IntStream和LongStream提供了静态方法range和rangeClosed来生成步数为1的数列.
1 | IntStream zeroToNinetyNine = IntStream.range(1, 100); |
当你有一个对象流,你可以使用mapToInt,mapToDouble,mapToLong方法把它转换成原生类型流.
1 | Stream<String> words=Stream.of("what", "which", "where","when"); |
使用boxed方法把原生类型流转换为它们的封装对象流:
1 | Stream<Integer> integers = IntStream.range(0, 100).boxed(); |
Random类的ints,longs和doubles方法返回随机数的原生类型流,但要注意,这些流是无限流.它们一般用于生成若干个随机数.
1 | Random random = new Random(); |
2.2.9 并行流
流使对大量运算并行化非常容易.这一过程基本是自动的,但你也需要遵守一些规则.首先,你要有一个并行流.除了Collection.parallelStream方法,流操作生成的都是顺序流.parellel方法可以把任意顺序流转换成并行流.
1 | Stream<Integer> parallelIntegers = Arrays.asList(1, 2).parallelStream(); |
只要流是处于并行模式,当执行终结方法的时候,所有的中间流操作都会被并行化.并行化的目的,是希望能得到和顺序执行一样的结果,所以操作必须是无状态和顺序无关,并且是线程安全的,这很重要.
默认情况下,从有序集合(数组或列表),范围值,生成器以及迭代器,或者Stream.sorted所产生的流,都是有序的.对这些流操作的结果也是按照顺序累计的,是完全可预测的.
当不考虑顺序时,某些操作可能会更加高效地并行化.通过调用stream.unordered方法,你可以表明你不关心顺序.你还可以通过放弃有序来加快limit方法的速度.如果你想要一个流中任意n个元素,可以这样写:
1 | stream.parallel().unordered().limit(10); |
注意,当你执行一个流操作的时候,你并不会修改流底层的集合(即使这个操作是线程安全的).准确一点说,由于中间流操作是延迟执行的,所以在终结操作执行的之前改变集合是可能的.例如,下面的代码是正确的:
1 | Stream<String> words = Stream.of("BEGIN","RUNNING"); |
2.2.10 函数式接口
在本章中,已经看了很多参数为函数的方法.例如Stream.filter方法:
1 | Stream<T> filter(Predicate<? super T> predicate); |
查看javadoc,发现Predicate是一个函数式接口,只包含一个返回boolean的非默认方法.在实际开发中,一般会传递lambda或者方法引用到filter方法.所以这个方法的名字并不重要,重要的是返回的是boolean值.你在看文档的时候,只要记住Predicate是一个返回boolean值的函数就行.
下面的图列举了一些常用的函数式接口:


2.4 其他更新
- 新的JavaFx API.
- 新的时间和日期API,它比
Calender更易用. - 新的JavaScript引擎Nashorn.
String.join(",",a,b,c),提供了方便的分隔符字符串拼接的方法.Integer类现在支持无符号数学运算Math添加了检测整数是否溢出.Collection和List中提供了很多新方法.- 对Base64提供了官方支持
- 注解可以重复使用,并且可以用在类型上.
Objects提供了对null的检查.java.util.Logger新增了一些方法,并且可以传入一个Supplier<T>参数来实现延迟构造信息.Pattern类增加了splitAsStream方法,可以将一个CharSequence按照正则表达式分隔,返回一个Stream.- JDBC升级到版本4.2