Java基础之内部类

[参考原文]http://www.cnblogs.com/chenssy/p/3388487.html

概述

是什么:· 将一个类的定义放在另一个类的定义内部,就是内部类

为什么使用内部类:

内部类自动拥有对其外围类所有成员的访问权。

使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。

当某个外围类的对象创建一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。 内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(非static类时) 普通的内部类(非静态内部类)不能有static数据和static字段,也不能包含嵌套类.

基本的使用

通过简单的成员内部类来介绍基本的内部类的使用,以及使用.this与.new。

一个成员内部类的操作案例:

public class OuterClass {
    private String outName ;

    public String getOutName() {
        return outName;
    }
    public void setOutName(String outName) {
        this.outName = outName;
    }

    public class InnerClass{   //内部类定义
        private String innerName;

        //private static int age=0; //编译错误,成员内部类中不能存在static变量和方法
        private static final int age=0;  //但是static final编译通过

        public String getInnerName(){
            return this.innerName;

        }

        public InnerClass(){   //内部类构造函数
            setOutName("white");    //可直接调用外部类的函数
        }

        public OuterClass getOuterClass(String innerName){
            this.innerName=innerName; //直接this内部类的引用
            return OuterClass.this;  //OuterClass.this外部类的引用

        }
    }

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


    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();//创建外部类实例
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();//通过.new 创建内部类实例,也可以通过getInnerClass()方法创建
        String outerName = innerClass.getOuterClass("black").getOutName();
        String innerName=innerClass.getInnerName();
        System.out.println(innerName);
        System.out.println(outerName);

    }

}

输出: black

white

成员内部类

成员内部类可以有访问修饰符,理解上可以看做外围类的变量

成员内部类也是最普通的内部类,可以无限制的访问外围类的所有 成员属性和方法,即使是private的。 但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。

成员内部类中不能存在任何static的方法,也不可以有static的变量,但可以有static final的变量; 成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。

实例见上

面试题 - 下面的代码哪些地方会产生编译错误?

class Outer {

    class Inner {}

    public static void foo() { new Inner(); }

    public void bar() { new Inner(); }

    public static void main(String[] args) {
        new Inner();
    }
}

注意:Java中非静态内部类对象的创建要依赖其外部类对象,上面的面试题中foo和main方法都是静态方法,静态方法中没有this,也就是说没有所谓的外部类对象,因此无法创建内部类对象,如果要在静态方法中创建内部类对象,可以这样做:


  new Outer().new Inner();

局部内部类

不可以有访问修饰符(public,private等)

有这样一种内部类,它是嵌套在方法和作用于内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类。 局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。

局部内部类定义下方法中,不可以有public,private等修饰符

内部类在方法体中

interface Destination{

    String getDestName();

}
public class OuterClass {
    public Destination destionation(String str){
       class PDestination implements Destination{//在方法体中定义内部类
            private String destName;
            private PDestination(String whereTo){
                destName = whereTo;
            }
            public String getDestName(){
                return destName;
            }
        }
        return new PDestination(str);//在方法中返回内部类的实例
    }

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        Destination d = outerClass.destionation("wing");
        System.out.println(d.getDestName());
    }

}

输出: wing

内部类在作用域中:

public class OuterClass {
    private void internalTrack(boolean b){
        if(b){  //作用域
            class innerClass{  //内部类
                private String name;
                innerClass(String name) {
                    this.name = name;
                }
            }
            innerClass ts = new innerClass("wing");//内部类实例
            System.out.println(ts.name);   //在作用域内,可以直接访问内部类的变量
        }
    }

    public void track(){
        internalTrack(true);
    }

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

}

输出: wing

匿名内部类

没有名字的内部类,不能有修饰符,不能有class关键字

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

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

interface InnerClass {
    //int getNumber();
    String getInnerName();
}

class OuterClass {
    private String outName;
    public InnerClass getInnerClass(final int num){//当所在的方法的形参需要内部类里面使用时,该形参必须为final
        return new InnerClass(){//匿名内部类定义,创建一个**继承**自InnerClass的匿名类的对象,通过new表达式返回的引用,被自动向上转型为对InnerClass的引用
            private int number = num + 3;
            private String innerName;

            /*通过代码块初始化,这种方式叫实例初始化
              实例初始化的效果就是构造函数,但是它是受限制的,不能重载实例初始化方法,仅有一个这样的构造器。
            */
            {
                 this.innerName=outName;//匿名内部类可以直接访问外围类的成员。

            }

            public String getInnerName(){
                return innerName;
            }

            public int getNumber(){
                return number;
            }
        };        /* 注意:分号不能省 */

    }


    public static void main(String[] args) {
        OuterClass out = new OuterClass();
        out.outName="wing";
        InnerClass inner = out.getInnerClass(2);
        System.out.println(inner.getInnerName());
       // System.out.println(inner.getNumber());//错误,若是接口中没有声明这个方法,即使内部类中有定义,也不可以调用
    }
}

输出:

wing

匿名内部类传参: 先通过new Destination(x),将参数传给父类,再用super来调用父类,取得此参数

class Destination{

    private String name;
    public Destination(String name)
    {
        this.name=name;
    }
    public String getName(){
        return name;
    }

}
public class OuterClass {
    public Destination destination(String name){
        return new Destination(name){
            public String getName(){
                return super.getName();

            }


        };

    }
    public static void main(String[] args){
        OuterClass out=new OuterClass();
        Destination dt=out.destination("wing");
        System.out.println(dt.getName());
    }
}

另一个实例:

 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 OuterClass {

    public void test(Bird bird){
        System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
    }

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        outerClass.test(new Bird() {

            public int fly() {
                return 10000;
            }

            public String getName() {
                return "大雁";
            }
        });
    }
}

输出:

大雁能够飞 10000米

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

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

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

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

  1. 匿名内部类是没有访问修饰符的,声明也不需要class关键字。
  2. 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
  3. 当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final。
  4. 匿名内部类是没有构造方法的。因为它连名字都没有何来构造方法。
  5. 匿名内部类中不能存在任何的静态成员变量和静态方法。
  6. 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

为什么final

首先我们知道在内部类编译成功后,它会产生一个class文件,该class文件与外部类并不是同一class文件,仅仅只保留对外部类的引用。当外部类传入的参数需要被内部类调用时,从java程序的角度来看是直接被调用:

public class OuterClass {
    public void display(final String name,String age){
        class InnerClass{
            void display(){
                System.out.println(name);
            }
        }
    }
}

从上面代码中看好像name参数应该是被内部类直接调用?其实不然,在java编译之后实际的操作如下:

public class OuterClass$InnerClass {
    public InnerClass(String name,String age){
        this.InnerClass$name = name;
        this.InnerClass$age = age;
    }


    public void display(){
        System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );
    }
}

所以从上面代码来看,内部类并不是直接调用方法传递的参数,而是利用自身的构造器对传入的参数进行备份,自己内部方法调用的实际上是自己的属性而不是外部方法传递进来的参数。

因此当我们在程序中修改了name和age的值,而内部类中this.InnerClass$name的值仍然会是原来的值

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

http://www.cnblogs.com/chenssy/p/3390871.html

静态内部类

可以有访问修饰符

使用static修饰的内部类我们称之为静态内部类,不过我们更喜欢称之为嵌套类;

如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类(静态内部类)会更方便

我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:

1、 它的创建是不需要依赖于外围类的。

2、 它不能使用任何外围类的非static成员变量和方法。

public class OuterClass {
    private String age;
    public static String outerName = "wing_outer";

    /**
     *静态内部类
     */
   public static class InnerClass1{
        /* 在静态内部类中可以存在静态成员 */
        public static String _inner_name_static = "wing_inner_static";

        public void display(){
            /* 
             * 静态内部类只能访问外围类的静态成员变量和方法
             * 不能访问外围类的非静态成员变量和方法
             */
            System.out.println("2:"+outerName);
            //System.out.println(age);  错误
        }
    }

    /**
     * 非静态内部类
     */
    class InnerClass2{
        /* 非静态内部类中不能定义静态成员 */
        public String _inner_name2 = "wing_inner";
        /* 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的 */
        public void display(){
            System.out.println("4:"+outerName);
        }
    }

    /**
     * @desc 外围类方法
     * @author chenssy
     * @data 2013-10-25
     * @return void
     */
    public void display(){
        /* 外围类访问静态内部类:内部类. */
        System.out.println("1:"+InnerClass1._inner_name_static);
        /* 静态内部类 可以直接创建实例不需要依赖于外围类 */
        new InnerClass1().display();

        /* 非静态内部的创建需要依赖于外围类 */
        OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();
        /* 访问非静态内部类的成员需要使用非静态内部类的实例 */
        System.out.println("3:"+inner2._inner_name2);
        inner2.display();
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        outer.display();
    }
}

输出:

1:wing_inner_static

2:wing_outer

3:wing_inner

4:wing_outer

内部类标识符

手动编译上面的实例OuterClass.java 这里写图片描述

可以看到.class的文件命名,分别对应java中的类是:

Destination接口

匿名内部类

成员内部类

静态内部类

外围类OuterClass

内部类也必须生成一个.class文件,包含它们的Class对象信息。 命名规则是:外围类的名字$内部类的名字.class 如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。

局部内部类与匿名内部类

都不可以有访问修饰符,都可以访问外围类的所有成员。

局部内部类的名字在方法体或作用域外是不可见的。那为什么我们仍然使用局部内部类而不是匿名内部类呢

  1. 需要一个已命名的构造函数,或是重载构造函数,而匿名内部类只能用于实例初始化
  2. 需要不止一个内部类的对象。

内部类可以被覆盖吗

问题:如果创建一个内部类,然后继承其外围类并重新定义此内部类,内部类可以被覆盖吗? 简单来整是不可以的 如下实例,"覆盖"内部类就好像它是外围类的一个方法,其实并不起什么作用

class Egg {
  protected class Yolk {
    public Yolk() {
      System.out.println("Egg.Yolk()");
    }
  }
  private Yolk y;
  public Egg() {
    System.out.println("New Egg()");
    y = new Yolk();
  }
}

public class BigEgg extends Egg {
  public class Yolk {
    public Yolk() {
      System.out.println("BigEgg.Yolk()");
    }
  }
  public static void main(String[] args) {
    new BigEgg();
  }

输出: New Egg() Egg.Yolk()

间接可以,让一个外围类A继承外围类B,A的内部类C继承自B.C的类,重写B.C类中的方法,然后创建A的内部类C的对象,将此引用传给B中

class Egg {
      protected class Yolk {
        public Yolk() {
          System.out.println("Egg.Yolk()");//3.输出  6.输出
        }
        public void f() {
          System.out.println("Egg.Yolk.f()");
        }
      }
      private Yolk y = new Yolk();//2.创建一个Yolk对象
      public Egg() {//4. 执行Egg构造函数,输出
        System.out.println("New Egg()");
      }
      public void insertYolk(Yolk yy) { y = yy; }//将新的Yolk的对象引用传过来,以便调用新的Yolk中的方法,来实现覆盖
      public void g() { y.f(); }
    }

    public class BigEgg extends Egg {//1.先初始化Egg类
      public class Yolk extends Egg.Yolk {//5.初始化Yolk类
        public Yolk() {
          System.out.println("BigEgg.Yolk()");
        }
        public void f() {
          System.out.println("BigEgg.Yolk.f()");
        }
      }
      //7.执行BigEgg构造函数:将新的Yolk的引用传给旧的Yolk中
      public BigEgg() { insertYolk(new Yolk()); }
      public static void main(String[] args) {
        Egg e = new BigEgg();//0.起源:创建一个BigEgg对象
        e.g();
      }
    }

输出: Egg.Yolk()

New Egg()

Egg.Yolk()

BigEgg.Yolk()

BigEgg.Yolk.f()

内部类的继承

略。。。

results matching ""

    No results matching ""