java8系列-01 Lambdas 表达式
Lambdas表达式是java 8的一个最重要的新特性。它允许把函数作为一个方法的参数(在一个方法中用函数来作为方法传递进去)。或者把一段代码看成是数据。
Lambdas表达式实例
基本语法如下:
(parameters) -> experssion
或
(parameters) -> {statements;}
举栗说明
public class LombokTest {
public static void main(String[] args) {
// 简单情况下不用return
Arrays.asList("a","b","c").forEach(System.out::println);
// 复杂情况下可以带对花括号,像java代码块一样
Arrays.asList("d","e","f").sort((e1,e2) ->{
int res = e1.compareTo(e2);
return res;
});
// runoob例子==类型声明
MathOperation sub = (int a,int b) -> a - b;
// runoob例子==不用类型声明
MathOperation add = (a,b) -> a+b;
LombokTest lt = new LombokTest();
System.out.println("1+2="+lt.operate(1,2,add));
System.out.println("1-2="+lt.operate(1,2,sub));
}
interface MathOperation {
int operation(int a, int b);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
控制台打印
a
b
c
1+2=3
1-2=-1
使用Lambdas表达式需要注意以下两点
- Lambda表达式主要用来定义行内执行的方法类型接口。例如上面的
MathOperation
接口。 - Lamda表达式免去了使用匿名方法的麻烦,并且给予java简单但是强大的函数化的编程能力。
Lambdas表达式中的变量作用域
Lambdas表达式可以引用类的成员变量和局部变量。如果这些类型不是final的话,它们也会隐含的转换成final
public class LombokTest {
int a = 12;
public static void main(String[] args) {
// 引用成员变量
LombokTest lt = new LombokTest();
Converter<Integer,String> s = (param) -> System.out.println((param + lt.a));
s.convert(8);
// 引用局部变量
String separator = ",";
Arrays.asList("a","b","c").forEach((String e) -> System.out.print(e+separator));
// 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "1";
Comparator<String> comparator = (first,second)
-> Integer.compare(first.length(),second.length()); // idea编译报错Variable 'first' is already defined in the scope
// Lambda中this的应用
lt.sayOk();
}
public String toString(){
return "\n are u ok?";
}
private void sayOk(){
Runnable runnable = () ->{
System.out.println(this.toString());
};
new Thread(runnable).start();
}
private interface Converter<T1,T2>{
void convert(int i);
default void say() {
}
}
}
注释掉报错代码后,控制台打印
20
a,b,c,
are u ok?
==NOTE==
- 和匿名对象有点不同的是,Lambda表达式当中引用的变量可以不用声明为final
- Lambda表达式的字段类型为final,所以在后面的代码中不能被修改。如例子中
String separator = ",";
后续代码中如果修改为separator = "。";
则会被编译器报错 - Lambda表达式当中不允许声明一个与局部变量重名的参数或者局部变量。
- 和局部变量不同的是,Lambda内部对于实例的字段(即:成员变量)以及静态变量是即可读又可写。
- Lambda表达式中无法访问到接口的默认方法(java 8新特性,下面讲到)。如上
Converter
接口的say()
是无法被访问的。 - Lambda中的this会去引用创建该Lambda表达式的方法的this,如上
are u ok?
接口的默认方法与静态方法
接口的默认方法与静态方法是java 8的新概念,用来扩展接口的声明。与传统接口不同,它可以在接口中添加方法。另外它并不受函数式接口(带有注解@FunctionalInterface)的契约,可以任意使用。
默认方法
默认方法用default
关键词修饰,可以在接口中存在多个。与接口抽象方法不同,它是一个非抽象的方法实现。它的使用方式有点类似于抽象类中的非抽象成员方法。这个特征也叫扩展方法
==Warning:==
在实现默认方法时,它是不能够重写Object中的方法,但是可以重载Object 中的方法。例如toString
、hashCode
等方法不能被覆盖,但是却可以被重载调用。
interface DefaultM{
default String myDefaultMothod(){
return "https://code666.top";
}
// 因为重写了Object方法,以下就会报错
default String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
static class DefaultMM implements DefaultM{
public void test(){
System.out.println(myDefaultMothod());
}
}
public static void main(String[] args) {
DefaultMM dmm = new DefaultMM();
dmm.test();
}
静态方法
静态方法用static
修饰,可以通过接口名调用。
interface DefaultM2{
static DefaultM create(Supplier<DefaultM> supplier){
return supplier.get();
}
}
static class DefaultMM implements DefaultM{
public void test(){
System.out.println(myDefaultMothod());
}
}
public static void main(String[] args) {
DefaultM defaultM = DefaultM2.create(DefaultMM::new);
System.out.println(defaultM.myDefaultMothod());
}
总结
为什么接口的默认方法不能重载toString(),hashCode()和equals()?
接口不能提供对Object类的任何方法的默认实现,因为若可以会很难确定什么时候该调用接口默认的方法。并且也是毫无意义的。因为每一个java类都是Object的子类,也都继承了它类中的equals等方法。
默认方法和静态方法的注意点
- 接口默认方法、静态方法可以有多个。
- 默认方法通过实例调用,静态方法通过接口名调用。
- default默认方法关键字只能用在接口中。
- 默认方法可以被继承,如果继承了多个接口,多个接口都定义了多个同样的默认方法,实现类需要重写默认方法不然会报错。
- 静态方法不能被继承及覆盖,所以只被具体所在的接口调用
函数式接口@FunctionalInterface
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。
定义如下
@FunctionalInterface
public interface Functional<T>{
void test(T t);
}
==Warnning:==
- 该注解只能标记在有且只有抽象方法的接口上。若接口有两个抽象方法则编译器会报错(上面提到的
默认方法
和静态方法
都不是抽象方法)。 - 因为接口也是默认继承java.lang.Object的,所以如果重写了Object内的方法则也不属于抽象方法。
- 并且该注解不是必须的。如果一个接口符合‘函数式接口’规范,那加不加该注解都不影响,但是加了该注解后编译器会校验。(==也就是说如果该接口是为了函数式接口而生的,就可以加上此注解==)
实例
接口还是上面那个。PS初级开发: static <T> void
中<T>
不是返回值,表示传入参数有泛型
public class FunctionalTest {
private static <T> void Test(Functional<T> fun, T t){
System.out.print("https://");
fun.test(t);
System.out.print(".top");
}
public static void main(String[] args) {
String s = "code666";
Test(System.out::print,s);
}
}
输出 https://code666.top
通过输入和输出的参数来区分,函数式接口有如下四类
接口名 | 说明 | 接口方法 |
---|---|---|
Function<T,R> | 接收一个T类型的参数,返回一个R类型的结果 | R apply(T t) |
Consumer | 接收一个T类型的参数,不返回结果 | void accept(T t) |
Predicate | 接收一个T类型的参数,返回一个boolean类型的结果 | boolean test(T t) |
Supplier | 不接收参数,返回一个T类型的结果 | T get() |
接收两个参数的相关函数式接口
接口名 | 说明 |
---|---|
BiFunction<T,U,R> | 接收T类型U类型两参数,返回一个R类型结果 |
BiConsumer<T,U> | 接收T类型U类型两参数不返回值 |
BiPredicate<T,U> | 接收T类型和U类型的两个参数,返回一个boolean类型的结果 |