Java高新技术【4】 反射机制 及 Java类加载原理及类加载器
注意:public class 类名{}这个 class 是小写的。而有一种类Class 十大写的。这个细节要注意区分。
反射的基石->Class类
【1】java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。·Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。Class类描述了那些信息呢?类的名字,类的访问属性,类所属的包名,字段名称的列表,方法名称的列表。
【2】对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?
-人-Person
-类-Class
【3】对比提问:Person类代表人,它的实例对象就是张三,李四这样的一个个具体的人,Class类代表Java类,
它的各个实例对象又分别对应什么呢?
(1)-对应各个类的内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
(2)-一个类被类加载器加载到内存中,占用一片内存空间,这个空间里面的内容就是类的字节码,
不同的类的字节码是不同的,
所以它们在内存中的内容是不同的,这个一个个的空间可以分别用一个个的对象来表示,
这些对象显然具有相同的类型,这个类型是什么呢?
要把二进制对象 加载到内存里面来 接着用它去创建一个个的实例对象。
首先 要把类的字节码 加载到内存里面来,再用字节码去复制出一个个对象来。
Class cls1 = Date.class // Date这个类的 在内存里面的字节码。
你用到这个类,内存里面就会出现这个类的字节码。
就会从硬盘上加载进来。
·如何得到各个字节码对应的实例对象(Class类型)
-类名.class,例如,System.class
-对象.getClass(),例如,new Date().getClass()
-Class.forName("类名"),例如,Class.forName("java.util.Date");
按参数中指定的字符串形式的类名去搜索并加载相应的类,如果该类字节码已经被加载过,则返回代表该字节码的Class实例对象,否则,按类加载器的委托机制去搜索和加载该类;
如果所有的类加载器都无法加载到该类,则抛出ClassNotFoundException。加载完这个Class字节码后,接着就可以使用Class字节码的newInstance方法去创建该类的实例对象了。
有时候,我们程序中所有使用的具体类名在设计时(即开发时)无法确定,只有程序运行时才能确定,这时候就需要使用Class.forName去动态加载该类,这个类名通常是在配置文件中配置的。
例如,spring的ioc中每次依赖注入的具体类就是这样配置的,jdbc的驱动类名通常也是通过配置文件来配置的,以便在产品交付使用后不用修改源程序就可以更换驱动类名。
java.lang
类 Class<T>
java.lang.Object java.lang.Class<T>
- 类型参数:
-
T
- 由此Class
对象建模的类的类型。例如,String.class
的类型是Class<String>
。如果将被建模的类未知,则使用Class<?>
。
- 所有已实现的接口:
- Serializable, AnnotatedElement, GenericDeclaration,Type
public final class Class<T>extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement
Class
类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该Class
对象。基本的 Java 类型(boolean
、byte
、char
、short
、int
、long
、float
和double
)和关键字void
也表示为Class
对象。
Class
没有公共构造方法。Class
对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass
方法自动构造的。
以下示例使用 Class
对象来显示对象的类名:
void printClassName(Object obj) { System.out.println("The class of " + obj + " is " + obj.getClass().getName()); }
还可以使用一个类字面值(JLS Section
15.8.2)来获取指定类型(或 void)的 Class
对象。例如:
System.out.println("The name of class Foo is: "+Foo.class.getName());
getName
public String getName()
-
以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
如果此类对象表示的是非数组类型的引用类型,则返回该类的二进制名称,Java Language Specification, Second Edition 对此作了详细说明。
如果此类对象表示一个基本类型或 void,则返回的名字是一个与该基本类型或 void 所对应的 Java 语言关键字相同的String。
如果此类对象表示一个数组类,则名字的内部形式为:表示该数组嵌套深度的一个或多个 '[' 字符加元素类型名。元素类型名的编码如下:
Element Type Encoding boolean Z byte B char C class or interface Lclassname; double D float F int I long J short S 类或接口名 classname 是上面指定类的二进制名称。
示例:
String.class.getName() returns "java.lang.String" byte.class.getName() returns "byte" (new Object[3]).getClass().getName() returns "[Ljava.lang.Object;" (new int[3][4][5][6][7][8][9]).getClass().getName() returns "[[[[[[[I"
-
- 返回:
- 此对象所表示的类或接口名。
getClassLoader
public ClassLoader getClassLoader()
-
返回该类的类加载器。有些实现可能使用 null 来表示引导类加载器。如果该类由引导类加载器加载,则此方法在这类实现中将返回 null。
如果存在安全管理器,并且调用者的类加载器不是 null,也不同于或是请求其类加载器的类的类加载器的祖先,则此方法通过
RuntimePermission("getClassLoader")
权限调用此安全管理器的checkPermission
方法,以确保可以访问该类的类加载器。如果此对象表示一个基本类型或 void,则返回 null。
-
- 返回:
- 加载此对象所表示的类或接口的类加载器。
- 抛出:
-
SecurityException
- 如果存在安全管理器,并且checkPermission
方法拒绝对该类类加载器的访问。 - 另请参见:
-
ClassLoader
,SecurityManager.checkPermission(java.security.Permission)
,RuntimePermission
getPackage
public Package getPackage()
- 获取此类的包。此类的类加载器用于查找该包。如果该类是通过引导类加载器加载的,则搜索从 CLASSPATH 加载的包的集合,以查找该类的包。如果所有包对象都不是用该类的类加载器加载的,则返回 null。
只有该类的附属清单中定义了信息,并且类加载器使用该清单中的属性创建了包实例时,包才具有版本和规范属性。
-
- 返回:
- 该类的包,如果存档或基本代码中没有可用的包信息,则返回 null。
getInterfaces
public Class<?>[] getInterfaces()
- 确定此对象所表示的类或接口实现的接口。
如果此对象表示一个类,则返回值是一个数组,它包含了表示该类所实现的所有接口的对象。数组中接口对象顺序与此对象所表示的类的声明的
implements
子句中接口名顺序一致。例如,给定声明:class Shimmer implements FloorWax, DessertTopping { ... }
s
的值为Shimmer
的一个实例;表达式:s.getClass().getInterfaces()[0]
FloorWax
接口的Class
对象;s.getClass().getInterfaces()[1]
DessertTopping
接口的Class
对象。如果此对象表示一个接口,则该数组包含表示该接口扩展的所有接口的对象。数组中接口对象顺序与此对象所表示的接口的声明的
extends
子句中接口名顺序一致。如果此对象表示一个不实现任何接口的类或接口,则此方法返回一个长度为 0 的数组。
如果此对象表示一个基本类型或 void,则此方法返回一个长度为 0 的数组。
-
- 返回:
- 该类所实现的接口的一个数组。
·九个预定义Class实例对象:
-参看Class.isPrimitive方法的帮助
-Int.class == Integer.TYPE
八个基本类型+一个void,可以写成:void.class 形式的。
Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE
·数组类型的Class实例对象
-Class.isArray()
-总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void...
1.Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定,不同的实例对象有不同的属性值。Java程序中各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这个类事物呢?这个类的名字就是Class,要注意与小写class关键字的区别,Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名的列表、方法名称的列表,等等。学习反射,首先就要明白Class这个类。
反射
·反射就是把Java类中的各种成分 映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息业用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应的类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
·一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
1,反射的基本类型,Class类
public class ReflectTest { /** * @param args * @throws ClassNotFoundException */ public static void main(String[] args) throws Exception { String str1 = "abc"; Class cls1 = str1.getClass(); // 得到 String类型的字节码。 Class cls2 = String.class; Class cls3 = Class.forName("java.lang.String"); System.out.println(cls1 == cls2);// true System.out.println(cls1 == cls3);// true System.out.println(cls1.isPrimitive()); // false .是否是原子类型、。这个的字节码 不是 基本类型的。 System.out.println(int.class.isPrimitive()); // 这个的字节码是基本类型的。// true System.out.println(int.class == Integer.class); // false // Integer.TYPE 代表 所包装的基本类型。 System.out.println(int.class == Integer.TYPE);// true System.out.println(int[].class.isPrimitive()); // false System.out.println(int[].class.isArray()); // 用于判断是否是 数组。 } }
2,构造方法的反射:
Constructor类代表某个类中的一个构造方法
·得到某个类所有的构造方法:
-例子:Constructor[] constructors =
Class.forName("java.lang.String").getConstructors();
·得到某一个构造方法:
得到哪个构造方法,是没有顺序的,所以要根据 传递的参数来确定 用哪个构造方法。
-例子:Constructor constructor =
Class.forName("java.lang.String").getConstructor(StringBuffer.class);
// 得到:StringBuffer类的构造函数。
String
public String(StringBuffer buffer)分配一个新的字符串,它包含字符串缓冲区参数中当前包含的字符序列。
该字符串缓冲区的内容已被复制,后续对它的修改不会影响新创建的字符串。
参数:
buffer - 一个 StringBuffer
// 得到 构造方法,这个StringBuffer代表 选择哪一个构造方法。代表类型
Constructor constructor2 = String.class.getConstructor(StringBuffer.class);
// 有了构造方法 就可以 拿到 他的实例对象。而 newInstance 是用来指定 到底对应 哪一个构造方法。
同样类型的对象。
// 下面的StringBuffer表示:用这个的时候 还要传递一个对象进去。
String str2 = (String)constructor2.newInstance(new StringBuffer("abc"));
·创建实例对象:
-通常方式:String str = new String(new StringBuffer("abc"));
newInstance 用来指定 到底对应 哪一个构造方法。
-反射方式:String str = (String)constructor.newInstance(new StringBuffer("abc"));
public class ReflectConstructor { /** * @param args * @throws ClassNotFoundException */ public static void main(String[] args) throws Exception { Constructor constructor1 = String.class.getConstructor(StringBuffer.class); // 为什么 "abc" 不可以,因为我要一个StringBuffer 类型的,您却给我String类型的,所以造成了 参数不匹配 异常。 String str2 = (String)constructor1.newInstance(/*"abc"*/new StringBuffer("abc")); System.out.println(str2.charAt(2)); // 结果为 c,说明 从零计数开始。 } }
3,FIeld反射:
import java.lang.reflect.Field; public class ReflectField { /** * @param args * @throws Exception * @throws SecurityException */ public static void main(String[] args) throws SecurityException, Exception { ReflectPoint pt1 = new ReflectPoint(3,5); // 得到类身上的某个字段. 这里fieldY 不代表具体的值,只代表一个变量。 Field fieldY = pt1.getClass().getField("y"); // fieldY 不是对象身上的变量,而是类上,要用他去取某个对象上对应的值。 // 下面 要取得 那个对象 身上的值。 System.out.println(fieldY.get(pt1)); // Field fieldX = pt1.getClass().getField("x"); // 私有的看不见。 Field fieldX2 = pt1.getClass().getDeclaredField("x"); // 不管私不私有、但是 只要你看见而已,不让你用。 fieldX2.setAccessible(true);// 暴力。 System.out.println(fieldX2.get(pt1)); chanageStringValue(pt1); System.out.println(pt1); } private static void chanageStringValue(Object obj) throws Exception, IllegalAccessException { Field[] fields = obj.getClass().getFields(); // 得到所有字段。 for(Field field : fields ){ // if(field.getType().equals(String.class)){ // 字节码 用 == 来比较。因为是同一份字节码。 if(field.getType() == String.class ){ String oldValue = (String) field.get(obj); String newValue = oldValue.replace("b", "a"); // 设值: field.set(obj, newValue); } } } }
package com.itm.day1; import java.util.Date; public class ReflectPoint { private Date birthday = new Date(); private int x; public int y; public String str1 = "ball"; public String str2 = "basketball"; public String str3 = "itcast"; public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final ReflectPoint other = (ReflectPoint) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } @Override public String toString(){ return str1 + ":" + str2 + ":" + str3; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
4,方法的反射:
重点 理解: 列车司机 把列车 停下来了,列车司机 是没有那么大的力气的。
Method类 ·Method类代表某个类中的一个成员方法 ·得到类中的某一个方法: -例子:Method charAt =
Class.forName("java.lang.String").getMethod("charAt",int.class);
·调用方法: -通常方式:System.out.println(str.charAt(1));
-反射方式:System.out.println(charAt.invoke(str,1));
·如果传递给Method对象的invoke()方法的第一个参数为null,
这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
·jdk1.4和jdk1.5的invoke方法的区别:
-Jdk1.5:public Object invoke(Object obj,Object... args)
-Jdk1.4:public Object invoke(Object obj,Object[] args),
即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,
数组的每个元素分别对应被调用方法中的一个参数,所以,
调用charAt方法的代码也可以用Jdk1.4改写为charAt.invoke("str",new Object[]{1})形式。
Method methodCharAt = String.class.getMethod("charAt", int.class);
System.out.println(methodCharAt.invoke(str1, 1));
import java.lang.reflect.Method; public class ReflectMethodMain { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { TestArguments.main(new String[]{"111","222"}); System.out.println("\n"); System.out.println("下面是反射"); String startingClassName = args[0]; Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class); // 把这两个参数 达成一个包,送进去。这时,接收到后就会把包拆开,所以就会认为是 三个参数。 // 所以要再打成一个包 new Object[]{new String[]{"111","222"}} mainMethod.invoke(null, (Object)new String[]{"111","222"}); // 利用反射: /* 1,我在写程序的时候 不知道 要用那个类 * */ System.out.println("\n"); Class clazz = Class.forName(args[0]); Method mMain = clazz.getMethod("main", String[].class); mMain.invoke(null, new Object[]{new String[]{"aaa","bbb"}}); } } class TestArguments{ public static void main(String[] args){ for(String arg : args){ System.out.println(arg); } } }
说明:
(1),
(2),mainMethod.invoke(null, (Object)new String[]{"111","222"}); 若把 (Object)去掉,则抛出异常。
5,
数组的反射
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
·代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class。
·基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;
如:编译出错:Object[] aObj3 = a1;
非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Java类加载原理及类加载器
http://blog.****.net/zdwzzu2006/article/details/2253982
Java和其他语言不同的是,Java是运行于Java虚拟机(JVM)。这就意味着编译后的代码是以一种和平台无关的格式保存的,而不是某种特定的机器上运行的格式。这种格式和传统的可执行代码格式有很多重要的区别。具体来说,不同于C或者C++程序,Java程序不是一个独立的可执行文件,而是由很多分开的类文件组成,每个类文件对应一个Java类。 另外,这些类文件并不是马上加载到内存,而是当程序需要的时候才加载。 类加载器就是Java虚拟机中用来把类加载到内存的工具。而且,Java类加载器也是用Java实现的。这样你就不需要对Java虚拟机有深入的理解就可以很容易创建自己的类加载器了。
为什么要创建类加载器?
既然Java虚拟金已经有了类加载器,我们还要自己创建其他的呢?问得好。默认的类加载器只知道如何从本地系统加载类。当你的程序完全在本机编译的话,默认的类加载器一般都工作的很好。但是Java中最激动人心的地方之一就是很容易的从网络上而不只是本地加载类。举个例子,浏览器可以通过自定义的类加载器加载类。 还有很多加载类的方式。除了简单的从本地或者网络外,你还可以通过自定义Java中最激动人心的地方之一:
* 执行非信任代码前自动验证数字签名
* 根据用户提供的密码解密代码
* 根据用户的需要动态的创建类
你关心的任何东西都能方便的以字节码的形式集成到你的应用中自定义类加载器的例子.如果你已经使用过JDK(Java软件开发包)中的appletviewer(小应用程序浏览器)或者其他Java嵌入式浏览器,你就已经使用了自定义类加载器了。Sun刚刚发布Java语言的时候,最
令人兴奋的一件事就是观看Java如何执行从远程网站下载的代码。执行从远程站点通过HTTP连接传送来的字节码看起来有点不可思议。
之所以能够工作,因为Java有安装自定义类加载器的能力。小应用程序浏览器包含了一个类加载器,这个类加载器不从本地找Java类,而是访问远程服务器,通过HTTP加载原始字节码文件,然后在Java虚拟机中转化为Java类。当然类加载器还做了其他的很多事情:他们阻止不安全的Java类,而且保持不同页面上的不同小程序不会互相干扰。Luke Gorrie写的一个包Echidna是一个开放的Java软件包,他允许在一个Java虚拟机中安全的运行多个Java应用程序。它通过使用自定义类加载器给每个应用程
序一份类文件的拷贝来阻止应用程序之间的干扰。
java类加载器 :
java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器(也叫应用类加载器)
类加载器是Java最强大的特征之一。但是开发者常常忘记类加载组件。类加载器是在运行时负责寻找和加载类文件的类。Java允许使用不同的类加载器,甚至自定义的类加载器。
Java 程序包含很多类文件,每一个都与单个Java类相对应,这些类文件不像静态C程序,一次性加载入内存,它们随时需要随时加载。这就是类加载器与众不同的地 方。它从源文件(通常是.class 或 .jar文件)获得不依赖平台的字节码,然后将它们加载到JVM内存空间,所以它们能被解释和执行。默认状态下,应用程序的每个类由 java.lang.ClassLoader加载。因为它可以被继承,所以可以自由地加强其功能。
使用自定义类加载器的原因
默认的 java.lang.ClassLoader仅仅可以从加载本地文件系统的类。Java被设计成不论本地磁盘或网络都有足够的弹性加载类,并且可以在加载 之前处理特殊事物。例如:应用程序可以检查Web站点或FTP上插入类的更新版本并且自动校验数字签名确保执行可信任的代码。许多众所周知的软件都使用自 己的类加载器。
通常默认加载器是所谓的bootstrap类加载器;它负责加载诸如java.lang.Object等关键类和加 载其他rt.jar文件的运行时代码到内存。因为Java语言规范没有提供bootstrap类加载器的详细信息,不同的JVM可能有不同的类加载器。如 果看到网页上有applets在运行,则它使用的是自定义类加载器。嵌入到浏览器中的applet阅读器包含了可以访问远程服务器上站点的类加载器,它可 以通过HTTP加载原始字节码文件,并且在JVM中将它们转换成类。
类加载器(除了bootstrap类加载器)有父类加载器,这些父类是基本加载器的加载器实例。最重要的一点是设置正确的父加载器。然后可以使用 类加载器的getParent()方法实现委派类请求(例如:自定义类加载器找不到使用专门方法的类时)。此时必须为将父加载器作为 java.lang.ClassLoader构造器的参数:
public class MyClassLoader extends ClassLoader
{
public MyClassLoader()
{
super(MyClassLoader.class.getClassLoader());
}
}
loadClass(String name)方法是ClassLoader的入口。名字参数是完全资格类名(FQCN),例如关于包类名。如果父加载器设置正确,当请求 MyClassLoader中的loadClass(String name)方法加载类,但又找不到需要加载的类时,则首先会询问父加载器。如果父加载器也找不到此类,则调用findClass(String name)方法。默认状态下findClass(String name)会抛出ClassNotFoundException例外,很多开发人员都很清楚这个例外。自定义类加载器的开发者都希望从 java.lang.ClassLoader继承时跳过这个方法。
findClass()方法的目标是为MyClassLoader容纳所有专门代码,此时不需要重复其他代码(例如当加载失败时调用系统 ClassLoader)。在此方法中,ClassLoader需要从原文件中获取字节码。一旦找到字节码则会调用defineClass()方法。 ClassLoader实例调用此方法是非常重要的。因此,如果两个ClassLoader实例定义了来自不同或相同原文件的字节码,则被定义的类也将区 别对待。
我们给出两个相似的类加载器MyClassLoader1 和 MyClassLoader2,它们都可以从相同的源文件找到MyCoolClass字节码。如果一个程序通过这两个加载器分别独立加载 MyCoolClass实例(coolClass1通过MyClassLoader1加载, coolClass2通过MyClassLoader2加载),MyCoolClass.class能够被独立定义。执行下面的代码:
MyCoolClass coolClass1 = (MyCoolClass)coolClass2;
将得到一个ClassCastException例外。(开发者如果没有很好的理解类加载机制则经常碰到这样的情况。)因为它们是不同的加载器 所定义的,JVM将它们看成不同的类。虽然它们是相同类型的类并且从相同的源文件加载,但是变量coolClass1和coolClass2不兼容。
不论是否跳过findClass() 或 loadClass(),getSystemClassLoader()方法将以实际ClassLoader对象的形式直接访问系统 ClassLoader。也可以通过调用findSystemClass(String name)方法间接访问。getParent()方法允许获得父加载器。Listing A给出了可以运行的自定义类加载器示例。