java学习笔记 内部类

本文详细介绍了Java内部类的概念、分类及其应用场景,包括非静态内部类、静态内部类、局部内部类和匿名内部类的特点与使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义内部类,只需要把一个类放在另一个类内部定义即可。“类内部”包括类中的任何位置,甚至在方法中也可以定义内部类(方法里定义的内部类被称为局部内部类)。成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员,成员内部类分为两种:静态内部类和非静态内部类,使用static修饰的成员内部类是静态内部类,没有使用static修饰的成员内部类是非静态内部类。

内部类的作用:
1.内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类
2.内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问
3.匿名内部类适合于创建那些仅需要依次使用的类

内部类作为其外部类的成员,可以使用任意访问修饰符如private、protected、public

一,非静态内部类

eg:类Cow里定义了一个CowLeg非静态内部类,并在CowLeg类的实例方法中直接访问Cow的private访问权限的实例变量

package meetdx;
public class Cow
{
    private double weight;
    // 外部类的两个重载的构造器
    public Cow(){}
    public Cow(double weight)
    {
        this.weight = weight;
    }
    // 定义一个非静态内部类
    private class CowLeg
    {
        // 非静态内部类的两个实例变量
        private double length;
        private String color;
        // 非静态内部类的两个重载的构造器
        public CowLeg(){}
        public CowLeg(double length , String color)
        {
            this.length = length;
            this.color = color;
        }
        public void setLength(double length)
        {
            this.length = length;
        }
        public double getLength()
        {
            return this.length;
        }
        public void setColor(String color)
        {
            this.color = color;
        }
        public String getColor()
        {
            return this.color;
        }
        // 非静态内部类的实例方法
        public void info()
        {
            System.out.println("当前牛腿颜色是:"
                + color + ", 高:" + length);
            // 直接访问外部类的private修饰的成员变量
            System.out.println("本牛腿所在奶牛重:" + weight);  
        }
    }
    public void test()
    {
        CowLeg cl = new CowLeg(1.12 , "黑白相间");
        cl.info();
    }
    public static void main(String[] args)
    {
        Cow cow = new Cow(378.9);
        cow.test();
    }
}
/*
当前牛腿颜色是:黑白相间, 高:1.12
本牛腿所在奶牛重:378.9
*/

这里写图片描述

开篇提到过,非静态内部类里可以直接访问外部类的private成员,因为在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用,如图所示,当在非静态内部类的方法内访问某个变量,系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量;若不存在,到该方法所在的内部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;若不存在,到该内部类所在的外部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;如果依然不存在,系统出现编译错误,提示找不到该变量。
这里写图片描述

eg:如果外部类成员变量、内部类成员变量与内部类里的方法的局部变量同名,可以使用this、外部类类名.this作为限定区分

package meetdx;

public class DiscernVariable
{
    private String prop = "外部类的实例变量";
    private class InClass
    {
        private String prop = "内部类的实例变量";
        public void info()
        {
            String prop = "局部变量";
            // 通过 外部类类名.this.varName 访问外部类实例变量
            System.out.println("外部类的实例变量值:"
                + DiscernVariable.this.prop);
            // 通过 this.varName 访问内部类实例的变量
            System.out.println("内部类的实例变量值:" + this.prop);
            // 直接访问局部变量
            System.out.println("局部变量的值:" + prop);
        }
    }
    public void test()
    {
        InClass in = new InClass();
        in.info();
    }
    public static void main(String[] args)
    {
        new DiscernVariable().test();
    }
}

注意:非静态内部类的成员可以访问外部类的private成员,但反过来就不成立了,非静态内部类的成员只有在非静态内部类范围内是可知的,并不能被外部类直接使用。

根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例等。
非静态内部类里不能有静态方法、静态成员变量、静态初始化块。如下程序将引发编译错误:

public class InnerNoStatic
{
    private class InnerClass
    {
        /*
        下面三个静态声明都将引发如下编译错误:
        非静态内部类不能有静态声明
        */
        static
        {
            System.out.println("==========");
        }
        private static int inProp;
        private static void test(){}
    }
}

二,静态内部类(类内部类)

如果使用static修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。静态内部类可以包含静态成员,也可以包含非静态成员,根据静态成员不能访问非静态成员规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员,即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。
eg:

public class StaticInnerClassTest
{
    private int prop1 = 5;
    private static int prop2 = 9;
    static class StaticInnerClass
    {
        // 静态内部类里可以包含静态成员
        private static int age;
        public void accessOuterProp()
        {
            // 下面代码出现错误:
            // 静态内部类无法访问外部类的实例变量
            System.out.println(prop1);
            // 下面代码正常
            System.out.println(prop2);
        }
    }
}

静态内部类是外部类的一个静态成员,外部类的所有方法、所有初始化块中可以使用静态内部类来定义变量、创建对象等。外部类依然不能访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。

public class AccessStaticInnerClass
{
    static class StaticInnerClass
    {
        private static int prop1 = 5;
        private int prop2 = 9;
    }
    public void accessInnerProp()
    {
        // System.out.println(prop1);
        // 上面代码出现错误,应改为如下形式:
        // 通过类名访问静态内部类的类成员
        System.out.println(StaticInnerClass.prop1);
        // System.out.println(prop2);
        // 上面代码出现错误,应改为如下形式:
        // 通过实例访问静态内部类的实例成员
        System.out.println(new StaticInnerClass().prop2);
    }
}

java允许在接口里定义内部类,接口里定义的内部类默认使用public static修饰,即接口内部类只能是静态内部类。

三,使用内部类

1.在外部类内部使用内部类
在外部类内部使用内部类与平常使用普通类没有太大区别,一样可以直接通过内部类类名来定义变量,通过new调用内部类构造器创建实例。
唯一区别是:不要在外部类的静态成员中使用非静态内部类

2.在外部类以外使用非静态内部类
该情况下,内部类不能使用private访问控制权限。具体如下:
(1)省略访问控制符的内部类,只能被与外部类处于同一个包中的其他类所访问
(2)protected,可被与外部类处于同一个包中的其他类和外部类的子类访问
(3)public,可以在任何地方被访问

package meetdx;

class Out
{
    // 定义一个内部类,不使用访问控制符,
    // 即只有同一个包中其他类可访问该内部类
    class In
    {
        public In(String msg)
        {
            System.out.println(msg);
        }
    }
}
public class CreateInnerInstance
{
    public static void main(String[] args)
    {
        Out.In in = new Out().new In("测试信息");
        /*
        上面代码可改为如下三行代码:
        使用OutterClass.InnerClass的形式定义内部类变量
        Out.In in;
        创建外部类实例,非静态内部类实例将寄存在该实例中
        Out out = new Out();
        通过外部类实例和new来调用内部类构造器创建非静态内部类实例
        in = out.new In("测试信息");
        */
    }
}

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

因为静态内部类是外部类类相关的,因此创建静态内部类对象时无需创建外部类对象,在外部类以外的地方创建静态内部类实例语法:
new OuterClass.InnerConstructor();
eg:

class StaticOut
{
    // 定义一个静态内部类,不使用访问控制符,
    // 即同一个包中其他类可访问该内部类
    static class StaticIn
    {
        public StaticIn()
        {
            System.out.println("静态内部类的构造器");
        }
    }
}
public class CreateStaticInnerInstance
{
    public static void main(String[] args)
    {
        StaticOut.StaticIn in = new StaticOut.StaticIn();
        /*
        上面代码可改为如下两行代码:
        使用OutterClass.InnerClass的形式定义内部类变量
        StaticOut.StaticIn in;
        通过new来调用内部类构造器创建静态内部类实例
        in = new StaticOut.StaticIn();
        */
    }
}

由上可以看出,不管是静态内部类还是非静态内部类,它们声明变量的语法完全一样,区别只是在创建内部类对象时,静态内部类只需使用外部类即可调用构造器,而非静态内部类必须使用外部类对象来调用构造器。

四,局部类部类

如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法内有效,不能在外部类的方法以外的地方使用,因此局部内部类不能使用访问控制符和static修饰符。
eg:需要用局部内部类定义变量、创建实例或派生子类,只能在局部内部类所在的方法内进行

public class LocalInnerClass
{
    public static void main(String[] args)
    {
        // 定义局部内部类
        class InnerBase
        {
            int a;
        }
        // 定义局部内部类的子类
        class InnerSub extends InnerBase
        {
            int b;
        }
        // 创建局部内部类的对象
        InnerSub is = new InnerSub();
        is.a = 5;
        is.b = 8;
        System.out.println("InnerSub对象的a和b实例变量是:"
            + is.a + "," + is.b);
    }
}

编译上面程序,生成三个class文件:
这里写图片描述
表明局部内部类class文件格式:OuterClass$NInnerClass.class,多了一个数字,因为同一个类里不可能有两个同名的成员内部类,而同一个类里则可能有两个以上同名的局部内部类(处于不同方法中),java用N作以区分。

五,匿名内部类

匿名内部类适合创建那种只需要一次使用的类,创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用,定义时必须继承一个父类或实现一个接口,但最多只能继承一个父类,或实现一个接口。
关于匿名内部类的两条规则:
1.匿名内部类不能使抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。
2.匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的事情
eg:

interface Product
{
    public double getPrice();
    public String getName();
}
public class AnonymousTest
{
    public void test(Product p)
    {
        System.out.println("购买了一个" + p.getName()
            + ",花掉了" + p.getPrice());
    }
    public static void main(String[] args)
    {
        AnonymousTest ta = new AnonymousTest();
        // 调用test()方法时,需要传入一个Product参数,
        // 此处传入其匿名实现类的实例
        ta.test(new Product()
        {
            public double getPrice()
            {
                return 567.8;
            }
            public String getName()
            {
                return "AGP显卡";
            }
        });
    }
}

当通过实现接口来创建匿名内部类时,匿名内部类不能显式创建构造器,因此匿名内部类只有一个隐式的无参数构造器,故new接口名后的括号里不能传入参数值。
但若通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,相似指的是有相同的形参列表。

abstract class Device
{
    private String name;
    public abstract double getPrice();
    public Device(){}
    public Device(String name)
    {
        this.name = name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return this.name;
    }
}
public class AnonymousInner
{
    public void test(Device d)
    {
        System.out.println("购买了一个" + d.getName()
            + ",花掉了" + d.getPrice());
    }
    public static void main(String[] args)
    {
        AnonymousInner ai = new AnonymousInner();
        // 调用有参数的构造器创建Device匿名实现类的对象
        ai.test(new Device("电子示波器")
        {
            public double getPrice()
            {
                return 67.8;
            }
        });
        // 调用无参数的构造器创建Device匿名实现类的对象
        Device d = new Device()
        {
            // 初始化块
            {
                System.out.println("匿名内部类的初始化块...");
            }
            // 实现抽象方法
            public double getPrice()
            {
                return 56.2;
            }
            // 重写父类的实例方法
            public String getName()
            {
                return "键盘";
            }
        };
        ai.test(d);
    }
}

在java8之前,java要求被局部内部类、匿名内部类访问的局部变量必须使用final修饰,从java8开始这个限制被取消了,如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用final修饰。java8将该功能称为“effectively final”,即对于被匿名内部类访问的局部变量,可以用finale修饰,也可以不用final修饰,但必须按照有final修饰的方式来用——即一次赋值以后不能重新赋值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值