Java学习之旅第二季-19:内部类

Java 中可以将一个类或与类同等地位的类型声明在类/接口,方法,代码块甚至表达式中,这样就可以把一些逻辑相关的代码组织在一起,提升了封装性,并能控制其可见性。这种就可以统称为内部类。

本小节以类为例介绍在其声明类的语法,一共有 4 中形式:成员内部类、静态内部类、局部内部类、匿名内部类。

19.1 成员内部类

成员内部类(Member Inner Class)是指声明在类中作为其成员的非static类,可使用任意访问控制修饰符修饰该类。

成员内部类也可以使用 abstract 或 final 修饰,甚至可以是密封类;它能继承其他类或实现接口,访问级别足够的话也能被外部类的子类继承。这种内部类能访问外部类的所有成员。比如下面的示例中,OuterClass 类中就声明了一个额成员年内部类 InnerClass1 :

public class OuterClass1 {
    public class InnerClass1 {

    }
}

要创建成员内部类的实例,先要有外部类的实例:

OuterClass1.InnerClass1 innerClass1 = new OuterClass1().new InnerClass1();

或者:

OuterClass1 outerClass=new OuterClass1();
OuterClass1.InnerClass1 innerClass1= outerClass.new InnerClass1();

内部类中访问外部类的成员:

  • 没有与外部类中名称相同的成员,直接使用即可
  • 有与外部类中名称相同的成员,使用OuterClass.this.member

将上面的类进行构造如下:

public class OuterClass1 {
    int num = 1;
    String name = "摸鱼的老谭1";

    public class InnerClass1 {
        String name = "摸鱼的老谭2";

        public void print() {
            String name = "摸鱼的老谭3";
            System.out.println(num);                
            System.out.println(name);
            System.out.println(this.name);
            System.out.println(OuterClass1.this.name);
        }
    }
}

调用测试代码:

static void main() {
    OuterClass1 outerClass=new OuterClass1();
    OuterClass1.InnerClass1 innerClass1= outerClass.new InnerClass1();
    innerClass1.print();
}

结果是:

1
摸鱼的老谭3
摸鱼的老谭2
摸鱼的老谭1

结论就很明显了,直接使用变量,采用的是就近原则,首先是本方法中的局部变量,若没有,则找更远的类成员,还是没有找到的话,就找外部类的成员。

使用this当然就限定了在本类中找成员变量,如果本类没有声明,则直接报错。

所以希望找到的是外部类的成员,则语法上要使用 OuterClass.this.name 这种写法。

19.2 静态内部类

静态内部类(Static Nested Class)也称为嵌套类,使用 static 修饰的内部类。声明在接口和枚举中的类自动就是public static修饰的。

静态内部类使用示例:

public class OuterClass2 {
 	public static class InnerClass2{

    }
}

创建其实例的代码如下:

OuterClass2.InnerClass2 innerClass2 = new OuterClass2.InnerClass2();

静态内部类只能访问外层的静态变量与静态方法。当然访问本类中的成员并无特殊要求。

public class OuterClass2 {
	static int num = 1;
	String name = "摸鱼的老谭";
	
 	public static class InnerClass2{
		public void print() {
            System.out.println(num);
            System.out.println(name);   // name 是外层类中声明的非static变量,访问不到
        }
    }
}

19.3 局部内部类

局部内部类(Local Inner Class),是定义在方法、构造方法或代码块中的内部类。它只能在其定义的范围内使用,并且通常用于封装仅在该范围中需要的逻辑。

局部内部类不能被public、protected、private、static 等关键字修饰,但可以使用 abstract 或 final 修饰。

声明示例如下:

public class OuterClass3 {
    public void method() {
        class InnerClass3 {
            
        }
    }
}

局部内部类中的非静态代码块及方法中可以直接访问外部类中的所有成员和本作用域中的所有变量,但静态代码块及方法中只可以直接访问外部类中的所有静态成员。

public class OuterClass3 {
    int num1 = 1;
    static int num2 = 2;

    public void method() {
        int num3 = 3;
        class InnerClass3 {
            {
                System.out.println(num1);
                System.out.println(num2);
                System.out.println(num3);
            }

            void innerMethod() {
                System.out.println(num1);
                System.out.println(num2);
                System.out.println(num3);
            }

            static {
                System.out.println(num2);
            }

            static void innerStaticMethod() {
                System.out.println(num2);
            }
        }
    }
}

上述代码能正常访问到的都进行的输出,没有输出的就是不能访问到的。

访问与局部内部类在同一个作用域中的局部变量必须是 final 或相当于 final 的。这句话不太好理解,以上面的代码为例,局部内部类是在方法method中声明的,所以在method方法中声明的局部变量 num3 就适用刚才的规则。现在简化下代码,且针对 num3 做一点操作:

public class OuterClass3 {
    public void method() {
        int num3 = 3;
        class InnerClass3 {
            void innerMethod() {
                System.out.println(num3);        // 编译错误
                num3++;
            }
        }
    }
}

在第 6 行访问了num3之后,对num3进行了自增,此时程序报错了,提示信息为:

Variable 'num3' is accessed from within inner class, needs to be final or effectively final

意味着这种情况下,我们没有办法更新 num3 的值,num3 虽然没有显式使用 final 修饰,但是此时它就相当于 final 变量,即不能被重新赋值,如果没有修改它的值就没事,一旦修改就违反了 final 变量的限制。

在开发如果有这样的情况,只有将局部变量声明到类中,这样就不会有编译错误了。

19.4 匿名内部类

匿名内部类(Anonymous Inner Class)是一种特殊形式的没有类名的局部内部类,它l适合临时继承一个类或实现一个接口,通常用于简化代码和一次性使用的场景。

匿名内部类本质上是隐式继承了一个父类或实现了一个接口,但是并没有 class、extends 和 implements 等关键字。

使用时直接使用 new 关键字在一条语句中声明和实例化,且不可复用。

比如现在有一个接口:

public interface Interface1 {
    void method1();

    void method2(int num);
}

常规情况下,应该声明一个该接口的子类,然后在实例化该子类并调用其中的方法。

但是有时我们只需临时使用并不想创建新的类,那么就可以使用这样的写法:

Interface1 obj1 = new Interface1() {
    @Override
    public void method1() {

    }

    @Override
    public void method2(int num) {

    }
};

在new关键字的后面,似乎是直接创建了接口的实例,但是在下面的代码块中实现了接口中的抽象方法,这应该理解为创建了一个没有名称的该接口子类的实例。注意这是一句语句,结尾有分号。

开发者更常见的场景是方法的形参是一个接口类型,在传入实参时直接使用匿名内部类的写法:

public class Interface1Test {
    public void method(Interface1 obj) {
        obj.method1();
        obj.method2(1);
    }
}

调用此方法时可以这么写:

Interface1Test test=new Interface1Test();
test.method(new Interface1() {
    @Override
    public void method1() {

    }

    @Override
    public void method2(int num) {

    }
});

这样传入的匿名内部类的实例既是接口的Interface1的子类实例,其中的方法也确定是实现了的。

匿名内部类中的方法可以访问外部类的成员和该匿名内部类同作用域中的局部变量(必须是 final 或实际上是 final ),当然静态环境下不能直接访问非静态成员。

另外还要注意识别双大括号初始化的写法,特别是直接实现一个类的匿名子类:

public class Child implements Interface1{
    @Override
    public void method1() {

    }

    @Override
    public void method2(int num) {

    }
}

上面的类是一个显式声明的Interface1类型的子类,如果针对这个Child类声明一个匿名内部类的实例:

 Interface1 obj1 = new Child() {
    {
        method1();
        method2(2);
    }
};

上面的写法并不是简单的创建Child的实例,而是创建其子类的实例,只是采用了匿名内部类的写法。

可以看到在Child( )后有两对大括号,这就是双大括号初始化的语法,其实里面的一对大括号就是类的成员之一:代码块。代码块中是可以直接编写语句的,调用类的方法也是可行的。

由于匿名内部类由于没有类名,编译之后的class文件名,会产生 OuterClass$数字.class 的文件。

19.5 接口嵌套

除了上述四种内部类的使用方式之外,接口和类也可以在各自内部使用:

在类中中声明的接口,可以使用任意访问控制修饰,按照接口的使用语法即可。

在接口中声明的类或接口,其默认修饰符是 public static,访问控制修饰符可以改为默认级别。

由于开发中使用较少,就不再举例了。

19.6 小结

本小节介绍了Java中的4种内部类形式:成员内部类、静态内部类、局部内部类和匿名内部类。成员内部类声明在类中,可访问外部类所有成员,创建需要外部类实例。静态内部类使用static修饰,只能访问外部类的静态成员。局部内部类定义在方法或代码块中,作用域有限,访问局部变量需为final。匿名内部类用于一次性实现接口或继承类,无需显式声明类名。每种内部类都有特定使用场景和访问规则,合理使用能提升代码封装性和组织性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值