zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java内部类(Java源码中 内部类 的分析使用)

JAVA源码 分析 内部 使用
2023-09-11 14:22:11 时间

1 学习内容

  1. 为什么使用内部类,内部类在java 源码中的设计;
  2. 成员内部类;
  3. 静态内部类;
  4. 局部内部类;
  5. 匿名内部类;
  6. 总结

2 具体内容

2.1 为什么使用内部类

为什么要使用内部类?在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。

在学习集合时看了一下集合中ArrayList源码不小心发现了 内部类的使用,在此分享:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
	/**
     *   返回这个Itr  对象
     */	 
	public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * 每个内部类都能独立地继承一个(接口的)实现,
	 *  所以无论外围类是否已经继承了某个(接口的)实现
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
          
        }

        public void remove() {
         
        }  
    }		
}
  • 其实在集合中这样的例子还有很多,HashMap集合中也是如此, Set 集合中这样的设计比较多。

下面我再举一个简单的例子:

public interface Father {

}

public interface Mother {

}

public class Son implements Father, Mother {

}

public class Daughter implements Father{

    class Mother_ implements Mother{
        
    }
}
  • 其实对于这个实例我们确实是看不出来使用内部类存在何种优点,但是如果Father、Mother不是接口,而是抽象类或者具体类呢?这个时候我们就只能使用内部类才能实现多重继承了。

  • 其实使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,但是如果我们不需要解决多重继承问题,那么我们自然可以使用其他的编码方式,但是使用内部类还能够为我们带来如下特性(摘自《Think
    in java》)(这些优点虽然我还没有体会到! 呵呵):

    • 内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。

    • 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。

    • 创建内部类对象的时刻并不依赖于外围类对象的创建。

    • 内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。

    • 内部类提供了更好的封装,除了该外围类,其他类都不能访问。

2.2 成员内部类

让内部类可以访问外部类中定义的一个私有的msg属性的内容。内部类有一个最大的优点:可以访问外部类中的私有操作。
内部类可以访问外部类的私有属性,反之,外部类可以通过内部类对象访问内部类的私有属性。

  • 在成员内部类中要注意两点,第一:成员内部类中不能存在任何static的变量和方法;第二:成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。

范例:

class Outer{
	private String msg = "hello world";
	class Inner{//定义了一个内部类
		private String info = "世界,你好";
		public void print(){
			System.out.println(msg);
		}
	}
	public void fun(){
		Inner in = new Inner();//内部类对象实例化
//	    直接利用内部类对象访问了内部类中定义的属性
		System.out.println(in.info);
	}
}
public class TestDemo{
	public static void main(String args[]){
		Outer out = new Outer();//实例化外部类
		out.fun();//调用外部类
	}
}

定义内部类之后,私有属性的访问变的简单了。
一直要求如果要想访问属性前面一定要加上this,如果直接在print()方法里面加上this表示是当前类对象的属性,但是此时实际访问的是外部类的属性,那么使用“外部类.this.属性”。

class Outer{
	private String msg = "hello world";
	class Inner{//定义了一个内部类
		public void print(){
//              外部类.this = 外部类的当前对象
			System.out.println(Outer.this.msg);
		}
	}
	public void fun(){
		new Inner().print();
	}
}
public class TestDemo{
	public static void main(String args[]){
		Outer out = new Outer();//实例化外部类
		out.fun();//调用外部类
	}
}
  • 以上的所有的代码都有一个特点:通过外部类的一个fun()方法访问了内部类的操作。那么内部类能否像外部类一样直接产生实例化对象呢?
    通过内部类的文件形式来观察,发现内部类的class文件的形式是:Outer美元符号Inner.class
    所有的“$”是在文件中的命名,如果换回到程序里面就变为了“.”,也就是说内部类的名称就是“外部类.内部类”。此时可以给出内部类的实例化语法:

    • 外部类.内部类 对象=new 外部类().new 内部类();

示例:内部类实例化

 class Outer{
    	private String msg="hello world";
    	class Inner{//定义了一个内部类
    		public void print(){
    			System.out.println(Outer.this.msg);
    		}
    	}
    }
    public class TestDemo{
    	public static void main(String args[]){
    		Outer.Inner oi = new Outer().new Inner();
    		oi.print();
    	}
}

内部类不可能离开外部类的实例化对象,所以一定要先实例化外部类,实例化外部类对象后才可以使用内部类对象。
如果现在一个内部类只希望被一个外部类访问,不能够外部调用,可以使用private定义。 报错:Outer.Inner可以在Outer中访问private 此时的内部类是不可能在外部进行对象实例化的

示例:内部类定义为private

class Outer{
	private String msg="hello world";
	private class Inner{//定义了一个内部类
		public void print(){
			System.out.println(Outer.this.msg);
		}
	}
}
public class TestDemo{
	public static void main(String args[]){
		Outer.Inner oi=new Outer().new Inner();
		oi.print();
	}
}

2.3 Static定义内部类(静态内部类)

使用static定义的属性或者方法是不受类实例化对象控制的,所以使用了static定义内部类。不受外部类实例化对象的控制。
如果一个内部类使用了static定义的话,那么这个内部类就变为了一个外部类,并且只能访问static定义的操作。相当于定义一个外部类。

  • 使用static修饰的内部类我们称之为静态内部类,不过我们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外部类,但是静态内部类却没有。没有这个引用就意味着
    • 它的创建是不需要依赖于外部类

    • 它不能使用任何外部类的非static成员变量和方法

示例:静态内部类

  class Outer{
        	private static String msg="Hello World";
        	static class Inner{
        		public void print(){
        			System.out.println(msg);
        		}
}

但是此时如果要想取得内部类的实例化对象,使用的语法如下:

  • 外部类.内部类 对象=new 外部类.内部类();

此时不需要先产生外部类对象,再产生内部类对象,仿佛一个独立的类。
示例:静态内部类的访问

class Outer{
	private static String msg="Hello World";
	static class Inner{
		public void print(){
			System.out.println(msg);
		}
	}
}
public class TestDemo{
	public static void main(String args[]){
		Outer.Inner oi = new Outer.Inner();
		oi.print();
	}
}

2.4 方法中定义内部类(局部内部类)

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

示例:在方法中定义内部类

class Outer{
	private String msg="Hello World";
	public void fun(){
		class Inner{
			public void print(){
				System.out.println(Outer.this.msg);
			}
		}
		new Inner().print();
	}
		
}
public class TestDemo{
	public static void main(String args[]){
		new Outer().fun();
	}
}

示例:访问方法中定义的参数或者是变量

class Outer{
	private String msg = "Hello World";
	public void fun(int num){//方法参数
		double score = 99.9;//方法变量
		class Inner{
			public void print(){
				System.out.println("属性" + Outer.this.msg);
				System.out.println("方法参数:" + num);
				System.out.println("方法变量:" + score);
			}
		}
		new Inner().print();
	}	
}
public class TestDemo{
	public static void main(String args[]){
		new Outer().fun(100);
	}
}

运行结果:
在这里插入图片描述

此时的方法中没有加入任何的修饰,方法中的内部类可以访问方法的参数和变量。但是只适合JDK1.8以后的版本。JDK1.7及以前的版本有一个严格的要求:方法中定义的内部类如果要想访问方法中的参数或者变量,那么参数或变量前一定要加上“final”标记。

示例:加了final的方法内部类

   class Outer{
    	private String msg="Hello World";
    	public void fun(final int num){//方法参数
    		final double score = 99.9;//方法变量
    		class Inner{
    			public void print(){
    				System.out.println("属性" + Outer.this.msg);
    				System.out.println("方法参数:" + num);
    				System.out.println("方法变量:" + score);
    			}
    		}
    		new Inner().print();
    	}	
    }
    public class TestDemo{
    	public static void main(String args[]){
    		new Outer().fun(100);
    	}
    }

2.5 匿名内部类

  • 匿名内部类是没有访问修饰符的,没有构造方法的。

示例:匿名内部类

  public class TestDemo{
    	public static void main(String args[]) throws Exception{
    		fun(new IMessage(){
    			public void print(){
    				System.out.println("匿名内部类");
    			}
    		});
    	}
    	public static void fun(IMessage msg){
    		msg.print();
    	}
    }

JDK1.8引入了函数式的编程

示例:使用Lamda表达式

interface IMessage{
	public void print();
}
public class TestDemo{
	public static void main(String args[]) throws Exception{
		fun(() -> System.out.println("hello wrold"));
	}
	public static void fun(IMessage msg){
		msg.print();
	}
}
  • Java8以后估计匿名内部类很少使用了,因为匿名内部类的代码量还是稍微比较大的,但是当做示例还是可以的,比如在线程的创建中使用。上面例子中整个操作匿名内部类只是进行一行语句的输出,所以此时使用了Lamda表示可以大大简化代码量,这是一件愉快的事。

3总结

  1. 内部类可以与外部类之间方便的进行私有属性的访问;

  2. 内部类可以使用private声明,声明之后无法在外部实例化内部类对象;

    • 语法:外部类.内部类 内部类对象=new 外部类().new 内部类();
  3. 使用static定义的内部类就相当于是一个外部类;

    • 实例化语法:外部类.内部类 内部类对象=new 外部类.内部类();
  4. 内部类可以在方法中定义。