[新]Java8的新特性
原文首发在我的博客:https://blog.liuzijian.com/post/86955c3b-9635-47a0-890c-f1219a27c269.html
1.Lambda表达式
lambda表达式是Java8的重要更新,lambda表达式可以用更简洁的代码来创建一个只有一个抽象方法的接口(函数式接口)的实例,从而更简单的创建匿名内部类的对象。
语法和使用
lambda表达式的基本语法是形参列表(可以省略类型),箭头,以及代码块,例如() -> {},或者(x, y) -> {},如果只有一个参数,那么小括号()可以省略,如果代码块只有一条语句,那么代码块的花括号{}可一并省略,如果代码块内只有一处return,那么return也可一并省略。
例: TreeSet类的构造器需要传进去一个Comparator的匿名类对象进去,来进行排序,所以程序实现了一个匿名内部类来封装处理行为,而且不得不用匿名内部类的语法来封装对象。
@Test
public void test() {
TreeSet<Integer> treeSet = new TreeSet<>(new Comparator<Integer>() {
@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表达式来简化创建匿名内部类对象,因此上面代码可以修改成这样
@Test
public void test() {
TreeSet<Integer> 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也可一并省略
@Test
public void test() {
TreeSet<Integer> 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,...) |
例: 类名::类方法
@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);
}
例: 特定对象::实例方法
@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"));
}
例: 类名::实例方法
@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
@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表达式和方法引用,函数式接口可以有多个默认方法或静态方法,但是必须只有一个抽象方法
定义
@FunctionalInterface
public interface MyPredicate<T> {
boolean fun(T obj);
default void other() {
System.out.println("hello world");
}
static void staticMethod() {
System.out.println("static method");
}
}
@FunctionalInterface注解:这是一个可选的注解,它可以帮助编译器在编译时检查接口是否符合函数式接口的要求,即是否只有一个抽象方法,如不符合还加这个注解,会导致编译器报错。
使用
编写一个实体类Employee
@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<Employee>作为第一个参数,函数式接口MyPredicate<Employee>作为第二个参数传进filter()方法,方法体内循环将每个Employee对象一一作为参数传入接口的抽象方法fun()中,并调用,根据抽象方法运行后得到的布尔值判断是否过滤掉。
private List<Employee> filter(List<Employee>employees, MyPredicate<Employee> predicate) {
List<Employee>list = 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已经为我们写好了一种策略模式的函数式接口。
private List<Employee> 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() {
List<Employee>list = filter(employees, new MyPredicate<Employee>() {
@Override
public boolean fun(Employee obj) {
return obj.getAge() > 16;
}
});
System.out.println(list);
}
Java8中,通过将策略接口实现简写为Lambda表达式的方式,可以使得语法显得更加简洁
List<Employee>list2 = 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个将函数式接口作为参数的方法
private void testConsumer(String str, Consumer<String>consumer) {
consumer.accept(str);
}
private String testSupplier(Supplier<String>supplier) {
return supplier.get();
}
private Integer testFunction(String str, Function<String, Integer>function) {
return function.apply(str);
}
private boolean testPredicate(String str, Predicate<String>predicate) {
return predicate.test(str);
}
分别调用这些方法,按照业务逻辑通过匿名内部类的lambda表达式写法实现函数式接口的抽象方法,作为参数传入
@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表达式的方法引用和构造器引用将调用修改的更简洁一些
@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.Streamjava.util.stream.LongStreamjava.util.stream.IntStreamjava.util.stream.DoubleStream
其中java.util.stream.Stream是个通用的流接口,以外的几种则代表流的元素类型为long,int,double
Stream操作是延迟执行的,这意味着它们会等到需要结果时在执行,Stream操作可以被链式调用,并且一般分为两类操作:中间操作和终止操作
创建Stream
从集合类型的stream()方法创建
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
从数组创建
Employee[] employees = new Employee[10];
Stream<Employee> employeeStream = Arrays.stream(employees);
通过Stream的静态方法创建流
Employee[] employees = new Employee[10];
Stream<Employee> employeeStream1 = Stream.of(employees);
迭代创建无限流,根据种子和消费接口
Stream.iterate(10, (x) -> x + 2)
.limit(10)
.forEach(System.out::println);
随机数
Stream.generate(Math::random)
.limit(20)
.forEach(System.out::println);
通过builder()创建一个int流
@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()返回truesorted()该方法用于排序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,并且中间操作是惰性执行的,直到终止操作才触发计算
下面是用例:
数据:
private List<Employee> 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<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
例: stream+limit筛选切片,满足e.getAge() > 16条件的对象达到两个时就停止迭代,而不是迭代一遍后返回前两个,提高效率。终止操作forEach()不触发,中间操作filter(),limit()也不会得到执行。
@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相反
@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
//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;
}
@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<Character>,再将List<Character>转换为Stream<Character>,"bbb"和 "ccc"同理,最后将转换成的三个Stream<Character>合并为含有9个元素的Stream<Character>,再调用结束方法collect()将其变为含有9个元素的List<Character>,依次打印输出。
@Test
public void test5() {
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
Function<String, Stream<Character>> function = (e) -> {
List<Character> characters = new ArrayList<>();
for (char c : e.toCharArray()) {
characters.add(c);
}
return characters.stream();
};
List<Character> collect = list.stream()
.flatMap(function)
.collect(Collectors.toList());
collect.forEach(System.out::println);
}
运行结果
a
a
a
b
b
b
c
c
c
例: map映射,得到流中的一个元素,处理组成新的流
@Test
public void test4() {
employees.stream().map((e) -> e.getName()).forEach(System.out::println);
}
运行结果
soo
lili
王萌
张帆
张帆
张帆
goog
例: sorted()自然排序
@Test
public void test() {
list.stream().sorted().forEach(System.out::println);
}
运行结果
aaa
bbb
ccc
ddd
eee
例: sorted(Comparator c)定制排序
@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()
@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<Employee> first = employees.stream().findFirst();
System.out.println(first.get());
Optional<Employee> any = employees.stream().findAny();
System.out.println(any.get());
long count = employees.stream().count();
System.out.println(count);
Optional<Employee> max = employees.stream()
.max(Comparator.comparingInt(Employee::getAge));
System.out.println(max.get());
Optional<Integer> 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
@Test
public void test() {
List<Integer> 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<Double> reduce = employees.stream().map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(reduce.get());
}
运行结果
45
37696.288
例: .collect(Collectors.toList()) .collect(Collectors.toCollection()) 收集为集合
@Test
public void test2() {
List<String> 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()) 求平均值
@Test
public void test5() {
Double avg = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avg);
}
例: collect(Collectors.joining()) 用相同的内容连接多个字符串,非常适合SQL等参数拼接场景
@Test
public void test() {
String collect = list.stream().collect(Collectors.joining(","));
System.out.println(collect);
}
运行结果
aaa,bbb,ccc,ddd,eee
例: 收集为Map Collectors.groupingBy()
将相同航司和票号的票和行李的价格加在一起
public class TestGroupBy {
private List<Detail> 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<String, List<Detail>> groupByAir = details.parallelStream().collect(Collectors.groupingBy(Detail::getAir));
groupByAir.forEach((air, sameAirs) -> {
Map<String, List<Detail>> groupByDoc = sameAirs.parallelStream().collect(Collectors.groupingBy(Detail::getDocument));
groupByDoc.forEach((doc, sameDocs) -> {
Optional<BigDecimal> 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() 实时打印调试看流处理的每一步里面的元素是什么样的
@Test
public void test6() {
List<String> 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(),它会保证合并结果的顺序,这通过收集器的设计来实现。
例: 并行流遍历打印
@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
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()
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
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
package test.testinterface;
public interface OtherInterface {
default void hello() {
System.out.println("other interface");
}
}
令类test.testinterface.SubClass再实现一个接口OtherInterface,该接口含有和接口MyInterface一样定义的default方法hello(),就产生了接口冲突,当实现的多个接口中有相同签名的默认方法时,子类必须显式重写冲突的方法hello(),最终程序输出结果:"sub hello!"
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(),却没有被执行,而是执行了类里面的方法,说明类优先,如果类或其父类中已经提供了方法实现,则优先使用类的实现,而不是接口的默认方法。
package test.testinterface;
public class MyClass {
public String test() {
System.out.println("class");
return "class";
}
}
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对象进行日期解析操作,会导致出现错误。
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方法,可以避免线程安全问题。
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对象,每个线程单独持有,防止线程安全问题。
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对象,各自使用,也可以避免线程安全问题。
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<SimpleDateFormat> 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-11java.time.LocalTime只包含时间,例如09:00:01java.time.LocalDateTime同时包含日期和时间,例如2024-11-30 04:09:45java.time.Period时间段java.time.OffsetDateTime带有时区偏移量的日期和时间,是LocalDateTime和ZoneOffset的结合体,更适用于需要精确到时间和偏移量的场景,尤其当你关心的只是某个时间点相对于 UTC 的偏移。例如,在处理需要表示时间差(例如时间戳、系统日志等)时,OffsetDateTime 比较合适。java.time.ZoneOffset时区偏移量,比如+8:00java.time.ZonedDateTime带有时区的日期和时间,是LocalDateTime和ZoneId的组合,ZonedDateTime更适用于需要考虑时区历史和夏令时等复杂问题的场景。例如,如果你需要表示某个特定时区(如America/New_York)的时间,并且要处理夏令时,ZonedDateTime会更加准确java.time.Clock时钟
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<Long>() {
@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之前的互转
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可能直接编译出错
Optional<String> optional = Optional.of("Hello, World!");
ofNullable方法允许填充一个可能为空的值进去
Optional<String> optional = Optional.ofNullable(null);
空的Optional对象
Optional<String> optional = Optional.empty();
检查
可以使用isPresent()方法判断
Optional<String> optional = Optional.empty();
if (optional.isPresent()) {
System.out.println("Value: " + optional.get());
} else {
System.out.println("No value present");
}
还可以采用ifPresent()避免if显式调用
optional.ifPresent(value -> System.out.println("Value: " + value));
默认值
如果为空,提供一个默认值
Optional<String> optional = Optional.empty();
String value = optional.orElse("Default Value");
还可以通过提供的Supplier函数式接口生成默认值
Optional<String> optional = Optional.empty();
String value = optional.orElseGet(() -> "Generated Default Value");
// optional.orElseGet(String::new);
如果值不存在,可以抛出自定义异常
Optional<String> optional = Optional.empty();
String value = optional.orElseThrow(() -> new RuntimeException("Value is missing!"));
转换
map() 如果有值进行处理,并返回处理后的Optional对象,否则返回Optional.empty()
空值,不执行输出
Optional<String> optional = Optional.empty();
Optional<String> upperCase = optional.map(String::toUpperCase);
upperCase.ifPresent(System.out::println);
非空,处理后返回新的Optional,输出:HELLO WORLD
Optional<String> optional = Optional.ofNullable("hello world");
Optional<String> upperCase = optional.map(String::toUpperCase);
upperCase.ifPresent(System.out::println);
使用flatMap()进一步防止空指针异常,如果optional中的值为null,flatMap()直接返回Optional.empty(),否则,它返回一个包含e.getName()的Optional对象
Employee employee = new Employee();
employee.setName("XXX");
Optional<Employee> optional = Optional.ofNullable(employee);
Optional<String> s = optional.flatMap((e) -> Optional.of(e.getName()));
s.ifPresent(System.out::println);
7.重复注解 (Repeating Annotations)
1.首先创建一个容器注解,这个注解类型包含一个注解数组,存储多个相同类型的注解
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标记
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注解。
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<TestAnnotation> 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引擎
这个不常用,未完待续
[新]Java8的新特性的更多相关文章
- Java8的新特性以及与C#的比较
函数式接口 VS 委托 在C中,可以使用函数指针来存储函数的入口,从而使得函数可以像变量一样赋值.传递和存储,使得函数的调用变得十分灵活,是实现函数回调的基础.然而函数指针不存在函数的签名信息,甚至可 ...
- java5、java6、java7、java8的新特性
Java5: 1.泛型 Generics: 引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处. Parameterized Type作为参数 ...
- 简单了解JAVA8的新特性
JAVA8新特性会颠覆整个JAVA程序员的编程习惯 甚至如果您坚守JAVA7之前的编程习惯,今后你看比较年轻的程序员写的JAVA代码都会无法理解 所以为了保证不脱钩,我觉得有必要学习JAVA8的新特性 ...
- java8的新特性以及用法简介
1. 介绍 2 接口的默认方法 2 lambda表达式 2.1 函数式接口 2.2 方法与构造函数引用 2.3 访问局部变量 2.4 访问对象字段与静态变量 3. 内建函数式接口 3.1 Predic ...
- Java8常用新特性实践
前言: 时下Oracle开速迭代的Java社区以即将推出Java10,但尴尬的是不少小中企业仍使用JDK7甚至JDK6开发. 从上面列出的JDK8特性中我们可以发现Java8的部分特性很明显的是从Sc ...
- Java8 Stream新特性详解及实战
Java8 Stream新特性详解及实战 背景介绍 在阅读Spring Boot源代码时,发现Java 8的新特性已经被广泛使用,如果再不学习Java8的新特性并灵活应用,你可能真的要out了.为此, ...
- Atitit.jdk java8的语法特性详解 attilax 总结
Atitit.jdk java8的语法特性详解 attilax 总结 1.1. 类型推断这个特别有趣的.鲜为人知的特性1 2. Lambda1 2.1. 内部迭代意味着改由Java类库来进行迭代,而不 ...
- java8的相关特性
1,为什么要介绍java8的相关特性? 因为现在的企业大部分已经从java7向java8开始迈进了,用java8的公司越来越多了,java8中的一些知识还是需要了解一些的; java8具有以下特点: ...
- CentOS添加新硬盘到新的分区(xfs/ext4) 或者添加新分区
CentOs添加新硬盘到新的分区(xfs/ext4) 添加新分区 转载请注明:http://www.cnblogs.com/juandx/p/5618162.html 这篇文章介绍怎么添加一块新的硬 ...
- Java web开发中页面跳转小技巧——跳转后新页面在新窗口打开
最近学习Java web,在学习过程中想实现一个需求,就是在jsp页面跳转的时候,希望跳转后的新页面在新窗口中打开, 而不是覆盖原来的页面,这个需求使我困惑了好长时间,后来通过大海捞针似的在网上寻找方 ...
随机推荐
- 分布式事务之dtm
github: https://github.com/dtm-labs/dtm 本人使用场景, 目前微服务中存在的用户服务, 商品服务,订单服务, 支付服务, 在进行下单操作的时候,需要创建订单并扣减 ...
- PostgreSql Docker 主从热备,异步流复制方案
环境说明 Docker Windows 11 PostgreSql 16 方案步骤 0. 宿主机准备: 找个地方创建一个文件夹用来挂载容器中数据库Data文件夹,这里我用的是: C:\Users\Ad ...
- jackson 中对 null 的处理
前情提要: 在项目中如何将null值转变为空字符串呢? @Configuration public class JacksonConfig { @Bean @Primary @ConditionalO ...
- GZY.EFCore.BulkExtensions 支持达梦数据库的EF Core批量操作库详解
前言 EFCore.BulkExtensions是一个常用的EF core 批量处理数据的库. 但是支持的数据库相对较少.特别是.NET5.0版本 连MySQL都无法支持 这个库就是改造的最新EFCo ...
- Java根据前端返回的字段名进行查询数据
在Java后端开发中,我们经常需要根据前端传递的参数(如字段名)来动态查询数据库中的数据.这种需求通常出现在需要实现通用查询功能或者复杂查询接口的场景中.为了实现这个功能,我们需要结合Java的反射机 ...
- vtkCellLocator IntersectWithLine 返回不是最近的交点
vtkCellLocator IntersectWithLine 有一个重载函数(下面),返回不是最近的交点,因为到交点的距离没有比较,就直接覆盖了.不知道原本是否就是这样.可以用其他重载代替. in ...
- docker安装cas
直接docker pull apereo/cas ,docker run的时候各种报错: standard_init_linux.go:178: exec user process caused &q ...
- Centos7.8安装Gitlab
公司为了合规性考虑,需要自己搭建私有化版的github.那不用想,肯定要上GitLab了. 项目背景: 服务器:华为云ECS,需要上公网,并在安全组打开80端口访问. 用户:关闭公开注册,新建用户后, ...
- 【人工智能】深度学习框架值TF入门-模型保存与加载
资料:https://tensorflow.google.cn/tutorials/keras/save_and_load#选项 Keras的方式 Keras版本模型保存与加载 函数 保存模型权重:m ...
- 【前端】【JavaScript】通过成绩判断等级
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...