为什么需要RTTI
- RTTI,Run-Time Type Identification
- 运行时类型信息使得你可以在程序运行时发现和使用类型信息
- 当需要知道某个泛化引用的确切类型,并根据不同的类型,进行不同的处理。
Class对象
Class
对象就是用来创建类的所有的“常规”对象的- Java使用Class对象来执行其RTTI操作
- 类是程序的一部分,每个类都有一个
Class
对象。换言之,每当编写并编译了一个新类,就会产生一个Class
对象。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统 - 所有类都是在对其第一次使用时,动态加载到JVM的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用
static
关键字。因此,使用new
操作符创建类的新对象也会被当作对类的静态成员的引用 - Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。
class Gum { static { System.out.println("Loading Gum"); } } class Candy { static { System.out.println("Loading Candy"); } } class Cookie { static { System.out.println("Loading Cookie"); } } public class SweetShop { public static void main(String[] args) { System.out.println("Inside main"); new Candy(); System.out.println("After creating Candy"); try { Class.forName("chapter14.Gum"); // 通过名称获取类的引用,切记,一定要写出具体的包名再到class名 } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println("After Class.forName(\"Gum\")"); new Cookie(); System.out.println("After creating Cookie"); } }
forName()
是取得Class
对象的引用的一种方法。它是用一个包含目标类的文本名(注意拼写和大小写)的String
作输入参数,返回的是一个Class
对象的引用。如果Class.forName()
找不到要加载的类,它会抛出异常ClassNotFoundException
- 如果已经有一个很感兴趣的类型的对象时,可以通过调用
getClass()
方法来获取Class
引用 getName()
获取全限定的类名,getSimpleName()
获取不包含包名的类名,getCanonicalName()
获取全限定的类名,isInterface()
方法得知是否为接口getInterfaces()
返回的是Class
对象,它们表示在感兴趣的Class
对象中所包含的接口getSuperClass()
方法查询其直接基类newInstance()
方法是实现“虚拟构造器”的一种途径,虚拟构造器允许你声明:我不知道你的确切类型,但无论如何要正确地创建你自己interface HasBatteries { } interface Waterproof { } interface Shoots { } class Toy { public Toy() { } public Toy(int i) { } } class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots { public FancyToy() { super(1); } } public class ToyTest { /** * 输出Class的一些信息 * @param cc */ static void printInfo(Class<?> cc) { System.out.println("Class name: " + cc.getName()); // 全限定类名 System.out.println("Is interface? [" + cc.isInterface() + "]"); // 判断是否为接口 System.out.println("Simple name: " + cc.getSimpleName()); // 不包括包名的类名 System.out.println("Canonical name: " + cc.getCanonicalName()); // 全限定类名 } public static void main(String[] args) { Class<?> c = null; try { c = Class.forName("chapter14.FancyToy"); } catch (ClassNotFoundException e) { e.printStackTrace(); System.exit(1); } printInfo(c); for (Class<?> face : c.getInterfaces()) { printInfo(face); } Class<?> up = c.getSuperclass(); Object obj = null; try { obj = up.getDeclaredConstructor().newInstance(); // 可通过指定参数的Class,进行指定构造器 } catch (InstantiationException e) { System.out.println("Cannot instantiate"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("Cannot access"); System.exit(1); } catch (InvocationTargetException e) { System.out.println("Cannot Invocation Target"); System.exit(1); } catch (NoSuchMethodException e) { System.out.println("No Such Method"); System.exit(1); } printInfo(obj.getClass()); } }
- 类字面常量
另一种获取Class
对象的引用
类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段Class<Boolean> type = Boolean.TYPE; Class<Boolean> booleanClass = boolean.class; // 两者等效
TYPE
。TYPE
字段是一个引用,指向对应的基本数据类型的Class
对象。但建议使用.class
形式 - 当使用
.class
来创建对Class
对象的引用时,不会自动地初始化该Class
对象。初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行
从class Initable { static final int staticFinal = 47; static final int staticFinal2 = ClassInitialization.rand.nextInt(); // 随机生成一个int 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 ClassNotFoundException { Class<Initable> initable = Initable.class; // 不触发初始化操作 System.out.println("After creating Initable ref"); System.out.println(Initable.staticFinal); // 不触发初始化操作,重点!!! System.out.println(Initable.staticFinal2); // 触发初始化操作 System.out.println(Initable2.staticNonFinal); // 触发初始化操作 System.out.println("After creating Initable3 ref"); Class<?> initable3 = Class.forName("chapter14.Initable3"); // 触发初始化操作 } } /* output After creating Initable ref 47 Initializing Initable -1172028779 Initializing Initable2 147 After creating Initable3 ref Initializing Initable3 */
Initable
引用的创建中可以看到,仅使用.class
语法来获得对类的引用不会引发初始化。
但是,Class.forName()
立即就进行了初始化,就像Initable3
引用的创建中所看到的
static final
是一个编译期常量,如Initable.staticFinal
所看到的,那么这个值不需要对Initable
类进行初始化就可以被读取。
如果一个static
域不是final
的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配内存空间)和初始化(初始化该存储空间),就像在对Initable2.staticNonFinal
的访问中所看到的那样。 - 为了使用类而做的准备工作实际包含三个步骤:
– 加载:由类加载器执行,该步骤将查找字节码,并从这些字节码中创建一个Class
对象
– 链接:将验证类中的字节码,为静态域分配存储空间,并且如果需要的话,解析这个类创建的对其他类的所有引用
– 初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块 - 泛化的
Class
引用
泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class
对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查
为了在使用泛化的public class GenericClassReferences { public static void main(String[] args) { Class intClass = int.class; Class<Integer> genericIntClass = int.class; // 指定了泛型 genericIntClass = Integer.class; // 与上面是相同的东西 intClass = double.class; // 没有指定泛型的,可以随意切换Class对象 //genericIntClass = double.class; // 指定了泛型了,不能切换成其他Class对象 } }
Class
引用时放松限制,可以使用通配符?
,表示任何事物。Class<?>
优于平凡的Class
,平凡的Class
不会产生编译器警告信息。Class<?>
的好处是它表示你并非碰巧或者由于疏忽,而是用了一个非具体的类引用,你就是选择了非具体的版本。(提醒作用)
限定一个Class<?> clazz = int.class; clazz = double.class;
Class
对象,为某个类型或者某个类型的子类型,需要将通配符和extends
关键字结合,创建一个范围。Class<? extends List> listClass = ArrayList.class; List list = listClass.getDeclaredConstructor().newInstance();
- 类的超类的
Class
对象Class<FancyToy> ftClass = FancyToy.class; Class<? super FancyToy> up = ftClass.getSuperclass(); Object o = up.getDeclaredConstructor().newInstance(); // 没办法向下转型,因此只能用Object接收
- 新的转型语法
Class
还添加了转型语法,cast()
方法。新的转型语法对于无法使用普通转型的情况显得非常有用(比如Class
对象不确定?)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); h = (House)b; //两者等效 } }
类型转换前先做检查
- 关键字
instanceof
,判断对象是不是某个特定类型的实例。 - 进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么
instanceof
是非常重要的,否则会得到一个ClassCastException
异常class Creature { } class Dog extends Creature { public void bark() { System.out.println("汪汪汪"); } } public class instanceofTest { public static void main(String[] args) { Creature dog = new Dog(); if (dog instanceof Dog) { ((Dog)dog).bark(); } } }
- 动态的
instanceof
Class.isInstance()
方法提供了一种动态地测试对象的途径boolean instance = List.class.isInstance(new ArrayList()); System.out.println(instance);
- 递归计数
isAssignableFrom()
来确认是否为指定基类的继承类Class<List> listClass = List.class; System.out.println(listClass.isAssignableFrom(List.class)); // true System.out.println(listClass.isAssignableFrom(ArrayList.class)); // true
public class TypeCounter extends HashMap<Class<?>, Integer> { private Class<?> baseType; // 基类 public TypeCounter(Class<?> baseType) { this.baseType = baseType; } /** * 输入一个对象,对该的类及其超类进行计数 * * @param obj */ public void count(Object obj) { Class<?> type = obj.getClass(); if (!baseType.isAssignableFrom(type)) { // 判断是否为基类的继承类 throw new RuntimeException(obj + " incorrect type: " + type + ", should be type or subtype of"); } countClass(type); } /** * 添加某个Class的计数 * * @param type */ private void countClass(Class<?> type) { Integer quantity = get(type); // 获取当前的个数 put(type, quantity == null ? 1 : quantity + 1); Class<?> superclass = type.getSuperclass(); if (superclass != null && baseType.isAssignableFrom(type)) { // 递归超类 countClass(superclass); } } @Override public String toString() { StringBuilder result = new StringBuilder("{"); for (Map.Entry<Class<?>, Integer> entry : entrySet()) { result.append(entry.getKey()).append("=").append(entry.getValue()).append(", "); // 它返回自己,使得可以连续append } result.delete(result.length() - 2, result.length()); // 左闭右开 result.append("}"); return result.toString(); } public static void main(String[] args) { TypeCounter counter = new TypeCounter(Object.class); counter.count(new ArrayList()); counter.count(new LinkedList()); System.out.println(counter); } }
注册工厂
- 工厂方法设计模式:将对象的创建工作交给类自己去完成,工厂方法可以被多态调用,从而创建恰当类型的对象
// 工厂接口 public interface Factory<T> { T create(); } public class Bean { // 内部工厂类 public static class BeanFactory implements Factory<Bean> { @Override public Bean create() { return new Bean(); } } public static void main(String[] args) { BeanFactory beanFactory = new BeanFactory(); Bean bean = beanFactory.create(); } }
反射
- 当通过反射与一个未知类型的对象打交道,JVM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样),在用它做其他事情之前必须加载那个类的
Class
对象。 - 因此,那个类的
.class
文件对于JVM来说必须是可获取的:要么在本地机器,要么可以通过网络获得。 - RTTI和反射之间真正的区别只在于,对RTTI来说,编译器在编译时打开和检查
.class
文件,而对于反射机制来说,.class
文件在编译时是不可获取的,所以是在运行时打开和检查.class
文件。 - 类方法提取器:
Class
的getMethods()
和getConstructors()
方法分别返回Method
对象的数组和Constructor
对象的数组。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'"; private static Pattern p = Pattern.compile("\\w+\\."); // 所有方法、构造器名字前面的包名 public static void main(String[] args) { args = new String[]{"chapter14.ShowMethods"}; if(args.length < 1) { System.out.println(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor<?>[] ctors = c.getConstructors(); if (args.length == 1) { for (Method method : methods) { System.out.println(p.matcher(method.toString()).replaceAll("")); // 将所有符合的,替换成空字符串 } for (Constructor<?> ctor : ctors) { System.out.println(p.matcher(ctor.toString()).replaceAll("")); // 将所有符合的,替换成空字符串 } lines = methods.length + ctors.length; } else { for (Method method : methods) { if (method.toString().contains(args[1])) { System.out.println(p.matcher(method.toString()).replaceAll("")); lines++; } } for (Constructor<?> ctor : ctors) { if (ctor.toString().contains(args[1])) { System.out.println(p.matcher(ctor.toString()).replaceAll("")); lines++; } } } } catch (ClassNotFoundException e) { System.out.println("Not such class: " + e); } } } /* output public static void main(String[]) public final void wait(long,int) throws InterruptedException public final void wait() 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() */
动态代理
- 代理是基本的设计模式之一,它是为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。
- 如果想要将额外的操作从实际对象中分离到不同的地方,特别是希望能够很容易地做出修改,从没有使用额外的操作转为使用这些操作,或者反过来时,代理就显得很有用。
interface Interface { void doSomething(); void SomethingElse(String arg); } /** * 接口简单的实现,真实的对象 */ class RealObject implements Interface { @Override public void doSomething() { System.out.println("doSomething"); } @Override public void SomethingElse(String arg) { System.out.println("SomethingElse " + arg); } } /** * 可以实现,在原来对象的基础上,额外增加一些需要的功能 */ class SimpleProxy implements Interface { private Interface proxied; /** * 传进一个代理对象 * @param proxied */ public SimpleProxy(Interface proxied) { this.proxied = proxied; } @Override public void doSomething() { System.out.println("SimpleProxy doSomething "); proxied.doSomething(); } @Override public void SomethingElse(String arg) { System.out.println("SimpleProxy SomethingElse " + arg); proxied.SomethingElse(arg); } } public class SimpleProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); iface.SomethingElse("bonobo"); } public static void main(String[] args) { consumer(new RealObject()); consumer(new SimpleProxy(new RealObject())); } }
- Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用
- 在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策
- 通过调用静态方法
Proxy.newProxyInstance()
可以创建动态代理,这个方法需要得到一个类加载器(你通常可以从已经被加载的对象中获取其类加载器,然后传递它),一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHandler
接口的一个实现。 -
class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("**** proxy: " + proxy.getClass() + ". method: " + method + ", args: " + args); // args输出对象的信息 if (args != null) { for (Object arg : args) { System.out.println(arg + " "); } } return method.invoke(proxied, args); } } public class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); iface.SomethingElse("bonobo"); } public static void main(String[] args) { RealObject real = new RealObject(); consumer(real); Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real)); consumer(proxy); } }
- 通过传递某些参数,过滤某些方法,只代理某些方法
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("interesting")) System.out.println("Proxy detected the interesting method"); return method.invoke(proxied, args); }
空对象
- 它可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何“真实”对象的值。通过这种方式,你可以假设所有的对象都是有效的,而不必浪费编程精力去检查
null
- 标记接口
public interface Null { }
- 创建
bean
时,内部添加空对象public class Person { public final String first; public final String last; public final String address; public Person(String first, String last, String address) { this.first = first; this.last = last; this.address = address; } @Override public String toString() { return "Person{" + "first='" + first + '\'' + ", last='" + last + '\'' + ", address='" + address + '\'' + '}'; } /** * 空对象 */ public static class NullPerson extends Person implements Null { public NullPerson() { super("None", "None", "None"); } @Override public String toString() { return "NullPerson"; } } public static final Person NULL = new NullPerson(); }
- 使用
bean
的类public class Position { private String title; private Person person; public Position(String title, Person person) { this.title = title; if (person == null) person = Person.NULL; this.person = person; } public Position(String title) { this.title = title; person = Person.NULL; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Person getPerson() { return person; } public void setPerson(Person person) { if (person == null) person = Person.NULL; this.person = person; } @Override public String toString() { return "Position{" + "title='" + title + '\'' + ", person=" + person + '}'; } }
- 通过动态代理实现空对象
接口与类型信息
interface
关键字的一种重要目标就是允许程序员隔离空间,进而降低耦合度。(详见设计模式)- 但是,如果摆脱了接口,直接使用实现类,耦合性还是会被传播出去。
public interface A { void f(); }
public class B implements A { @Override public void f() { System.out.println("f()"); } public void g() { System.out.println("g()"); } }
public class InterfaceViolation { public static void main(String[] args) { A a = new B(); a.f(); //a.g(); // 编译错误 B b = (B) a; // 强转 b.g(); // 接口就没办法隔离具体实现类了 } }
- 若想要保护接口,强制不允许强转成实现类,可以用以下方法
public class C implements A { @Override 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(); } }
public class HiddenImplementation { public static void main(String[] args) throws Exception { A a = HiddenC.makeA(); a.f(); // a.g(); // 编译失败 //(C)a; // 转型失败 // 通过反射,都可以用!! callHiddenMethod(a, "f"); callHiddenMethod(a, "g"); callHiddenMethod(a, "u"); callHiddenMethod(a, "v"); callHiddenMethod(a, "w"); } /** * 通过反射调用方法 * @param a 对象 * @param methodName 方法名 * @throws Exception 抛出异常 */ static void callHiddenMethod(Object a, String methodName) throws Exception { Method g = a.getClass().getDeclaredMethod(methodName); // 获取方法 g.setAccessible(true); // 设置方法是可触及的 g.invoke(a); // 调用方法 } }
makeA
产生A
接口类型的对象, 即使你从makeA()
返回的是C
类型,你在包外部依旧不能使用A
以外的任何方法。
但是,强大的反射可以实现!通过反射,依旧可以到达并调用所有方法,甚至是private
方法。 - 通过反射,可以调用私有内部类的所有方法
public class InnerA { // 内部类 private static class C implements A { @Override 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()); // 无比强大的反射,就算是私有内部类的方法,不需要强转也可以直接调用方法!! HiddenImplementation.callHiddenMethod(a, "g"); HiddenImplementation.callHiddenMethod(a, "u"); HiddenImplementation.callHiddenMethod(a, "v"); HiddenImplementation.callHiddenMethod(a, "w"); } }
- 匿名类,依旧可以!
public class AnonymousA { public static A makeA() { // 匿名内部类 return new A() { @Override 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 WithPrivateFinalField { private int i = 1; private final String s = "I am totally safe"; private String s2 = "Am I safe?"; @Override public String toString() { return "WithPrivateFinalField{" + "i=" + i + ", s='" + s + '\'' + ", s2='" + s2 + '\'' + '}'; } }
public class ModifyingPrivateFields { public static void main(String[] args) throws Exception { WithPrivateFinalField pf = new WithPrivateFinalField(); System.out.println(pf); Field i = pf.getClass().getDeclaredField("i"); // 获取指定属性 i.setAccessible(true); System.out.println("i.getInt(pf) " + i.getInt(pf)); i.setInt(pf, 10); System.out.println(pf); Field s = pf.getClass().getDeclaredField("s"); s.setAccessible(true); System.out.println("s.get(pf) "+s.get(pf)); s.set(pf, "No, you're not"); // final域没办法修改!!! System.out.println(pf); Field s2 = pf.getClass().getDeclaredField("s2"); s2.setAccessible(true); System.out.println("s2.get(pf) "+s2.get(pf)); s2.set(pf, "No, you're not"); System.out.println(pf); } }
但是,final域实际上在遭遇修改时是安全的,运行时系统会在不抛异常的情况下接受任何修改尝试,但实际上不会发生任何修改。