基础知识
为什么要关心Java 8
- Java怎么又变了
- 日新月异的计算应用背景:多核和处理大型数据集(大数据)
- 改进的压力:函数式比命令式更适应新的体系架构
- Java 8的核心新特性:Lambda(匿名函数)、流、默认方法
Java 8是在2014年3月发布的,为什么你应该关心Java 8?
我们的理由是,Java 8所做的改变,在许多方面比Java历史上任何一次改变都深远。而且好消息是,这些改变会让你编起程来更容易,用不着再写类似下面这种啰嗦的程序了(对inventory中的苹果按照重量进行排序):
Collections.sort(inventory, new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
在Java 8里面,你可以编写更为简洁的代码,这些代码读起来更接近问题的描述:
inventory.sort(comparing(Apple::getWeight));
Lambda表达式
函数式接口
函数式接口是一种包含单一抽象方法(single abstract method)的接口。类通过为接口中的所有方法提供实现来实现任何接口,这可以通过顶级类(top-level class)、内部类甚至匿名内部类完成。
public class RunnableDemo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(
"inside runnable using an anonymous inner class");
}
}).start();
new Thread(() -> System.out.println(
"inside Thread constructor using lambda")).start();
}
}
lambda表达式必须匹配接口中单一抽象方法签名的参数类型和返回类型,这被称为与方法签名兼容。因此,lambda表达式属于接口方法的实现,可以将其赋给该接口类型的引用。
Runnable r = () -> System.out.println(
"lambda expression implementing the run method");
new Thread(r).start();
File directory = new File("./src/main/java");
String[] names = directory.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}
});
System.out.println(Arrays.asList(names));
File directory = new File("./src/main/java");
String[] names = directory.list((dir, name) -> name.endsWith(".java"));
java.util.function.Predicate
java.util.function.Predicate<T>
接口定义了一个名叫test
的抽象方法,它接受泛型T
对象,并返回一个boolean
。
Optional.filter(Predicate<? super T> predicate)
如果值存在且匹配某个给定的谓词,则返回描述该值的Optional,否则返回一个空 Optional。
Collection.removeIf(Predicate<? super E> filter)
删除集合中所有满足谓词的元素。
Stream.allMatch(Predicate<? super T> predicate)
如果流的所有元素均满足给定的谓词,则返回true。anyMatch和noneMatch 方法的用法与之类似。
Collectors.partitioningBy(Predicate<? super T> predicate)
返回一个Collector,它将流分为两类(满足谓词和不满足谓词)。
java.util.function.Consumer
java.util.function.Consumer<T>
定义了一个名叫accept
的抽象方法,它接受泛型T
的对象,没有返回(void)。你如果需要访问类型T
的对象,并对其执行某些操作,就可以使用这个接口。在所有传入Consumer作为参数的方法中,最常见的是java.util.Iterable
接口的默认forEach
方法。
List<String> strings = Arrays.asList("this", "is", "a", "list", "of", "strings");
strings.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
strings.forEach(s -> System.out.println(s));
strings.forEach(System.out::println);
- 匿名内部类实现
- lambda表达式
- 方法引用
Optional.ifPresent(Consumer<? super T> consumer)
如果值存在,则调用指定的consumer;否则不进行任何操作。
Stream.forEach(Consumer<? super T> action)
对流的每个元素执行操作。Stream.forEachOrdered方法与之类似,它根据元素的出现顺序(encounter order)访问元素。
Stream.peek(Consumer<? super T> action)
首先执行给定操作,再返回一个与现有流包含相同元素的流。
java.util.function.Function<T, R>
java.util.function.Function<T, R>
接口定义了一个叫作apply
的方法,它接受一个 泛型T的对象,并返回一个泛型R
的对象。如果你需要定义一个Lambda
,将输入对象的信息映射 到输出,就可以使用这个接口。
List<String> names = Arrays.asList("Mal", "Wash", "Kaylee", "Inara",
"Zoë", "Jayne", "Simon", "River", "Shepherd Book");
List<Integer> nameLengths = names.stream()
.map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return s.length(); }
})
.collect(Collectors.toList());
nameLengths = names.stream()
.map(s -> s.length())
.collect(Collectors.toList());
nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.printf("nameLengths = %s%n", nameLengths);
// nameLengths == [3, 4, 6, 5, 3, 5, 5, 5, 13]
java.util.function.Supplier
Supplier接口相当简单,它不包含任何静态或默认方法,只有一个抽象方法T get()。
为实现Supplier接口,需要提供一个不传入参数且返回泛型类型(generic type)的方法。
根据Javadoc的描述,调用Supplier时,不要求每次都返回一个新的或不同的结果。
Supplier的一种简单应用是Math.random方法,它不传入参数且返回double型数据。
Logger logger = Logger.getLogger("...");
DoubleSupplier randomSupplier = new DoubleSupplier() {
@Override
public double getAsDouble() {
return Math.random();
}
};
randomSupplier = () -> Math.random();
randomSupplier = Math::random;
logger.info(randomSupplier);
- 匿名内部类实现
- lambda表达式
- 方法引用
Supplier的一个主要用例是延迟执行(deferred execution)。
通过行为参数化传递代码
行为参数化
- 匿名类
- Lambda表达式
方法引用
使用双冒号表示法(::)将实例引用或类名与方法分开。
Stream.of(3, 1, 4, 1, 5, 9).forEach(x -> System.out.println(x));
Stream.of(3, 1, 4, 1, 5, 9).forEach(System.out::println);
Consumer<Integer> printer = System.out::println;
Stream.of(3, 1, 4, 1, 5, 9).forEach(printer);
语法
方法引用包括以下三种形式,其中一种存在一定的误导性。
object::instanceMethod
引用特定对象的实例方法,如System.out::println。
Class::staticMethod
引用静态方法,如Math::max。
Class::instanceMethod
调用特定类型的任意对象的实例方法,如String::length。
最后一种形式或许令人困惑,因为在Java开发中,一般只通过类名来调用静态方法。请记住,lambda 表达式和方法引用在任何情况下都不能脱离上下文存在。以对象引用为例,上下文提供了方法的参数。对于System.out::println,等效的lambda表达式为:
// 相当于System.out::println
x -> System.out.println(x)
静态方法max与之类似:
// 相当于Math::max
(x, y) -> Math.max(x,y)
通过类名来调用实例方法,其等效的lambda表达式为:
// 相当于String::length
x -> x.length()
构造函数引用
List<String> names =
Arrays.asList("Grace Hopper", "Barbara Liskov", "Ada Lovelace",
"Karen Spärck Jones");
List<Person> people = names.stream()
.map(name -> new Person(name))
.collect(Collectors.toList());
// 或采用以下方案
List<Person> people = names.stream()
.map(Person::new)
.collect(Collectors.toList());
默认方法
接口中的默认方法
接口中的静态方法
默认方法的使用模式
可选方法
以Iterator接口为例来说。Iterator接口定义了hasNext、next,还定义了remove方法。Java 8之前,由于用户通常不会使用该方法,remove方法常被忽略。因此,实现Interator接口的类 通常会为remove方法放置一个空的实现,这些都是些毫无用处的模板代码。
采用默认方法之后,可以为这种类型的方法提供一个默认的实现,这样实体类就无需在自 己的实现中显式地提供一个空方法。
interface Iterator<T> { boolean hasNext();
T next();
default void remove() {
throw new UnsupportedOperationException();
}
}
行为的多继承
Java的类只能继承单一的类,但是一个类可以实现多接口。
- 类型的多继承
- 利用正交方法的精简接口
- 组合接口
用Optional取代null
如何为缺失的值建模
采用防御式检查减少NullPointerException
if
null带来的种种问题
-
它是错误之源。
NullPointerException是目前Java程序开发中最典型的异常。
-
它会使你的代码膨胀。
它让你的代码充斥着深度嵌套的null检查,代码的可读性糟糕透顶。
-
它自身是毫无意义的。
null自身没有任何的语义,尤其是,它代表的是在静态类型语言中以一种错误的方式对缺失变量值的建模。
-
它破坏了Java的哲学。
Java一直试图避免让程序员意识到指针的存在,唯一的例外是:null指针。
-
它在Java的类型系统上开了个口子。
null并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量。
Optional
Optional类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空” 的Optional对象,由方法Optional.empty()返回。
创建Optional对象
声明一个空的Optional
Optional<Car> optCar = Optional.empty();
依据一个非空值创建Optional
Optional<Car> optCar = Optional.of(car);
如果car是一个null,这段代码会立即抛出一个NullPointerException。
可接受null的Optional
Optional<Car> optCar = Optional.ofNullable(car);
如果car是null,那么得到的Optional对象就是个空对象。
使用map从Optional对象中提取和转换值
String name = null;
if(insurance != null){
name = insurance.getName();
}
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
使用flatMap链接Optional对象
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
默认行为及解引用Optional对象
- get()
- orElse(T other)
- orElseGet(Supplier<? extends T> other)
- orElseThrow(Supplier<? extends X> exceptionSupplier)
- ifPresent(Consumer<? super T>)
使用filter剔除特定的值
Optional类的方法
方法 | 描述 |
---|---|
empty | 返回一个空的Optional实例 |
filter | 如果值存在并且满足提供的谓词,就返回包含该值的Optional对象;否则返回一个空的Optional对象 |
flatMap | 如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象 |
get | 如果该值存在,将该值用Optional封装返回,否则抛出一个NoSuchElementException异常 |
ifPresent | 如果值存在,就执行使用该值的方法调用,否则什么也不做 |
isPresent | 如果值存在就返回true,否则返回false |
map | 如果值存在,就对该值执行提供的mapping函数调用 |
of | 将指定值用Optional封装之后返回,如果该值为null,则抛出一个NullPointerException异常 |
ofNullable | 将指定值用Optional封装之后返回,如果该值为null,则返回一个空的Optional对象 |
orElse | 如果有值则将其返回,否则返回一个默认值 |
orElseGet | 如果有值则将其返回,否则返回一个由指定的Supplier接口生成的值 |
orElseThrow | 如果有值则将其返回,否则抛出一个由指定的Supplier接口生成的异常 |
函数式数据处理
要讨论流,我们先来谈谈集合,这是最容易上手的方式了。Java 8中的集合支持一个新的stream方法,它会返回一个流(接口定义在java.util.stream.Stream里)。
引入流
流与集合
只能遍历一次
和迭代器类似,流只能遍历一次。以下代码会抛出一个异常,说流已被消 费掉了:
List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);
外部迭代与内部迭代
使用Collection接口需要用户去做迭代(比如用for-each),这称为外部迭代。 相反,Streams库使用内部迭代。
流操作
中间操作
诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。
终端操作
终端操作会从流的流水线生成结果。其结果是任何不是流的值,比如List、Integer,甚至void。
使用流
流的使用一般包括三件事:
- 一个数据源(如集合)来执行一个查询;
- 一个中间操作链,形成一条流的流水线;
- 一个终端操作,执行流水线,并能生成结果。
使用流
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH)
);
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
@Override
public String toString() {
return name;
}
public enum Type { MEAT, FISH, OTHER }
}
筛选和切片
用谓词筛选
Streams接口支持filter方法,该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
筛选各异的元素
流还支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的 hashCode和equals方法实现)的流。例如,以下代码会筛选出列表中所有的偶数,并确保没有 重复。
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
截短流
流支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
请注意limit也可以用在无序流上,比如源是一个Set。这种情况下,limit的结果不会以 任何顺序排列。
跳过元素
流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
映射
一个非常常见的数据处理套路就是从某些对象中选择信息。比如在SQL里,你可以从表中选择一列。Stream API也通过map和flatMap方法提供了类似的工具。
对流中每一个元素应用函数
下面的代码把方法引用Dish::getName传给了map方法, 来提取流中菜肴的名称:
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());
给定一个单词列表,返回另一个列表,显示每个单词中有几个字母。
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
流的扁平化
flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
查找和匹配
另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。Stream
API通过allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了这样的工具。
检查谓词是否至少匹配一个元素
anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。比如,你可以用它来看 看菜单里面是否有素食可选择:
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
anyMatch方法返回一个boolean,因此是一个终端操作。
检查谓词是否匹配所有元素
allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词。
boolean isHealthy = menu.stream()
.allMatch(d -> d.getCalories() < 1000);
noneMatch
和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);
anyMatch、allMatch和noneMatch这三个操作都用到了我们所谓的短路,这就是大家熟悉 的Java中&&和||运算符短路在流中的版本。
查找元素
findAny方法将返回当前流中的任意元素。
Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian)
.findAny();
Optional简介
Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的代码中,findAny可能什么元素都没找到。Java 8的库设计人员引入了Optional,这样就不用返回众所周知容易出问题的null了。
- isPresent()将在Optional包含值的时候返回true, 否则返回false。
- ifPresent(Consumer block)会在值存在的时候执行给定的代码块。
- T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
- T orElse(T other)会在值存在时返回值,否则返回一个默认值。
查找第一个元素
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 9
归约
元素求和
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
reduce接受两个参数:
- 一个初始值,这里是0;
- 一个BinaryOperator来将两个元素结合起来产生一个新值,这里我们用的是
lambda (a, b) → a + b。
int product = numbers.stream().reduce(1, (a, b) -> a * b);
在Java 8中,Integer类现在有了一个静态的sum方法来对两个数求和,这恰好是我们想要的,用不着反复用Lambda写同一段代码了:
int sum = numbers.stream().reduce(0, Integer::sum);
无初始值
reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象:
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
最大值和最小值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
表5-1 中间操作和终端操作
操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream | Predicate | T → boolean |
distinct | 中间(有状态-无界) | Stream | ||
skip | 中间(有状态-有界) | Stream | long | |
limit | 中间(有状态-有界) | Stream | long | |
map | 中间 | Stream | Function<T, R> | T → R |
flatMap | 中间 | Stream | Function<T, Stream> | T → Stream |
sorted | 中间(有状态-无界) | Stream | Comparator | (T, T) → int |
anyMatch | 终端 | boolean | Predicate | T → boolean |
noneMatch | 终端 | boolean | Predicate | T → boolean |
allMatch | 终端 | boolean | Predicate | T → boolean |
findAny | 终端 | Optional | ||
findFirst | 终端 | Optional | ||
forEach | 终端 | void | Consumer | T → void |
collect | 终端 | R | Collector<T, A, R> | |
reduce | 终端(有状态-有界) | Optional | BinaryOperator | (T, T) → T |
count | 终端 | long |
用流收集数据
本章内容
- 用Collectors类创建和使用收集器
- 将数据流归约为一个值
- 汇总:归约的特殊情况
- 数据分组和分区
- 开发自己的自定义收集器
java.util.stream.Collectors
归约和汇总
查找流中的最大值和最小值
- Collectors.maxBy
- Collectors.minBy
Comparator<Dish> dishCaloriesComparator =
Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream()
.collect(maxBy(dishCaloriesComparator));
汇总
求和
- Collectors.summingInt
- Collectors.summingLong
- Collectors.summingDouble
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
平均数
- Collectors.averagingInt
- Collectors.averagingLong
- Collectors.averagingDouble
double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));
连接字符串
- Collectors.joining
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
广义的归约汇总
- Collectors.reducing
分组
- Collectors.groupingBy
Map<Dish.Type, List<Dish>> dishesByType =
menu.stream().collect(groupingBy(Dish::getType));
{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza],
MEAT=[pork, beef, chicken]}
多级分组
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel =
menu.stream().collect(
groupingBy(Dish::getType,
groupingBy(dish -> {
if (dish.getCalories() <= 400)
return CaloricLevel.DIET;
else if (dish.getCalories() <= 700)
return CaloricLevel.NORMAL;
else
return CaloricLevel.FAT;
})
)
);
{MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]},
FISH={DIET=[prawns], NORMAL=[salmon]},
OTHER={DIET=[rice, seasonal fruit],
NORMAL=[french fries, pizza]}}
按子组收集数据
- Collectors.groupingBy(Function<? super T, ? extends K>, Supplier, Collector<? super T, A, D>)
Map<Dish.Type, Long> typesCount = menu.stream().collect( groupingBy(Dish::getType, counting()));
Map<Dish.Type, Optional<Dish>> mostCaloricByType =
menu.stream()
.collect(groupingBy(Dish::getType,
maxBy(comparingInt(Dish::getCalories))));
分区
- Collectors.partitioningBy
收集器接口
java.util.stream.Collector<T, A, R>
并行数据处理与性能
并行流
将顺序流转换为并行流
对顺序流调用parallel方法。
调用sequential方法就可以把它变成顺序流。
并行流内部使用了默认的ForkJoinPool,它默认的,这个值是由Runtime.getRuntime().availableProcessors()得到的。但是可以通过系统属性java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,如下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
这是一个全局设置,因此它将影响代码中所有的并行流。反过来说,目前还无法专为某个并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值, 除非你有很好的理由,否则我们强烈建议你不要修改它。
高效使用并行流
- 如果有疑问,测量。
- 留意装箱。
- 有些操作本身在并行流上的性能就比顺序流差。
- 还要考虑流的操作流水线的总计算成本。
- 对于较小的数据量,选择并行流几乎从来都不是一个好的决定。
- 要考虑流背后的数据结构是否易于分解。
- 流自身的特点,以及流水线中的中间操作修改流的方式,都可能会改变分解过程的性能。
- 还要考虑终端操作中合并步骤的代价是大是小。
流的数据源和可分解性
源 | 可分解性 |
---|---|
ArrayList | 极佳 |
LinkedList | 差 |
IntStream.range | 极佳 |
Stream.iterate | 差 |
HashSet | 好 |
TreeSet | 好 |
新的日期和时间API
LocalDate、LocalTime、Instant、Duration以及Period
使用LocalDate和LocalTime
LocalDate date = LocalDate.of(2014, 3, 18);
int year = date.getYear();
Month month = date.getMonth();
int day = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek();
int len = date.lengthOfMonth();
boolean leap = date.isLeapYear();
int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
LocalTime time = LocalTime.of(13, 45, 20);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
LocalDate date = LocalDate.parse("2014-03-18");
LocalTime time = LocalTime.parse("13:45:20");
从系统时钟中获取当前的日期:
LocalDate today = LocalDate.now();
// 2014-03-18T13:45:20
LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
机器的日期和时间格式
java.time.Instant
定义Duration或Period
Duration d1 = Duration.between(time1, time2);
Duration d1 = Duration.between(dateTime1, dateTime2);
Duration d2 = Duration.between(instant1, instant2);
Period tenDays = Period.between(LocalDate.of(2014, 3, 8),
LocalDate.of(2014, 3, 18));
日期-时间类中表示时间间隔的通用方法
方法名 | 是否是静态方法 | 方法描述 |
---|---|---|
between | 是 | 创建两个时间点之间的interval |
from | 是 | 由一个临时时间点创建interval |
of | 是 | 由它的组成部分创建interval的实例 |
parse | 是 | 由字符串创建interval的实例 |
addTo | 否 | 创建该interval的副本,并将其叠加到某个指定的temporal对象 |
get | 否 | 读取该interval的状态 |
isNegative | 否 | 检查该interval是否为负值,不包含零 |
isZero | 否 | 检查该interval的时长是否为零 |
minus | 否 | 通过减去一定的时间创建该interval的副本 |
multipliedBy | 否 | 将interval的值乘以某个标量创建该interval的副本 |
negated | 否 | 以忽略某个时长的方式创建该interval的副本 |
plus | 否 | 以增加某个指定的时长的方式创建该interval的副本 |
subtractFrom | 否 | 从指定的temporal对象中减去该interval |
操纵、解析和格式化日期
LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.plusWeeks(1);
LocalDate date3 = date2.minusYears(3);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
表示时间点的日期-时间类的通用方法
方法名 | 是否是静态方法 | 方法描述 |
---|---|---|
from | 是 | 依据传入的Temporal对象创建对象实例 |
now | 是 | 依据系统时钟创建Temporal对象 |
of | 是 | 由Temporal对象的某个部分创建该对象的实例 |
parse | 是 | 由字符串创建Temporal对象的实例 |
atOffset | 否 | 将Temporal对象和某个时区偏移相结合 |
atZone | 否 | 将Temporal对象和某个时区相结合 |
format | 否 | 使用某个指定的格式器将Temporal对象转换为字符串(Instant类不提供该方法) |
get | 否 | 读取Temporal对象的某一部分的值 |
minus | 否 | 创建Temporal对象的一个副本,通过将当前Temporal对象的值减去一定的时长创建该副本 |
plus | 否 | 创建Temporal对象的一个副本,通过将当前Temporal对象的值加上一定的时长创建该副本 |
with | 否 | 以该Temporal对象为模板,对某些状态进行修改创建该对象的副本 |
使用TemporalAdjuster
TemporalAdjuster类中的工厂方法
方法名 | 方法描述 |
---|---|
dayOfWeekInMonth | 创建一个新的日期,它的值为同一个月中每一周的第几天 |
firstDayOfMonth | 创建一个新的日期,它的值为当月的第一天 |
firstDayOfNextMonth | 创建一个新的日期,它的值为下月的第一天 |
firstDayOfNextYear | 创建一个新的日期,它的值为明年的第一天 |
firstDayOfYear | 创建一个新的日期,它的值为当年的第一天 |
firstInMonth | 创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的值 |
lastDayOfMonth | 创建一个新的日期,它的值为当月的最后一天 |
lastDayOfNextMonth | 创建一个新的日期,它的值为下月的最后一天 |
lastDayOfNextYear | 创建一个新的日期,它的值为明年的最后一天 |
lastDayOfYear | 创建一个新的日期,它的值为今年的最后一天 |
lastInMonth | 创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值 |
next/previous | 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期 |
nextOrSame/previousOrSame | 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期,如果该日期已经符合要求,直接返回该对象 |
打印输出及解析日期-时间对象
java.time.format.*
LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate date1 = LocalDate.parse("20140318",
DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18",
DateTimeFormatter.ISO_LOCAL_DATE);
和老的java.util.DateFormat相比较,所有的DateTimeFormatter实例都是线程安全的。
附录 类库的更新
集合
其他新增的方法
集合类和接口中新增的方法
类/接口 | 新方法 |
---|---|
Map | getOrDefault,forEach,compute,computeIfAbsent,computeIfPresent,merge,putIfAbsent,remove(key,value),replace,replaceAll |
Iterable | forEach,spliterator |
Iterator | forEachRemaining |
Collection | removeIf,stream,parallelStream |
List | replaceAll,sort |
BitSet | stream |
Collections类
Collections类已经存在了很长的时间,它的主要功能是操作或者返回集合。Java 8中它又新增了一个方法,该方法可以返回不可修改的、同步的、受检查的或者是空的NavigableMap或NavigableSet。除此之外,它还引入了checkedQueue方法,该方法返回一个队列视图,可以扩展进行动态类型检查。
Comparator
新的实例方法包含了下面这些。
- reversed——对当前的Comparator对象进行逆序排序,并返回排序之后新的Comparator对象。
- thenComparing——当两个对象相同时,返回使用另一个Comparator进行比较的Comparator对象。
- thenComparingInt、thenComparingDouble、thenComparingLong——这些方法的工作方式和thenComparing方法类似,不过它们的处理函数是特别针对某些基本数据类型(分别对应于ToIntFunction、ToDoubleFunction和ToLongFunction)的。
新的静态方法包括下面这些。
- comparingInt、comparingDouble、comparingLong——它们的工作方式和comparing类似,但接受的函数特别针对某些基本数据类型(分别对应于ToIntFunction、 ToDoubleFunction和ToLongFunction)。
- naturalOrder——对Comparable对象进行自然排序,返回一个Comparator对象。
- nullsFirst、nullsLast——对空对象和非空对象进行比较,你可以指定空对象(null)比非空对象(non-null)小或者比非空对象大,返回值是一个Comparator对象。
- reverseOrder——和naturalOrder().reversed()方法类似。
并发
原子操作
java.util.concurrent.atomic.*
在Java 8中新增了更多的方 法支持。
- getAndUpdate——以原子方式用给定的方法更新当前值,并返回变更之前的值。
- updateAndGet——以原子方式用给定的方法更新当前值,并返回变更之后的值。
- getAndAccumulate——以原子方式用给定的方法对当前及给定的值进行更新,并返回变更之前的值。
- accumulateAndGet——以原子方式用给定的方法对当前及给定的值进行更新,并返回变更之后的值。
Adder和Accumulator
多线程的环境中,如果多个线程需要频繁地进行更新操作,且很少有读取的动作(比如,在统计计算的上下文中),Java API文档中推荐大家使用新的类LongAdder、LongAccumulator、DoubleAdder以及DoubleAccumulator,尽量避免使用它们对应的原子类型。
ConcurrentHashMap
- 性能
- 类流操作
- 计数
- 集合视图
Arrays
Arrays类提供了不同的静态方法对数组进行操作。现在,它又包括了四个新的方法(它们都有特别重载的变量)。
使用parallelSort
parallelSort方法会以并发的方式对指定的数组进行排序,你可以使用自然顺序,也可以为数组对象定义特别的Comparator。
使用setAll和parallelSetAll
setAll和parallelSetAll方法可以以顺序的方式也可以用并发的方式,使用提供的函数计算每一个元素的值,对指定数组中的所有元素进行设置。
使用parallelPrefix
Number和Math
Number
Number类中新增的方法如下。
- Short、Integer、Long、Float和Double类提供了静态方法sum、min和max。
- Integer和Long类提供了compareUnsigned、divideUnsigned、remainderUnsigned和toUnsignedLong方法来处理无符号数。
- Integer和Long类也分别提供了静态方法parseUnsignedInt和parseUnsignedLong将字符解析为无符号int或者long类型。
- Byte和Short类提供了toUnsignedInt和toUnsignedLong方法通过无符号转换将参数转化为int或者long类型。类似地,Integer类现在也提供了静态方法toUnsignedLong。
- Double和Float类提供了静态方法isFinite,可以检查参数是否为有限浮点数。
- Boolean类现在提供了静态方法logicalAnd、logicalOr和logicalXor,可以在两个boolean之间执行and、or和xor操作。
- BigInteger类提供了byteValueExact、shortValueExact、intValueExact和longValueExact,可以将BigInteger类型的值转换为对应的基础类型。不过,如果在转换过程中有信息的丢失,方法会抛出算术异常。
Math
如果Math中的方法在操作中出现溢出,Math类提供了新的方法可以抛出算术异常。支持这一异常的方法包括使用int和long参数的addExact、subtractExact、multipleExact、 incrementExact、decrementExact和negateExact。此外,Math类还新增了一个静态方法toIntExact,可以将long值转换为int值。其他的新增内容包括静态方法floorMod、floorDiv 和nextDown。
Files
- Files.list——生成由指定目录中所有条目构成的Stream。这个列表不是递归 包含的。由于流是延迟消费的,处理包含内容非常庞大的目录时,这个方法非常有用。
- Files.walk——和Files.list有些类似,它也生成包含给定目录中所有条目的Stream。不过这个列表是递归的,你可以设定递归的深度。注意,该遍历是依照深度优先进行的。
- Files.find——通过递归地遍历一个目录找到符合条件的条目,并生成一个Stream对象。
Reflection
Java 8中注解机制的几个变化。Reflection API的变化就是为了支撑这些改变。
除此之外,Relection接口的另一个变化是新增了可以查询方法参数信息的API,比如,你现在可以使用新增的java.lang.reflect.Parameter类查询方法参数的名称和修饰符,这个类被新的java.lang.reflect.Executable类所引用,而java.lang.reflect.Executable通 用函数和构造函数共享的父类。
String
String类也新增了一个静态方法,名叫join。