黑马程序员——反射
-------android培训、java培训、期待与您交流! ----------
1.类的加载、连接和初始化
1.1 类的加载:指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,
也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。
1.2 类的连接:当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。
类的连接可分为以下3个阶段:
(1)验证:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
(2)准备:负责为类的静态Field分配内存,并设置默认初始值。
(3)解析:将类的二进制数据中的符号引用替换成直接引用。
1.3 类的初始化:在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态Field进行初始化。在Java类中对静态Field指定初始值有两种方式:
(1)声明静态Field时指定初始值。
(2)使用静态初始化块为静态Field指定初始值。
JVM初始化一个类包含如下几个步骤:
(1)假如这个类还没有被加载和连接,则程序先加载并连接该类。
(2)假如该类的直接父类还没有被初始化,则先初始化其直接父类。
(3)假如类中有初始化语句,则系统依次执行这些初始化语句。
当程序调用任何一个类时,都会保证该类以及所有父类都会被初始化。
类初始化的时机:
(1)创建类的实例。为某个类创建实例的方式包括:使用new关键字创建实例、通过反射来创建实例。
(2)调用某个类的静态方法。
(3)访问某个类或接口的静态Field,或为该静态Field赋值。
(4)使用反射方式来强制创建某个类或接口对应的Class对象。
(5)初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。
(6)直接使用java.exe命令来运行某个主类,程序会先初始化该主类。
2. Class类
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。
Class类的实例对象代表内存中的一份字节码,每一个字节码文件都是Class的一个实例对象。
2.1 获取Class对象的方法。
Class类是没有构造方法的,获得Class对象的方法有以下三种:
(1) 类名.class:调用某个类的class属性来获取给类对应的Class对象。
例如:Person.class将会返回Person类对应的Class对象。
(2) 对象.getClass( ):调用某个对象的getClass( )方法。该方法是java.lang.Object类中的方法,所以所有的Java对象都可以调用该方法,该方法将返回该对象所属类对应的Class对象。
例如:new Date().getClass();
(3) Class.forName(“类名”):使用Class类的forName( )静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全名。
例如:Classcls=Class.forName(java.lang.String);
注意:Class.forName(“类名”)的作用是返回字节码,其返回的方式有两种:
I.当这份字节码曾经被加载过,已经存在在JVM虚拟机中的话,直接返回即可。
II.当JVM中还没有这份字节码时,则使用类加载器进行加载,把加载进来的字节码缓存进JVM,以后要得到这份字节码就无需加载了。
2.2 九个预定义Class实例对象
九个预定义Class实例对象是指由Java虚拟机创建,与其表示的基本类型同名。
包括8个基本数据类型:boolean、byte、char、short、int、long、float、double
以及void关键字。
注意:isPrimitive( ):判断字节码是否是预定义的原始的字节码。
代码:
public class ReflectDemo {
public static void main(String[] args) {
try {
String str="abc";
//通过三种方法来获取str对应的Class对象
Class cls1=str.getClass();
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
//包装类的TYPE属性代表的是所包装的基本数据类型
System.out.println(int.class==Integer.TYPE);//true
//判断数组是否是预定义类型
System.out.println(int[].class.isPrimitive());//false
//判断类型是否是数组
System.out.println(int[].class.isArray());//true
} catch (ClassNotFoundException e) {
throw new RuntimeException("类名错误!");
}
}
}
总之:只要是在源程序中出现的类型,都有各自的Class实例对象。
3.反射
反射就是把Java类中的各种成分映射成相应的Java类。
例如:一个Java类中用一个Class类的对象来表示一个类中的组成部分。成员变量、方法、构造方法、包等信息也用一个个的Java类来表示。
表示Java类的Class类显然要提供一系列的方法来获得其中的变量、方法、构造方法、修饰符、包等信息。
这些信息就是用相应类的实例对象来表示,它们是Field、Method、 Constructor、Package等等。
3.1 Constructor类:代表某个类中的一个构造方法。
3.1.1 获取构造方法
(1)得到某个类所有的构造方法:Class.getConstructors( );
例如:获取String类的所有的构造方法
Constructor[] cons=Class.forName(“java.lang.String”).getConstructors();
(2)得到某一个构造方法:Class.getConstructor(Class<?>…参数);
例如:获取String类的参数类型为字节数组的构造方法
Constructorcon=String.class.getConstructor(byte[].class);
3.1.2 创建对象
通过反射创建对象有如下两种方法:
I. 使用Class对象的newInstance( )方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类型有默认构造方法,而执行newInstance( )方法时实际上是利 用默认构造方法来创建该类型的实例。
II. 先使用Class对象获取指定的Constructor对象,在调用Constructor对象的newInstance( )方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造方法来创建实例。为了使用指定的构造器来创建Java对象,需要以下3个步骤:
A.获取该类的Class对象。
B.利用Class对象的getConstructor()方法来获取指定的构造器。
C.调用Constructor的newInstance( )方法来创建Java对象。
代码实例:
import java.lang.reflect.Constructor;
public class ConstructorDemo {
public static void main(String[] args) {
try {
//获取String对应的Class对象
Class cla=Class.forName("java.lang.String");
//使用Class对象的方法来创建对象
String str1=(String)cla.newInstance();
//获取Constructor对象来创建对象
Constructor con=cla.getConstructor();
String str2=(String)con.newInstance();
//获取指定参数类型的Constructor来创建对象
Constructor con2=cla.getConstructor(char[].class);
String str3=(String)con2.newInstance(new char[]{'a','b','c'});
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3.2 Field类:代表某个类中的一个成员变量。Field对象是对应到类上面的成员变量,而要得到此类某一对象的Field值,则需将Field对象具体映射到某一个对象上。
通过Class对象的getFields( )或getField( )方法可以获取该类所包括的全部的Field或指定Field。Field提供了以下两组方法来获取或设置Field值。
A.get(Object obj):获取obj对象该Field的属性值。
B.set(Object obj,val):将obj对象的该Field设置成val值。
代码示例:
import java.lang.reflect.Field;
//通过反射来为设置对象的字段值
public class FieldDemo {
public static void main(String[] args) {
try {
//创建一个Person对象
Person p=new Person();
//获取Person类对应的Class对象
Class<Person> personClass=Person.class;
//获取Person的名为name的Field
//使用getDeclaredField方法表明可获得各种访问控制符的field
Field nameField=personClass.getDeclaredField("name");
//设置通过反射访问该Field时取消访问权限检查
nameField.setAccessible(true);
//通过set方法为p对象的name字段设置值
nameField.set(p, "张三");
//为age字段设置值
Field ageField=personClass.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(p, 20);
System.out.println(p);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
class Person
{
private String name;
private int age;
public String toString()
{
return "name:"+name+",age:"+age;
}
}
3.3 Method类:代表某个类中的一个成员方法。当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定方法,这两个方法的返回值是Method数组或者Method对象。每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用它invoke方法。
代码示例:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodDemo {
public static void main(String[] args) {
try {
//创建字符串对象
String str="0123456789";
//获取String类的Class对象
Class cla=String.class;
//通过Class对象来获取charAt()
Method method=cla.getMethod("charAt", int.class);
//调用charAt()
System.out.println(method.invoke(str, 3));
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
注意:(1)如果传递给Method对象的invoke方法的第一个参数为null时,说明Method对象对应的是一个静态方法。
(2)当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用 Method对象的setAccessible(boolean flag)方法将accessible值设置为true,指示该Method在使用时应取消Java语言的访问权限检查。
(3)jdk1.5与jdk1.4的invoke方法的区别:
Jdk1.5:public Objectinvoke(Object obj,Object…args)。
Jdk1.4:public Objectinvoke(Object obj,Object[] args)。
即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法。数组中的每个元素分别对应被调用方法中的一个参数。
3.4 数组的反射
通过java.lang.reflect包下的Array类来创建数组,操作数组中的元素。
代码示例:
import java.lang.reflect.*;
public class ArrayDemo
{
public static void main(String args[])
{
// 创建一个元素类型为String ,长度为10的数组
Object arr = Array.newInstance(String.class, 10);
// 依次为arr数组中index为5、6的元素赋值
Array.set(arr, 1, "黑马");
Array.set(arr, 2, "程序员");
// 依次取出arr数组中index为5、6的元素的值
Object obj1 = Array.get(arr , 1);
Object obj2 = Array.get(arr , 2);
// 输出arr数组中index为5、6的元素
System.out.println(obj1);
System.out.println(obj2);
}
}
注意:
A.具有相同维数和元素类型的数组属于同一个类型,即它们具有相同的Class实例对象。
B. 代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class。
C. 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。
代码示例:
public class ArrayDemo2 {
public static void main(String[] args) {
//建立一维数组
int[] arr1=new int[3];
int[] arr2=new int[5];
//建立二维数组
int[][] arr3=new int[1][3];
//建立字符串数组
String[] arr4=new String[3];
//具有相同维数和元素类型的数组属于同一个类型,即它们具有相同的Class实例对象。
System.out.println(arr1.getClass()==arr2.getClass());//true
//代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class。
System.out.println(arr1.getClass().getSuperclass().getName());//java.lang.Object
System.out.println(arr2.getClass().getSuperclass().getName());//java.lang.Object
System.out.println(arr3.getClass().getSuperclass().getName());//java.lang.Object
System.out.println(arr4.getClass().getSuperclass().getName());//java.lang.Object
//基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;
//非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。
Object obj1=arr1;
Object obj2=arr3;
Object obj4=arr4;
Object[] arrobj3=arr3;
Object[] arrobj4=arr4;
}
}
4. 反射和泛型
从JDK1.5以后,Java的Class类增加了泛型功能,从而允许使用泛型来限制Class类。例如,String.class的类型实际上是Class<String>。如果Class对应的类暂时未知,则使用Class<?>。通过反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换。
代码示例:
import java.sql.Date;
public class reflectType
{
public static void main(String[] args)
{
//获取实例后无需类型转换
Date date=reflectType.getInstance(Date.class);
}
public static <T> T getInstance(Class<T> cls)
{
try {
return cls.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
}
使用反射来获取泛型信息:
代码示例:
import java.util.*;
import java.lang.reflect.*;
public class reflectType2
{
private Map<String , Integer> score;
public static void main(String[] args)
{
//获取本类的Class对象
Class<reflectType2> clazz = reflectType2.class;
Field f;
try {
f = clazz.getDeclaredField("score");
// 获得Field实例f的泛型类型
Type gType = f.getGenericType();
// 如果gType类型是ParameterizedType对象
if(gType instanceof ParameterizedType)
{
// 强制类型转换
ParameterizedType pType = (ParameterizedType)gType;
// 获取原始类型
Type rType = pType.getRawType();
System.out.println("原始类型是:" + rType);
// 取得泛型类型的泛型参数
Type[] tArgs = pType.getActualTypeArguments();
System.out.println("泛型类型是:");
for (int i = 0; i < tArgs.length; i++)
{
System.out.println("第" + i + "个泛型类型是:" + tArgs[i]);
}
}
else
{
System.out.println("获取泛型类型出错!");
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
}
-------android培训、java培训、期待与您交流! ----------