Java基础-内部类

内部类感觉听高端的,需要学会它的使用方法。

内部类概述

在一个类的内部定义的类就称为内部类。
内部类可以处于4中种访问级别(public、protected、默认和private)之一(而外部顶级类即类名和文件名相同的只能使用public和default)。
使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
为了方便起见,本文把最外层的类称为顶层类,把内部类所在的类称为外部类。Outer类是顶层类,Inner类是内部类,并且Outer类是Inner类的外部类。Tester类会访问Outer类和Inner类,因策把Tester类称为客户类。
注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。所以内部类的成员变量/方法名可以和外部类的相同。

内部类的种类

内部类可按照作用域进行分类。
它分为成员内部类和局部内部类两大类。成员内部类又可分为实例内部类和静态内部类。除此之外,匿名类是一种特殊的内部类。

成员内部类

顶层类只能处于public和默认访问级别,而成员内部类可以处于public、protected、默认和private这4中访问级别。
成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有 成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。

实例内部类

实例内部类是成员内部类的一种,没有static修饰。
实例内部类具有以下特点:

  1. 在创建实例内部类的实例时,外部类的实例必须已经存在。例如要创建Inner类的实例,必须先创建Outer外部类的实例:
    1
    Outer.Inner inner = new Outer().new Inner();

以上代码等价于:

1
2
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

以下代码会导致编译错误:

1
Outer.Inner inner = new Outer.Inner();

  1. 实例内部类的实例自动持有外部类的实例的引用。在内部类中,可以直接访问外部类的所有成员,包括成员变量和成员方法。
  2. 外部类可以访问内部类的特定实例的成员变量和方法,但不能直接访问内部类的成员变量和方法。
    例如,以下类A的test()方法一开始试图直接访问内部类B的b1和b2成员变量,这是非法的。接下来,test()方法创建了两个内部类B的实例,并且分别访问它们成员变量,这是合法的:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package innerref;
    public class A{
    class B{ //类B是类A的内部类
    private int b1=1;
    public int b2=2;
    }
    public void test(){
    int v1=b1; //编译错误,不能直接访问内部类B的成员变量b1
    int v2=b2; //编译错误,不能直接访问内部类B的成员变量b2
    B x = new B(); //合法
    int v3=x.b1; //合法,可以通过内部类B的实例去访问变量b1
    int v4=x.b2; //合法,可以通过内部类B的实例去访问变量b2
    B y new B(); //合法
    y.b2=5; //合法,可以通过内部类B的实例去访问变量b2
    }
    }

Tips:在以上类A的test()方法中可以直接创建类B的实例,new B()语句相当于this.new B()语句,因此,新建的实例B引用当前实例A。

静态内部类

静态内部类是成员内部类的一种,用static修饰,静态内部类的实例不依赖于外部类的特定实例。
静态内部类具有以下特点:

  1. 静态内部类的实例不会自动持有外部类的特定实例的引用,在创建内部类的实例时,不必创建外部类的实例。
    例如以下类A有一个静态内部类B,客户类Tester创建类B的实例时不必创建A的实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package staticnew;
    class A{
    public static class B{
    int v;
    }
    }
    class Tester{
    public void test(){
    A.B b = new A.B();
    b.v=1;
    }
    }
  2. 静态内部类可以直接访问外部类的静态成员,如果访问外部类的势实例成员,必须通过外部类的实例去访问。
    例如在以下静态内部类B中,可以直接访问外部类A的静态变量a2,但是不能直接访问实例变量a1.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package accessouter;
    class A{
    private int a1; //实例变量a1
    private static int a2; //静态变量a2
    public static class B{
    int b1=a1; //编译错误,不能直接访问外部类A的实例变量a1
    int b2=a2; //合法,可以直接访问外部类A的静态变量a2
    int b3=new A().a1; //合法,可以通过类A的实例访问变量a1
    }
    }
  3. 客户类可以通过完整的类名直接访问静态内部类的静态成员。
    例如,在以下例程中,Tester类可以通过A.B.v2的形式访问内部类B的静态变量v2,但是不能用A.B.v1的形式访问内部类B的实例变量v1。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package visitstatic;
    class A{
    public static class B{
    int v1;
    static int v2;
    }
    }
    class Tester{
    public void test(){
    A.B b = new A.B();
    b.v1=1;
    b.v2=1;
    A.B.v1=1; //编译错误
    A.B.v2=1; //合法
    }
    }

局部内部类

局部内部类是在一个方法中定义的内部类,他的可见范围是当前方法。和局部变量一样,局部内部类不能用访问控制修饰符(public、private和protected)及static修饰符来修饰。局部内部类具有以下特点:

  1. 局部内部类只能在当前方法中使用。
  2. 局部内部类和实例内部一样,可以直接访问外部类的所有成员,此外,局部内部类还可以访问所在方法中的final类型的参数和变量。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package localacess;
    class A{
    int a;
    public void method(final int p1,int p2){
    int localV1=1;
    final int localV2=2;
    class B{ //局部内部类
    int b1=a; //合法
    int b2=p1; //合法
    int b3=p2; //编译错误
    int b4=localV1; //编译错误
    int b5=localv2; //合法
    }
    }
    }

匿名类

匿名类是一种特殊的内部类,这种类没有名字。”new A(){…};”(分号不能省略)语句定义了一个继承类A的匿名类,大括号内是类A的类体,”new A(){…};”语句返回该匿名类的一个实例的引用。
匿名类具有以下特点:

  1. 匿名类本身没有构造方法,但会调用父类的构造方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class A{
    A(int v){
    System.out.println("another constructor");
    }
    void method(){
    System.out.println("from A");
    }
    }
    public static void main(String arg[]){
    int v=1;
    A a = new A(v){ //匿名类
    void method(){
    System.out.println("from anonymous");
    }
    };
    a.method(); //打印from anonymous
    }

以上代码的打印结果为:

1
2
from constructor
from anonymous

在以上的”new A(){…};”中,如果参数v是局部变量,并且在匿名类的类体中会使用它,那么v必须是final类型,否则会导致编译错误:

1
2
3
4
5
6
7
8
9
public static void main(String arg[]){
int v=1; //编译错误,v必须定义为final类型
A a = new A(v){ //匿名类
void method(){
System.out.println("from anonymous"+v); //使用局部变量v
}
};
a.method(); //打印from anonymous
}

  1. 匿名类除了可以继承类,还可以实现接口。
  2. 匿名类和局部内部类一样,可以访问外部类的所有成员,如果匿名类位于一个方法中,还能访问所在方法的final类型的变量和参数。
您的支持将鼓励我努力创作!