一 、lambda表达式
1.1需求分析
public class Test01 {
public static void main(String[] args) {
//开启一个线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程的名称"+Thread.currentThread().getName());
}
}).start();
System.out.println("主线程的名称mian"+Thread.currentThread().getName());
}
}
代码分析:
- Thread类需要一个Runnable接口作为参数,接口中的抽象方法是用来指定线程任务内容的核心
- 为了指定run方法体,不得不创建Runnable接口实现类并重写抽象方法run()
- 为了省区创建实现类单独定义一个类文件,因此引入了匿名内部类
new Runnable() {需要重写的方法}
- 实际上我们只关心重写的方法体中的内容->因此引出了Lambda表达式进一步简化匿名内部类写法,强调方法体内容
1.2Lambda表达式
- 格式:
(参数)->{方法体}
- Lambda表达式其实是一个匿名函数,类似于匿名内部类,我们不需要关心具体的函数名是什么
new Thread(()->{System.out.println("Lambda表达式实现,新线程的名称"+Thread.currentThread().getName());}).start();
1.2.1无参数的Lambda表达式
- 定义接口,接口中定义一个抽象方法show()无参数无返回值.
@java.lang.FunctionalInterface
public interface FunctionalInterface {
void show();//无参抽象方法
}
- 定义测试类
public class Test {
public static void main(String[] args) {
//01、匿名内部类
new FunctionalInterface() {
@Override
public void show() {
System.out.println("匿名内部类方式");
}
}.show();
//02、lambda表达式
FunctionalInterface functionalInterface = () -> {
System.out.println("无参构造方法Lambda表达式写法调用");
};
functionalInterface.show();
//03、精简版Lambda表达式
((FunctionalInterface)()->{
System.out.println("精简版Lambda表达式");
}).show();
}
}
1.2.2优参数的Lambda表达式
- 定义接口,接口中的抽象方法有参数
案例1:
@FunctionalInterface
public interface Message {
void Send(String information);
}
- 定义测试类
public class Lambda01 {
public static void main(String[] args) {
//Lambda表达式实现将实例化对象作为参数进行传递
senMessage((information)->{
System.out.println(information);
});
}
//定义一个方法,参数式接口Message实例类型,由于接口无法new,所以之恶能通过匿名内部类或者Lambda形式传入实例化的参数
public static void senMessage(Message message){
message.Send("这是要传递的信息");//message就是实例化的对象
}
}
案例2:
- 定义实体类
public class UserEntity {
private String name;
private int age;
public UserEntity(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "UserEntity{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 定义测试类
import java.util.ArrayList;
import java.util.function.Consumer;
public class Lambda_Sort {
public static void main(String[] args) {
//创建UserEntity类型的集合,并添加数据
ArrayList<UserEntity> userEntities = new ArrayList<>();
userEntities.add(new UserEntity("liMing",18));
userEntities.add(new UserEntity("wangqing",28));
userEntities.add(new UserEntity("Lucy",15));
userEntities.add(new UserEntity("Jack",26));
//01、按照年龄进行排序,匿名内部类方式
/* userEntities.sort(new Comparator<UserEntity>() {
@Override
public int compare(UserEntity o1, UserEntity o2) {
return o1.getAge()-o2.getAge();
}
});*/
//02、Lambda实现排序,当方法体只有一条语句时不用写return,和大括号
userEntities.sort((o1, o2) -> o1.getAge()-o2.getAge() );
//03、foreach进行输出,匿名内部类方法
/*
System.out.println("正常forEach----------------------");
userEntities.forEach(new Consumer<UserEntity>() {
@Override
public void accept(UserEntity userEntity) {
System.out.println(userEntity);
}
});*/
//04、lambda表达式实现forEach
System.out.println("lambda表达式实现forEach----------------------");
userEntities.forEach(userEntity->System.out.println(userEntity));
}
}
1.3注解:@FunctionalInterface介绍
@FunctionalInterface
是Java 8引入的一个注解,用于指示接口应该是一个函数式接口。函数式接口是指只有一个抽象方法的接口,这种接口可以用lambda表达式实现,不用再定义一个实现类。在接口上使用@FunctionalInterface
注解可以帮助编译器(编译阶段)检查该接口是否满足函数式接口的条件,如果不满足,编译器会报错。
例如,下面的代码定义了一个函数式接口:
@FunctionalInterface
public interface MyFunctionalInterface {
void doSomething();
}
在这个例子中,MyFunctionalInterface
只有一个抽象方法doSomething()
,因此它是一个函数式接口。我们可以使用lambda表达式来实现这个接口:
MyFunctionalInterface myFunc = () -> System.out.println("Hello, world!");
myFunc.doSomething(); // 输出 "Hello, world!"
1.3.1为什么使用@FunctionalInterface注解
在写Lambda表达式的时候,如果实现的接口中有多个方法,将无法确定方法体应该式属于哪个抽象方法
在接口上使用此注解后,如果接口中定义了多个抽象方法,会报错提示.
1.4Lambda表达式原理分析
1.4.1匿名内部类原理
匿名内部类底层实际上在编译阶段会生成一个Class文件
- 定义的接口
public interface Animal {
void Eat();
}
- 实现类
public class Main {
public static void main(String[] args) {
InEat(new Animal() {
@Override
public void Eat() {
System.out.println("小狗吃骨头");
}
});
}
public static void InEat(Animal animal){
animal.Eat();
}
}
1.4.2Lambda省略写法
在标准写法的基础上,可以使用省略写法的规则:
- 小括号内参数类型可以省略
- 如果小括号内只有一个参数,小括号也可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号,return关键字,语句分号
public class Main {
public static void main(String[] args) {
InEat(new Animal(String name) {
@Override
public void Eat() {
System.out.println("小狗吃骨头");
}
});
}
public static void InEat(Animal animal){
animal.Eat();
}
}
省略写法:
小括号内省略了数据类型和括号,因为只有一个参数
public class Main {
public static void main(String[] args) {
InEat(name->
System.out.println("小狗吃骨头")
);
}
public static void InEat(Animal animal){
animal.Eat();
}
}
1.4.3Lambda表达式使用前提
- 方法的参数会局部变量类型必须是接口才能使用Lambda
- 接口中有且只有一个抽象方法(@FunctionalInterface注解修饰的接口)
1.5Lambda表达式与匿名内部类的区别(面试题)
- 所需类型不一样:
(1)匿名内部类可以传递的类型:类、抽象类、接口
(2)Lambda表达式需要的类型只能是接口 - 抽象方法的数量不同
(1)匿名内部类所需的接口中抽象方法数量是随意的,有多少可以实现多少
(2)Lambda表达式所需的接口中只能由一个抽象方法 - 实现原理不同
(1)匿名内部类是在编译后形成class
(2)Lambda表达式是在程序运行过程中动态生成class
二、JDK8接口增强
2.1接口可以新增的内容
JDK8前后接口中定义的变化主要体现在默认实现、静态方法和函数接口这三个方面
- 默认实现方法
(1)概念和语法:在JDK8之前,接口中只能包含抽象方法,即只有方法声明而没有方法体。JDK8开始允许在接口中定义带有具体实现的方法,称为默认实现。默认实现使用default修饰符。
(2)应用场景:默认实现的一个重要应用场景是简化接口的实现过程,减少冗余代码。例如,在Spring框架中的HandlerInterceptor接口中,JDK8之前需要实现所有声明的方法,即使某些方法对业务逻辑无关紧要。通过默认实现,接口设计者可以提供一些方法的默认行为,实现类可以选择性地重写这些方法。
优势:默认实现减少了抽象适配器类的需要,使得接口更加灵活和方便,同时也降低了实现类的复杂度。 - 静态方法
(1)概念和语法:接口中的静态方法是指那些用static修饰的方法,可以在不创建对象实例的情况下,通过接口名直接调用。
(2)应用场景:静态方法常用于工具类或工具方法的场景。例如,可以在接口中定义一个静态的工厂方法来创建实例。此外,静态方法也可以被用于在接口中封装与该接口相关的一些通用操作。
注意事项:静态方法不会在接口的实现类或子接口中继承,但它们提供了一种将相关工具方法组织在一起的方式。需要注意的是,静态方法不能访问接口中的非静态成员。 - 函数接口
(1)概念和语法:如果一个接口只声明了一个抽象方法(包括继承自父接口的抽象方法),那么这个接口被称为函数式接口,并且可以用@FunctionalInterface注解标注。函数式接口非常适合于使用Lambda表达式进行简洁的实现。
(2)应用场景:函数式接口在Java 8及以后的版本中大量用于函数式编程和流式处理,如java.util.function包下的Predicate、Consumer等接口。
优势:函数式接口的使用大大简化了代码结构,使得多线程编程、集合处理等功能变得更加直观和高效。
2.2默认方法
2.2.1为什么要在JDK8中新加默认方法?
- 定义接口
public interface Animal {
//定义一个动物接口
void Eat();
}
- 定义实现类与测试类
public class Imp {
public static void main(String[] args) {
A a = new A();
a.Eat();
B b = new B();
b.Eat();
}
}
//定义两个实现类
class A implements Animal{
@Override
public void Eat() {
System.out.println("类A实现抽象方法");
}
}
class B implements Animal{
@Override
public void Eat() {
System.out.println("类B实现抽象方法Eat");
}
}
- 当接口中需要新增加一个抽象方法时,所有接口实现类都需要实现该方法
2.2.2出现的问题
如果一个接口有几百个实现类,接口中新添加一个抽象方法,几百个实现必须都要实现此抽象方法,大大降低类接口的可扩展性。因此JDK8中引入了默认方法,可以在接口中定义默认的方法体,实现类可以直接调用,或者重写此方法。
default void play(){
System.out.println("接口中定义的默认方法");
}
2.2.3默认方法的两种使用方式
- 实现类直接调用默认方法
- 实现类重写默认方法
package 测试.JDK8默认方法;
public class Imp {
public static void main(String[] args) {
A a = new A();
a.Eat();
B b = new B();
b.Eat();
b.play();
}
}
//定义两个实现类
class A implements Animal{
@Override
public void Eat() {
System.out.println("类A实现抽象方法");
}
@Override
public void play(){
System.out.println("实现类A重写默认方法");
}
}
class B implements Animal{
@Override
public void Eat() {
System.out.println("类B实现抽象方法Eat");
}
}
2.2.4总结
- 引入默认方法的原因就是,当接口的实现类比较多的时候,如果接口中新添加一个抽象方法,所有的实现类都需要实现该方法,不管这个方法对自已有没有用,所以引入默认方法解决此问题。
- 定义了默认方法之后,方法体中定义一些默认的行为,如果某些实现接口的类需该方法,可以直接使用类对象进行调用,如果某些实现类觉得该方法不太适合自己,也可以对该方法进行重写,满足自己的需求。
3.
2.3静态方法
作用也是为了增强接口扩展,接口中的静态方法在实现类中无法像默认方法那样重写。
语法结构:
修饰符 interface 接口名{
static 返回值类型 方法名(){
方法体
}
}
2.4默认方法与静态方法区别
- 默认方法可以通过实例对象调用,静态方法只能通过接口名调用
- 默认方法可以被实现类继承,实现类可以直接调用默认方法或重写默认方法
- 静态方法不能被继承,实现类不能重写接口中静态方法,之恶能通过接口名调用