JAVA中的内部类

前言

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

什么是JAVA中的内部类

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

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

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

内部类可以分为成员内部类和局部内部类两种,定义在方法中的内部类是局部内部类,大部分时候,内部类都是作为成员内部类定义。
成员内部类包括非静态内部类(无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
可见:

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

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

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

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

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

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

如果想自己区分到底调用哪里的变量,则可以使用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);
	}
}

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

注意:

  1. 匿名内部类必须继承一个父类或者实现一个接口(有且必须一个)
  2. 匿名内部类不能是抽象类,因为在创建匿名内部类的同时会创建其对象
  3. 匿名内部类不可定义构造器,因为他本身没有类名。如果继承接口,则只有隐形无参构造器(就是上述代码中的new ProductLocal(),括号中无参数值);如果继承类,则会用于和父类类似的构造器,也就是形参相同。
  4. 匿名内部类必须实现接口、父类中抽象方法,也可以重写父类中普通方法。
  5. 匿名内部类访问的局部变量,JAVA系统会自动使用final修饰,不可再次赋值。
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页