Java内部类详解

在一个类的里面再定义的类,叫做内部类,也可以叫做寄生类,在平时的编程中很少用到内部类,而且显的特别乱。

但是内部类也是有很大作用的:

  • 提供了更好的封装,把类隐藏在外部类中,这样其他的类不能调到。
  • 内部类可以使用外部类的所有数据,包括private修饰的。

在jdk中哟哟很多地方都用到了内部类,在集合的源码中,比如在ArrayList中为了实现Iterator接口,就利用了内部类实现了Iterator接口。


包含内部类的类叫做顶层类,顶层类只能用public和默认修饰,内部类可以用四种权限修饰符的任意一个。我们要访问内部类的时候,要通过外部类访问内部类,不能直接访问内部类。

package demo_Inner;

public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.print();//外部类

        Outer.Inner inner = new Outer().new Inner();
        inner.print();//内部类
    }
}
class Outer{
    public class Inner{
        public void print() {
            System.out.println("内部类");
        }
    }

    public void print() {
        System.out.println("外部类");
    }
}
以上代码有三个类,一个测试类,一个外部类Outer,里面有一个内部类Inner,我们在测试类的main方法中测试,直接创建外部类对象,访问到的当然是外部类中的方法,打印输出“外部类”,接下来创建内部类对象,调用内部类中的方法。注意内部类只能通过外部类才能调用到,所以内部类的完整类名是Outer.Inner,在创建对象的时候也是通过外部类的对象找到内部类,再创建内部类对象,所以以上创建内部类的写法是:

Outer.Inner inner = new Outer().new Inner()
或者是:

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

在外部类是直接可以创建内部类对象的,只有在测试类中要创建内部类对象要通过外部类,当作一个属性

public Inner inner = new Inner();

如果不想让测试类创建内部类对象可以用private修饰内部类对象,但是这个在外部类中依然可以访问到内部类,注意外部类和内部类不能重名,否则会报错。


java虚拟机遇到几个class就会生成几个class文件,内部类也是一样,只不过内部类和其他类的文件名字不一致,外部类$内部类.class


内部类一共分为四种:实例内部类,静态内部类,局部内部类,匿名内部类

一、实例内部类

实例内部类的创建必须依赖外部类的实例,上面的例子就是实例内部类。注意实例内部类是可以访问到外部类中用任何权限修饰符修饰的数据,包括private


对上面的程序改造:在外部类中加入各种权限修饰符修饰的成员变量和一个方法,在内部类中调用

package demo_Inner;

public class Test {
    public static void main(String[] args) {
//        Outer outer = new Outer();
//        Outer.Inner inner = outer.new Inner();
//        outer.print();

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


    }
}
class Outer{
    private int id = 1001;
    protected String name = "小明";
    public String sex = "男";
    static int score = 77;

    public void print() {
        System.out.println("外部类中方法");
    }

    public class Inner{
        public void out() {
            System.out.println(id);
            System.out.println(name);
            System.out.println(sex);
            System.out.println(score);
            print();
        }

    }
}

输出:

1001
小明

77
外部类中方法


通过输出可以看到,我们没有创建外部类的对象,就可以直接访问到外部类的成员变量和方法。内部类之所以能访问到内部类的数据,因为当内部类的实例存在的时候,内部类的实例肯定已经存在,内部类自动持有外部类的引用。如果在内部类中还有一个内部类,这个内部类依然可以访问到属于他的两个外部类的数据。

class Outer{
    private int id = 1001;
    protected String name = "小明";
    public String sex = "男";
    static int score = 77;

    public void print() {
        System.out.println("外部类中方法");
    }

    public class Inner{
        class IInner{
            public void a() {
                System.out.println(id);
                System.out.println(name);
                System.out.println(sex);
                System.out.println(score);
                out();
                print();
            }
        }
        public void out() {
            print();
        }

    }
}

利用javap命令反编译内部类的class文件,可以解释为什么内部类可以直接访问到外部类的数据。在内部类编译的时候创建了一个引用这个引用就是外部类的引用(demo_Inner是包名),内部类的构造方法传进去的参数就是这个引用,在最后红框中也注释说明了这个引用是外部类的引用。也就解释了为什么内部类可以直接访问到外部类的数据。


反过来则不行,外部类不能直接去访问内部类的数据,必须要创建内部类对象去访问。一个内部类只会对应一个外部类的实例,但是一个外部类实例可以对应多个内部类实例,

就好比箱子里的人一定知道自己在箱子里,外面的人不知道箱子里有没有人。

内部类中不能定义静态的成员变量和内部类类,但是外部类中可以有一个静态的内部类,这就是静态内部类

class Outer{    
    private int id = 1001;
    protected String name = "小明";
    public String sex = "男";
    static int score = 77;

    public void print() {
        System.out.println("外部类中方法");
    }

    public class Inner{
        static int a;//编译报错
        static class IInner{ }//编译报错
        public void out() {
            print();
        }

    }
}

二、静态内部类


静态内部类也是定义在外部类中,用一个static修饰静态内部类不会自动持有外部类的引用,对于非静态的成员变量和方法直接调用会报错,如果是静态的成员变量或方法不会,如果想要调用需要创建外部类对象。

class Outer{
    private int id = 1001;
    protected String name = "小明";
    public String sex = "男";
    static int score = 77;

    public void print() {
        System.out.println("外部类中方法");
    }

    public static class Inner{
        public void out() {
            System.out.println(id);//编译报错
            System.out.println(name);//编译报错
            System.out.println(sex);//编译报错
            System.out.println(score);//合法
            print();//编译报错
        }

    }
}
我们可以在内部类中创建外部类的对象访问

class Outer{
    private int id = 1001;
    protected String name = "小明";
    public String sex = "男";
    static int score = 77;

    public void print() {
        System.out.println("外部类中方法");
    }

    public static class Inner{
        public void out() {
            Outer outer = new Outer();
            System.out.println(outer.id);//编译报错
            System.out.println(outer.name);//编译报错
            System.out.println(outer.sex);//编译报错
            System.out.println(score);//合法
            outer.print();//编译报错
        }

    }
}

在测试类中可以直接通过类名创建对象:

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

三、局部内部类

局部内部类是在方法中的内部类,可以把它看作一个局部变量,只不过这个变量是一个类,当然他的作用范围是当前方法,局部内部类不能用权限修饰符修饰,也不能用static修饰

class Outer{

    public void print() {
        class Inner{
            public void out() {

            }

            public static void out1() {//编译错误
                
            }

        }
        Inner inner = new Inner();
        inner.out();
    }
}

另外局部内部类也是可以访问外部类的数据的,自动持有引用。

四、匿名内部类

匿名内部类没有名字,因为没有名字,所以只能使用一次,但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。

先说一下匿名内类的好处,在实现接口的时候可以节省代码。这是我们最常用的结构,实现一个接口:

package demo_Inner;


interface Animal{
    public void run();
}

public class Dog implements Animal{
    
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.run();
    }

    @Override
    public void run() {
        System.out.println("跑");
    }
}
接下来用匿名内部类实现:
package demo_Inner;


interface Animal{
    public void run();
}

public class Dog {

    public static void main(String[] args) {
        Animal animal=new Animal() {
            @Override
            public void run() {
                System.out.println("跑");  
            }
        };
        animal.run();
    }
    
}
也可以这么写:

public static void main(String[] args) {
        new Animal() {
            @Override
            public void run() {
                System.out.println("跑");
            }
        }.run();
    }

把接口中的方法直接在创建接口对象的时候在大括号内实现了,不用再去单独拿出一个类实现接口,再创建对象去调用

注意:匿名内部类是没有构造方法的,类中没有任何static修饰的静态变量和方法,内部类中使用形参的时候,该形参必须是final修饰的常量。

public static void main(String[] args) {
        final int a = 0;
        new Animal() {
            @Override
            public void run() {
                System.out.println("跑");
                System.out.println(a);//如果a不用final修饰会报错
            }
        }.run();
    }
在这里引用http://blog.csdn.net/chenssy/article/details/13170015的看法:

       在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用final来避免形参的不改变。

      简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。

      故如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,那么编译器会要求该参数引用是final的。




原文地址:https://www.cnblogs.com/duzhentong/p/7816556.html