当前位置: 首页 > news >正文

【Java】从匿名内部类到函数式接口

文章目录

  • 前言
  • 一、匿名内部类
    • 1.1 什么是匿名内部类
    • 1.2 继承普通类(抽象类)
    • 1.3 实现接口
    • 1.4 关于匿名内部类
  • 二、函数式接口
    • 2.1 函数式接口和Lambda是什么
    • 2.2 JDK内置的核心函数式接口
  • 三、从匿名内部类到函数式接口
  • 总结


前言

假想一下你需要对一个集合进行排序,但这是一个临时排序,排序规则只在这一处使用。我们知道,排序需要一个类实现Comparator接口,并且该类需要实现接口中的compare方法,最后将这个实现类的实例传入Collections.sort方法,该方法会在内部调用我们定义的compare方法。为这一处临时排序逻辑单独定义一个类,重写方法后再实例化,实在过于麻烦,实现起来也不够优雅。

考虑到这种情况,Java提供了两种解决方案——匿名内部类和函数式接口。

匿名内部类可以用来临时实现接口或继承类,它没有类名,只能在创建时使用一次,非常适合封装临时逻辑。而2014年Java 8引入的Lambda表达式改变了这一局面:用Lambda简化函数式接口的实现变得非常流行,链式调用也更为普遍。Lambda表达式的背后正是函数式接口(即仅包含一个抽象方法的接口),我们可以通过函数式接口(及Lambda表达式)简洁地封装临时逻辑。但Lambda真能完全替代匿名内部类吗?为什么说Lambda依赖于函数式接口?下面就让我们对比分析匿名内部类与函数式接口。


一、匿名内部类

1.1 什么是匿名内部类

在聊匿名内部类之前我们得先回顾下接口和普通类。

  • 接口本质上是定义的规范,不能够直接被实例化。接口包含抽象方法,默认方法和静态方法。只有抽象方法是没有方法体的,其实现类必须重写。默认方法虽然直接在接口里带方法体,但不是必须要求实现类里重写。抽象方法,默认方法都是需要在实现类的实例里调用,只有静态类是直接从接口名调用,并且不能被重写。
  • 普通类主要是用来封装属性与方法,是直接能实例化的(抽象类除外)。
    普通类可以直接创建实例,而接口必须通过实现类才能实例化。

匿名内部类的核心是为临时逻辑提供临时载体,它是Java中一种特殊的内部类,无需显式定义类名,直接在内部编写临时逻辑。通过匿名内部类,既可以继承普通类编写自定义逻辑,也能实现接口编写自定义逻辑。

1.2 继承普通类(抽象类)

假设我有个抽象日志方法需要在子类中实现具体格式输出。日志输出的格式根据业务逻辑需要,得非常灵活。匿名内部类能很好的将根据业务插入对应的逻辑,并且无需重复定义多个日志子类
抽象日志类

abstract class Logger {//log需要被匿名内部类实现public abstract void log(String message);
}

模拟用户业务

class UserService {private String username;public UserService (String username) {this.username = username;}//模拟用户登录操作时记录带用户名的日志public void login() {//用匿名内部类继承Logger,定制用户日志格式Logger userLogger = new Logger() {@Overridepublic void log(String message) {System.out.println("[用户操作] 用户名: " + username + " - " + message);}};userLogger.log("登录成功");}
}public class LoggerExample {public static void main(String[] args) {//记录用户登录日志UserService userService = new UserService ("araby");userService.login();}
}

这里再临时重写某个类的方法,通过匿名内部类直接继承并修改逻辑。重点在于通过new Logger(),并且对void方法直接重写,大括号包含的就上子类的逻辑。

1.3 实现接口

匿名内部类不光能继承类,也能实现接口。我们把上面的抽象日志类换成日志接口
抽象日志接口

interface  Logger {void log(String message);
}

模拟用户业务

class UserService {private String username;public UserService (String username) {this.username = username;}//模拟用户登录操作时记录带用户名的日志public void login() {//用匿名内部类实现Logger,定制用户日志格式Logger userLogger = new Logger() {@Overridepublic void log(String message) {System.out.println("[用户操作] 用户名: " + username + " - " + message);}};userLogger.log("登录成功");}
}public class LoggerExample {public static void main(String[] args) {//记录用户登录日志UserService userService = new UserService ("araby");userService.login();}
}

1.4 关于匿名内部类

上面,我们分别演示了通过匿名内部类继承类和实现接口的方式。对比这两种实现,初看匿名内部类的实现没有什么区别,但其实这是一种误解。对于普通类(抽象类)的匿名内部类,它内部是通过new来完成继承,对应接口的的匿名内部类,它内部是通过new完成实现。

一个显著差异是:若接口包含多个抽象方法,匿名内部类必须全部实现;而继承类(非抽象类)时,仅需重写需要定制的方法,无需强制重写所有方法(抽象类需重写全部抽象方法,与接口规则一致)。

虽然匿名内部类已经帮我们极大的简化了对临时逻辑的封装,但是对于仅含一个抽象方法接口的情况,我们有另外一种更加简洁方便的方案,也就是接下来要说到函数式接口。

二、函数式接口

2.1 函数式接口和Lambda是什么

函数式接口是指仅包含一个抽象方法的接口,随着Java 8一并引入的还有一个重要概念——Lambda表达式,函数式接口Lambda的载体,通过Lambda能大大的简化通过匿名内部类实现仅含一个抽象方法的接口要编写的代码。值得注意的是Lambda并不能完全替代匿名内部类,如果这个接口含有多个抽象方法,用Lambda表达式传入逻辑,Java是无法判断出该临时逻辑是要重写到哪个抽象方法里。

虽然函数式接口仅包含1个抽象方法,但是能有多个默认方法(default修饰)或静态方法(static修饰)。函数式接口的规范是在接口上用@FunctionalInterface注解修饰。

自定义函数式接口示例

@FunctionalInterface
interface FunctionalInterfaceExample {//唯的一抽象方法int calculate(int a, int b); // 唯一抽象方法//默认方法default void defaultMethod() {System.out.println("默认方法");}//静态方法static  void staticMethod() {System.out.println("静态方法");}
}

以上就一个典型的函数式接口。调用这个函数式接口有两种方式,一种是传统的实现接口重写方法,另一种就是大家更为习惯的Lambda

实现接口重写方法

//显式实现接口的类
class Calculator implements FunctionalInterfaceExample {//必须重写唯一的抽象方法 calculate@Overridepublic int calculate(int a, int b) {//实现两数相加的逻辑return a + b;}
}public class Main {public static void main(String[] args) {//创建实现类对象FunctionalInterfaceExample calculator = new Calculator();//调用抽象方法int sum = calculator.calculate(3, 5);System.out.println("3 + 5 = " + sum);//调用默认方法calculator.defaultMethod();//调用静态方法FunctionalInterfaceExample.staticMethod(); // 输出:静态方法}
}

而Lambda表达式是函数式接口中唯一抽象方法的简化实现,用于快速实现函数式接口的抽象方法。
Lambda表达式

//(参数列表) -> {方法体}
public class Main {public static void main(String[] args) {FunctionalInterfaceExample add = ( int a,int b) ->  {int sum = a + b;return sum;};//省略参数类型FunctionalInterfaceExample add1 = ( a,b) ->  {int sum = a + b;return sum;};//省略大括号和 returnFunctionalInterfaceExample add2 = ( a,b) -> a + b;System.out.println(add.calculate(1, 2));}
}

若参数类型可推导,可省略类型,比如 (a,b) -> a + b;若只有一个参数,可省略括号,比如s -> s.length();
若只有一行代码,可省略大括号和 return,比如(a, b)-> a + b;多行代码则必须用大括号包裹

回到我们最开始使用函数式接口的初衷,无非是需要一个容器去承载我们要自定义的逻辑去执行。对于一段逻辑来说,无非是输入,执行,输出。有的逻辑不需要输入参数,有的逻辑执行不需要输出结果。为此Java帮我们预定义了大量函数式接口,覆盖了工作生成中遇到的绝大多数需要临时传递逻辑执行的场景。

有过C#开发经验的伙伴看到这里应该会非常熟悉。这有点类似于C#中的委托,也是将逻辑作为参数传来传去。但是C#中的委托是一种类型,可以多播支持绑定多个方法。而Java的函数式接口是接口,不支持绑定多个方法。

2.2 JDK内置的核心函数式接口

JDK里预定义了大量函数式接口,这里拿常见的Consumer函数式接口举例分析
Consumer<T> 接口的源码实现

package java.util.function;
import java.util.Objects;@FunctionalInterface
public interface Consumer<T> {void accept(T t);default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}
}

首先Consumer函数式接口有@FunctionalInterface注解修饰,并且仅有一个accept抽象方法,可以用 Lambda 表达式直接实现。我们通过Lambda表达式传入的自定义逻辑会自动重写这个accept方法。

此接口还有一个默认方法andThen()。它的参数是 Consumer<? super T> after,可以接收T类型的对象,也可以接收T的父类型的对象。用于先执行当前Consumer的accept,再执行参数after对应的 accept,实现链式调用。

Consumer<Object> objectConsumer = o -> System.out.println("打印对象:" + o);
Consumer<String> stringConsumer = s -> System.out.println("打印字符串:" + s);
Consumer<String> chain = stringConsumer.andThen(objectConsumer);
chain.accept("hello");----输出结果----
打印字符串:hello
打印对象:hello

常用内置函数式接口

接口类别接口名称抽象方法逻辑用途描述示例场景
消费型Consumer< T>void accept(T t)接收参数 T,无返回值(消费数据)遍历打印、修改对象属性
消费型BiConsumer<T,U>void accept(T,U)接收两个参数,无返回值处理 Map 键值对
供给型Supplier< T>T get()无参数,返回 T(提供数据)生成随机数、获取配置
函数型Function<T,R>R apply(T t)接收 T,返回 R(数据转换)字符串转整数、提取对象属性
函数型BiFunction<T,U,R>R apply(T,U)接收两个参数,返回 R两数相加并返回结果
断言型Predicate< T>boolean test(T)接收 T,返回布尔值(条件判断)过滤集合、数据校验
操作型UnaryOperator< T>T apply(T t)参数和返回值类型相同(一元操作)数值自增、字符串转大写

三、从匿名内部类到函数式接口

前面我们发现匿名内部类的实现语法冗余,并且需要显式声明子类型重写方法并且再实例化,但是适合有多方法实现这种逻辑复杂的场景。通过函数式接口,比如Lambda的实现,虽然简洁,但是这种写法是直接对应抽象方法的实现,只适合单一抽象方法的简单逻辑。

显然匿名内部类适用范围更加广,它不仅能实现多抽象方法接口,也能继承普通类或抽象类并重写方法。而函数式接口的定义规则已经将Lambda限制在单抽象方法的接口中。

但是通过匿名内部类编译后生成独立.class 文件,存在类加载开销,而Lambda表达式是编译后通过 invokedynamic 指令,开销更低。

值得注意的是Lambda表达式中的this是指向外部类,它内部没有独立实例。也就是Lambda内部代码的作用域范围和外部类一致


总结

匿名内部类本质上还是通过对类的临时实现来实现逻辑封装,而Lambda表达式更多的是将函数逻辑作为一个参数传入,是函数式编程思想的体现。灵活的使用二者能帮我们更加优雅的封装代码里的逻辑,写出简洁的代码。

http://www.dtcms.com/a/475390.html

相关文章:

  • 小型企业网站建设项目天津百度seo
  • 怎样增加网站流量什么是企业vi设计
  • 广汉移动网站建设微信公众官方平台入口
  • 团购网站开发与设计wordpress有中文吗
  • 青岛网郑州做网站优化公
  • 2025年--Lc181--H331. 验证二叉树的前序序列化(二叉树,计数器)--Java版
  • 快速建设一个网站wordpress页面排序
  • 营销网站制作都选ls15227芜湖中凡网站建设公司
  • 网站程序预装长沙专业竞价优化首选
  • 淄博桓台网站建设报价钢筋网片规格型号
  • 深圳真空共晶炉公司
  • 站长工具里查看的网站描述和关键词都不显示阿里云镜像wordpress
  • 潍坊地区网站制作官方网站的要素
  • 微信小程序一站式开发男女做羞羞羞的网站
  • 咸宁建设网站wordpress发表的文章在页面找不到
  • 松原市城乡建设局网站安阳论坛网
  • 佛山市官网网站建设怎么样建筑设计资料网站
  • 四川网站开发制作做新闻h5网站
  • 验证-SystemVerilog-数据类型、断言
  • 淘宝网站建设可以申请赔款建筑招工网站
  • 响水做网站德州做网站建设的公司哪家好
  • 建设银行纪念币预约网站神马网站快速排名软件
  • 打开网站代码中国机械加工网app
  • RFSOC 47DR PCIE板卡支持PCIE4.0
  • 建设厅网站给领导留言如何查看wordpress用户量上限
  • 「机器学习笔记11」深入浅出:解密基于实例的学习(KNN算法核心原理与实践)
  • 初识JAVA-1
  • 网络攻防技术:口令攻击
  • 综合评价-云模型 极简化软件操作+全流程方法介绍
  • 怎么开发网站平台公司网站后台密码