首页 > 技术文章 > Java内部类详解

codebetter 2019-12-03 16:38 原文

内部类特点

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

内部类与外部类的区别

  1. 内部类比外部类可以多使用三个修饰符: privateprotectedstatic ,外部类不可以使用 这三个
    修饰符 。
  2. 非静态内部类不能拥有静态成员。

非静态内部类

  1. 内部类一定是放在另一个类的类体部分(也就是类名后的花括号部分)定义
  2. 在非静态内部类里可以直接访问外部类的 private 成员
  3. 非静态内部类不能拥有静态成员
  4. 静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用访问其实例成员
  5. 不允许在外部类的静态成员中直接使用非静态内部类

外部类和非静态内部类的成员调用

当内部类的方法调用变量时,

  1. 首先在该方法中查找该变量名的局部变量

  2. 在内部类中查找该变量名的成员变量

  3. 在内部类所在的外部类中查询该变量名的成员变量

  4. 如果没有查找到则编译错误

  5. 如果内部类方法中的局部变量、内部类成员变量、外部类成员变量的名字相同,则可通过使用 this、外部类类名.this作为限定来区分 。

  6. 外部类不能直接访问内部类的成员,需要创建内部类的实例

    public class InnerClassTest {
    	public static void main(String[] args) {
    		Cow cow = new Cow(333);
    		cow.test();
    	}
    }
    
    class Cow {
    
    	private double weight;
    	private String var = "外部类成员变量";
    	
    	public Cow() {
    	}
    	
    	public Cow(double weight) {
    		this.weight = weight;
    	}
    	
    	public double getWeight() {
    		return weight;
    	}
    	
    	public void setWeight(double weight) {
    		this.weight = weight;
    	}
    	
    	private class CowLeg {
    	
    		private String color;
    		private double length;
    		private String var = "内部类成员变量";
    	
    		public CowLeg() {
    		}
    	
    		public CowLeg(String color, double length) {
    			this.color = color;
    			this.length = length;
    		}
    	
    		public String getColor() {
    			return color;
    		}
    	
    		public void setColor(String color) {
    			this.color = color;
    		}
    	
    		public double getLength() {
    			return length;
    		}
    	
    		public void setLength(double length) {
    			this.length = length;
    		}
    	
    		private void info() {
    			String var = "局部变量";
    			System.out.println("CowLeg color:" + color + "--" + "length:" + length);
    			System.out.println("Cow weight:" + weight);
                //同名变量引用
    			System.out.println("外部类成员变量:" + Cow.this.var);
    			System.out.println("内部类成员变量:" + this.var);//或CowLeg.this.var
    			System.out.println("局部变量:" + var);
    		}
    	}
    	
    	public void test() {
            //外部类引用内部类成员
    		new CowLeg("red", 20).info();
    	}
    
    }
    

    输出:

    CowLeg color:red--length:20.0
    Cow weight:333.0
    外部类成员变量:外部类成员变量
    内部类成员变量:内部类成员变量
    局部变量:局部变量

静态内部类

  1. 使用 static 修饰的成员内部类是静态内部类
  2. 如果使用 static 来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用 statlc 修饰的内部类被称为类内部类,有的地方也称为静态内部类 。
  3. 静态内部类可以包含静态成员, 也可以包含非静态成员
  4. 根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员
  5. 静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员

外部类和静态内部类的成员调用

public class StaticInnerClassTest {

	public static void main(String[] args) {
		new OuterClass.InnerClass().innerClassMethod();
		new OuterClass().outerClassMethod();
	}

}

class OuterClass {

	private String var1 = "外部类成员变量";
	private static String var2 = "外部类静态变量";
	
	static class InnerClass {
		// 静态内部类可以由静态变量和非静态变量
		private String innerVar = "静态内部类成员变量";
		private static String staticInnerVar = "静态内部类静态变量";
	
		public void innerClassMethod() {
			// System.out.println(var1); // 静态内部类无法调用外部类成员变量
			System.out.println(var2); // 静态内部类调用外部类类变量
			System.out.println(new OuterClass().var1); // 静态内部通过外部类的实例调用外部类成员变量
		}
	}
	
	public void outerClassMethod() {
		System.out.println(InnerClass.staticInnerVar);// 外部类调用内部类类变量
		System.out.println(new InnerClass().innerVar);// 外部类调用内部类成员变量
	}

}

输出:

外部类静态变量
外部类成员变量
静态内部类静态变量
静态内部类成员变量

局部内部类

局部内部类定义在方法中,且仅在该方法中有效。不能使用访问权限控制符和static修饰。

匿名内部类

创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接口。

  1. 匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。因
    此不允许将匿名内部类定义成抽象类。
  2. 匿名内部类不能定义构造器 。由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类
    可以定义初始化块 ,可以通过实例初始化块来完成构造器需要完成的事情 。
  3. 当通过实现接口来创建匿名内部类时 , 匿名内部类也不能显式创建构造器,因此匿名内部类只有一
    个隐式的无参数构造器,故 new 接口名后的括号里不能传入参数值 。
    但如果通过继承父类来创建匿名内部类时, 匿名 内部类将拥有和父类相似的构造器,此处的相似指
    的是拥有相同的形参列表 。
//调用接口创建匿名内部类
public class AnonymousInnerClassTest {

	public static void eatFruit(Fruit fruit) {
		System.out.println("fruit color :" + fruit.getColor() + " -- fruit taste :" + fruit.getTaste());
	}

	public static void main(String[] args) {
		eatFruit(new Fruit() {

			@Override
			public String getColor() {
				return "red";
			}

			@Override
			public String getTaste() {
				return "sweet";
			}

		});
	}
}

interface Fruit {

	public String getColor();

	public String getTaste();
}

//调用抽象类创建匿名内部类
class Anonymouslnner {
	public void test(Device d) {
		System.out.println("购买了一个 " + d.getName() + " ,花掉了 " + d.getPrice());
	}

	public static void main(String[] args) {
		Anonymouslnner ai = new Anonymouslnner();
		// 调用有参数的构造器创建 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);
	}
}

abstract class Device {
	private String name;
	
	public abstract double getPrice();
	
	public Device() {
	}
	
	public Device(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
}

调用接口创建输出:

fruit color :red -- fruit taste :sweet

调用抽象类创建输出:

购买了一个 电子示波器 ,花掉了 67.8
匿名内部类的初始化块 .. .
购买了一个 键盘 ,花掉了 56.2

JDK8之前匿名内部类访问的局部变量必须使用final修饰,JDK8不在需要final修饰,但是会默认改变量式final修饰的,即在匿名内部类中访问的局部变量不能再被赋值。

interface A {
	void test();
}

class ATest {
	public static void main(String[] args) {
		int age = 8; // ①
		A a = new A() {
			public void test() {
				// 在 Java 8 以前下面语句将提示错误 : age 必须使用 final 修饰
				// 从 Java 8 开始,匿名内部类、局部内部类允许访问非 final 的局部变量
				System.out.println(age);
			}
		};
		a.test();
		//age = 1; // 不能重新赋值
	}
}

推荐阅读