Lambda的官方文档链接https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
在学习Lambda表达式之前,我们要先知道两个概念:内部类 匿名类
一、内部类
1.1为什么要用内部类?
在《Think in java》中有这样一句话:使用内部类最吸引人的原因是“每个内部类都能独立地继承一个接口的实现,所以无论外围类是否已经继承了某个接口的实现,对于内部类没有影响”
所以如果我们需要的某个类,想要继承多个接口,实现不同的功能,那么可以使用内部类来实现。
(当然,如果是把接口写成多个父类的话,当前类是可以继承多个类的,但是这就违背了为什么在有类的时候我们还需要引入接口的初衷了)
现在是2021-9-21,关于国家的双减政策刚出来不久,我们就举个教育机构的例子吧。
public interface ChineseTeacher{
void taechChinese();
}
public interface MathTeacher{
void teachMath();
}
public interface EnglishTeacher{
void teachEnglish();
}
public class SunEducation implements ChineseTeacher{
private final static String name = "Sun";
public static String getName() {
return name;
}
@Override
public void teachChinese() {
System.out.println(getName() + " has Chinese teachers ");
}
class TeachMath implements MathTeacher{
@Override
public void teachMath() {
System.out.println(getName() + " has Math teachers ");
}
}
class TeachEnglish implements EnglishTeacher{
@Override
public void teachEnglish() {
System.out.println(getName() + " has English teachers ");
}
}
public void DistributeTeacher(boolean chineseIsNeeded, boolean mathIsNeeded, boolean englishIsNeeded){
if (chineseIsNeeded){
teachChinese();
}
if (mathIsNeeded){
TeachMath teachMath = new TeachMath();
teachMath.teachMath();
}
if (englishIsNeeded){
TeachEnglish teachEnglish = new TeachEnglish();
teachEnglish.teachEnglish();
}
}
public static void main(String[] args) {
SunEducation sunEducation = new SunEducation();
sunEducation.DistributeTeacher(true, true, true);
}
}
Sun has Chinese teachers
Sun has Math teachers
Sun has English teachers
1.2内部类的基础操作
既然都叫内部类了,那内部类和外围类(SunEducation)那就是一家人了
- 在外围类的方法中,可以实例化内部类,并调用其中我们需要的方法。
- 内部类可以无限制的调用外围类的属性以及方法,例子中的getName()
- 内部类只给自己和外围类使用,对于其他类,有很好的封装性
二、匿名类
我们看上述的代码,在内部申明一个类,然后实例化这个类,去实现某些功能,是不是觉得这个代码有点冗余了?那么我们就会想有没有更简单的方法呢?那么匿名类就来了
顾名思义,匿名类(anonymous class),没有名字的类,这种类一般使用在当某个类我们只需要使用一次的情况下,也就是只需要实例化一个对象的时候,那么匿名类就可以将类的实现和实例化一次性做完。
当然这个的要求是有某个接口或者抽象类需要我们去实现,这样我们才能去实现匿名类。
Anonymous classes enable you to make your code more concise.They enable you to declare and instantiate a class at the same time.
所以按照匿名类我们其实可以可以把上面的SunEducation简化成
public class SunEducation implements ChineseTeacher{
private final static String name = "Sun";
public static String getName() {
return name;
}
@Override
public void teachChinese() {
System.out.println(getName() + " has Chinese teachers ");
}
MathTeacher mathTeacher = new MathTeacher() {
@Override
public void teachMath() {
System.out.println(getName() + " has Math teachers ");
}
};
EnglishTeacher englishTeacher = new EnglishTeacher() {
@Override
public void teachEnglish() {
System.out.println(getName() + " has English teachers ");
}
};
public void DistributeTeacher(boolean chineseIsNeeded, boolean mathIsNeeded, boolean englishIsNeeded){
if (chineseIsNeeded){
teachChinese();
}
if (mathIsNeeded){
mathTeacher.teachMath();
}
if (englishIsNeeded){
englishTeacher.teachEnglish();
}
}
public static void main(String[] args) {
SunEducation sunEducation = new SunEducation();
sunEducation.DistributeTeacher(true, true, true);
}
}
其中,下面两个为接口的实现以及实例化,当然我们还可以在{}里面实现其他我们需要的方法,并且可以直接通过对象调用。
MathTeacher mathTeacher = new MathTeacher() {
@Override
public void teachMath() {
System.out.println(getName() + " has Math teachers ");
}
};
EnglishTeacher englishTeacher = new EnglishTeacher() {
@Override
public void teachEnglish() {
System.out.println(getName() + " has English teachers ");
}
};
在Java中匿名类也经常被使用,比如多线程中的Thread类
//因为Runnable是一个接口,所以这里其实就是用了匿名类的便利来让我们使得创建线程这个步骤的代码更加简单
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread t running");
}
});
匿名类一方面可以将类的申明和实例化在我们只用这个类一次的场景中统一为一次实例化操作的代码,一方面我们也可以对接口进行方法的增加,实现我们需要的功能。
如果我们这个接口中只有一个方法呢,并且我们的类也只需要这一个方法,那么这个时候有没有办法把匿名类再简单一点?
所以即便匿名类已经足够简单了,但是针对这种情况Java推出了更简单的Lambda表达式。
三、Lambda表达式
3.1Lambda表达式
3.1.1包含内容
一个Lmabda表达式包含:() -> { function}
- 括号里的参数,可以省略数据类型,另外如果只有一个参数,可以省略括号
- 箭头令牌
- 主体,正常情况下我们正常的写Java的函数就行了,如果有比较简单的表达式,比如我们只需要输出,那么就直接
()-> System.out.println()
就可以了
3.1.2Lambda表达式是什么?
我们把Lambda和内部类以及匿名类放一起,那么很明显,肯定是跟他们是有关系的,因为,Lambda其实就是更简单的匿名类,具体的说,就是某个类需要实现的接口只有一个方法,并且这个类的实例化对象只有一个的情况下。
匿名类是定义函数和实例化一起,Lambda也是定义函数以及实例化一起进行的,只是这个定义比匿名类的要求更高,因为Lambda只有->
后面的一处申明,那么我最多只能写一个函数,所以接口的方法只能有一个。
那么最重要的就是,我们在() -> { function}
返回的其实就是一个对象。
所以Lambda其实就是一个对象,只是这个对象的方法我们可以在创建的时候取重写他。
3.1.3Lambda表达式怎么用?
还是按照上面的例子,我们用Lambda表达式再写一遍
public class SunEducation implements ChineseTeacher{
private final static String name = "Sun";
public static String getName() {
return name;
}
@Override
public void teachChinese() {
System.out.println(getName() + " has Chinese teachers ");
}
public void DistributeTeacher(boolean chineseIsNeeded, boolean mathIsNeeded, boolean englishIsNeeded, MathTeacher mathTeacher, EnglishTeacher englishTeacher){
if (chineseIsNeeded){
teachChinese();
}
if (mathIsNeeded){
mathTeacher.teachMath();
}
if (englishIsNeeded){
englishTeacher.teachEnglish();
}
}
public static void main(String[] args) {
SunEducation sunEducation = new SunEducation();
sunEducation.DistributeTeacher(true, true, true,
() -> {
System.out.println("test Lambda ");
System.out.println(getName() + " has Math teachers");
},
() -> System.out.println(getName() + " has English teachers"));
}
}
Sun has Chinese teachers
test Lambda
Sun has Math teachers
Sun has English teachers
四、Example:实现Comparator
在Java中自定义排序方法是一个最常用的函数,而要实现自定义排序函数其实就是实现Comparator接口的过程。
正常使用内部匿名类的自定义排序:
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
});
我们可以看到,对于Collections.sort这个函数,一个是list参数students,一个是参数实例化Comparator对象,所以正常使用匿名类,做法如上。
使用Lambda表达式的自定义排序:
如果学会了Lambda表达式的话,这里应该很容易改了
Collections.sort(students, (Student s1, Student s2) -> {return s1.age -s2.age;});
如果大家用的是IDEA的话,会发现,写了这个表达式,在IDEA中会有这样的提示Can be replaced with Comparator.comparingInt
,如果我们使用了的话就变成了
Collections.sort(students, Comparator.comparingInt((Student s) -> s.age));
看看用匿名类和用Lambda表达式的方法,明显让代码简化了很多,这就是Lambda表达式的目的。
五、Lambda表达式的其他应用
5.1List的循环输出
像上面的排序排完后,我们就可以用下面的输出
students.forEach(student -> System.out.println(student.age));
5.2数组的循环输出
对于数组的话就需要额外的加一个操作,就是把数组转成stream或者List再去foreach
Arrays.stream(students1).forEach(System.out::println);
Arrays.asList(students1).forEach(System.out::println);
这里System.out::println
和student -> System.out.println(student.age)
是一个意思,当然如果这里转stream或者转List再输出的话,在效率上其实就低了一些,毕竟要多一个转化的过程。
六、总结
所以Lambda表达式的使用其实最常见的就是两种,一种是对内部类实例化的优化,一种是对循环输出的代码的简化。
目前还每学到Lambda表达式是否有性能上的优化,等学到了,如果有优化的话再来更新,但至少,对于数组转list再用lambda的话,这个我觉得不是很适用,毕竟性能这一块还是代码比较看重的,不能为了好看把最基本的放弃了。
在一开始学习前,一直把Lambda表达式和函数编程搞不明白,然后写文章的时候又看了下两者的关系,其实更多的是包含关系,那么我们看看Java中的函数编程是怎么样的
1.流的转换,创建
在Java中要想函数式编程,对于任意数据结构都需要变成流式结构,然后才能对想用的一些函数编程的函数进行使用。常用的流式转换,先用数组的转化举个例子,.
后面的都是直接提供的一些操作,如果指定Scala的同学会熟悉一点,只是在Java中也有
Arrays.stream(array).peek(e -> xx).filter(e -> xx)
- 数组转流
Arrays.stream()
, - 集合类型转流
Collection.stream()
- 任意类型对象转流
Stream.Of()
2.常用的操作符
- fliter
- map
- peek
- findAny
- findFirst
- sorted
- flatMap
所以我们可以看到,在Java中如果想用Lambda表达式让代码更好看的话,也就是说向走函数编程那一套的话,性能或多或少会有一定的损失,所以选择要看具体情况,但好消息是数组转流时间花费不是很多,在影响不大的情况下可以酌情使用。