内部类

来源:互联网 发布:大族激光打标机软件 编辑:程序博客网 时间:2024/06/05 23:08

把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类(有的地方也叫嵌套类),包含内部类的类也被称为外部类(有的地方也叫宿主类)。

Java从JDK1.1开始引入内部类,内部类主要有如下作用。

  1. 内部类提供了更好的封装,可以把内部类隐藏在外部类之内不允许同一个包中的其他类访问该类。假设需要创建Cow类,Cow类需要组合一个CowLeg对象,CowLeg类只有在Cow类里才有效,离开了Cow类之后没有任何意义。在这种情况下,就可以把CowLeg定义成Cow内部类,不允许其他类访问CowLeg。
  2. 内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问。但外部类不能访问内部类的实现细节,例如内部类的成员变量。
  3. 匿名内部类适合用于创建那些仅需要一次使用的类。

从语法角度来看,定义内部类与定义外部类的语法大致相同,内部类除了需要定义在其他类里面之外,还存在如下两点区别。

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

非静态内部类成员

内部类都被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员;局部内部类和匿名内部类则不是类成员。

成员内部类分为两种:静态内部类和非静态内部类,使用static修饰的成员内部类是静态内部类,没有使用static修饰的成员内部类是非静态内部类。

内部类的作用域

外部类的上一级程序单元是包,所以它只有2个作用域同一个包或任何位置,因此只需2种访问权限:包访问权限,正好对应省略访问控制符和public访问控制符。省略访问控制是包访问权限,即同一个包的其他类可以访问省略访问控制符的成员。因此,如果一个外部类不使用任何访问控制修饰符,则只能被同一个包中其他类访问。而内部类的上一级是外部类,所以它就具有4个作用域:同一个类、同一个包、父子类和任何位置。因此可以使用4种访问权限。

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 void info()        {            System.out.println("当前牛腿颜色是:" + color + "高:" + length);            //直接访问外部类的private修饰的成员变量            System.out.println("本牛腿所在的奶牛重:" + weight);//①        }    }    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();        CowLeg c2 = new Cow(383).new CowLeg(1.0,"花色");        c2.info();    }}
当前牛腿颜色是:黑白相间高:1.12本牛腿所在的奶牛重:378.9当前牛腿颜色是:花色高:1.0本牛腿所在的奶牛重:383.0

编译上面程序,看到在文件所在路径生成了两个class文件。一个是Cow.class,前者是外部类Cow的class文件,后者是内部类CowLeg的class文件,即成员内部类(包括静态内部类、非静态内部类)的class文件总是这种形式:OuterClass$InnerClass.class

前面提到过,在非静态内部类里可以直接访问外部类的private成员,上面①号代码就是直接访问其外部类的private实例变量。这是因为在非静态内部类对象里,保存了它所寄生的外部类对象的引用(这这边是Cow.this)(当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,非静态内部类实例必须寄生在外部类实例里)。

当在非静态内部类的方法内访问某个变量时,系统首先在该方法里查找是否存在该名字的局部变量,如果存在就使用该变量,如果不存在,见到该方法所在的外部类中查找是否存在该名字的成员变量,如果存在则使用该程序员变量;如果依然不存在,系统将出现编译错误:提示找不到该变量。

因此,如果外部类成员变量、内部类成员变量与内部类方法的局部变量同名,则可通过this.外部类类名.this限定来区别。

非静态内部类的成员可以访问外部类的private成员,但反过来就不成立了。非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用访问其实例成员。

非静态内部类对象必须寄生在外部类对象里,而外部类对象不一定有非静态内部类对象寄生其中。因而外部类不允许访问非静态内部类的实例成员。

根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例等。总之,不允许在外部类的静态成员中直接使用非静态内部类。

不允许在非静态内部类里定义静态成员。

静态内部类

如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,有的地方也被称为静态内部类。

注意:static关键字的作用就是把类的成员变成类相关,而不是实例相关,即static修饰的成员属于整个类,而不属于但个对象。外部类的上一级程序单元是包,所以不可使用static修饰符;而内部类的上一级程序单元是外部类,使用static修饰符可以将内部类变成外部类相关。

静态内部类可以包含静态成员,也可以包含非静态成员。静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。

静态内部类是外部类的类相关的,而不是外部类的对象相关的。也就是说静态内部类是寄生在外部类本身中,而不是外部类的实例中。当静态内部类对象存在时,并不存在一个被它寄生的外部类对象,静态内部类对象只持有一个外部类的类引用,没有外部类对象的引用,如果允许静态内部类的实例方法访问外部类的实例成员,但找不到被外部类对象,这将引起错误。

和非静态内部类一样,外部类依然不能直接访问静态内部类的成员但可以使用静态内部类的类名作为调用者来访问内部类的成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。

使用内部类

定义内部类的主要作用就是定义变量、创建实例和作为父类被继承。定义内部类的主要作用也如此,但使用内部类定义变量和创建实例和外部类存在一些小小差异。下面分为三种情况来讨论内部类的用法。

在外部类中使用内部类

直接通过内部类类名来定义变量,通过new调用内部构造器来创建实例。

唯一存在的一个区别是:不要在外部类的静态成员(包括静态方法和静态初始化块)中使用非静态内部类,因为静态成员不能访问非静态成员。

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

如果希望外部类以外的地方使用内部类,则内部类不能使用private访问控制权限

  1. 省略访问控制符,只能被外部类处于同一个包中的其他类所访问。
  2. 使用protected修饰的内部类,只能被外部类处于统一包中的其他类和外部类的子类访问。
  3. 使用public修饰的内部类,可以在任何地方被访问。

在外部类以外的地方定义内部类(包括静态和非静态两种)变量的语法格式如下:

OuterClass.InnerClass varName//内部类完整的类名应该是OuterClass.InnerClass

在外部类以外的地方创建非静态内部类实例的语法格式如下。

OuterInstance.new InnerConstructor()//在外部类以外的地方创建非静态内部类实例必须使用外部类实例和new来调用非静态内部类的构造器。

当创建一个子类,子类构造器总会调用父类构造器,因此在创建非静态内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内部类的构造器时,必须存在一个外部类对象。

public class SubClass extends Out.In{    //显式定义SubClass的构造器    public SubClass(Out out)    {        //通过传入的Out对象显式调用In的构造器        out.super("hello");    }}/*非静态内部类In类的构造器必须使用外部类来调用,代码中super代表调用In类的构造器,而out则代表外部类对象。*//*如果需要创建SubClass对象,就必须创建一个Out对象。这是合理的,因为SubClass对象是非静态内部类In的子类,非静态内部类In对象里必须有一个对Out对象的引用,其子类SubClass对象里也应该持有对Out对象的引用。当创建SubClass对象时传给该构造器的Out对象就是SubClass对象里Out对象所指向的对象。*//*非静态内部类In对象和SubClass对象都必须持有指向Outer对象的引用,区别是创建两种对象时传入Out对象的方式不同:当创建非静态内部类In类的对象时,必须通过Outer对象来调用new关键字;当创建SubClass类的对象时,必须使用Outer对象作为调用者调用In类的构造器。*/

非静态内部类的子类不一定是内部类,它可以是一个外部类,但非静态内部类子类实例一样需要保留一个引用,该引用指向其父类所在外部类的对象。也就是说,如果有一个内部类子类的对象存在,则一定存在与之对应的外部类对象。

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

在外部类以外的地方创建静态内部类实例的语法如下:

new OuterClass.InnerConstructor()//静态内部类只需使用即可调用构造器,而非静态内部类必须使用外部类对象来调用构造器。

因为调用构造器时无须使用外部类对象,所以创建静态内部类的子类也比较简单

public class StaticSubClass extends StaticOut.StaticIn{}//当定义一个静态内部类时,其外部类非常像一个包空间

是否可以为外部类定义子类在子类中再定义一个内部类来重写其父类中的内部类呢?

不可以!内部类的类名不再是简单地由内部类的类名组成,它实际上还把外部类的类名作为一个命名空间,作为内部类类名的限制。因此子类中的内部类和父类中的内部类不可能完全同名,即使二者所包含的内部类的类名相同,但因为它们所处的外部空间不同,所以不可能完全同名,也就不可能重写。

局部内部类

如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。由于局部内部类不能在外部类的方法以外的地方使用,因此局部内部类也不能使用访问控制符合static修饰符修饰。

对于局部成员而言,不管是局部变量还是局部内部类,它们的上一级程序都是方法,而不是类,使用static修饰它们没有任何意义。因此,所有局部成员都不能使用static修饰。不仅如此,因为局部成员的作用域是所在方法,其他程序单元永远不可能访问另一个方法中的局部成员,所以所有的局部成员都不能使用访问控制符修饰。

局部内部类的class文件总遵循如下的命名格式:OuterClass$NInnerClass。注意到局部内部类的class文件名比成员内部类的class文件的文件名多一个数字,这是因为同一个类里不可能有两个同名的成员内部类,而同一个类里可能有两个同名的局部内部类(处于不同的方法中),因此Java为局部内部类的class文件增加一个数字,用于区别。

Java 8改进的匿名内部类

匿名内部类适合创建那种只需要使用一次的类。创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。

new 实现接口() | 父类构造器(实参列表){    //匿名内部类的类体部分}/*匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接口。*/

关于匿名内部类还有如下两条规则。

  1. 匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。因此不允许将匿名内部类定义成抽象类。
  2. 匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的事情。

当创建匿名内部类时,既可以传入参数,代表调用父类带参数的构造器;也可以不传入参数,代表调用父类无参数的构造器。

在Java 8以前,Java要求被局部内部类、匿名内部类访问的局部变量必须使用final修饰,从Java 8开始这个限制被取消了,Java 8 更智能:如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final修饰。

0 0
原创粉丝点击