一.概念
Reflection(反射)是被称为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
1.为啥叫反射?
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(并且一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
我们可以看到正好是相反的。
2.动态语言和静态语言
二.Java反射机制能干啥
(1)在运行时
1.判断任意一个对象所属的类
2.判断任意一个类所具有的成员变量和方法
3.构造任意一个类的对象
4.调用任意一个对象的成员变量和方法
5.获取泛型信息
6.处理注解
而且在某个类的外部,不可以通过这个类的对象调用其内部私有结构,但是通过反射是可以调用这个类的私有的结构的。私有的构造器、方法、属性都可以。
(2)同时,还可以生成动态代理(后面更新)
三.反射相关的主要API
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法:
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
四.疑问
1.通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用哪个?
建议用直接new的方式。
如果在编译的时候不确定要造哪个类的对象,那就用反射的方式。
2.反射机制与面向对象的封装性的关系(是否矛盾)
封装性是说私有的不要用,反射又告诉我们私有的也可以用。
不矛盾。
封装性解决的是建不建议你调用的问题,反射解决的是你能不能调用私有的东西的问题。封装性告诉你,私有的你最好别调用,因为我的其他公开的方法可能已经把这个私有的方法调用了,而且还增强了它的功能,但是如果你非要用反射去调用,也不是不行,但是用起来可能效果还不如不调用私有的东西。
五.Class类
java.lang.Class
1.Class类的理解
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
换句话说,Class类的实例就对应着一个运行时类。
2.获取Class类的实例的方式
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。这个运行时类只有一个,也可以理解成单例的。
有四种方式(其中第四种理解即可)
//方式一:调用运行时类的属性.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("Person");//这里是全类名,就是包名.类名
System.out.println(clazz3);
//方式四:使用类的加载器:ClassLoader(这个用的少)
ClassLoader classLoader = Main.class.getClassLoader();
Class clazz4 = classLoader.loadClass("Person");
System.out.println(clazz4);
这里输出
class Person
class Person
class Person
class Person
用的最多的,是第三种,即
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("Person");//这里是全类名,就是包名.类名
System.out.println(clazz3);
在这里,我们验证一下,只有一个运行时类对象,我们只是用不同的方法获取到
所以我们打印
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
System.out.println(clazz1 == clazz4);
会发现输出
true
true
true
说明三个对象的内存地址都是一样的,也就是都是同一个对象。
3.Class的拓展
Class不仅可以作为类的对象,还可以有其他类型
哪些类型可以有Class对象???
对于数组,只要数组的元素类型与维度一样,就是同一个Class
六.ClassLoader的理解
1.概念&作用
类加载器作用是用来把类(class)装载进内存的。
2.分类
3.代码测试
public class ClassLoaderTest {
public void test1(){
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//调用系统类加载器的getParent(),获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//调用扩展类加载器的getParent(),无法获取引导类加载器
//引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
}
}
输出
jdk.internal.loader.ClassLoaders$AppClassLoader@2f0e140b
jdk.internal.loader.ClassLoaders$PlatformClassLoader@7ef20235
null
第三个是null,并不是说没有,而是获取不到。
七.newInstance()
1.通过反射,创建运行时类的对象
Class clazz = Person.class;
Object obj = clazz.newInstance();
System.out.println(obj);
控制台打印出
调用了无参构造器
Person{a=0, sex= }
2.解析
newInstance():调用此方法,创建对应的运行时类的对象,内部调用了运行时类的空参构造器。
如果想让此方法正常的创建运行时类的对象,需要
①运行时类必须提供空参的构造器
②空参的构造器的权限要足够,一般设置为public
另外,在javabean中要求提供一个public的空参构造器,原因是
①便于通过反射创建运行时类的对象
②便于子类通过继承此运行时类时,默认调用super()时,保证父类有此构造器。
3.使用newInstance()体会反射的动态性
首先写一个创建对象的方法
//创建一个指定类的对象
//classPath是指定类的全类名
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
System.out.println(Util.getInstance("Person"));
这里就打印出
调用了无参构造器
Person{a=0, sex= }
说明创建了一个Person对象,也就是动态创建的。在编译的时候,是不知道要创建什么对象的,因为你有可能这样写
if(input == 1){
Util.getInstance("类1的全类名");
}else if(input == 2){
Util.getInstance("类2的全类名");
}else if(input == 3){
Util.getInstance("类3的全类名");
}else if(input == 4){
Util.getInstance("类4的全类名");
}
这样的话,你在编译的时候,不知道要创建啥对象,只有运行的时候才知道。这样就体现了反射的动态性。
八.通过反射获取运行时类的结构(了解,没必要掌握)
1.获取类的属性相关
后面用到的Person类
public class Person {
public int number;
String name;
private char sex;
public Person() {
System.out.println("调用了无参构造器");
}
}
(1)getFields()
获取当前运行时类及其父类中声明为public访问权限的属性
Class clazz = Person.class;
//获取属性结构
//getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for(Field field: fields){
System.out.println(field);
}
打印出
public int Person.number
(2)getFields()
获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
//getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for(Field field: declaredFields){
System.out.println(field);
}
打印出
public int Person.number
java.lang.String Person.name
private char Person.sex
(3)分别获取属性的访问权限,属性名以及数据类型
//分别获取权限修饰符,数据类型,变量名
for (Field f: declaredFields){
//1.获取权限修饰符
int modifier = f.getModifiers();
System.out.print(Modifier.toString(modifier) + "|");
//2.获取数据类型
Class type = f.getType();
System.out.print(type.getName() + "|");
//3.获取变量名
String fName = f.getName();
System.out.println(fName);
}
输出(注意:name属性没有写访问权限修饰符,就是默认的,不打印)
public|int|number
|java.lang.String|name
private|char|sex
2.获取类的方法相关
下面要用到的Person类
public class Person {
public int number;
String name;
private char sex;
public Person() {
System.out.println("调用了无参构造器");
}
public void eat(){
System.out.println("吃东西");
}
private void drink(){
System.out.println("喝水");
}
}
(1)getMethods()
获取当前运行时类及其所有父类中声明为public权限的方法
//getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
for (Method m: methods){
System.out.println(m);
}
打印出
public void Person.eat()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
(1)getDeclaredMethods()
获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
//getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method dm: declaredMethods){
System.out.println(dm);
}
打印出
private void Person.drink()
public void Person.eat()
(3)获取方法的具体信息
Class clazz = Person.class;
Method[] methods = clazz.getMethods();
for(Method m:methods){
//获取方法的注解
Annotation[] annos = m.getAnnotations();
for(Annotation a: annos){
System.out.print(a + "|");
}
//获取访问权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + "|");
//获取返回值类型
System.out.print(m.getReturnType().getName() + "|");
//获取方法名
System.out.print(m.getName() + "|");
//获取形参列表
Class[] parameterTypes = m.getParameterTypes();
if(!(parameterTypes == null && parameterTypes.length == 0)){
for (int i = 0;i < parameterTypes.length;i++){
if(i == parameterTypes.length -1){
System.out.print(parameterTypes[i].getName() + i+"|");
break;
}
System.out.print(parameterTypes[i].getName() + i + ",");
}
}
//获取所有的异常
Class[] exceptionTypes = m.getExceptionTypes();
if(exceptionTypes.length > 0){
for (int i = 0;i < exceptionTypes.length;i++){
if(i == exceptionTypes.length -1){
System.out.print(exceptionTypes[i].getName() + i+"|");
break;
}
System.out.print(exceptionTypes[i].getName() + i + ",");
}
}
System.out.println();
}
打印出
public|void|eat|
public final|void|wait|long0,int1|java.lang.InterruptedException0|
public final|void|wait|java.lang.InterruptedException0|
public final native|void|wait|long0|java.lang.InterruptedException0|
public|boolean|equals|java.lang.Object0|
public|java.lang.String|toString|
@jdk.internal.HotSpotIntrinsicCandidate()|public native|int|hashCode|
@jdk.internal.HotSpotIntrinsicCandidate()|public final native|java.lang.Class|getClass|
@jdk.internal.HotSpotIntrinsicCandidate()|public final native|void|notify|
@jdk.internal.HotSpotIntrinsicCandidate()|public final native|void|notifyAll|
(4)获取类的构造器
注意这里没有得到父类的构造器,因为得到父类的构造器没有什么用
Class clazz = Person.class;
//getConstructors():获取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for(Constructor c: constructors){
System.out.println(c);
}
System.out.println();
//getDeclaredConstructors():获取当前运行时类中声明的所有构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor c: declaredConstructors){
System.out.println(c);
}
3.获取类的父类相关
(1)获取运行时类的父类以及泛型相关
注意获取运行时类带泛型的父类的泛型,这个后面可能用到
Class clazz = Person.class;
//获取运行时类的父类
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
//获取运行时类带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
//获取运行时类带泛型的父类的泛型
Type gsc = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) gsc;
//获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
System.out.println(actualTypeArguments[0].getTypeName());
(2)获取运行时类以及其父类实现的接口
Class clazz = Person.class;
//获取运行时类实现的接口
Class[] interfaces = clazz.getInterfaces();
for(Class c:interfaces){
System.out.println(c);
}
System.out.println();
//获取运行时类的父类实现的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for(Class c: interfaces1){
System.out.println(c);
}
(3)获取运行时类声明的注解
//获取运行时类声明的注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annos: annotations){
System.out.println(annos);
}
九(重点)调用运行时类中指定的结构
用到的Person类
public class Person {
int number;
public String name;
private char sex;
private static void show(){
System.out.println("我是一个人");
}
public Person() {
System.out.println("调用了无参构造器");
}
public Person(String what) {
System.out.println(what);
}
String eat(String thing){
System.out.println("吃东西"+thing);
return "hello";
}
private void drink(){
System.out.println("喝水");
}
}
1.操作属性
(1)平常用的不多的方式
Class clazz = Person.class;
//1.因为这里的属性不是静态属性,要依托于对象,所以要先创建对象
Person person = (Person)clazz.newInstance();
//2.获取指定的属性,要求运行时类中属性声明为public
//通常不采用此方法
Field name = clazz.getField("name");
//3.设置当前属性的值
//set():参数1是指明哪个对象的属性。参数2是将此属性值设置为多少
name.set(person,"zhangsan");
//4.得到当前属性的值
//get():参数是哪个对象
Object o = (String)name.get(person);
System.out.println(o);
输出结果
调用了无参构造器
zhangsan
(2)平常用的多的方式
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person)clazz.newInstance();
//getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");
//保证当前属性是可访问的
name.setAccessible(true);
//设置、获取指定对象的属性值
name.set(p,"zhangsan");
System.out.println(name.get(p));
输出结果
调用了无参构造器
zhangsan
2.操作方法
直接讲用的多的
(1)这是调用指定一般方法
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person)clazz.newInstance();
//获取指定的某个方法
//getDeclaredMethod():参数1:指明方法的名称 参数2:指明方法的形参列表
Method eat = clazz.getDeclaredMethod("eat", String.class);
//保证方法是可访问的
eat.setAccessible(true);
//调用方法的invoke,参数1是方法的调用者,参数2是给方法形参赋值的实参
//invoke的返回值就是对应方法的返回值
eat.invoke(p,"东西");
(2)这是调用指定静态方法
Class clazz = Person.class;
Method show = clazz.getDeclaredMethod("show");
//保证方法是可访问的
show.setAccessible(true);
show.invoke(Person.class);
//一样的做法:show.invoke(null);
(3)这是调用指定构造器(用的特别少)
Class clazz = Person.class;
//获取指定的构造器
//getDeclaredConstructor():参数:指明构造器的参数列表
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
//保证此构造器是可以访问的
declaredConstructor.setAccessible(true);
Person person = (Person)declaredConstructor.newInstance("这里是啥");
System.out.println(person);