内部类概述:
把类定义在其他类的内部,这个定义在其他类内部的类也被称为内部类(嵌套类),包含内部类的类也被称为外部类(宿主类)。
非静态内部类访问特点
a:非静态内部类可以直接访问外部类的成员,包括私有。
因为内部类被当成其外部类成员,同一个类的成员之间可以相互访问,但外部类不能访问内部类的实现细节,例如内部类的成员变量
b:外部类要访问内部类的成员,必须创建对象。
非静态内部类
案例1: 下面程序在Cow类里定义了一个CowLeg非静态内部类,并在CowLeg类的实例方法中直接访问Cow的private访问权限的成员
public class Cow {
private double weight;
//外部类的两个重载的构造器
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;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
//非静态内部类的实例方法
public void info(){
System.out.println("当前牛腿的颜色是:"+color+",高:"+length);
//直接访问外部类的private修饰的成员
System.out.println("本牛腿所在奶牛重:"+weight);//1
}
}
public void test(){
CowLeg c1 = new CowLeg(1.12, "黑白相间");
c1.info();
}
public static void main(String[] args) {
Cow cow = new Cow(378.9);
cow.test();
//编译上面的程序,看到文件夹所在的路径生成了两个class文件,一个是Cow.class,另一个是Cow$CowLeg.class,前者是外部类Cow的class文件,后者是内部类CowLeg的class文件,即成员内部类(包括静态内部类,非静态内部类)的class文件总是这种形式:OuterClass$InnerClass.class.
}
}
输出:
当前牛腿的颜色是:黑白相间,高:1.12
本牛腿所在奶牛重:378.9
补充: 编译上面的程序,看到文件夹所在的路径生成了两个class文件,一个是Cow.class,另一个是Cow$CowLeg.class,前者是外部类Cow的class文件,后者是内部类CowLeg的class文件,即成员内部类(包括静态内部类,非静态内部类)的class文件总是这种形式:OuterClass$InnerClass.class.
在上面程序序号1处就是在CowLeg类的方法内直接访问其外部类的private实例变量。这是因为在非静态内部类的对象里,保存了一个它寄存的外部类对象的引用(当调用非静态内部类的实例方法时,必须有一个非静态内部类的实例,而非静态内部类实例必须寄存在外部类的实例里)
案例2: 非静态内部类的成员可以访问外部类的private成员,但反过来就不成立了。非静态内部类的成员只在非静态内部类范围是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显示创建非静态内部类对象来调用访问其实例成员。
public class Outer {
private int outProp=9;
class Inner{
private int inProp=5;
public void acessOuterProp(){
//非静态内部类可以直接访问外部类的成员
System.out.println("外部类的outProp值:"+outProp);
}
}
public void accessInnerProp(){
//外部类不能直接访问非静态内部类的实例成员变量
//下面代码出现编译错误
//System.out.println("内部类的inProp值:"+inProp);
//如需访问内部类的实例成员变量,则必须显示创建内部类对象
System.out.println("内部类的inProp值:"+new Inner().inProp);
}
public static void main(String[] args) {
//执行下面的代码,只创建了外部类对象,还未创建内部类对象
Outer out = new Outer();
out.accessInnerProp();
}
}
输出:
内部类的inProp值:5
补充: 非静态内部类对象和外部类对象的关系是怎样的?
**答:**非静态内部类对象必须寄存在外部类对象里,而外部类对象则不一定有非静态内部对象寄存其中。简单来说,如果存在一个非静态内部内对象,则一定存在一个被它寄存的外部类对象。但外部类对象存在时,外部类对象里不一定寄存了非静态内部类对象。因此外部类对象访问非静态内部类成员时,可能非静态内部类对象根本不存在!而非静态内部类对象访问外部类成员时,外部类对象一定存在。
案例3: 当非静态内部类的方法访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量;如果不存在,则到该方法所在的内部类中查找是否存在改名字的变量,如果存在就使用该成员的变量;如果不存在,则到该内部类所在的外部类中查找是否存在改名字的成员变量,如果存在就使用该变量;如果依然不存在,系统将出现变异错误:提示找不到该变量。因此,如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可以通过是使用this、外部类类名.this作为限定来区分。如下程序所示:
public class DiscernVariable {
private String prop="外部类实例变量";
private class Inclass{
private String prop="内部类的实例变量";
public void info(){
String prop="局部变量";
//通过外部类类名.this.varName访问外部类实例变量
System.out.println("外部类的变量值:"+DiscernVariable.this.prop);
//通过this.varName访问内部类实例的变量
System.out.println("内部类的变量值:"+this.prop);
//直接访问局部变量
System.out.println("局部变量的值:"+prop);
}
}
public void test(){
Inclass in = new Inclass();
in.info();
}
public static void main(String[] args) {
new DiscernVariable().test();
}
}
输出:
外部类的变量值:外部类实例变量
内部类的变量值:内部类的实例变量
局部变量的值:局部变量
案例4: 根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能访问非静态成员内部类,包括不能使用非静态内部类定义变量、创建变量实例等。总之,不允许在外部类的静态成员中直接使用非静态内部类。如下程序所示:
public class StaticTest {
//定义一个非静态内部类,是一个空类
private class In{}
//外部类的静态方法
public static void main(String[] args) {
//下面代码引发编译异常,因为静态成员(main方法)
//无法访问静态成员(In类)
//new In();
}
}
Error:无法从静态上下文中引用非静态 变量 this
java不允许在非静态内部类里定义静态成员,下面程序示范了非静态内部类里包含静态内部类将引起编译错误
public class InnerNoStatic {
private class InnerClass{
/*
下面三个静态声明都将引发编译错误:
非静态内部类不能有静态声明
*/
// static {
// System.out.println("----");
// }
// private static int inProp;
// private static void test(){}
}
}
静态内部类不能有静态方法、静态成员变量、静态初始化块,所以上面三个变量声明都引发错误
静态内部类
如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,有的地方也成为静态内部类。
注意: static关键字的作用是把类的成员变成类相关,而不是实例相关,即static修饰的成员属于整个类,而不属于单个对象。外部类的上一级程序单元式包,所以不可以使用static修饰;而内部类的上一级程序单元是外部类,使用static修饰可以将内部类变成外部类相关,而不是外部类实例相关。因此static关键字不可以修饰外部类,但可以修饰内部类。
案例: 静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。下面程序就演示了这条规则:
public class StaticInnerClassTest {
private int prop1=5;
private static int prop2=9;
static class StaticInnerClass{
//静态内部类里可以包含静态成员
private static int age;
public void accessOuterProp(){
//下面代码出现错误
//静态内部类无法访问外部类的实例成员
// System.out.println(prop1);
//下面代码正常
System.out.println(prop2);
}
}
}
补充: 为什么静态内部类的实例方法也不能访问外部类的实例属性呢?
答: 因为静态内部类是外部类的类相关,而不是外部类的对象相关的。也就是说,静态内部类对象不是寄存在外部类对象里的,而是寄存在外部类的类本身中。当静态内部类对象存在时,并不存在一个被它寄存的外部类对象,静态内部类对象里只有外部类的类引用,没有特有外部类对象的引用。如果允许静态内部类的实例方法访问外部类的实例成员,但找不到被寄存的外部类对象,这将引起错误。
案例: 外部类不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。下面程序示范了这条规则:
public class AccessStaticInnerClass {
static class StaticInnerClass{
private static int prop1=5;
private int prop2=9;
}
public void accessInnerProp(){
//System.out.println(prop1);
//上面代码出现错误,应改为如下格式
//通过类名访问静态内部类的成员
System.out.println(StaticInnerClass.prop1);
//System.out.println(prop2);
//上面的代码出现错误,应改为如下格式
//通过实例访问静态内部类的实例成员
System.out.println(new StaticInnerClass().prop2);
}
}
除此之外,java还允许在接口里定义内部类,接口里定义的内部类默认使用public static修饰,也就是说,接口内部类只能是静态内部类。
如果为接口内部类指定访问控制符,只能指定public访问控制符;如果定义接口内部类时省略访问控制符,则该内部类默认是public访问控制权限
补充: 接口里是否能定义内部接口?
答: 可以的,接口里的内部类接口是接口的成员,因此系统默认添加public static修饰符。如果定义接口里的内部接口时指定访问控制符,则只能使用public修饰符。当然,定义在接口里的内接口的意义不大,因为接口的作用是定义一个公共规范(暴露出来供大家使用),如果把这个接口定义成一个内部接口,那么意义何在?
使用内部类
(1)在外部类内部使用内部类
从前面的成语可以看出,在外部类内部使用内部类时,与平常使用普通类没有太大的差别。一样可以通过内部类类名来定义变量,通过new调用内部类构造器来创造实例。
唯一的却区别就是:不要在外部类的静态成员(包括静态方法和静态初始化块)中使用非静态内部类,因为静态成员不能访问非静态成员
(2)在外部类外使用非静态内部类
如果希望在外部类以外的地方访问内部类(包括静态和非静态两种),则内部类不能使用private访问控制权限,private修饰的内部类只能在外部类内部使用。对于使用其他访问控制修饰符的内部类,则只能在访问对应的访问权限内使用
补充:
>省略访问控制符的内部类,只能被与外部类处于同一个包中的其他类访问
>使用protected修饰的内部类,可被与外部类处于同一个包中的其他类和外部类的子类访问
>使用public修饰的内部类,可以在任何地方被访问
案例: 在外部类以外的地方创建非静态内部类实例必须使用外部类实例和new来调用非静态内部类的构造器。下面程序示范了如何在外部类以外的地方创建非静态内部类对象,并把它赋给非静态内部类类型的变量
class Out{
//定义一个内部类,不使用访问控制符
//即只有同一个包中的其他类可访问该类内部类
class In{
public In(String msg){
System.out.println(msg);
}
}
}
public class CreatInnerInstance {
public static void main(String[] args) {
Out.In in = new Out().new In("测试信息"); //1
/*
上面代码可以改为如下的三行代码:
使用OutterClass.InnerClass的形式定义内部类变量
Out.In in;
创建外部类实例,非静态内部类实例将寄存在该实例中
Out out=new Out();
通过外部类实例和new来调用内部类构造器创建非静态内部类实例
in=out.new In("测试信息");
*/
}
}
上面程序1处创建了一个非静态内部类的对象,从上面的代码可以看出,非静态内部类的构造器必须使用外部类对象来调用
如果需要在外部类以外的地方创建非静态内部类的子类,则尤其要注意上面的规则:非静态内部类的构造器必须通过外部类对象来调用
案例: 我们知道:当创建一个子类时,子类构造器总会调用父类的构造器,因此在创建非静态内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内部类的构造器时,彼此存在一个外部类对象。下面定义了一个子类继承了Out类的非静态内部类In类:
public class SubClass extends Out.In{
//显示定义SubClass的构造器
public SubClass(Out out){
//通过传入的Out对象显示调用In的构造器
out.super("hello");
}
}
非静态内部类In类的构造器必须使用外部类对象来调用,代码中super代表调用In类的构造器,而out则代表外部类对象(上面的Out、In两个类直接来自于前一个CreatInnerInstance.java)
从上面代码中可以看出,如果需要创建SubClass是非静态内部类In类的子类,非静态内部类In对象里必须有一个对Out对象的引用,其子类SubClass对象里也应该存在一个Out引用。当创建SubClass对象时传给该构造器的Out对象,就是SubClass对象里Out对象引用所指向的对象
非静态内部类In对象和SubClass对象里Outer对象引用,区别是创建两种对象时传入Out对象的方式不同:当创建非静态内部类In类的对象时,必须使用Outer对象作为调用者来调用In类的构造器
(3)在外部类以外使用静态内部类
因为静态内部类是外部类类相关的,因此创建内部类对象时无须创建外部类对象
格式: 外部类名.内部类名 对象名 = new 外部类名.内部类名();
案例: 下面程序示范了如何在外部类以外的地方创建静态内部类的实例:
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();
/*
上面的代码可以改为如下的代码:
使用OuterClass.InnerClass的形式定义内部类变量
StaticOut.StaticIn in;
通过new来调用内部类构造器创建静态内部类实例
in=new StaticOut.StaticIn();
*/
}
}
从上面代码中可以看出,不管是静态内部类对象还是非静态内部类对象,它们声明变量的语法完全一样。区别只是在创建内部类对象时,静态内部类只需使用外部类即可调用构造器,而非静态内部类必须使用外部类对象来调用构造器
补充: 既然内部类是外部类的成员,那么是否可以为外部类定义子类,在子类中再定义一个内部类来重写其父类中的内部类?
答: 不可以,从上面是知识可以看出,内部类的类名不再是简单地有内部类的类名组成,它实际上还把外部类的类名作为一个命名空间,作为内部类类名的限制。因此子类中的内部类和父类中的内部类不可能完全同名,即使二者所包含的内部类的类名相同,但因为它们所处的外部类空间不同,所以它们不可能完全同名,不就不可能重写
局部内部类
对于局部成员而言,不管是局部变量还是局部内部类,它们的上一级程序单元都是方法,而不是类,使用static修饰它们没有任何意义。因此,所有的局部成员都不能使用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=5;
is.b=8;
System.out.println("InnerSub对象的a和b成员变量是:"+is.a+","+is.b);
}
}
补充: 编译上面程序,看到生成看三个class文件:LocalInnerClass.class、LocalInnerClass$InnerBase.class和LocalInner$1InnerSub.class,这表明局部内部类的class文件总是遵循如下格式:OuterClass$NInnerClass.class.注意到局部内部类的class文件的文件名比成员内部类的class文件的文件名多了一个数字,这是因为同一个类里不可能有两个同名的成员内部类,而同一个类里则可能有两个以上同名的局部内部类(处于不同的方法中),所以java为局部内部类的class文件名中增加了一个数字,用于区分
匿名内部类
匿名内部类适合创建那种只需要一次使用的类,匿名内部类的语法有点奇怪,创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。
定义匿名内部类的格式如下:
new 父类构造器(实例列参)|实现接口(){
//匿名内部类的类体部分
}
从上面定义可以看出来,匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接口。
关于匿名内部类还有如下两条规则:
>匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。因此不允许匿名内部类定义成抽象类
>匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义实例初始化块,通过实例初始化块来完成构造器需要完成的事情。
案例: 最常用的创建匿名内部类的方式是需要创建某个接口类型的对象,如下程序所示:
interface Product{
public double getPrice();
public String getName();
}
public class AnonymousTest {
public void test(Product p){
System.out.println("购买了一个"+p.getName()+",花掉了"+p.getPrice());
}
public static void main(String[] args) {
AnonymousTest ta = new AnonymousTest();
//调用test方法时,需要传入一个Product参数
//此处传入其匿名实现类的实例
ta.test(new Product() {
@Override
public double getPrice() {
return 567.8;
}
@Override
public String getName() {
return "AGP显卡";
}
});
}
}
输出:
购买了一个AGP显卡,花掉了567.8