面向对象--内部类

来源:互联网 发布:花千骨网游进阶数据 编辑:程序博客网 时间:2024/04/28 21:18


理解内部类

大部分时候,类被定义成一个独立的程序单元。

在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类被称为内部类,包含内部类的类也被称为外部类

内部类主要有如下作用:

1),内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。

2),内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问。但外部类不能访问内部类的实现细节,比如内部类的属

性。

3),匿名内部类适合用于创建那些仅需要一次使用的类。比如在命令模式中,当需要传入一个Command对象时,使用匿名内部类将更加方便。

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

1),内部类比外部类可以多使用三个修饰符,private,protected,static,外部类不可以使用这三个修饰符

2),非静态内部类不能拥有静态成员


非静态内部类

定义内部类非常简单,只要把一个类放在另一个类内部定义即可。此处的类内部包括类中的任何位置,甚至在方法中也可以定义内部类,这个时候这个内部类被称为局部内部类,

关于局部内部类我后面会有整理。内部类定义的结构如下:

public class OuterClass{    // 此处可以定义内部类}

理解非静态内部类

大部分时候,内部类都被作为成员内部类定义,而不是作为局部内部类,成员内部类是一种与属性,方法,构造器和初始化块相似的类成员,局部内部类和匿名内部类则不是类成

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

因为内部类作为其外部类的成员,所以可以使用任意访问控制符,比如private,protected,public等修饰。如何理解这一点呢?外部类的上一级程序单元是包,所以它只有两个作

用域,同一个包内和任何位置,因此只需两种访问权限,包访问权限和公开访问权限,正好对应省略访问控制符和public访问控制符,省略访问控制符是包访问权限,即同一个包

中的其他类可以访问省略访问控制符的成员。因此如果一个外部类不使用任何访问控制符修饰,则只能被同一个包中其他类访问,而内部类的上一级程序单元是外部类,它就具有

四个作用域,同一个类,同一个包,父子类和任何位置,因此可以使用四种访问控制权限。

/** * 非静态内部类 * 在外部内部类中使用非静态内部类,与平时使用普通类没什么区别 *  * @author LinkinPark */public class LinkinPark{private String name = "LinkinPark";private void sayHi(){System.out.println("你用你优雅的姿势say hi");}private class Linkin{private Integer age = 25;public void test(){// 在非静态内部类中可以直接访问外部类的private成员System.out.println("内部类中访问外部类的属性:" + name);sayHi();System.out.println("内部类中访问内部类的属性:" + age);// 内部类中访问外部类的属性:LinkinPark// 你用你优雅的姿势say hi// 内部类中访问内部类的属性:25}}public void test(){Linkin binger = new Linkin();binger.test();}public static void main(String[] args){LinkinPark linkin = new LinkinPark();linkin.test();}}

编译上面的程序,可以看到文件所在路径生成了2个class文件:LinkinPark.class,LinkinPark$Linkin.class。成员内部类(包括静态内部类,非静态内部类)的class总是这样子:

OutClass$InnerClass.class。

前面说到了,在非静态内部类里可以直接访问外部类的private成员,这是因为在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用,当调用非静态内部类的实例方法

时,必须有一个非静态内部类的实例,非静态内部类的实例必须寄生在外部类实例中。当在非静态内部类的方法内访问某个变量时:

1),系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量

2),如果不存在则到该方法所在的内部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量

3),如果不存在则到该内部类所在的外部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量

4),如果依然不存在,系统将出现编译错误,提示找不到该变量

那么问题来了:若外部类字段,内部类字段,内部类方法中的局部变量同名,则具体的访问方式是怎么样的呢?

如果外部类成员变量,内部类成员变量和内部类里中的局部变量同名,可以通过使用this,外部类类名.this作为限定来区分。

1),访问外部类的成员变量:外部类类名.this.字段

2),访问内部类的成员变量:this.字段

3),访问内部类方法的局部变量:字段

/** * 外部类成员变量,内部类成员变量和内部类里中的局部变量同名 *  * <pre> * 1,访问外部类的成员变量:外部类类名.this.字段 * 2,访问内部类的成员变量:this.字段 * 3,访问内部类方法的局部变量:字段 * </pre> *  * @author LinkinPark */public class LinkinPark{private String name = "LinkinPark";private class Linkin{private String name = "NightWish";public void test(){String name = "Linkin";System.out.println("访问内部类方法中局部变量:" + name);System.out.println("访问内部类中的属性:" + this.name);System.out.println("访问外部类中的属性:" + LinkinPark.this.name);}}public void test(){Linkin linkin = new Linkin();linkin.test();}public static void main(String[] args){LinkinPark linkin = new LinkinPark();linkin.test();// 访问内部类方法中局部变量:Linkin// 访问内部类中的属性:NightWish// 访问外部类中的属性:LinkinPark}}

非静态内部类的成员可以访问外部类的private成员,除此之外还有三点要注意:

1),非静态内部类的成员只有在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用

访问其实例成员。理解一下为什么?

非静态内部类对象必须寄生在外部类对象里,而外部类对象则不必一定有非静态内部类对象寄生其中。简单的说,如果存在一个非静态内部类对象,则一定存在一个被他寄生的外

部类对象,但外部类对象存在时,外部类对象不一定寄生了非静态内部类,因此外部类对象访问非静态内部类成员时,可能非静态普通内部类对象根本不存在,而非静态内部类对

象访问外部类成员时,外部类对象一定存在

/** * 非静态内部类和外部类的关系 *  * <pre> * 1,非静态内部类访问外部类,外部类对象一定存在 * 2,外部类访问非静态内部类,非静态内部类可能不存在 * </pre> *  * <pre> * 1,非静态内部类可以直接访问外部类的private成员变量 * 2,外部类不能直接访问非静态内部类的成员变量 * 3,如果外部类想要访问非静态内部类的实例属性,必须创建内部类对象 * </pre> *  * @author LinkinPark */public class LinkinPark{private String name = "LinkinPark";private class Linkin{private Integer age = 25;public void test(){// 非静态内部类可以直接访问外部类的private成员变量System.out.println("非静态内部类可以直接获取外部类的实例属性:" + name);}}public void test(){// 下行代码报错,编译不过System.out.println("外部类不能直接访问非静态内部类的实例属性:" + age);// 下行代码正确,编译通过System.out.println("如果外部类想要访问非静态内部类的实例属性,必须创建内部类对象" + new Linkin().age);}public static void main(String[] args){// 执行这行代码只创建了外部对象,并没有创建非静态内部类实例LinkinPark linkin = new LinkinPark();linkin.test();}}

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

外部类的静态成员中直接使用非静态内部类。

/** * 不允许在外部类的静态成员中直接使用非静态内部类 *  * @author LinkinPark */public class LinkinPark{private class Linkin{}public static void main(String[] args){// 下行代码报错,编译不过new Linkin();// 下行代码正确,编译通过new LinkinPark().new Linkin();}}

3),Java不允许在非静态内部类里定义静态成员。非静态内部类里不能有静态方法,静态成员变量,静态初始化块

/** * 非静态内部类中不能有静态方法,静态成员变量,静态初始化块 *  * @author LinkinPark */public class LinkinPark{private class Linkin{// 当然在内部类中定义一个常量是可以的private static final String name = "LinkinPark";// 非静态内部类中不可以定义静态成员,下面代码报错,编译不过private static Integer age;private static void sayHi(){}}}


静态内部类

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

理解一下静态内部类

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

但是内部类的上一级程序单元是外部类,使用static修饰可以将内部类变成外部类相关,而不是外部类实例相关。因此,static关键字不可修饰外部类,但可修饰内部类

使用static修饰内部类,该内部类属于其外部类,而不属于外部类的实例;静态内部类可包括静态成员也可包括非静态成员。

根据静态成员不能访问非静态成员的规定,所以静态内部类不能访问外部类实例成员,只能访问外部类的静态成员。即使是静态内部类的方法也不可以。

为什么静态内部类实例方法中也不能访问外部类的实例属性?

静态内部类是外部类类相关的,而不是外部类的对象相关的。也就是说,静态内部类对象不是寄生在外部类的实例中,而是寄生在外部类的类本身中的。当静态内部类对象存在

时,并不存在一个被他寄生的外部类对象,静态内部类对象只持有外部类的类引用,没有持有外部类对象的引用。如果允许静态内部类的实例方法访问外部类的实例成员,但是实

际上又找不到被寄生的外部类对象,这将引起错误。

/** * 静态成员不能访问非静态成员 * 静态内部类实例方法也不能直接访问外部类的实例方法和实例属性 *  * @author LinkinPark */public class LinkinPark{private String name = "LinkinPark";private static class Linkin{private Integer age1 = 25;private static Integer age2 = 24;private void sayHi(){// 下面代码报错,编译不错System.out.println("静态内部类的实例方法不能直接访问外部类的实例属性" + name);// 下面代码正确,编译通过,如果非要访问就只能new一个外部类出来啦System.out.println("静态内部类的实例方法不能直接访问外部类的实例属性" + new LinkinPark().name);}}}

除此之外,对比非静态内部类,使用静态内部类还有如下几点规则:

1),静态内部类是外部类的一个静态成员,因此外部类的所有方法,所有初始化中可以使用静态内部类来定义变量,创建对象等等

/** * 外部类(不管是静态方法还是非静态方法)可以使用静态内部类来定义变量或者创建外部类对象 * 对比非静态内部类,非静态内部类只能在非静态方法中才可以 *  * @author LinkinPark */public class LinkinPark{private static class Linkin{private void sayHi(){System.out.println("你用你优雅的姿势say hi");}}public static void main(String[] args){Linkin linkin = new Linkin();linkin.sayHi();// 你用你优雅的姿势say hi}}

2),外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,当然也可以使用静态内部类对象作为调用者来访问静态

内部类的实例成员

/** * 使用静态内部类的类名作为调用者来访问静态内部类的类成员 * 使用静态内部类对象作为调用者来访问静态内部类的实例成员 *  * @author LinkinPark */public class LinkinPark{private static class Linkin{private static String name = "LinkinPark";private Integer age = 25;private void sayHi(){System.out.println("你用你优雅的姿势say hi");}}public static void main(String[] args){System.out.println("通过类名打点访问静态内部类的静态属性" + Linkin.name);System.out.println("通过静态内部类对象打点访问静态内部类的实例属性" + new Linkin().age);// 通过静态内部类对象打点访问静态内部类的实例方法new Linkin().sayHi();}}

3),Java还允许在接口里定义内部类,接口里定义的内部类默认使用public static修饰,也就是说,接口内部类只能是静态内部类。

如果为接口内部类指定访问控制符,则只能指定public访问控制符,如果定义接口内部类是省略访问控制符,则该内部类默认是public访问控制权限。


局部内部类

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

能使用访问控制符和static修饰符修饰。

理解下局部内部类的几个修饰符使用情况

对于局部成员而言,不管是局部变量,还是局部内部类,他们的上一级程序单元是方法,而不是类,所以使用static完全没有意义。不仅如此,因为局部成员的作用域是所在的方

法,其他程序单元永远不能访问另一个方法中的局部变量,所以局部变量不可以使用访问控制符修饰

如果需要用局部内部类定义变量,创建实例或派生子类,那么都只能在局部内部类所在的方法内进行。

局部内部类只能访问方法中final修饰的局部变量:因为final修饰的变量相当于一个常量,其生命周期超出了方法运行的生命周期

/** * 局部内部类 * Java规定被局部内部类访问的局部变量必须使用final修饰 *  * @author LinkinPark */public class LinkinPark{public void test(){class Binger{}class Binger1{}};public static void main(String[] args){final int age = 25;class Binger{String name;public void test(){// 局部内部类中访问局部变量必须使用final修饰那个变量System.out.println(age);}}class Binger1 extends Binger{String name1;}Binger1 binger1 = new Binger1();binger1.name = "LinkinPark";binger1.name1 = "binger";System.out.println(binger1.name + "---" + binger1.name1);// LinkinPark---binger}}

编译上面的程序看到有5个class,这表明局部内部类的class文件总是以下命名方式:OutClass$数字InnerClass.class,注意到局部内部类的文件名的class文件比内部类的class文

件多了一个数字,这是因为同一个类中不可能有2个同名的成员变量,但是同一个类中可能有2个或者2个以上的同名的局部内部类,所以java为局部内部类的class文件增加了一个

数字,用于区分。上面的源码编译后生成的class文件有:LinkinPark.class;LinkinPark$1Binger;LinkinPark$2Binger;LinkinPark$2Binger;LinkinPark$2Binger1。


理解下局部内部类的鸡肋

局部内部类是一个非常鸡肋的语法,在实际开发中很少定义局部内部类,因为局部类的作用域实在太小了,再能在当前的方法中使用。

大部分定义一个类之后,当然希望多次复用这个类,但局部内部类无法离开它所在的方法,因此在实际开发中很少使用局部内部类。

匿名内部类

匿名内部类适合创建那种只需要一次使用的类,比如命令者模式中所需要的Command对象,创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不

能重复使用。定义匿名内部类格式如下:

new 父类构造器([实参列表]) 或 接口(){//匿名内部类的类体部分}

关于匿名内部类的四条规则:

1),匿名内部类不能是抽象类,因为系统在创建匿名内部类的时候,会立即创建匿名内部类的对象。因此不允许将匿名内部类定义成抽象类。

2),匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的事情

3),匿名内部类必须继承一个父类或者实现一个接口,但最多只能一个父类或实现一个接口。匿名内部类必须实现它的抽象父类或者接口中包含的所有的抽象方法。如果有需要

也可以重写父类中的普通方法。

4),匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。被匿名内部类访问的局部变量也必须使用final修饰

/** * 匿名内部类的使用 *  * @author LinkinPark */public class LinkinPark{// 定义一个方法,其中的参数要用到下面的抽象类public static void test(Linkin linkin){System.out.println(linkin.test());System.out.println(linkin.getName());}// 定义一个方法,其中的参数要用到下面的接口public static void test1(Huhu huhu){System.out.println(huhu.test());}public static void main(String[] args){// 1,直接传入一个继承抽象类的匿名内部类LinkinPark.test(new Linkin(){// 必须实现抽象类中的抽象方法@Overridepublic String test(){return "匿名内部类必须重写抽象类中的抽象方法";}// 匿名内部类可以重写继承过来的方法@Overridepublic String getName(){return "匿名内部类可以重写继承于抽象类的方法";}});System.out.println("==================");// 2,直接传入一个继承抽象类的匿名内部类LinkinPark.test(new Linkin("调用父类有参数的构造器来初始化匿名内部类"){// 必须实现抽象类中的抽象方法@Overridepublic String test(){return "匿名内部类必须重写抽象类中的抽象方法";}});System.out.println("==================");// 3,直接传入一个实现接口的匿名内部类LinkinPark.test1(new Huhu(){@Overridepublic String test(){return "匿名内部类必须实现接口中的抽象方法";}});}// 匿名内部类必须重写抽象类中的抽象方法// 匿名内部类可以重写继承于抽象类的方法// ==================// 匿名内部类必须重写抽象类中的抽象方法// 调用父类有参数的构造器来初始化匿名内部类// ==================// 匿名内部类必须实现接口中的抽象方法}/** * 定义一个抽象类 *  * @author LinkinPark */abstract class Linkin{private String name = "LinkinPark";public Linkin(){}public Linkin(String name){this.name = name;}public String getName(){return name;}public void setName(String name){this.name = name;}// 定义一个抽象方法abstract public String test();}/** * 定义一个接口 *  * @author LinkinPark */interface Huhu{public String test();}

这里补充一个Java8的功能:effectively final 

effectively final,它的意思是对于匿名内部类访问的局部变量,可以使用final修饰,也可以不用final修饰,但必须按照有final修饰的方式来用,也就是一次赋值后,以后不能重新赋

值。前面的整理中我说到了,Java8之前,Java要求被局部内部类,匿名内部类访问的局部变量必须使用final修饰,从Java8开始这个限制被取消啦,Java8更加智能,如果局部变

量被匿名内部类访问,那么该局部变量相当于自动使用final修饰符


使用内部类

定义类的作用就是定义变量,创建实例和作为父类被继承。定义内部类的主要作用也如此,但使用内部类定义变量和创建实例则与外部类存在一些小小的差异。

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

非静态内部类对象必须寄存在外部类对象中,但是外部类对象不一定非要有非静态内部类对象寄存其中。因此外部类对象访问非静态内部类成员时,可能非静态内部类对象就压根

没存在,反过来:要是非静态内部类对象访问外部类成员时,外部类对象一定存在的。

关于这点我前面已经整理了一遍了,在外部类内部使用内部类时,与平常使用普通类没有太大区别。一般以直接通过内部类类名来定义变量,通过new调用内部类构造器来创建实

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

/** * 在外部类内部使用非静态内部类 *  * @author LinkinPark */public class LinkinPark{private String name = "LinkinPark";private class Binger{private String name = "NightWish";private String huhu = "Binger";// 非静态内部类的实例方法public void test(){String name = "huhu";// java不允许在非静态内部类中定义静态成员:包括静态方法,静态属性,静态初始化块// static String name = "huhu";System.out.println("局部变量的属性" + name);System.out.println("内部类中的属性" + this.name);System.out.println("外部类中的属性" + LinkinPark.this.name);}}// 外部类的实例方法public void test(){// 外部类不允许直接访问非静态内部类的实例属性,如果确实需要访问的话,必须显式new内部类对象出来// System.out.println(huhu);System.out.println("内部类中的属性" + new Binger().huhu);Binger binger = new Binger();binger.test();}public static void main(String[] args){// 外部类的静态成员也不可以直接使用非静态内部类// new Binger();LinkinPark linkin = new LinkinPark();linkin.test();// 内部类中的属性Binger// 局部变量的属性huhu// 内部类中的属性NightWish// 外部类中的属性LinkinPark}}


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

如果希望在外部类以外的地方访问内部类,包括静态和非静态两种,则内部类不能使用private访问控制权限,private修饰的内部类只能在外部类内部使用。对于使用其他访问控制

符修饰的内部类,则能在访问控制符对应的访问权限内使用

在外部类以外的地方定义内部类

语法格式:

OuterClass.InnerClass varName

从语法格式可以看出,在外部类之外的地方使用内部类,内部类完整的类名应该是OuterClass.InnerClass。如果外部类有包名,则还应该添加包前缀。实际编码中一般都是在同一

个类中定义内部类,一般不会在外部类之外定义一个内部类。

在外部类以外的地方访问内部类

非静态内部类对象是存放在外部类的对象里的,因此在创建非静态内部类对象之前,必须先创建其外部类的对象。特别要注意:非静态内部类的构造器必须使用外部类来调用

语法格式:

OuterInstance.new InnerClass([参数列表])

/** * 在外部类外部使用非静态内部类 * 特别要注意: * 非静态内部类的构造器必须使用外部类对象来调用 *  * @author LinkinPark */public class LinkinParkTest{public static void main(String[] args){LinkinPark linkinPark = new LinkinPark();System.out.println("外部类属性:" + linkinPark.name);LinkinPark.Linkin linkin = new LinkinPark().new Linkin();System.out.println("内部类属性:" + linkin.name);// 外部类属性:LinkinPark// 内部类属性:NightWish}}class LinkinPark{String name = "LinkinPark";class Linkin{String name = "NightWish";public Linkin(){}public Linkin(String name){this.name = name;}}}


在外部类以外的地方访问内部类子类

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

类子类的对象存在,则一定存在与之关联的外部类对象。

非静态内部类父类对象和子类对象都必须持有外部类的引用,区别是创建者两种对象时传入外部类的方式不同,创建父类内部类的时候必须通过外部类对象来调用new关键字,当

创建子类内部类的时候必须将外部类对象作为参数传入构造器,然后将这个外部类对象作为调用者来调用父类的构造器

/** * 在外部类外部使用非静态内部类 * 特别要注意: * 1,非静态内部类的构造器必须使用外部类对象来调用 * 2,如果是子类的话,将外部类作为构造器的参数传入 *  * @author LinkinPark */public class LinkinParkTest{public static void main(String[] args){LinkinPark linkinPark = new LinkinPark();System.out.println("外部类属性:" + linkinPark.name);LinkinPark.Linkin linkin = new LinkinPark().new Linkin();System.out.println("内部类属性:" + linkin.name);SubLinkin subLinkin = new SubLinkin(linkinPark);System.out.println("内部类子类属性:" + subLinkin.name);// 外部类属性:LinkinPark// 内部类属性:NightWish// 内部类子类属性:Binger}}// 外部类只外创建内部类对象(比如自己类的对象,比如一个子类对象),都要使得内部类保持外部类对象的一个引用。// 前者可以通过外部类对象直接new内部类出来,后来将外部类作为参数传入内部类的子类构造器中。class SubLinkin extends LinkinPark.Linkin{String name = "Binger";// 下面的构造器必须在,而且还要必须传入一个外部类,因为非静态内部类对象中必须存在一个外部类对象的引用的public SubLinkin(LinkinPark linkinPark){linkinPark.super();}}class LinkinPark{String name = "LinkinPark";class Linkin{String name = "NightWish";public Linkin(){}}}


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

因为静态内部类是外部类的类成员,因此在创建内部类对象时不需创建外部类的对象。语法如下:

new OuterClass.InnerClass([参数列表])
/** * 在外部类外部使用静态内部类 * 特别要注意:将外部类类名.内部类类名当成一个整体就OK *  * @author LinkinPark */public class LinkinParkTest{public static void main(String[] args){LinkinPark.Binger binger = new LinkinPark.Binger();LinkinPark.Binger.staticShow();// 调用静态内部类静态方法binger.show();// 调用静态内部类的实例方法// 静态内部类静态方法// 静态内部类实例方法}}class LinkinPark{static String name;static class Binger{static String name;public void show(){System.out.println("静态内部类实例方法");}public static void staticShow(){System.out.println("静态内部类静态方法");}}}


静态内部类和非静态内部类使用对比

1),不管是静态内部类还是非静态内部类,声明变量的语法完全一样。

2),创建内部类对象时,静态内部类只需要使用外部类即可调用构造器,非静态内部类必须使用外部类对象来调用构造器

3),相比之下,使用静态内部类比使用非静态内部类要简单很多,只要把外部类当成静态内部类的包空间即可。因此当程序要使用内部类时,应该优先考虑使用静态内部类


2 0