一、接口的默认方法
二、Lambda 表达式
三、函数式接口
四、方法与构造函数引用
五、Lambda 作用域
六、访问局部变量
七、访问对象字段与静态变量
八、访问接口的默认方法
九、Date API
十、Annotation 注解:支持多重注解
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:
public interface Formula {
double calculate(int a);
// jdk8能给接口添加一个非抽象的方法实现
default double sqrt(int a){
return Math.sqrt(a);
}
}
Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,
默认方法sqrt将在子类上可以直接使用。
@Test
public void test1() {
Formula formula = new Formula() {
//这里只实现了一个方法,没有实现用default修饰的方法
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
System.out.println(formula.calculate(100)); //100.0
System.out.println(formula.sqrt(100)); //10.0
}
1).如果一个类实现两个接口,这两个接口同时有相同的抽象方法,在类中只需要重写一次这个方法。
@Test
public void test2() {
// 如果用Lambda表达式,一定要写明泛型
List list = Arrays.asList("peter","anna","make");
// ①.老版本的Java中是这样排列字符串的
/* Collections.sort(list, new Comparator() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
*/
// ②.Java 8提供了更简洁的语法,lambda表达式:
/*
Collections.sort(list ,(String a,String b) -> {
return a.compareTo(b);
});
*/
// ③.还可以写得更短
Collections.sort(list, (String a, String b) -> a.compareTo(b));
// ④.还可以这么写
// Collections.sort(list, String::compareTo);
System.out.println(Collections.singletonList(list)); //[[anna, make, peter]]
}
1. 什么是λ表达式
lambda包含3个部分:
(1)括弧包起来的参数
(2)一个箭头
(3)方法体,可以是单个语句,也可以是语句块
参数可以写类型,也可以不写,jvm很智能的,它能自己推算出来
方法可以有返回,也可以无返回,如果有多个语句,还要返回值,需要加上return 。
一个很有意思的事情:
之前我们说Object是一切类的父类,然而在加入了lambda以后,这种大一统的局面将不复存在:
@Test
public void test33() {
Object r = ()->System.out.println("hello,lambda");
}
编译报错:incompatible types: Object is not a functional interface
@Test
public void test33() {
Object r = (Runnable)()->System.out.println("hello,lambda");
}
如果你认为lambda表达式仅仅是为了从语法上简化匿名内部类,那就太小看jdk8的lambda了!
lambda的定义:
(1)lambda是方法的实现
(2)lambda是延迟执行的
public static void main(String[] args) {
// 正常实现
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("hello lambda!");
}
};
runnable.run();
// 用lambda实现如下(如果run方法体里只有一行数据可以省去{)
Runnable runnable1 = () -> {
System.out.println("hello lambda2!");
System.out.println("2");
};
runnable1.run();
}
lambda是如何做到的呢?可以反编译后查看字节码。(里面有一个叫做invokedynamic的指令。invokedynamic是从jdk7开始引入的,jdk8开始落地。)可以看出来
lambda并不是语法糖,它不是像匿名内部类那样生成那种带有$的匿名类。简单的说,这里只是定义了一个方法调用点,具体调用那个方法要到运行时才能决定,这就是前面所说的:延迟执行。具体的细节请google:invokedynamic。
(1)是一个接口
(2)只有一个待实现的方法 !!!!
因为jdk8开始,接口可以有default方法,所以,函数式接口也是可以有default方法的,但是,只能有一个未实现的方法。
与此对应,新引入了一个注解: @FunctionalInterface。这个注解只是起文档的作用,说明这个接口是函数式接口,编译器并不会使用这个注解来决定一个接口是不是函数式接口。不管加不加@FunctionalInterface这个注解,下面的接口都是函数式接口:
interface Something {
public String doit(Integer i);
}
Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
@FunctionalInterface
public interface Converter {
// 如将字条串转为int类型
T convert(E str);
// 函数式接口只能有 一个 抽象方法
// T convert2(E str, E sre);
}
@Test
public void test3() {
Converter converter = (str) ->Integer.valueOf(str);
//上面还可以通过静态方法引用来表示:
// Converter converter = Integer::valueOf;
Integer integer = converter.convert("123");
System.out.println(integer);
}
需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。
public class InnerClassExamples {
public static void main(String... args) {
Hello h = new Hello();
h.r.run();
}
}
class Hello {
public Runnable r = new Runnable() {
public void run() {
// 这里的this指的是匿名类,而非Hello类。
System.out.println("-->1 "+this);
System.out.println("-->2 "+toString());
// 想要引用Hello类需要Hello.this这样!!!
System.out.println("++1 "+Hello.this);
System.out.println("++2 "+Hello.this.toString());
}
};
public String toString() {
return "Hello's custom toString()";
}
}
-->1 com.changwen.jdk8.Hello$1@6d6f6e28 -->2 com.changwen.jdk8.Hello$1@6d6f6e28 ++1 Hello's custom toString() ++2 Hello's custom toString() |
public class Test7{
public static void main(String args[]){
Hello2 h = new Hello2();
h.r.run();
}
}
class Hello2{
public Runnable r = () -> {
System.out.println(this);
System.out.println(toString());
};
public String toString() {
return "Hello's custom toString()";
}
}
Hello's custom toString() Hello's custom toString() |
匿名内部类只能引用作用域外面的final的变量,在lambda中对这个限制做了削弱,只需要是“等价final”就可以,没必要用final关键字来标识。
我们可以直接在lambda表达式中访问外层的局部变量:
public static void main(String args[]){
String message = "Howdy, world!";//不需要是final的
Runnable runnable = () -> System.out.println(message);
runnable.run();
}
“等效final”的意思是:事实上的final,所以,一旦赋值也是不可以改变的!比如
public static void main(String args[]){
String message = "Howdy, world!";//不需要是final的
Runnable runnable = () -> System.out.println(message);
runnable.run();
message = "change it";
}
error: local variables referenced from a lambda expression must be final or effectively final
真正的大杀器出现了:
现在我们要把多个Student对象进行排序,有时候是按照firstName来排,有时候是按照lastName或者是age来排,使用lambda可以这样来做:
public class Student {
public String firstName;
public String lastName;
public int age;
public Student (String firstName, String lastName, int age){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String toString(){
return firstName+","+lastName+","+age;
}
}
@Test
public void test12() {
Student[] students = new Student[]{
new Student("Ted", "Neward", 41),
new Student("Charlotte", "Neward", 41),
new Student("Michael", "Neward", 19),
new Student("Matthew", "Neward", 13)
};
//sort by firstName
Arrays.sort(students, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));
System.out.println(Arrays.asList(students));
}
我们可以把Comparator抽取出来,变成是Student对象的成员变量:
public class Student {
public String firstName;
public String lastName;
public int age;
public Student (String firstName, String lastName, int age){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public final static Comparator compareFirstName =
(lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);
public final static Comparator compareLastName =
(lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);
public final static Comparator compareAge =
(lhs, rhs) -> lhs.age - rhs.age;
public String toString(){
return firstName+","+lastName+","+age;
}
}
@Test
public void test12() {
Student[] students = new Student[]{
new Student("Ted", "Neward", 41),
new Student("Charlotte", "Neward", 41),
new Student("Michael", "Neward", 19),
new Student("Matthew", "Neward", 13)
};
//sort by firstName
// Arrays.sort(students, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));
Arrays.sort(students, Student.compareFirstName); //这里直接引用lambda
System.out.println(Arrays.asList(students));
}
能起到同样的作用,但是语法看上去很奇怪,因为之前我们都是创建一个满足Comparator签名的方法,然后直接调用,而非定义一个变量,
public class Student {
public String firstName;
public String lastName;
public int age;
public Student (String firstName, String lastName, int age){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public static int compareFirstName(Student lhs, Student rhs){
return lhs.firstName.compareTo(rhs.firstName);
}
public static int compareLastName(Student lhs, Student rhs){
return lhs.lastName.compareTo(rhs.lastName);
}
public String toString(){
return firstName+","+lastName+","+age;
}
}
@Test
public void test12() {
Student[] students = new Student[]{
new Student("Ted", "Neward", 41),
new Student("Charlotte", "Neward", 41),
new Student("Michael", "Neward", 19),
new Student("Matthew", "Neward", 13)
};
//sort by firstName
Arrays.sort(students, Student::compareFirstName);
System.out.println(Arrays.asList(students));
}
看Student::compareFirstName这种调用方式,
public class Student {
public String firstName;
public String lastName;
public int age;
public Student (String firstName, String lastName, int age){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// get方法
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String toString(){
return firstName+","+lastName+","+age;
}
}
@Test
public void test12() {
Student[] students = new Student[]{
new Student("Ted", "Neward", 41),
new Student("Charlotte", "Neward", 41),
new Student("Michael", "Neward", 19),
new Student("Matthew", "Neward", 13)
};
//sort by firstName
Arrays.sort(students, Comparator.comparing(Student::getFirstName));
System.out.println(Arrays.asList(students));
}
注意这里的Person::getFirstName,很显然getFirstName()并不是static的,这是jdk做了封装的缘故!
@Test
public void test12() {
Student[] students = new Student[]{
new Student("Ted", "Neward", 41),
new Student("Charlotte", "Neward", 41),
new Student("Michael", "Neward", 19),
new Student("Matthew", "Neward", 13)
};
//sort by firstName
/* Collections.sort(Arrays.asList(students), (lhs, rhs)->{
if(lhs.getLastName().equals(rhs.getLastName())){
return lhs.getAge()-rhs.getAge();
}else{
return lhs.getLastName().compareTo(rhs.getLastName());
}
});
*/
// 上面可以改为这样子
Collections.sort(Arrays.asList(students),
Comparator.comparing(Student::getLastName).thenComparing(Student::getAge));
System.out.println(Arrays.asList(students));
}
还有更多的诸如:andThen()这样的方法,可以查api。
public class Person {
String firstName;
String lastName;
public Person() {}
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
接下来我们指定一个用来创建Person对象的对象工厂接口:
public interface PersonFactory {
P create(String firstName, String lastName);
}
这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:
@Test
public void test4() {
//我们只需要使用 Person::new 来获取Person类构造函数的引用,
// Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
PersonFactory personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
}
我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}