当程序第一次使用某一个java.util.Date类的时候,就会把该类的字节码对象加载进JVM,并创建出一个class对象,此时class对象就表示java.util.Date的字节码。
Class类可以表示N个类的字节码对象,问题是怎么区分Class类此时表示的哪一个类的字节码呢?Class类的设计者提供了泛型Class<T>
java.lang.String类的字节码类型:class<java.lang.String>
java.util.Date类的字节码类型:class<java.util.Date>
java.util.ArrayList类的字节码类型:class<java.util.ArrayList>
一. 那么如何创建Class对象呢?下面我们以获取java.util.Date类的字节码对象为例:
方式一:使用class属性
Class<java.util.Date> cz1 = java.util.Date.class;
方式二:通过对象的getclass方法来获取,getclass是Object类中的方法
java.util.Date date = new java.util.Date();
Class<java.util.date> cz2 = date.getclass();
方式三:通过Class类中的静态方法forName(String className)
Class<?> cz3 = Class.forname("java.util.Date");
注意:同一个类在JVM中只存在一份字节码对象,也就说上述cz1 = cz2 = cz3;
以后使用最多的是第三种,在框架中大量使用。
问题来了:在上述讲了三种获取Class对象的方式,基本数据类型不能表示为对象,也就不能使用getclass方式来获取。基本类型也没有类名的概念,所以也不能使用Class.forName方法,那么如何表示基本类型的字节码对象呢?
所有的数据类型都有class属性
class clz = 数据类型.class
接下来我们将讲下九大内置Class实例:JVM中预先提供好的Class实例分别为:byte,short,int,long,float,double,boolea,char,void
表示:byte.class,short.class,int.class...void.class
在8大基本数据类型的包装类中,都有一个常量:TYPE,用于返回该包装类对应基本类的字节码对象。
Sysout.out.println(Integer.TYPE == int.class); //true
注意:Integer和int是不同的数据类型
System.out.println(Integer.class == int.class); //false
数组的Class实例:数组是引用数据类型,数组其实本身就是对象,那么如何来表示数组的Class实例
方式1:数组类型.class
方式2:数组对象.getclass();
注意:所有的具有相同的维数和相同元素类型的数组共享同一份字节码对象,和元素没有关系
Class:描述所有的类型,所以Class类中应该具有所有类型的相同方法
Object:描述所有的对象,所以在Object类中应该具有所有对象的共同的方法
二. 从该字节码对象中去找需要获取的构造器
Class类获取构造器方法:Constructor类表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
public Constructor<?>[] getConstructors();该方法只能获取当前Class所表示类的public修饰的构造器
public Constructor<?>[] getDeclaredConstructors();获取当前Class所表示类的所有构造器,和访问权限无关
public Constructor<T> getConstructor(Class<?>... parameterTypes);获取类中当前Class所表示类中指定的一个public构造器
public Constructor<?>[] getDeclaredConstructor(Class<?>... parameterTypes);获取当前Class所表示类中指定的一个构造器
参数: parameterTypes表示构造器参数的Class类型
例子:
class User{
public User() {
}
public User(String name){
}
private User(String name, int age){
}
}
//获取指定的一个构造器
private static void getone() throws Exception{
//1.获取构造器所在类的字节码对象
Class<user> u1 = User.class;
//2. 获取u1对象中所有的构造器
//需求1:获取public User()
Constructor<User> con = u1.getConstructor();
//需求2:获取public User(String name)
con = u1.getDeclaredConstructor(String.class);
//需求3:获取private User(String name, int age)
con = u1.getDeclaredConstructor(String.class,int.class);
}
//获取所有的构造器
private static void getAll(){
//1.获取构造器所在类的字节码对象
Class<user> u1 = User.class;
//2.获取u1对象中所有的构造器Constructor<?>[] cs = u1.getConstructors();
for(Constructor<?> c : cs) {
System.out.println(c);
}
}
三. 使用反射调用构造器创建对象
构造器最大的作用就是创建对象。为什么要使用反射创建对象呢,为什么不直接使用new呢?
因为在框架中,提供给我们的都是字符串。
Constructor<T>类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
常用方法:
public T newInstance(Object... initargs);如调用带参数的构造器,只能使用该方式。
参数:initargs表示调用构造器的实际参数
返回创建的实例,T表示Class所表示类的类型
注意:如果一个类中的构造器是外界可以直接访问的,同时没有参数,那么可以直接使用class类中的newInstance方法创建对象。
public Object newInstance();相当于new 类名();
例子:
class Person{
private Person() {
}
public Person(String name){
}
private Person(String name, int age){
}
}
Class <Person> c1 = Person.class;
//调用public Person()
Constructor<Person> con = c1.getConstructor();
//调用构造器的newInstance方法来创建对象,并传入实参
con.newInstance();
//调用public Person(String name)
con = c1.getConstructor(String.class);
con.newInstance("龙");
//调用private Person(String name, int age)
con = c1.getDeclaredConstructor(String.class, int.class);
//设置当前构造器可以访问的
con.setAccessible(true);
con.newInstance("Will", 17);
注意:访问私有成员必须先设置可访问的对象setAccessible(true);
四. 获取类中的方法
1.获取方法所在类的字节码对象
2.获取方法
class类中常用方法:
public Method[] getMethods();获取包括自身和继承过来的所有的public方法
public Method[] getDeclaredMethods();获取自身类中所有的方法(不包括继承的,和访问权限无关)
参数: methodName:表示被调用方法的名字
parameterTypes:表示被调用方法的参数的Class类型如:String.class
public Method geDeclaredtMethod(String methodName,Class<?>... parameterTypes);表示调用指定的一个本类中的方法(不包括继承)
例子:
Class user{
public void dowork(){}
public static void dowork(String name){}
private String sayHello(String name, int age){}
}
//获取user类中指定的一个方法
private static void getone() throws Exception{
Class c1 = User.class;
//需求:获取dowork()
Mthod m = c1.getMethod("dowork");
//需求:获取dowork(String name)
m = c1.getMethod("dowork", String.class);
//需求:获取private String sayhello(String name, int age)
m = c1.getDeclaredMethod("sayhello", String.class, int.class);
}
//获取User类中所有的方法
private static void getAll(){
class c1 = User.class;
Method[] ms = c1.getMethods();
for(Method c : ms){
System.out.println(c);
}
ms = c1.getDeclaredMethods();
for(Method c : ms){
System.out.println(c);
}
}
五.使用反射调用方法
1)获取方法所在类的字节码对象
2)获取方法对象
3)使用反射调用方法
public Object invoke(Object obj,Object... args):表示调用当前Method所表示的方法
参数:obj:表示被调用方法底层所属对象
args:表示调用方法是传递的实际参数
注意:调用私有方法之前,应该设置该方法为可访问的,又因为Method是AccessibleObject子类,所以Method具有该方法。
例子:
class Person{
public void doWork(){}
public void doWork(String name){}
private void doWork(String name, int age){}
}
public class MethodInvokeDemo{
public static void main(String[] args) throws Exception{
//需求:调用public void doWork()
Class c1 = Person.class;
Method m = c1.getMethod("doWork");
Object ret = m.invoke(c1.newInstance());
//需求:调用public void doWork(String name)
m = c1.getMethod("doWork", String.class);
ret = m.invoke(c1.newInstance(), "Lucy");
//需求:调用private void doWork(String name, int age)
m = c1.getDeclaredMethod("doWork", String.class, int.class);
m.setAccessible(true);
ret = m.invoke(c1.newInstance, "will", 17);
}
}
六.使用反射调用静态方法
静态方法不属于任何对象,静态方法属于类本身
此时把invoke方法的第一个参数设置为null即可
m.invoke(null, "Lucy");
七.使用反射调用数组参数(可变参数)
王道:调用方法的时候把实际参数统统作为Object数组的元素即可。
Method对象.invoke(方法底层所属对象,new Object[]{所有实参});
class Employee{
public static void dowork1(int... arr){}
public static void dowork1(String... arr){}
}
//使用反射调用数组参数(可变参数)
public class MethodInvoke{
public class static main(String[] args) throws Exception{
class c1 = Employee.class;
//情况1:数据的元素类型是基本类型
Method m = c1.getMethod("doWork1", int[].class);
//m.invoke(null, 1,2,3,4,5); //ERROR
m.invoke(null, new int[]{1,2,3,4,5}); //YES
m.invoke(null, new Object[]{new int[]{1,2,3,4,5}});//YES
//情况2:数组的元素类型为引用类型
m = c1.getMethod("doWork1", String[].class);
//m.invoke(null, "a",''b","c"); //ERROR
m.invoke(null, new String[]{"a",''b","c"}); //ERROR
m.invoke(null, new Object[]{new String[]{"a",''b","c"}});//YES
}
}
八.操作反射其他的API
Class类中的
int getModifiers():获得修饰符
String getName():返回类的全限定名
Package getPackage():获得该类的包
String getSimpleName():获得类的简单名字
Class getSuperclass():获得类的父类
boolean isArray():判断该Class实例是否是数组
boolean isEnum() :判断该Class实例是否是枚举
附加问题:
对象有编译类型和运行类型
Object obj = new java.util.Date();
编译类型:Object
运行类型:java.util.Date
需求:通过obj对象,调用java.util.Date类中的toLocalString方法
obj.toLocalString():此时编译报错,编译时,会检查该编译类型中是否有存在toLocalString方法,如果存在,编译成功,否则编译失败解决方案是,因为obj的真实类型是java.util.Date类,所以我们可以把obj对象强制转换为java.util.Date类型
java util.Date d = (java util.Date)obj;
d.toLocalString();//调用成功
如果不知道obj的真实类型,就不能强转(底层有一个方法,返回一个Object类型的java.util.Date对象)
Object obj = new java.util.Date();
Method m = obj.getclass().getMethod("tolocalString");
Object d = m.invoke(obj);