您的位置:首页 > 编程语言 > Java开发

Java高新技术(二)之类加载机制和反射

2012-12-08 22:08 465 查看
-------android培训、java培训、期待与您交流! ----------

反射

反射(Reflection)的功能:

a)
在运行时判断任意一个对象所属的类

b)
在运行时构造一个类的对象

c)
在运行时判断任意一个类所具有的成员变量和方法

d)
在运行时调用任意一个对象的方法

在jdk中,主要由以下类来实现java反射机制,这些类位于java.lang.reflect包中

1.
Class类:代表一个类

在java当中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象

即一个类对应于一个Class对象。

2.
Field:代表类的成员变量(成员变量也称为类的属性)。

3.
Method类:代表类的方法

4.
Constructor:代表类的构造方法

5.
Array类:提供了动态创建数组,以及访问数组的元素的静态方法

Class对象

Class是所有类的对象。它是怎么获取的呢。

获取某个类或某个对象所对应的Class对象常用的有三种方式

a) 使用Class类的静态方法forName,例如Class.forName(“java.lang.String”);

b) 使用类.Class语法.例如:String.class;

c) 使用对象(object)的getClass()的方法。例如String
s=“aa”;Class<?>cla=s.getClass();

这三种所获得的一个类的Class字节码都是一样的

九个预定义对象:

基本的 Java
类型(boolean、byte、char、short、int、long、float和
double)和关键字 void也表示为 Class对象,他们都是使用Class类的isPrimitive()方法判断

比如:Integer.Type返回的是int.而Integer.class返回的是Integer所对应的Class对象

我们要是判断数组类型的Class实例对象,使用的是isArray()方法。

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void

我们看看如下代码演示:

publicclass ClassDemo {
publicstaticvoid main(String[] args)throws
Exception{
//Class对象的字节码判断
String str="Hello";
Class c1=str.getClass();
Class c2=String.class;
Class c3=Class.forName("java.lang.String");
System.out.println("c1==c2:"+(c1==c2));//true
System.out.println("c1==c3:"+(c1==c3));//ture
//基本类型的判定
System.out.println(c1.isPrimitive());//true
System.out.println(int.class.isPrimitive());//true
System.out.println(int.class==Integer.class);//false
System.out.println(int.class==Integer.TYPE);//true
System.out.println(int[].class.isPrimitive());//false
System.out.println(int[].class.isArray());//true
}
}

Constructor类

Constructor就是构造方法的反射

构造方法

我们通过Class对象的一些方法来获得Constructor的构造方法

获得所有的构造方法:

Constructor<?>[]getDeclaredConstructors()

获得publie所有构造方法

Constructor<?>[]getConstructors()

获得某一个构造方法

Constructor<T>getConstructor(Class<?>... parameterTypes)

创建实例对象两种方式:

a) 先获得Class对象,然后通过该Class对象的newInstance()直接生成即可,例如:

Class<?> classType=String.class;

Object obj=classType.newInctance();

b) 先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInctance()方法生成,例如:

Class<?> classType()=Cunstomer.class;

Constructor cons=classtype.getConstructor(new Class[]{});

Object obj=cons.newInstance(new Object[]{});

若想通过类的的带参数的构造方法生成对象,只能使用下面这一种方式:

Class<?> classType()=Cunstomer.class;

Constructor cons=classtype.getConstructor(newClass[]{String.class,int.class});

Object obj=cons.newInstance(new Object[]{“hello”,3});

比如:

普通方式:

StringBuffer sb=new StringBuffer(newStringBuffer(“abc”));

反射方式:

String str=(String)Constructor.newInstance(newStringBuffer(“abc”));

Field类

Field类都是成员变量的反射

构造方法都是通过Class对象获取的

构造方法:

获得指定的无需访问权限的Field:

Field getDeclaredField(String name)

获得所有的Field

Field[] getFields()

获得publie
的某个Field

Field getField(String name)

Field类的常用方法

a) Object get(Object obj):返回指定对象上此Field表示的字段的值

b) void setAccessible(boolean flag):将此对象的 accessible标志设置为指示的布尔值。

比如如下代码所示:

importjava.lang.reflect.Constructor;
import java.lang.reflect.Field;

publicclass FieldClass {
publicstaticvoid main(String[] args)throws
Exception {
FieldDemo fd=new FieldDemo(3,5);
//取出FieldDemo的x值
Field fieldx=FieldDemo.class.getField("x");
Object objx=fieldx.get(fd);
//取出FieldDemo的y值
Field fieldy=FieldDemo.class.getDeclaredField("y");
//由于y是private修饰的,所以暴力反射成可取的值
fieldy.setAccessible(true);
Object objy=fieldy.get(fd);

System.out.println("x=="+objx+",y=="+objy);
}
}
class FieldDemo{
publicintx;
privateinty;//private修饰的field
FieldDemo(int x,int y){
this.x=x;
this.y=y;
}
}

Method类

Method是成员方法的反射

构造方法都是通过Class对象获取的

构造方法:

获得所有的方法:

Method[] getDeclaredMethods()

获得无限制的指定方法

Method getDeclaredMethod(String name,Class<?>... parameterTypes)

获得所有public方法

Method[] getMethods()

获得指定public方法

Method getMethod(String name,Class<?>... parameterTypes)

Method的常用方法

调用一个类的指定的方法

Object invoke(Object obj, Object... args)

如果是调用时指定类的静态方法,那么Object invoke (null,Object….args)

我们代码演示下:

import java.lang.reflect.Method;

publicclass MethodTest {
publicstaticvoid main(String[] args)throws
Exception {
String str="Hello";
//指定String类的charAt方法
Method charAtMethod=String.class.getMethod("charAt",int.class);
System.out.println(charAtMethod.invoke(str, 4));
//指定String类的subString方法
Method subMethod=String.class.getMethod("substring",int.class,int.class);
System.out.println(subMethod.invoke(str, 1,3));

//获得此类的所有方法
Method[] allMethod=String.class.getDeclaredMethods();
for (Method me:allMethod){
System.out.println(me);
}
}
}

Array类

Array类就是数组的反射

Array的常用方法是:

a)
static Object get(Object array, int index) :返回指定数组对象中索引组件的值。

b)
static int getLength(Object array):以
int
形式返回指定数组对象的长度。

c)
static Object newInstance(Class<?> componentType, int length):创建一个具有指定的组件类型和长度的新数组。

d)
static void set(Object array, int index, Object value):将指定数组对象中索引组件的值设置为指定的新值。

如下面代码演示

import java.lang.reflect.Array;

publicclass ArrayDemo {
publicstaticvoid main(String[] args) {

int[] a ={1,2,3,4,5};
Class clazz=a.getClass();
if (clazz.isArray()){
int len=Array.getLength(a);//获得数组的长度
for(int i=0;i<len;i++){
System.out.println(Array.get(a, i));
}
}
}
}

反射的框架应用

反射是经常用在框架上的,当我们需要某个类时,可在配置文件里使用键值对来指定我们需要的引用的类。比如我们新建一个文件名称是config.properties,里面写上classname=java.util.ArrayList.注意这里值不要用双引号。那么我们使用反射来实现一个小小的框架。代码演示如下:
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.*;

publicclass FrameDemo {
publicstaticvoid main(String[] args)throws
Exception {
//关联系统中次配置文件
InputStreamin=new FileInputStream("config.properties");
Properties pro=new Properties();
pro.load(in);//加载此配置文件
//获得此配置文件中key对应的我们要取的对象
String className=pro.getProperty("classname");
//将此对象使用反射来获得实例
Collection coll=(Collection) Class.forName(className).newInstance();

coll.add(new Point(3, 3));
coll.add(new Point(5, 5));
coll.add(new Point(3, 3));

System.out.println(coll);
}
}
class Point{
intx;
inty;
public Point(int x,int
y) {
this.x=x;
this.y=y;
}
@Override
public String toString() {
return"x=="+x+",y=="+y;
}
}
我们在加载配置文件的时候(上面红色部分替换掉)建议使用这行代码,以后在开发时有用
InputStream in=
FieldDemo.class.getResourceAsStream("config.properties");

JavaBean

想要对JavaBean操作就是内省IntroSpector,javaBean是特殊的Java类。

Javabean属性是根据方法名来的,主要是用于传递数据信息

第一种抽取Javabean方法:

Javabean包下有个类是PropertyDescriptor,它描述 Java Bean通过一对存储器方法导出的一个属性

常用的方法:

a)
构造方法:PropertyDescriptor(StringpropertyName, Class<?> beanClass):传入一个成员值,并传入类是javaBean的对象

b)
Method getReadMethod() :获得JavaBean的get方法

c)
Method getWriteMethod() :获得javabean的set方法

代码演示如下:

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

publicclass JavaBeanDemo {
publicstaticvoid main(String[] args)throws
Exception {
Point p=new Point(3, 5);

String propertyName="x";
PropertyDescriptor pd=new PropertyDescriptor(propertyName,Point.class);
//获得get方法
Method meGetX= pd.getReadMethod();
//调用get方法
Object retVal=meGetX.invoke(p);
System.out.println(retVal);

Object value=7;
//获得set方法
Method meSetY=pd.getWriteMethod();
//调用set方法并传值
meSetY.invoke(p, value);
System.out.println(meGetX.invoke(p));
}
}
//javabean
class Point{
privateintx;
privateinty;
public Point(int x,int
y) {
this.x=x;
this.y=y;
}
publicint getX() {
returnx;
}
publicvoid setX(int
x) {
this.x = x;
}
publicint getY() {
returny;
}
publicvoid setY(int
y) {
this.y = y;
}
}

第二种抽取JavaBean的方法:

第二种我们使用内省器(Introspector)来抽取出BeanInfo的方法

Instrospector有个静态的方法是getBeanInfo(ClassbeanClass),向这个方法传递一个javaBeanClass对象,获得一个BeanInfo对象,可以通过这个对象的方法获得PropertyDescriptor,然后通过它来操作Bean的方法。BeanInfo可以操作属性,方法,和事件

比如我们对上面进行第二种写法的过程,类Point我们借用上面的。

public class JavaBeanDemo {

publicstatic void main(String[] args) throws Exception {

Pointp = new Point(3, 5);

StringpropertyName = "x";

//第一步:通过内省器来获得BeanInfo

BeanInfobean = Introspector.getBeanInfo(Point.class);

//第二步:BeanInfo对象获得后可以对方法,属性,事件操作。

//这里我们通过它的方法getPropertyDescriptors()来获得PropertyDescriptor数组

PropertyDescriptor[]pd = bean.getPropertyDescriptors();

//遍历PropertyDescrptor数组,并通过它的方法来对bean进行操作

for(PropertyDescriptor pds : pd) {

Class<?>clazz = pds.getPropertyType();

if(clazz == null)

continue;

System.out.println("Propertytype:" + clazz.getName());

System.out.println("Propertyname:" + pds.getName());

//判断所获得的变量名是否是x

if(pds.getName().equals(propertyName)) {

MethodgetXMethod = pds.getReadMethod();

Objectretval = getXMethod.invoke(p);

System.out.println(retval);

break;

}

}

}

}

第三种抽取JavaBean方法:

使用的Apache的开源BeanUtils工具包的jar包,来操作Bean。而且我们还要下载一个logging包,否则会报错,在BeanUtils中,JavaBeam类必须是public。否则会抛出NoSuchMethodException。

在Beanutils包有两种抽取Javabean的方法:

a) 第一种:BeanUtils类

b) 第二种:ProperUtils类

它们都有两个方法:分别是

a)
获得set方法,setProperty(bean,name,value);

b)
获得get方法:getProperty(bean,name);

代码演示如下:

注:依然引用上面的Point类

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

publicclass JavaBeanDemo {
publicstaticvoid main(String[] args)throws
Exception {
Point p = new Point(3, 5);
String propertyName ="y";

System.out.println("----------BeanUtils----------");
System.out.println("x=="+BeanUtils.getProperty(p,"x"));
BeanUtils.setProperty(p,"x",9);
System.out.println("x=="+p.getX());
System.out.println("----------Properutils-----------");
System.out.println("y=="+PropertyUtils.getProperty(p,"y"));
PropertyUtils.setProperty(p,"y", 3);
System.out.println("y=="+p.getY());
}
}

类加载机制剖析

系统加载器

系统默认三种类加载器:

a) BootStrap classLoader:JRE/lib/rt.jar,根类加载器

它被称为引导类加载器,它负责加载java的核心类,它不是ClassLoader的子类,是jvm自身实现的,即不是java实现的。

b) ExitClassLoader(Extension ClassLoader):,扩展类加载器

它负责加载JRE/lib/exit/下面的jar包的类,这样可以为java扩展核心类以外的类。

c) AppClassLoader(System classLoader):classpath指定的所有jar包或目录。系统类加载器

它负责JVM启动时加载来自java命令的的classPath环境变量所指定的jar包和类

d) 指定特殊的目录:这里放置的是我们自定义加载器,用户类加载器

这上面三个类是继承关系的,Bootstrap是顶类,AppClassLoader继承ExitClassLoader

这里的继承关系是类加载器实例之间的关系,不是传统的意义。

这里涉及到一个委托机制:

类加载器从AppClassLoader开始即发起者,如果找不到指定的类,就交给父类ExitClassLoader,如果父类没找到就交给顶类Bootstrap,如果找到就可以进行加载,如果还是找不到,就往回返,返回到发起者就会停止报错。所以通常情况下AppClassLoader下的子类是不会被加载到的,除非我们可以自定义加载器

我们代码演示下JVM类的类加载器

import java.io.*;

import java.net.*;

import java.util.*;

class ClassLoaderProTest

{

publicstatic void main(String[] args) throws Exception

{

ClassLoadersystemLoader=ClassLoader.getSystemClassLoader();

System.out.println("获取系统加载器:"+systemLoader);

//获取系统类加载器的加载路径

Enumeration<URL>emu=systemLoader.getResources("");

while(emu.hasMoreElements())

{

System.out.println(emu.nextElement());

}

ClassLoaderparentLoader=systemLoader.getParent();

System.out.println("获得此加载器的父类扩展类:"+parentLoader);

System.out.println("扩展类加载器的加载路径:"+System.getProperty("java.ext.dirs"));

System.out.println("扩展类");

}

}

从以上代码可以看出它们分别加载的路径是什么?扩展类的加载路径是JRE/lib/exit/

系统加载器加载的路径是程序运行的当前路径。

类加载机制:

1) 全盘负责:当一个加载器加载某个类的时候,这个类所引用的或依赖其他的class也将由此加载器加载,除非特别显式其他加载器来加载

2) 父类委托:加载一个类时,试图让父类(parent)先加载该class,只有在父类无法加载该类时,才尝试从自己的加载路径来加载。

3) 缓存机制:它将会保证所有被加载的Class都被缓存,当程序需要使用某个类时,先在缓存中查找该类,如果该类不存在该Class对象,系统才会读取二进制数据,将其转换成Class对象,存入缓存中。这也就是为什么修改了程序还要执行编译命令的原因。

自定义加载器

自定义加载器必须继承ClassLoader,是为了覆盖findclass,得到class文件来转换成字节码,

这种转换的方法是defineClass()。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: