原文首发在我的博客: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作为第一个参数,函数式接口MyPredicate作为第二个参数传进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.Stream
- java.util.stream.LongStream
- java.util.stream.IntStream
- java.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()返回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) 遍历流中全部元素,对每个元素实行action
- toArray() 将流中全部元素转换为一个数组
- reduce() 通过某种操作来合并流中的元素
- min() 返回流中元素的最小值
- max() 返回流中元素的最大值
- anyMatch(Predicate predicate) 如果流中任一元素匹配给定条件,返回 true
- allMatch(Predicate predicate) 如果流中全部元素都匹配给定条件,返回 true
- noneMatch(Predicate predicate) 如果流中没有任何元素匹配给定条件,返回 true
- findFirst() 返回流中的第一个元素
- 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,再将List转换为Stream,"bbb"和 "ccc"同理,最后将转换成的三个Stream合并为含有9个元素的Stream,再调用结束方法collect()将其变为含有9个元素的List,依次打印输出。- @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);
- }
复制代码 运行效果例: map映射,得到流中的一个元素,处理组成新的流- @Test
- public void test4() {
- employees.stream().map((e) -> e.getName()).forEach(System.out::println);
- }
复制代码 运行效果例: sorted()天然排序- @Test
- public void test() {
- list.stream().sorted().forEach(System.out::println);
- }
复制代码 运行效果例: 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());
- }
复制代码 运行效果例: .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);
- }
复制代码 运行效果例: 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);
- }
复制代码 运行效果例: 收集为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);
- }
复制代码 运行效果例: 并行流多线程将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();
- }
- }
- }
复制代码 如果值不存在,可以抛出自定义异常- 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();
- }
- }
- }
复制代码 转换
map() 如果有值进行处理,并返回处理后的Optional对象,否则返回Optional.empty()
空值,不实行输出- 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();
- }
- }
- }
复制代码 非空,处理后返回新的Optional,输出:HELLO WORLD- 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();
- }
- }
复制代码 使用flatMap()进一步防止空指针异常,如果optional中的值为null,flatMap()直接返回Optional.empty(),否则,它返回一个包罗e.getName()的Optional对象- 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);
- }
- }
复制代码 7.重复注解 (Repeating Annotations)
1.首先创建一个容器注解,这个注解范例包罗一个注解数组,存储多个雷同范例的注解- 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());
- }
- }
复制代码 2.定义一个重复注解,并使用@Repeatable标记- Optional<String> optional = Optional.of("Hello, World!");
复制代码 3.测试,通过反射访问方法上的注解,由于MyAnnotation是重复注解,所以一个方法加上多个也不会语法报错,然后提取此中的多个MyAnnotation注解。- Optional<String> optional = Optional.ofNullable(null);
复制代码 8.Nashorn JavaScript引擎
这个不常用,未完待续
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |