Java编程思想读书笔记——第十章:内部类

第十章 内部类

  • 将一个类的定义放在另一个类的内部,这就是内部类
  • 内部类和组合是完全不同的概念,内部类了解外部类

10.1 创建内部类

创建就好,好像没啥可说的,直接来上练习:

练习1:(1)编写一个名为Outer的类,它包含一个名为Inner的类。在Outer中添加一个方法,它返回一个Inner类型的对象。在main()中,创建并初始化一个指向某个对象的引用。

public class Outer {
	
	public Inner getInnerInstance() {
		return new Inner();
	}
	
	public static void main(String[] args) {
		Outer outer = new Outer();
		Inner inner = outer.getInnerInstance();

	}
	
	public class Inner {
		{
			System.out.println("创建Inner");
		}
	}

}

输出:
创建Inner

10.2 链接到外部类

所有内部类自动拥有对其外围类所有成员的访问权。内部类对象会秘密捕获一个指向那个外围类对象的引用。这些东西编译器都帮我们处理好了。直接来上练习:

练习2、创建一个类,持有一个String,将这个新类的对象添加到一个Sequence对象中,然后显示它们

练习3、修改练习1,使得Outer类包含一个private String域,Inner包含一个显示这个域的toString()方法,创建一个Inner类型的对象并显示它

这两个练习比较简单,略

10.3 使用.this与.new

如果需要生成对外部类的引用,直接外部类的名字后面紧跟圆点和this

要想直接创建内部类对象,不能按照我们想象的方式,必须使用外部类的对象来创建该内部类对象,在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到创建它的外部类对象上

练习4、生成对外部类Sequence的引用

练习5、创建一个包含内部类的类,在另一个独立的类中,创建此内部类的实例

还是比较简单,贯彻落实使用.this和.new就OK, 略

10.4 内部类与向上转型

内部类向上转型为其基类,能够很方便地隐藏实现细节,直接上练习:

练习6:(2)在第一个包中创建一个至少有一个方法的接口。然后在第二个包内创建一个类,在其中添加一个protected的内部类以实现那个接口。在第三个包中,继承这个类,并在一个方法中返回该protected内部类的对象,在返回的时候向上转型为第一个包中的接口的类型。

// 第一个包
package one1;

public interface SimpleInterface {
	void eat();
}

// 第二个包
package two;

public class TestParent {
	protected class Inner implements SimpleInterface {

		public Inner() {
			System.out.println("创建了内部类");
		}

		@Override
		public void eat() {
			System.out.println("吃东西");
			
		}
		
	}
}

// 第三个包
package one3;

public class Test extends TestParent{
	
	public SimpleInterface getInner() {
		return new Inner();
	}

	public static void main(String[] args) {
		new Test().getInner().eat();
	}

}

输出:
创建了内部类
吃东西

练习7:(2)创建一个含有private域和private方法的类。创建一个内部类,它有一个方法可以用来修改外围类的域,并调用外围类的方法。在外围类的另一方法中,创建此内部类的对象,并且调用它的方法,然后说明对外围类对象的影响。

// 这个练习显示了内部类具有对外部类的透明访问,甚至是私有域和方法
public class Test {
	
	private int count = 22;;
	
	private void onUseOuter() {
		System.out.println("外部类被调用了");
	}
	
	public Inner getInnerInstance() {
		return new Inner();
	}
	
	private class Inner {
		public void changeValue(int i) {
			count = i;
			System.out.println(i);
			onUseOuter();
		}
	}

	public static void main(String[] args) {
		Test test = new Test();
		Inner inner = test.getInnerInstance();
		inner.changeValue(33);
	}

}

输出:
33
外部类被调用了

练习8:(2)确定外部类是否可以访问其内部类的private元素

// 此练习显示外部类也可以访问内部类的private元素
public class Test2 {
	
	private class Inner {
		private int count = 22;
	}
	
	private void changeInnerValue(int i) {
		Inner inner = new Inner();
		inner.count = i;
		System.out.println("改变后的值为" + inner.count);
	}
	public static void main(String[] args) {
		Test2 test2 = new Test2();
		test2.changeInnerValue(55);
	}
	
}

输出:
改变后的值为55

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

以前没接触过,也没这么写过。但是,在方法和作用域中使用内部类,是一种更加难以理解的技术

这么做有两个理由:

  1. 创建某类型的接口,创建并返回对其的引用
  2. 要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但又不希望这个类是公共可用的

直接上练习吧:

练习9:(1)创建一个至少有一个方法的接口。在某个方法内定义一个内部类以实现此接口,这个方法返回对此接口的引用。

public class Test3 {
	
	private SimpleInterface get() {
		class simpleImpl implements SimpleInterface {
			public simpleImpl() {
				System.out.println("创建了一个方法内定义的内部类");
			}
			
			@Override
			public void f() {
				System.out.println("接口的f()方法");
			}
		}
		return new simpleImpl();
	}
	
	public static void main(String[] args) {
		SimpleInterface simpleInterface = new Test3().get();
	}
	
	
	public interface SimpleInterface {
		void f();
	}
}

练习10:(1)重复前一个练习,但将内部类定义在某个方法的一个作用域内。

// 和练习9的区别就是这个方法,加个if
private SimpleInterface get() {
		if(true) {
			class simpleImpl implements SimpleInterface {
				public simpleImpl() {
					System.out.println("创建了一个方法内定义的内部类");
				}
				
				@Override
				public void f() {
					System.out.println("接口的f()方法");
				}
			}
			return new simpleImpl();
		}
		return null;
	}

练习11:(2)创建一个private内部类,让它实现一个public接口。写一个方法,它返回一个指向此private内部类的实例的引用,并将此引用向上转型为该接口类型。通过尝试向下转型,说明此内部类被完全隐藏了。

// 此练习显示,因为内部类是私有的,只能向上转型,返回可见的基类
public class Test4 {
	
	private class Inner implements SimpleInterface {

		@Override
		public void f() {
			
		}
		
	}
	
	public SimpleInterface get() {
		return new Inner();
	}
	
	public Inner get2() {
		return new Inner();
	}
	
	
	public interface SimpleInterface {
		void f();
	}
}


public class Test5 {
    public static void main(String[] args) {
		Test4 test4 = new Test4();
		SimpleInterface simpleInterface = test4.get();
		simpleInterface = test4.get2();
		// 下面的直接报错,编译不通过
		// Inner i1 = test4.get2();
		// Inner i2 = (Inner)simpleInterface;
	}
}

// 注意此例子必须再其他类中的main()方法中
// 如果仍然在Test4中的main()方法就会编译通过

10.6 匿名内部类

// 某个方法返回一个匿名内部类对象
public Contents getContents() {
    return new Contents() {
        private int i = 11;
        public int value() {
            return i;
        }
    };
}

重点,也是我们在开发中常遇到的:

  • 如果一个匿名内部类想使用一个外部定义的对象,那么编译器会要求这个参数引用必须是final的
  • 匿名类中不可能有命名构造器

直接上练习:

练习12:(1)重复练习7

练习13:(2)重复练习9

练习14:

这三个略,练习9用匿名内部类写就是我们平时写的回调,更简单了

练习15:(2)创建一个类,它有非默认的构造器(即需要参数的构造器),并且没有默认构造器(没有无参数的构造器)。创建第二个类,它包含一个方法,能够返回对第一个类的对象的引用。通过写一个继承自第一个类的匿名内部类,来创建一个返回对象。 

public class NoDefault {
	
	private int i;
	
	public NoDefault(int i) {
		this.i = i;
	}
	
	public void f() {
		System.out.println("NoDefault");
	}

}



public class Pratice15 {
	
	public NoDefault get(int i) {
		return new NoDefault(i) {};
	}
	
	public NoDefault get2(int i) {
		return new NoDefault(i) {
			public void f() {
				System.out.println("NoDefault匿名内部类");
			}
		};
	}
	
	public static void main(String[] args) {
		Pratice15 pratice15 = new Pratice15();
		NoDefault noDefault = pratice15.get(2);
		noDefault.f();
		noDefault = pratice15.get2(3);
		noDefault.f();
	}

}

输出:
NoDefault
NoDefault匿名内部类

10.6.1 再访工厂方法

使用工厂方法,声明一个static的工厂匿名内部类,返回当前类的对象。感觉十分的美妙。

练习16:(1)修改第9章中练习18的解决方案,让它使用匿名内部类

        interface Cycle { 
		int wheels(); 
	}
	
	interface CycleFactory { 
		Cycle getCycle(); 
	}
	
	class Unicycle implements Cycle {
 
		public int wheels() { 
			return 1; 
		} 
		
		public static CycleFactory factory = new CycleFactory() { 
			public Unicycle getCycle() { 
				return new Unicycle(); 
			} 
		}; 
	} 
	
	class Bicycle implements Cycle { 
		
		public int wheels() { 
			return 2; 
		} 
		
		public static CycleFactory factory = new CycleFactory() { 
			public Bicycle getCycle() { 
				return new Bicycle(); 
			} 
		}; 
	} 
	
	class Tricycle implements Cycle { 
		
		public int wheels() { 
			return 3; 
		} 
		
		public static CycleFactory factory = new CycleFactory() {
			public Tricycle getCycle() { 
				return new Tricycle(); 
			} 
		}; 
	}

        public class Pratice16 {
	    public static void ride(CycleFactory fact) {
		Cycle c = fact.getCycle();
		System.out.println(c.wheels());
	    }
	
	    public static void main(String[] args) {
		ride(Unicycle.factory);
		ride(Bicycle.factory);
		ride(Tricycle.factory);
	    }
    } 

练习17:略

10.7 嵌套类

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

嵌套类意味着:

  1. 要创建嵌套类的对象,并不需要其外围类的对象。
  2. 不能从嵌套类的对象中访问非静态的外围类对象。
  3. 嵌套类的内部可以包含static数据,包含嵌套类

练习18:略

练习19:略

10.7.1 接口内部的类

嵌套类可以作为接口的一部分,甚至可以在内部类中实现其外围接口。

比如也可以在嵌套类中写一个main函数

练习20:略

练习21:略

10.7.2 从多层嵌套类中访问外部类的成员

一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员。

10.8 为什么需要内部类

内部类最吸引人的原因是:

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

内部类使得多重继承的解决方案变得完整。内部类有效地实现了“多重继承”。

使用内部类还可以获得其他一些特性:

  1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
  2. 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
  3. 创建内部类对象的时刻并不依赖于外围类对象的创建
  4. 内部类并没有令人迷惑的 “is-a” 关系;它就是一个独立的实体

练习22:(2)实现Sequence.java中的reverseSelector()方法

// 改编一个吧,最简单的,猫狗啥的,哈哈哈
// 因为每个内部类都可以独立的实现一个接口,
// 内部类最重要突出的功能就是实现同一个接口,
// 很容易就能拥有另一个方法,暂时可以这么粗鄙的理解不知道合适不,哈哈哈
public class Pratice17 {
	
	private class Dog implements Animal {
		@Override
		public void eat() {
			System.out.println("吃骨头");
		}
	}
	
	private class Cat implements Animal {

		@Override
		public void eat() {
			System.out.println("吃鱼");
		}
		
	}
	
	public Animal getDog() {
		return new Dog();
	}
	
	public Animal getCat() {
		return new Cat();
	}
	
	public interface Animal {
		void eat();
	}
	
	public static void eatFood(Animal animal) {
		animal.eat();
	}
	
	public static void main(String[] args) {
		Pratice17 pratice17 = new Pratice17();
		eatFood(pratice17.getDog());
		eatFood(pratice17.getCat());	
	}
}

练习23:

创建一个接口U,包含三个方法。

创建一个类A,包含一个方法,在此方法中通过创建一个匿名内部类来生成一个指向U的引用。

创建一个类B,包含一个由U组成的数组。B有几个方法,第一个方法接受U的引用,并存储的数组中。第二个方法将数组的引用设置为null,第三个方法遍历此数组,并在U中调用这些方法。

在main()中,创建一组A的对象和一个B的对象。用A类对象产生的U类型的引用填充B对象的数组。使用B回调所有A的对象。再从B中移除某些U的引用。

public interface U {
	void f();
	void g();
	void h();
}



public class A {
	
	String name;
	
	public A(String name) {
		this.name = name;
	}
	
	public U getU() {
		return new U() {

			@Override
			public void f() {
				System.out.println(name + "f()");
				
			}

			@Override
			public void g() {
				System.out.println(name + "g()");
				
			}

			@Override
			public void h() {
				System.out.println(name + "h()");
				
			}
			
		};
	}
}




public class B {
	
	U[] array;
	
	public B(int size) {
		array = new U[size];
	}
	
	public boolean add(U u) {
		for(int i = 0; i < array.length; i++) {
			if (array[i] == null) {
				array[i] = u;
				return true;
			}
		}
		return false;
	}
	
	public boolean setNull(int i) {
		if (i < 0 || i >= array.length) {
			return false;
		}
		array[i] = null;
		return true;
	}
	
	public void callMethods() {
		for (int i = 0; i < array.length; i++) {
			if (array[i] != null) {
				array[i].f();
				array[i].g();
				array[i].h();
			}
		}
	}
}


public class Test {

	public static void main(String[] args) {
		A[] aa = {new A("哈登"), new A("保罗"), new A("卡佩拉")};
		B b = new B(3);
		for (int i = 0; i < aa.length; i++) {
			b.add(aa[i].getU());
		}
		System.out.println("所有的--------------------");
		b.callMethods();
		
		System.out.println("移除后--------------------\n");
		
		b.setNull(1);
		b.setNull(2);
		b.callMethods();
	}

}

// 输出

所有的--------------------
哈登f()
哈登g()
哈登h()
保罗f()
保罗g()
保罗h()
卡佩拉f()
卡佩拉g()
卡佩拉h()

移除后--------------------
哈登f()
哈登g()
哈登h()

10.8.1 闭包与回调

闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。

那么基于这个定义,内部类是面向对象的闭包。

在我们做Android开发的时候,各种点击事件,各种网络回调,很常见的,在匿名内部类当中我们可以随意的使用当前类的成员变量。我想这样更容易理解。

书中的例子就不说了,讲的很好,但是没有一定的实践和经验也不好理解。

Java更加小心仔细,没有在语言中使用指针,而使用了回调。

内部类提供的闭包功能是优良的解决方案,它比指针更灵活、更安全。

10.8.2 内部类与控制框架

这节内容挺抽象的其实,哈哈,控制框架,用来解决响应事件的需求

书中的例子,Event构造方法中传入延迟的时间,start()方法获得结束时间,ready()方法返回是否到达时间,抽象出一个action()去做动作,Controller()来控制

反正注意内部类的两点吧:

  1. 内部类将实现的细节封装了起来
  2. 内部类能够很容易地访问外围类的任意成员,这一点很重要

控制温室的运作,这个例子里面充满了内部类,其实也不用细看,做andorid的,点击事件匿名内部类是经常的,要么写一个内部类

练习24:略

练习25:略

10.9 内部类的继承

这个在平时的开发也用的少

结合练习简单说一下吧

练习26:创建一个包含内部类的类,此内部类有一个非默认构造器(需要的构造器 ),创建另一个也包含内部类的类,此内部类继承自第一个内部类。

public class WithNonDefault {
	class Inner{
		int i;
		public Inner(int i) {
			this.i = i;
		}
		public Inner() {
			i = 47;
		}
		public void say() {
			System.out.println("父内部类的方法");
		}
	}
}

public class Test {
	class InnerSon extends WithNonDefault.Inner {
		// 这个直接报错了,也就是说,要想继承一个内部类,必须在构造方法传入此内部类的外围类的引用
//		public InnerSon(int i) {
//			super(i);
//		}
		
		public InnerSon(WithNonDefault wnd, int i) {
			wnd.super(i);
		}
		
		public void say() {
			System.out.println("子类的方法");
			super.say();
		}
	}
	
	public static void main(String[] args) {
		Test test = new Test();
		InnerSon innerSon = test.new InnerSon(new WithNonDefault(),2);
		innerSon.say();
	}

}

10.10 内部类可以被覆盖吗

假设这样一个场景,创建一个内部类,继承外围类并定义此内部类时,会发生什么呢

// 鸡蛋和蛋黄
public class Egg {
	
	private Yolk yolk;
	
	public Egg() {
		System.out.println("创建了鸡蛋");
		yolk = new Yolk();
	}
	
	protected class Yolk {
		public Yolk() {
			System.out.println("创建了蛋黄");
		}
	}
}


// 大鸡蛋继承鸡蛋
public class BigEgg extends Egg{
	
	public class Yolk {
		public Yolk() {
			System.out.println("创建了大蛋黄");
		}
	}
	
	public static void main(String[] args) {
		new BigEgg();
	}
}

输出:
创建了鸡蛋
创建了蛋黄

告诉我们一个道理:
类似这种像覆盖方法一样去覆盖内部类,是无法覆盖的
这两个内部类是完全独立的两个实体,各自在自己的命名空间内

明确的继承某个内部类是可以的:

package com__;



public class Egg {
	
	private Yolk yolk = new Yolk();
	
	public Egg() {
		System.out.println("创建了鸡蛋");
	}
	
	public void insertYolk(Yolk y) {
		yolk = y;
	}
	
	public void changeYolk() {
		yolk.change();
	}
	
	protected class Yolk {
		public Yolk() {
			System.out.println("创建了蛋黄");
		}
		
		public void change() {
			System.err.println("蛋黄变了");
		}
	}
}



public class BigEgg extends Egg{
	
	public BigEgg() {
		insertYolk(new Yolk());
	}
	
	public class Yolk extends Egg.Yolk {
		
		public Yolk() {
			System.out.println("创建了大蛋黄");
		}
		
		@Override
		public void change() {
			System.out.println("大蛋黄变了");
		}
		
	}
	
	public static void main(String[] args) {
		Egg egg = new BigEgg();
		egg.changeYolk();
	}
}

输出:
创建了蛋黄
创建了鸡蛋
创建了蛋黄
创建了大蛋黄
大蛋黄变了

是这样执行的:

  • new一个子类,肯定先执行父类的成员变量,先初始化Yolk,那么执行了Yolk的构造方法——创建了蛋黄
  • 然后是父类的构造方法——创建了鸡蛋
  • 然后是子类的构造方法,调用插入蛋黄的方法,又new了一个蛋黄,此蛋黄是子类的蛋黄,继承了父类中的蛋黄,所以new一个子类蛋黄,先执行父类构造方法——创建了蛋黄
  • 然后再执行子类构造方法——创建了大蛋黄
  • 最后调用变化的方法——执行变化方法的对象时子类蛋黄,所以——大蛋黄变了

正好借此机会在复习一下执行顺序这一块:

先不说继承,就是一个类正常的初始化:

  1. 肯定是先静态(静态成员和静态代码块谁在前谁先执行),将变量和代码块都看作是成员,同级的
  2. 然后非静态(非静态成员和非静态代码块也是谁在前谁先执行),将变量和代码块都看作是成员,同级的
  3. 最后是构造方法

继承的顺序是这样的:

继承的时候,记住一点,静态优先,所有就有了:

  1. 父类的静态成员,父类的静态代码块
  2. 子类的静态成员,子类的静态代码块
  3. 父类的成员,父类的非静态代码块,父类的构造方法
  4. 子类的成员,子类的非静态代码块,子类的构造方法
  5. 子类的有参构造方法没有super父类的构造方法,那么子类执行有参构造方法会默认调用父类的无参构造方法

10.11 局部内部类

局部内部类其实,哈哈,我没用过,用的比较少,一般都是匿名内部类

使用的方法,典型的方法就是在方法体内创建,局部内部类不能有访问修饰符,可以访问当前方法的常量,和外围类的所有成员

// 举个例子
public class Test1 {
	// 局部内部类,使用局部内部了的唯一理由是,我们需要一个已命名的构造器,或者需要重载构造器
	public Count getCount() {
		class LocalCount implements Count {
			
			public LocalCount() {
				// 重载构造器
			}
			
			public int next() {
				return 1;
			}
		}
		return new LocalCount();
	}
	
	// 匿名内部类,只能实例初始化
	public Count getCount1() {
		return new Count() {
			{
				// 不过匿名内部类可以有代码块,这个操作以前没考虑过,哈哈
			}
			@Override
			public int next() {
				return 0;
			}
		};
	}
	
	public interface Count {
		int next();
	}
}

10.12 内部类标识符

每个类都会生产.class文件,匿名内部类会在$后跟个数字,如果是内部类嵌套在别的内部类当中,名字加在外围类名字与“$”的后面

10.13 总结

接口和内部类其实挺抽象挺复杂的,这两者结合起来能解决C++中使用多重继承所能解决的问题。

什么时候使用接口,什么时候使用内部类,或者两者同时使用,我们自己熟悉了以后,去识别这些情况

 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值