1.什么是反射
java反射就是程序在运行过程中,可以通过一段类的描述(类的全类名也叫类的完整路径: 包名.包名.类名) 可以动态获取类中的所有资源(比如:属性 方法 构造方法 私有的 注解...) 这种动态获取的方式就是反射
2.反射使用步骤
- 先获取class对象
1.类名.class
Class c1 =User.class;
2.对象名.getClass();
Class u =new User;
3.通过Class.forName("全类名");
Class c3=Class.forName("aaa.User");
- 通过class对象.getxxx()获取类中任何资源(包括私有的)
C1.getMethod()获取方法类
c2.getField()获取属性类
c3.getConstructor()获取构造方法类
3.java反射存在几种特殊的类
- class类:描述class类中的信息 它是类对象 也是反射的前提
- Method类:描述方法的类 比如:类中定义的所有方法 都是不同的Method类
- Constructor类:描述构造方法的类 类中有几个构造方法 就会存在几个Constructor类 如果没有定义 也会有一个默认无参的Constructor类
- Field类:描述属性的类
4.java反射应用
- 获取类中的资源
//1.获取类对象
Class c1=Person.class;
Class c2=Class.forName("day5.Person");
Person p=new Person("张三",18);
Class c3=p.getClass();
//2.通过类对象动态获取类中的所有资源
//c1.getXXX();
String name=c1.getName();
System.out.println("全类名:"+name);
String simpleName=c1.getSimpleName();
System.out.println("类名:"+simpleName);
String superName=c1.getSuperclass().getSuperclass().getName();
System.out.println("父类全类名:"+superName);
System.out.println("==========获取属性Field=============");
//获取该类中不包括父类的属性
Field[] fs=c1.getDeclaredFields();
for(Field f:fs){
String fieldName=f.getName();
Class type=f.getType();
System.out.println("属性名:"+fieldName+" 类型:"+type);
}
System.out.println("--------------------");
//获取该类中所有公开属性(父类的...)
Field[] fs2=c1.getFields();
for(Field f:fs2){
String fieldName=f.getName();
Class type=f.getType();
System.out.println("属性名:"+fieldName+" 类型:"+type);
}
- 设置和获取属性
//获取一个属性(如果是私有的如何获取)
Field f1=c1.getDeclaredField("name");//私有的
Field f2=c1.getDeclaredField("time");//共有的
//设置属性值 获取属性值
f2.set(p,new Date()); //等价于p.time=new Date()
Object value=f2.get(p); //等价于p.time
System.out.println("日期属性:"+value);
//如果是私有属性 如何处理
f1.setAccessible(true);//开启私有访问
f1.set(p,"重设名字");
Object value2=f1.get(p);
System.out.println("姓名属性:"+value2);
- 调用方法
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class c=Class.forName("day6.Demo1");
//获取本类所有方法(没有父类的)
Method[] ms=c.getDeclaredMethods();
for(Method m:ms){
String name=m.getName();
Class[] cs=m.getParameterTypes();
Class result=m.getReturnType();
System.out.println(name+" "+ Arrays.toString(cs)+" "+result);
}
//获取一个方法对象 进行调用 (参数1:方法名,参数2:方法的参数的Class类型)
Method m1=c.getDeclaredMethod("test1");
Method m2=c.getDeclaredMethod("test2",Integer.class);
Method m3=c.getDeclaredMethod("test3",String.class,Integer.class);
//调用方法(参数1:对象 参数2:实际参数)
//返回值:方法调用后的结果
Demo1 d=new Demo1();
Object result1=m1.invoke(d);
m2.setAccessible(true);//开启私有访问
Object result2=m2.invoke(d,100);
Object result3=m3.invoke(d,"java",1000);
System.out.println(result1);
System.out.println(result2);
System.out.println(result3);
}
- 创建对象
- 利用class对象直接创建:缺点是只能调用无参 不能设置私有权限
- 利用构造方法对象创建:
class TestDemo2{
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1.通过Class对象直接创建
Class c1=Demo2.class;
//创建对象 底层原理无参构造方法
//Demo2 d=(Demo2)c1.newInstance();
//System.out.println(d);
//2.通过构造方法对象创建
Constructor con1=c1.getDeclaredConstructor();
Constructor con2=c1.getDeclaredConstructor(Integer.class);
Constructor con3=c1.getDeclaredConstructor(String.class,Integer.class);
//创建对象 底层原理:所有的构造方法 并且可以设置私有权限
con1.setAccessible(true);
Demo2 d1=(Demo2)con1.newInstance();
Demo2 d2=(Demo2)con2.newInstance(100);
Demo2 d3=(Demo2)con3.newInstance("java",100);
System.out.println(d1);
System.out.println(d2);
System.out.println(d3);
}
}
- 通过反射实现通用方法的调用
class TestDemo3{
//通用方法 反射可以调用任意对象的任意方法
//返回值: 目标方法的返回值
//参数1:调用方法的对象 参数2:方法名 参数3:方法的实参
//bug: 如果传递了基本类型 会自动转换成封装类型 导致了方法不存在
public static Object base(Object o,String name,Object ... params) throws Exception{
Class c=o.getClass();
Class[] args=new Class[params.length];
for(int i=0;i<args.length;i++) {
args[i] = params[i].getClass();
}
Method m=c.getDeclaredMethod(name,args);
m.setAccessible(true);
Object result=m.invoke(o,params);
return result;
}
//bug:方法重载呢?
public static Object base2(Object o,String name,Object ... params) throws Exception{
Class c=o.getClass();
Class[] args=new Class[params.length];
Method[] ms=c.getDeclaredMethods();
for(Method m:ms){
if(m.getName().equals(name)){
args=m.getParameterTypes();
break;
}
}
Method m=c.getDeclaredMethod(name,args);
m.setAccessible(true);
Object result=m.invoke(o,params);
return result;
}
public static void main(String[] args) throws Exception {
Demo3 d=new Demo3();
base2(d,"test1");
base2(d,"test2",100);
base2(d,"test3","java",100);
base2(d,"test4",100);
}
}
5.类加载机制 ---面试题
5.1 类加载的时机
- 创建类的实例(对象)
- 调用类的类方法(静态方法)
- 访问类的变量或者给类变量赋值(静态变量)
- 使用反射强制性创建某个类的class对象
- 初始化某个类的子类
- 直接使用java.exe命令运行某个主类
5.2类加载器的规则
类加载器主要作用就是加载java类的字节码(.class文件)加载到JVM中(在内存中生成一个该类的class对象)
JVM启动的时候,类加载器不会一次性加载所有类,而是根据需要去动态加载(延迟加载:什么时候使用什么时候加载) 这样就说明,大部分java的类只有用到才会去加载 目的对内存更加友好
5.3类加载器分类
5.4类加载器的三个特点
- 双亲委派(面试题):当一个自定义的类加载器需要加载时,比如:java.lang.String 不会上来直接加载它 而是委托给父类加载器去加载 父加载器如果发现自己还有父类 会一直网上找 直接找到最顶层的加载器 如果顶层加载器已经加载java.lang.String自己这个子类就无需重复加载 如果这几个加载器都没有加载到目标类 就会抛出一个异常ClassNotFoundException
- 负责依赖:如果一个加载器加载某个类,这个类它依赖于另外几个类或者接口 也会尝试加载这些依赖项
- 缓存加载:为了提高加载效率,消除重复加载的问题 一旦类被摸一个加载器加载 那么它会缓存这个加载的直接使用 不会再加载
5.4.1双亲委派源码解析
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1.检测该类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) { // c==null 说明没有被加载过
long t0 = System.nanoTime();
try {
if (parent != null) {
//当父类加载器不为空时,则通过父类加载器实现
c = parent.loadClass(name, false);
} else {
//当父类为空时 则调用启动类加载器来加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//这些加载器都无法找到相应类,则抛出这个异常
}
if (c == null) {
// 当父类加载器找到了,但是无法加载时
//调用findClass()来加载这个类
//用户可以重写方法 来自定义加载器
long t1 = System.nanoTime();
c = findClass(name);
//用于统计类加载器的相关信息
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
好处:
- 可以解决重复加载的问题(JVM区分不同类的方式不仅仅是类名,相同的类文件被不同的类加载器加载 由于规则不同 很可能会产生两个不同的类)
- 如果没有使用双亲委派机制 ,而是每个类加载器 加载自己的内容会出现问题
- 如果使用了双亲委派机制可以保证的是我加载的是JRE里面的那个Object类,而不是我自己定义的Object 因为AppClassLoader准备加载你的Object类,会委派给ExtensionClassLoader去加载 它又委托给BootStrapLclassLoader,这时BootStrapLclassLoader发现自己已经加载过了,会直接返回,不会去加载你的类
- 相同的类文件被不同类加载器 就会产生不同的类文件
5.5类加载的过程 ---面试题
类的生命周期主要分为7个阶段:加载 验证 准备 解析 初始化 使用 卸载
-
加载:也可以称之为装载阶段,根据提供的class全类名,为了获取二进制类文件的字节流,不会检测语法或者格式的错误
-
验证:确保class文件里字节流信息符合当前虚拟机的要求,不会去危害虚拟器安全
-
准备:这个阶段会创建一些静态字段 并将其初始化为标准的默认值(比如:null或者0) 会在方法区分配这些变量所使用的内存空间 注意准备的阶段没有任何java代码
-
解析:主要分成四种情况,类和接口的解析,字段的解析,类方法的解析,接口方法解析,简单来说 当一个变量引用某个对象时 这个引用在class文件 将原来的字节流替换成引用地址
-
初始化:JVM规范要求 必须在类的首次主动使用时 才会执行类的初始化操作,包括:类构造方法、static静态变量赋值语句、static静态代码块 如果一个子类初始化 势必会对其父类初始化 最后必然先把java.lang.Object初始化