Java 内部类

2017-11-04 21:58:46

内部类概述:把类定义在其他类的内部,这个类就叫做内部类。

内部类作用: 1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整

       2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏

       3.方便编写事件驱动程序

       4.方便编写线程代码

       5.内部类的存在使得Java的多继承机制变得更加完善

内部类的访问特点内部类可以直接访问外部类的成员,包括私有;

         外部类要访问内部类的成员必须创建对象;

内部类的分类:成员内部类(成员位置)

         局部内部类(函数内部)

         静态内部类

         匿名内部类

  • 成员内部类

访问成员内部类格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象

成员内部类访问修饰符:跟成员变量一样没有作用域修饰符限制(创建依赖于外部类,即在外部类中如果要访问成员内部类成员时,需要先创建外部类对象,通过外部类对象引用来访问内部类对象),创建成员内部类方式:new OutterClass().new InnerClass();

一般需要加上private进行保护,但是这样就不能在外部访问了,但是可以在类内提供一个成员方法进行访问。

class OuterClass{
    private int num = 10;

    class Inner{
        public void show(){
            System.out.println(num);
        }
    }
}

public class InnerClass {
    public static void main(String[] args) {
     // 创建方法 OuterClass.Inner inner = new OuterClass().new Inner(); inner.show(); } }

成员内部类重名访问问题的解决:

/**
 * 依次输出30,20,10
 */
class OuterClass{
    private int num = 10;


    class Inner{
        private int num=20;
        public void show(){
            int num=30;
            System.out.println(num);
            System.out.println(this.num);
            System.out.println(OuterClass.this.num);
        }
    }
}

public class InnerClass {
    public static void main(String[] args) {
        OuterClass.Inner inner = new OuterClass().new Inner();
        inner.show();
    }
}

内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:

1)成员内部类的引用方式必须为 外部类名.内部类名

2)构造器中必须有指向外部类对象的引用,并通过这个引用调用super()

class WithInner{
    class Inner{}
}
public class InheritInner extends WithInner.Inner{
    InheritInner(WithInner wi){
        wi.super(); //wi的父类是object
    }
    public static void main(String[] args){
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

 而进一步,当被继承的内部类只有非默认构造器时应该怎么办呢?

class WithInner{
    class Inner{
        public Inner(int i){
            System.out.println(i);
        }
    }
}
public class InheritInner extends WithInner.Inner{
    InheritInner(WithInner wi){
        int i=0;
        wi.super(i);//如代码所示,当被继承的构造器需要参数时,应把参数传递给这个super函数
    }
    public static void main(String[] args){
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

我们可以看出要想创建Inner的对象必须先创建WithInner的对象之后才能创建Inner对象,那么现在你要用一个类InheritInner继承Inner类,在继承过程中构造方法会被调用,即使你不写也会调用默认构造方法,但问题出现了,在调用父类Inner构造方法时找不到WithInner的对象,所以就必须给InheritInner类的构造方法传入WithInner对象再通过wi.super();方法调用Inner的默认构造方法,因为这是创建对象的基本流程,所以这句话wi.super();是必须的。

  • 静态内部类

访问成员内部类格式外部类名.内部类名 对象名 = 外部类名.内部类对象

静态内部类访问修饰符跟静态变量一样没有作用域修饰符限制,创建静态内部类方式:new OutterClass.InnerClass();

被静态修饰的成员内部类只能访问外部类的静态成员

内部类方法被静态修饰后的方法可以是静态方法,也可以是非静态方法

class OuterClass{
    private static int num = 10;

    static class Inner{
        public void show(){
            System.out.println(num);
        }
    }
}

public class InnerClass {
    public static void main(String[] args) {
        OuterClass.Inner inner = new OuterClass.Inner();
        inner.show();
    }
}
  • 局部内部类

局部内部类的访问修饰符跟局部变量一样不能有作用域修饰符

局部内部类的访问特点:局部内部类可以直接访问外部类的成员

             在局部位置,可以创建内部类对象,通过对象可以调用内部类的方法,来使用局部类功能

             局部内部类访问局部变量必须是final修饰

局部变量使用final修饰的原因:局部变量是随着方法的调用儿调用的,随着调用完毕而消失(所以局部变量不能声明为static);

              而堆内存的内容不会立即消失,所以,我们需要加上final修饰符;

              在加入fianl修饰符后,这个变量就变成了常量,编译器会进行优化,类似于C++里的宏变量,会直接将所有出现这个final的地方用它的值进行简单替换。

class Outer
{
  int num =2;

  public void method()
  {
     final int num2 = 3;
     
     class Inner
    {
       System.out.println(num);
       System.out.println(num2);
    }
  }
}                
  • 匿名内部类

匿名内部类:其实就是内部类的简化写法,省略了类名。好处是匿名内部类在使用完后就会被回收

前提存在一个类或者接口(这里的类可以是具体类也可以是抽象类)

匿名内部类访问修饰符无修饰符

匿名内部类的本质是一个继承了该类或者实现了该接口的实现类的对象

匿名内部类的缺陷:

1)它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类是不能够被重复使用

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

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

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

5)匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效

匿名内部类的格式

new 类名或接口名(){
  重写方法;
}

匿名内部类在访问局部变量的时候也需要是final类型,但是,局部内部类和匿名内部类访问的局部变量必须由final修饰,java8开始,可以不加final修饰符,由系统默认添加。java将这个功能称为:Effectively final 功能。

interface Interdemo {
    public void show();
}

class OuterClass {
    public void method() {
        //final int num = 2;
        int num = 2;
        Interdemo interdemo = new Interdemo() {
            @Override
            public void show() {
                System.out.println(num);
            }
        };

        interdemo.show();
    }
}

public class InnerClass {
    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        outerClass.method();
    }
}

匿名内部类初始化:

我们一般都是利用构造器来完成某个实例的初始化工作的,但是匿名内部类是没有构造器的!那怎么来初始化匿名内部类呢?使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。Java中匿名内部类的双括号初始化,形如:

// 新建一个列表并赋初值A、B、C
    ArrayList<String> list = new ArrayList<String>() {{
        add("A");
        add("B");
        add("C");
    }};

// 新建一个Map并给初值
HashMap<Character, Integer> map = new HashMap<Character, Integer>(){
        {
            put('I', 1);
            put('V', 5);
            put('X', 10);
            put('L', 50);
            put('C', 100);
            put('D', 500);
            put('M', 1000);
        }
    };

这种方法被称为双大括号初始化(double brace initialization)或者匿名内部类初始化法,实际上是一种取巧的方式。

这里以ArrayList的例子解释,首先第一层花括号定义了一个继承于ArrayList的匿名内部类 (Anonymous Inner Class):

 

// 定义了一个继承于ArrayList的类,它没有名字
new ArrayList<String>(){
  // 在这里对这个类进行具体定义
};

 

 

第二层花括号实际上是这个匿名内部类实例初始化块 (Instance Initializer Block)(或称为非静态初始化块):

 

new ArrayList<String>(){
  {
    // 这里是实例初始化块,可以直接调用父类的非私有方法或访问非私有成员
  }
};

 

我们通过new得到这个ArrayList的子类的实例并向上转型为ArrayList的引用:

 

ArrayList<String> list = new ArrayList<String>() {{}};
  • 我们得到的实际上是一个ArrayList的子类的引用,虽然这个子类相比ArrayList并没有任何功能上的改变。
  • 可以认为这是个本身装有数据的子类(因为它的数据来自于自身的初始化),而不是取得引用后再赋值。
  • 非静态内部类的对象会隐式强引用其外围对象,所以在内部类未释放时,外围对象也不会被释放,从而造成内存泄漏。

 

posted @ 2017-11-05 17:11  hyserendipity  阅读(306)  评论(0编辑  收藏  举报