JAVA中的内部类

it2023-03-02  85

文章目录

前言什么是JAVA中的内部类非静态内部类静态内部类内部类的使用在外部类中使用内部类在外部类以外使用非静态内部类在外部类以外使用静态内部类局部内部类的使用匿名内部类的使用

前言

李刚老师《JAVA疯狂讲义》第5版,第6章学习笔记。

什么是JAVA中的内部类

大多数情况下,类会被定义为一个单独的程序单元,但是有些时候,也会把一个类放在另一个类的内部定义。定义在一个类内部的类就是内部类。

由于内部类定义在一个类的内部,因此除了public,还可以使用private、protected和static三个访问修饰符修饰(外部类不可使用这三个访问修饰符修饰)。 为什么会这样呢? 外部类的上一级程序结构是包,所以外部类只有两种作用域:

同一个包内项目任何位置 因此外部类只需要规定是这两种访问权限中的哪一种便可,只需使用public访问修饰符。 但是内部类的上一级程序结构是类,因此内部类就有四种作用域:同一个类内同一个包内不同包的父类、子类中项目中的任何位置 因此,内部类就可以使用四种访问修饰符修饰。 定义内部类非常简单,只需要把一个类放在另一个类的内部定义即可。类内部可以指任何位置,包括方法内等。

内部类可以分为成员内部类和局部内部类两种,定义在方法中的内部类是局部内部类,大部分时候,内部类都是作为成员内部类定义。 成员内部类包括非静态内部类(无static修饰)、静态内部类(staitc修饰)两种。

非静态内部类

public class Cow { //Cow的实例变量 private double weight; //Cow的重载构造器 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; } //实例变量的set、get方法 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.3,"黄色"); cl.info(); } public static void main(String[] args) { Cow cow = new Cow(350); cow.test(); } }

上述代码中,创建了一个外部类Cow,以及Cow的一个非静态内部类,CowLeg 可见:

Cow类中包含了一个test()方法,test方法中创建了一个CowLeg类的实例对象。在外部类中,使用内部类与使用普通类并没有很大区别。CowLeg内部类可以直接使用Cow类的私有变量weigthCow外部类中不可以直接使用CowLeg内部类的私有变量length、color如果Cow外部类想要访问CowLeg内部类的私有变量,则必须显性的创建CowLeg对象,通过这个对象来访问私有变量。Cow类的静态方法中,不能访问非静态内部类CowLeg非静态内部类CowLeg中不可包含静态方法、静态成员变量、静态初始化块等

注意: 为什么内部类可以直接访问外部类的静态变量,但是外部类不能访问内部类的静态变量呢?

因为,内部类的实例对象一定是依附于外部类的实例对象的。但是外部类的实例对象不一定需要有内部类的实例对象依附于他。

例如,牛腿类一定是依附于牛类的,只要有这个内部类的实例对象,就一定有外部类的实例对象,在非静态内部类的对象中,保存了一个它所寄生的外部类的对象的引用;但是创建牛类后,不一定有牛腿类,所以如果直接用牛类这个外部类调用内部类的实例变量的话,是有问题的。

综上,当在非静态内部类的方法中访问某个变量时:

系统首先在该方法内找是否存在该名字的局部变量,存在则使用如果不存在,在方法所在内部类中寻找该名字的成员变量,存在则使用如果不存在,在内部类所在的外部类中寻找该名字的成员变量,存在则使用如果不存在,系统出现编译错误

如果想自己区分到底调用哪里的变量,则可以使用this、外部类类名.this来区别变量。

静态内部类

static修饰的内部类就是静态内部类,静态内部类属于外部类本身,而不属于外部类的某个对象。(static修饰的对象都是类相关,而不是实例相关)。在接口中定义的内部类,默认使用public static修饰,因此,接口中的内部类只能是静态内部类。(接口中一般不会再定义内部接口,因为接口代表的是公共规范,如果定义在内部,就没有什么意义了。)

静态内部类本身可以包括静态成员,也可以包括非静态成员。但是,静态内部类只能访问外部类的静态成员,不能访问外部类的非静态成员。例如:

public class StaticInnerClassTest { private int test1 = 5; private static int test2 = 6; static class StaticInnerClass{ private static int test3 = 7; private int test4 = 8; public void StaticInnerTest() { // 下方代码报错 System.out.println(test1); // 下方代码输出6 System.out.println(test2); } } public static void main(String[] args) { StaticInnerClass a = new StaticInnerClass(); a.StaticInnerTest(); } }

内部类的使用

内部类的使用可以分为三个场景讨论:在外部类中使用内部类、在外部类以外使用非静态内部类、在外部类以外使用静态内部类。

在外部类中使用内部类

这种情况下,内部类的使用和普通类的使用没有太大区别。但需要注意,不要在非静态成员中使用静态内部类

在外部类以外使用非静态内部类

外部类以外使用非静态内部类,不同访问修饰符的访问权限不同(请参考:JAVA中的访问控制符)。 例如:

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("在外部类以外使用非静态内部类"); } }

注意: 非静态内部类的构造器必须使用外部类对象来调用。

同时,我们知道,子类构造器总会调用父类的构造器。因此,在创建非静态内部类的子类时,必须保证,可以让子类构造器调用非静态内部类的构造器,而非静态内部类的构造器又必须使用外部类对象来调用。

具体例如:

class SubClass extends Out.In{ //定义SubClass类的构造器 public SubClass(Out out) { out.super("我是SubClass类的构造器"); } }

可见,非静态内部类的子类的构造器参数为内部类对应的外部类,那么这样就保证了,在构建这个子类的对象时,一定会有外部类对象,也就符合上面所述的逻辑。这里的super就代指SubClass的父类 In,可以把out.super()视为 out.In(),也就是这里调用了父类的构造器。

注意: 非静态内部类的子类可以是外部类,但是无论这个子类是内部类,还是外部类,都必须保证,该子类的对象有可以依附的相应父类外部类的对象。

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

静态内部类是外部类类相关的,不是外部类对象相关的,因此,创建静态内部类的对象无需依附于外部类对象,例如:

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(); } }

可见,在外部类以外使用静态内部类,无需依附于外部类对象。同样的,静态内部类的子类也无需依附于外部类对象。

注意: 内部类可以是做外部类的一个成员,那么能否类似于方法,在外部类的子类中,重写父类中的子类呢? 不可以。因为外部类相当于内部类的一个作用空间,即使外部类和其子类中包含同一个名字的内部类,二者的作用空间也是不同的。在调用的时候,还是要在前面写上对应的外部类的类名,既然这样,就无所谓重写不重写了,反正都是不一样的。

局部内部类的使用

局部内部类就是指定义在方法中的类,局部内部类仅在方法内有效。由于所有的局部成员的作用域都是方法,因此,局部内部类永远不能使用static以及访问修饰符修饰。 例如:

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 = 1; is.b = 2; } }

可见,在方法中使用局部内部类和一般的类的使用基本没有差别。 实际上,局部内部类的应用场景非常少,因为类的构建是希望反复使用,但局部内部类只能在当前方法中使用,因此实际开发中很少使用。

匿名内部类的使用

匿名内部类用于创建只需要使用一次的类,创建匿名内部类时会立刻创建该类对象,该类随后立刻消失。最常见的使用场景是,需要创建某个接口类型的对象,例如:

interface ProductLocal{ public double getPrice(); public String getName(); } public class AnonymousTest { public void test(ProductLocal P) { System.out.println("购买了一个"+P.getName()+",花费了"+P.getPrice()); } public static void main(String[] args){ AnonymousTest ta = new AnonymousTest(); ta.test(new ProductLocal() { public double getPrice() { return 10.5; } public String getName() { return "香蕉茄子大菠萝"; } }); } }

上述代码中,首先定义了一个ProductLocal接口,包含getPrice()、getName()两个方法。随后定义了AnonymousTest类,该类中定义了test()方法,该方法的参数是接口类型的对象。 随后在main函数中调用AnonymousTest类的test()方法,则需要一个ProductLocal接口对象,但是目前并没有类实现该对象,这个方法我可能在只会在这里用一次,单独再创建一个类来实现该接口有点浪费,所以可以使用匿名内部类。 可见,定义匿名内部类时,无需使用class关键字,定义时可以直接生成该匿名内部类对象。 上述可以替换为:

interface ProductLocal{ public double getPrice(); public String getName(); } class AnonymousClass implements ProductLocal{ public double getPrice() { return 10.5; } public String getName() { return "香蕉茄子大菠萝"; } } public class AnonymousTest { public void test(ProductLocal P) { System.out.println("购买了一个"+P.getName()+",花费了"+P.getPrice()); } public static void main(String[] args){ AnonymousTest ta = new AnonymousTest(); AnonymousClass p = new AnonymousClass(); ta.test(p); } }

上述代码就是创建了一个实现类,来调用方法。显然,使用匿名内部类的方式代码更为简洁。

注意:

匿名内部类必须继承一个父类或者实现一个接口(有且必须一个)匿名内部类不能是抽象类,因为在创建匿名内部类的同时会创建其对象匿名内部类不可定义构造器,因为他本身没有类名。如果继承接口,则只有隐形无参构造器(就是上述代码中的new ProductLocal(),括号中无参数值);如果继承类,则会用于和父类类似的构造器,也就是形参相同。匿名内部类必须实现接口、父类中抽象方法,也可以重写父类中普通方法。匿名内部类访问的局部变量,JAVA系统会自动使用final修饰,不可再次赋值。
最新回复(0)