jdk8新特性 基础解析

    科技2024-04-16  12

    jdk8的新特性

    Java语言自发布以来,从Java1.1一直到现在的Java14,Java通过不停的增加新功能,使得这门语言不断 得到良好的提升。

    其中比较具有重要里程碑的版本,如JDK5.0,提供给我们诸如增强for循环,可变参数列表,静态导入, 枚举类型定义,自动装箱拆箱,泛型等一些列优秀的功能。

    以及后来的jdk7.0,提供犹如二进制字面量,数字常量下划线,Switch运算中String类型的引入,try-with-resource的资源自动释放等,都给我们带来了很方便的操作和极大的便利。

    java8中的新特性

    接口方法lambda表达式方法引用Stream API

    ### 接口方法

    JDK1.8之前,接口中的方法,都必须是抽象方法。实现接口的类,必须实现接口中定义的每一个抽象方 法。

    由于JDK1.8的API,在已有的接口上,新增了很多的新方法,这种新增变化带来的问题,正如上述的情况 一样,也会给使用这些接口的老用户带来很多的不便。

    为了解决这个问题,JDK1.8中引入了一种新的机制:接口可以支持在声明方法的同时,提供实现。 主要通过两种方式可以完成这种操作:

    默认方法

    静态方法在接口中,

    以上俩种方法都可以有具体实现。因此,实现接口的类,可以继承这些方法并直接使用,就 不再需要强制在实现类中的去实现(重写)接口中的方法了。这种机制可以使我们更平滑地进行接口的 优化和演进。

    默认方法

    jdk1.8中,接口里面可以定义默认方法

    定义语法

    interface FirstInterface{ //传统定义,抽象方法,没有方法体。 void before(); //默认方法 default void test() { System.out.println("Default method in FirstInterface"); } } class FirstClass implements FirstInterface{ //所有实现类必须实现接口中未实现的方法。 @Override public void before() { System.out.println("我是FirstInterface中的抽象方法,所有实现类必须实现!"); } } public class DefaultMethod { public static void main(String[] args) { FirstClass fc = new FirstClass(); fc.test(); //此处输出Default method in FirstInterface,对于默认方法,如果实现中没有实现就是用默认的。 fc.before(); //此处输出我是FirstInterface中的抽象方法,所有实现类必须实现} }

    默认方法存在的两大优势

    默认方法存在的两大优势:

    可以让接口更优雅的升级,减少使用人员操作的负担 不必随着接口方法增加,从而修改实现代码,因为默认方法在子类中可以不用实现可以让实现类中省略很多不必要方法的空实现

    接口继承及默认方法冲突

    Java中一个类只能继承一个父类,但是可以实现多个接口。由于Java 8中,接口里面的方法可以直接进 行实现,那么类也就可以从多个接口中继承这些方法。

    interface A{ default void test(){ System.out.println("Default Method test in A"); } } interface B extends A{ default void test(){ System.out.println("default method test in B"); } } class C implements A,B{ public static void main(String[] args){ C c = new C(); c.test(); } } //运行结果: default method test in B

    方法调用的判断规则

    类中声明的方法优先级最高。 类或父类中,声明的方法要高于任何默认方法的优先级如果无法依据第一条进行判断,那么子接口的优先级更高 例如,如果 B 接口继承了 A接口 ,那么 B 就比 A 更加具体,优先级更高 所以,在上述例子中,B是子接口,优先级别更高,调用test方法后输出:defaut method test in B.最后,如果还是无法判断,那么继承了多个接口的类,必须通过实现(重写)方法来确定方法的 调用

    例如:一个类C,实现两个接口A、B,但是A和B之间没有子父接口关系

    interface A{ default void test(){ System.out.println("Default Method test in A"); } } interface B { default void test(){ System.out.println("default method test in B") } } //如下代码编译会报错。 /*class C implements A,B{ public static void main(String[] args){ C c = new C(); c.test(); } }*/ //如果C需要同时实现A和B接口,那么必须显示覆盖 class C implements A,B{ public void test(){ //如果在C中需要显示访问A/B的默认方法,可以使用接口名.super.方法名(); A.super.test(); B.super.test(); //或者自己编写test方法的实现 } }

    例如:一个类C,继承父类A,实现接口B

    class A{ public void test(){ System.out.println("Default Method test in A"); } } interface B { default void test(){ System.out.println("default method test in B"); } } class C extends A implements B{ public static void main(String[] args){ C c = new C(); c.test(); } } //运行结果:default method test in A

    因为A是类,B是接口,A里面的test方法优先度更高

    静态方法

    jdk1.8中,接口里面可以定义静态方法

    和类中定义的静态方法类似,接口中的静态方法可以直接通过 接口名.静态方法名 的形式进行访问。

    interface InterfaceName{ static returnType methodName(arg-list){ //代码实现 } }

    示例:

    public interface StaticMethod{ public static void test(){ System.out.println("我是StaticMethod接口中的静态方法!"); } } class A implements StaticMethod{} class Test{ public static void main(String args[]){ //运行成功 StaticMethod.test(); //编译报错: //Static method may be invoked on containing interface class only //A.test(); } }

    Lambda

    lambda表达式是jdk1.8新增的一种语法,以确保在java代码中可以支持函数式编程,让代码的表示含义更简单

    使用:

    虽然Lambda表达式在java中的实际意义,是对一个接口的实现,但并不是任何接口都可以使用Lambda

    表达式来进行实现。

    原因也很简单,一个Lambda表达式,只是描述了一个函数的参数列表、函数主体、返回类型,那么它

    顶多是对接口中的一个抽象方法的实现,如果接口中有多个抽象方法呢?

    很显然,这时候一个Lambda表达式是无法表示为这个接口的实现,因为无法实现接口中多个抽象方

    法。

    所以,接口中有且只有一个抽象方法的时候,才可以使用Lambda表达式来对其进行实现。

    函数式接口:

    有且只有一个抽象方法的接口,就是函数式接口

    意思是可以允许接口中有其他静态方法和有默认实现的方法

    @FunctionalInterface 可以用来检查被标注的接口,是不是一个函数式接口,如果不是,那么编译器会报错

    Lambda语法

    函数式接口中,抽象方法无参无返回值 public class Test { public static void main(String[] args) { /** * Action action1 = new Action(){ * * public void run(){ * * } * }; */ Action action1 = () -> {}; /** * Action action2 = new Action(){ * * public void run(){ * System.out.println("hello"); * } * }; */ //函数主体中,如果只有一句代码,那么可以省去大括号 Action action2 = () -> System.out.println("hello"); /** * Action action3 = new Action(){ * * public void run(){ * int a = 1; * int b = 2; * System.out.println(a+b); * } * }; */ //函数主体中,如果有多句代码,那么大括号必须要写 Action action3= () -> { int a = 1; int b = 2; System.out.println(a+b); }; } } interface Action{ public void run(); } 函数式接口中,抽象方法有参,无返回值 public class Test { public static void main(String[] args) { /** * Action action = new Action(){ * * public void run(int a){ * * } * }; */ Action action = (int a) -> {}; /** * Action action1 = new Action(){ * * public void run(int a){ * * } * }; */ //只有一个参数时,可以不加小括号 //并且参数的类型,JVM运行时会做自动推断的,及时不写,它也知道是inAction action1 = a -> {}; /** * Action action2 = new Action(){ * * public void run(int a){ * System.out.println(a); * } * }; */ Action action2 = a -> System.out.println(a); /** * Action action3 = new Action(){ * * public void run(int a){ * a++; * } * }; */ Action action3 = a -> a++; } } //抽象方法是一个参数的情况 interface Action{ public void run(int a); } public class Test { public static void main(String[] args) { /** * Action action1 = new Action(){ * * public void run(int a,int b){ * * } * }; */ Action action1 = (a,b) -> {}; } } //抽象方法是多个参数的情况 interface Action{ public void run(int a,int b); } 函数式接口中,抽象方法无参,有返回值 public class Test { public static void main(String[] args) { /** * Action action1 = new Action(){ * * public void run(){ * return 1; * } * }; */ //如果就一句代码,大括号可以去掉,return关键字也会省去 Action action1 = () -> 1; /** * Action action2 = new Action(){ * * public void run(){ * int num = 10; * return (int)(Math.random()*num); * } * }; */ Action action2 = () -> { int num = 10; return (int)(Math.random()*num); }; } } interface Action{ public int run(); } 函数式接口中,抽象方法有参数,有返回值 public class Test { public static void main(String[] args) { /** * Action action1 = new Action(){ * * public int run(int a,int b){ * return a+b; * } * }; */ Action action1 = (a,b) -> a + b; /** * Action action2 = new Action(){ * * public int run(int a,int b){ * int num = a+b; * return (int)(Math.random()*num); * } * }; */ Action action2 = (a,b) -> { int num = a+b; return (int)(Math.random()*num); }; } } interface Action{ public int run(int a,int b); }

    Lambda表达式中的参数列表,里面的参数可以不写类型,因为JVM在运行时会自动推断

    常用接口

    1. Predicate

    java.util.function.Predicate 接口定义了一个名叫test的抽象方法,他接受泛型T对象,并返回一个boolean类型的结果

    @FunctionalInterface public interface Predicate<T> { boolean test(T t); }

    例如: 定义一个方法,用来过滤数组中所有符合要求的数据,选出大于50的数据

    public class Test { public static void main(String[] args) { Test t = new Test(); Integer[] arr = {12,3,43,123,34,6,56,7}; arr = t.filter(arr,e->e>50); System.out.println(Arrays.toString(arr)); } public Integer[] filter(Integer[] arr, Predicate<Integer> p)List<Integer> list = new ArrayList<>(); for(Integer i: arr){ //判断当前数据是否符合要求 if(p.test(i)){ list.add(i); } } //把集合转为Integer类型数组 return list.toArray(new Integer[list.size()]); } } //运行结果: [123, 56]

    add()

    public class Test { public static void main(String[] args) { Test t = new Test(); Integer[] arr = {12,3,43,123,34,6,56,7}; //条件1,数据大于10 Predicate<Integer> p1 = e -> e>10; //条件2,数据小于50 Predicate<Integer> p2 = e -> e<50; //俩个条件同时成立 Predicate<Integer> p = p1.and(p2); arr = t.filter(arr,p); System.out.println(Arrays.toString(arr)); } public Integer[] filter(Integer[] arr, Predicate<Integer> p)List<Integer> list = new ArrayList<>(); for(Integer i: arr){ //判断当前数据是否符合要求 if(p.test(i)){ list.add(i); } } //把集合转为Integer类型数组 return list.toArray(new Integer[list.size()]); } } //运行结果: [12, 43, 34]

    or() 方法

    public class Test { public static void main(String[] args) { Test t = new Test(); Integer[] arr = {12,3,43,123,34,6,56,7}; //条件1,数据小于10 Predicate<Integer> p1 = e -> e<10; //条件2,数据大于50 Predicate<Integer> p2 = e -> e>50; //俩个条件成立一个即可 Predicate<Integer> p = p1.or(p2); arr = t.filter(arr,p); System.out.println(Arrays.toString(arr)); } public Integer[] filter(Integer[] arr, Predicate<Integer> p)List<Integer> list = new ArrayList<>(); for(Integer i: arr){ if(p.test(i)){ //判断当前数据是否符合要求 list.add(i); } } //把集合转为Integer类型数组 return list.toArray(new Integer[list.size()]); } } //运行结果: [3, 123, 6, 56, 7]

    negate() 方法 ;取反

    public class Test { public static void main(String[] args) { Test t = new Test(); Integer[] arr = {12,3,43,123,34,6,56,7}; //条件1,数据大于10 Predicate<Integer> p1 = e -> e>10; //获取条件1相反的数据 Predicate<Integer> p = p1.negate(); arr = t.filter(arr,p); System.out.println(Arrays.toString(arr)); } public Integer[] filter(Integer[] arr, Predicate<Integer> p)List<Integer> list = new ArrayList<>(); for(Integer i: arr){ //判断当前数据是否符合要求 if(p.test(i)){ list.add(i); } } //把集合转为Integer类型数组 return list.toArray(new Integer[list.size()]); } } //运行结果: [3, 6, 7]

    2. Consumer

    java.util.function.Consumer 接口:

    @FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } } public class Test { public static void main(String[] args) { Test t = new Test(); Student stu = new Student("tom"); //操作1,给stu对象的name属性值加前缀 Consumer<Student> consumer1 = (s) -> s.name = "briup_"+s.na//操作2,给stu对象的name属性值加后缀 Consumer<Student> consumer2 = (s) -> s.name = s.name+"_"+System.currentTimeMillis(); //操作3,给stu对象的name属性值,先加前缀,再加后缀 Consumer<Student> consumer3 = consumer1.andThen(consumer2)//如果传入consumer1,表示只加前缀 //如果传入consumer2,表示只加后缀 //如果传入consumer3,表示先加前缀,再加后缀 t.operStu(stu,consumer3); System.out.println(stu.name); } public void operStu(Student stu, Consumer<Student> consumer)consumer.accept(stu); } } class Student{ String name; public Student(String name) { this.name = name; } } //运行结果: briup_tom_1598282322884

    3. Function

    java.util.function.Function<T,R>;

    @FunctionalInterface public interface Function<T, R> { R apply(T t); default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } //Returns a function that always returns its input argument. static <T> Function<T, T> identity() { return t -> t; } }

    例如,需要定义一个方法,用来将字符串转换为字符数组

    public class Test { public static void main(String[] args) { String str = "a-b-c-a-b-c"; //传入字符串,返回数组,操作为把字符串按照 "-" 进行分割为字符串数组 // "a-b-c-a-b-c" 转换为 {"a","b","c","a","b","c"} Function<String,String[]> f1 = s -> s.split("-"); //传入字符串数组,返回Set<String>集合,目的是去除数组中重复的数据,存放把结果存放到 Set集合中 //{"a","b","c","a","b","c"} 转换为集合 [a, b, c] Function<String[], Set<String>> f2 = arr -> { Set<String> set = new HashSet<>(); for(String string : arr){ set.add(string); } return set; }; //刚好,f1函数的结果,作为f2函数的参数,f1和f2组合成f3函数 //f3函数表示传入字符串,最后返回Set<String>集合 //其实内部是先将字符串交给f1函数转换数组,在将数组交给f2函数转换Set集合 //通过上面列出的andThen源码也可以看出是这个效果 Function<String,Set<String>> f3 = f1.andThen(f2); Set<String> set = f3.apply(str); System.out.println(set); } } //运行结果: [a, b, c]

    4. Supplier

    java.util.function.Supplier接口

    @FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }

    5. 针对基本数据类型的函数式接口

    IntPredicateDoublePredicateIntConsumerLongBinaryOperator

    6. 局部变量

    如果在Lambda表达式中,使用了局部变量,那么这个局部变量一定要使用fuinal修饰.

    方法引用

    1. 静态方法引用

    语法: 类名::静态方法名

    public class Test { public static void main(String[] args) { //只要函数的参数列表是String类型,函数的返回值是int类型,就可以作为Action接口的具体实现 Action a1 = str -> 1; //使用 类名::静态方法名 的形式来引用当前类中的len方法 Action a2 = Test::len; System.out.println(a1.run("hello"));//输出1 System.out.println(a2.run("hello"));//输出5 } public static int len(String str){ return str.length(); } } interface Action{ int run(String str); }

    2. 实例方法引用

    语法: 类名::非静态方法名;

    public class Test { public static void main(String[] args) { /** * Action a1 = new Action() { * @Override * public int run(String str) { * return 1; * } * }; */ Action a1 = str -> 1; /** * Action a2 = new Action() { * @Override * public int run(String str) { * return str.hashCode(); * } * }; */ Action a2 = String::hashCode; /** * Action a3 = new Action() { * @Override * public int run(String str) { * return str.length(); * } * }; */ Action a3 = String::length; } } interface Action{ int run(String str); }

    对于 int run(String str) 来讲,可以直接引用 String 类型中的 length() 方法来表示对 run 方 法的实现,这时候需要按照以下要求:

    run方法的参数类型是String类型,才可以使用String引用它的非静态方法来对run进行实现使用String引用它的非静态方法,必须是无参的方法,因为run方法只有一个String类型参数使用String引用它的非静态方法,必须是int返回类型,因为run方法的返回类型是int

    如果run方法是两个参数

    public class Test { public static void main(String[] args) { /** * Action a1 = new Action() { * @Override * public int run(String str,int i) { * return 1; * } * }; */ Action a1 = (str,i) -> 1; /** * Action a2 = new Action() { * @Override * public int run(String str,int i) { * return str.indexOf(i); * } * }; */ Action a2 = String::indexOf; /** * Action a3 = new Action() { * @Override * public int run(String str,int i) { * return str.lastIndexOf(i); * } * }; */ Action a3 = String::lastIndexOf; System.out.println(a2.run("abc-abc",'a'));//输出 0 System.out.println(a3.run("abc-abc",'a'));//输出 4 } } interface Action{ int run(String str,int i); }

    对于 int run(String str,int i) 来讲,可以直接引用 String 类型中的 indexOf(int ch) 方法 来表示对 run 方法的实现,这时候需要按照以下要求:

    run方法的第一个参数类型是String类型,才可以使用String引用它的非静态方法来对run进行实现使用String引用它的非静态方法,必须是有参的,并且参数类型是int,因为run方法的第二个参数 是int类型使用String引用它的非静态方法,必须是int返回类型,因为run方法的返回类型是int

    也就是说,如果你是想按照如下形式,来完成run方法的实现:

    public int run(String str,int i){ return str.xxx(i); }

    3.使用对象引用方法

    语法: 对象::非静态方法

    public class Test { public static void main(String[] args) { MyHandler handler = new MyHandler(); /** * Action a1 = new Action() { * @Override * public String run(String str) { * return str; * } * }; */ Action a1 = str -> str; //这里表示,使用handler对象的test方法,来对Action接口进行实现 //因为test方法的参数列表和返回类型,恰好是和run方法的参数列表和返回类型保持一致 Action a2 = handler::test;; System.out.println(a2.run("tom"));//输出:hello! tom } } interface Action{ String run(String str); } class MyHandler{ public String test(String name){ return "hello! "+name; } }

    构造方法引用

    类名::new

    无参构造器

    public class Test {public static void main(String[] args) { /** * Action a1 = new Action() { * @Override * public Student run() { * return new Student(); * } * }; */ Action a1 = ()->new Student(); //这里表示,使用Student类的无参构造函数,来对Action接口进行实现 //因为Student类的无参构造函数,恰好是和run方法的参数列表和返回类型保持一致 Action a2 = Student::new; System.out.println(a2.run()); } } interface Action{ Student run(); } class Student{ }

    有参构造器

    public class Test { public static void main(String[] args) { /** * Action a1 = new Action() { * @Override * public Student run(String name) { * return new Student(name); * } * }; */ Action a1 = name->new Student(name); //这里表示,使用Student类的有参构造函数,来对Action接口进行实现 //因为Student类的有参构造函数,恰好是和run方法的参数列表和返回类型保持一致 Action a2 = Student::new; System.out.println(a2.run("tom")); } } interface Action{ Student run(String name); } class Student{ private String name; public Student(String name){ this.name = name; } }

    数组构造

    数组类型::new

    public class Test { public static void main(String[] args) { /** * Action a1 = new Action() { * @Override * public int[] run(int len) { * return new int[len]; * } * }; */ Action a1 = len -> new int[len]; //这里表示,使用int数组的构造函数,来对Action接口进行实现 //因为int数组的构造函数,恰好是和run方法的参数列表和返回类型保持一致 Action a2 = int[]::new; System.out.println(a2.run(5)); } } interface Action{ int[] run(int len); }

    Optinal

    在Java8之前,一个函数可能因为代码逻辑问题,最终返回一个null,这时候程序中很可能出现空指针异 常。而在Java8中,不推荐返回 null ,而是返回 Optional

    Optional 中常用的方法:

    ofofNullableisPresentgetorElseorElseGetmapflatMapfilter public class Test { public static void main(String[] args) { /** * of方法 为非null的值创建一个Optional对象 * 如果传入参数为null,则抛出NullPointerException */ Optional<String> op1 = Optional.of("hello"); /** * ofNullable方法 * ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况 */ Optional<String> op2 = Optional.ofNullable(null); /** * isPresent方法 如果值存在返回true,否则返回false * get方法 如果Optional有值则将其返回,否则抛出NoSuchElementException */ if(op1.isPresent()){ System.out.println(op1.get()); } if(op2.isPresent()){ System.out.println(op2.get()); } /** * ifPresent方法 如果Optional实例有值则为其调用Consumer接口中的方法,否则不做处理 * Consumer: * public void accept(T t); */ op1.ifPresent(str->System.out.println(str)); op2.ifPresent(str->System.out.println(str));//这个不执行 因为op2里面的值是 null /** * orElse方法 如果有值则将其返回,否则返回指定的其它值 */ System.out.println(op1.orElse("如果op1中的值为null则返回这句话")); System.out.println(op2.orElse("如果op2中的值为null则返回这句话")); /** * orElseGet方法 orElseGet与orElse方法类似,区别在于得到的默认值的方式不同 * Supplier: * public T get(); */ System.out.println(op1.orElseGet(()->"自己定义的返回值"))System.out.println(op2.orElseGet(()->"自己定义的返回值"))/** * map方法 如果有值,则调用mapper的函数处理并得到返回值 * 返回值并且依然Optional包裹起来,其泛型和你返回值的类型一致 * public <U> Optional<U> map(Function<? super T, ? extends U> mapper) */ Optional<Integer> map1 = op1.map(str->1); System.out.println(map1.orElse(0)); Optional<Double> map2 = op2.map(str->1.2); System.out.println(map2.orElse(0D)); /** * flatMap方法 如果有值,则调用mapper的函数返回Optional类型返回值,否则返回空 Optional * flatMap与map方法类似,区别在于flatMap中的mapper返回值必须是Optional * 调用结束时,flatMap不会对结果用Optional封装,需要我们自己把返回值封装为Optiona* public <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper); */ Optional<String> flatMap = op1.flatMap(str->Optional.of(str+"_briup")); System.out.println(flatMap.get()); /** * filter方法 如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional * public Optional<T> filter(Predicate<? super T> predicate); */ op1 = op1.filter(str->str.length()<10); System.out.println(op1.orElse("值为null")); op1 = op1.filter(str->str.length()>10); System.out.println(op1.orElse("值为null")); } }

    Stream

    java.util.stream.Stream 接口,表示能应用在一组元素上,一次执行的操作序列,也就是可 以对一组数据进行连续的多次操作

    Stream在使用的时候,需要指定一个数据源,比如 java.util.Collection 的子类, List 或者 Set 都可以,但是 Map 类型的集合不支持。

    Stream是对集合功能的增强,它提供了各种非常便利、高效的聚合操作,可以大批量数据操作,同时再 结合Lambda表达式,就可以极大的提高编程效率。

    Stream操作分为中间操作或者最终操作两种:

    中间操作,返回Stream本身,这样就可以将多个操作依次串起来 例如, map、flatMap、filter、distinct、sorted、peek、limit、skip、parallel、 sequential、unordered最终操作,返回一特定类型的计算结果 例如, forEach、forEachOrdered、toArray、reduce、collect、min、max、count、 anyMatch、allMatch、noneMatch、findFirst、findAny、iterator

    基本类型

    对于基本数值类型,有专门的三种Stream类型:

    IntStreamLongStreamDoubleStream

    虽然也可以用Stream类型

    并指定泛型:StreamStream Stream

    但是,在数据量较大的时候,自动拆箱/装箱会比较消耗性能,所以提供了上面三种专门针对基本类型 的Stream

    Processed: 0.012, SQL: 9