反射是动态语言的关键,反射机制允许程序在运行期间借助ReflectionApi取得任何类的内部信息,并能直接操作任意对象的内部属性的方法。
概述:java提供的一套api,可以使用api可以在运行时动态的获取指定对象所属的类,创建运行时类的对象,调用指定的结构(属性、方法)
优点: 提高自适应能力降低耦合性, 允许程序控制任何类的对象,无需提前硬编码目标类 缺点:性能低、可读性差
1、面向对象使用反射和不使用反射有何区别 不使用反射我们需要考虑封装性 比如出了某各类后就要考虑某各类中私有结构不能用的情况 使用反射,我们可以调用运行时类中的任意构造器、属性、方法 反射关注的是能不能的问题、封装考虑的是好不好的问题 2、使用场景:两种方法 那种用的多 从开发业务角度来说 我们开发中主要是完成业务代码,对于相关的对象方法调用都是确定的 我们使用非反射的情况多一些。
因为反射体现了动态性 可以在运行时动态的获取对象所属的类,动态的调用相关的方法。所以我们在设计框架的时候会大量使用反射。意味这需要学习框架的源码,那么就需要学习反射 框架=注解+反射+设计模式
3、单例模式的懒汉式中,私有化的构造器!此时通过反射,可以创建单例模式的多个类对象吗? 是的! 4、通过反射,可以调用类中私有化的结构,是否与面向对象思想有冲突 ?是不是Java语言设计存在bug 不存在bug! 封装性是否建议调用内部api的问题,比如 用private生明的结构,意味着不建议调用 如果要调用就修改访问权限 反射:体现的是我们能否调用的问题。反射之所以可以用的前提是因为类的完整结构都加载到内存中,所以就能调用。
class类的理解:
针对编写好的.java源文件进行编译会生成一个或者多个.class字节码文件 我们使用java.exe对指定的.class进行解释运行。这个解释运行的过程,我们需要将.class字节码文件加载到内存中。通过类加载器加载到内存中的.class文件对应的结构即为一个Class实例 比如:加载到内存中的Person类或者String或者User。都可以作为一个一个的实例
运行时类 Class calzz=Person.class; Class clazz=String.class; Class clazz=User.class;
运行时类 在内存中会缓存起来 Class是反射的源头
获取Class实例的方法 1、调用运行时类的静态属性:class Class clazz1=User.class; 2、调用运行时类的对象的Getclass User u1=new User(); Class clazz2=u1.getClass(); 3、调用class的静态方法forName(String className) String className="com.atguigu2._class.User"; Class clazz3=Class.forName(className); clazz1==clazz2 true clazz1=clazz3 true 4、使用类的加载的方法 Class clazz4=ClassLoader.getSystemClassLoader().loadClass("com.at.User"); clazz1=clazz4;
类加载的过程 过程1 类的装载(loading):将类的class文件读入内存中,并为之创建一个java.lang.Class.此过程为类加载器完成;
过程2 链接(Linking)有三个步骤: 1、验证(Verify):确保加载的类的信息符合jvm规范,例如以cafebabe(魔数)开头,没有安全方面的问题。 2、准备(Prepare):正式为类变量(static静态变量)分配内存并设置类变量默认初始化值的阶段,这些内存都将在方法区中进行分配。 3、解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
过程3 初始化 执行类构造器<clinit>()方法的过程 类构造器方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
类加载器的作用: 负责类的加载,并对应于一个Class的实例 分类: BootstrapClassLoader:引导类加载器、启动类加载器、 使用c/c++编写的类加载 负责加载rt.jar 继承与ClassLoader的类加载器: ExtensionClassLoader:扩展类加载器 负责加载java.ext.dirs系统属性所指定的加载类库、或者从JDK安装目录的jre/lib/ext目录下记载的内容 SystemClassLoader、 ApplicationClassLoader:系统类加载器、应用类加载器 加载我们自定义类的默认加载器 用户自定义加载器: 实现应用隔离 同一个类在一个应用程序中加载多份;数据加密
反射的应用: 1、创建运行时类对象 1.1如何实现 通过class的实例调用newInstance()方法即可 Class clazz=Person.class; //创建Person类的实例 Person per=(Person)clazz.newInstance(); 1.2想要创建成功,需要满足: 要求运行时类必须提供一个空参构造器 要求空参构造器的访问权限要足够 1.3JavaBean中要求给当期类提供一个公共的空参构造器,有什么用? 场景一:子类对象在实例化时,子类的构造器的首行默认调用父类的空参构造器; 场景二:在反射中经常用来创建运行时类对象,要求各个运行时类都提供一个空参构造器便于我们编写创建运行时类的对象代码 1.4 jdk1.9中newInstance是标示为过时。此时应该使用Constructor类调用newInstance(..); 2获取运行时类的内部结构 2.1获取运行时类内部结构:所有属性、所有方法、所有构造器、 2.2获取运行时类内部结构:父类、接口、包、带泛型的父类、父类的泛型等 3*调用指定的结构,指定的属性、方法、构造器 调用指定的属性 //public int age=1;: Class clazz=Person.class; Person per =(Person)clazz.newInstance(); //1获取运行时类指定属性 Field ageField=clazz.getField("age"); //2获取哪个对象的age属性的值 sout(ageField.get(per)); //不仅可以获取 也可以设置属性的zhi ageField.set(per,2); ------------------------------- //private String name; 上面操作获取的是public类型的属性的值 关于私有属性值获取值如下: Class clazz=Person.class; Person per =(Person)clazz.newInstance(); //通过Class实例调用getDeclaredField(String fieldName)获取运行时类的指定名称的属性 Field nameField = clazz.geeDeclaredField("name"); //确保此属性是可以访问的 nameField.setAccessible(true); //set给其属性赋值 get获取其属性的值 nameField.set(per,"Tom"); sout(nameField.get(per)); ---------------------------------- 此可以作为调用指定属性的通用步骤 ****************** 调用指定的方法*********** // private String showNation(String nation,int age){} Class clazz=Person.class; Person per =(Person)clazz.newInstance(); 1、通过Class的实例调用geeDeclaredMethod 方法,获取指定的方法 Method nationMethod = clazz.geeDeclaredMethod("showNation",String.class,int.class); 2、确保此方法可以访问到 nationMethod.setAccessible(true); 3、传参调用方法 可以获取返回值 invoke的返回值就是方法shownation的返回值 如果shownation返回值是void 则invoke返回值是null Object obj=nationMethod. (per,"chn",10); ***************************************** ***************调用指定的构造器*********** // private Person(String name ,String int){} Class clazz=Person.class; 1、通过Class实例调用geeDeclaredConstructor(参数) 获取指定参数类型的构造器 Constructor constructor=clazz.geeDeclaredConstructor("showNation",String.class,int.class); 2/确保此构造器可以访问 constructor.setAccessible(true); 3/返回运行时类的实例 Person per=constructor.newInstance("Tom",12);