Java内部类

转载出处:http://www.jianshu.com/p/e385ce41ca5b

概念

Java类中不仅可以定义变量和方法,还可以定义类,这样定义在类内部的类就被称为内部类。根据定义的方式不同,内部类分为静态内部类,成员内部类,局部内部类,匿名内部类四种。
Java为什么要引入内部类这个概念呢?原因在于,内部类定义在类的内部,可以方便访问外部类的变量和方法,并且和其它类进行隔离。

Thinking in java中对内部类的描述

1、内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。

2、在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。

3、创建内部类对象的时刻并不依赖于外围类对象的创建。

4、内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。

5、内部类提供了更好的封装,除了该外围类,其他类都不能访问。

一 静态内部类

定义在类内部的静态类,就是静态内部类。

1.语法

定义一个静态内部类

public class Out {
    private static int a;
    private int b;

    public static class Inner {
        public void print() {
            System.out.println(a);
        }
    }
}

Inner就是静态内部类。静态内部类可以访问外部类所有的静态变量和方法,即使是private的也一样。静态内部类和一般类一致,可以定义静态变量、方法,构造方法等。
其它类使用静态内部类需要使用“外部类.静态内部类”方式,如下所示:

Out.Inner inner = new Out.Inner();
inner.print();

2.应用场景

Effictive Java中的builder模式就是利用的静态内部类来建立外部类实例

Java集合类HashMap内部就有一个静态内部类Entry。Entry是HashMap存放元素的抽象,HashMap内部维护Entry数组用了存放元素,但是Entry对使用者是透明的。像这种和外部类关系密切的,且不依赖外部类实例的,都可以使用静态内部类。

二 成员内部类

定义在类内部的非静态类,就是成员内部类。

1.语法

定义一个成员内部类:

public class Out {
    private static int a;
    private int b;

    public class Inner {
        public void print() {
            System.out.println(a);
            System.out.println(b);
        }

        public Out getOut(){
            return Out.this;
        }
    }

    /*推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时 */
    public InnerClass getInnerClass(){
        return new Inner();
    }
}

成员内部类可以访问外部类所有的变量和方法,包括静态和实例,私有和非私有。和静态内部类不同的是,每一个成员内部类的实例都依赖一个外部类的实例。其它类使用内部类必须要先创建一个外部类的实例。如下所示:

Out out = new Out();
Out.Inner inner = out.new Inner();  //out.getInnerClass();
inner.print();

在成员内部类中要注意两点
第一:成员内部类中不能存在任何static的变量和方法;
第二:成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。

分析:非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。
1、static类型的属性和方法,在类加载的时候就会存在于内存中。
2、要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中。
基于以上两点,可以看出,如果一个非static的内部类如果具有static的属性或者方法,那么就会出现一种情况:内部类未加载,但是却试图在内存中创建static的属性和方法,这当然是错误的。原因:类还不存在,但却希望操作它的属性和方法。

三 局部内部类

定义在方法或作用域中的类,就是局部内部类。

1.语法

定义一个局部内部类

public class Out {
    private static int a;
    private int b;

    public void test(final int c) {
        final int d = 1;
        class Inner {
            public void print() {
                System.out.println(a);
                System.out.println(b);
                System.out.println(c);
                System.out.println(d);
            }
        }

        //如果想在外部得到该内部类,必须继承或者实现一个现有的类或接口,并且返回new Inner2();
        class Inner2 extends People{

            @Override
            public String readName() {
                return null;
            }

            public People getInner2(){
                return new Inner2();
            }
        }
    }

    public static void testStatic(final int c) {
        final int d = 1;
        class Inner {
            public void print() {
                System.out.println(a);
                //定义在静态方法中的局部类不可以访问外部类的实例变量
                //System.out.println(b);
                System.out.println(c);
                System.out.println(d);
            }
        }
    }
}

2.注意

1.定义在实例方法中的局部类可以访问外部类的所有变量和方法
2.定义在静态方法中的局部类只能访问外部类的静态变量和方法
3.局部类还可以访问方法的参数和方法中的局部变量,这些参数和变量必须要声明为final的(如果参数传入进来而没有被内部类使用则可以不用声明为final的)。

四 匿名内部类

1.写法

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

在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。

public abstract class Bird {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public abstract int fly();
}

public class Test {
    
    public void test(Bird bird){
        System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
    }
    
    public static void main(String[] args) {
        Test test = new Test();
        test.test(new Bird() {
            
            public int fly() {
                return 10000;
            }
            
            public String getName() {
                return "大雁";
            }
        });
    }
}
------------------
Output:
大雁能够飞 10000米

在Test类中,test()方法接受一个Bird类型的参数,同时我们知道一个抽象类是没有办法直接new的,我们必须要先有实现类才能new出来它的实现类实例。所以在mian方法中直接使用匿名内部类来创建一个Bird实例。

 由于匿名内部类不能是抽象类,所以它必须要实现它的抽象父类或者接口里面所有的抽象方法。

  对于这段匿名内部类代码其实是可以拆分为如下形式:
public class WildGoose extends Bird{
    public int fly() {
        return 10000;
    }
    
    public String getName() {
        return "大雁";
    }
}

WildGoose wildGoose = new WildGoose();
test.test(wildGoose);

例子2

public class Super {

    public void test1(){
        System.out.println("superClass");
    }

}

Super s = new Super(){
            @Override
            public void test1() {
                System.out.println("sonClass");
            }
        };
s.test1();

在这里系统会创建一个继承自Bird类的匿名类的对象,该对象转型为对Bird类型的引用。

  对于匿名内部类的使用它是存在一个缺陷的,就是它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类是不能够被重复使用。对于上面的实例,如果我们需要对test()方法里面内部类进行多次使用,建议重新定义类,而不是使用匿名内部类。

2.注意事项

在使用匿名内部类的过程中,我们需要注意如下几点:

  1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。

 2、匿名内部类中是不能定义构造函数的。

 3、匿名内部类中不能存在任何的静态成员变量和静态方法。

  4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。

 5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法

五 final

为何局部内部类(包含匿名内部类),它所使用到的外部的参数(来自外部方法或者外部类)必须是声明为final的呢

分析

先定义一个接口:

public interface MyInterface {
    void doSomething();
}

然后创建这个接口的匿名子类:

public class TryUsingAnonymousClass {
    public void useMyInterface() {
        final Integer number = 123;
        System.out.println(number);

        MyInterface myInterface = new MyInterface() {
            @Override
            public void doSomething() {
                System.out.println(number);
            }
        };
        myInterface.doSomething();

        System.out.println(number);
    }
}

这个匿名子类会被编译成一个单独的类,反编译的结果是这样的:

class TryUsingAnonymousClass$1
        implements MyInterface {
    private final TryUsingAnonymousClass this$0;
    private final Integer paramInteger;

    TryUsingAnonymousClass$1(TryUsingAnonymousClass this$0, Integer paramInteger) {
        this.this$0 = this$0;
        this.paramInteger = paramInteger;
    }

    public void doSomething() {
        System.out.println(this.paramInteger);
    }
}

可以看到名为number的局部变量是作为构造方法的参数传入匿名内部类的(以上代码经过了手动修改,真实的反编译结果中有一些不可读的命名)。

如果Java允许匿名内部类访问非final的局部变量的话,那我们就可以在TryUsingAnonymousClass$1中修改paramInteger,但是这不会对number的值有影响,因为它们是不同的reference。

这就会造成数据不同步的问题。

所以,Java为了避免数据不同步的问题,做出了局部内部类(包含匿名内部类)只可以访问final的局部变量的限制。

你可能感兴趣的:(Java内部类)