JDK8学习笔记
一、JDK8新特性
1. Lambda表达式 2. 接口的增强 3. 函数式接口 4. 方法引用 5. Stream API 6. Optional 7. 新时间日期API
二、Lambda表达式
1. 需求分析
创建一个新的线程,指定线程要执行的任务
public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("当前线程名:"+Thread.currentThread().getName()); } }).start(); System.out.println("主线程名字:"+ Thread.currentThread().getName()); }
代码分析:
- Thread类需要一个Runnable接口作为参数,其中的抽象方法run是用来指定线程任务内容的核心
- 为了指定run方法体,不得不需要Runnable的的实现类
- 为了省区定义一个Runnable的实现类,不得不使用匿名内部类
- 必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值都不得不重写一遍,而且不能出错。
- 而实际上,我们只在乎方法体中的代码
2. Lambda表达式初体验
Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码
new Thread(()->{System.out.println("Lambda线程名字:"+ Thread.currentThread().getName());}).start();
Lambda表达式的有点:简化了匿名内部类的使用,语法更加简单。
匿名内部类语法冗余,体验了Lambda表达式和,发现Lambda表达式是简化匿名内部类的一种方式。
3. Lambda表达式语法规则
lambda省去了面向对象的条条框框,Lambda的标注格式由3部分组成
(String[] args) ->{ 代码体 }
格式说明:
- (参数类型、参数名称):参数列表
- (代码体):方法体
- ->:分割参数列表和方法体
3.1 无参无返回值的Lambda
定义一个接口
public interface UserService { public void show(); }
然后创建主方法使用
public static void main(String[] args) { goShow(new UserService() { @Override public void show() { System.out.println("show方法执行了:"+ Thread.currentThread().getName()); } }); goShow(() ->{ System.out.println("Lambda的show方法执行了:"+Thread.currentThread().getName()); }); } public static void goShow(UserService userService){ userService.show(); }
输出:
方法名为:main Lambda表达式方法名字:main
3.2 有参有返回值的Lambda
创建Person对象
@Data @NoArgsConstructor @AllArgsConstructor public class Person { private String name; private Integer age; private Integer height; }
在List集合中保存多个Person对象,然后根据这些对象做age排序操作
public static void main(String[] args) { List<Person> list = Arrays.asList( new Person("周杰伦",27,175), new Person("周星驰",32,157), new Person("周公瑾",182,188), new Person("周恩来",82,177) ); Collections.sort(list, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getAge() - o2.getAge(); } }); for (Person p: list){ System.out.println( p .toString()); } System.out.println("----------------------------"); Collections.sort(list,(Person o1,Person o2)->{ return o2.getAge() - o1.getAge(); }); for (Person p: list){ System.out.println("lambda====" + p .toString()); } }
我们发现sort方法的第二个参数是一个Comparator接口的匿名内部类,且执行的方法有参数和返回值的表达式
输出结果
Person(name=周杰伦, age=27, height=175) Person(name=周星驰, age=32, height=157) Person(name=周恩来, age=82, height=177) Person(name=周公瑾, age=182, height=188) ---------------------------- lambda====Person(name=周公瑾, age=182, height=188) lambda====Person(name=周恩来, age=82, height=177) lambda====Person(name=周星驰, age=32, height=157) lambda====Person(name=周杰伦, age=27, height=175)
4. @FunctionalInterface注解
/** * @FunctionalInterface * 这是一个标志注解,被该注解修饰的接口只能声明一个抽象方法 */ @FunctionalInterface public interface UserService { public void show(); }
5. Lambda表达式的原理
匿名内部类会在编译的时候产生一个class文件
Lambda表达式在程序运行的 时候会形成一个类
- 在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码
- 还会形成一个匿名内部类,实现接口,重写抽象方法
- 在接口中重写方法会调用新生成的方法
6. Lambda表达式省略写法
在lambda表达式的标准写法基础上,可以使用省略写法的规则为:
- 小括号内的参数类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,则可以同时省略大括号,return关键字以及分号。
public static void main(String[] args) { goOrderShow((String name) ->{ System.out.println(name); return name+"666"; }); goStudyShow((String name ,Integer age) ->{ System.out.println(name + age); return name + age +"777"; }); System.out.println("Lambda简化写法"); goOrderShow(name -> name+"6666"); goStudyShow((name ,age) -> name + age + "7777"); } public static void goOrderShow(OrderService orderService){ orderService.show("张三"); } public static void goStudyShow(StudentService studentService){ studentService.show("李四",32); }
7. Lambda表达式的使用前提
Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,必须满足两个条件
- 方法的参数或者局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法(@FunctionalInterface)
8. Lambda和匿名内部类的对比
- 所需类型不一样
- 匿名内部类的类型可以是类、抽象类、接口
- Lambda表达式需要的类型必须是接口
- 抽象方法的数量不一样
- 匿名内部类所需的接口中的抽象方法的数量是随意的
- Lambda表达式所需的接口中只能有一个抽象方法
- 实现原理不一样
- 匿名内部类再编译后形成一个class
- Lambda表达式是在程序运行的时候动态生成class
三、 接口中新增的方法
1. 新增方法
在JDK8针对接口做了增强,在JDK8之前
interface 接口名{ 静态常量; 抽象方法; }
JDK8之后
interface 接口名{ 静态常量; 抽象方法; 静态方法; 默认方法; }
2. 默认方法
2.1为什么增加默认方法
在JDK8以前接口中只有抽象方法和静态常量,会存在一下问题
如果接口中有新增抽象方法,那么实现类必须抽象这个抽象方法,非常不利与接口扩展
2.2 接口默认方法的格式
接口默认方法的语法格式是
interface 接口名{ 修饰符 default 返回值类型 方法名{ 方法体; } }
2.3 接口中默认方法的使用
接口中的默认方法有两种使用方式
- 实现类直接调用接口的默认方法
- 实现类重写接口的方法
3. 静态方法
JDK8中为接口新增了静态方法,作用也是为了接口的扩展
3.1 语法规则
interface 接口名{ 修饰符 static 返回值类型 方法名字{ 方法体; } }
3.2 静态方法的使用
public class Demo01Interface { public static void main(String[] args) { B b = new B(); System.out.println(b.test1()); System.out.println(b.test2()); System.out.println(A.test3()); } } interface A{ String test1(); public default String test2(){ return "接口新增了默认方法,可以被实现类重写,必须实例化调用"; } public static String test3(){ return "接口新增了静态方法,不能被实现类重写,类名.方法名调用"; } } class B implements A{ @Override public String test1() { return "接口抽象方法"; } }
接口中的静态方法在实现类中是不能被重写的。调用只能通过接口类型来实现:接口名.静态方法();
4. 两者的区别
- 默认方法通过实例调用,静态方法通过接口名调用
- 默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法
- 静态方法不能被继承,实现类不能重写接口的静态方法,只能通过接口名调用
四、 函数式接口
1. 函数式接口的由来
我们知道使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名字、抽象方法名。只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda表达式更加的方便,JDK中提供了大量常用的函数式接口。
2. 函数式接口介绍
在JDK中帮我们提供的有函数式接口,主要是在Java.util.function包中
2.1 Supplier
无参有返回值的接口,对于Lambda表达式需要提供一个返回数据的类型。
@FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
使用
public static void main(String[] args) { fun1(()->{ int[] arr = {7,3,5,12,42,1}; // int max = 0; // for (int i : arr){ // if(i>max){ // max = i; // } // } // return max ; Arrays.sort(arr); return arr[arr.length -1]; }); } public static void fun1 (Supplier<Integer> supplier){ //get方法是一个无参有返回值的抽象方法 Integer max = supplier.get(); System.out.println("Max ====="+ max); }
2.2 Consumer
有参数无返回值的接口,前面介绍的Supplier是接口用来生产数据的,而Consumer是用来消费数据的。使用的时候需要指定一个泛型来定义参数类型
@FunctionalInterface public interface Consumer<T> { void accept(T var1); }
使用
public class ConsumerTest { public static void main(String[] args) { fun1(a-> a+=12); } public static void fun1 (Consumer<Integer> consumer){ int a = 32; System.out.println(a); consumer.accept(a); System.out.println(a); } }
默认方法:andThen
如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现效果,消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合,而这个方法就是Consumer接口中的default方法andThen方法
default Consumer<T> andThen(Consumer<? super T> var1) { Objects.requireNonNull(var1); return (var2) -> { this.accept(var2); var1.accept(var2); }; }
具体操作
public class ConsumerAndThenTest { public static void main(String[] args) { func(msg ->{ System.out.println("转换为小写》》》》》"+msg.toLowerCase(Locale.ROOT)); },msg2 ->{ System.out.println("转换为大写》》》》》"+msg2.toUpperCase(Locale.ROOT)); }); } public static void func (Consumer<String> c1,Consumer<String> c2){ // c1.accept("ZhangSan"); // c2.accept("ZhangSan"); // c1.andThen(c2).accept("ZhangSan"); c2.andThen(c1).accept("ZhangSan"); } }
2.3 Function
有参有返回值的接口,Function接口是根据一个类型的数据得到另一个类型的数据,前者称为前置i套件,后者成为后置条件,有参数有返回值
@FunctionalInterface public interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t); }
使用:传入一个字符串返回一个数字
public class FunctionTest { public static void main(String[] args) { int a = func(msg -> Integer.parseInt(msg)); System.out.println(a); } public static Integer func(Function<String,Integer> fun){ return fun.apply("322"); } }
默认方法:andThen,也是进行组合操作
public class FunctionAndThenTest { public static void main(String[] args) { Integer result = func(msg -> Integer.parseInt(msg),msg2-> msg2*10); System.out.println(result); } public static Integer func (Function<String,Integer> f1,Function<Integer,Integer> f2){ // int a = f1.apply("32"); // int b = f2.apply(a); // return b; return f1.andThen(f2).apply("54"); } }
默认的compose方法的作用顺序和andThen刚好相反
而静态方法identity则是,输入什么参数就返回什么参数