Java中的内部类

Java中的内部类

总结一下内部类。如有错误或者不足,欢迎交流讨论。

  1. 内部类的定义,作用
  2. 内部类的分类
  3. 和内部类相关的几个问题

1、什么是内部类。为什么须要内部类,它有什么用?

在一个类的内部定义一个类,就是内部类。内部类提供了某种进入外围类的窗体;每一个内部类都能独立继承子一个实现(接口或抽象类)。不管其外围类是否实现。对内部类都没有影响;实现多继承,Java的中继承是单根继承,仅仅能继承自一个类,尽管能够实现多个接口,可是假设要继承自多个类,那么仅仅有内部类才干够解决,思路是外围类继承一个类,内部类继承另外一个类。尽管内部类仅仅继承了一个类。可是由于它有一个隐式的指向外围类对象的引用(非静态内部类才干够)。它能够訪问外围类的成员。若是要訪问外围类的方法。能够定义一个生成对外围类对象的引用的方法。这样就是实现了“多继承”。

2、内部类的分类

2.1 普通内部类

直接在一个类里面再定义一个类,不在某个方法里面。

创建内部类的对象不能像普通类一样,由于隐含有指向外围类对象的引用,必须先创建外围类。例如以下所看到的(还实现了继承自两个类):

package innerclass;
/**
 * 普通内部类。并实现“多继承”
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
class A{
    public A(){
        System.out.println("class A constructor");
    }

    public void f(){
        System.out.println("class A f()");
    }
}

class B{
    public B(){
        System.out.println("class B constructor");
    }

    public void f(){
        System.out.println("class B f()");
    }

    class InnerClass extends A{
        public InnerClass(){
            System.out.println("class InnerClass constructor");
        }
        //返回生成内部类的外围类对象的引用
        public B outer(){
            return B.this;
        }
    }

    //典型新建内部类情况,外围类有一个返回内部类对象的引用的方法
    public InnerClass inner(){
        return new InnerClass();
    }
}

public class CommonInnerClass {

    public static void main(String[] args) {
        B b = new B();
        //通过外围类创建内部类
        B.InnerClass ic1 = b.new InnerClass();
        //通过外围类的方法创建内部类。典型方法
        B.InnerClass ic2 = b.inner();
        ic1.f();
        //使用内部类訪问外围类的f()
        ic2.outer().f();
        //验证outer()方法返回的引用就是创建内部类的外围类的引用,打印其hashcode
        System.out.println(b);
        System.out.println(ic2.outer());
    }
}
/**
 输出:
class B constructor
class A constructor
class InnerClass constructor
class A constructor
class InnerClass constructor
class A f()
class B f()
innerclass.B@2a139a55
innerclass.B@2a139a55
 */

2.2 局部内部类

在方法中定义的类,它不能有訪问说明符。由于不是外围类的一部分。它属于包括它的方法。另外,假设要在内部类中使用在外部定义的对象,为了防止在外围类中被改动。应将该对象引用修饰为final。

package innerclass;
/**
 * 局部类
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
interface Counter{
    int next();
}
public class LocalInnerClass {
    private int count = 0;

    //内部类要使用name,定义为final是为了防止外围类对name进行了改动
    Counter getCounter(final String name){
        class LocalCounter implements Counter{

            public LocalCounter(){
                System.out.println("LocalCounter constructor");
            }

            public int next(){//继承、实现不能缩小訪问范围
                System.out.print(name);
                return count++;
            }

        }
        return new LocalCounter();
    }
    public static void main(String[] args) {
        LocalInnerClass l = new LocalInnerClass();
        Counter c  = l.getCounter(" LocalInnerClass ");
        for (int i = 0; i < 4; i++)
            System.out.println(c.next());
    }
}
/**
 * 输出:
 LocalCounter constructor
 LocalInnerClass 0
 LocalInnerClass 1
 LocalInnerClass 2
 LocalInnerClass 3
 */

2.3匿名内部类

匿名内部类看起来就像在创建一个类的对象时。突然插入了一个类的定义。由于没有名字,所以没有构造器。没有构造器也带来了一个有趣的问题,怎样初始化呢?在初始化的博客中。我写到。实例初始化在构造器之前,就是由于匿名内部类的存在,它没有构造器,能够利用实例初始化当做构造器使用。所以,实例初始化是在构造器之前运行。

假设某一内部类仅仅是须要一个对象,那么定义为匿名内部类最合适只是了。

只是也有缺点,匿名内部类仅仅能实现一个接口或继承自一个类,不能实现两个接口,或者一个接口并继承自一个类,由于是匿名的。若能够两个,那么返回类型(通过new)无法确定。

package innerclass;
/**
 * 匿名内部类,在一个包内,Counter接口在上面已经定义了
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
public class AnonymousInnerClass {
    private int count = 0;
    Counter getCounter(final String name){
        return new Counter(){
            //实例初始化,当做构造器使用
            {
                System.out.println("Instance initialization");
            }
            public int next(){
                System.out.print(name);
                return count++;
            }
        };
    }
    public static void main(String[] args) {
        AnonymousInnerClass a = new AnonymousInnerClass();
        Counter c2 = a.getCounter(" Anonymous inner ");
        for(int i = 0 ; i < 4;i++)
            System.out.println(c2.next());
    }
}
/**
 * 输出:
Instance initialization
 Anonymous inner 0
 Anonymous inner 1
 Anonymous inner 2
 Anonymous inner 3
 */

2.4 静态内部类

又名嵌套类(注意不是嵌套的类)。如其名,内部类的定义前加了static修饰符。这时静态内部类与外围类无联系,创建静态内部类的对象不须要先创建外围类的对象,也不能訪问非静态的外围类对象,也称为嵌套类。

静态内部类与普通内部类的差别除了上述外,普通内部类的成员或方法无法被static修饰。这是一个编译问题,先从静态成员变量说起。静态成员变量是在类载入时初始化的,也就是第一次创建类的对象或者訪问静态成员变量,它在类的内存内存分布中是有确切的位置的,对于内部类来说。其内存的分配和非静态的成员变量是相似的,不创建外围类的对象,那么成员变量的值就不确定。从而其内存位置也不确定,且外围类对象的内存位置也不确定,其引用的内存位置也就不能确定。而普通内部类的创建依赖外围类的对象的创建,所以,普通的内部类是不能有static修饰的成员变量和方法的。

package innerclass;
/**
 * 静态内部类
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
interface Contents{
    int value();
}

interface Destination{
    String readLabel();
}

public class NestedClass {

    private static class ParcelContents implements Contents{
        private int i = 11;
        public int value(){
            return i;
        }

        void printf(){
            System.out.println("ParcelContents: "+i);
        }
    }   

    protected static class ParcelDestination implements Destination{
        private String label;

        private ParcelDestination(String label){
            this.label = label;
        }
        public String readLabel(){
            return label;
        }

        public static void f(){}
        static int x = 10;

        static class AnotherLevel{
            static int x = 10;
            public static void f(){}
        }
    }

    public static Destination destination(String s){
        return new ParcelDestination(s);
    }
    public static Contents contents(){
        return new ParcelContents();
    }

    public static void main(String[] args) {
        Contents c = contents();
        Destination d = destination("Yanni");
        c.value();
    }

}

这段例程例程有些地方还须要分析,在后面的内部类与向上转型。

接口内部类,即在接口里面定义的类,接口中的不论什么类都是隐式public和static的。比如:

package innerclass;
/**
 * 接口内部类
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
public interface ClassInsideInterface {
    void howdy();

    class Test implements ClassInsideInterface{
        @Override
        public void howdy() {
            System.out.println("howdy");
        }
        public static void main(String[] args){
            new Test().howdy();
        }
    }
}

编译器不会报错,接口内部类可用来实现接口的公共代码,该代码能够被不同的实现所公用。

为每一个类编写測试,可用嵌套类,就不用每一个都编写。

3、内部类的几个问题

  • 内部类与向上转型
    将内部类向上转型为基类。尤其是接口,内部类的实现细节就隐藏了。而且不可见,若内部类是private的。那么在非外围类中还无法向下转型,能够说实习细节全然隐藏了
package innerclass;
/**
 * 内部类和向上转型
 * @author Administrator @date 2016年3月29日
 * http://blog.csdn.net/xiaoguobaf
 */
public class InnerClassAndUpcasting {

    private class ParcelContents implements Contents{
        private int i = 11;
        public int value(){
            return i;
        }

        //默认是private的,随类。实际上没什么用。搭配public配合接口。向上转型为接口类型。可实现全然隐藏实现细节
        void printf(){
            System.out.println("ParcelContents: "+i);
        }
        //仅仅在InnerClassAndUpcasting中能訪问,无特色,在protected修饰的类中还能够訪问,但没有必要,不如声明为private的。
        public void callPrintf(){
            printf();
        }       
    }   

    protected class ParcelDestination implements Destination{
        private String label;

        private ParcelDestination(String label){
            this.label = label;
        }
        public String readLabel(){
            return label;
        }
    }

    public Destination destination(String s){
        return new ParcelDestination(s);
    }
    public Contents contents(){
        return new ParcelContents();
    }

    public static void main(String[] args) {
        InnerClassAndUpcasting inau = new InnerClassAndUpcasting();
        Contents c = inau.contents();
        Destination d = inau.destination("Yanni");
        //必须先要向下转型才干訪问子类型的方法。由于继承扩展信息,父类不能訪问子类中扩展的信息
//      c.printf();//提示方法未定义
        ((ParcelContents)c).printf();
        ((ParcelContents)c).callPrintf();
        d.readLabel();
    }
}
/**
 * 一个class文件中面能够有两个main函数,仅仅是在运行时eclipse将提示你要运行哪个。我这里是为了方便測试
 * @author Administrator @date 2016年3月29日
 * http://blog.csdn.net/xiaoguobaf
 */
class Test {
    public static void main(String[] args){
        InnerClassAndUpcasting inau1 = new InnerClassAndUpcasting();
        Contents c1 = inau1.contents();
        System.out.println(c1.value());
        //无法向下转型,由于ParcelContents类是private,出了其外围类InnerClassAndUpcasting。无法訪问
        //((ParcelContents)c).printf();
    }
}
/**
 * 输出:
ParcelContents: 11
ParcelContents: 11

11
*/  

  • 多层嵌套的内部类的訪问
    多层嵌套的内部类能够透明的訪问全部它所嵌入的外围类的全部成员。非常好理解,由于有指向外围类的引用(非static的),静态内部类能够理解为和静态成员变量一样的,从内存角度来看的话。
  • 内部类的继承
    内部类能够继承。语法特殊些,由于要将指向外围类的引用初始化(非static)。

package innerclass;
/**
 * 内部类的继承
 * @author Administrator @date 2016年3月29日
 * http://blog.csdn.net/xiaoguobaf
 */
class WithInner{
    class Inner{}
}
public class InnerClassInheriting extends WithInner.Inner{
    InnerClassInheriting(WithInner wi){
        wi.super();
    }
    public static void main(String[] args){
        WithInner wi = new WithInner();
        InnerClassInheriting ic = new InnerClassInheriting(wi);
    }
}
  • 内部类能够覆盖么
    内部类不能够覆盖,就像在父类和子类中定义同名同属性的成员相似,是不同的。

  • 内部类与闭包、回调
    闭包,是一个可调用的对象,它的信息来自于创建它的作用域。内部类是面向对象的闭包。由于它包括外围类对象的信息,还默认拥有(非static)一个指向外围类对象的引用。
    回调:简单的来说,就是你调用我,我调用你,先写到这里。看TIJ我还认为上面样例有点想策略模式,查的结果有的说想观察者模式。先放着吧,可參考 以及

參考资料:

  1. http://blog.csdn.net/lanxuezaipiao/article/details/41822683
  2. http://arron-li.iteye.com/blog/681733
  3. http://hellosure.iteye.com/blog/1130176
  4. http://www.iteye.com/problems/96742
posted @ 2017-07-08 21:45  cxchanpin  阅读(263)  评论(0编辑  收藏  举报