1.Class对象
1.1普通的Class对象
Class对象是一个特殊的对象,是用来创建其它对象的对象(这里的其他对象就是指:java类的实例)。其实Class对象就是java类编译后生成的.class文件,它包含了与类有关的信息。
每当第一次使用一个类时,JVM必须使用“类加载器”子系统加载该类对象的Class对象。一旦这个类的Class对象被载入内存,它就被用来创建这个类的所有对象。当我们使用new关键字创建一个类的第一个对象的时候,JVM会帮助我们加载该类Class对象,但是当我们想自己加载这个类的Class对象怎么办呢?实际上有3种方法:
- Class.forName("类名字符串") (注意:类名字符串必须是全称,包名+类名)
- 类字面常量法:类名.class
- 实例对象.getClass()
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 Cookie(){ System.out.println("initializing Cookie"); } } public class SweetShop { public static void main(String[] args){ Class classType; System.out.println("inside main"); try{ classType=Class.forName("typeInfo.Gum"); }catch(ClassNotFoundException e){ System.out.println("Couldn't not find Gum"); } System.out.println("After creating Class.forName(\"Gum\")"); classType=Candy.class; System.out.println("After creating Candy"); Cookie cookie=new Cookie(); classType=cookie.getClass(); System.out.println("After creating Cookie"); } }
从输出中可以看出,Class对象仅在需要的时候才被加载(我们在Java 对象及其内存控制一文中说过:static初始化是在类加载时进行的)。
为什么没有打印出“Loading Candy”呢?因为,使用类字面常量法创建对Class对象的引用时,不会自动的初始化该Class对象。
1.2泛化的Class对象
由于普通Class引用指向的是它所指向的对象的确切类型。在Java引入泛型的概念之后,Java SE5的设计者将Class引用的类型通过使用泛型限定变得更具体了。再看我们上面的程序,一个Class classType既可以指向Candy类的Class对象也可以指向Gum类的Class对象还可以指向Cookie类的Class对象。这就很像我们编程时使用Object作为引用变量的类型一样。当我们用泛型限定了上面代码的classType之后,便会有错误出现了:
与使用普通的Class对象相比,使用泛型的Class对象,在调用newIntance方法时返回的不在是一个Object类型而是该对象的确切类型:
现在当我们需要放宽条件,即需要创建一个Class引用,它被限定为某种类型,或者该类型的任何子类型,这是我们就需要使用通配符"?"与extends关键字相结合,创建一个“范围”:
public class NumberClassObj { public static void main(String[] args) { Class<? extends Number> numType=int.class; numType=double.class; numType=Number.class; } }
当然,在这里调用newIntance方法的话返回的就是Number对象了。
到了这里,是不是有些朋友就会想,那我们是不是就可以使用泛型创建一个Class引用,它指向“某个类,它是一个类的超类”?当然可以:
public class SuperClassObj { interface Inter{ public void sayHello(); } class Base{ } class Sub extends Base implements Inter{ @Override public void sayHello() { System.out.println("hello everyone"); } } public static void main(String[] args) throws InstantiationException, IllegalAccessException { Class<? super Sub> superOfSub=Base.class; superOfSub=Inter.class; } }
但是这样创建出来的Class对象,在调用newInstance方法时将返回Object对象:
这是为什么呢?我的理解是:因为它表示的是一个很模糊的概念,编译器找不到一个合适的类型与之对应,并且没有这样一个.class文件。(不知道理解的是否合适,请各位大神不吝赐教)
2.类型转换前先做检查
2.1instanceof 运算符的陷阱
instanceof运算符的前一个操作数通常是引用类型的变量,后面一个操作数通常是一个类(也可以是接口),它用于判断前面的对象是否是后面的类或其子类、实现类的实例。是则返回true;否则,返回false。
Java规范,使用instanceof运算符有一个限制:instanceof运算符前面操作数的编译时类型必须是如下3种情况:
①与后面的类相同;
②是后面类的父类;
③是后面类的子类。
如果前面操作数的编译时类型与后面的类型没有任何关系,程序将没法通过编译,只有通过编译后才能考虑它的运算结果是true还是false。
对于上面这段代码,是不是有点晕了,为什么Math math=(Math)str;这里没有出现编译错误,而是在下面出现了编译错误呢?其实是这样的:当编译器编译Java程序时,编译器无法检查引用变量实际引用对象的类型,它只检查该变量的编译类型。对于math instanceof String而言math编译类型为Math,Math既不是String类型,也不是String类型的父类更不是String类型的子类,因此程序没法通过编译。至于math实际引用对象的类型是什么,编译器并不关心。至于Math math=(Math)str;没有出现编译错误,这和强制类型转换机制有关。对于Java的强制类型转换而言,也可以分为编译、运行两个阶段类分析它。
在编译阶段:强制类型转换要求被转换变量的编译时类型必须是如下3种情况:
①被转换变量的编译时类型与目标类型相同;
②被转换变量的编译时类型是目标类型的父类;
③被转换变量的编译时类型是目标类型的子类,这种情况下可以自动向上转型,无须强制转换。
Math math=(Math)str;没有提示编译错误的原因是,str编译时的类型是目标类型(Math)的父类,所以编译是正确的,可见,强制类型转换的编译阶段只关心引用变量的编译时类型,至于该引用变量实际引用对象的类型,编译器并不关心,也没法关心。
在运行阶段:被转换变量所引用对象的实际类型必须是目标类型的实例,或者是目标类型的子类的实例、实现类的实例,否则在运行时将引发java.lang.ClassCastException异常。
再如:
Object obj=new Integer(10); String str=(String)obj; System.out.println(str);
这段代码中由于obj变量实际引用的变量的类型是Integer,而Integer既不是String的子类也不是父类,所以在运行阶段抛出了java.lang.ClassCastException异常。
Instanceof有一个额外的功能:它可以确保第一个操作数所引用的对象不是null,当第一个操作数所引用的对象为null时,instanceof运算符返回false,而不会报异常。
2.2 Class.isInstance()
Class.isInstance方法提供了一种动态测试对象的途径。它与instanceof表达式的区别是:
Class.isInstance方法更加适合泛类型的检测(如代理,接口,抽象类等规则),常与泛化Class对象出现,而instanceof表达式适合直接类型的检查,常与普通的Class对象出现。
3.反射
前面讲了获取(手动加载)一个类的Class对象一些方法,我们在使用new创建一个对象的时候JVM首先会检查该类的Class对象是否被加载,没有的话,JVM会自动帮我们加载,那我们为什么还要手动加载呢?原因就是创建一个对象的方法不止使用new这一种,本节我们讲述的反射就是另外一种创建类实例的方法,但是JVM不会为反射创建对象自动加载Class对象。因此我们需要手动加载。
大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。
Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类,直到运行时才得知名称的class,这样的特点就是反射。
反射在使用JDBC、开发第三方插件、开发框架的时候用的比较多。
Java反射机制所需要的类、接口等都在java.lang.reflect包中。仔细查看该包的内容相信一定会熟练运用该技能的。这里就不展开讲解各个API了。
3.1动态代理
说到了反射,我们就顺便再了解一下动态代理。它是以反射为基础的。
什么是代理呢?代理就是用来代替“实际”对象的对象。这里的“实际”对象被称为委托类。它主要用在:不允许直接访问某些类时;对访问要做特殊处理时等。或者,要对原方法进行统一的扩展,例如加入日志记录。
代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等,因此它充当着“中间人”的角色,它与委托类具有相同点接口。一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
根据代理的创建方式,可以将代理类分为:
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:通过反射机制动态生成。
我们先来看一个静态代理:
public interface Interface { void doSomething(); void someElse(String str); } public class RealObject implements Interface { @Override public void doSomething() { System.out.println("doing something"); } @Override public void someElse(String str) { System.out.println("someElse "+str); } } public class SimpleProxy implements Interface { private Interface delegate; public SimpleProxy(Interface delegate){ this.delegate=delegate; } @Override public void doSomething() { System.out.println("SimpleProxy doSomething"); delegate.doSomething(); } @Override public void someElse(String str) { System.out.println("SimpleProxy someElse "+str); delegate.someElse(str); } } public class Business { public static void consumer(Interface inter){ inter.doSomething(); inter.someElse("bonobo"); } public static void main(String[] args) { consumer(new RealObject()); System.out.println("-----------------------"); consumer(new SimpleProxy(new RealObject())); } }
从上面代码中我们可以发现:相比SimpleProxy,RealObject类更专注于特定的功能,它将一些“额外”的操作(在这里指的就是System.out.println("SimpleProxy doSomething");和System.out.println("SimpleProxy someElse "+str);)从代码中分离出来,这就使得这些“额外”的操作的变化非常容易(比如我要变为System.out.println("AA");或者直接使用RealObject定义的功能),这也体现了设计模式的核心思想:封装变化点。RealObject类的核心功能是不变的,变的是那些“额外”的操作,因此,将这些“额外”的操作封装起来不就可以了。
再来看一下动态代理:
public interface Interface { void doSomething(); void someElse(String str); } public class RealObject implements Interface { @Override public void doSomething() { System.out.println("doing something"); } @Override public void someElse(String str) { System.out.println("someElse "+str); } } public 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); if(args!=null) for(Object arg : args) System.out.println(" "+arg); return method.invoke(proxied, args); } } public class SimpleDynamicProxy { public static void consumer(Interface inter){ inter.doSomething(); inter.someElse("bonobo"); } public static void main(String[] args) { RealObject realObj=new RealObject(); consumer(realObj); System.out.println("-----------------"); Interface proxy=(Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(realObj)); consumer(proxy); } }
查看JDK: