面向对象(四)
《疯狂Java讲义》学习笔记
Java8新增的 Lambda 表达式
Lambda
表达式允许使用代码块作为方法的参数,Lambda
表达式使用更简洁的代码来创建只有一个抽象方法的接口(这个接口被称为函数式接口
)
Lambda表达式入门
public class TestLambda {
public static void main(String[] args) {
CommandTest commandTest = new CommandTest();
commandTest.test();
//输出:使用匿名内部类:数组元素的和是:9
//输出:使用Lambda:数组元素的和是:9
}
}
//使用匿名内部类的情况:
//Command接口
interface Command {
void process(int[] target);
}
class ProcessArray {
public void process(int[] target, Command cmd) {
cmd.process(target);
}
}
class CommandTest {
public void test() {
ProcessArray pa = new ProcessArray();
int[] target = {3, -4, 6, 4};
// 使用匿名内部类的情况:
pa.process(target, new Command() {
@Override
public void process(int[] target) {
int sum = 0;
for (int temp : target) {
sum += temp;
}
System.out.println("使用匿名内部类:数组元素的和是:" + sum);
}
});
// 使用Lambda的情况:
pa.process(target, (int[] array) -> {
int sum = 0;
for (int temp : target) {
sum += temp;
}
System.out.println("使用Lambda:数组元素的和是:" + sum);
});
}
}
- 从上面的代码中可以看出使用
Lambda
与创建匿名内部类时需要实现的process(int[] target)
方法完全相同,只是不需要new Xxx(){}
这种繁琐的代码,并不需要指定重写的方法的名字,也不需要给出重写的方法的返回值类型——只要给出重写方法的方法括号,以及括号中的形参列表即可 - 当使用
Lambda
表达式代替匿名内部类创建对象时,Lambda
表达式的代码块将会代替实现抽象方法的方法体,Lambda
相当于一个匿名方法 Lambda
表达式由3部分组成:
①、形参列表:形参列表允许省略形参类型,如果参数列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
②、箭头(->
):必须通过英文中划线号和大于符号组成。
③、代码块:如果代码块中只包含一条语句,Lambda
允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束。Lambda
代码块中只有一条return
语句,甚至可以省略return
关键字。Lambda
表达式需要返回值,而它的代码块中仅有一条省略了return
的语句,Lambda
表达式会自动的返回这条语句
public class Test {
public static void main(String[] args) {
new TestLambda().test();
}
}
//Lambda表达式
interface Eatable {
void taste();
}
interface Flyable {
void fly(String weather);
}
interface Addable {
int add(int a, int b);
}
class TestLambda {
// 该方法的需要 Eatable 对象
public void eat(Eatable e) {
e.taste();
}
// 该方法的需要 Flyable 对象
public void drive(Flyable f) {
System.out.println("我正在驾驶" + f);
f.fly("天气很好");
}
// 该方法需要 Addable 对象
public int testAdd(Addable add) {
return add.add(1, 2);
}
public void test() {
TestLambda lambda = new TestLambda();
// Lambda 表达式只有一条语句时,可以省略花括号
lambda.eat(() -> System.out.println("苹果的味道真不错!!"));
lambda.drive(weather -> System.out.println(weather));
int result = lambda.testAdd((a, b) -> a + b);
System.out.println(result);
}
}
调用上面三个方法依次需要传入的参数为
Eatable
对象 、Flyable
对象 、Addable
对象 ,但却都传入了Lambda
表达式,这说明Lambda
表达式会被当做“任意类型”
的对象,到底需要何种类型的对象,这取决于运行环境的需要。
实现接口、使用匿名内部类、使用 Lambda
表达式的对比
public class Main {
public static void main(String[] args) {
LambdaTest test = new LambdaTest();
// 使用实现类调用eat方法
EatImpl eatImpl = new EatImpl();
test.eat(eatImpl);
// 使用匿名内部类,重写eat方法
test.eat(new Eatable() {
@Override
public void eat() {
System.out.println("吃饭");
}
});
// 使用Lambda
test.eat(() -> {
System.out.println("吃饭");
});
}
}
interface Eatable {
void eat();
}
class LambdaTest {
//调用接口中的eat方法
public void eat(Eatable e) {
e.eat();
}
}
//实现Eatable接口
class EatImpl implements Eatable {
//重写eat方法
@Override
public void eat() {
System.out.println("吃饭");
}
}
Lambda
表达式与函数式接口
Lambda
表达式被称作目标类型
,Lambda
表达式的目标类型必须为函数式接口
。“函数式接口”代表只含有一个抽象方法的接口。函数式接口可以包含多个默认方法,类方法;
如果采用匿名内部类语法创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下即可采用Lambda
表达式来创建对象;该表达式创建的对象目标类型就是这个函数式接口。
//检查一个接口是否是函数式接口
@FunctionalInterface
interface Flyable{
void fly();
}
//由于 Lambda 表达式的结果就是被当做对象,因此程序中完全可以使用 Lambda 表达式进行赋值
public class Test {
public static void main(String[] args) {
Runnable r = () -> {
for (int i = 0; i < 100; i++) {
System.out.println();
}
};
}
}
- Lambda实现的是匿名方法,他只能实现特定函数式接口中的唯一方法。
因此Lambda有两个限制:
①、Lambda
表达式的目标类型必须是明确的函数式接口;
②、Lambda
表达式只能为函数式接口创建对象。Lambda
表达式只能实现一个抽象方法,因此它只能为只有一个抽象方法的接口(函数式接口)创建对象
//编译出错
// Target type of a lambda conversion must be an interface
Object obj = () -> {
for (int i = 0; i < 100; i++) {
System.out.println();
}
};
- 为保证
Lambda
表达式的目标类型是一个明确的函数式接口,可以使用如下的三种方式:
①、将Lambda
表达式赋值给函数式接口类型的变量;
②、将Lambda
表达式做为函数式接口类型的参数传给某个方法;
③、使用函数是借口对Lambda
表达式进行强制类型转换;
//把下面的 Lambda 表达式进行强制类型转换,这样就确定了目标类型为 Runnable 函数式接口;
Object obj = (Runnable) () -> {
for (int i = 0; i < 100; i++) {
System.out.println();
}
};
方法引用与构造器引用
如果
Lambda
表达式的代码块中只有一条代码,程序就可以省略Lambda
表达式中代码块的花括号。
不仅如此,如果Lambda
表达式的代码块只有一条代码,还可以在代码块中使用方法引用和构造器引用。
方法引用和构造器引用都需要使用两个英文冒号。
Lambda
表达式支持如下的几种引用方式
Lambda
表达式支持如下的几种引用方式
种类 | 示例 | 说明 | 对应的 Lambda 表达式 |
---|---|---|---|
引用类方法 | 类名::类方法 | 函数式接口中被实现方法的全部参数传给该类方法作为参数 | (a,b,…) -> 类名 . 类方法(a,b…) |
引用特定对象的实例方法 | 特定对象::实例方法 | 函数式接口中被实现方法的全部参数传给该方法作为参数 | (a,b,…) -> 特定对象 . 实例方法(a,b…) |
引用某类对象的实例方法 | 类名::实例方法 | 函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数 | (a,b,…) -> a . 实例方法(b…) |
引用构造器 | 类名::new | 函数式接口中被实现方法的全部参数传给该构造器作为参数 | (a,b,…) -> new 类名 (a,b…) |
- ①、引用类方法,格式:类名::类方法
public class MethodRefer {
public static void main(String[] args) {
// 使用 Lambda 表达式
// 子类类型赋给父类变量
Converter c1 = from -> Integer.valueOf(from);
// 父类变量调用convert方法
System.out.println(c1.convert("123") + 1);
// 方法引用代替 Lambda 表达式:引用类方法
Converter c2 = Integer::valueOf;
System.out.println(c2.convert("123") + 1);
}
}
//接口
@FunctionalInterface
interface Converter {
// 把字符串转换成Integer类型
Integer convert(String from);
}
方法引用代替
Lambda
表达式,引用类方法
函数式接口中被实现的方法的全部参数传给该方法作为参数
Converter c2 = Integer::valueOf;
- ②、引用特定对象的实例方法
public class MethodRefer {
public static void main(String[] args) {
// 使用 Lambda 表达式
// 子类类型赋给父类变量
Converter c1 = from -> "fkit.java".indexOf("it");
// 父类变量调用convert方法
System.out.println(c1.convert("it"));
// 方法引用代替 Lambda 表达式:引用特定对象实例的方法
Converter c2 = "fkit.java"::indexOf;
System.out.println(c2.convert("it"));
}
}
//接口
@FunctionalInterface
interface Converter {
// 把字符串转换成Integer类型
Integer convert(String from);
}
- ③、引用某类对象实例的方法
//MyTest接口
@FunctionalInterface
interface MyTest {
String test(String a, int b, int c);
}
public class MethodRefer {
public static void main(String[] args) {
//使用lambda表达式
MyTest myTest = (a, b, c) -> a.substring(b, c);
System.out.println(myTest.test("mytest", 0, 2));
//引用某类对象实例的代替 Lambda 表达式:引用某类对象实例的方法
myTest = String::substring;
System.out.println(myTest.test("mytest", 0, 2));
}
}
- ④、引用构造器
interface YourTest {
JFrame win(String title);
}
public class TestLambda {
public static void main(String[] args) {
//使用Lambda 表达式创建YourTest 对象
YourTest yt = a -> new JFrame(a);
JFrame jFrame = yt.win("哈哈哈");
System.out.println(jFrame);
//使用构造器引用代替 Lambda
YourTest yt2 = JFrame::new;
JFrame hhh = yt2.win("hhh");
System.out.println(hhh);
}
}
对于上面的构造器引用,也就是调用某个
Jframe
类的构造器来实现YourTest
函数式接口中唯一的抽象方法,当调用YourTest
函数式接口中唯一的抽象方法时,调用参数会传给JFrame
构造器。
Lambda 表达式与匿名内部类的区别和联系
Lambda表达式是匿名内部类的一种简化,因此他部分取代匿名内部类的作用,Lambda 表达式与匿名内部类存在如下相同点;
①、Lambda 表达式和匿名内部类一样,都可以访问 Effectively final 的局部变量,以及外部类的成员变量(包括类变量和实例变量)
②、Lambda 表达式和匿名内部类生成的对象一样,都可以调用从接口中继承的默认方法
package testlambda.lambdaandinnerclass;
//接口
interface Displayable {
// 定义一个抽象方法和默认方法
void display();
default int add(int a, int b) {
return a + b;
}
}
public class LambdaAndInner {
private int age;
private static String name = "疯狂软件教育中心";
public void testInnerClass() {
String book = "疯狂Java讲义";
//使用匿名内部类
Displayable displayable = new Displayable() {
@Override
public void display() {
//访问 "Effectively final" 的局部变量
System.out.println("book的局部变量:" + book);
//访问外部类的实例变量和类变量
System.out.println("外部类age实例变量为:" + age);
System.out.println("外部类name 类变量为:" + name);
}
};
displayable.display();
System.out.println(displayable.add(2, 3));
}
public void testLambda() {
String book = "疯狂Java讲义";
//使用 Lambda 表达式
Displayable displayable = () -> {
//访问 "Effectively final" 的局部变量
System.out.println("book的局部变量:" + book);
//访问外部类的实例变量和类变量
System.out.println("外部类age实例变量为:" + age);
System.out.println("外部类name 类变量为:" + name);
};
displayable.display();
System.out.println(displayable.add(2, 3));
}
public static void main(String[] args) {
LambdaAndInner lambdaAndInner = new LambdaAndInner();
lambdaAndInner.age = 10;
lambdaAndInner.testInnerClass();
lambdaAndInner.testLambda();
}
}
- 上面程序分别使用匿名内部类和
Lambda
表达式创建了Displayable
对象,由此可以得到,Lambda
表达式不仅可以访问“Effectively final
”局部变量,也可以访问类变量和实例变量。 - 与匿名内部类相似的是,
Lambda
表达式访问的局部变量book
之后,就不允许再次修改book
的值了; Lambda
表达式与匿名内部类存在如下区别
①、匿名内部类可以为任意接口创建实例,不管接口中存在多少个抽象方法,只要匿名内部类全部实现接口中的抽象方法即可;但Lambda
只可以为函数式接口创建实例
②、匿名内部类可以为抽象类或者普通类创建实例;Lambda只能为函数是接口创建实例
③、匿名内部类实现的抽象方法的方法体中调用接口中定义的默认方法;但Lambda
表达式的代码块不允许调用接口中的默认方法
//在匿名内部类调用接口的默认方法
public void testInnerDefault() {
Displayable displayable = new Displayable() {
@Override
public void display() {
//调用上面 Displayable 接口的默认方法:add(int a, int b)
System.out.println(add(2, 3));
}
};
displayable.display();
}
//使用 Lambda 表达式调用接口中的默认方法
public void testLambda() {
String book = "疯狂Java讲义";
//使用 Lambda 表达式
Displayable displayable = () -> {
//编译出错
//No candidates found for method call add(2,3).
System.out.println(add(2,3));
};
displayable.display();
}
- 使用 Lambda 表达式调用Arrays的类方法
public static void main(String[] args) {
String[] arr1 = new String[]{"java", "fkava", "fkit", "ios", "android"};
Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
int[] arr2 = {3, -4, 25, 16, 30, 18};
Arrays.parallelPrefix(arr2, (left, right) -> left * right);
}