Java为了便于管理class文件,把加载进内存中的,class字节码文件最为一个特殊的类的实例。这个特殊的类就是Class类。
Java语言为了便于我们了解这些实例的内部构造,获取他们的内部方法与内部字段,专门提供了几个方法来进行处理。这极大的方便了人们对以字节码文件的处理。
下面是获取Class实例的三种方法:
class Test
{
public static void main(String[] args) throws Exception
{
String str1 = "abc";
//当只有类的实例对象时,可以调用对象的getClass方法
Class cls1 = str1.getClass();
System.out.println(cls1.getName());
//当有类名时,可以直接调用 类名.class获取字节码文件
Class cls2 = String.class;
System.out.println(cls1.getName());
//当有类的全名时,可以调用Class类的forName方法,全类名作为参数传入
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1.getName());
}
}
Java中有9中预定义的Class对象,表示八个基本类型和 void。这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,即
boolean、byte、char、short、int、long、float和double。
通过反射机制可以得到三个主要的类:
(1)Constructor类
通过使用getConstructor(parameterTypes)得到Class对象类中的构造函数,如果想得到空参数的构造函数,方法的参数使用null。
方法的参数是可变参数的所以要得到多参数的构造方法,就在参数中加入多个参数,并用逗号隔开就可以了。
Constructor constructor1 = cls1.getConstructor(null);
Constructor constructor2 = cls1.getConstructor(char[].class);
class Test
{
public static void main(String[] args) throws Exception
{
String str1 = "abc";
Class cls1 = str1.getClass();
System.out.println(cls1.getName());
Constructor constructor1 = cls1.getConstructor(null);
Constructor constructor2 = cls1.getConstructor(StringBuffer.class);
//从构造方法对象创建新的String实例
String str2 = (String)constructor2.newInstance(new StringBuffer("abcdefgh"));
System.out.println(str2);
}
}
(2)Field类
Field类的实例代表类中的字段,通过使用getField(String name)来获取公共的字段,要获取访问受限的私有字段,可以使用方法
getDeclaredField(String name)来获取。
由于得到的字段的访问权限还是私有的,需要调用setAccessible(boolean flag)方法来设置字段的访问权限,俗称暴力破解。
import java.lang.reflect.Field;
class Test
{
public static void main(String[] args) throws Exception
{
Person person = new Person("lisir",20);
Field name = person.getClass().getDeclaredField("name");
name.setAccessible(true);
System.out.println("name: " + name.get(person));
Field age = person.getClass().getDeclaredField("age");
age.setAccessible(true);
System.out.println("age: " + age.get(person));
}
}
class Person
{
public String name = null;
private int age = 0;
Person(String name,int age){
this.age = age;
this.name = name;
}
}
(3)Method类
Method类的实例代表类中的方法,通过使用getMethod(String name)来获取公共的方法,要获取访问受限的私有方法,可以使用方法
getDeclaredMethod(String name)来获取。
这和Field类很相似,由于得到的方法的访问权限还是私有的,需要调用setAccessible(boolean flag)方法来设置方法的访问权限。
如果需要调用此方法,还需要调用invoke(Object obj,Object... args)方法来执行方法对象。
class Test
{
public static void main(String[] args) throws Exception
{
Person person = new Person("lisir",20);
Method getAge = person.getClass().getDeclaredMethod("getAge");
getAge.setAccessible(true); //设置访问权限为public
//其中要调用得到的方法对象需要调用invoke方法
System.out.println("getAge: " + getAge.invoke(person, null));
}
}
class Person
{
public String name = null;
private int age = 0;
Person(String name,int age){
this.age = age;
this.name = name;
}
private int getAge() {
return age;
}
}
反射的应用
由于程序的运行需求是不可知的,通过反射可以实现这样的需求:
将一个对象存入Collection类型的容器中,但不知道运行要使用的Collection的那个子类容器。这样就可以写一个配置文件,运行时只需要修改配置文件就可以了。
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
public class ReflectTest2 {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
Properties props = new Properties();
props.load(ips);
ips.close();
//一定要使用完整的运算出来的完整路径
String className = props.getProperty("className");
Collection collections = (Collection) Class.forName(className).newInstance();
//Collection collections = new HashSet();
ReflectPoint pt1 = new ReflectPoint(3, 3);
ReflectPoint pt2 = new ReflectPoint(5, 5);
ReflectPoint pt3 = new ReflectPoint(3, 3);
String s1 = new String("I am a string");
String s2 = new String("hello");
String s3 = new String("world");
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
//改变参与hasCode()运算的值将会造成内存泄露,即pt1删除不了,
//这是由于hasCode算法导致存储元素是分区的,找不到元素本来应该存在的区域内,就无法删除
//pt1.y = 7;
//collections.remove(pt1);
System.out.println(collections.size());
}
}
配置文件为config.properties文件:
内容为:
className=java.util.HashSet
java文件通过将config.properties配置文件调入,并使用getProperty方法来获取键className的值java.util.HashSet。
再通过反射调取Class.forName()方法将HashSet类字节码文件载入。这样就实现了将元素存入HashSet集合中。想将元素存入ArrayList集合,就只需要将配置文件改为className=java.util.ArrayList即可。