java8越来越普及,里面提供了很多新的API,大大提高了我们日常的编码效率,本文对java8的一些新特性做了简单归纳,并附有代码示例
代码示例:https://github.com/aalansehaiyang/java8-tutorial
- 接口使用default方法
- Lambda表达式
- Functional接口
- Method and Constructor 引用
- Lambda 范围
- 方法引用
- 内置 Functional Interfaces
- 可选择 Optionals
- 流 Streams
- 并行 Streams
- 集合 Maps
- 日期 API
- 注解
Java 8 允许我们在接口类中使用非结构化方法比如default
修饰词,来实现接口。
这一特性在 virtual extension methods有详细介绍。
示例:
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
Formula
接口类中除了定义抽象方法 calculate
,还定义default方法sqrt
。实现类必须实现抽象方法calculate
。default修饰的方法sqrt
在类外同样可以使用。
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
formula.calculate(100); // 100.0
formula.sqrt(16); // 4.0
公式采用匿名实现类。代码非常冗余,需要6行代码来实现一个简单的计算 sqrt(a * 100)
。下一节中,用Java 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);
}
});
静态方法 Collections.sort
接收list集合和一个comparator比较器从而实现排序功能。通常你需要创建一个匿名比较器,并传递给排序方法。
为了替换匿名类实例,Java 8 引入一种非常简洁的语法, lambda 表达式:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
当然你也可以采用更短更易读的写法,如上。
Collections.sort(names, (String a, String b) -> b.compareTo(a));
可以进一步精简,只剩一行代码,省略{}
和return
方法,如上。
names.sort((a, b) -> b.compareTo(a));
List类现在提供sort
方法。同时java编译器能自动识别参数类型,所以编码时你可以忽略它们。接下来让我们深入学习lambda表达式如何广泛使用。
lambda表达式是如何识别Java的系统类型?每个lambda对应一个由接口指定的类型。因此每一个_functional 接口_ 的定义必须包含一个抽象方法声明。每个lambda表达式的类型需要与抽象方法匹配。由于默认方法不是抽象的,你需要将默认方法添加到函数接口。
我们可以任意定义一个接口作为lambda表达式,其内部需要包含一个抽象方法。 为了确保接口满足规范,你需要添加 @FunctionalInterface
注解。一旦你试图添加第二个抽象方法,编译器会自动检测并抛出一个编译错误。
示例:
@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
代码:com.winterbe.java8.samples.lambda.Lambda2
记住,如果省略@FunctionalInterface
,代码也是有效的。
上面的代码示例可以采用静态方法引用进一步简化:
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted); // 123
Java 8允许你通过方法或构造器的引用,如 ::
。上面示例演示了引用一个静态方法。另外我们也可以用类实例对象的方法。
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工厂接口,用于创建新的persons:
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
与传统的实现方式不同,我们通过调用构造器方法来实现:
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
我们通过 Person::new
来触发Person的构造器函数。Java编译器能自动选择合适的构造器函数来匹配PersonFactory.create
。
代码:com.winterbe.java8.samples.lambda.Lambda2
lambda表达式访问外部变量同匿名对象相似。你可以访问final修饰的本地局部变量。
我们可以读取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
num
在代码编译时必须是隐式的final类型。下面的写法编译会报错:
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;
与局部变量相反,我们可以在lambda表达式中读或写全局变量或静态全局变量。
代码:com.winterbe.java8.samples.lambda.Lambda4
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);
};
}
}
还记得第一节的示例公式吗?Formula
接口类中定义一个默认方法 sqrt
,它可以被每一个formula实例对象包括匿名对象访问。但这并适用于lambda表达式。
lambda表达式语句不能直接访问default方法。下面写法编译不过:
Formula formula = (a) -> sqrt(a * 100);
方法引用和 lambda 表达式拥有相同的特性(例如,它们都需要一个目标类型,并需要被转化为函数式接口的实例),不过我们并不需要为方法引用提供方法体,我们可以直接通过方法名称引用已有方法。
方法引用分三种情况:
- 1)类+动态方法
- 2)类+静态方法
- 3)类实例对象+动态方法
- 4)类实例对象+静态方法(无效,不正确写法)
public static void main(String[] args) {
// 1)类+动态方法
BiConsumer<LinkedHashSet, Object> biConsumer1 = LinkedHashSet::add;
LinkedHashSet s1 = new LinkedHashSet();
biConsumer1.accept(s1, "aaa");
System.out.println(s1);
// 2)类+静态方法
BiConsumer<String, Long> biConsumer2 = Utils::concatStatic;
biConsumer2.accept("first_param", 6L);
// 3)类实例对象+动态方法
BiConsumer<String, Long> biConsumer3 = new Utils()::concat;
biConsumer3.accept("first_param", 7L);
// 4)类实例对象+静态方法
// Error:(35, 48) java: 方法引用无效 ,静态限制范围方法引用
// BiConsumer<String, Long> biConsumer4 = new Utils()::concatStatic;
// biConsumer4.accept("first_param", 8L);
}
接收 int 参数的数组构造方法
IntFunction<int[]> arrayMaker = int[]::new;
int[] array = arrayMaker.apply(10) // 创建数组 int[10]
JDK 1.8 API提供了许多内置的功能接口。其中一些来自Java旧版本,如Comparator
或Runnable
。通过@FunctionalInterface
注解扩展那些已存在的接口以实现lambda支持。
同时Java 8 API还提供一些新的functional interfaces满足多场景需求。一些新特性出自Google Guava 三方库。
Predicates内部定义了一个boolean类型判断方法,带有一个入参。这个接口还包含很多default方法满足各种复杂的逻辑表达式,如(与、或、非)
代码:com.winterbe.java8.samples.lambda.Lambda3
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();
函数接收一个入参并返回一个结果。default
方法被用于将多个功能函数链接在一起,(compose 之前执行、andThen 之后执行)
代码:com.winterbe.java8.samples.lambda.Lambda3
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
Suppliers 生产指定类型的结果。不同于Functions,Suppliers 不接受任何参数。
代码:com.winterbe.java8.samples.lambda.Lambda3
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
案例:java8引入了一个对log方法的重载版本,这个版本的log方法接受一个Supplier作为参数。这个替代版本的log方法的函数签名如下:
public void log(Level level, Supplier<String> msgSupplier)
你可以通过下面的方式对它进行调用:
logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());
如果日志器的级别设置恰当, log 方法会在内部才执行作为参数传递进来的Lambda表达式(注意:常规写法,先执行所有的入参方法,得到实参,再执行方法)。惰性求值,可以有效避免一些不必要的性能开销。
这里介绍的 Log 方法的内部实现如下:
public void log(Level level, Supplier<String> msgSupplier){
if(logger.isLoggable(level)){
log(level, msgSupplier.get());
}
}
https://my.oschina.net/bairrfhoinn/blog/142985
Consumers 表示对单个输入参数加工处理,并提供 andThen 'default'方法进行后续处理。
代码:com.winterbe.java8.samples.lambda.Lambda3
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
Comparators 在Java老版本就已经存在。Java 8增加了各种各样的default
方法
代码:com.winterbe.java8.samples.lambda.Lambda3
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
Comparator与reversed组合使用,支持多字段的排序,默认由小到大,或由大到小
代码:com.winterbe.java8.samples.functional.Comparator1
List<Score> list = new ArrayList<>();
list.add(new Score("xiaohong", 90L, 91L));
list.add(new Score("xiaoming", 85L, 90L));
list.add(new Score("wanggang", 90L, 96L));
list.add(new Score("xiaoma", 85L, 70L));
// 先按语文由小到大,如果相等,按数学由小到大
Collections.sort(list, Comparator.comparing(Score::getYuwen).thenComparing(Score::getShuxue));
System.out.println("先按语文由小到大,如果相等,按数学由小到大");
list.forEach(System.out::println);
// 先按语文由大到小,如果相等,按数学由大到小
Comparator c1 = Comparator.comparing(Score::getYuwen).reversed();
Comparator c2 = Comparator.comparing(Score::getShuxue).reversed();
Collections.sort(list, c1.thenComparing(c2));
System.out.println("先按语文由大到小,如果相等,按数学由大到小");
list.forEach(System.out::println);
象BinaryOperator、等
代码位置:com.winterbe.java8.samples.functional
Optionals 不是一个函数式接口,主要是为了防止NullPointerException
。下一节会重点介绍,现在让我们先了解下Optionals是如何工作的。
Optional是一个用于存放 null 或非null值的简易容器。试想,一个带返回值的方法有时会返回空。相反,Java 8 返回的是Optional
,而非 null
。
代码:com.winterbe.java8.samples.stream.Optional1
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"
java.util.Stream
可以对元素列表进行一次或多次操作。Stream操作可以是中间值也可以是最终结果。
最后的操作返回的是某种类型结果,而中间操作返回的是stream本身。因此你可以在一行代码链接多个方法调用。Streams被创建于java.util.Collection
,比如 list or set (map 并不支持)。Stream可以顺序执行,也可以并行执行。
Streams 非常强大, 因此我单独写了一篇文章介绍 Java 8 Streams Tutorial。代码库 Sequency
- 中间操作:filter、map、mapToInt、mapToLong、mapToDouble、flatMap、sorted、distinct、limit、skip、of、iterate
- 终止操作:forEach、count、collect、reduce、toArray、anyMatch、allMatch、noneMatch、findAny、findFirst、max、min
- 原始类型特化流:IntStream、LongStream、DoubleStream
Filter通过predicate
判断函数来过滤所有的元素。这个操作是中间操作,需要通过终止操作才会触发执行。
代码:com.winterbe.java8.samples.stream.Stream_filter
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa2", "aaa1"
map
是一种中间过程操作,借助函数表达式将元素转换成另一种形式。下面的例子将每个字符串转换成大写的字符串。但你也可以使用map
将每个对象转换为另一种类型。最终输出的结果类型依赖于你传入的函数表达式。
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a)) //由大到小
.forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
如果涉及到一对多映射,需要将映射结果放入Stream中。使用flatMap方法的效果是,转换后的多个结果并不是分别映射成一个流,而是映射成流的内容。
代码:com.winterbe.java8.samples.stream.Stream_flatMap
List<String> lists = Arrays.asList("Hello", "World");
lists.stream().flatMap(word-> Stream.of(word.split("")))
.distinct()
.forEach(System.out::println);
Sorted是一个中间态操作,它返回流的有序视图。 除非你传递自定义的Comparator
,否则元素按默认的由小到大
排序。
代码:com.winterbe.java8.samples.stream.Stream_sorted
//默认排序
stringCollection.stream().sorted().forEach(System.out::println);
System.out.println(stringCollection);
特别注意:`sorted`只是创建流的排序视图,并没有改变原始集合的顺序。所以说`stringCollection`的顺序并没有改变。
//自定义排序,按字符串,由大到小
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
终止型操作,通过给定的函数表达式来处理流中的前后两个元素,或者中间结果与下一个元素。Lambda 反复结合每一个元素,直到流被归约成一个值。例如求和或查找最大元素。
代码:com.winterbe.java8.samples.stream.Stream_reduce
// 将流数据列表拆分多批,sum初始为0,每批都执行 (sum, p) -> sum = sum + p.age,得到局部的sum总和。并行计算思想
// 最后通过 (sum1, sum2) -> sum1 + sum2 ,计算最终的总和
// (sum1, sum2) -> sum1 + sum2,主要适用于并行,parallelStream(),单线程是无效的。
private static void test3(List<Person> persons) {
Integer ageSum = persons.parallelStream().reduce(0, (sum, p) -> sum += p.age, (sum1, sum2) -> sum1 + sum2);
System.out.println(ageSum);
}
更多reduce用法可参考:https://blog.csdn.net/io_field/article/details/54971679
Count是一个终止型操作,返回一个long类型的元素列表总数。
代码:com.winterbe.java8.samples.stream.Stream_count
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
各种匹配操作用于判断是否满足stream条件。所有的操作都完成后,返回一个boolean类型结果。
代码:com.winterbe.java8.samples.stream.Stream_match
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");
// 只需要一个条件满足
boolean anyStartsWithA = stringCollection.stream().anyMatch((s) -> s.startsWith("a"));
System.out.println("anyMatch:" + anyStartsWithA); // true
// 所有条件都要满足
boolean allStartsWithA = stringCollection.stream().allMatch((s) -> s.startsWith("a"));
System.out.println("allMatch:" + allStartsWithA); // false
// 所有的条件都要不满足
boolean noneStartsWithZ = stringCollection.stream().noneMatch((s) -> s.startsWith("z"));
System.out.println("noneMatch:" + noneStartsWithZ); // true
// 返回任意一个元素
Optional<String> anyE = stringCollection.stream().findAny();
System.out.println("findAny:" + anyE.get());
//返回第一个元素
Optional<String> firstE = stringCollection.stream().findFirst();
System.out.println("findFirst:" + firstE.get());
返回一个扔掉前n个元素的流
代码:com.winterbe.java8.samples.stream.Stream_skip
// 扔掉前三个元素
stringCollection
.stream()
.skip(3)
.forEach(System.out::println);
只取前N个结果
代码:com.winterbe.java8.samples.stream.Stream_limit
// 取前三个元素
stringCollection
.stream()
.limit(3)
.forEach(System.out::println);
接受各种做法作为参数,将流中的元素累积成一个汇总结果
常见例子:
- 对一个交易列表按货币分组,获得该货币的所有交易额总和(返回一个Map<Currency,Integer>)
- 将交易列表分成两组,贵的和不贵的(返回一个Map<Boolean,List<Transaction>>)
- 创建多级分组,比如按城市对交易分组,然后进一步按照贵的或不贵分组
Collectors常见方法:
- Collectors.toList,得到List列表
- Collectors.toSet,得到Set集合
- Collectors.joining ,通过
连接符
拼接字符串 - Collectors.groupingBy(Function<? super T,? extends K>) ,按K值分组,返回Map<K,List>
- Collectors.groupingBy(Function<? super T,? extends K>, Collector<? super T,A,D>),二级分组,得到两级Map
- Collectors.partitioningBy(Predicate<? super T> predicate) ,分区是分组的特殊情况,返回一个布尔值,意味着得到的分组Map的key只能是Boolean,于是它最多可以分为两组
- Collectors.maxBy,求最大值,需要传一个自定义的Comparator
- Collectors.reducing,广义的归约汇总。
- Collectors.toMap ,得到Map集合。注意:如果key重复会抛异常,要特殊处理
代码:com.winterbe.java8.samples.stream.Stream_collect
// 将字符串换成大写,并用逗号连接起来
List<String> citys = Arrays.asList("USA", "Japan", "France");
String cityS = citys.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
// 按性别分组
Map<String, List<Student>> maps = studentList.stream().collect(Collectors.groupingBy(Student::getSex));
// 先按性别分组,然后再按年龄段分组
Map<String, Map<String, List<Student>>> maps = studentList.stream()
.collect(Collectors.groupingBy(Student::getSex,
Collectors.groupingBy(s -> {
if (s.getAge() < 20) {
return "低age";
} else {
return "高age";
}
})));
// 按年龄25分成两个组
Map<Boolean, List<Student>> maps = studentList.stream().collect(Collectors.partitioningBy(s -> {
if (s.getAge() < 25) {
return true;
} else {
return false;
}
}));
// 找出年龄最大的人
Optional<Student> optional1 = studentList.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));
optional1.ifPresent(System.out::println);
// 年龄总和
// reducing的参数,第一个:初始值。第二个:转换函数。第三个:累积函数
int sum = studentList.stream().collect(Collectors.reducing(0, Student::getAge, Integer::sum));
// 转换成一个Map<sex,name>,注意:如果key重复会抛异常
// 如果key重复,用后一个的value值覆盖前一个
private static void test8(List<Student> studentList) {
Map<String, String> sexNameMap = studentList.stream().collect(Collectors.toMap(p -> {
return p.getSex();
}, p2 -> {
return p2.getName();
}, (oldValue, newValue) -> newValue));
System.out.println(sexNameMap);
}
如下所述,流可以是串行执行,也可以并行执行。对于流的串行执行是单个线程完成。而并行流处理则是在多个线程上同时执行。
下面这个例子将会演示如何通过并行流处理来显著提升性能。
首先我们创建一个大容量的List元素集合:
代码:com.winterbe.java8.samples.stream.Stream_reduce
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
代码:com.winterbe.java8.samples.stream.Streams3
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
代码:com.winterbe.java8.samples.stream.Streams3
正如你所看到的那样,两块代码片段几乎是相同的。但并行排序大约能快50%。所以你要做的是将stream()
换成parallelStream()
。
如前所述,Maps不直接支持streams。Map接口并不提供 stream()
相关方法。然而你能借助于map.keySet().stream()
, map.values().stream()
和 map.entrySet().stream()
创建基于keys,values或entries的流。
此外maps还提供一些新的有用的方法来支持常规任务。
代码:com.winterbe.java8.samples.misc.Maps1
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));
putIfAbsent
如果为空,执行put,否则返回key对应的value值,这样可以避免一次空判断代码冗余。forEach
内部通过BiConsumer来操作。
下面的例子讲述了,map如何利用functions函数来计算:
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(缺失才执行)
下面,我们学习如何通过给定key来删除entries,前提它当前存在kv映射:
// 3对应的value等于“val3”,才执行删除动作
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
其它有用的方法:
// 返回key关联的值,否则返回后面的默认值
map.getOrDefault(42, "not found"); // not found
map中的entries合并也是非常容易的:
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
如果key-value键值对不存在,则合并到map中。否则执行function函数来改变其value值。
Java 8提供了全新的日期和时间API,位于 java.time
包下面。全新的日期API与Joda-Time 库相当,但它也不太一样。下面的例子涵盖了此新API的最重要部分。
时钟提供了对当前日期和时间的访问。时钟知晓时区,可以用来代替System.currentTimeMillis()
来检索自Unix EPOCH以来的当前时间(以毫秒为单位)。在时间轴上的某一时刻用Instant
表示。Instant
可以创建遗留的java.util.Date
对象。
Clock provides access to the current date and time. Clocks are aware of a timezone and may be used instead of System.currentTimeMillis()
to retrieve the current time in milliseconds since Unix EPOCH. Such an instantaneous point on the time-line is also represented by the class Instant
. Instants can be used to create legacy java.util.Date
objects.
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date
代码:com.winterbe.java8.samples.time.LocalTime1
时区是通过 ZoneId
来表示,它提供了很多静态方法。时区定义了在瞬间和本地日期和时间之间转换的重要偏移。
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]
代码:com.winterbe.java8.samples.time.LocalTime1
LocalTime表示没有时区的时间,如晚上10点 或者 17:30:15。下文例子创建了两个带时区的本地时间。我们比较这两个时间,并计算两者之间的小时或分钟差值。
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 提供了很多工厂方法用于创建各种新实例,包括解析时间字符串。
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
代码:com.winterbe.java8.samples.time.LocalTime1
LocalDate表示不同的日期,如 2014-03-11。它是不可变的并且与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
从字符串解析LocalDate就像解析LocalTime一样简单:
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
代码:com.winterbe.java8.samples.time.LocalDate1
LocalDateTime表示日期时间。它包含了上面的日期和时间组成一个实例。LocalDateTime
是不可变的,它的工作方式类似于LocalTime 和 LocalDate。我们可以利用日期时间检索某些字段的方法:
代码:com.winterbe.java8.samples.time.LocalDateTime1
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
通过时区的附加信息,它可以转换为时刻。轻松地将实例转换为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("MM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("07 25, 2017 - 14:00", formatter);
System.out.println(parsed); // 2017-07-25T14:00
与 java.text.NumberFormat
不同, DateTimeFormatter
是不可改变的, 线程安全.
更多语法详情, 参考这里.
Java 8中的注解是可重复的。让我们通过一个例子说明。
首先,我们定义一个包装器注解,它包含实际注解的数组:
代码:com.winterbe.java8.samples.misc.Annotations1
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}
Java 8允许我们通过声明注解 @Repeatable
来使用相同类型的多个注释。
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}
@Hint("hint1")
@Hint("hint2")
class Person {}
形式2,java编译器隐式的设置@Hints
注解。这对于经由反射读取注解信息非常重要。
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
虽然我们从未在 Person
类上定义@Hints
注解,但它仍然可以通过 getAnnotation(Hints.class)
读取。然而,更方便的是 getAnnotationsByType
能访问所有带@Hint
的注解。
此外,Java 8 注解扩展了两个新的target:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
收到一些热衷于技术的同学反馈,考虑github实时通知不是特别好,以后时间里,一些技术经验总结、心得体会,会不定期同步一份到微信公众号上,欢迎关注,多多交流,共同进步