原文链接:https://blog.liuzijian.com/post/86955c3b-9635-47a0-890c-f1219a27c269.html
1.Lambda表达式
lambda表达式是Java8的重要更新,lambda表达式可以用更简洁的代码来创建一个只有一个抽象方法的接口(函数式接口)的实例,从而更简单的创建匿名内部类的对象。
语法和使用
lambda表达式的基本语法是形参列表(可以省略类型) ,箭头 ,以及代码块 ,例如() -> {}
,或者(x, y) -> {}
,如果只有一个参数,那么小括号()
可以省略,如果代码块只有一条语句,那么代码块的花括号{}
可一并省略,如果代码块内只有一处return
,那么return
也可一并省略。
例: TreeSet
类的构造器需要传进去一个Comparator
的匿名类对象进去,来进行排序,所以程序实现了一个匿名内部类来封装处理行为,而且不得不用匿名内部类的语法来封装对象。
```java
@Test
public void test() {
TreeSet treeSet = new TreeSet<>(new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
});
treeSet.add(20);
treeSet.add(78);
treeSet.add(-98);
System.out.println(treeSet);
}
```
Comparator
接口是一个函数式接口,因此完全可以使用lambda表达式来简化创建匿名内部类对象,因此上面代码可以修改成这样
```java
@Test
public void test() {
TreeSet treeSet = new TreeSet<>((Integer x, Integer y) -> {
return x.compareTo(y);
});
treeSet.add(20);
treeSet.add(78);
treeSet.add(-98);
System.out.println(treeSet);
}
```
进一步简化: 参数类型可以省略,如果代码块只有一条语句,那么代码块的花括号{}
可一并省略,如果代码块内只有一处return
,那么return
也可一并省略
```java
@Test
public void test() {
TreeSet treeSet = new TreeSet<>((x, y) -> x.compareTo(y));
treeSet.add(20);
treeSet.add(78);
treeSet.add(-98);
System.out.println(treeSet);
}
```
逻辑与上面代码是完全相同的,只是不再需要new Xxx() {}
这种繁琐的语法,不需要指出重写方法的名字,也不需要给出重写方法的返回值类型,只需要给出重写方法的括号以及括号内的形参变量即可,用lambda表达式的代码块代替掉匿名内部类抽象方法的方法体,lambda表达式在这里就像是一个匿名方法。
方法引用和构造器引用
前面说过如果花括号只有一条代码,便可以省略花括号,不仅如此,还可以使用方法引用和构造器引用,使得lambda表达式变得再简洁一些,方法引用和构造器引用的语法是两个英文冒号::
,支持以下使用方式
种类 | 语法 | 说明 | lambda表达式写法 |
---|---|---|---|
类方法 | 类名::类方法 | 抽象方法全部参数传给该类某个方法作为参数 | (a,b,...) -> 类名.类方法(a,b,...) |
特定对象实例方法 | 特定对象::实例方法 | 抽象方法全部参数传给该方法作为参数 | (a,b,...) -> 特定对象.实例方法(a,b,...) |
某类对象实例方法 | 类名::实例方法 | 抽象方法第一个参数作为调用者,后面的参数全部传给该方法作为参数 | (a,b,c,...) -> a.实例方法(b,c,...) |
构造器 | 类名::new | 抽象方法全部参数传给该构造器作为参数 | (a,b,...) -> new 类名(a,b,...) |
例: 类名::类方法
```java
@FunctionalInterface
interface Convert {
Integer fun(String s);
}
@Test
public void test8() {
Convert convert = from -> Integer.valueOf(from);
System.out.println(convert.fun("150") + 1);
}
@Test
public void test9() {
Convert convert = Integer::valueOf;
System.out.println(convert.fun("150") + 1);
}
```
例: 特定对象::实例方法
```java
@FunctionalInterface
interface Convert {
Integer fun(String s);
}
@Test
public void test8() {
Convert convert = from -> "liuzijian.com".indexOf(from);
System.out.println(convert.fun("zi"));
}
@Test
public void test9() {
Convert convert = "liuzijian.com"::indexOf;
System.out.println(convert.fun("zi"));
}
```
例: 类名::实例方法
```java
@FunctionalInterface
interface Fun {
String test(String a, int b, int c);
}
@Test
public void test8() {
Fun fun = (a, b, c) -> a.substring(b, c);
String s = fun.test("abcdefghi", 3, 5);
System.out.println(s);
}
@Test
public void test9() {
Fun fun = String::substring;
String s = fun.test("abcdefghi", 3, 5);
System.out.println(s);
}
```
例: 类名::new
```java
@FunctionalInterface
interface Fun {
BigDecimal test(String n);
}
@Test
public void test8() {
Fun fun = (n) -> new BigDecimal(n);
BigDecimal b = fun.test("45.64");
System.out.println(b);
}
@Test
public void test9() {
Fun fun = BigDecimal::new;
BigDecimal b = fun.test("45.64");
System.out.println(b);
}
```
2.函数式接口
在Java8中,引入了函数式接口的概念,函数式接口是一个只有一个抽象方法的接口,通常用于Lambda表达式和方法引用,函数式接口可以有多个默认方法 或静态方法 ,但是必须只有一个抽象方法
定义
```java
@FunctionalInterface
public interface MyPredicate {
boolean fun(T obj);
default void other() {
System.out.println("hello world");
}
static void staticMethod() {
System.out.println("static method");
}
}
```
@FunctionalInterface
注解:这是一个可选的注解,它可以帮助编译器在编译时检查接口是否符合函数式接口的要求,即是否只有一个抽象方法,如不符合还加这个注解,会导致编译器报错。
使用
编写一个实体类Employee
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Employee {
private String name;
private Double salary;
private Integer age;
public Employee(Integer age) {
this.age = age;
}
public Employee(Integer age, String name) {
this.age = age;
this.name = name;
}
}
```
新增一个按条件过滤的方法filter
,将List
作为第一个参数,函数式接口MyPredicate
作为第二个参数传进filter()
方法,方法体内循环将每个Employee对象一一作为参数传入接口的抽象方法fun()
中,并调用,根据抽象方法运行后得到的布尔值判断是否过滤掉。
```java
private List filter(Listemployees, MyPredicate predicate) {
Listlist = new ArrayList<>();
for (Employee e : employees) {
if (predicate.fun(e)) {
list.add(e);
}
}
return list;
}
```
声明一个员工集合employees
,插入5个对象,然后调用filter()
方法,将employees
作为第一个参数传入,然后直接new一个实现MyPredicate
接口抽象方法的匿名内部类作为第二个参数传入,这样一来,调用时既告诉了目标方法filter()
要处理的数据是employees
,也一并将数据的具体处理规则obj.getAge() > 16
告诉了目标方法,调用同一个方法可以有无数种处理数据的策略,这个实际上就是一种典型的策略模式 ,实际上Java8已经为我们写好了一种策略模式的函数式接口。
```java
private List employees = Arrays.asList(
new Employee("soo", 8547.322, 17),
new Employee("lili", 1000D, 15),
new Employee("王萌", 2154D, 16),
new Employee("张帆", 8547.322, 22),
new Employee("goog", 353D, 12)
);
@Test
public void test3() {
Listlist = filter(employees, new MyPredicate() {
@Override
public boolean fun(Employee obj) {
return obj.getAge() > 16;
}
});
System.out.println(list);
}
```
Java8中,通过将策略接口实现简写为Lambda
表达式的方式,可以使得语法显得更加简洁
```java
Listlist2 = filter(employees, (e) -> e.getAge() < 16);
```
内置的函数式接口
Java8提供了一些预定义的函数式接口,位于java.util.function
包中
java.util.function.Consumer
消费java.util.function.Supplier
供给java.util.function.Function
函数java.util.function.Predicate
断言java.util.function.BinaryOperator
不常用java.util.function.UnaryOperator
不常用
编写4个将函数式接口作为参数的方法
```java
private void testConsumer(String str, Consumerconsumer) {
consumer.accept(str);
}
private String testSupplier(Suppliersupplier) {
return supplier.get();
}
private Integer testFunction(String str, Functionfunction) {
return function.apply(str);
}
private boolean testPredicate(String str, Predicatepredicate) {
return predicate.test(str);
}
```
分别调用这些方法,按照业务逻辑通过匿名内部类的lambda表达式写法实现函数式接口的抽象方法,作为参数传入
```java
@Test
public void test4() {
testConsumer("hello lambda", (x) -> System.out.println(x));
String str = testSupplier(() -> { return "hello world"; });
System.out.println(str);
Integer integer = testFunction("66", (x) -> Integer.valueOf(x));
System.out.println(integer);
boolean b = testPredicate("hello", (e) -> e.equals("hello"));
System.out.println(b);
}
```
得到运行结果
```
hello lambda
hello world
66
true
```
还可以通过lambda表达式的方法引用和构造器引用将调用修改的更简洁一些
```java
@Test
public void test2() {
testConsumer("hello lambda", System.out::println);
Integer integer = testFunction("66", Integer::valueOf);
}
```
3.Stream API
Stream是Java8引入的一个新特性,是一个数据流,它提供了一种声明性的方法来处理集合、数组等数据源中的数据,可以更简洁、函数式的方式进行数据处理,它不会改变数据源本身,而是返回一个新的Stream或者是最终的结果。
Java8中引进的常见流式API包括:
java.util.stream.Stream
java.util.stream.LongStream
java.util.stream.IntStream
java.util.stream.DoubleStream
其中java.util.stream.Stream
是个通用的流接口,以外的几种则代表流的元素类型为long
,int
,double
Stream操作是延迟执行的,这意味着它们会等到需要结果时在执行,Stream操作可以被链式调用,并且一般分为两类操作:中间操作和终止操作
创建Stream
从集合类型的stream()
方法创建
```java
List list = new ArrayList<>();
Stream stream = list.stream();
```
从数组创建
```java
Employee[] employees = new Employee[10];
Stream employeeStream = Arrays.stream(employees);
```
通过Stream的静态方法创建流
```java
Employee[] employees = new Employee[10];
Stream employeeStream1 = Stream.of(employees);
```
迭代创建无限流,根据种子和消费接口
```java
Stream.iterate(10, (x) -> x + 2)
.limit(10)
.forEach(System.out::println);
```
随机数
```java
Stream.generate(Math::random)
.limit(20)
.forEach(System.out::println);
```
通过builder()
创建一个int流
```java
@Test
public void test5() {
IntStream intStream = IntStream.builder()
.add(1)
.add(2)
.add(3)
.add(4).build();
// 下面的聚集方法每次只能执行一行
System.out.println(intStream.max().getAsInt());
//System.out.println(intStream.min().getAsInt());
//System.out.println(intStream.sum());
//System.out.println(intStream.count());
//System.out.println(intStream.average());
}
```
Stream的操作
Stream的操作包含中间操作 和终止操作 ,在《疯狂Java讲义》一书中,李刚老师也将其称为中间方法和末端方法,中间操作允许流保持打开状态,并允许直接调用后续方法,中间方法返回值是另一个流。终止方法是对流的最终操作,在对某个流执行终止操作后,整个流将不再可用。
常见中间操作
filter(Predicate predicate)
过滤流中不符合predicate的元素mapToXxx(ToXxxFunction mapper)
使用ToXxxFunction对流中的元素进行一对一转换。返回的新流中包含ToXxxFunction转换生成的所有元素peek(Consumer action)
依次对每个元素执行了一些操作,返回的流与原有的流包含相同的元素(多用于调试)distinct()
排除流中所有重复元素,判断标准是equals()
返回true
sorted()
该方法用于排序sorted(Comparator comparator)
该方法用于根据自定义规则排序limit(long maxSize)
截取流中的前maxSize
个元素skip(long n)
跳过流中的前n
个元素map(Function mapper)
映射每个元素为其他形式flatMap(Function mapper)
将每个元素转换为一个流,然后将多个流合并成一个流
常见终止操作
collect(Collector collector)
将流中的元素收集到一个容器中(如集合、列表、映射等)count()
返回流中的元素个数forEach(Consumer action)
遍历流中所有元素,对每个元素执行actiontoArray()
将流中所有元素转换为一个数组reduce()
通过某种操作来合并流中的元素min()
返回流中元素的最小值max()
返回流中元素的最大值anyMatch(Predicate predicate)
如果流中任一元素匹配给定条件,返回 trueallMatch(Predicate predicate)
如果流中所有元素都匹配给定条件,返回 truenoneMatch(Predicate predicate)
如果流中没有任何元素匹配给定条件,返回 truefindFirst()
返回流中的第一个元素findAny()
返回流中的任意一个元素
中间操作返回的是一个新的Stream,并且中间操作是惰性执行的,直到终止操作才触发计算
下面是用例:
数据:
```java
private List employees = Arrays.asList(
new Employee("soo", 8547.322, 17),
new Employee("lili", 1000D, 18),
new Employee("王萌", 2154D, 16),
new Employee("张帆", 8547.322, 22),
new Employee("张帆", 8547.322, 22),
new Employee("张帆", 8547.322, 22),
new Employee("goog", 353D, 12)
);
private List list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
```
例: stream+limit筛选切片,满足e.getAge() > 16
条件的对象达到两个时就停止迭代,而不是迭代一遍后返回前两个,提高效率。终止操作forEach()
不触发,中间操作filter()
,limit()
也不会得到执行。
```java
@Test
public void test() {
employees.stream()
.filter((e) -> {
// 中间操作
System.out.println("中间操作");
return e.getAge() > 16;
})
.limit(2) //中间操作
.forEach(System.out::println); //终止操作
}
```
运行结果:
```
中间操作
Employee(name=soo, salary=8547.322, age=17)
中间操作
Employee(name=lili, salary=1000.0, age=18)
```
例: 跳过流中的前n
个元素,与limit
相反
```java
@Test
public void test2() {
employees.stream().skip(2).forEach(System.out::println);
}
```
运行结果
```
Employee(name=王萌, salary=2154.0, age=16)
Employee(name=张帆, salary=8547.322, age=22)
Employee(name=张帆, salary=8547.322, age=22)
Employee(name=张帆, salary=8547.322, age=22)
Employee(name=goog, salary=353.0, age=12)
```
例: 去重,根据equals
,hashCode
,本例去重成功的前提是Employee
类需要重写equals
,hashCode
```java
//Employee类重写equals hashCode
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
if (!Objects.equals(name, employee.name)) return false;
if (!Objects.equals(salary, employee.salary)) return false;
return Objects.equals(age, employee.age);
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (salary != null ? salary.hashCode() : 0);
result = 31 * result + (age != null ? age.hashCode() : 0);
return result;
}
```
```java
@Test
public void test3() {
employees.stream().distinct().forEach(System.out::println);
}
```
运行结果
```
Employee(name=soo, salary=8547.322, age=17)
Employee(name=lili, salary=1000.0, age=18)
Employee(name=王萌, salary=2154.0, age=16)
Employee(name=张帆, salary=8547.322, age=22)
Employee(name=goog, salary=353.0, age=12)
```
例: flatMap
将流中每个值,都转换成另一个流,然后把所有流连接成一个
下面程序先将"aaa"
转换成由3个'a'
构成的List
,再将List
转换为Stream
,"bbb"
和 "ccc"
同理,最后将转换成的三个Stream
合并为含有9个元素的Stream
,再调用结束方法collect()
将其变为含有9个元素的List
,依次打印输出。
```java
@Test
public void test5() {
List list = Arrays.asList("aaa", "bbb", "ccc");
Function> function = (e) -> {
List characters = new ArrayList<>();
for (char c : e.toCharArray()) {
characters.add(c);
}
return characters.stream();
};
List collect = list.stream()
.flatMap(function)
.collect(Collectors.toList());
collect.forEach(System.out::println);
}
```
运行结果
```
a
a
a
b
b
b
c
c
c
```
例: map
映射,得到流中的一个元素,处理组成新的流
```java
@Test
public void test4() {
employees.stream().map((e) -> e.getName()).forEach(System.out::println);
}
```
运行结果
```
soo
lili
王萌
张帆
张帆
张帆
goog
```
例: sorted()
自然排序
```java
@Test
public void test() {
list.stream().sorted().forEach(System.out::println);
}
```
运行结果
```
aaa
bbb
ccc
ddd
eee
```
例: sorted(Comparator c)
定制排序
```java
@Test
public void test2() {
employees.stream()
.sorted((e1, e2) -> e1.getAge() - e2.getAge())
.forEach(System.out::println);
}
```
运行结果
```
Employee(name=goog, salary=353.0, age=12)
Employee(name=王萌, salary=2154.0, age=16)
Employee(name=soo, salary=8547.322, age=17)
Employee(name=lili, salary=1000.0, age=18)
Employee(name=张帆, salary=8547.322, age=22)
Employee(name=张帆, salary=8547.322, age=22)
Employee(name=张帆, salary=8547.322, age=22)
```
例: xxxMatch
,findXXX
,count()
,max()
,min()
```java
@Test
public void test3() {
boolean b = employees.stream().allMatch((e) -> e.getAge() > 10);
System.out.println(b);
b = employees.stream().anyMatch((e) -> e.getAge() > 100);
System.out.println(b);
b = employees.stream().noneMatch((e) -> e.getAge() > 100);
System.out.println(b);
Optional first = employees.stream().findFirst();
System.out.println(first.get());
Optional any = employees.stream().findAny();
System.out.println(any.get());
long count = employees.stream().count();
System.out.println(count);
Optional max = employees.stream()
.max(Comparator.comparingInt(Employee::getAge));
System.out.println(max.get());
Optional maxAge = employees.stream()
.map(Employee::getAge)
.max(Integer::compare);
System.out.println(maxAge.get());
}
```
运行结果
```
true
false
true
Employee(name=soo, salary=8547.322, age=17)
Employee(name=soo, salary=8547.322, age=17)
7
Employee(name=张帆, salary=8547.322, age=22)
22
```
例: reduce()
将流中元素反复结合,得到新值,先将起始值作为x,从流中取出一个值作为y
```java
@Test
public void test() {
List list = Arrays.asList(1,2,3,4,5,6,7,8,9);
Integer sum = list.stream().reduce(0, Integer::sum);
System.out.println(sum);
Optional reduce = employees.stream().map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(reduce.get());
}
```
运行结果
```
45
37696.288
```
例: .collect(Collectors.toList())
.collect(Collectors.toCollection())
收集为集合
```java
@Test
public void test2() {
List names = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
//.collect(Collectors.toCollection(LinkedList::new))
names.forEach(System.out::println);
}
```
运行结果
```
soo
lili
王萌
张帆
张帆
张帆
goog
```
例: collect(Collectors.averagingDouble())
求平均值
```java
@Test
public void test5() {
Double avg = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avg);
}
```
例: collect(Collectors.joining())
用相同的内容连接多个字符串,非常适合SQL等参数拼接场景
```java
@Test
public void test() {
String collect = list.stream().collect(Collectors.joining(","));
System.out.println(collect);
}
```
运行结果
```
aaa,bbb,ccc,ddd,eee
```
例: 收集为Map Collectors.groupingBy()
将相同航司和票号的票和行李的价格加在一起
```java
public class TestGroupBy {
private List details = new ArrayList<>();
@Before
public void mock() {
details.add(new Detail(1, "001", "123456789", new BigDecimal("120.00")));
details.add(new Detail(2, "001", "123456789", new BigDecimal("99.32")));
details.add(new Detail(3, "003", "333222111", new BigDecimal("27.32")));
details.add(new Detail(4, "003", "333222111", new BigDecimal("36.00")));
details.add(new Detail(5, "003", "123456789", new BigDecimal("48.32")));
details.add(new Detail(6, "101", "123456789", new BigDecimal("53.32")));
details.add(new Detail(7, "101", "123456789", new BigDecimal("10.32")));
details.add(new Detail(8, "102", "333222111", new BigDecimal("3.32")));
details.add(new Detail(9, "103", "123456789", new BigDecimal("9.00")));
details.add(new Detail(10, "103", "123456789", new BigDecimal("12.12")));
}
@Test
public void test() {
Map> groupByAir = details.parallelStream().collect(Collectors.groupingBy(Detail::getAir));
groupByAir.forEach((air, sameAirs) -> {
Map> groupByDoc = sameAirs.parallelStream().collect(Collectors.groupingBy(Detail::getDocument));
groupByDoc.forEach((doc, sameDocs) -> {
Optional reduce = sameDocs.parallelStream().map(Detail::getPrice).reduce(BigDecimal::add);
reduce.ifPresent(e -> {
System.out.println(air + " "+ doc + " " + e);
});
});
});
}
@Data
@AllArgsConstructor
public static class Detail {
/**
* ID
*/
private Integer id;
/**
*航司编码
*/
private String air;
/**
*票号
*/
private String document;
/**
*机票价格
*/
private BigDecimal price;
}
}
```
运行结果
```
001 123456789 219.32
101 123456789 63.64
102 333222111 3.32
003 333222111 63.32
003 123456789 48.32
103 123456789 21.12
```
例: peek()
实时打印调试看流处理的每一步里面的元素是什么样的
```java
@Test
public void test6() {
List names = Arrays.asList("liuzijian", "liutongtong", "zhaoying", "wangwendi");
names.stream()
.filter(name -> name.startsWith("liu"))
.peek(name -> System.out.println("过滤后: " + name))
.map(String::toUpperCase)
.peek(name -> System.out.println("变成大写后: " + name))
.collect(Collectors.toList());
}
```
运行结果
```
过滤后: liuzijian
变成大写后: LIUZIJIAN
过滤后: liutongtong
变成大写后: LIUTONGTONG
```
并行流和串行流
在Java8中,流可以分为并行流和串行流,这两者的主要区别在于数据处理的方式。
Java8的stream()
默认是串行流,即数据按顺序一个一个处理,可以通过parallel()
方法将串行流转换为并行流,或者直接在流创建时使用parallelStream()
并行流底层是基于Java的ForkJoinPool
实现的,这个池管理多个线程来并行处理数据,流的元素会被拆分成多个子任务并分配到不同的线程中处理,最后将结果合并。
并行流本身并不保证顺序。但是,在某些操作中,比如Collectors.joining()
,它会保证合并结果的顺序,这通过收集器的设计来实现。
例: 并行流遍历打印
```java
@Test
public void test() {
list.parallelStream().forEach(System.out::println);
}
```
运行结果
```
ccc
eee
ddd
bbb
aaa
```
例: 并行流多线程将0加到100
LongStream.rangeClosed(0, 100000000000L)
创建了从0
到100000000000L
之间所有整数的流,然后reduce()
会先将流分成多个子流,每个子流计算局部的和,在不同的线程中进行,每个线程分别计算一部分和,计算完成后,再将各个子任务计算的结果合并,得到计算结果932356074711512064
```java
public static void main(String[] args) {
long reduce = LongStream.rangeClosed(0, 100000000000L)
.parallel() // 转换为并行流,底层是fork-join
.reduce(0, Long::sum);
System.out.println(reduce);
}
```
以上就是Java8 StreamAPI的全部内容。
4.接口的默认方法
Java8前的接口,只能有两个成员,全局静态常量和抽象方法,Java8引入了接口的默认方法和静态方法作为新特性,它们的引入是为了增强接口的功能,特别是在接口的扩展性和灵活性方面。
接口中的默认方法,使用default
修饰符修饰,可以带有实现,实现类可以直接继承使用,实现类可以选择重写默认方法,也可以直接使用。
接口中的静态方法只能通过接口名调用,不能通过接口的实现类或实例调用,为接口提供相关的工具性功能,而不需要依赖具体的实现类,静态方法不会被实现类继承,也不能被实现类重写。
接口的默认方法和静态方法
编写一个接口test.testinterface.MyInterface
,拥有两个默认方法test()
,hello()
和一个静态方法helloworld()
```java
package test.testinterface;
public interface MyInterface {
default String test() {
System.out.println("default");
return "default";
}
default void hello() {
System.out.println("my interface");
}
static void helloworld() {
System.out.println("hello java8!!!");
}
}
```
编写一个类test.testinterface.SubClass
,实现接口MyInterface
```java
package test.testinterface;
public class SubClass implements MyInterface {
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.hello();
MyInterface.helloworld();
}
}
```
不实现接口里面的hello()
方法也可以直接调用默认方法hello()
,而且可以通过接口名直接调用接口的静态方法helloworld()
,程序输出:
```
my interface
hello java8!!!
```
方法冲突
编写另一个接口test.testinterface.OtherInterface
,并实现一个默认方法hello
```java
package test.testinterface;
public interface OtherInterface {
default void hello() {
System.out.println("other interface");
}
}
```
令类test.testinterface.SubClass
再实现一个接口OtherInterface
,该接口含有和接口MyInterface
一样定义的default方法hello()
,就产生了接口冲突,当实现的多个接口中有相同签名的默认方法时,子类必须显式重写冲突的方法hello()
,最终程序输出结果:"sub hello!"
```java
package test.testinterface;
public class SubClass implements MyInterface, OtherInterface {
/**
* 多实现方法冲突,实现类必须实现
**/
@Override
public void hello() {
System.out.println("sub hello!");
}
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.hello();
}
}
```
类优先
编写一个类test.testinterface.MyClass
,里面有一个方法String test()
,并让SubClass
类继承它,并执行subClass.test();
,得到输出结果:"class",但是SubClass
实现的接口MyInterface
里面也有个方法String test()
,却没有被执行,而是执行了类里面的方法,说明类优先,如果类或其父类中已经提供了方法实现,则优先使用类的实现,而不是接口的默认方法。
```java
package test.testinterface;
public class MyClass {
public String test() {
System.out.println("class");
return "class";
}
}
```
```java
package test.testinterface;
public class SubClass extends MyClass implements MyInterface, OtherInterface {
// 多实现方法冲突,实现类必须实现
@Override
public void hello() {
System.out.println("sub hello!");
}
public static void main(String[] args) {
SubClass subClass = new SubClass();
// 类优先原则, 继承类的方法
subClass.test();
}
}
```
5.新的日期和时间API (java.time)
旧API的线程安全问题
旧的日期时间工具类java.text.SimpleDateFormat
存在线程安全问题,例如SimpleDateFormat线程不安全,内部依赖一个Calendar实例来解析和格式化日期,而Calendar是线程不安全的,多线程格式化会并发更新Calendar状态会导致出现异常。
以下代码使用100个线程并发调用一个format对象进行日期解析操作,会导致出现错误。
```java
package test.time;
import java.text.SimpleDateFormat;
public class Test1 {
public static void main(String[] args) {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
Runnable r = new Runnable() {
@Override
public void run() {
try {
System.out.println(format.parse("20191231"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
for (int i=0; i<100; i++) {
new Thread(r, "t"+i).start();
}
}
}
```
可以采取同步块,线程单独持有format对象,以及线程池内使用ThreadLocal的办法解决。采用同步代码块时,只能有一个线程执行parse方法,可以避免线程安全问题。
```java
package test.time;
import java.text.SimpleDateFormat;
public class Test1 {
public static void main(String[] args) {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
Runnable r = new Runnable() {
@Override
public void run() {
synchronized (format) {
try {
System.out.println(format.parse("20191231"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
};
for (int i=0; i<100; i++) {
new Thread(r, "t"+i).start();
}
}
}
```
采用线程独自持有format对象的方法解决,每个线程执行时创建一个format对象,每个线程单独持有,防止线程安全问题。
```java
package test.time;
import java.text.SimpleDateFormat;
public class Test1 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
try {
System.out.println(new SimpleDateFormat("yyyyMMdd").parse("20191231"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
for (int i=0; i<100; i++) {
new Thread(r, "t"+i).start();
}
}
}
```
线程池+ThreadLocal,10个线程同时派发100个格式化任务,可以为每个线程绑定一个format对象,各自使用,也可以避免线程安全问题。
```java
package test.time;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test1 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadLocal threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println(threadLocal.get().parse("20191231"));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
};
for (int i=0; i<100; i++) {
executorService.submit(runnable, "t"+i);
}
executorService.shutdown();
}
}
```
新的日期时间API
Java 8通过发布新的Date-TimeAPI(JSR310)进一步加强了对日期与时间的处理。
首先,在旧版的Java中,日期时间API存在诸多问题,首先java.util.Date
是非线程安全的,所有的日期类都是可变的。
其次,Java的日期/时间类的定义并不一致,在java.util
和java.sql
的包中都有日期类,负责格式化和解析的类又在java.text包中定义。java.util.Date
同时包含日期和时间,而java.sql.Date
仅包含日期,而且放进sql包下并不合理。
而且,无法更好的处理时区,日期类不能国际化,没有时区支持,因此Java引入了java.util.Calendar
和java.util.TimeZone
,但是它们仍然存在一样的问题。
于是Java8引入了新的日期时间API,位于java.time
包下,该包下有几个重要的类:
java.time.Instant
时间戳java.time.Duration
时间差java.time.LocalDate
只包含日期,例如2011-07-11
java.time.LocalTime
只包含时间,例如09:00:01
java.time.LocalDateTime
同时包含日期和时间,例如2024-11-30 04:09:45
java.time.Period
时间段java.time.OffsetDateTime
带有时区偏移量的日期和时间,是LocalDateTime和ZoneOffset的结合体,更适用于需要精确到时间和偏移量的场景,尤其当你关心的只是某个时间点相对于 UTC 的偏移。例如,在处理需要表示时间差(例如时间戳、系统日志等)时,OffsetDateTime 比较合适。java.time.ZoneOffset
时区偏移量,比如+8:00
java.time.ZonedDateTime
带有时区的日期和时间,是LocalDateTime
和ZoneId
的组合,ZonedDateTime更适用于需要考虑时区历史和夏令时等复杂问题的场景。例如,如果你需要表示某个特定时区(如America/New_York)的时间,并且要处理夏令时,ZonedDateTime会更加准确-
java.time.Clock
时钟```java
package test.time;import org.junit.Test;
import java.time.;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.;
import java.util.Date;public class Test4 {
/** * java8 API获取当前时间 */ @Test public void current() { Instant instant = Instant.now(); LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.now(); LocalDateTime localDateTime = LocalDateTime.now(); ZonedDateTime zonedDateTime = ZonedDateTime.now(); System.out.println(instant); System.out.println(localDate); System.out.println(localTime); System.out.println(localDateTime); System.out.println(zonedDateTime); } /** * Instant的常见方法 */ @Test public void testInstant() { //通过Instant获取当前时间戳,格林威治时间 Instant now = Instant.now(); System.out.println(now); //添加时区,转换为带时区的时间:OffsetDateTime OffsetDateTime us = now.atOffset(ZoneOffset.ofHours(-4)); System.out.println(us);//US //设置偏移量 OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(+8)); System.out.println(offsetDateTime);//CN System.out.println(now.atOffset(ZoneOffset.ofHours(+9)));//JP System.out.println(now.atOffset(ZoneOffset.ofHours(+10)));//AU //根据给定的Unix时间戳(即自1970年1月1日00:00:00 UTC起的秒数)创建一个Instant对象 Instant instant = Instant.ofEpochSecond(1);//开始于1970 System.out.println(instant); //设置时区 ZonedDateTime zonedDateTime = now.atZone(ZoneId.of("GMT+9")); LocalDateTime localDateTime = zonedDateTime.toLocalDateTime(); System.out.println(localDateTime); } /** * LocalDateTime LocalDate LocalTime 的常见方法和使用 */ @Test public void testLocalDateTime() { // 获取当前时间 LocalDateTime now = LocalDateTime.now(); System.out.println(now); //构造时间 LocalDateTime localDateTime = LocalDateTime.of(2019,8,8,12,23,50); System.out.println(localDateTime); //从LocalDate和LocalTime构造时间 System.out.println(LocalDateTime.of(LocalDate.now(), LocalTime.now())); // 获取年月日时分秒 System.out.println(localDateTime.getYear()); System.out.println(localDateTime.getDayOfYear()); System.out.println(localDateTime.getDayOfMonth()); //星期 DayOfWeek dayOfWeek = localDateTime.getDayOfWeek(); System.out.println(dayOfWeek); //当前时间的纳秒部分,表示这个时间点内的精细时间 System.out.println(localDateTime.getNano()); //时间计算 System.out.println(LocalDateTime.now().plusMonths(2)); System.out.println(LocalDateTime.now().minusYears(2)); System.out.println(LocalDateTime.now().plusHours(24)); System.out.println(LocalDateTime.now().plusNanos(500)); System.out.println(LocalDateTime.now().plusYears(2).plusMonths(8).plusDays(9)); // Period.of 用于创建一个表示特定时间间隔的Period对象 System.out.println(LocalDateTime.now().plus(Period.of(3, 5, 20))); ; // ChronoUnit.DECADES代表十年 System.out.println(LocalDateTime.now().plus(3, ChronoUnit.DECADES)) ; // 时间修改 System.out.println(LocalDateTime.now().withMonth(2)); System.out.println(LocalDateTime.now().withDayOfMonth(25)); System.out.println(LocalDateTime.now().withSecond(22)); System.out.println(LocalDateTime.now().with(ChronoField.DAY_OF_MONTH, 2)); System.out.println(LocalDateTime.now().with(ChronoField.MONTH_OF_YEAR, 8)); // LocalDate LocalTime System.out.println(LocalDate.of(2020, 1, 19)); System.out.println(LocalDate.of(2020, Month.AUGUST, 19)); System.out.println(LocalDate.of(2020, Month.of(12), 19)); System.out.println(LocalTime.of(20, 0)); System.out.println(LocalDate.now().withMonth(8)); System.out.println(LocalDate.of(2020, Month.AUGUST, 19).plusDays(5)); System.out.println(LocalDate.of(2020, Month.of(12), 19)); System.out.println( LocalTime.of(20, 0).plusHours(8) ); // LocalDate的方法,判断当前年份是否为闰年 System.out.println(LocalDate.now().isLeapYear()); } /** * TemporalAdjusters 时间校正器 */ @Test public void testTemporalAdjusters() { // 下一个周四 LocalDateTime dateTime = LocalDateTime.now(); dateTime.with(TemporalAdjusters.next(DayOfWeek.THURSDAY)); System.out.println(dateTime); dateTime.with(TemporalAdjusters.previous(DayOfWeek.THURSDAY)); System.out.println(dateTime); dateTime.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY)); System.out.println(dateTime); dateTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.THURSDAY)); System.out.println(dateTime); System.out.println(LocalDate.now().with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY))); // 获取月份第一天 System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfMonth())); System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth())); // 自定义 计算下一个工作日 LocalDateTime nextWorkDay = LocalDateTime.now().with((e) -> { LocalDateTime temp = LocalDateTime.from(e); DayOfWeek dayOfWeek = temp.getDayOfWeek(); if (dayOfWeek.equals(DayOfWeek.FRIDAY)) { return temp.plusDays(3); } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) { return temp.plusDays(2); } else { return temp.plusDays(1); } }); System.out.println(nextWorkDay); } public void test() { System.out.println(Year.now()); System.out.println(YearMonth.now()); System.out.println(MonthDay.now()); } /** * 计算时间间隔:武汉封了多少天,多少小时 */ @Test public void testChronoUnit() { LocalDateTime from = LocalDateTime.of(2020, Month.JANUARY, 23, 10, 0,0); LocalDateTime to = LocalDateTime.of(2020, Month.APRIL, 8, 0, 0,0); long days = ChronoUnit.DAYS.between(from, to); long hours = ChronoUnit.HOURS.between(from, to); System.out.println( days ); System.out.println( hours ); } /** * 使用 TemporalQuery 来计算当前时间与一个指定时间点(2020年1月19日10:00:00)之间的小时差, * 并将其作为 long 类型的值返回 */ @Test public void testTemporalQuery() { long l = LocalDateTime.now().query(new TemporalQuery
() { @Override public Long queryFrom(TemporalAccessor temporal) { LocalDateTime now = LocalDateTime.from(temporal); LocalDateTime from = LocalDateTime.of(2020, Month.JANUARY, 19, 10, 0,0); return ChronoUnit.HOURS.between(from, now); } }); System.out.println(l); } /** * Duration类,只能计算时间差异 */ @Test public void testDurationPeriod() { LocalTime start = LocalTime.of(20, 0); LocalTime end = LocalTime.of(21, 30); // 时间间隔 Duration between = Duration.between(start, end); System.out.println(between.toHours()); System.out.println(between.toMinutes()); } /** * 格式化 DateTimeFormatter */ @Test public void testDateTimeFormatter() { DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME; System.out.println(LocalDateTime.now().format(formatter)); LocalDate localDate = LocalDate.parse("2009-12-31", DateTimeFormatter.ofPattern("yyyy-MM-dd")); System.out.println(localDate); LocalDateTime localDateTime = LocalDateTime.parse("2009-12-31 01:01:02", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); System.out.println(localDateTime); // 2024年12月1日 星期日 System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL))); // 2024年12月1日 System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG))); // 24-12-1 System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT))); // 2024-12-1 System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM))); } @Test public void getAvailableZoneIds() { // 当前系统时区 System.out.println(ZoneId.systemDefault()); // 打印java8中所有支持时区 ZoneId.getAvailableZoneIds().forEach(System.out::println); } /** * OffsetDateTime */ @Test public void testOffsetDateTime() { OffsetDateTime offsetDateTime = new Date().toInstant().atOffset(ZoneOffset.of("-4")); System.out.println(offsetDateTime); System.out.println(offsetDateTime.toLocalDateTime()); OffsetDateTime of = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.of("-4")); System.out.println(of); } /** * ZonedDateTime */ @Test public void testZonedDateTime() { // 当前时间转换为东京时间是几时 ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo")); System.out.println(zonedDateTime); System.out.println(zonedDateTime.toLocalDateTime()); ZonedDateTime of = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Asia/Tokyo")); System.out.println(of); // 为当前时间带上时区 ZonedDateTime tokyo = LocalDateTime.now().atZone(ZoneId.of("Asia/Tokyo")); System.out.println(tokyo); System.out.println(tokyo.toLocalDateTime()); // 将一个时区时间转换为同一时刻另一个时区时间 ZonedDateTime beijing = tokyo.withZoneSameInstant(ZoneId.of("GMT+8")); System.out.println(beijing); ZonedDateTime usa = LocalDateTime.now() .atZone(ZoneId.systemDefault()) .withZoneSameInstant(ZoneId.of("GMT-4")); System.out.println(usa); } }
```
新API和旧的Date之前的互转
```java
package test.time;
import org.junit.Test;
import java.sql.Timestamp;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
public class Test5 {
/**
* 将 LocalDateTime 和系统默认时区结合,转换为 ZonedDateTime
* 再将 ZonedDateTime 转换为 Instant,这是一个包含 UTC 时间戳的对象。
* Date.from():将 Instant 转换为 java.util.Date 对象
*/
@Test
public void toDate() {
LocalDateTime localDateTime = LocalDateTime.now();
Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date date = Date.from(instant);
System.out.println(date);
}
@Test
public void toLocalDateTime() {
Date date = new Date();
LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println(dateTime);
}
/**
* java.sql.Date 转换 LocalDateTime
*/
@Test
public void sqlDate() {
java.sql.Date date = new java.sql.Date(System.currentTimeMillis());
LocalDate localDate = date.toLocalDate();
System.out.println(localDate);
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
LocalDateTime localDateTime = timestamp.toLocalDateTime();
System.out.println(localDateTime);
}
/**
* Calendar 转换 LocalDateTime
*/
@Test
public void calendarToLocalDateTime() {
Calendar calendar = Calendar.getInstance();
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());
System.out.println(zonedDateTime.toLocalDateTime());
}
}
```
6.Optional
java.util.Optional
是一个容器类,用来表示可能包含或者不包含值的对象。它提供了一种优雅的方式来避免出现空指针,从而帮助开发者更安全、更清晰地处理可能为NULL的值。
创建
包装一个非空的值,如果传入的变量为null
会直接抛出空指针异常,如果直接写死null
进去,IDEA可能直接编译出错
```java
Optional optional = Optional.of("Hello, World!");
```
ofNullable
方法允许填充一个可能为空的值进去
```java
Optional optional = Optional.ofNullable(null);
```
空的Optional
对象
```java
Optional optional = Optional.empty();
```
检查
可以使用isPresent()
方法判断
```java
Optional optional = Optional.empty();
if (optional.isPresent()) {
System.out.println("Value: " + optional.get());
} else {
System.out.println("No value present");
}
```
还可以采用ifPresent()
避免if
显式调用
```java
optional.ifPresent(value -> System.out.println("Value: " + value));
```
默认值
如果为空,提供一个默认值
```java
Optional optional = Optional.empty();
String value = optional.orElse("Default Value");
```
还可以通过提供的Supplier
函数式接口生成默认值
```java
Optional optional = Optional.empty();
String value = optional.orElseGet(() -> "Generated Default Value");
// optional.orElseGet(String::new);
```
如果值不存在,可以抛出自定义异常
```java
Optional optional = Optional.empty();
String value = optional.orElseThrow(() -> new RuntimeException("Value is missing!"));
```
转换
map()
如果有值进行处理,并返回处理后的Optional
对象,否则返回Optional.empty()
空值,不执行输出
```java
Optional optional = Optional.empty();
Optional upperCase = optional.map(String::toUpperCase);
upperCase.ifPresent(System.out::println);
```
非空,处理后返回新的Optional
,输出:HELLO WORLD
```java
Optional optional = Optional.ofNullable("hello world");
Optional upperCase = optional.map(String::toUpperCase);
upperCase.ifPresent(System.out::println);
```
使用flatMap()
进一步防止空指针异常,如果optional中的值为null,flatMap()
直接返回Optional.empty()
,否则,它返回一个包含e.getName()
的Optional对象
```java
Employee employee = new Employee();
employee.setName("XXX");
Optional optional = Optional.ofNullable(employee);
Optional s = optional.flatMap((e) -> Optional.of(e.getName()));
s.ifPresent(System.out::println);
```
7.重复注解 (Repeating Annotations)
1.首先创建一个容器注解,这个注解类型包含一个注解数组,存储多个相同类型的注解
```java
package test.anno;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
```
2.定义一个重复注解,并使用@Repeatable
标记
```java
package test.anno;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.TYPE_PARAMETER; // 类型注解
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String value() default "hello world";
}
```
3.测试,通过反射访问方法上的注解,由于MyAnnotation是重复注解,所以一个方法加上多个也不会语法报错,然后提取其中的多个MyAnnotation注解。
```java
package test.anno;
import java.lang.reflect.Method;
import java.util.Arrays;
public class TestAnnotation {
@MyAnnotation("hello")
@MyAnnotation("world")
public void test(String s) {
}
public static void main(String[] args) {
Class clazz = TestAnnotation.class;
try {
Method method = clazz.getMethod("test", String.class);
MyAnnotation[] annotations = method.getAnnotationsByType(MyAnnotation.class);
Arrays.stream(annotations).map(MyAnnotation::value).forEach(System.out::println);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
```
8.Nashorn JavaScript引擎
这个不常用,未完待续