zoukankan      html  css  js  c++  java
  • 【Java】内部类

    前言

    将一个类的定义放在另外一个类定义的内部,里面的就叫做内部类。内部类是一种非常有用的特性,它允许你把一些逻辑相关的类组织在一起,并控制内部的类的可视性。看起来像是一种代码隐藏机制:将类的实现定义在了其他类中。但是,内部类的优点远不止于此,它可以访问外围类的任何成员并与之通信、让一个类实现多继承等。

    本篇博客先介绍内部类的基础语法,然后介绍内部类的一些特性。

    内部类的创建及使用

    public class Parcel {
    	//内部类
    	class Contents{
    		private int i = 1;
    		public int value() {return i;}
    	}
    	//内部类
    	class Destination{
    		private String label;
    		Destination(String whereTo){
    			label = whereTo;
    		}
    		String readLabel() {return label;}
    	}
    	
    	public void ship(String dest) {
    		Contents c = new Contents();
    		Destination d = new Destination(dest);
    		System.out.println(d.readLabel());
    	}
    	
    	//获取内部类对象
    	public Contents contents() {return new Contents();}
    	public Destination to(String dest) {return new Destination(dest);}
    	
    	public static void main(String[] args) {
    		Parcel p = new Parcel();
    		p.ship("Bermuda");
    		
    		//获取内部类对象 
    		Parcel.Contents c = p.contents();
    		Parcel.Destination d = p.to("Borneo");	
    	}
    }
    /*
    output:
    Bermuda
    */
    

    若是想在除外围类的非静态方法之外的其他位置创建内部类对象,就需要具体指明这个对象的类型:OuterClassName.InnerClassName,并且要先创建这个外围类对象,再使用.new语法。

    Parcel.Destination d = p.new Destination();
    

    内部类访问外围类

    当生成一个内部类对象时,此对象与制造它的外围类对象之间就有了某种联系,它可以访问外围类对象的所有方法,而不需要任何特殊权限。此外,内部类还拥有外围类的所有域的访问权限

    interface Selector{
    	boolean end();
    	Object current();
    	void next();
    }
    
    public class Sequence {
    	private Object[] items;
    	private int next = 0;
    	public Sequence(int size) {items = new Object[size];}
    	public void add(Object x) {
    		if(next < items.length) {
    			items[next++] = x;
    		}
    	}
    	//一个迭代器的实现
    	private class SequenceSelector implements Selector{
    		private int i = 0;
    		//访问外围类中的数组
    		public boolean end() {return i == items.length;}	//检查是否到了末尾
    		public Object current() {return items[i];}	//返回当前对象
    		public void next() {if(i < items.length) i++;}	//移到序列中的下一个
    	}
    	
    	public Selector selector() {return new SequenceSelector();}
    	
    	public static void main(String[] args) {
    		Sequence sequence = new Sequence(10);
    		for(int i=0; i<10; i++)
    			sequence.add(Integer.toString(i));
    		//获取一个内部类对象
    		Selector selector = sequence.selector();
    		while(!selector.end()) {
    			System.out.print(selector.current() + " ");
    			selector.next();
    		}
    	}
    }
    /*
    output:
    0 1 2 3 4 5 6 7 8 9 
    */
    

    上面的例子是一个迭代器设计模式的例子,从例子中可以看出,内部类是可以访问到外围类的数据成员。当然内部类也是可以访问外围类的方法的,就像自己拥有它们,这使使编程十分便利。

    那么,内部类自动拥有对外围类所有成员的访问权限这是如何实现的呢?

    当某个外围类对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是使用那个引用来选择外围类的成员。这些实现,编译器会处理。

    我们需要注意:内部类的对象只有在与其外围类对象相关联的情况下才能被创建(此时的内部类是非static的)。一个内部类被嵌套多少层不重要,它仍旧可以透明地访问所有它所嵌入的外围类的所有成员。

    使用.this和.new

    前面我们获取内部类对象时,是使用外围类对象调用它的创建内部类的方法,那么有没有其他方式来创建内部类对象呢?还是有的,使用.new语法。

    class A{
        class B{
        //    ...
        }
        //...
        public static void main(String args[]){
            A a = new A();
            A.B b = a.new B(); //错误的做法是 A.B b = new B();
        }
    }
    

    我们必须使用外部类对象来创建内部类对象,这样也解决了内部类名字作用域问题,不必去声明a.new A.B(),但是需要注意a.new A.B()是错误的。最简单的理由便是内部类对象不能获取到外部类对象的引用。若是创建的是静态内部类,则不需要对外部类对象的引用(后面会介绍)。

    关于.this的使用,是用于生成对外部类对象的引用,使用方法为外部类的名字后面紧跟圆点和this。这样产生的引用自动地拥有正确的类型,这一点在编译期便可以被知晓并受到检查,因此没有任何运行时开销。

    class A{
        public void f(){
            //...
        }
        class B{
            public A getA(){
                return A.this;
            }        
        }
        public static void main(String args[]){
            A a = new A();
            A.B b = a.new B();
            b.getA().f();
        }
    }
    

    private内部类

    继续以前面的内部类访问外部类的代码说明。SequenceSelector是private的,在外部不能直接被创建,于是在Sequence中使用了一个创建方法获取该类对象。使用接口Selector引用指向该内部类对象操纵其内部方法。我们可以看出,在外部我们只是使用接口引用去操作,对内部类的具体实现是一无所知,甚至内部类的名称我们也不知道。这真的是很方便的隐藏了实现细节。private内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。

    以上介绍了内部类的基本使用,看起来和使用普通的类并无两样,但是这只是内部类的简单使用,下面介绍内部类中的一些复杂的技术。

    在方法和作用域内的内部类

    不仅可以在类里面定义内部类,也可以在一个方法或者任意的作用域内定义内部类。

    在方法的作用域内创建一个完整的类叫做局部内部类

    class A{
        int i = 0;
        public void fun(){
            class B{
                i = 10;	//可以访问外围类的成员
                //...
            }
        }
        //...
    }
    

    在任意作用域内嵌入一个内部类

    class A{
        public void fun(boolean b){
            if(b){
                class B{
                    ...
                }
                //...
            }
        }
        //...
    }
    

    B类被嵌入在if语句的作用域中,不是说明该类的创建是有条件的。实际上B类与其他类都一同编译过了,只是它将在if这个作用域内使用。

    匿名内部类

    interface Destination{
    	String readLabel();
    }
    
    interface Contents{
    	int value();
    }
    
    

    public class Parcel2 {
    	public Contents contents() {
    		//创建匿名内部类
    		return new Contents() {
    			private int i = 1;
    			@Override
    			public int value() { return i;}
    		};
    	}
    	public static void main(String[] args) {
    		Parcel2 p2 = new Parcel2();
    		Contents c = p2.contents();
    	}
    }
    

    contents()方法将返回值的创建与表示这个返回值的类的定义结合在了一起,并且这个类是匿名没有名字。这种语法指出的是:创建一个继承自Contents的匿名内部类的对象。通过new表达式返回的引用被自动向上转型为对Contents的引用

    上述匿名内部类的语法是下述形式的简化:

    public class Parcel2b{
        
    	class MyContents implements Contents{
    		private int i = 1;
    		public int value() { return i; }
    	}
    	
    	public Contents contents() { return new MyContents();}
    	
    	public static void main(String[] args) {
    		Parcel2b p2b = new Parcel2b();
    		Contents c = p2b.contents();
    	}
    }
    

    在匿名内部类中使用的是默认构造器生成Contents。但是,若基类需要一个有参数的构造器怎么办?

    public class Wrapping {
    	private int i;
    	public Wrapping(int ii) { i = ii;}
    	public int value() {return i;}
    }
    

    使用普通的具体类作为基类。

    public class Parcel3 {
    	public Wrapping wrapping(int ii) {
    		//将参数传给基类带参数的构造器
    		return new Wrapping(ii) {
    			@Override
    			public int value() {
    				return super.value() * 31;
    			}
    		};
    	}
    	
    	public static void main(String[] args) {
    		Parcel3 p = new Parcel3();
    		Wrapping w = p.wrapping(21);
    	}
    }
    

    若是基类构造器带参数,则简单地将合适的参数传递给基类的构造器即可。在匿名内部类末尾的分号不是用来标记内部类结束标志的。实际上,是表达式的结束。

    为匿名内部类创建一个构造器

    在匿名内部类中定义字段时,可以使用形参为其进行初始化,但是使用的参数引用必须使用final修饰。

    class A{
        public void fun(final String str){
            return new Destination(){
                private String label = str;
                public String readLabel { return label;}
            }
        }
    }
    

    我们可以使用此方法为匿名内部类中的字段进行初始化,那么有没有可能为匿名内部类创建一个构造器,使用构造器为字段进行初始化呢?我们要知道,匿名类是没有名字的,所以不可能创建一个有名字的构造器。但是,我们可以通过实例初始化的方式,达到构造器的效果

    abstract class Base{
    	public Base(int i) {
    		System.out.println("Base 构造器, i="+i);
    	}
    	public abstract void f();
    }
    
    public class AnonymousConstructor {
        //变量i不要是final的,因为是给匿名内部类的基类使用,不会再匿名内部类的内部直接被使用
    	public static Base getBase(int i) {
    		return new Base(i) {
                //实现构造器效果
    			{
    				System.out.println("内部类初始化");
    			}
    			@Override
    			public void f() {
    				System.out.println("匿名内部类f()");
    			}
    		};
    	}
    	public static void main(String[] args) {
    		Base base = getBase(31);
    		base.f();
    	}
    }
    /*
    output:
    Base 构造器, i=31
    内部类初始化
    匿名内部类f()
    */
    

    对于匿名内部类而言,实例初始化的效果就是构造器。这也受到了限制,不能重载实例初始化方法。匿名内部类与正常的类的继承也有一些限制:匿名内部类可以扩展类也可以实现接口,但是一次只能选一个

    使用匿名内部类实现工厂方法

    使用匿名内部类改善上篇博客中的工厂方法的模板。

    interface Service{
        void method1();
        void method2();
    }
    
    interface ServiceFactory{
        Service getService();
    }
    
    class Implementation1 implements Service{
        Implementation1(){}
        public void method1(){System.out.println("Implementation1 method1");}
        public void method2(){System.out.println("Implementation1 method2");}
        //使用匿名内部类
        public static ServiceFactory factory = new ServiceFactory() {
    		@Override
    		public Service getService() {
    			return new Implementation1();
    		}
    	};
    }
    
    
    class Implementation2 implements Service{
        Implementation2(){}
        public void method1(){System.out.println("Implementation2 method1");}
        public void method2(){System.out.println("Implementation2 method2");}
        
        public static ServiceFactory factory = new ServiceFactory() {
    		@Override
    		public Service getService() {
    			return new Implementation2();
    		}
    	};
    }
    
    public class Factories{
        public static void serviceConsumer(ServiceFactory fact){
            Service s = fact.getService();
            s.method1();
            s.method2();
        }
        
        public static void main(String args[]){
            serviceConsumer(Implementation1.factory);
            serviceConsumer(Implementation2.factory);
        }
    }
    /*
    output:
    Implementation1 method1
    Implementation1 method2
    Implementation2 method1
    Implementation2 method2
    */
    

    嵌套类

    如果不需要内部类对象与其外围类对象之间有联系,那么就可以将内部类声明为static。这通常称为嵌套类

    嵌套类与普通内部类的不同在于:

    • 创建嵌套类对象不需要外围类对象。嵌套类对象没有外围类对象的隐式引用。
    • 不能从嵌套类对象访问非静态的外围类对象。

    普通的内部类不能含有static数据和static方法,也不可以包含嵌套类。但是嵌套类可以包含这些东西。

    public class Parcel4 {
    	//嵌套类
    	public static class PContents implements Contents{
    		private int i = 1;
    		public int value() {return i;}
    	}
    	
    	//嵌套类
    	protected static class PDestination implements Destination{
    		private String label;
    		private PDestination(String whereTo) {
    			label = whereTo;
    		}
    		public String readLabel() {return label;}
    		
    		//包含其他的嵌套类
    		public static void f() {}
    		static int x = 10;
    		static class AnotherLevel{
    			public static void f() {}
    			static int x = 10;
    		}
    	}
    	
    	public static Destination destination(String s) { return new PDestination(s);}
    	public static Contents contents() {return new PContents();}
    	
    	public static void main(String[] args) {
    		Contents c = contents();
    		Destination d = destination("Bermuda");
    	}
    }
    

    接口内的类

    正常情况下,不能在接口中防止任何代码,但是嵌套类可以是接口的一部分。放置到接口中的任何类自动的都是public和static的。可以在接口的内部类中实现该外围接口。

    public interface ClassInInterface {
    	void howdy();
    	//内部类实现外围接口
    	class Test implements ClassInInterface{
    		public void howdy() { System.out.println("Howdy!"); }
    	}
    	public static void main(String[] args) {
    		new Test().howdy();
    	}
    }
    /*
    output:
    Howdy!
    */
    

    为什么需要内部类

    使用内部类最吸引人的原因在于:

    每个内部类都能独立地继承自一个接口的实现,无论外围类是否已经继承了某个接口的实现,对于内部类都没有影响。

    内部类可以提供继承多个具体或抽象类的能力,使得一个类可以实现多继承

    前面交给接口也实现了类的多继承问题,而内部类更有效地实现了“多重继承”。一个类可以使用内部类可以继承多个非接口类型(具体类或者抽象类)。如果拥有的是抽象类或具体类,而不是接口,那么只能使用内部类才能实现多重继承。

    内部类除了可以解决“多重继承”,还有一些其他特性:

    • 内部类可以有多个实例,每个实例都有自己的状态信息,与外围类对象的信息相互独立。
    • 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
    • 内部类没有令人迷惑的“is-a”关系,它就是一个独立的实体。

    内部类的继承

    内部类对象会隐式拥有外部类对象的引用,但是内部类的导出类却不会隐式拥有外部类的引用,所以需要在内部类的导出类的构造器中显式传递外围类的引用

    class Outer{
        class Inner{}
    }
    
    public class InheritInner extends Outer.Inner{
    	public InheritInner(Outer o) {
            //使用enclosingClassReference.super()语法提供必要的引用
    		o.super();
    	}
    	public static void main(String[] args) {
    		Outer outer = new Outer();
    		InheritInner ii = new InheritInner(outer);
    	}
    }
    

    内部类可以被覆盖吗?

    如果创建了一个内部类,然后继承其外围类并又定义了一个与此内部类同名的内部类,会覆盖掉原来的内部类吗?

    答案是不会的。两个内部类是完全独立的两个实体,在自己的命名空间内。

    内部类标识符

    每个类都会产生一个.class文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个“meta-class”,叫做Class对象),当然,内部类也会生成一个.class文件已包含它们的Class对象信息。这些文件的命名有严格的规则:外围类的名字,加上$,在加上内部的名字。若内部类是匿名的,编译器则会简单地产生一个数组作为其标识符

    如下代码生成的.class文件为

    class A{
    	class B{}
    }
    class C{}
    class D{
    	public C getC() {
    		return new C() {
    		};
    	}
    	public static class E{}
    }
    public class ClassFile {
    	public static void main(String[] args) {
    		new D().getC();
    	}
    }
    

    小结

    本文主要讲了内部类的定义以及一些特殊的用途。其实在Java的GUI编程中也经常使用内部类,我没有过多介绍,感兴趣的看官可以去搜搜了解。内部类的如何使用,还是要根据实际的设计情况去安排,先了解有这些用法,用时再取也不迟。不再做过多总结了,有问题的地方望各位看官指出。

    参考:

    [1] Eckel B. Java编程思想(第四版)[M]. 北京: 机械工业出版社, 2007

  • 相关阅读:
    纯css实现全兼容的元素水平垂直居中
    javascript事件之:jQuery.event.dispatch详解
    jQuery事件之:jQuery.event.trigger
    javascript事件之:jQuery.event.remove事件详解
    javascript事件之:jQuery.event.add事件详解
    requirejs 使用实例
    javascript事件之:jQuery事件中Data数据结构
    The D Programming Language 书评
    我来写个人民币金额转大写的例子
    python 集合去重
  • 原文地址:https://www.cnblogs.com/myworld7/p/10414401.html
Copyright © 2011-2022 走看看