JAVA类加载机制与反射,通过反射绕过泛型的编译验证
1.类加载机制
当程序使用一个还未被加载到内存的类时,JVM会通过加载、连接、初始化三个步骤将该类进行初始化。类的加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象。
(1).CLASS类
- Class类也是类的一种,只是名字和class关键字高度相似。Java是大小写敏感的语言。
- Class类的对象内容是你创建的类的类型信息,比如你创建一个shapes类,那么,Java会生成一个内容是shapes的Class类的对象
- Class类的对象不能像普通类一样,以 new shapes() 的方式创建,它的对象只能由JVM创建,因为这个类没有public构造函数
-
Class类的作用是运行时提供或获得某个对象的类型信息,和C++中的typeid()函数类似。这些信息也可用于反射。
获得一个Class类对象
第一种办法,Class类的forName函数注意,这里forName(arg)里面传入的是类的全称
public class shapes{}
Class obj= Class.forName("shapes");
第二种办法,使用对象的getClass()函数
public class shapes{}
shapes s1=new shapes();
Class obj=s1.getClass();
Class obj1=s1.getSuperclass();//这个函数作用是获取shapes类的父类的类型
第三种办法,使用类字面常量注意,使用这种办法生成Class类对象时,不会使JVM自动加载该类(如String类)。而其他办法会使得JVM初始化该类。
Class obj=String.class;
Class obj1=int.class;
(2)静态加载类
静态加载类导致的弊端是,假如一个类里面需要加载多个类,其中某一个类有问题,或者不存在,则在程序编译的过程中,会导致编译报错;尤其是在一个主干上加载一些功能型模块,我们一般希望某个功能出问题不要影响其他功能的实现,这就需要引入动态加载类的概念
public class StaticLoadingClass {
public static void main(String[] args) {
//new 创建对象 是静态加载类,在编译时刻就需要加载所有可能使用到的类
String[] args1=new String[]{"QQ","WEIXIN"};
if("QQ".equals(args1[0])){
QQ qq =new QQ(); //<------假设只定义了QQ这个类,这句话会正确编译
qq.begin();
}
if("WEIXIN".equals(args1[1])){
WEIXIN weixin=new WEIXIN(); //<-----假设没有定义WEIXIN这个类,那么这句话编译时报错
weixin.begin();
}
}
}
(3)动态加载类
思考:下面的class.forMame可以动态的给,假设场景是,用户在前台点击多个功能按钮中的一个,会传不同的参数到这里,假如是QQ,这个QQ功能模块是能正常使用的,假如某个用户点击WEIXIN这个模块,这时程序才会报错,这样就隔离了QQ和微信的功能模块之间的关系
DynamicLoadingClass.java
public class DynamicLoadingClass {
public static void main(String[] args) {
try {
//动态加载类,在运行时刻加载
Class c=Class.forName("javareflect.loadingClass.QQ");
//通过类类型,创建该类对象
Tencent tx=(Tencent)c.newInstance();
tx.begin();
Class b=Class.forName("javareflect.loadingClass.WEIXIN");
Tencent tx1=(Tencent)b.newInstance();
tx1.begin();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Tencent.java
interface Tencent {
public void begin();
}
QQ.java
public class QQ implements Tencent{
public void begin(){
System.out.println("QQ......begin");
}
}
2.通过反射查看类的信息
下面例子是通过反射获取方法,成员变量,构造函数的信息
package javareflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ClassUtil {
/*
* 打印类的信息,包活类的成员函数
*
* @param obj 该对象所属类的信息
*/
public static void printClassMessage(Object obj) {
// 要获取类的信息 首先获取类的类类型
Class<? extends Object> c = obj.getClass();// 传递的是哪个子类的对象,c就是该类的类类型
// 获取类的名称
System.out.println("类的名称是:" + c.getName());
/*
* Method类,方法对象 一个成员方法就是一个Method对象 public Method[]
* getMethods()返回某个类的所有公用(public)方法包括其继承类的公用方法, 当然也包括它所实现接口的方法。 public
* Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,
* 包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。
*/
Method[] ms = c.getMethods();
for (int i = 0; i < ms.length; i++) {
// 得到方法的返回值类型的类类型
Class<?> returnType = ms[i].getReturnType();
System.out.print(returnType.getSimpleName() + " ");
// 得到方法名称
System.out.println(ms[i].getName() + "(");
// 获取参数类型--->得到参数列表的类型的类类型
Class[] paramType = ms[i].getParameterTypes();
for (Class<?> paramTypeList : paramType) {
System.out.print(paramTypeList.getName() + ",");
}
System.out.println(")");
}
}
private static void getFieldMessage(Object obj) {
Class c = obj.getClass();
/*
* 成员变量也是对象 java.lang.reflect.Field Field类封装了关于成员变量的操作
* getFields()方法获取的是所有的public的成员变量的信息
* getDeclaredFields()方法获取的是该类自己声明的成员变量的信息
*/
// Field[] fs = c.getFields();
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
// 得到成员变量的类型的类类型
Class fieldType = field.getType();
String typeName = fieldType.getName();
// 得到成员变量的名称
String fieldName = field.getName();
System.out.println(typeName + " " + fieldName);
}
}
private static void getConstructMessage(Object obj){
Class c=obj.getClass();
/*
* 构造函数也是对象
* java.lang.Constructor封装了构造函数信息
* getConstructors()获取所有的public的构造函数
* getDeclaredConstructors()得到所有构造函数
*/
//Constructor[] cs = c.getConstructors();
Constructor[] cs = c.getDeclaredConstructors();
for(Constructor construor:cs){
System.out.println(construor.getName()+"(");
//获取构造函数的参数列表,得到的是参数列表的类类型
Class[] paramTypes=construor.getParameterTypes();
for(Class class1:paramTypes){
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}
public static void main(String[] args) {
String str = "1";
printClassMessage(str);
System.out.println("+++++++++++++++++++++++++++++++++");
getFieldMessage(str);
System.out.println("+++++++++++++++++++++++++++++++++");
getConstructMessage(str);
}
}
3.方法的反射
(1)如何获取某个方法:
方法的名称和方法的参数列表才能唯一决定某个方法
(2)方法反射的操作
method.invoke(对象,参数列表)
这里有一个需要注意的是:c.getDeclaredMethod(name, parameterTypes) 表示的类或接口声明的所有方法,假如要获取一个私有的private方法,需要在反射出这个方法后,给这个方法开启私有访问权限
Method m2 = c.getDeclaredMethod("printNullParameterTypes");
m2.setAccessible(true);// 开启私有访问权限
m2.invoke(a);
package javareflect;
import java.lang.reflect.Method;
/*
* 1.如何获取某个方法:方法名称和方法的参数列表才能唯一决定某个方法
* 2.方法的反射操作: method.invoke(对象,参数列表)
*/
public class MethodInvoke {
public static void main(String[] args) {
// 要获取print(int,int)方法 1.要获取一个方法就是获取类的信息,获取类的信息首先要获取类的类类型
A a = new A();
Class c = a.getClass();
/*
* 2.获取方法 名称和参数列表决定 getMethod(name, parameterTypes)获取的是public方法
* c.getDeclaredMethod(name, parameterTypes) 表示的类或接口声明的所有方法
*/
try {
// Method m=c.getMethod("print", new Class[]{int.class,int.class});
Method m = c.getMethod("print", int.class, int.class);
// 方法反射操作
// a.print(10,20);
// m.invoke(a, new Object[]{10,20});
Object obj = m.invoke(a, 10, 20);
/*
* Method m1=c.getMethod("printNullParameterTypes"); Object
* obj1=m1.invoke(a);
*/
Method m2 = c.getDeclaredMethod("printNullParameterTypes");
m2.setAccessible(true);// 开启私有访问权限
m2.invoke(a);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class A {
private void printNullParameterTypes() {
System.out.println("null参数");
}
public void print(int a, int b) {
System.out.println(a + b);
}
}
4.通过Class,Method来认识泛型的本质
java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了,我们可以通过方法的反射绕过编译
package javareflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
* 通过Class,Method认识泛型本质
*/
public class GenericityReflect {
public static void main(String[] args) {
ArrayList list=new ArrayList();
ArrayList<String> list1=new ArrayList<String>();
list1.add("hello");
//list1.add(10); 编译错误
Class c1 =list.getClass();
Class c2=list1.getClass();
System.out.println(c1==c2);
//反射操作都是编译之后的操作
/*
* c1==c2结果返回true 表示编译之后集合的泛型是去泛型化的
* java中集合的泛型是为了防止错误输入,只在编译阶段有效,绕过编译阶段就失效了
* 我们可以通过方法的反射操作,绕过编译,验证上一句话
*/
try {
Method m=c2.getMethod("add", Object.class);
Object obj=m.invoke(list1,10);
System.out.println(list1.size());
System.out.println(list1);
// for(String str:list1){
// System.out.println(str);
// } 不能这么编译,因为有int型也传入进去了
} catch (Exception e) {
e.printStackTrace();
}
}
}