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

内部类 --- (寄生的哲学)

内部类总共有 4 种(静态内部类、非静态内部类、局部内部类、匿名内部类)

作用:

一:内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。

二:内部类可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问,但是外部类不能访问内部类的实现细节,例如内部类的成员变量。

三:匿名内部类适合用于创建那些只需要创建一次使用的类。

四:内部类比外部类可以多使用三个修饰符:private、protected、static

五:非静态内部类不能拥有静态成员。

1.非静态内部类:

非静态内部类定义在外部类里面

public class Demo {

    private String name;

    private Integer age;
    
    //定义构造器重载
    public Demo(){}
    
    public Demo(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    //定义一个非静态内部类
    private class InnerDemo {
        //非静态内部类
        private double length;
        private String color;
        public InnerDemo(){}
    
        public InnerDemo(double length, String color) {
            this.length = length;
            this.color = color;
        }
        // 此处省略 length  的getter 和 setter 方法
        .....................

        //非静态内部类实例方法
        public void info() {
            System.out.print("名字是:" + name + ": " + "年龄是: " + age +"肤色是: " + color + "身高是: " + length);
        }
        
    }

     public void test() {
        
        var innerDemo = new InnerDemo(180, "黄");
        innerDemo.info();
    }

    public static void main(String[] args) {
        Demo demo = new Demo("李白", 30);
        demo.test();
    }

}

当非静态成员内部类的方法访问某个变量的时候,系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量;如果不存在则查找该方法所在的内部类中是否存在该名字的成员变量, 如果不存在,则到外部类中查找是否存在该名字的成员变量,如果依然不存在,则会报编译错误:提示找不到该变量。

因此,如果外部类成员变量、内部类成员变量与内部类里面的方法同名,则可以通过使用 this、外部类类名.this 作为限定区分。

可以参考如下模板:

public class Demo {
    private String name = "外部类的成员变量";
    
    private class InClass {
        private String name = "内部类的成员变量";
        public void info(){
            var name = "局部变量";
            System.out.print("外部类的实例变量值:" + Demo.this.name);
            
            //通过 this.varName 访问内部类的成员变量
            System.out.print("内部类的成员变量:" + this.name);

            //直接访问局部变量
            System.out.print("局部变量的值:" + name);
        }
    }
    
    public void test() {
        var inClass = new InClass();
        inClass.info();
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        demo.test();
        
    }

}

2. 静态内部类

使用 static 修饰的内部类被称为静态内部类,它属于外部类本身,不属于外部类某个实例对象。

静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。

public class Demo {
    private String name = "李白";

    private static int age = 2;

    static class StaticInnerClass {
        //静态内部类可以包含静态成员
        private static String womanName = "李清照";
        
        public void info() {
            //下面代码出现错误,静态内部类不能访问外部类的实例变量
            System.out.print(name);
            //下面代码正常
            System.out.print(age);
        }

    }

}

静态内部类是外部类的一个成员,因此外部类的所有方法、所有初始化块中可以使用静态内部类来定义变量、创建对象等

public class Demo {
    
    static class StaticInnerClass {
        private static int age = 10;
        private String name = "李白";
    }
    
    public void info() {
    
        //System.out.print(age)
        //上面代码出现错误,应改为如下格式
        //通过类名访问静态内部类的类成员
        System.out.print(StaticInnerClass.age);
        //System.out.print(name);
        //上面代码出现错误,应改为如下格式
        //通过实例访问静态内部类的实例成员
        System.out.print(new StaticInnerClass().name);
    }

}

Java 还允许在接口里定义内部类,接口里定义的内部类默认使用 public static 修饰,也就是说接口内部类只能是静态内部类。

3.使用内部类:

1.在外部类内部使用内部类

在外部类内部使用内部类的时,与平常使用普通类没有太大的区别。一样可以直接通过内部类类名来定义变量,通过 new 调用内部类构造器来创建实例。

唯一的区别是:不要在外部类的静态成员(包括静态方法和静态初始化)中去使用非静态内部类,因为静态成员不能访问非静态成员。

在外部类内部定义内部类的子类与平常定义子类也没有太大的区别。

2.在外部类外部使用非静态内部类

如果想在外部类外部使用非静态内部类,则内部类不能使用 private 访问控制权限,private 修饰的内部类只能在外部类内部使用。对于使用其他访问控制符修饰的内部类,则能在访问控制符对应的访问权限内使用。

由于非静态内部类的对象必须寄生在外部类的对象里,因此创建非静态内部类对象之前,必须先创建其外部类对象。在外部类外面创建内部类实例的语法如下:

outerInstance.new InnerContructor();

当创建一个子类的时候,子类构造器总会调用父类的构造器,因此在创建非静态内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内部类的构造器时,必须存在一个外部类对象

class Out  {

    class Inner {
        public Inner(String name) {
            System.out.print(name);
        }
    }
}


public class Demo extends Out.Inner {
    //显式定义构造器
    public Demo(Out out) {
        out.super("Hello")
    }

}

非静态内部类的子类不一定是内部类,它也可以是外部类。但非静态内部类的子类实例一样需要保留一个引用,该引用指向其父类所在外部类的对象。也就是说,如果有一个内部类子类的对象存在,则一定存在与之对应的外部类对象。

3.在外部类以外使用静态内部类。

因为静态内部类是外部类类相关的,因此创建静态内部类对象时无需创建外部类对象。创建语法如下:

new OutClass.InnerClass();

相比之下,使用静态内部类比使用非静态内部类要简单很多,只要把外部类当成静态内部类的包空间即可。因此当程序需要使用内部类时,应该优先考虑使用静态内部类。

4.局部内部类

把内部类定义在方法里面,这个就是局部内部类。局部内部类因为只能在方法里面有效,不能在外部类方法以外使用。所以不能使用访问控制符和 static 修饰符修饰。

局部内部类是一份非常鸡肋的语法,在实际开发中很少定义局部内部类,这是因为局部内部类的作用域太小了;只能在当前方法中使用。大部分时候,定义一个类之后,当然希望多次复用这个类,但局部内部类无法离开它所在的方法,因此在实际开发中很少使用。

5.匿名内部类

匿名内部类适合创建那种只需要创建一次使用的类,匿名内部类不能重复使用。

匿名内部类必须继承一个父类或者实现一个接口,但最多只能继承一个父类,或实现一个接口。

关于匿名内部类还有如下两条规则:

1.匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象,所以不能把不允许将匿名内部类定义成抽象类。

2.匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但可以定义初始化块,可以通过初始化块来完成构造器需要完成的事。

interface Animal {
    double getPrice();
    String getName();
}


public class Demo {

    public void test(Animal a) {

        System.out.print("购买了一个: " + a.getName() + "价格是:" + a.getPrice());
    }

    public static void main(String[] args) {
        
        var tar = new Demo ();
        //调用 test()方法时需要传入一个 Animal 参数
        //此处传入匿名实现类的实例
        tar.test(new Animal() {
            public double getPrice() {
                return 567.9;
            }
        
            public String getName() {

                return "二哈";
            }

        });
    }

}

上面程序中的 Animal 类定义了一个 test()方法,该方法需要一个 Animal 对象作为参数,但 Animal 只是一个接口,无法直接创建对象,因此此处创建一个 Animal 接口实现类的对象传入该方法----如果这个 Animal 接口实现类需要重复用,则应该将实现类定义为一个独立类:如果这个 Animal 接口实现类只需一次使用,则可以采用上面程序中的方式,定义一个匿名内部类。

定义匿名内部类无需 class 关键字,而是在定义匿名内部类的时候直接生成该匿名内部类的对象。

由于匿名内部类不能是抽象类,所以匿名内部类必须实现它的抽象类或者接口里面包含的所有抽象方法。

当通过实现接口创建匿名内部类时,匿名内部类不能显式地定义构造器,因此匿名内部类只有一个隐式的无参构造器,故 new 接口名后的括号里不能传入参数值。

但如果是通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,此处的相似指的时拥有相同的形参列表。

abstract class Device {
    private String name;

    public abstract double getPrice();
    public Device(){}
    public Device(String name) {
        this.name = name;
    }
    
    //此处省略 setter 和 getter 方法
    ...


}


public class AnonymousInner {

    public void test(Device d) {
        System.out.print("购买了一个"+d.getName() + ", 花掉了"+d.getPrice());
    }

    public static void main(String[] args) {

        var ai = new AnonymousInner();
        
        ai.test(new Device("电饭煲") {
        
            public double getPrice(){
                return 500.2;
            }
        });
    
        //调用无参数的构造器创建Device 匿名实现类的对象
        var d = new Device(){
            //初始化块
            {
                System.out.print("匿名内部类的初始化块....");
            }
            //实现抽象方法
            public double getPrice() {
                return 56.2;
            }
        
            //重写父类的实例方法
            public String getName() {
                return "鼠标";
            }
            
            
        };
        ai.test(d);
 
        
    }

    
}

上面的程序创建了一个抽象父类 Device 类,这个抽象父类包含两个构造器:一个无参数的,一个有参数的。当创建以Device 为父类的匿名内部类时,既可以传入参数,也可以不传入参数。

当创建匿名内部类时,必须实现接口或抽象类里面的所有抽象方法。如果有需要,也可以重写父类中的普通方法。

在 Java 8 之前,Java 要求被局部内部类、匿名内部类访问的局部变量必须使用 final 修饰,从 Java 8 开始这个限制被取消了,Java 8更加智能,如果局部变量被匿名内部类访问,那么该局部变量相当于自动加上了 final 修饰。


http://www.kler.cn/a/467460.html

相关文章:

  • Rocketmq 探索MQClientFactoryScheduledThread线程工作
  • HarmonyOS:@Builder装饰器:自定义构建函数
  • 如何使用脚手架工具开始,快速搭建一个 Express 项目的基础架构
  • 在Spring Boot项目中使用Zookeeper和Curator实现高效、可靠的分布式锁
  • 深度学习中的步数指的是什么
  • React虚拟DOM:理解和应用
  • MQ消息队列
  • 【GBT32960协议学习系列】GBT 32960协议学习大纲
  • 【Seed-Labs 2.0】Buffer Overflow Attack Lab (Server Version)
  • [读书日志]从零开始学习Chisel 第二篇:Scala的变量与函数(敏捷硬件开发语言Chisel与数字系统设计)
  • 每日一学——自动化工具(Ansible)
  • 【练习】PAT乙1033 旧键盘打字
  • SQL把字符串按逗号分割成记录
  • 19.2、windows安全分析与防护
  • CSP初赛知识学习计划(第一天)
  • Linux相关开发工具
  • wordpress主题开发之function.php的10大作用
  • Spring Boot中的 6 种API请求参数读取方式
  • 如何解析和处理电商平台的按图搜索商品API返回值?
  • Seata 使用教程:全面解锁分布式事务管理
  • 32单片机从入门到精通之开发环境——集成开发环境(IDE)(五)
  • 源码理解 UE4中的 FCookStatsManager::FAutoRegisterCallback RegisterCookStats
  • MySQL数据结构选择
  • 13-Gin 中使用 GORM 操作 mysql 数据库 --[Gin 框架入门精讲与实战案例]
  • 软件测试面试题(一)
  • AI来帮忙:蛋白纯化不用慌