接口的默认方法
Java 8允许给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展方法
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用
Lambda表达式
从Java 8出现以来lambda是最重要的特性之一,它可以用简洁流畅的代码完成一个功能。 很长一段时间Java被吐槽是冗余和缺乏函数式编程能力的语言,随着函数式编程的流行Java 8种也引入了这种编程风格
可以取代大部分的匿名内部类,写出更优雅的Java代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构
JDK也提供了大量的内置函数式接口供使用,使得Lambda表达式的运用更加方便、高效
什么是Lambda?
lambda表达式是一段可以传递的代码,核心思想是将面向对象中的传递数据变成传递行为
Lambda的作用?
所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是“那段代码”,需要是这个接口的实现
Lambda表达式本身就是一个接口的实现
语法:
() -> {}
语法字符 | 描述 |
---|---|
() | 用来描述参数列表 |
-> | 为 lambda运算符,读作(goes to) |
{} | 用来描述方法体 |
使用Lambda对接口的要求
虽然使用Lambda表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用Lambda表达式来实现。Lambda规定接口中只能有一个需要被实现的方法,也就是只有一个抽象方法;不是规定接口中只能有一个方法,被default修饰的方法会有默认实现,不是必须被实现的方法,所以不影响Lambda表达式的使用
函数式接口
Lambda表达式是如何在Java的类型系统中表示的呢?
每一个Lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的Lambda表达式都会被匹配到这个抽象方法。因为默认方法不算抽象方法,所以也可以给函数式接口添加默认方法。
@FunctionalInterface
可以将Lambda表达式当作任意只包含一个抽象方法的接口类型,确保接口一定达到这个要求,只需要给接口添加@FunctionalInterface注解,@FunctionalInterface修饰函数式接口,要求接口中的抽象方法只有一个,编译器如果发现标注了这个注解的接口有多于一个抽象方法的时候会报错的
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted); // 123
需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的,因为它只有一个需要实现的抽象方法
Lambda作用域
在Lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量
Lambda访问局部变量
可以直接在Lambda表达式中访问外层的局部变量
final int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确
int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;
在lambda表达式中试图修改num同样是不允许的
Lambda访问对象字段与静态变量
和本地变量不同的是,Lambda对于实例字段、静态变量是即可读又可写。该行为和匿名对象是一致的
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
**加粗样式** };
}
}
无法访问接口的默认方法
Lambda表达式中是无法访问到接口的默认方法,将无法编译
JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在Lambda上
匿名内部类
Runnable r = new Runnable() {
@Override
public void run() { //实现函数化接口的抽象方法run
System.out.println("do something.");
}
};
与在实现类中实现抽象方法相似,实现后可以直接runnable.run()调用函数化接口->只有一个抽象方法
Lambda表达式实现
Runnable runnable=( )->{ //run方法的表达式 ( )是方法参数,没有就空着
System.out.println("do something.");
};
匿名内部类
匿名内部类也就是没有名字的内部类,正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写
使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口
不具有抽象和静态属性。并且不能派生出子类
匿名内部类只能访问外部的静态变量和final修饰的常量
对于实例字段、静态变量是即可读又可写|实例字段:必须先由其所在的类构造一个实例出来。对象.实例字段
作用:内部类通过该访问路径可以进行内部类内部和外部的数据交互,一般与final结合使用比较多
new Runnable(){}其实不是实例化Runnable接口来的,实际上一种内部类的一种简写
①首先构造了一个“implements Runnable”的无名local内部类(方法内的内部类)
②然后构造了这个无名local内部类的一个实例
③然后用Runnable来表示这个无名local内部类的type(OO多态)
package com.cph.Thread;
public class Text2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Runnable runnable=new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("i am new Runnable(){xxx}");
}
};
Thread thread=new Thread(runnable);
thread.start();
}
}
上面这段代码编译后你会看到其实是编译了两个类来的
其中Text2$1就是无名local内部内类,这个也就很好地解释了为什么在main()方法中new Runnable(){xxx}里面的调用main()方法中的变量的时候要用final关键字来修饰。 上面只是借助new Runnable(){xxx}特例来说明接口在方法中new的机制,对其他接口同理可得
匿名内部类(Anonymous Inner Class),在创建实例的同时给出类的定义,所有这些在一个表达式中完成
方法与构造函数引用
Java 8允许使用“ :: ”关键字来传递方法或者构造函数引用
如何引用一个静态方法,也可以引用一个对象的方法
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted); // 123
converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
构造函数是如何使用“ :: ”关键字来引用
首先定义一个包含多个构造函数的简单类
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
接下来指定一个用来创建Person对象的对象工厂接口
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
这里使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
只需要使用Person::new来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数