Think in Java第四版 读书笔记8第14章 类型信息(RTTI与反射)

Java如何在运行时识别对象和类的信息?
1.RTTI(Run-time type information) 它假定我们在编译时已经知道了所有类型
2.反射 它允许我们在运行时发现和使用类的信息

14.1 为什么需要RTTI

答:为了知道对象的实际存储的类型信息(比如父类有n个子类,使用父类引用指向子类对象时,要知道是哪个具体子类对象)
例子

abstract class Shape {
	void draw() {
		System.out.println(this + ".draw()");//此处this其实是this.toString()
	}

	abstract public String toString();//强制子类实现  
}

class Circle extends Shape {
	public String toString() {
		return "Circle";
	}
	
	public void test(){};
}

class Square extends Shape {
	public String toString() {
		return "Square";
	}
	public void test(){};
}

class Triangle extends Shape {
	public String toString() {
		return "Triangle";
	}
	public void test(){};
}

public class Shapes {
	public static void main(String[] args) {
		
		List<Shape> shapeList = Arrays.asList(new Circle(), new Square(),
				new Triangle());//发生向上转型(父类引用指向子类对象) //丢失了子类特征 如果子类有父类没有的特殊方法 无法调用,比如test方法
		//但是具有多态特性 即子类覆盖了父类方法,使用父类引用调用时 实际会调用子类覆盖后的方法
		for (Shape shape : shapeList){//取出数组时 只知道元素是Shape类型(编译时 泛型可以识别) 而不知道具体类型
			shape.draw();
			//shape.test();//compile error,can't find this method
		}
			
		
	}
} /*
 * Output: Circle.draw() Square.draw() Triangle.draw()
 */// :~
//通常 我们希望对象尽可能少的了解对象的类型 而只是与对象家族中的一个通用表示打交道(基类),这样是代码更易写易读易维护
//上面这句话就是父类引用指向子类对象
//但是存在这样一种情况 比如以上面的这样例子为例,我们在数组里面存了一堆数据,然后想让数组的元素旋转 我们知道circle旋转是无效的,那么我们如何避开circle 调用其他shape的对象进行旋转呢?
//这个时候就要了解对象的确切类型 而不是只知道它是一个shape RTTI就是起到这个作用

14.2 Class对象

//类的类型信息在运行时的表示:
//类的Class对象保存了类型信息

//Java类只有在需要的时候才会被加载

class Candy {
  static { System.out.println("Loading Candy"); }
}

class Gum {
  static { System.out.println("Loading Gum"); }
}

class Cookie {
  static { System.out.println("Loading Cookie"); }
}

public class SweetShop extends Object{
  public static void main(String[] args) {	
    System.out.println("inside main");
    new Candy();
    System.out.println("After creating Candy");
    try {
      Class.forName("typeinfo.Gum");//可以根据类名获取相应类的引用(类加载器的作用即是获取类的引用) 
      //注意和书上有包名的不同 否则报错 找不到类Gum
      //Class.forName这里的作用是获取对应类的引用 他还有一个副作用:如果类没有被加载,则会先加载对应的类
    } catch(ClassNotFoundException e) {
      System.out.println("Couldn't find Gum");
    }
    System.out.println("After Class.forName(\"typeinfo.Gum\")");
    new Cookie();
    System.out.println("After creating Cookie");
  }
} /* Output:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
*///:~

Object的getClass方法
    /**
     * Returns the runtime class of this {@code Object}. The returned
     * {@code Class} object is the object that is locked by {@code
     * static synchronized} methods of the represented class.
     *
     * <p><b>The actual result type is {@code Class<? extends |X|>}
     * where {@code |X|} is the erasure of the static type of the
     * expression on which {@code getClass} is called.</b> For
     * example, no cast is required in this code fragment:</p>
     *
     * <p>
     * {@code Number n = 0;                             }<br>
     * {@code Class<? extends Number> c = n.getClass(); }
     * </p>
     *
     * @return The {@code Class} object that represents the runtime
     *         class of this object.
     * @jls 15.8.2 Class Literals
     */
    public final native Class<?> getClass();

这是一个native方法,要想获得运行时使用的具体类型信息,除了使用Class.forName(),如果有具体的对象,则还可以通过具体对象.getClass来获取运行时信息(实际类型)

Class的常用方法
class方法1 isInterface:判断当前class对象是不是接口类型
class方法2 getSimpleName:获取当前class对象的类名(简写)
class方法3 getCanonicalName:获取当前class对象的正规类名(详写)
class方法4 forName:获取class引用
class方法5 getInterfaces:获取class实现的所有接口的class信息
class方法6 getSuperclass:获取当前class的父类class
可以通过这些方法在运行时获知一个类的完整类结构(父类 实现接口 父类实现的接口 父类的父类等等)

class常用方示例

interface HasBatteries {}
interface Waterproof {}
interface Shoots {}

class Toy {
  //注释默认构造方法来看调用newInstance时是否发生NoSuchMethodError
  Toy() {}
  Toy(int i) {}
}

class FancyToy extends Toy
implements HasBatteries, Waterproof, Shoots {
  FancyToy() { super(1); }
}

public class ToyTest {
  static void printInfo(Class cc) {
    System.out.println("Class name: " + cc.getName() +
      " is interface? [" + cc.isInterface() + "]");//class方法1 isInterface:判断当前class对象是不是接口类型
    System.out.println("Simple name: " + cc.getSimpleName());//class方法2 getSimpleName:获取当前class对象的类名(简写)
    System.out.println("Canonical name : " + cc.getCanonicalName());//class方法3 getCanonicalName:获取当前class对象的正规类名(详写)
  }
  public static void main(String[] args) {
    Class c = null;
    try {
      c = Class.forName("typeinfo.toys.FancyToy");//class方法4 forName:获取class引用
    } catch(ClassNotFoundException e) {
      System.out.println("Can't find FancyToy");//没有找到FancyToy则退出
      System.exit(1);
    }
    printInfo(c);//打印FancyToy的类型信息	
    for(Class face : c.getInterfaces())//class方法5 getInterfaces:获取class实现的所有接口的class信息
      printInfo(face);//打印各个接口的类型信息	
    Class up = c.getSuperclass();//class方法6 getSuperclass:获取当前class的父类
    Object obj = null;
    try {
      // Requires default constructor:
      obj = up.newInstance();//必须有无参构造方法  如果没有无参构造方法 则会发生InstantiationException
    } catch(InstantiationException e) {
      System.out.println("Cannot instantiate");
      System.exit(1);
    } catch(IllegalAccessException e) {//如果无参构造方法为private 则会抛出IllegalAccessException
      System.out.println("Cannot access");
      System.exit(1);
    }
    printInfo(obj.getClass());//打印父类的class信息
  }
} /* Output:
Class name: typeinfo.toys.FancyToy is interface? [false]
Simple name: FancyToy
Canonical name : typeinfo.toys.FancyToy
Class name: typeinfo.toys.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name : typeinfo.toys.HasBatteries
Class name: typeinfo.toys.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name : typeinfo.toys.Waterproof
Class name: typeinfo.toys.Shoots is interface? [true]
Simple name: Shoots
Canonical name : typeinfo.toys.Shoots
Class name: typeinfo.toys.Toy is interface? [false]
Simple name: Toy
Canonical name : typeinfo.toys.Toy
*///:~

14.2.1 类字面常量

类字面常量意思是类名.class
比如
c = Class.forName(“typeinfo.toys.FancyToy”);
使用类字面常量则写做
c = FancyToy.class;
这样写不仅写法简单 不需要使用包名+类名,不用担心包名拼写错误,还不需要进行ClassNotFoundException的检测(将运行时异常提前到编译时检测)
字面常量除了可以使用在普通类,接口类型,数组类型上,也可以使用在基本数据类型
使用forName和类字面常量的区别在于类字面常量不会进行Class对象 而forName会(forName的副作用)
为了使用类,需要做一些准备工作:
1.加载 类加载器查找字节码 根据字节码创建Class对象(将java源文件编译为.class字节码文件)
2.链接 验证类中的字节码 为静态域分配存储空间,如果有必要 解析这个类创建的对其它类的所有引用(将Java class文件合并到JVM的运行状态,包括1验证:确保 Java 类的二进制表示在结构上是合理的 2准备:创建静态域并赋值 3 解析:确保当前类引用的其他类被正确地找到,该过程可能会触发其他类被加载)
3.初始化 执行静态初始化器和静态初始化块(当 Java类第一次被真正使用的时候,JVM 会负责初始化该类。包括:1 执行静态代码块 2 初始化静态域)
初始化例子

//初始化尽可能是惰性的(初始化时间尽可能延后)
class Initable {
	static final int staticFinal = 47;
	static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
	public Initable() {
		System.out.println("Initable 构造函数");
	}
	static {
		System.out.println("Initializing Initable");
	}
}

class Initable2 {
	static int staticNonFinal = 147;
	static {
		System.out.println("Initializing Initable2");
	}
}

class Initable3 {
	static int staticNonFinal = 74;
	static {
		System.out.println("Initializing Initable3");
	}
}

public class ClassInitialization {
	public static Random rand = new Random(47);

	public static void main(String[] args) throws Exception {
		Class initable = Initable.class;// .class 没有触发初始化(静态代码块没有调用)
		System.out.println("After creating Initable ref");
		System.out.println(Initable.staticFinal);//它是一个编译期常量,初始化不需要Initable支持,只触发静态变量初始化
		System.out.println(Initable.staticFinal2);//它不是一个编译期常量,初始化该变量需要Initable也初始化,所以触发静态代码块以及静态变量初始化
		System.out.println(Initable2.staticNonFinal);//对比staticFinal和staticNonFinal 他们的区别在于一个final,
		//如果static域不是final的,那么对它访问时总是被要求在它被读取之前(之前是重点)需要进行链接(分配内存空间)和初始化(初始化存储空间 赋初值),因此先触发了静态代码块
		//然后是变量初始化
		Class initable3 = Class.forName("typeinfo.Initable3");//forName会触发初始化(调用静态代码块)
		System.out.println("After creating Initable3 ref");
		System.out.println(Initable3.staticNonFinal);
	}
} /*
 * Output: 
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
 */// :~

14.2.2 泛化的class的引用

public class GenericClassReferences {
  public static void main(String[] args) {
    Class intClass = int.class;//不使用泛型可以赋值任意类型的class
    Class<Integer> genericIntClass = int.class;//使用泛型 只能赋值为Integer类型的class
    genericIntClass = Integer.class; // Same thing
    intClass = Double.class;
    // genericIntClass = double.class; // Illegal
    
    //int.class和Integer.class还是稍稍有区别的
    Class<Integer> a = int.class;
    Class<Integer> b = Integer.TYPE;
    Class<Integer> c = Integer.class;
    System.out.println(System.identityHashCode(a));
    System.out.println(System.identityHashCode(b));
    System.out.println(System.identityHashCode(c));
    System.out.println(int.class);
    System.out.println(Integer.TYPE);
    System.out.println(Integer.class);
  }
} ///:~
/**
 * 输出:(数字在不同机器也许不同但是int.class和Integer.TYPE的输出始终一致)
366712642
366712642
1829164700
int
int
class java.lang.Integer
 */

如上述例子所述
泛型加在Class上可以在编译期强制要求Class引用指向正确类型的对象,而不加泛型则可以将引用指向任意class类型的对象
要规范class类型 但是也不是严格要求是某一个固定类型,如何来做呢?(稍微放松的class泛型)

public class WildcardClassReferences {
	public static void main(String[] args) {
		//Class<Number> genericNumberClass = int.class;
		//Class<Number> genericNumberClass1 = double.class;
		//你也许想像上面这么做来实现放松泛型对class的严格限制 因为Integer和Double都继承自Number
		//但是实际是会报错的 Integer Class和Double Class都不是Number class的子类(15章讨论)
        //报错为 Type mismatch: cannot convert from Class<Integer> to Class<Number>
		
		//稍微放松泛型的显示 Class<?>的引用可以指向任意类型的Class对象(?表示任何事物) 虽然和不使用泛型是同样的效果,但是Class<?>明确表示此处使用类型不定的Class,而直接使用Class则可能是因为遗漏
		//因此即使作用相同 推荐使用Class<?>
		Class<?> intClass = int.class;
		intClass = double.class;
	}
} ///:~
Class<?>太宽泛了 范围再缩小一点呢?
public class BoundedClassReferences {
	public static void main(String[] args) {

		//Class<? extends Number>声明的是任何是Number子类类型的(包括Number)的Class
		Class<? extends Number> bounded = Integer.class;
		bounded = double.class;
		bounded = Number.class;
		// Or anything else derived from Number.
	}
} // /:~

注意对比上一个例子 Class只是声明一个Number类型的Class Class<? extends Number>才是声明一个Number以及继承了Number的Class.
将泛型引入 仅仅是为了将类型错误发现提前到编译时。
泛型的Class在newInstance有另外的作用

class CountedInteger {
	private static long counter;
	private final long id = counter++;

	public String toString() {
		return Long.toString(id);
	}
}

public class FilledList<T> {// 定义class时包含了一个泛型T,T
							// 意为Type,当然也可替换为S或者L等任意字母。但我们通常还是约定俗成地使用T
	private Class<T> type;

	public FilledList(Class<T> type) {
		this.type = type;
	}

	public List<T> create(int nElements) {
		List<T> result = new ArrayList<T>();
		try {
			for (int i = 0; i < nElements; i++)
				result.add(type.newInstance());//调用输入类型地无参构造方法来创建输入类型的实例 如果没有无参构造方法会报错。
			//使用泛型的Class newInstance得到的将是具体泛型定义的类型 而不是Object类型
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return result;
	}

	public static void main(String[] args) {
		//泛型T在以下代码中为CountedInteger类型
		FilledList<CountedInteger> fl = new FilledList<CountedInteger>(
				CountedInteger.class);
		System.out.println(fl.create(15));//这里会调用CountedInteger的toString方法 
	}
} /*
 * Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
 */// :~
Class<? extends XXX>的限制,在一些情况,该声明方式有些含糊不清

 public class GenericToyTest {
	public static void main(String[] args) throws Exception {
		Class<FancyToy> ftClass = FancyToy.class;
		// 产生精确类型:
		FancyToy fancyToy = ftClass.newInstance();
		Class<? super FancyToy> up = ftClass.getSuperclass();// 产生FancyToy父类的Class类型
		// 编译报错:
		// Class<Toy> up2 = ftClass.getSuperclass();//不允许直接声明为Class<Toy>,虽然Toy是FancyToy的父类,这就是含糊不清的地方
		// 只能产生Object类型:(虽然obj确实是Toy类型的)
		Object obj = up.newInstance();//由于上面的种种原因 newInstance不知道返回的确切类型 只能返回Object类型
		System.out.println(obj instanceof Toy);//实际类型是Toy
	}
} ///:~
/**
 * output:
 * true
 * 
 */

14.2.3 新的转型语法

class Building {}
class House extends Building {}

public class ClassCasts {
  public static void main(String[] args) {
    Building b = new House();
    Class<House> houseType = House.class;
    House h = houseType.cast(b);//将Building的实例转化成House类型
    h = (House)b; // 与上面等价,在实际应用中 还没有使用过cast方法,看上去cast比强制转换(即使用括弧转换)更麻烦,暂时不清楚其用途在哪
  }
} ///:~

14.3 类型转换前先做检查

已知的RTTI的形式包括
1)使用强制类型转换 如果无法转换则会抛出ClassCastException
2)代表对象的类型的Class对象,可以根据Class对象获取运行时所需的信息
向下转型和向上转型
通常我们绘制继承结构图时将基类画在上边 子类绘制在下边,因此父类转换成子类称为向下转型,子类转换成父类是向上转型。子类转换成父类(向上转型)是安全的,因此可以直接声明一个父类引用但是实际赋值为子类对象,只不过此时丢弃了子类特征。而父类转换成子类(向下转型),是不安全的,必须使用强制转换符(编译器会检查向下转型是否转型到正确的类型)

class Father {
}
class Child1 extends Father {
}
class Child2 extends Father {
}
public class Test {
	public static void main(String[] args) {
		Father father1 = new Child1();// 向上转型
		Father father2 = new Child2();// 向上转型
		Father father = new Father();
		Child1 child1;
		Child2 child2;
		child1 = (Child1) father;//向下转型 转换失败test.Father cannot be cast to test.Child1
		child2 = (Child2) father;//向下转型 转换失败test.Father cannot be cast to test.Child2
		child1 = (Child1) father1;//向下转型 成功
		child2 = (Child2) father2;//向下转型 成功
	}
}

3) RTTI在Java中使用的第三种形式是instanceof 该操作符用于判断某个实例是不是某种类型的实例
比如 x instancof Dog,就是判断x是不是Dog实例
有了这个操作符 我们在进行强制转换是可以假设类型检查 一避免不必要的类型转换异常

instanceof的使用案例

package typeinfo.pets;

public class Individual implements Comparable<Individual> {//可比较和可排序的Individual //这个类比较复杂 可以到17章再看 目前先忽略
  private static long counter = 0;
  private final long id = counter++;
  private String name;
  public Individual(String name) { this.name = name; }
  // 'name' 是可选的:
  public Individual() {}
  //重写toString方法 输出类名+name(如果有name的话)
  @Override
  public String toString() {
    return getClass().getSimpleName() +
      (name == null ? "" : " " + name);
  }
  public long id() { return id; }
  
  //重写equals方法  判断对象相等的条件 1都是Individual实例 2两个实例的id相等
  @Override
  public boolean equals(Object o) {
    return o instanceof Individual &&
      id == ((Individual)o).id;
  }
  
  //重写hashCode方法
  @Override
  public int hashCode() {
    int result = 17;
    if(name != null)
      result = 37 * result + name.hashCode();//如果有name的case
    result = 37 * result + (int)id;//没有name的case
    return result;
  }
  
  //Comparable接口的方法
  public int compareTo(Individual arg) {
    // 先比较Class Name:
    String first = getClass().getSimpleName();
    String argFirst = arg.getClass().getSimpleName();
    int firstCompare = first.compareTo(argFirst);
    if(firstCompare != 0)
    return firstCompare;
    //如果Class Name相等 比较name属性
    if(name != null && arg.name != null) {
      int secondCompare = name.compareTo(arg.name);
      if(secondCompare != 0)
        return secondCompare;
    }
    //如果没有name 或者name相等 比较id
    return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
  }
} ///:~
下面是继承自Individual类的体系
public class Person extends Individual {
  public Person(String name) { super(name); }
} ///:~

public class Pet extends Individual {
  public Pet(String name) { super(name); }
  public Pet() { super(); }
} ///:~

public class Dog extends Pet {//继承自Pet的Dog
  public Dog(String name) { super(name); }
  public Dog() { super(); }
} ///:~

public class Mutt extends Dog {//混种狗
  public Mutt(String name) { super(name); }
  public Mutt() { super(); }
} ///:~

public class Pug extends Dog {//哈巴狗
  public Pug(String name) { super(name); }
  public Pug() { super(); }
} ///:~

public class Cat extends Pet {//继承自Pet的Cat
  public Cat(String name) { super(name); }
  public Cat() { super(); }
} ///:~

public class EgyptianMau extends Cat {//埃及貓
  public EgyptianMau(String name) { super(name); }
  public EgyptianMau() { super(); }
} ///:~

public class Manx extends Cat {//马恩岛猫(一种无尾家猫)
  public Manx(String name) { super(name); }
  public Manx() { super(); }
} ///:~

public class Rodent extends Pet {//啮齿动物 继承自Pet
  public Rodent(String name) { super(name); }
  public Rodent() { super(); }
} ///:~

public class Mouse extends Rodent {//老鼠
  public Mouse(String name) { super(name); }
  public Mouse() { super(); }
} ///:~

public class Hamster extends Rodent {//仓鼠
  public Hamster(String name) { super(name); }
  public Hamster() { super(); }
} ///:~

//创建宠物的类
public abstract class PetCreator {
  private Random rand = new Random(47);
  // types方法返回一个Pet及其子类的list
  public abstract List<Class<? extends Pet>> types();//这是个抽象方法 在子类中实现 这样就可以返回不同的Pet List
  public Pet randomPet() { // 创建一个随机类型的Pet
    int n = rand.nextInt(types().size());
    try {
      return types().get(n).newInstance();
    } catch(InstantiationException e) {//实例化出错 如果创建的是一个接口或者抽象类 则抛出此异常
      throw new RuntimeException(e);
    } catch(IllegalAccessException e) {//访问受限 如果默认构造器为private的 则会抛出该异常
      throw new RuntimeException(e);
    }
  }	
  public Pet[] createArray(int size) {//返回一个知道大小的Pet数组 其中包含随机种类的Pet
    Pet[] result = new Pet[size];
    for(int i = 0; i < size; i++)
      result[i] = randomPet();//使用randomPet生成Pet
    return result;
  }
  public ArrayList<Pet> arrayList(int size) {//返回一个知道大小的Pet ArrayList其中包含随机种类的Pet
    ArrayList<Pet> result = new ArrayList<Pet>();
    Collections.addAll(result, createArray(size));//复用createArray
    return result;
  }
} ///:~

实现抽象类的一个例子

public class ForNameCreator extends PetCreator {
	private static List<Class<? extends Pet>> types = new ArrayList<Class<? extends Pet>>();
	// Types that you want to be randomly created:
	private static String[] typeNames = {// 注意包名
	"typeinfo.pets.Mutt", "typeinfo.pets.Pug", "typeinfo.pets.EgyptianMau",
			"typeinfo.pets.Manx", "typeinfo.pets.Cymric", "typeinfo.pets.Rat",
			"typeinfo.pets.Mouse", "typeinfo.pets.Hamster" };

	@SuppressWarnings("unchecked")
	private static void loader() {
		try {
			for (String name : typeNames) {
				types.add((Class<? extends Pet>) Class.forName(name));
			}
		} catch (ClassNotFoundException e) {//如果传入的string无法解析为一个Class 则抛出异常
			throw new RuntimeException(e);
		}
	}

	static {
		loader();
	}

	public List<Class<? extends Pet>> types() {
		return types;
	}
} ///:~

利用instanceof统计各种Pet出现的次数

// Using instanceof.
package typeinfo;
import typeinfo.pets.*;
import java.util.*;

public class PetCount {
  static class PetCounter extends HashMap<String,Integer> {//PetCounter是一个HashMap
    public void count(String type) {//以宠物name作为key 出现次数作为value
      Integer quantity = get(type);
      if(quantity == null)
        put(type, 1);
      else
        put(type, quantity + 1);
    }
  }	
  public static void
  countPets(PetCreator creator) {
    PetCounter counter= new PetCounter();//创建一个HashMap 用于计数Pet出现次数
    for(Pet pet : creator.createArray(20)) {//遍历创建的随机20个Pet
      // List each individual pet:
      System.out.print(pet.getClass().getSimpleName() + " ");//输出每一Pet的类名
      if(pet instanceof Pet)
        counter.count("Pet");
      if(pet instanceof Dog)
        counter.count("Dog");
      if(pet instanceof Mutt)
        counter.count("Mutt");
      if(pet instanceof Pug)
        counter.count("Pug");
      if(pet instanceof Cat)
        counter.count("Cat");
      if(pet instanceof Manx)
        counter.count("EgyptianMau");
      if(pet instanceof Manx)
        counter.count("Manx");
      if(pet instanceof Manx)
        counter.count("Cymric");
      if(pet instanceof Rodent)
        counter.count("Rodent");
      if(pet instanceof Rat)
        counter.count("Rat");
      if(pet instanceof Mouse)
        counter.count("Mouse");
      if(pet instanceof Hamster)
        counter.count("Hamster");
    }
    // Show the counts:
    System.out.println();
    System.out.print(counter);//调用hashmap的toString方法
  }	
  public static void main(String[] args) {
    countPets(new ForNameCreator());
  }
} /* Output:
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric
{Pug=3, Cat=9, Hamster=1, Cymric=7, Mouse=2, Mutt=3, Rodent=5, Pet=20, Manx=7, EgyptianMau=7, Dog=6, Rat=2}
*///:~ 

例子很长 但是很简单,很容易理解 Individual可以先不管,删除里面的东西都没有关系,不影响学习instanceof的使用

14.3.1 使用类字面常量

上一个例子 如果使用类字面常量来实现

//在ForNameCreator中我们使用字符串typeinfo.pets.Mutt typeinfo.pets.Pug等来代表各种pet,然后使用Class.forName进行转换,这样既容易出错,一个字符写错了就导致找不到类的异常 另外也没有类字面常量看起来清楚。

public class LiteralPetCreator extends PetCreator {
	// No try block needed.
	// 所有Pet类型的List
	@SuppressWarnings("unchecked")
	public static final List<Class<? extends Pet>> allTypes = Collections
			.unmodifiableList(Arrays.asList(Pet.class, Dog.class, Cat.class,
					Rodent.class, Mutt.class, Pug.class, EgyptianMau.class,
					Manx.class, Cymric.class, Rat.class, Mouse.class,
					Hamster.class));
	// 随机创建的Pet类型集合:
	private static final List<Class<? extends Pet>> types = allTypes.subList(
			allTypes.indexOf(Mutt.class), allTypes.size());

	//覆盖父类types方法 因此实际使用的是这里的types //在父类randomPet方法中会调用该方法
	public List<Class<? extends Pet>> types() {
		return types;
	}

	public static void main(String[] args) {
		System.out.println("LiteralPetCreator main");
		System.out.println(types);
	}
} /*
 * Output: [class typeinfo.pets.Mutt, class typeinfo.pets.Pug, class
 * typeinfo.pets.EgyptianMau, class typeinfo.pets.Manx, class
 * typeinfo.pets.Cymric, class typeinfo.pets.Rat, class typeinfo.pets.Mouse,
 * class typeinfo.pets.Hamster]
 */// :~
 
public class Pets {
	// 使用外观模式包裹LiteralPetCreator实例 //关于外观模式
	// 可以参考:https://blog.csdn.net/u011109881/article/details/82344097
	public static final PetCreator creator = new LiteralPetCreator();

	public static Pet randomPet() {
		System.out.println("randomPet");
		return creator.randomPet();
	}

	public static Pet[] createArray(int size) {
		System.out.println("createArray");
		return creator.createArray(size);
	}

	public static ArrayList<Pet> arrayList(int size) {
		System.out.println("arrayList");
		return creator.arrayList(size);
	}
} ///:~

public class PetCount2 {
	public static void main(String[] args) {
		//调用PetCount方法实际参数为LiteralPetCreator类型
		PetCount.countPets(Pets.creator);
	}
} /* (Execute to see output) */// :~
输出与PetCount一致
可以看出类字面常量比ForName的形式方便很多

14.3.2 动态的instanceof

在PetCount中 我们使用了
if(pet instanceof Pet)
counter.count(“Pet”);
if(pet instanceof Dog)
counter.count(“Dog”);
if(pet instanceof Mutt)
counter.count(“Mutt”);
if(pet instanceof Pug)
counter.count(“Pug”);
if(pet instanceof Cat)
counter.count(“Cat”);
if(pet instanceof Manx)
counter.count(“EgyptianMau”);
if(pet instanceof Manx)
counter.count(“Manx”);
if(pet instanceof Manx)
counter.count(“Cymric”);
if(pet instanceof Rodent)
counter.count(“Rodent”);
if(pet instanceof Rat)
counter.count(“Rat”);
if(pet instanceof Mouse)
counter.count(“Mouse”);
if(pet instanceof Hamster)
counter.count(“Hamster”);
这一长串的类型判断,实际可以遍历hashMap的Key,将他与pet进行比较
即pair.getKey().isInstance(pet)
如果匹配 则计数+1
实际中isInstance使用得相对较少,一般用于不知道要判断的Class类型的时候

14.3.3 递归计数

14.4 注册工厂(写的有些乱 没看懂书里的意思)

为什么需要注册工厂?
在之前的例子中 我们维护一个列表
LiteralPetCreator的
public static final List<Class<? extends Pet>> allTypes = Collections
.unmodifiableList(Arrays.asList(Pet.class, Dog.class, Cat.class,
Rodent.class, Mutt.class, Pug.class, EgyptianMau.class,
Manx.class, Cymric.class, Rat.class, Mouse.class,
Hamster.class));
这个列表十分死板,加入我们新建了一种Pet 就必须更新该列表 有没有什么方法在我们新建了一种类型是 自动更新列表呢?注册工厂就是为了这个目的(从后面的例子看似乎不是这样。。。)
这是一个简单的工厂方法的例子
public interface Factory { T create(); } ///:~
实现这个接口 可以返回自定义类型
完整的例子

/**
 * 
 * @author hjcai
 * 
 * structure:
 * Part
 *  |
 *  |----------------------------------------------------------
 *  |                                                         |
 * Filter                                                    Belt
 *  |                                                         |
 *  |----------------------------------------------           |-----------------------------
 *  |             |             |              |              |             |              |
 * FuelFilter  AirFilter    CabinAirFilter  AirFilter       FanBelt      GeneratorBelt PowerSteeringBelt
 */

class Part {
	public String toString() {
		return getClass().getSimpleName();
	}

	static List<Factory<? extends Part>> partFactories = new ArrayList<Factory<? extends Part>>();
	static {
		//静态代码块 包含了所有具体子类的工厂接口
		partFactories.add(new FuelFilter.Factory());
		partFactories.add(new AirFilter.Factory());
		partFactories.add(new CabinAirFilter.Factory());
		partFactories.add(new OilFilter.Factory());
		partFactories.add(new FanBelt.Factory());
		partFactories.add(new PowerSteeringBelt.Factory());
		partFactories.add(new GeneratorBelt.Factory());
	}
	private static Random rand = new Random(47);

	// 该方法
	public static Part createRandom() {
		//以partFactories的size为边界值  取一个随机数
		int n = rand.nextInt(partFactories.size());
		//取出List partFactories的第n个Factory调用其create工厂方法创建对象
		return partFactories.get(n).create();
	}
}

abstract class Filter extends Part {
}

class FuelFilter extends Filter {
	// Create a Class Factory for each specific type:
	public static class Factory implements typeinfo.factory.Factory<FuelFilter> {
		public FuelFilter create() {
			return new FuelFilter();
		}
	}
}

class AirFilter extends Filter {
	public static class Factory implements typeinfo.factory.Factory<AirFilter> {
		public AirFilter create() {
			return new AirFilter();
		}
	}
}

class CabinAirFilter extends Filter {
	public static class Factory implements
			typeinfo.factory.Factory<CabinAirFilter> {
		public CabinAirFilter create() {
			return new CabinAirFilter();
		}
	}
}

class OilFilter extends Filter {
	public static class Factory implements typeinfo.factory.Factory<OilFilter> {
		public OilFilter create() {
			return new OilFilter();
		}
	}
}

abstract class Belt extends Part {
}

class FanBelt extends Belt {
	public static class Factory implements typeinfo.factory.Factory<FanBelt> {
		public FanBelt create() {
			return new FanBelt();
		}
	}
}

class GeneratorBelt extends Belt {
	public static class Factory implements
			typeinfo.factory.Factory<GeneratorBelt> {
		public GeneratorBelt create() {
			return new GeneratorBelt();
		}
	}
}

class PowerSteeringBelt extends Belt {
	public static class Factory implements
			typeinfo.factory.Factory<PowerSteeringBelt> {
		public PowerSteeringBelt create() {
			return new PowerSteeringBelt();
		}
	}
}

public class RegisteredFactories {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++){
			//10次调用Part的静态方法createRandom
			//System.out.println会调用Part的toString方法
			System.out.println(Part.createRandom());
		}
	}
} /*
 * Output: GeneratorBelt CabinAirFilter GeneratorBelt AirFilter
 * PowerSteeringBelt CabinAirFilter FuelFilter PowerSteeringBelt
 * PowerSteeringBelt FuelFilter
 */// :~

此处还是不明白工厂方法相对于使用类字面常量的优势。。。。。。工厂方法还是要存在一个静态代码块用于初始化所有子类工厂的List,每次新加一种类型都需在静态代码块添加该类型的工厂对象

14.5 instanceof与Class的等价性

本节讨论使用instanceof和使用Class对象直接比较的区别

class Base {
}

class Derived extends Base {
}

public class FamilyVsExactType {
	static void test(Object x) {
		System.out.println("Testing x of type " + x.getClass());
		System.out.println("x instanceof Base " + (x instanceof Base));
		System.out.println("x instanceof Derived " + (x instanceof Derived));
		System.out.println("Base.isInstance(x) " + Base.class.isInstance(x));
		System.out.println("Derived.isInstance(x) "
				+ Derived.class.isInstance(x));
		System.out.println("x.getClass() == Base.class "
				+ (x.getClass() == Base.class));
		System.out.println("x.getClass() == Derived.class "
				+ (x.getClass() == Derived.class));
		System.out.println("x.getClass().equals(Base.class)) "
				+ (x.getClass().equals(Base.class)));
		System.out.println("x.getClass().equals(Derived.class)) "
				+ (x.getClass().equals(Derived.class)));
		System.out.println();
		System.out.println();
	}

	public static void main(String[] args) {
		test(new Base());
		test(new Derived());
	}
} /*
 * Output:
Testing x of type class typeinfo.Base
x instanceof Base true
x instanceof Derived false
Base.isInstance(x) true
Derived.isInstance(x) false
x.getClass() == Base.class true
x.getClass() == Derived.class false
x.getClass().equals(Base.class)) true
x.getClass().equals(Derived.class)) false


Testing x of type class typeinfo.Derived
x instanceof Base true
x instanceof Derived true
Base.isInstance(x) true
Derived.isInstance(x) true
x.getClass() == Base.class false
x.getClass() == Derived.class true
x.getClass().equals(Base.class)) false
x.getClass().equals(Derived.class)) true
 */// :~

结论:
instanceof和isInstance返回结果相同,他们用于判断一个对象实例是否是某个类型或者某个类型的子类
==与equals的返回结果相同,他们用于判断一个对象实例是否是确切的某个类型,是子类 父类都不行 ,必须和指定的类型完全相同(此结论只在这里适用 在String上可不适用)

14.6 反射:运行时的类型信息

反射与RTTI极其类似,他们的目的都是获取类型信息,RTTI在编译时就知道类型信息 而反射则发生在运行时(运行时才知道类型信息)
反射的目的之一是为了远程方法调用(RMI remote method invoke)
反射的基本组成:Class类+java.lang.reflect类库 类库包含Field Method Constructor,有了这些类以及其他类中的方法 结合class类型信息 就能在运行时创建出对象,在编译期 反射可以什么都不做
反射和RTTI的关键之处在于Class对象,他们的类型信息获取都是从class对象获取的。反射在编译期无法获取.class文件,只有运行时才能从本地或网络获取,RTTI则在编译期读取.class文件

14.6.1 类方法提取器(反射的使用案例)

我们通常可以不直接使用反射,反射是为其他特性服务的,比如对象序列化和Android中跨进程AIDL
但是我们也可以使用反射来实现动态提取某个类的信息
以下例子在eclipse无法运行成功 因为没有找到生成.class文件
我是将其copy到其他路径执行以下命令得到输出的

javac ShowMethods.java
java ShowMethods ShowMethods

// Using reflection to show all the methods of a class,
// even if the methods are defined in the base class.
// {Args: ShowMethods}
import java.lang.reflect.*;
import java.util.regex.*;

public class ShowMethods {
	private static String usage = "usage:\n"
			+ "ShowMethods qualified.class.name\n"
			+ "To show all methods in class or:\n"
			+ "ShowMethods qualified.class.name word\n"
			+ "To search for methods involving 'word'";

	public static void main(String[] args) {
		if (args.length < 1) {
			System.out.println(usage);
			System.exit(0);
		}
		try {
			Class<?> c = Class.forName(args[0]);
			Method[] methods = c.getMethods();//get Method
			Constructor[] ctors = c.getConstructors();//get Constructor
			if (args.length == 1) {//number of parameter=1
				for (Method method : methods) {
					System.out.println(method.toString());
				}

				for (Constructor ctor : ctors) {
					System.out.println(ctor.toString());
				}

			} else {//number of parameter >1
				for (Method method : methods)
					if (method.toString().indexOf(args[1]) != -1) {
						System.out.println(method.toString());
					}
				for (Constructor ctor : ctors)
					if (ctor.toString().indexOf(args[1]) != -1) {
						System.out.println(ctor.toString());
					}
			}
		} catch (ClassNotFoundException e) {
			System.out.println("No such class: " + e);
		}
	}
} /*
 * Output: 
C:\Users\hjcai\Desktop>java ShowMethods ShowMethods
public static void ShowMethods.main(java.lang.String[])
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
public ShowMethods()
 */// :~

与书中相比 我去掉了正则表达式,如果没有去掉正则 则输出结果如下

C:\Users\hjcai\Desktop>java ShowMethods ShowMethods
public static void main(String[])
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public ShowMethods()

正则表达式去掉了"数个字符和."的字符集合
并且从输出 我们能看到,方法的打印会打印当前类及从父类继承的方法。

14.7 动态代理

关于代理模式 可以参考https://blog.csdn.net/u011109881/article/details/82848719
书里也举了一个代理模式的例子

interface Interface {
  void doSomething();
  void somethingElse(String arg);
}

class RealObject implements Interface {
  public void doSomething() { System.out.println("doSomething"); }
  public void somethingElse(String arg) {
    System.out.println("somethingElse " + arg);
  }
}	

class SimpleProxy implements Interface {//代理对象
  private Interface proxied;
  public SimpleProxy(Interface proxied) {//创建代理对象时会传入真实对象 代理对象调用方法时实际执行的还是真实对象
    this.proxied = proxied;
  }
  public void doSomething() {
    System.out.println("SimpleProxy doSomething");
    proxied.doSomething();
  }
  public void somethingElse(String arg) {
    System.out.println("SimpleProxy somethingElse " + arg);
    proxied.somethingElse(arg);
  }
}	

class SimpleProxyDemo {
  public static void consumer(Interface iface) {
    iface.doSomething();
    iface.somethingElse("bonobo");
  }
  public static void main(String[] args) {
    consumer(new RealObject());//直接调用
    System.out.println();
    consumer(new SimpleProxy(new RealObject()));//通过代理SimpleProxy调用
  }
} /* Output:
doSomething
somethingElse bonobo

SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
*///:~

代理模式的一个用途是限制客户端的方法调用,使其无法调用“多余”的方法
比如代理模式的可以统计调用的开销 但是你不希望客户端知道你在统计开销,就可以使用代理,客户端使用代理对象,服务端在真实对象的调用处进行统计,客户端完全不知道。

动态代理的案例

class DynamicProxyHandler implements InvocationHandler {//InvocationHandler 只有一个invoke方法
	private Object proxied;//代理对象

	public DynamicProxyHandler(Object proxied) {//构造方法初始化了代理对象
		this.proxied = proxied;
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("**** proxy: " + proxy.getClass() + ", method: "
				+ method + ", args: " + args);
		if (args != null){//输出参数
			for (Object arg : args){
				System.out.println("  " + arg);
			}
		}
		return method.invoke(proxied, args);
	}
}

class SimpleDynamicProxy {
	public static void consumer(Interface iface) {//静态方法consumer 负责调用Interface的两个方法
		iface.doSomething();
		iface.somethingElse("bonobo");
	}

	public static void main(String[] args) {
		RealObject real = new RealObject();
		consumer(real);//使用RealObject调用Interface的方法
		System.out.println();
		// 使用代理再调用一遍:
		Interface proxy = (Interface) Proxy.newProxyInstance(
				Interface.class.getClassLoader(),
				new Class[] { Interface.class }, new DynamicProxyHandler(real));//Proxy.newProxyInstance可以创建动态代理
		//Proxy.newProxyInstance 需要的参数
		//1 类加载器 可以从已加载的类获取类加载器
		//2 希望代理类实现的接口数组(非抽象类或类)
		//3 一个实现了InvocationHandler的对象实例
 		consumer(proxy);//此处对接口的调用转为对代理的调用
	}
} /*
 * Output: (95% match) 
doSomething
somethingElse bonobo

**** proxy: class typeinfo.$Proxy0, method: public abstract void typeinfo.Interface.doSomething(), args: null
doSomething
**** proxy: class typeinfo.$Proxy0, method: public abstract void typeinfo.Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@6bc7c054
  bonobo
somethingElse bonobo
 */// :~

通过动态代理管理方法的调用

// Looking for particular methods in a dynamic proxy.
import java.lang.reflect.*;

class MethodSelector implements InvocationHandler {
	private Object proxied;

	public MethodSelector(Object proxied) {
		this.proxied = proxied;
	}

	// 通过invoke方法来判断观察的调用,甚至可以根据方法名截断某个方法的调用
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		if (method.getName().equals("interesting")) {// 检测interesting方法
			System.out.println("Proxy detected the interesting method");
		}
		return method.invoke(proxied, args);
	}
}

interface SomeMethods {
	void boring1();
	void boring2();
	void interesting(String arg);
	void boring3();
}

class Implementation implements SomeMethods {
	public void boring1() {
		System.out.println("boring1");
	}

	public void boring2() {
		System.out.println("boring2");
	}

	public void interesting(String arg) {
		System.out.println("interesting " + arg);
	}

	public void boring3() {
		System.out.println("boring3");
	}
}

class SelectingMethods {
	public static void main(String[] args) {
		SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance(
				SomeMethods.class.getClassLoader(),
				new Class[] { SomeMethods.class }, new MethodSelector(
						new Implementation()));
		// SomeMethods对象是代理 new Implementation()是实际对象
		proxy.boring1();
		proxy.boring2();
		proxy.interesting("bonobo");
		proxy.boring3();
	}
} /*
boring1
boring2
Proxy detected the interesting method
interesting bonobo
boring3
 */// :~

14.8 空对象

例子讲解中涉及一个思想:极限编程原则之一:做可以工作的最简单的事情,再设计草案的初稿中,应该使用最简单的可以工作的结构,直到程序的某方面的需求要求添加额外的特性,而不是一开始就假设他是必需的。
空对象的创建是为了不必做过多的判空操作,但是也不能滥用空对象,如果判空不是很多,就不必使用空对象
空对象案例

package typeinfo;
//Null对象的使用 可以是我们不需要再调用方法时都对对象进行判空
//定义Null表示空对象
public interface Null {

}


package typeinfo;
//定义Person类并包含一个NullPerson的单例表示未初始化的Person
class Person {
	public final String first;
	public final String last;
	public final String address;
	// etc.
	public Person(String first, String last, String address) {
		this.first = first;
		this.last = last;
		this.address = address;
	}
	public String toString() {
		return "Person: " + first + " " + last + " " + address;
	}
	
	public static class NullPerson extends Person implements Null {//创建NullPerson表示没有初始化的Person
		private NullPerson() {//私有化构造方法
			super("None", "None", "None");
		}
		public String toString() {
			return "NullPerson";
		}
	}

	public static final Person NULL = new NullPerson();//NULL常量表示Person的空对象 单例
	//判断空对象的方法
	//1 可以利用此单例与实际person对象进行比较 以确定是不是空对象
	//2 可以使用instanceof来判断是不是NullPerson 而不需要手动添加isNull方法
} ///:~

package typeinfo;
//定义Position类 该类包含一个Person域
class Position {//Position的person域为空 则表示Position为空 因此不必为Position创建专门的空对象
	private String title;
	private Person person;

	public Position(String jobTitle, Person employee) {//构造方法1 两个参数,如果employee为空 则person域初始化为空对象
		title = jobTitle;
		person = employee;
		if (person == null) {
			person = Person.NULL;
		}
	}

	public Position(String jobTitle) {//构造方法2 一个参数,person域初始化为空对象
		title = jobTitle;
		person = Person.NULL;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String newTitle) {
		title = newTitle;
	}

	public Person getPerson() {
		return person;
	}

	public void setPerson(Person newPerson) {//setPerson方法 如果newPersonperson为空 person域赋值空对象
		person = newPerson;
		if (person == null) {
			person = Person.NULL;
		}
	}

	public String toString() {
		return "Position: " + title + " " + person;
	}
} ///:~


package typeinfo;

import java.util.*;
//定义Staff类 该类是一个包含一系列Position的ArrayList 
//同时该类也是测试类  目的是测试空对象的使用优势:不需要额外进行判空处理(在调用toString的地方)
public class Staff extends ArrayList<Position> {// Staff本身是一个List
	public void add(String title, Person person) {
		// Staff新增职位的方法1 默认设置Position的title和person
		add(new Position(title, person));
	}

	public void add(String... titles) {// Staff新增职位的方法2 默认只设置Position的title
		for (String title : titles) {
			add(new Position(title));
		}
	}

	public Staff(String... titles) {// 构造方法 调用Staff新增职位的方法2,只设置Position的title
		add(titles);
	}

	// 职位有效的条件是 1.title与指定title一致 2.职位的Person不是空对象
	public boolean positionAvailable(String title) {
		for (Position position : this) {// 遍历当前列表 寻找指定title的position
			if (position.getTitle().equals(title)
					&& position.getPerson() == Person.NULL) {
				return true;
			}
		}
		return false;
	}

	// 遍历position 找到指定title的position,如果该position的Person是空对象,重新赋值
	public void fillPosition(String title, Person hire) {
		for (Position position : this) {
			if (position.getTitle().equals(title)
					&& position.getPerson() == Person.NULL && hire != null) {
				position.setPerson(hire);
				return;
			}
		}
		throw new RuntimeException("Position " + title + " not available");
	}

	public static void main(String[] args) {
		Staff staff = new Staff("President", "CTO", "Marketing Manager",
				"Product Manager", "Project Lead", "Software Engineer",
				"Software Engineer", "Software Engineer", "Software Engineer",
				"Test Engineer", "Technical Writer");
		staff.fillPosition("President", new Person("Me", "Last",
				"The Top, Lonely At"));// 填充指定Position
		staff.fillPosition("Project Lead", new Person("Janet", "Planner",
				"The Burbs"));// 填充指定Position
		if (staff.positionAvailable("Software Engineer")) {
			staff.fillPosition("Software Engineer", new Person("Bob", "Coder",
					"Bright Light City"));// 重新填充职位Software Engineer
		}
		System.out.println(staff);// 调用AbstractCollection的toString方法 无需判空
	}
} /*
 * Output: [Position: President Person: Me Last The Top, Lonely At, Position:
 * CTO NullPerson, Position: Marketing Manager NullPerson, Position: Product
 * Manager NullPerson, Position: Project Lead Person: Janet Planner The Burbs,
 * Position: Software Engineer Person: Bob Coder Bright Light City, Position:
 * Software Engineer NullPerson, Position: Software Engineer NullPerson,
 * Position: Software Engineer NullPerson, Position: Test Engineer NullPerson,
 * Position: Technical Writer NullPerson]
 */// :~

另一个空对象的例子
使用接口(Robot)取代具体类(Person) 就可以使用动态代理自动创建空对象

package typeinfo;

public interface Operation {//操作接口 包含一个命令+一个描述 
  String description();
  void command();
} ///:~


package typeinfo;
import java.util.*;

public interface Robot {//Robot接口包含 name方法 模型方法 一个Operation的数组
  String name();
  String model();
  List<Operation> operations();
  class Test {//嵌套类
    public static void test(Robot r) {//测试方法
      if(r instanceof Null){
    	  System.out.println("[Null Robot]");
      }
      System.out.println("Robot name: " + r.name());
      System.out.println("Robot model: " + r.model());
      for(Operation operation : r.operations()) {//遍历Robot的operation
        System.out.println(operation.description());
        operation.command();
      }
    }
  }
} ///:~

测试类

package typeinfo;

import java.util.*;

public class SnowRemovalRobot implements Robot {
	private String name;

	public SnowRemovalRobot(String name) {//构造方法
		this.name = name;
	}

	public String name() {//Robot接口方法
		return name;
	}

	public String model() {//Robot接口方法
		return "SnowBot Series 11";
	}

	//SnowRemovalRobot包含一个Operation数组  数组包含4个Operation
	public List<Operation> operations() {
		return Arrays.asList(new Operation() {
			public String description() {
				return name + " can shovel snow";
			}

			public void command() {
				System.out.println(name + " shoveling snow");
			}
		}, new Operation() {
			public String description() {
				return name + " can chip ice";
			}

			public void command() {
				System.out.println(name + " chipping ice");
			}
		}, new Operation() {
			public String description() {
				return name + " can clear the roof";
			}

			public void command() {
				System.out.println(name + " clearing roof");
			}
		});
	}

	public static void main(String[] args) {
		Robot.Test.test(new SnowRemovalRobot("Slusher"));
	}
} /*
 * Output: 
Robot name: Slusher
Robot model: SnowBot Series 11
Slusher can shovel snow
Slusher shoveling snow
Slusher can chip ice
Slusher chipping ice
Slusher can clear the roof
Slusher clearing roof
*/// :~


package typeinfo;

// Using a dynamic proxy to create a Null Object.
import java.lang.reflect.*;
import java.util.*;

class NullRobotProxyHandler implements InvocationHandler {// 
	private String nullName;
	private Robot proxied = new NRobot();

	NullRobotProxyHandler(Class<? extends Robot> type) {
		nullName = type.getSimpleName() + " NullRobot";
	}

	private class NRobot implements Null, Robot {//嵌套类NRobot 代表空对象的Robot
		public String name() {
			return nullName;
		}

		public String model() {
			return nullName;
		}

		public List<Operation> operations() {
			return Collections.emptyList();
		}
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		return method.invoke(proxied, args);
	}
}

public class NullRobot {//测试类NullRobot
	//获取空Robot的静态方法  通过动态代理Proxy.newProxyInstance返回Robot对象
	public static Robot newNullRobot(Class<? extends Robot> type) {
		return (Robot) Proxy.newProxyInstance(NullRobot.class.getClassLoader(),
				new Class[] { Null.class, Robot.class },
				new NullRobotProxyHandler(type));
	}

	public static void main(String[] args) {
		Robot[] bots = { new SnowRemovalRobot("SnowBee"),
				newNullRobot(SnowRemovalRobot.class) };
		for (Robot bot : bots){//数组中存在一个非空对象和一个空对象
			Robot.Test.test(bot);
		}
	}
} /*
 * Output: 
Robot name: SnowBee
Robot model: SnowBot Series 11
SnowBee can shovel snow
SnowBee shoveling snow
SnowBee can chip ice
SnowBee chipping ice
SnowBee can clear the roof
SnowBee clearing roof

[Null Robot]
Robot name: SnowRemovalRobot NullRobot
Robot model: SnowRemovalRobot NullRobot
*/// :~

14.8.1 模拟对象与桩

空对象的逻辑变体是模拟对象与桩
模拟对象属于更轻量级的东西,如果需要做很多事情,通常创建大量小而简单的模拟对象 桩通常是重量级的,桩经常在测试间被复用
(桩在单元测试中被广泛运用)

14.9 接口与类型信息

接口可以降低耦合(原因参见 https://blog.csdn.net/hhhuuu2020/article/details/52440279)
例如
interface Test{}
class Test1 implements Test{}
class Test2 implements Test{}
假设有个方法
void say(Test test){System.out.println("");}
它接受Test对象
我们可以这样调用
Test t1 = new Test1();
或者t1 = new Test2();
不管哪一种都可以调用say(t1)
我们可以任意添加或删除Test的实现者,这不会影响现有代码,或者在代码调用处更改t1的实际子类对象 比如假设这样t1 = new Test3();
这种灵活操作就是低耦合,低程度地依赖其他类。

但是书中提及接口并非是对解耦地无懈可击的保障,原因如下:

package typeinfo.interfacea;

public interface A {
	void f();
} // /:~


package typeinfo;

// 偷偷摸摸绕过接口的例子
import typeinfo.interfacea.*;

class B implements A {// A只有f方法
	public void f() {
	}

	public void g() {
	}
}

public class InterfaceViolation {
	public static void main(String[] args) {
		A a = new B();
		a.f();
		// a.g(); // Compile error
		System.out.println(a.getClass().getName());
		if (a instanceof B) {
			//不用接口而使用实际类型
			//偷偷摸摸绕过接口A 转换成了classB调用B类的方法
			//这样做合法 但是接口低耦合的效果就没了(增加了耦合度)
			B b = (B) a;
			b.g();
		}
	}
} /*
 * Output: 
typeinfo.B
 */// :~

到这里 可以看到接口是可以被绕过的 通过instanceof和强制类型转换
有一种方法可以避免这种RTTI的漏洞即控制权限

package typeinfo.packageaccess;

import typeinfo.interfacea.*;
//避免绕过接口,直接使用实际类型的方法是控制权限 使得包外部的的客户端访问者看不到实际接口
class C implements A {
	public void f() {
		System.out.println("public C.f()");
	}

	public void g() {
		System.out.println("public C.g()");
	}

	void u() {
		System.out.println("package C.u()");
	}

	protected void v() {
		System.out.println("protected C.v()");
	}

	private void w() {
		System.out.println("private C.w()");
	}
}

public class HiddenC {//返回父类引用 但其实际指向子类对象
	public static A makeA() {
		return new C();
	}
} ///:~


package typeinfo;
// 无法绕过包访问权限
import typeinfo.interfacea.*;
import typeinfo.packageaccess.*;
import java.lang.reflect.*;

public class HiddenImplementation {
  public static void main(String[] args) throws Exception {
    A a = HiddenC.makeA();//实际引用是C类型的
    a.f();
    System.out.println(a.getClass().getName());//a的实际对象是C类型的
    // 编译报错: 找不到符号 'C': 因为C的访问权限限制为包内访问
    /* if(a instanceof C) {
      C c = (C)a;
      c.g();
    } */
    // 哎呦!反射仍然允许我们调用C的方法 
    callHiddenMethod(a, "g");
    //甚至可以访问缺少权限的方法
    callHiddenMethod(a, "u");
    callHiddenMethod(a, "v");
    callHiddenMethod(a, "w");
  }
  static void callHiddenMethod(Object a, String methodName)
  throws Exception {
    Method g = a.getClass().getDeclaredMethod(methodName);
    g.setAccessible(true);//可以访问缺少权限的方法是因为这句调用
    g.invoke(a);
  }
} /* Output:
public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

从上述例子我们看到 访问权限可以堵住RTTI的漏洞 使程序无法绕开接口,但是 我们同时发现,访问权限无法限制反射 通过反射,我们仍然可以调用所有方法

实际上,反射是无法避免的,不管是私有接口,私有内部类和匿名内部类 都无法逃脱被反射发现所有方法(包括私有方法)的命运

package typeinfo;
// 私有内部类也无法避免,反射仍然可以调用所有方法 
import typeinfo.interfacea.*;

class InnerA {
  private static class C implements A {
    public void f() { System.out.println("public C.f()"); }
    public void g() { System.out.println("public C.g()"); }
    void u() { System.out.println("package C.u()"); }
    protected void v() { System.out.println("protected C.v()"); }
    private void w() { System.out.println("private C.w()"); }
  }
  public static A makeA() { return new C(); }
}	

public class InnerImplementation {
  public static void main(String[] args) throws Exception {
    A a = InnerA.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Reflection still gets into the private class:
    HiddenImplementation.callHiddenMethod(a, "g");
    HiddenImplementation.callHiddenMethod(a, "u");
    HiddenImplementation.callHiddenMethod(a, "v");
    HiddenImplementation.callHiddenMethod(a, "w");
  }
} /* Output:
public C.f()
InnerA$C
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

package typeinfo;
// 匿名内部类也无法避免,反射仍然可以调用所有方法 
import typeinfo.interfacea.*;

class AnonymousA {
  public static A makeA() {
    return new A() {
      public void f() { System.out.println("public C.f()"); }
      public void g() { System.out.println("public C.g()"); }
      void u() { System.out.println("package C.u()"); }
      protected void v() { System.out.println("protected C.v()"); }
      private void w() { System.out.println("private C.w()"); }
    };
  }
}	

public class AnonymousImplementation {
  public static void main(String[] args) throws Exception {
    A a = AnonymousA.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Reflection still gets into the anonymous class:
    HiddenImplementation.callHiddenMethod(a, "g");
    HiddenImplementation.callHiddenMethod(a, "u");
    HiddenImplementation.callHiddenMethod(a, "v");
    HiddenImplementation.callHiddenMethod(a, "w");
  }
} /* Output:
public C.f()
AnonymousA$1
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

谈完了方法 我们再看看域

package typeinfo;

import java.lang.reflect.*;
//private 域也可以被修改,除了final域
class WithPrivateFinalField {
	private int i = 1;
	private final String s = "I'm totally safe";
	private String s2 = "Am I safe?";

	public String toString() {
		return "i = " + i + ", " + s + ", " + s2;
	}
}

public class ModifyingPrivateFields {
	public static void main(String[] args) throws Exception {
		WithPrivateFinalField pf = new WithPrivateFinalField();
		System.out.println(pf);//调用同String
		System.out.println("--------------------");
		
		Field f = pf.getClass().getDeclaredField("i");//获取WithPrivateFinalField的域i
		f.setAccessible(true);
		System.out.println("f.getInt(pf): " + f.getInt(pf));//打印i的初始值
		f.setInt(pf, 47);//修改i的值
		System.out.println(pf);//确认i的值可以被修改
		System.out.println("---------end test i-----------");
		
		f = pf.getClass().getDeclaredField("s");//获取WithPrivateFinalField的域s
		f.setAccessible(true);
		System.out.println("f.get(pf): " + f.get(pf));//打印s的初始值
		f.set(pf, "No, you're not!");//尝试修改s的值
		System.out.println(pf);//final的值无法被修改
		System.out.println("---------end test s-----------");
		
		f = pf.getClass().getDeclaredField("s2");//获取WithPrivateFinalField的域s2
		f.setAccessible(true);
		System.out.println("f.get(pf): " + f.get(pf));//打印s2的初始值
		f.set(pf, "No, you're not!");//尝试修改s2的值
		System.out.println(pf);//确认s2的值可以被修改
		System.out.println("----------end test s2----------");
	}
} /*
i = 1, I'm totally safe, Am I safe?
--------------------
f.getInt(pf): 1
i = 47, I'm totally safe, Am I safe?
---------end test i-----------
f.get(pf): I'm totally safe
i = 47, I'm totally safe, Am I safe?
---------end test s-----------
f.get(pf): Am I safe?
i = 47, I'm totally safe, No, you're not!
----------end test s2----------
*/// :~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值