1、java主要有两种方式在运行时识别对象和类的信息:RTTI和“反射”机制。
2、类加载器系统实际上可以包含一条类加载器链,但是只有一个原生态加载器,它是JVM实现的一部分。原生态加载器加载的是所谓的可信类,包括Java API,它们通常都是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有需求,那么你有一种方式可以挂载额外的类加载器。
3、所有的类都是在对其第一次使用时,动态加载到JVM中。java程序再它开始运行之前并非完全加载,其各个部分是在必需时才加载。
4、使用Class.forName()加载类时,必需使用全限定名(包括包名)。
5、java还提供了“类字面常量”来获得Class对象的引用。即类A获取Class引用的方法为A.class。这样做不仅简单,安全(因为在编译期是就会检查),而且比forName的方法高效。对于基本类型的包装类,还有一个标准字段TYPE指向Class对象的引用。boolean.class等价于Boolean.TYPE。
6、 为了使用类而做的准备有三个步骤:
- 加载:由类加载器进行,该步骤将查找字节码,并从字节码中创建Class对象引用。
- 链接:验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。
- 初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
public class Initable {static final int staticFinal = 47;static final int staticFinal2 = new Random().nextInt(100);static {System.out.println("Initable");}
}public class Initable2 {static int staticNonFinal = 74;static{System.out.println("Initable2");}
}
public class Initable3 {static int staticNonFinal = 174;static{System.out.println("Initable3");}
}
public class ClassInitialization {public static void main(String[] args) throws Exception{Class 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);Class initable3 = Class.forName("com.yanguang.Initable3");System.out.println("After creating Initable3 ref");System.out.println(Initable3.staticNonFinal);}
}
/* Output
After creating Initable ref
47
Initable
15
Initable2
74
Initable3
After creating Initable3 ref
174
*/
- 初始化有效地实现了尽可能的“惰性”。仅使用.class不会触发类的初始化。而调用Class.forName会导致类初始化。(比如Initable.class和Class.forName("com.yanguang.Initable3"))
- 如果一个类的变量是static final的
- 这个变量的值是编译器常量:这个值可以在类不被初始化时就可以读取。(Initable.staticFinal)
- 这个变量的值动态获得(比如通过随机函数生成),那么对这个值得调用会导致类的初始化。(Initable.staticFinal2)
- 如果一个static域不是final的,那么在访问它时,总是要求在它被读取前,先进行链接(分配空间)和初始化(初始化该空间)。(Initable2.staticNonFinal)
7、泛化的Class引用。向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查。
Class intClass = int.class;
intClass = double.class; //编译通过
Class<Integer> genericIntClass = int.class;
// genericIntClass = double.class; // 编译报错。
有时为了放松校验的范围,可以使用泛型的特性? extends
// Class<Number> genericNumberClass = int.class;
Class<? extends Number> bounded = int.class;
bounded = double.class;
因为Integer Class对象不是Number Class对象的子类。
8、instanceof 和isInstance()生成的结果完全一样,equals()和==也一样。另外,instanceof保持了类型的概念,它指的是“你是这个类吗?或者你是这个类的派生类吗?”而如果用==将会比较实际的Class对象,就没考虑继承-它或者是这个确切的类型,或者不是。
9、当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样)。在真正调用它之前,必须先加载它的Class对象。因此,它的class文件对于JVM来说,必须是可以获取的,要么是在本地机器,要么是在网络上。所以RTTI和反射之间真正的区别只在于,对于RTTI来说,编译器在编译时打开和检查.class文件。而对于反射来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。
10、代理是基本的设计模式之一,它是你为了提供额外的操作或不同的操作,而插入的用来代替实际对象的对象。Java的动态代理可以动态地创建代理并动态地处理对所调方法的调用。
- 动态代理得实现InvocationHandler接口,其中有invoke方法,参数分别为代理对象,方法,参数。
- invoke()方法中传递进来代理对象,以防你需要区分请求的资源。
- 通常,会用Proxy.newProxyInstance(Interface.class.getClassLoader(),new Class[]{Interface.class}, new DynamicProxyHandler(realObject))的方法生成代理类,并用被代理对象的接口指向代理类,然后在方法中用Method.invoke()将请求转发给被代理对象,并传入必须的参数。
11、使用interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合度。但是有了instanceof之后,你就能知道接口引用所指向的具体对象,从而向下转型,这就会使得代码和具体对象的耦合提高,这是我们所不希望看到的。对于这种情况,最简单的方式是对接口的实现类使用包访问权限,这样在包外部的客户端就不能看到它们了。
class C implements A{
}
public class HiddenC{
pubic static A makeA(){return new C();}
}
- 但是通过反射,你仍旧可以到达并调用所有方法,即使是private方法。
static void callHiddenMethod(Object a, String methodName) throws Exception{
Method g = a.getClass().getDeclaredMethod(method);
g.setAccessible(true);
g.invoke(a);
}