我们都知道 Lambda 是一个匿名函数
,使用Lambda可以帮我们简化匿名内部类
的使用,而匿名内部类在实际开发当中并不怎么用,很多人对他的印象其实并不深,所以我们有必要先了解清楚匿名内部类。
什么是匿名内部类。
匿名内部类,就是没有名字的一种嵌套类。它是Java对类的定义方式之一。
为什么要使用匿名内部类?
在实际开发中,我们常常遇到这样的情况:一个接口/类的方法的某个实现方式在程序中
只会执行一次
,但为了使用它,我们需要创建它的实现类/子类去实现/重写
。此时可以使用匿名内部类的方式,可以无需创建新的类,减少代码冗余。
匿名内部类只可以使用在接口上吗
不是的。匿名内部类可以用在具体类、抽象类、接口上,且对方法个数没有要求。
下面详细说明一下
假设当前有一个接口,接口中只有一个方法:
public interface Interface01 {
void show();
}
为了使用该接口的show方法,我们需要去创建一个实现类,同时书写show方法的具体实现方式
public class Interface01Impl implements Interface01{
@Override
public void show() {
System.out.println("I'm a impl class...");
}
}
如果实现类Interface01Impl全程只使用一次,那么为了这一次的使用去创建一个类,未免太过麻烦。我们需要一个方式来帮助我们摆脱这个困境。匿名内部类则可以很好的解决这个问题。
我们使用匿名内部类
public static void main(String[] args) {
Interface01 interface01 = new Interface01() {
@Override
public void show() {
System.out.println("这里使用了匿名内部类");
}
};
//调用接口方法
interface01.show();
}
注意:匿名内部类不能有普通的静态变量声明,只能有静态常量。
通常,我们也习惯用匿名内部类
的方式创建并启动线程
通过下面示例会发现:Thread是个普通类,可以用匿名内部类,Runnable是个接口他也可以使用匿名内部类创建。
// 创建线程的方式一:创建一个继承与Thread类的子类,重写run方法
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建新线程1");
}
}.start();
// 创建线程的方式二:实现Runnable接口,再将Runnable的实现类传入Thread构造器,然后通过Thread启动线程
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("我是一个线程");
}
};
new Thread(runnable).start();
// 创建线程的方式二::创建一个实现Callable接口的实现类,重写call方法
什么是函数式(Functional)接口
函数式接口就是
只有一个抽象方法
的接口,同时,只有函数式接口可以使用Lambda表达式。
在java.util.function包下定义了Java 8 的丰富的函数式接口
Java当中的lambda是函数式编程吗
在函数式编程语言当中,Lambda表达式的类型是函数。
在Java8中,Lambda表达式是对象,而不是函数
,它们必须依附于一类特别的对象类型——函数式接口
。
Runnable接口就是一个典型的函数式接口,在上面创建线程的时候也用到了,他是可以用 匿名内部类
的形式进行实例化的。
@FunctionalInterface
public interface MyNumber {
public double getValue();
}
函数式接口中使用泛型:
@FunctionalInterface
public interface MyFunc<T> {
public T getValue(T t);
}
有且仅有一个抽象方法
"的接口上,表示函数式接口。该注解不是必须的
,如果一个接口符合"函数式编程"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查,如果编写的不是函数式接口,但是加上了@Functionallnterface 那么编译器会报错。代码如下:
@FunctionalInterface
public interface TestInterface {
void showList();
}
这四个有必要了解一下,我现在还记着去年面试的时候当时就被问到了。
JDK1.8在线API:https://docs.oracle.com/javase/8/docs/api/
java.util.function包下都是函数式接口。
什么是Lambda 表达式?
Lambda 是一个
匿名函数
,我们可以把 Lambda 表达式理解为是一段可以传递的代码
(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码
。
Lambda表达式 和 函数式接口 的关系
Lambda表达式就是一个函数式接口的实例
(注意是实例,Object a = new Object()这个a只是个引用变量,而new Object这是一个实例)。只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
Lambda表达式 和 匿名内部类 的关系
函数式接口是可以使用匿名内部类
进行创建实例,而lambda可以简化使用匿名内部类创建。
注意: 使用lambda简化匿名内部类的前提条件是,接口必须是函数式接口
。而匿名内部类
创建实例却不一定非得是函数式接口,甚至都不一定是接口,还可以是普通类。
匿名内部类、函数式接口、lambda表达式 这三个关系一定要搞清楚
。
Lambda 语法
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符
为 “->
” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
从匿名类到 Lambda 的转换举例1:
通过下面示例我们能发现一个问题:使用匿名内部类,没有变量引用也可以创建,而lambda则不可以,他必须要有变量引用。这个变量引用可以是直接的变量引用,也可以是方法内参数传递,
Lambda表达式就是一个函数式接口的实例
。
假如没有变量引用例如:
() -> System.out.println("我是一个函数式");
只有这个肯定是不行的,他需要靠前面的TestInterface变量引用来做类型推断。
@FunctionalInterface
interface TestInterface {
void showList();
}
public class Test {
public static void main(String[] args) {
new TestInterface() {
@Override
public void showList() {
System.out.println("我是一个函数式");
}
};
// 没有变量引用:() -> System.out.println("我是一个函数式");
TestInterface testInterface = () -> System.out.println("我是一个函数式");
}
}
从匿名类到 Lambda 的转换举例2:
通过下面这个示例会发现,他其实就是在不断的优化,尽可能的让我们开发人员少写代码。他是如何做到一步一步省略的?其实这就是Java语法糖。
对于Java语法糖这里我就不过多提了,想了解的可以去看一下我的这一篇文章:
https://blog.csdn.net/weixin_43888891/article/details/124567498?spm=1001.2014.3001.5501
public static void main(String[] args) {
// 匿名内部类创建多线程
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("我是一个线程");
}
};
new Thread(runnable).start();
// 省去runnable变量,这种方式等同于上面的
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是一个线程");
}
}).start();
// 使用Lambda表达式,实现多线程,省去了new Runnable和重写的方法名字
new Thread(() -> {
System.out.println("我是一个线程");
}).start();
// 优化Lambda,去掉大括号
new Thread(() -> System.out.println("我是一个线程")).start();
}
代码说明:使用Lambda表达式。一般的格式是
()-> 0
,如果0里面只有一行代码,则0可以省略。
->左边的()表示参数列表
,如果有形参,则在()中添加形参,->右边0表示具体逻辑
。如果方法体返回值是void,则甚至可以在0中不写任何逻辑(当然也要结合场景)。返回值如果有值,则需要写具体的逻辑, return处理后的值。
Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,由编译器推断出来的。这就是所谓的“类型推断
”。
代码示例:
public static void main(String[] args) {
// 匿名内部类创建 带有泛型参数的
new Consumer<String>() {
@Override
public void accept(String o) {
System.out.println(o);
}
};
// 使用lambda进行改造
Consumer<String> consumer = (String a) -> System.out.println(a);
// 由于前面变量泛型声明了数据类型,通过引用变量的泛型类型,所以后面的lambda数据类型可以省略
Consumer<String> consumer1 = (a) -> System.out.println(a);
}
我们都知道lambda是一个实例,使用lambda的时候必须要有变量引用,除变量引用外,这样进行方法内的参数传递也是可以的
代码示例一:
@FunctionalInterface
interface MyFunc<T> {
T getValue(T t);
}
public class Test {
public static void main(String[] args) {
// str的小括号是可以去掉的
String abc = toUpperString((str) -> str.toUpperCase(), "abc");
// String abc = toUpperString(str -> str.toUpperCase(), "abc");
System.out.println(abc);
}
public static String toUpperString(MyFunc<String> consumer, String str) {
return consumer.getValue(str);
}
}
将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型 必须是 与该 Lambda 表达式兼容的函数式接口
的类型。
代码示例二:
Java当中很多都是基于参数传递来使用lambda的,就拿集合的forEach来说。
import java.util.Arrays;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4);
list.forEach(a -> System.out.println(a));
}
}
什么是方法引用?
方法引用也是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
什么时候使用方法引用?
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
如下三种主要使用情况:
格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来
。
ClassName::methodName
使用要求
实现接口的抽象方法的参数列表 和 返回值类型,必须与方法引用的方法的 参数列表 和 返回值类型 保持一致!
代码示例一:
Consumer消费型函数,也就是只有入参,没有返回值的。
public static void main(String[] args) {
// 匿名函数创建
Consumer consumer = new Consumer() {
@Override
public void accept(Object o) {
System.out.println("消费型接口");
}
};
// lambda创建
Consumer consumer1 = (x) -> System.out.println(x);
// 方法引用
Consumer consumer2 = System.out::print;
}
代码示例二:
Comparator 比较器 函数,有两个入参,和一个返回值。
public static void main(String[] args) {
// 匿名内部类创建,这个泛型一定要带,不然下面就只能是object
Comparator comparator = new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return Integer.compare(a, b);
}
};
// lambda方式创建,这个泛型一定要带,不然报错
Comparator<Integer> comparator1 = (a, b) -> Integer.compare(a, b);
// 方法引用,这个泛型一定要带,不然报错
Comparator<Integer> comparator2 = Integer::compare;
}
代码示例三:
BiPredicate是一个判定型的 函数,有两个入参,和一个Boolean返回值。
public static void main(String[] args) {
// 这里的类型不能丢,因为下面用的是String,如果不声明类型就是Object
BiPredicate biPredicate = new BiPredicate<String, String>() {
@Override
public boolean test(String o, String o2) {
return o.equals(o2);
}
};
// 这里变量没有声明类型,那么就是Object,equals也是调用的Object的
BiPredicate biPredicate1 = (a, b) -> a.equals(b);
// 这里类型必须要写,因为后面写明了调用String的equals
BiPredicate<String, String> biPredicate2 = String::equals;
}
格式: ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
要求构造器参数列表要与接口中抽象方法的 参数列表一致!且方法的返回值即为构造器对应类的对象。
代码示例
import java.util.function.Function;
class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher(String name) {
this.name = name;
}
}
public class Test4 {
public static void main(String[] args) {
// 匿名内部类
Function function = new Function<String, Teacher>() {
@Override
public Teacher apply(String name) {
return new Teacher(name);
}
};
// lambda
Function<String, Teacher> function1 = (a) -> new Teacher(a);
// 构造器引用
Function<String, Teacher> function2 = Teacher::new;
}
}
格式: type[] :: new
public static void main(String[] args) {
// 匿名内部类
Function function = new Function<Integer, Integer[]>() {
@Override
public Integer[] apply(Integer x) {
return new Integer[x];
}
};
// lambda
Function<Integer, Integer[]> function1 = a -> new Integer[a];
// 数组引用
Function<Integer, Integer[]> function2 = Integer[]::new;
}