反射
首先要了解什么是“动态类型语言”:类型的检查是在运行时检查的,程序在运行时可以改变程序的结构和类型。常见的语言:javascript、Python、Ruby,而静态性语言是在程序编译的阶段,对程序进行检查。相对的来说有"动态的类型语言",就会有"静态性语言"的存在的,常见的"静态性语言"有 java、c、c++等,这些静态性语言都是在程序编译的时候检查程序。
静态类型的语言的特点:
优点:结构非常的规范,便于调试,类型安全。
缺点:为此需要写更多的类型相关代码,导致不便于阅读、不清晰明了。
动态类型的语言的特点:
优点:方便阅读,不需要写非常多的类型相关的代码。
缺点:自然就是不方便调试,命名不规范时会造成读不懂,不利于理解等。
静态语言的开发效率可由IDE工具提高,而动态语言对程序员个人要求更高。
反射可以更好的帮助我们理解java中所说的"一切皆对象"的论述,正因为java中含有反射的技术,所以可以认为java语言是"准动态性的语言",我们可以利用反射机制、字节码操作获得类似动态语言的特性
从而让编程的时候更加灵活!
反射机制:
原先我们想调用一个类中的方法的时候,那么我们最初的做法是,先创建一个对象,然后使用对象.方法名()来调用方法,当然如果方法是静态的方法的话,那么我们可以使用类名.方法名来调用;我们知道,java源代码经过javac变成.class的字节码,然后被类加载器加载,加载完成后在堆内存的方法区产生一个Class的对象(一个类中对应一个对象),这个对象对应类中的完整的结构,这个对象就像一面镜子,我们通过这个镜子可以看见这个类的完整的结构,这就是反射。
反射是在程序运行的时候,通过获取某个类的Class类的对象,进而可以操作此类的所有的属性和方法。那么问题就转换成如何获得Class的对象。
获取Class的对象的方式:
1.类名.class :前提是知道该类的类名。
2.对象.getClass:前提是知道该类的对象。
3.Class对象.forName("类的全路径 = 包名 + 类名");一般用于加载配置文件,来获取相应的class的对象(比如说好多框架的原理)
4.使用类的加载器:ClassLoader.loadClass("类的全类名");
我们知道类的加载器将字节码的文件加载进来,一个类只对应一个class,那么这个类中的成员也有相对应的class,这也就对应了,万物皆对象的概念,正如 jdkApi所说:
Class
的实例表示正在运行的 Java 应用程序中的类和接口。
枚举是一种类,enum
注释(指的是注解,是给计算机看的)是一种接口。annocation
每个数组([])属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class
对象。
基本的 Java 类型(primitive type)(boolean
、byte
、char
、short
、int
、long
、float
和 double
)和关键字 void
也表示为 Class
对象。
Class
没有公共构造方法。
Class
对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass
方法自动构造的。
那么可以将各个类型 的Class的对象,打印一下,看看都是什么,代码如下:
基本数据类型的Class的对象仍然是他们本身(void的class对象也是他本身):
public static void main(String[] args) { //基本数据类型: Class bytes = byte.class; System.out.println(bytes); Class shorts = short.class; System.out.println(shorts); Class ints = int.class; System.out.println(ints); Class longs = long.class; System.out.println(longs); Class floats = float.class; System.out.println(floats); Class doubles = double.class; System.out.println(doubles); Class bolleans = boolean.class; System.out.println(bolleans); Class chars = char.class; System.out.println(chars); }
结果:
数组:只要是数据类型相同,维度相同,那么他们是存在同一个数据的class中的。
class中对应的情况:
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造方法
Class.getPackage()获取包
getName():全限定名
动态的构造对象分为两种情况:
1.含有无参的构造函数:可以直接使用Class的对象来调用newInstance();创建对象。
/* * 动态的获取对象的属性: */ @Test public void getObj() throws Exception { //1.构造方法非私有的。 Class clazz = Student.class; Object instance = clazz.newInstance(); System.out.println(instance);//Student [name=null, age=0] //2.给属性赋值: //获取某个属性在字节码文件 中Field类型的对象 Field name = clazz.getDeclaredField("name"); //由于属性被私有化了,那么需要打开访问的权限才可以访问 name.setAccessible(true); //设置属性name的值为"张三" name.set(instance, "张三"); System.out.println(name.get(instance));//获取哪个对象的实例 //3.设置id的值:与上面的设置属性是同理的。 Field fieldId = clazz.getDeclaredField("age"); fieldId.setAccessible(true); fieldId.set(instance, 1); System.out.println(fieldId.get(instance)); }
实体类:
package com.atguigu.reflect.bean; public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } /** * 获取学生的信息 * @return */ public String getInfo() { return "名称:" + name + ",年龄:" + age; } }
2.如果没有无参数的构造函数:就不能直接使用字节码的对象来调用newInstance();来直接创建对象;可以创建Constructor的对象,然后再去newInstance()
/* * 动态的获取成员的方法。 * 在没有无参数的构造函数 */ @Test public void getObj_3() throws Exception { //1.同样也是先获取该类的Class的对象 Class clazz = Student.class; //2.实例化相应的对象(要求对象有默认的无参构造) //Object instance = clazz.newInstance(); //如果没有无参数构造函数的话,那么:需要使用有参构造 Constructor constructor = clazz.getConstructor(String.class,int.class); Object instance = constructor.newInstance("张三丰",12); //3.使用Class的对象去获取相应的Method //想要确定一个方法:1.方法名;2.参数列表 Method method = clazz.getDeclaredMethod("getInfo"); //方法被某个对象调用。 Object object = method.invoke(instance); //因为设计者并不知道,具体的什么方法会被执行,所以设置的是任意的类型, //所以想使用具体的那个方法的话,那么只有是将其向下转型处理 String s = (String) object; System.out.println(s); }
实体类:
package com.atguigu.reflect.bean; public class Student { private String name; private int age; /*public Student() { }*/ public Student(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } /** * 获取学生的信息 * @return */ public String getInfo() { return "名称:" + name + ",年龄:" + age; } }
通过反射来获取泛型
Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有和泛型有关的类型全部擦除。所以原先直接获取不到泛型信息。为了通过反射操作这些类型以迎合实际开发的需要,Java5.0就新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class中的类型但是又和原始类型齐名的类型。
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型,比如:T[],ArrayList<T>[]
-
TypeVariable:是各种类型变量的公共父接口。比如:T,E
- Type[] getBounds()可以继续获取上限,如果类型变量指定了上限例如T extends Person就返回上限Person,如果未指定就是Object
-
ParameterizedType:表示一种参数化的类型,比如:Comparable<Cat>,ArrayList<?>,ArrayList<? extends/super T/Cat>
-
Type[] getActualTypeArguments()返回表示此类型实际类型参数的 Type 对象的数组。
WildcardType:代表一种通配符类型表达式,(Wildcard通配符)比如?,? extends/super Person/T
Type[] getUpperBounds()上限,如果有的话,没有就默认是Object
Type[] getLowerBounds()下限,如果有的话,没有就返回长度为0的数组
泛型类和泛型接口:
Type getGenericSuperclass():如果父类有泛型,那么返回ParameterizedType参数化类型,即extends后面的父类写法
例如:com.reflection.type.DAOImpl<T, P>保留
com.reflection.type.DAOImpl<User, Integer>
com.reflection.type.DAOImpl 擦除
如果此 Class 表示 Object 类、接口、基本类型或 void,则返回 null。如果此对象表示一个数组类,则返回表示 Object 类的 Class 对象。
(2)Type[] getGenericInterfaces():同上
(3)TypeVariable[] clazz.getTypeParameters():返回本Class对象对应类型的泛型的类型变量
例如:ArrayList类的E,如果有E extends 父类,可以通过TypeVariable的getBounds()方法得到上限。如果没有指定上限,默认上限是Object
通过反射读取注解配置参数的值:
package com.atguigu.reflect.annocation; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import org.junit.Test; /** * 所用的知识: a:自定义注解 b:使用反射读取注解中的值 * * @author DHB * */ public class AnnocationTest { public static void main(String[] args) { } /** * 获取方法上的注解 * @throws InstantiationException * @throws IllegalAccessException * @throws SecurityException * @throws NoSuchMethodException */ @Test public void test_2() throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException { // 获取一个类上的注解: Class clazz = UseAnnocation.class; Method method = clazz.getMethod("test"); // 获取相应的注解的对象: Annotation annotation = method.getDeclaredAnnotation(MyAnnocation.class); //向下转型:有风险 MyAnnocation my = (MyAnnocation) annotation; System.out.println(my.value()); } /** * 获取类上的注解 * @throws InstantiationException * @throws IllegalAccessException */ @Test public void test() throws InstantiationException, IllegalAccessException { // 获取一个类上的注解: Class clazz = UseAnnocation.class; // 获取相应的注解的对象: Annotation annotation = clazz.getAnnotation(MyAnnocation.class); // 获取的对象的时候,一般需要对其进行向下转型。 MyAnnocation m = (MyAnnocation) annotation; System.out.println(m.value()); } } // 自定义一个注解:定义其作用的位置(类上、方法上) @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) // 指定runtime的生命周期,反射访问的到 @interface MyAnnocation { // 声明配置参数 String value() default "atguigu"; } // 定义一个类去使用注解: @MyAnnocation("生命在于静止....") class UseAnnocation { @MyAnnocation public void test() { System.out.println("hello world!!!"); } }
反射的应用之一:动态的代理。
动态代理的演示将会在后续的码出。