8、lambda表达式和内部类
重点:
8.1、使用lambda表达式实现接口
8.2、lambda表达式的基础语法
8.3、lambda表达式的语法进阶
次重点:
8.4、lambda表达式的函数引用
8.1、lambda表达式简介
8.1.1、lambda表达式的简介
lambda表达式jdk1.8之后推出的新特性。本质上来说,lambda表达式就是一个匿名函数。可以使用lambda表达式简洁的实现接口中的方法。
8.1.2、lambda的小案例
interface Calculate {
int calculate(int a, int b);
}
class Addition implements Calculate {
@Override
public int calculate(int a, int b) {
return a + b;
}
}
class Program {
public static void main(String[] args) {
// 给接口类型的引用进行赋值,右侧需要是这个接口的实现类类型对象。
Calculate calculate = new Addition();
int result = calculate.calculate(10, 20);
// 如果使用lambda表达式来完成同样的操作
Calculate calculate2 = (a, b) -> a + b;
int result2 = calculate2.calculate(10, 20);
}
}
8.1.3、lambda表达式对接口的要求
并不是所有的接口都可以使用lambda表达式来实线。lambda表达式毕竟只是一个匿名方法,如果接口中只有一个方法必须要实现,则可以使用lambda表达式;但是如果接口中必须要实现的方法比较多。
lambda表达式只能对 函数式接口 进行简洁的实现。
8.2、lambda表达式的基础语法
lambda表达式,从本质来讲,就是一个匿名方法。一个方法的组成,有 返回值、方法名、参数、方法体 部分。
而lambda表达式是匿名方法,所以,方法名可以忽略。另外,在lambda表达式中,返回值类型也可以忽略。
因此,对于lambda表达式只需要关注两点即可:
- 参数 : 以()括起来,将参数写入到()中。
- 方法体 : 以 {} 括起来,将方法体写入到 {} 中。
对于lambda表达式,还需要记住一个新的语法: ->
- lambda运算符 -> : 可以分隔参数和方法体。
// 说明需要实现一个无参、无返回值的方法
() -> { System.out.println("hello world"); }
// 说明需要实现一个 (int, int) 参数的、int返回值的方法
(int a, int b) -> { return a + b; }
// 无参无返回
NoneParameterNoneReturn lambda1 = () -> {
System.out.println("hello world");
};
lambda1.test(); // 最终的实现,是lambda表达式中的实现
// 多个参数、有返回
MutipleParameterSingleReturn lambda2 = (int x, int y) -> {
return x + y;
};
int ret2 = lambda2.test(10, 20);
System.out.println(ret2);
8.3、lambda表达式的语法进阶
lambda表达式是为了简洁的实现接口的,所以某些部分是可以省略的。
8.3.1、参数的精简
1、参数的类型,已经在接口的方法中有明确规定了,因此在lambda表达式中,可以省略参数的类型。
注意:如果要省略形参的类型,则每一个形参都必须省略。不能出现有的形参带类型,有的没有。
MutipleParameterNoneReturn lambda1 = (a, b) -> {
System.out.println("a = " + a + ", b = " + b);
};
lambda1.test(10, 20);
2、如果参数列表中,形参的数量只有一个,此时可以省略小括号。
SingleParameterNoneReturn lambda2 = a -> {
System.out.println("a = " + a);
};
lambda2.test(10);
8.3.2、方法体的精简
1、如果方法体中只有一句语句,则大括号可以省略。
SingleParameterNoneReturn lambda2 = a -> System.out.println("a = " + a);
lambda2.test(10);
2、如果方法中唯一的一条语句是返回语句,则大括号省略的同时,return也必须省略。
MutipleParameterSingleReturn lambda3 = (a, b) -> a + b;
int result = lambda3.test(10, 30);
System.out.println(result);
8.4、lambda表达式的函数引用
推理:
lambda表达式可以简洁的实现一个接口,但是,一般情况下,在lambda表达式中的都是简单的逻辑。如果需要处理的逻辑比较复杂,不推荐将复杂的逻辑直接的写到lambda表达式中。
如果需要在lambda表达式中进行的复杂的逻辑处理,已经在其他的方法中实现过了,可以直接调用这个方法即可。或者可以使用函数引用,将引用的函数,实现接口中的方法。
public class CSyntax {
public static void main(String[] args) {
// 需求:
// 计算a和b的最大公约数
// 这是个函数引用,使用calculate方法实现接口中的方法
MutipleParameterSingleReturn lambda = CSyntax::calculate;
int result = lambda.test(12, 8);
System.out.println(result);
}
public static int calculate(int a, int b) {
// 1、找出最小值
int min = a > b ? b : a;
// 2、从最小值开始,向下递减直到1,找到一个能够同时整除a和b的数字
for (int i = min; i >= 1; i--) {
if (a % i == 0 && b % i == 0) {
return i;
}
}
return -1;
}
}
构造方法的引用
如果接口中的方法,需要返回一个对象,则此时,可以引用构造方法。
public class DSyntax {
public static void main(String[] args) {
/*
GetPerson lambda = () -> new Person();
Person xiaoming = lambda.get();
System.out.println(xiaoming);
*/
// 引用的构造方法
GetPerson lambda2 = Person::new;
System.out.println(lambda2.get("xiaoming", 10));
}
}
interface GetPerson {
Person get(String name, int age);
}
class Person {
String name;
int age;
public Person() {
super();
System.out.println("Person的无参构造方法执行了");
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
System.out.println("Person的有参构造方法执行了");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
8.5、系统内置的函数式接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VqYslWlU-1571049674888)(./images/functional.png)]
8.6、闭包
1、在lambda表达式中,使用到的局部变量,必须是常量。
public static void main(String[] args) {
int a = 10;
NoneParameterNoneReturn lambda = () -> {
// 此时,这里会有问题,因为在lambda表达式中是用到了局部变量a,则这个变量默认就是常量。
a = 20;
};
}
2、如果在一个lambda表达式中引用到了某一个局部变量,此时,lambda表达式会提升这个局部变量的生命周期,直到lambda表达式也执行结束。
public class EClosure {
public static void main(String[] args) {
NoneParameterNoneReturn ret = test();
ret.test();
}
private static NoneParameterNoneReturn test() {
// 定义一个局部变量
int a = 10;
NoneParameterNoneReturn lambda = () -> {
System.out.println(a);
};
return lambda;
}
}
8.7、内部类
定义在一个类内部,或者是定义在一个方法内部的类,叫做内部类。
8.7.1、成员内部类(了解)
写在一个类的内部,与属性、方法平级的类。并且这样的类,没有使用static修饰。这种类,可以看做是外部类的一个成员。
-
成员内部类的访问权限可以是:任意权限。
-
实例化的时候,需要借助外部类对象来完成
// 1、实例化外部类对象 Outter outter = new Outter(); // 2、实例化内部类对象 Outter.Inner inner = outter.new Inner();
-
编译后会生成 外部类$内部类.class 文件
8.7.2、静态内部类(了解)
写在一个类的内部,与属性、方法平级的类。并且这样的类,用static修饰。这样的类,叫做静态内部类。
-
静态内部类的访问权限可以是:任意权限。
-
实例化的时候,直接实例化即可,但是需要用外部类.内部类的形式来访问,或者导包。
Outter.StaticInner inner2 = new Outter.StaticInner();
-
编译后会生成 外部类$内部类.class 文件
8.7.3、局部内部类(了解)
写在一个代码段中,与局部变量相似。这个类仅在这个代码段中生效。
- 局部内部类访问权限:没有,局部内部类是不能加访问权限修饰符的,仅在当前代码段中生效。
- 实例化的时候,与外部类的形式一模一样。
- 编译后会生成 外部类$序号内部类.class 文件
8.7.4、匿名内部类
// 实例化一个狗的对象
Dog dog = new Dog() {
// 这里,就是一个匿名内部类
// 上方的new,实例化的是一个匿名内部类的对象
// 这个匿名内部类和Dog的关系:继承关系。这个匿名内部类,继承自Dog类。
};
在匿名内部类中,一般不去添加新的成员,因为即便添加了,向上转型后的对象也不能访问。在匿名内部类中,常常是对父类的某些方法进行重写。
匿名内部类,常常和抽象类、接口配合使用
配合抽象类使用:
Animal animal = new Animal() {
@Override
public void walk() {
System.out.println("匿名子类用蹦的");
}
};
animal.walk();
配合接口使用:
MutipleParameterSingleReturn obj = new MutipleParameterSingleReturn() {
@Override
public int test(int a, int b) {
return 0;
}
};
- 配合抽象类使用,实例化的是抽象类的匿名子类对象
- 配合接口使用,实例化的是接口的匿名实现类对象
- 匿名类的对象实例化完成后,会进行向上转型。
Animal animal = new Animal() {
@Override
public void walk() {
System.out.println(“匿名子类用蹦的”);
}
};
animal.walk();
配合接口使用:
```java
MutipleParameterSingleReturn obj = new MutipleParameterSingleReturn() {
@Override
public int test(int a, int b) {
return 0;
}
};
- 配合抽象类使用,实例化的是抽象类的匿名子类对象
- 配合接口使用,实例化的是接口的匿名实现类对象
- 匿名类的对象实例化完成后,会进行向上转型。