前言
李刚老师《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类的私有变量weigth
- Cow外部类中不可以直接使用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修饰,不可再次赋值。