匿名内部类 - ( 零基础学java )

Java-匿名内部类

我们先分析匿名内部类的结构,然后逐一解释,最后以下罗列的问题都会在下面的内容中一一得到解答 :

匿名内部类到底是什么?

我们为什么要学习匿名内部类 ?

匿名内部类都有怎样的作用 ?

匿名内部类应用的场景又有哪些 ?

匿名内部类是否有缺陷?

让我们带着这些问题来学习Java中的匿名内部类吧 !

结构决定性质

结构

  • 匿名内部类基本语法 :
new 父类构造器(参数) / 实现接口() {
    // 类的主体部分
};
  • 解释匿名内部类语法中所有的概念:

    • new 父类构造器(参数) 表示匿名内部类是某个类的子类实例。

    • 实现接口() 表示匿名内部类是某个接口的实现实例。

    • { ... } 内部是匿名内部类的主体部分,包含类的字段、方法等定义。


性质

匿名内部类(Anonymous Inner Class)是一种在声明和创建对象的同时定义类的方式,它没有显式的类名。通过 匿名内部类 看这几个字的字面意思我们都知道这是个没有名字的类,即 非具名类 . 以下是匿名内部类的具备的一些性质 :

  1. 可以实现接口或继承类: 匿名内部类可以实现接口或继承某个类,从而提供具体的实现。
  2. 没有显式的类名: 匿名内部类没有显式的类名,因为它是一种临时的、一次性的实现。
  3. 一次性使用: 通常用于临时的、一次性的场景,不需要复用。因为匿名内部类没有类名,所以无法在其他地方重复使用。
  4. 可以访问外部类的成员: 匿名内部类可以访问外部类的成员,包括成员变量和方法。对于外部类的局部变量,有一些规则,比如必须是final或者事实上是final的。
  5. 可以包含字段和方法: 在匿名内部类的主体部分,可以包含字段(成员变量)和方法的定义。
  6. 不可以包含静态成员: 匿名内部类不能包含静态成员,包括静态方法和静态变量。

我们来一个一个的解释,同时我也会拿具体的代码演示.

可以实现接口或继承类

  • 匿名内部类可以用来实现接口或者继承类,这也是匿名内部类最常用的一个用法,我们在实际开发中,如果需要实现(重写)某个接口(类)的方法,而且这个方法只是临时使用,不需要复用,而且会出现很多这样的场景,我们肯定是不想一一都去额外的封装一个类去实现接口或者继承类然后再创建类的实例,取调用我们需要的方法,这个时候我们就 匿名内部类 就排上用场啦,我们可以通过匿名内部类去临时创建一个接口的实例(接口不能实例化,这里只是形象比喻一下)或者创建一个临时的子类重写了父类方法的实例. 总结一下 : 当匿名内部类需要实现接口或者继承类时,它可以直接在创建对象的地方定义类的主体,而不需要显式地声明一个具名的类。
package src.demo;

//定义一个接口
interface  Chef{
    void cook();
}

class Sever{
    public void shangCai(){
        System.out.println("服务员上菜");
    }
}

public class Demo01 {
    public static void main(String[] args) {

        //使用匿名内部类来实现接口
        Chef chef1 = new Chef() {
            @Override
            public void cook() {
                System.out.println("炒蛋炒饭");
            }
        };
        //调用我们使用匿名内部类实现接口实例中的抽象方法
        chef1.cook();


        //使用匿名内部类重写父类方法
        Sever sever = new Sever() {
            @Override
            public void shangCai() {
                System.out.println("服务员上菜->蛋炒饭");
            }
        };
        //调用我们使用匿名内部类重写父类的方法
        sever.shangCai();
    }
}

通过上述的代码,我演示了两种使用匿名内部类的情况:

  1. 实现接口: 通过匿名内部类实现了 Chef 接口,提供了 cook 方法的具体实现。

    Chef chef1 = new Chef() {
        @Override
        public void cook() {
            System.out.println("炒蛋炒饭");
        }
    };
    

    这部分代码演示了匿名内部类用于实现接口的情况,通过创建一个实现了 Chef 接口的匿名内部类的实例,重写了接口中的抽象方法 cook

  2. 重写父类方法: 通过匿名内部类重写了 Sever 类的 shangCai 方法。

    Sever sever = new Sever() {
        @Override
        public void shangCai() {
            System.out.println("服务员上菜->蛋炒饭");
        }
    };
    

    这部分代码演示了匿名内部类用于重写父类方法的情况,通过创建一个继承自 Sever 类的匿名内部类的实例,重写了 Sever 类的方法 shangCai

通过这两种使用情况,你展示了匿名内部类在实现接口和重写父类方法时的便利性。匿名内部类可以直接在创建对象的地方提供类的定义,避免了显式地声明一个具名的类,尤其适用于一次性的实现或重写。

没有显式的类名

没有显式的类名,这意味这我们使用匿名内部类创建出来的实例是无法通过类名去访问的,如果我们需要实现函数回调的话,我们就可以使用父类引用或者接口引用去接收. 所以,我们可以知道 **匿名内部类是必须基于已存在的类或者接口,因为它的实质是在这个基础上创建一个新的匿名子类或者实现一个匿名实例,**这样做的优势就是我们省略了显式的创建一个具名子类的步骤,这对于我们一些简单的或者一次性的任务是非常方便的,因我们我们可以在需要的地方直接实现类的功能,而无需为此专门定义一个新的类,这使得代码可以更为紧凑和直观.

一次性使用

匿名内部类具备这个性质的理由就是 : 因为匿名内部类没有类名,所以无法在其他地方重复使用。

可以访问外部类的成员

实际需求中如果我们需要使用匿名内部类访问外部类的一些成员(包括属性和方法) , 那么其中是否有什么限制或者规则吗 ? 有的 ! 我们后续通过代码测试也可以知道,在这里我们先提前说明有怎样的规则 :

匿名内部类可以访问外部类的成员,包括成员变量和方法。对于外部类的局部变量,有一些规则,比如必须是final或者事实上是final.

(1)外部类的成员变量:

匿名内部类中可以访问外部类的成员变量,并且可以进行修改。这是因为外部类的成员变量在匿名内部类中有完整的作用域,而且匿名内部类实际上是外部类的一个扩展,可以直接访问外部类的成员。(需要注意的是 : 在匿名内部类的方法中,可以访问和修改外部类的成员变量。这些修改在匿名内部类的方法中生效,但不会影响外部类实例之外的其他代码。这是因为匿名内部类实际上是一个新的类,其代码块被包含在外部类的方法内部,而成员变量的修改是在匿名内部类的方法中执行的。)

实例代码如下 :

package src.main;

public class Example {
    private int memberVar = 10;

    public void modifyMemberVar() {
        // 匿名内部类
        Runnable r1 = new Runnable() {
            @Override
            public synchronized  void run() {
                // 访问外部类的成员变量
                System.out.println("Before modification: " + memberVar);

                // 修改外部类的成员变量
                memberVar = 20;

                System.out.println("After modification: " + memberVar);
            }
        };

        Runnable r2 = new Runnable() {
            @Override
            public synchronized  void run() {
                // 访问外部类的成员变量
                System.out.println("Before modification: " + memberVar);

                // 修改外部类的成员变量
                memberVar = 20;

                System.out.println("After modification: " + memberVar);
            }
        };

        // 使用匿名内部类的实例
        new Thread(r1).start();
        new Thread(r2).start();
    }

    public static void main(String[] args) {
        Example example = new Example();
        example.modifyMemberVar();
    }
}

  1. 代码中,匿名内部类实现了Runnable接口,其中的run方法中访问并修改了外部类Example的成员变量memberVar
  2. 匿名内部类的作用域包括了它所在的方法,也就是modifyMemberVar方法。在这个方法中,您创建了两个不同的匿名内部类的实例(r1r2),每个实例都有自己的run方法。因此,每个实例的run方法中的对memberVar的修改是在各自匿名内部类的作用域内完成的。
  3. modifyMemberVar方法中创建了两个线程,每个线程都启动了一个匿名内部类实例。这意味着两个线程可以并发地执行各自匿名内部类的run方法,但彼此之间的修改不会相互干扰。

(2)外部类的局部变量:

匿名内部类可以访问外部类的成员变量和方法,但如果要访问外部方法中的局部变量,这个局部变量必须是 final 或者是 effectively final。这是因为匿名内部类的实例可能会在方法执行完毕之后仍然存在,而且对局部变量的引用是在匿名内部类中存储的。如果局部变量不是 final 或者 effectively final,那么在方法执行完毕后,外部局部变量的生命周期结束,但匿名内部类的实例可能仍然存在,这时如果访问这个局部变量就会出现问题。

这也就是我们说的 变量捕获机制,确切地说是对局部变量的捕获机制。

变量捕获机制的关键是,编译器会生成一个匿名内部类的构造函数,并将外部的局部变量传递给这个构造函数,以确保匿名内部类在之后仍然能够访问这些变量。这种捕获方式确保了在匿名内部类中对外部局部变量的访问是安全的。

示例代码如下:

public class Example {
    public void someMethod() {
        final int localVar = 42;//局部变量
//局部变量必须是 final 或者是 effectively final
        Runnable r = new Runnable() {
            @Override
            public void run() {
                //localVal = 1; 报错,无法修改
                
                System.out.println(localVar);//在匿名内部类中访问外部类的局部变量
            }
        };

        // 使用匿名内部类的实例
        new Thread(r).start();
    }
}
  • 在外部类修改局部变量的值(报错)
image-20231211084543439
  • 在匿名内部类中修改局部变量的值(报错)
image-20231211084615734
  • 不修改外部类局部变量的值,而是显式的让其被final修饰
image-20231211084851143
  • 不修改外部类的局部变量的值,而是隐式的意味着事实上是final,也就是局部变量一旦被定义就不再被修改,那么在事实上就可以认为这个局部变量是一个final修饰的了,而且就是修改了,在匿名类中访问这样的被修改的外部类的局部变量是会报错的!!! 因为变量捕获机制的存在 !!!
image-20231211085216762

在上述代码中,localVar 是一个局部变量,因为匿名内部类 Runnable 中引用了这个局部变量,所以必须声明为 final

(3) 所以,总结一下:

  • 外部类的成员变量可以在匿名内部类中被访问和修改。
  • 外部类的局部变量必须要求是 final 的,或者是事实上是 final 的,才能在匿名内部类中被访问。(变量捕获机制)

可以包含字段和方法

贴代码

interface Greeting {
    void greet();
}

public class AnonymousInnerClassExample {
    public static void main(String[] args) {
        // 使用匿名内部类实现接口并包含字段和方法
        Greeting greeting = new Greeting() {
            //字段
            private String message = "Hello from anonymous inner class";

            @Override
            public void greet() {
                System.out.println(message);
                sayGoodbye(); // 调用匿名内部类中定义的方法
            }
			//方法
            private void sayGoodbye() {
                System.out.println("Goodbye from anonymous inner class");
            }
        };

        // 调用实例方法
        greeting.greet();
    }
}

不可以包含静态成员

这是我的理解 :

匿名内部类的本质是一个实例,这个实例可以用来重写父类中的实例方法,也可以用来实现接口中的抽象方法,也可以用来实现抽象类中的抽象方法,但是不可以声明静态成员变量或者定义静态方法,虽然可以重写父类的静态方法,但是重写的静态方法是通过实例调用的,这违背了java面向对象编程的理念.所以匿名内部类是一个非具名的实例,之所以叫做类是因为匿名内部类既完成了拓展原有类或者接口的目的又完成了实例的创建.

( 匿名内部类是一个实例,而不是一个类。静态成员是属于类的,而匿名内部类没有类名,无法定义属于自己的静态成员。)

贴代码 :

package src.main;

public class AnonymousInnerClassExample {
    public static void main(String[] args) {
        // 尝试在匿名内部类中定义静态成员(编译错误)
        Runnable myRunnable = new Runnable() {
            // 尝试定义静态成员变量(编译错误)
            // static int staticVariable = 20;

            @Override
            public void run() {
                // 尝试定义静态方法(编译错误)
                // staticMethod();
                System.out.println("Inside Runnable");
            }
        };

        // 调用匿名内部类中的 run 方法
        myRunnable.run();
    }

    // 尝试在外部类中定义静态方法(正常)
    public static void staticMethod() {
        System.out.println("Static method");
    }
}

分析 : 在这个例子中,我们尝试在匿名内部类中定义静态成员变量和静态方法,但这会导致编译错误。匿名内部类无法包含静态成员,因为它本身是一个实例,而静态成员是属于类的,必须在类级别上声明。


  • 通过分析匿名内部类的结构和性质,我们已经解决了文章开头我们提出的疑问了,现在还剩下最后一个疑问,那就是 : 匿名内部类是否有缺陷? 如果有缺陷是否有更好的替代方案 ? (答案是有缺陷,而且有更好的替代方案)

    匿名内部类是一种便捷但有局限性的编程方式。虽然它在短期、轻量级的情况下很方便,但它的一次性使用、可读性较差、不适合复杂继承关系、对外部变量的访问限制、不能包含静态成员以及可能导致生命周期延长等缺陷,使得在一些复杂或长期维护的场景中,更倾向于使用具名类或Lambda表达式等更灵活的替代方式。


使用场景

在以下的场景中,匿名内部类的简洁语法和临时性的特性使得它成为一种方便的编码方式。然而,需要在使用时权衡其便利性和一些限制,选择适合当前情况的实现方式。

  1. 事件处理

    在 GUI 编程中,匿名内部类常用于处理用户界面上的事件,比如按钮的点击事件。这简化了代码,避免了为每个事件都创建一个独立的类。

    button.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            // 处理按钮点击事件的逻辑
        }
    });
    
  2. 线程创建

    匿名内部类可以用于创建简单的线程对象,尤其在某个地方需要一次性的线程执行逻辑时。

    new Thread(new Runnable() {
        @Override
        public void run() {
            // 线程执行的逻辑
        }
    }).start();
    
  3. 实现接口或抽象类

    当只需要实现某个接口或继承某个抽象类的单一实例时,匿名内部类可以提供简洁的语法。

    SomeInterface instance = new SomeInterface() {
        @Override
        public void someMethod() {
            // 实现接口的逻辑
        }
    };
    
  4. 测试和调试

    在单元测试或调试时,有时需要临时性地实现某个接口或者继承某个类,匿名内部类能够方便地提供这种快速的实现。

    TestingTool.runTest(new TestInterface() {
        @Override
        public void runTest() {
            // 测试逻辑
        }
    });
    
  5. 简化工厂方法

    在工厂方法中,如果只需要创建一个实例,匿名内部类可以用于快速创建。

    Factory.createInstance(new SomeInterface() {
        @Override
        public void someMethod() {
            // 实现接口的逻辑
        }
    });
    
  6. 回调函数

    在某些设计模式或异步编程中,匿名内部类可以作为回调函数,用于定义异步操作完成后的回调逻辑。

    asyncOperation.doAsync(new Callback() {
        @Override
        public void onComplete() {
            // 异步操作完成后的回调逻辑
        }
    });
    

通过上述的分析, 相信大家应该对Java中的匿名内部类有了很深刻的理解了,如果文章中有什么地方写的不对,或者理解有误,欢迎大家在评论区留言, 教学相长 ~

如果觉得这篇文章有帮助到您,您的三连是对我最大的支持 !

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/235489.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SSM的教师上课系统

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…

基于以太坊的智能合约开发Solidity(基础篇)

参考教程:基于以太坊的智能合约开发教程【Solidity】_哔哩哔哩_bilibili 1、第一个程序——Helloworld: //声明版本号(程序中的版本号要和编译器版本号一致) pragma solidity ^0.5.17; //合约 contract HelloWorld {//合约属性变…

一些AG10K FPGA 调试的建议-Douglas

PLL AGM FPGA 在配置成功时,PLL 已经完成锁定,lock 信号已经变高;如果原设计中用 lock 信号输出实现系统 reset 的复位功能,就不能正确完成上电复位;同时,为了保证 PLL 相移的稳定,我们需要在 P…

项目文章(ChIP-seq)|Plant Physiol组蛋白H3K4甲基转移酶DcATX1促进乙烯诱导康乃馨花瓣衰老

发表单位:华中农业大学 期 刊 :Plant Physiology(IF:7.4) 发表日期:2023年5月2日 2023年5月2日华中农业大学的张帆教授研究团队在期刊Plant Physiology(IF:7.4)发表了题为“Histone H3K…

【软考】信息系统项目管理师论文方向猜想

报喜不报忧,每天都在为鸡零狗碎推诿扯皮,属实是有辱师门。 通过软考,目前算是真正有意义的事情。 虽然都说高项的论文是个玄学,但是道听途说了一些通关感想还是蛮有启发的。 文件要求 参考了一份广西省高级工程师评审的文件&am…

磁学单位SI制和CGS制的转换

电磁学领域中除了使用一般的SI国际制单位外,还会使用CGS高斯制单位,这对于接触磁性材料的朋友们来说,有时就需要做单位的转换,而这两种单位制的转换计算非常复杂。为了方便大家使用,我们系统地总结了一下电磁学中的单位…

python用turtle画樱花飘落,python代码画樱花代码

这篇文章主要介绍了python用turtle画樱花飘落,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获,下面让小编带着大家一起了解一下。 1、python画樱花树代码为: import turtle as T import random import…

洲际酒店集团内外并举 引领行业多元包容文化发展

携手多家本地合作伙伴聚焦女性职业发展和残障群体关爱 2023年12月8日,上海 — 近年来,随着“中国式现代化”进程的不断推进,建立多元、包容的雇佣环境,促进公平就业机会等愈加受到社会各界的重视。洲际酒店集团深知,“…

ssm基于JAVA的网上药品售卖系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本网上药品售卖系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息…

从零开始搭建企业管理系统(四):集成 Knife4j

集成 Knife4j 前言Knife4j是什么集成 Knife4j引入 pom 依赖添加基础配置启动程序测试完善文档信息编写配置类修改 UserController修改 UserEntity修改 BaseEntity 文档效果图swagger 界面knife4j 界面 前言 前面一小节我们使用postman来进行接口的调试,如果接口一多…

大数据机器学习算法项目——基于Django/协同过滤算法的房源可视化分析推荐系统的设计与实现

大数据机器学习算法项目——基于Django/协同过滤算法的房源可视化分析推荐系统的设计与实现 技术栈:大数据爬虫/机器学习学习算法/数据分析与挖掘/大数据可视化/Django框架/Mysql数据库 本项目基于 Django框架开发的房屋可视化分析推荐系统。这个系统结合了大数据…

Java项目-瑞吉外卖Day3

填充公共字段: 目的:由于某些属性,例如createdTime这些需要填充的字段会在多个地方出现,所以考虑使用公共字段自动填充的办法减少重复代码。 在对应属性上加入TableField注解。通过fill字段表明策略,是插入/更新的时候…

5个免费AI文案生成器【2024】

随着科技的不断进步,人工智能在各行各业中崭露头角,其中,AI文案生成器的出现在内容创作领域引起了翻天覆地的变化。在这个信息快速传递的时代,如何更高效地进行文案创作成为许多从业者关注的焦点。本文将深入探讨AI文案生成器&…

每日一博 - 图解5种Cache策略

文章目录 概述读策略Cache AsideRead Through 写策略Write ThroughWrite AroundWrite Back 使用场景举例 概述 缓存是在系统中存储数据的临时存储器,用于提高访问速度。缓存策略定义了如何在缓存和主存之间管理数据 读策略 Read data from the system: &#x1f5…

如何从众多知识付费平台中正确选择属于自己的平台(我有才知识付费平台)

在当今的知识付费市场中,用户面临的选择越来越多,如何从众多知识付费平台中正确选择属于自己的平台呢?下面,我们将为您介绍我有才知识付费平台相比同行的优势,帮助您做出明智的选择。 一、创新的技术架构,…

Oracle中LISTAGG 函数 的使用

概念:对于查询中的每个组,LISTAGG 聚合函数根据 ORDER BY 表达式对该组的行进行排序,然后将值串联成一个字符串

一键优化工具,十分不错的win7、win10系统优化的工具,可以帮助用户轻松快速优化系统,供大家学习研究参考~

主要功能 01、禁用索引服务 02、禁止window发送错误报告 03、禁用"最近使用的项目” 04、关闭Windows Defender 05、关闭防火墙 06、检查更新而不自动下载更新 07、启动电源计划“高性能” 08、调整电源选项 09、禁用休眠(删除休眠文件) 10、开启快速启动 11、…

C语言-每日刷题练习

[蓝桥杯 2013 省 B] 翻硬币 题目背景 小明正在玩一个“翻硬币”的游戏。 题目描述 桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零),比如可能情形是 **oo***oooo,如果…

【高数:3 无穷小与无穷大】

【高数:3 无穷小与无穷大】 1 无穷小与无穷大2 极限运算法则3 极限存在原则4 趋于无穷小的比较 参考书籍:毕文斌, 毛悦悦. Python漫游数学王国[M]. 北京:清华大学出版社,2022. 1 无穷小与无穷大 无穷大在sympy中用两个字母o表示无…

第 375 场 LeetCode 周赛题解

A 统计已测试设备 模拟&#xff1a;记录当前已测试设备数量 class Solution { public:int countTestedDevices(vector<int> &batteryPercentages) {int res 0;int s 0;for (auto x: batteryPercentages) {if (x - s > 0) {res;s;}}return res;} };B 双模幂运算 …