• Java基础 -- 嵌套类(非静态嵌套类、静态嵌套类)


    可以将一个类的定义放在另一个类的内部定义,这样的类就被称为嵌套类,包含嵌套类的类被称为外部类(outer class),也可以叫做封闭类。

    嵌套类可以分为两种:

    • 静态嵌套类(Static Nested Classes):使用static声明,一般称为嵌套类(Nested Classes);
    • 非静态嵌套类(Non-static Nested Classes):非static声明,一般称为内部类(Inner Classes);

    嵌套类是它的外部类的成员。非静态嵌套类(内部类)可以访问外部类的其他成员,即使该成员是私有的,而静态嵌套类只能访问外部类的静态成员。

    嵌套类作为外部类的一个成员,可以被声明为:private,包访问,protected,public(注意:外部类只能被声明为public或者包范围)。

    使用嵌套类的主要优点有以下几个方面:

    • 嵌套类可以访问外部类的数据成员和方法,即使它是私有的;
    • 提高可读性和可维护性:因为如果一个类只对另外一个类可用,那么将它们放在一起,这更便于理解和维护;
    • 提高封装性:给定两个类A和B,如果需要访问A类中的私有成员,则可以将B类封装在A类中,这样不仅可以使得B类可以访问A类中的私有成员,并且可以在外部隐藏B类本身;
    • 减少代码的编写量;

    静态嵌套类可以使用外部类的名称来访问它。例如:

    OuterClass.StaticNestedClass
    

     如果要为静态嵌套类创建对象,则使用以下语法:

    OuterClass.StaticNestedClass nestedObject =
            new OuterClass.StaticNestedClass();
    

    非静态嵌套类包含三种:

    • 成员内部类(Member inner clss);
    • 局部内部类(Local inner class);
    • 匿名内部类(Anonymous inner class);

    Nested Classes

     描述
    成员内部类 在类中和方法外部创建的类
    匿名内部类 为实现接口或扩展类而创建的类,它的名称由编译器决定
    局部内部类 在方法内部创建的类
    静态嵌套类 在类中创建的静态类
    嵌套接口 在类中或接口中创建的接口

     

     

     

     

     

     

    一 非静态嵌套类(内部类)

    非静态嵌套类也就是内部类,它有以下几个特点:

    • 实例化内部类必须先实例化一个外部类;
    • 内部类实例与外部类实例相关联(内部类可以访问外部类的成员),所有不能在内部类中定义任何静态成员;
    • 内部类是非静态的;

    1、成员内部类

    示例一

    在外部类中并且在外部类的方法外创建的非静态嵌套类,称为成员内部类。说白了成员内部类就是外部类的一个非静态成员而已。如下:

    public class Parcel1 {
    	class Contents{
    		private int i = 11;
    		public int value() {return i;}
    	}
    	class  Destination{
    		private String label;
    		Destination(String whereTo){
    			label = whereTo;
    		}		
    		String readLable() {return label;}
    	}	
    	public void ship(String dest) {
    		Contents c = new Contents();
    		Destination d = new Destination(dest);
    		System.out.println(d.readLable());
    	}	
    	public static void main(String[] args) {
    		Parcell p = new Parcel1();
    		p.ship("Zhengy");
    	}
    }
    

    在ship()方法中使用成员内部类,与使用普通类没有什么区别,在这里,实际的区别就是内部成员类的定义是嵌套在Parcel1中。

    示例二

    下面我们尝试在外部类中定义一个方法,该方法返回一个指向成员内部类的引用。

    public class Parcel2 {
    	class Contents{
    		private int i = 11;
    		public int value() {return i;}
    	}	
    	class  Destination{
    		private String label;
    		Destination(String whereTo){
    			label = whereTo;
    		}		
    		String readLable() {return label;}
    	}		
    	public Destination to(String s) {
    		return new Destination(s);
    	}	
    	public Contents contents() {
    		return new Contents();
    	}		
    	public void ship(String dest) {
    		Contents c = new Contents();
    		Destination d = new Destination(dest);
    		System.out.println(d.readLable());
    	}	
    	public static void main(String[] args) {
    		Parcel2 p = new Parcel2();
    		p.ship("南京");		
    		Parcel2 q = new Parcel2();
    		Parcel2.Destination d = q.to("北京");
    		Parcel2.Contents c = q.contents();
    	}
    }
    

     如果想在类外创建某个成员内部类的对象,必须像main()方法那样,具体指明这个对象的类型:OuterClassName.InnerClassName。

    示例三

    当创建一个成员内部类的对象时,此对象与创建它的外部类对象之间就有了一种联系,所以它能够访问其外部类对象的所有成员,而不需要任何特殊条件。下面的例子说明了这一点:

    //类似迭代器
    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();
    		}		
    	}
    }
    

     输出结果如下:

    0  1  2  3  4  5  6  7  8  9  
    

    上面演示了一个:"迭代器"设计模式的例子。

    示例四

    在成员内部类中如果需要创建对外部类对象的引用,可以使用外部类的名字后面跟着.this。下面演示如何使用.this:

    ///使用.this
    public class DotThis {
    	void f() {
    		System.out.println("DotThis.f()");
    	}	
    	//成员内部类
    	public class Inner{
    		public DotThis outer() {
    			return DotThis.this;
    		}
    	}	
    	public Inner inner() {
    		return new Inner();
    	}	
    	public static void main(String[] args) {
    		DotThis dt = new DotThis();
    		DotThis.Inner dti = dt.inner();
    		dti.outer().f();
    	}
    }
    

     输出如下:

    DotThis.f()
    

    示例五

    想要直接创建成员内部类的对象,不可以直接引用外部类的名字,而是必须使用外部类的对象来创建该成员内部类对象。在拥有外部类对象之前是不可能创建成员内部类对象的,这是因为成员内部类对象会暗暗地连接到创建它的外部类对象上。

    但是,如果创建的是静态内部类,那么它就不需要对外部类对象的引用。

    ///.new
    public class DotNew {
    	public class Inner{
    		public void f(){
    			System.out.print("test");
    		}
    	}
    	public static void main(String[] args) {
    		DotNew dn = new DotNew();
    		DotNew.Inner dni = dn.new Inner();	
    		dni.f();
    	}
    }
    

    示例六

    当将成员内部类(private修饰)向上转型为其基类,尤其是转为一个接口(public或者"包访问")时,由于成员内部类使用private修饰,因此可以隐藏其内部的具体实现,但是由于把成员内部类声明为private,因此不能访问该成员内部类任何新增的原本不属于公共接口的方法,所以扩展接口是没有意义的。下面演示一个例子:

    interface Destination{
    	String readLabel();
    }
    interface Contents{
    	int value();
    }
    class Parcel4{
    	//私有类,隐藏其具体实现
    	private class PContents implements Contents{
    		private int i = 11;
    		public int value() {return i;}		
    	}		
    	protected class PDestination implements Destination{
    		private String label;
    		private PDestination(String whereTo) {
    			label = whereTo;
    		}		
    		public String readLabel(){
    			return label;
    		}				
    	}
    	public Destination destination(String s) {
    		return new PDestination(s);
    	}	
    	public Contents contents(){
    		return new PContents();
    	}
    }
    public class TestParcel {
    	public static void main(String[] args) {
    		Parcel4 p = new Parcel4();
    		Contents c = p.contents();
    		Destination d = p.destination("北京");
    	}
    }
    

    示例七

    一个成员内部类被嵌套多少层并不重要,它能透明的访问所有它所嵌入的外部类的所有成员,如下所示:

    class MNA{
    	private void f() {}
    	//第一个内部类
    	class A{
    		private void g() {}
    		//第二个内部类
    		public class B{
    			void h() {
    				g();
    				f();
    			}
    		}
    	}
    }
    public class MultiNestingAccess {
    	public static void main(String[] args) {
    		MNA mna = new MNA();
    		MNA.A mnaa = mna.new A();
    		MNA.A.B mnab = mnaa.new B();
    	}
    }
    

    成员内部类的工作方式:

    Java每个类都会产生一个.class文件,所以Java 编译器还会创建一个成员内部类OuterClass$MemberInnerClass.class,成员内部类文件名格式为外部类名$成员内部类名

    2 、局部内部类

    局部内部类(Local inner class)通常定义在一个块中。所以通常你会在一个方法块中找到局部内部类。

    正如局部变量那样,局部内部类的作用域受到方法的限制。它可以访问外部类的所有成员,和它所在的局部方法中所有局部变量。

    如果你想调用局部内部类中的方法,你必须先在局部方法中实例化局部内部类。

    局部内部类的一些规则:

    • 无法从方法外部调用局部内部类;
    • 局部内部类不能被声明为private, public, protected;
    • JDK1.7之前局部内部类不能访问非final的局部变量,但是在JDK1.8及之后是可以访问非final的局部变量的;

    示例一

    在一个方法中定义一个局部内部类:

    public class Parcel5 {
    	public Destination destination(String s) {
    		class PDestination implements Destination{
    			private String label;
    			private PDestination(String whereTo) {
    				label = whereTo;
    			}			
    			public String readLabel(){
    				return label;
    			}
    		}
    		return new PDestination(s);
    	} 	
    	public static void main(String[] args) {
    		Parcel5 p = new Parcel5();
    		Destination d = p.destination("北京");
    	} 
    }
    

    示例二

    下面我们尝试在一个作用域内定义一个局部内部类:

    public class Parcel6 {
    	private void internalTracking(boolean b) {
    		if(b) {
    			class TrackingSlip{
    				private String id;
    				TrackingSlip(String s){
    					id = s;
    				}
    				String getSlip() {
    					return id;
    				}
    			}
    			TrackingSlip ts = new TrackingSlip("slip");
    			String s = ts.getSlip();
    		}
    	}	
    	public void track() {
    		internalTracking(true);
    	}	
    	public static void main(String[] args) {
    		Parcel6 p = new Parcel6();
    		p.track();
    	}
    }
    

    TrackingSlip类被嵌入在if语句的作用域中,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。然而,在定义TrackingSlip的作用域之外,它是不可用的,除此之外,它和普通的类一样。

    局部内部类的工作方式:

    Java每个类都会产生一个.class文件,所以Java 编译器还会创建一个名为localInner$Local.class的文件。

    3、匿名内部类

    在 Java 中没有命名的内部类称为匿名内部类,当我们需要重写类或接口中的方法时,都会使用到它。匿名内部类在使用的时候将同时声明和实例化。

    匿名类可以通过以下两种方式进行创建:

    • 类(可能是抽象类或者其他类);
    • 接口;

    实例一

    下面将会创建一个匿名类,该匿名类是接口Contents的实现类:

    ///匿名内部类
    
    public class Parcel7 {
    	//
    	public Contents contents() {
    		//返回一个匿名类对象,该类是Contents接口的实现
    		return new Contents() {
    			private int i = 11;
    			public int value() {
    				return i;
    			}
    		};		
    	}
    	
    	public static void main(String[] args) {
    		Parcel7 p = new Parcel7();
    		Contents c = p.contents();
    	}
    
    }
    

     上述代码中通过new表达式返回的引用 被自动向上转为对Contents类型的引用。上述匿名内部类的语法是下述代码的简化形式:

    ///匿名内部类
    
    public class Parcel7 {
    	
    //	public Contents contents() {
    //		//返回一个匿名类对象,该类是Contents接口的实现
    //		return new Contents() {
    //			private int i = 11;
    //			public int value() {
    //				return i;
    //			}
    //		};		
    //	}
    	
    	class PContents implements  Contents{
    		private int i = 11;
    		public int value() {
    			return i;
    		}
    	}	
    	public Contents contents() {
    		return new PContents();
    	}		
    	public static void main(String[] args) {
    		Parcel7 p = new Parcel7();
    		Contents c = p.contents();
    	}
    }
    

    实例二

     在示例一匿名内部类中,使用的是默认构造函数,但是如果基类需要一个有参数的构造器,我们只需要简单的传递合适的参数给基类的构造器即可,下面演示一个例子:

    class Wrapping{
    	private int i;
    	//需要一个参数
    	public Wrapping(int x) {
    		i = x;
    		System.out.println("基类构造器:"+x);
    	}	
    	public int value() {
    		return i;
    	}
    }
    public class Parcel8 {
    	public Wrapping wrapping(int x) {
    		//一个匿名类 实现类Wrapping类,并传递x给基类的构造器
    		return new Wrapping(x) {
    			public int value() {
    				return super.value()*47;
    			}
    		};
    	}	
    	public static void main(String[] args) {
    		Parcel8 p = new Parcel8();
    		Wrapping w = p.wrapping(10);
    	}
    }
    

    Wrapping是一个具有具体实现的简单类。我们将x传递进new Wrapping(x),这个x最终会传递给基类Wrapping的构造函数。

     实例三

    如果在匿名类中定义字段,还可以对其执行初始化,如下:

    ///匿名类中初始化字段
    public class Parcel9 {
    	//dest用来初始化匿名类字段,必须声明为final
    	public Destination destination(final String dest) {
    		return new Destination() {
    			private String label = dest;
    			public String readLabel(){
    				return label;
    			}
    		};
    	}	
    	public static void main(String[] args) {
    		Parcel9 p = new Parcel9();
    		Destination d = p.destination("北京");
    	}	
    

     如果我们我们定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final,就像上面这个例子一样。

    实例四

    如果只是给匿名类中的一个字段赋值,示例三是很好的作用。但是如果想做一些类似构造器的行为呢?然而,匿名函数不可能有命名构造器,但是可以通过实例初始化,就能达到为匿名内部类创建一个构造器的效果,如下:

    ///通过实例初始化,达到为匿名内部类创建一个构造器的效果
    
    abstract class Base{
    	public Base(int i) {
    		System.out.println("Base constructor, i = "+i);
    	}
    	public abstract void f(); 
    } 
    public class AnonymousConstructor {		
    	//price必须是final,因为它在匿名类内部直接使用
    	public static Base getBase(int i,final float price) {
    		//i被传给匿名类的基类构造器,不要求是final,因为它不在匿名类内部直接使用
    		return new Base(i) {		
    			private int cost;
    			//内部类实例初始化
    			{
    				cost = Math.round(price); 
    				System.out.println("Inside instance initializer:"+cost);				
    			}
    			public void f() {
    				System.out.println("In anonymous f()");
    			}			
    		};
    	}	
    	public static void main(String[] args) {
    		Base base = getBase(47,101.395F);
    		base.f();
    	}
    }
    

     输出结果如下:

    Base constructor, i = 47
    Inside instance initializer:101
    In anonymous f()
    

     在上例中i不要求是final的,因为i是被传递给匿名类的基类构造器的,它并不会在匿名类内部被直接使用。而price必须是final的,因为它在匿名类内部直接使用。

    匿名内部类和正规的继承相比有些受限,匿名内部类既可以扩展类,也可以扩展接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。

    实例五

    我们先来看一个工厂方法实例:

    ///工厂方法
    
    interface  Service{
    	void method1();
    	void method2();
    }
    interface ServiceFactory{
    	Service getService();
    }
    class Implementations1 implements Service{
    	private Implementations1(){}
    	public void method1() {
    		System.out.println("Implementation1 method1");
    	}	
    	public void method2() {
    		System.out.println("Implementation1 method2");
    	}	
    	public static ServiceFactory factory = new ServiceFactory() {
    		public Service getService() {
    			return new Implementations1();
    		}
    	} ;
    }
    class Implementations2 implements Service{
    	private Implementations2(){}
    	public void method1() {
    		System.out.println("Implementation1 method1");
    	}	
    	public void method2() {
    		System.out.println("Implementation1 method2");
    	}	
    	public static ServiceFactory factory = new ServiceFactory() {
    		public Service getService() {
    			return new Implementations2();
    		}
    	} ;
    }
    public class Factories {
    	public static void serviceConsumer(ServiceFactory fact) {
    		Service s = fact.getService();
    		s.method1();
    		s.method2();
    	}	
    	public static void main(String[] args) {
    		serviceConsumer(Implementations1.factory);
    		serviceConsumer(Implementations2.factory);		
    	}
    }
    

     在上例中Implementations1和Implementations2的构造器都是private的,并且把工厂类写成了匿名内部类的形式。此外,在实际中,经常只需要单一的工厂对象,因此把factory声明为static,这样表明一个类最多只会生成一个实例对象。

    二 成员内部类的继承

    1、成员内部类的继承

    在之前我们介绍过成员内部类可以使用外部类的成员,这是因为当某个外部类对象创建一个成员内部类对象时,成员内部类对象会秘密的捕获一个指向外部类对象的引用,在访问外部类成员时,就是使用那个引用来选择外部类的成员(编译器会帮我们处理这个细节)。

    构建成员内部类对象时,需要一个指向其外部类对象的引用,如果编译器访问不到这个引用就会报错。因此,如果有一个子类继承自成员内部类,当创建一个子类对象时,首先会调用成员内部类的构造函数创建一个内部成员类对象,但是此内部成员类对象不存在指向其外部类对象的引用,就会报错。要解决这个问题,必须向子类构造器传入一个指向外部类对象的引用,并且在构造器第一句中使用如下语法:

    enclosingClassReference.super(xx) //enclosingClassReference外部类对象引用,super(xx)成员内部类的构造函数
    

     如下所示:

    ///成员内部类的继承
    
    class WithInner{
    	private String name = "临安";
    	private int age = 25;
    	private boolean sex = false;
    	
    	WithInner(boolean sex){
    		this.sex = sex;
    		System.out.println("外部类构造函数:WithInner()");
    	}	
    	//成员内部类
    	class Inner{
    		//构造函数 初始化外部类成员
    		Inner(String name,int age){
    			WithInner.this.name = name;
    			WithInner.this.age = age;
    			System.out.println("内部类构造函数:Inner():" + WithInner.this.name + " " + WithInner.this.age + " " + WithInner.this.sex);
    		}
    	}
    }
    
    //成员内部类的子类
    public class InheritInner  extends WithInner.Inner {
    	//构造函数  必须传递一个指向外部类对象的引用
    	InheritInner(WithInner wi,String name,int age){		
    		//调用成员内部类构造函数
    		wi.super(name, age);			
    	}		
    	public static void main(String[] args) {
    		//创建一个外部类对象
    		WithInner wi = new WithInner(true);
    		InheritInner ii = new InheritInner(wi,"郑阳",23);
    	}
    }
    

     输出结果:

    外部类构造函数:WithInner()
    内部类构造函数:Inner():郑阳 23 true
    

    三 静态嵌套类

    静态嵌套类其实就是在顶级类中封装的一个顶级类,它的行为和顶级类一样,它被封装在顶级类中其实就是为了方便打包。

    • 静态嵌套类是外部类的静态成员,它可以直接使用外部类名.静态嵌套类名访问自身;
    • 它可以访问外部类的静态成员和静态私有成员;
    • 与其他类一样,静态嵌套类不能访问非静态成员;

    实例一

    正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。并且放到接口中任何类型(字段,方法)都自动的是public和static的。由于接口中的类是static,因此它是静态嵌套类。如下所示:

    ///接口内部的类:自动是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();
    	}
    }
    

    我们在接口ClassInInterface中定义了一个静态嵌套类Test,该静态嵌套类实现了外部接口类。

    实例二

    class OuterClass{  
        // 外部类的静态数据成员
        static int data = 30;  
        // 外部类中的静态嵌套类
        static class StaticNestedClass {  
            // 静态嵌套类中的实例方法
            void getData() {System.out.println("data is "+data);}  
        }
        public static void main(String args[]){  
            // 实例化静态嵌套类(创建对象)
            OuterClass.StaticNestedClass obj = new OuterClass.StaticNestedClass();  
            obj.getData();  
        }  
    }  
    

     在上面的例子中,你需要先创建一个静态嵌套类的实例,因为你需要访问它的实例方法getData()。但是你并不需要实例化外部类,因为静态嵌套类相对于外部类来说它是静态的,可以直接使用外部类名.静态嵌套类名访问。

    实例三

    如果静态嵌套类中有静态成员,则不需要实例化静态嵌套类即可直接访问。

    class OuterClass2{  
        static int data = 10;  
        static class StaticNestedClass{  
            // 静态嵌套类的静态方法
            static void getData() {System.out.println("data is "+data);}  
        }  
        public static void main(String args[]){  
            // 不需要创建实例可以直接访问
            OuterClass2.StaticNestedClass.getData();
        }  
    }  
    

    参考文献

    [1] Java编程思想

    [2]Java 嵌套类基础详解

    [3]static关键字修饰类

  • 相关阅读:
    java处理jqueryGantt甘特图数据的task.depends依赖规则方法
    中国行政区划表,包括34个省、直辖市的所有数据 mysql数据
    使用mybatis的resultMap进行复杂查询
    intel 酷睿core系列cpu的类型:U M H HQ MQ
    mybatis问题。foreach循环遍历数组报错情况,及其解决方法
    Android开发 DownloadManager详解
    Android开发 WorkManager详解
    Android开发 在不使用ItemTouchHelper的情况下实现ItemView的左右滑动
    AndroidStudio ViewBinding详解
    Android开发 滚轮View第三方框架
  • 原文地址:https://www.cnblogs.com/zyly/p/10596268.html
Copyright © 2020-2023  润新知