在一般数据操作的时候,我们都知道依赖数据类型的,比如:new对象的时候;根据类型定义变量,接口,类,数组;等等。
编译器也是根据类型进行代码的检查编译。
反射就不一样,它是在运行的时候,而非编译时,动态获取类型的信息,比如接口信息、成员信息、方法信息、构造方法信息等,根据这些动态获取的信息创建对象、访问/修改成员、调用方法等。
1.Class类
每个已加载的类在内存都有意分类信息,所有类的父类Object有一个方法,可以获取对象的Class对象:
public final native Class> getClass();
获取Class对象并不一定需要实例化对象,可以通过类名先来获取Class对象
Class cls = Date.class
接口也有Class对象:
Class cls = Comparable.class
基本数据类型没有getClass方法,但是有对应的Class对象 ,类型参数为对应的包装类型:
Class intCls = int.class;
Class byteClass = byte.class;
Class charCls = char.class;
Class boolCls = boolean.class;
对于数组,每种类型都有对应数组类型的Class对象,每个维度一个:
String[] strArr = new String[10];
int[][] towintArr=new int[3][2];
int[] oneIntArr=new int[10];
Class strArrCls = (Class) strArr.getClass();
Class towIntArrCls = (Class) towintArr.getClass();
Class oneIntArrCls = (Class) oneIntArr.getClass();
Class类中还有一个静态方法forName,可以根据类名直接加载Class,获取Class对象:
try {
Class> cls = Class.forName("java.util.Map");
System.out.println(cls);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
可以看出上述有三种方法取得Class对象:
1.类名.Class
2.实例化对象.getClass();
3.Class.forName("包名+类名");
有了Class对象之后,就可以了解到关于类型的很多信息。
1.1 名称信息
public String getName()//返回Java内部使用的真正的名称
public String getSimpleName()//返回不带包信息的名称
public String getCanonicalName()//返回带包名称的信息
可以看出"["开头的表示数组,有几个“["就是几维数组,I表示int类型,L表示接口或者类。
1.2属性信息
类中定义的属性,可以用反射进行获取,用Field表示
public Field[] getFields() throws SecurityException//获取类中全部属性,包括其父类的
public Field getField(String name) throws NoSuchFieldException, SecurityException//根据名称获取属性
public Field[] getDeclaredFields() throws SecurityException//返回本类属性,包括public,但不包括父类的
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException//通过指定名获取
class Person{
public String name;
public int age;
private String address;
}
class student extends Person{
public String studentID;
public String school;
private int achievement;
}
public class Test {
public static void main(String[] args) {
Class cls = student.class;
Field[] fields = cls.getFields();
for (Field field :
fields) {
System.out.println(field.getName());
}
}
}
上述的代码运行的结果为:
studentID
school
name
age
class Person{
public String name;
public int age;
private String address;
}
class student extends Person{
public String studentID;
public String school;
private int achievement;
}
public class Test {
public static void main(String[] args) {
Class cls = student.class;
Field[]fields1 = cls.getDeclaredFields();
for (Field field :
fields1) {
System.out.println(field.getName());
}
}
}
上述代码运行结果为:
studentID
school
achievement
上述两段代码的运行结果就很好的比较了getFileds()与getDeclaredFields()的不同用法。
1.3方法信息
public Method[] getMethods() throws SecurityException//返回所有的public方法,包括父类的
public Method getMethod(String name, Class>... parameterTypes) throws NoSuchMethodException, SecurityException//通过指定名返回方法
public Field[] getDeclaredFields() throws SecurityException//返回所有方法,不包括父类
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException//通过指定名返回方法
class Person{
public void PublicPerson(){ }
void DefaultPerson(){}
protected void ProtectPerson(){}
private void PrivatePerson(){}
}
class student extends Person{
public void PublicStudent(){ }
void DefaultStudent(){}
protected void ProtecStudentt(){}
private void PrivateStudent(){}
}
public class Test {
private static final int _1MB = 1024*1024;
public static void main(String[] args) {
Class cls = student.class;
Method[] methods = cls.getMethods();
for (Method method :
methods) {
System.out.println(method.getName());
}
System.out.println("-------------");
Method[]methods1 = cls.getDeclaredMethods();
for (Method method :
methods1) {
System.out.println(method.getName());
}
}
}
上述代码的运行结果很好的比较了getMethods()与getDeclaredMethods();
1.4创建对象和构造方法
Class类中还有一个方法,可以创造实例化对象
public T newInstance() throws InstantiationException, IllegalAccessException
该方法会调用类的默认构造方法,如果该类没有该构造方法,会抛出异常。
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
private static final int _1MB = 1024*1024;
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class cls = Person.class;
Person person = cls.newInstance();
System.out.println(person);
}
}
那当没有默认的构造方法时,但又想通过反射获取实例化对象,那么就需要通过反射获取构造方法,再通过构造方法实例化对象。
所以Class类中获取构造方法的方法:
public Constructor>[] getConstructors() throws SecurityException//返回所有public构造方法
public Constructor getConstructor(Class>... parameterTypes) throws NoSuchMethodException, SecurityException//获取指定参数的public构造方法
public Constructor>[] getDeclaredConstructors() throws SecurityException//返回所有构造方法
public Constructor getDeclaredConstructor(Class>... parameterTypes) throws NoSuchMethodException, SecurityException//通过指定参数返回构造方法
有了构造方法可以通过构造方法实例化对象:
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Constructor stringConstructor = Person.class.getConstructor(new Class[]{String.class,int.class});
Person person = stringConstructor.newInstance("张三",23);
System.out.println(person);
}
}
运行结果:Person{name='张三', age=23}
就这样通过构造方法实例化了对象。
class Application {
public static String toString(Object obj) {
try {
Class> cls = obj.getClass();
StringBuilder sb = new StringBuilder();
sb.append(cls.getName() + "\n");
for (Field f : cls.getDeclaredFields()) {
if (!f.isAccessible()) //如果对象不可以访问
{
f.setAccessible(true);//表示忽略Java的访问检查机制,以允许读写非public的字段
}
sb.append(f.getName() + "=" + f.get(obj).toString() + "\n");//获取指定对象中该字段的值
}
return sb.toString();
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static void setFieldValue(Field f, Object obj, String value) throws Exception {
Class> type = f.getType();
if (type == int.class) {
f.setInt(obj, Integer.parseInt(value));
} else if (type == byte.class) {
f.setByte(obj, Byte.parseByte(value));
} else if (type == short.class) {
f.setShort(obj, Short.parseShort(value));
} else if (type == long.class) {
f.setLong(obj, Long.parseLong(value));
} else if (type == float.class) {
f.setFloat(obj, Float.parseFloat(value));
} else if (type == double.class) {
f.setDouble(obj, Double.parseDouble(value));
} else if (type == char.class) {
f.setChar(obj, value.charAt(0));
} else if (type == boolean.class) {
f.setBoolean(obj, Boolean.parseBoolean(value));
} else if (type == String.class) {
f.set(obj, value);
} else {
Constructor> ctor = type.getConstructor(new Class[] { String.class });
f.set(obj, ctor.newInstance(value));
}
}
public static Object fromString(String str) {
try {
String[] lines = str.split("\n");
if (lines.length < 1) {
throw new IllegalArgumentException(str);
}
Class> cls = Class.forName(lines[0]);
Object obj = cls.newInstance();
if (lines.length > 1) {
for (int i = 1; i < lines.length; i++) {
String[] fv = lines[i].split("=");
if (fv.length != 2) {
throw new IllegalArgumentException(lines[i]);
}
Field f = cls.getDeclaredField(fv[0]);
if(!f.isAccessible()){
f.setAccessible(true);
}
setFieldValue(f, obj, fv[1]);
}
}
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class simpleMapperDemo {
static class Student {
String name;
int age;
Double score;
public Student() {
}
public Student(String name, int age, Double score) {
super();
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
}
public static void main(String[] args) {
Student zhangsan = new Student("张三", 18, 89d);
String str = Application.toString(zhangsan);
Student zhangsan2 = (Student) Application.fromString(str);
System.out.println(zhangsan2);
}
}
//上述代码节选自 Java编程逻辑
上面的代码就是将一个对象变成字符串,再通过字符串创建一个对象新对象。
核心代码就是toString()与fromString()两个方法。
2.类加载机制
我们都知道类的信息都在Class文件中描述,但是,最终需要加载到虚拟机中才可以使用。
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以形成被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
字节码文件加载到内存中创建Class对象就需要ClassLoader来加载其他类。
Java运行时,会根据类的完全限定名寻找并加载类,寻找的方式就是在系统类和指定类路径中寻找,如果是class文件的根目录,则直接查看是否有对应的子目录以及文件。
2.1 类加载器
从Java虚拟机的角度来讲,类加载器一般就分为两种,一种是启动类加载器;另一种就是其它类加载器。
但是其实还可以划分的更细,一般程序运转时基本会用到三个类加载器;
1.启动类加载器(Bootstrap ClassLoader):这个加载器是Java虚拟机的一部分,是由C++实现的,它负责加载Java的基础类,主要是
2.扩展类加载器(Extension ClassLoader):这个加载器的实现类是sun.misc.Launcher$ExtClassLoader,他会负责加载Java的一些扩展类,一般是
3.应用程序类加载器(Application ClassLoader):这个加载器的实现类是sun.misc.Launcher$AppClassLoader,它负责加载应用程序的类,包括自己写的和引入的第三方类库,即所有在类路径中指定的类。
这三个类加载器是父子委派关系,Application ClassLoader的父亲是Extension ClassLoader;Extension ClassLoader的父亲是Bootstrap ClassLoader。
2.2双亲委派模型
类加载过程:
1.判断是否已经加载过了,加载过了,直接返回Class对象,一个类只会被ClassLoader加载一次;
2.没有加载过,先让父ClassLoader去加载,加载成功,就返回得到的Class对象;
3.父类加载器没有加载成功时,自己尝试加载类。
上述过程就是一个双亲委派模型。
利用双亲委派模型就可以避免Java类库被覆盖的问题。
比如:自己定义了一个ArrayList,通过双亲委派模型,就只会被启动类加载器加载,避免自己定义的类覆盖Java库类。
ClassLoader是一个抽象类,上述说过Application ClassLoader以及 Extension ClassLoader都实现类;
首先Class类下面有一个方法获取实际加载它的ClassLoader:
public ClassLoader getClassLoader()
ClassLoader有一个方法,可以获取它的父ClassLoader:
public final ClassLoader getParent()
因为BootStrap ClassLoader是C++写的,没有实现类,所以返回为null;
public class Test {
public static void main(String[] args) {
Class>cls = Test.class;
System.out.println(cls.getClassLoader());
System.out.println(cls.getClassLoader().getParent());
System.out.println(cls.getClassLoader().getParent().getParent());
}
}
结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4554617c
null
ClassLoader有一个静态方法,可以获取默认的系统类加载器;
public static ClassLoader getSystemClassLoader()
ClassLoader中加载类的方法;
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
需要说明的是loadClass()不会执行类的代码块;
public class Test {
public static class Hello{
static {
System.out.println("Hello World");
}
}
public static void main(String[] args) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
try {
Class> cls = cl.loadClass("Test$Hello");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
结果并没有执行静态代码块中的内容。
public class Test {
public static class Hello{
static {
System.out.println("Hello World");
}
}
public static void main(String[] args) {
try {
Class> cls = Class.forName("Test$Hello");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
但是使用forName加载类的时候,就会执行。
好了,敲代码了