反射
反射是动态语言的关键,反射机制运行程序在运行期间借助于Reflection API取得任何类的内部信息,并能之间操作任意对象的内部属性与方法。
反射的意义:
1.面向对象中创建对象,调用指定结果(属性、方法)等功能,可以不适用反射,也可以使用反射。有什么区别?
使用反射,我们可以调用运行时类中任意的构造器、属性、方法。包括了私有属性、方法构造器
2.以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的画,那种使用的多?场景?
3.单例模式的饿汉式与懒汉式,私有化类的构造器。此时通过反射可以通过单例模式中类的多个对象吗?
是的
4.通过反射,可以调用类中私有结构,是否与面向对象的封装性有冲突?
封装性:提醒的是是否建议我们调用内部api的问题。比如,private声明的结果,意味着不建议调用。
反射:提醒的是我们能否调用的问题。因为类的完整结构都加载到了内存中,所有我们就有能力进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| public class PersonTest { @Test public void test() throws InstantiationException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException { Class<Person> clazz = Person.class; Person p1 = clazz.newInstance(); System.out.println(p1);
Field ageField = clazz.getField("age"); ageField.set(p1, 10); System.out.println(ageField.get(p1));
Method showMethod = clazz.getMethod("show"); showMethod.invoke(p1); }
@Test public void test2() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { Class clazz1 = Person.class; Constructor cons = clazz1.getDeclaredConstructor(String.class, int.class); cons.setAccessible(true); Person p1 = (Person) cons.newInstance("Tom",12); System.out.println(p1);
Field nameField = clazz1.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(p1,"jerry"); System.out.println(nameField.get(p1));
Method showNationMethod = clazz1.getDeclaredMethod("showNation", String.class); showNationMethod.setAccessible(true); String info = (String) showNationMethod.invoke(p1, "CHN"); System.out.println(info);
} } class Person{ private String name; public int age;
public Person() { }
private Person(String name, int age) { this.name = name; this.age = age; }
public void show(){ System.out.println("showshow"); }
private String showNation(String nation){ return nation; }
@Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
|
Class
针对于编写好的.java源文件进行编译(使用javac.exe),会生产一个或多个.class字节码文件。接着我们使用java.exe命令对知道的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件(使用类的加载器)加载到内存中。加载到内存中(方法区)的.class文件对应的结果即为Class的一个实例。
比如:加载到内存中的Person类或String类或User类。都作为Class的一个个实例
Class clazz1 = Person.class;//运行时类
Class clazz4 = Comparable.class;
说明:运行时类在内存会缓存起来,在整个执行期间,只会加载一次。
获取Class的实例的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class ClassTest { @Test public void test() throws ClassNotFoundException { Class clazz1 = Person.class; System.out.println(clazz1);
Person p1 = new Person(); Class clazz2 = p1.getClass(); System.out.println(clazz1 == clazz2);
String className= "ex2.Person"; Class clazz3 = Class.forName(className); System.out.println(clazz1==clazz3);
Class clazz4 = ClassLoader.getSystemClassLoader().loadClass("ex2.Person"); System.out.println(clazz1==clazz4); } }
|
Class的实例可以指向哪些结构
所有java类型
类的加载过程
过程1:类的装载Load
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
过程2:连接Link
- 验证(Verify):确保加载的类信息符合JVM规范。
- 准备(Prepare):正式为类变量份分配内存并设置类变量默认初始值的阶段,这些内存都会在方法区分配
- 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
过程3:初始化initialization
执行类构造器()方法的过程。
类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
类的加载器
作用:负责类的加载,并对应于一个Class的实例
分类(分为两种):
1.BootStrapClassLoader:引导类加载器
- 使用C/c++语言编写,不能通过Java代码获取其实例
- 负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)
2.继承于ClassLoader的类加载器
- ExtensionClassLoader:扩展类加载器
- SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器
- 用户自定义类加载器
以上的类加载器是否存在继承关系
不存在。
通过classLoader加载指定配置文件
1 2 3 4 5 6 7 8 9 10
| @Test public void test2() throws IOException { Properties pros = new Properties(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("../info.properties"); pros.load(is); String name = pros.getProperty("name"); String pwd = pros.getProperty("password"); System.out.println(name+":"+pwd); }
|
应用
1.创建运行时的对象
1.1 实现方法:
通过Class的实例调用newInstance()方法即可
1 2 3 4 5 6 7 8 9 10
| public class NewInstanceTest { @Test public void test1() throws InstantiationException, IllegalAccessException { Class clazz = Person.class;
Person per = (Person) clazz.newInstance(); System.out.println(per); } }
|
1.2 创建对象成功的满足条件:
要求运行时类中必须提供一个空参的构造器
要求提供的空参构造器的权限要足够
1.3 JavaBean中要给一个当前类提供一个公共的空参构造器的意义
场景1:子类对象在实例化时,子类的构造器的首行默认调用父类空参的构造器。
场景2:在反射中,经常用来创建运行时类的对象。那么我要求各个运行时类都提供一个空参的构造器,便于我们编写创建运行时类对象的代码
1.4在jdk9中标识为过时,替换成什么结构
通过Constructor类调用newInstance(…)
2.获取运行时类的内部结构
2.1获取运行时类的内部结果1:所有属性、所有方法、所有构造器
2.2获取运行时类的内部结果2:父类、接口、包、带泛型的父类、父类的泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Test public void test2() throws ClassNotFoundException { Class clazz = Class.forName("com.alugg.reflection.Person"); Type superclass = clazz.getSuperclass();
Class[] interfaces = clazz.getInterfaces(); for(Class c:interfaces){ System.out.println(c); } Package pack = clazz.getPackage(); Type superclass1 = clazz.getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) superclass1; Type[] arguments = paramType.getActualTypeArguments(); System.out.println(((Class)arguments[0]).getName()); }
|
3.调用指定的结构:指定的属性、方法、构造器
3.1调用指定属性:
实例属性
1 2 3 4 5 6 7 8 9 10 11 12 13
| public void test3() throws InstantiationException, IllegalAccessException, NoSuchFieldException { Class clazz = Person.class; Person per = (Person)clazz.newInstance();
Field nameField = clazz.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(per,"alugg"); System.out.println(nameField.get(per));
}
|
静态类属性
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void test3() throws InstantiationException, IllegalAccessException, NoSuchFieldException { Class clazz = Person.class;
Field infoField = clazz.getDeclaredField("info"); infoField.setAccessible(true); infoField.set(Person.class,"xixixhaha"); System.out.println(infoField.get(Person.class)); }
|
3.2调用方法
实例方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Test public void test4() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Class clazz = Person.class; Person per = (Person) clazz.newInstance();
Method showNationMethod = clazz.getDeclaredMethod("showNation", String.class, int.class) showNationMethod.setAccessible(true); Object returnValue = showNationMethod.invoke(per,"chnxxx"); System.out.println(returnValue); }
|
类静态方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void test4() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Class clazz = Person.class; Method showNationMethod = clazz.getDeclaredMethod("showInfo"); showNationMethod.setAccessible(true); Object returnValue = showNationMethod.invoke(null); System.out.println(returnValue); }
|
3.3调用构造器
1 2 3 4 5 6 7 8 9 10 11
| @Test public void test5() throws NoSuchMethodException { Class clazz = Person.class; Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); Person tom = (Person) constructor.newInstance("TOM", 12); }
|
4.获取注解信息
1 2 3 4 5 6 7 8 9 10 11 12
| @Test public void test1() throws NoSuchFieldException { Class clazz = Customer.class; Table annotation = (Table) clazz.getDeclaredAnnotation(Table.class); System.out.println(annotation.value());
Field nameField = clazz.getDeclaredField("name"); Column nameColumn = nameField.getDeclaredAnnotation(Column.class); System.out.println(nameColumn.columnName()); System.out.println(nameColumn.columnType()); }
|
常见问题
反射的好处,为什么需要反射,使用的场合
实现反射的类有哪些?
反射是如何实现?
Class类的作用和生成Class对象有哪些
Class.forName()会调用哪些方法?会调用构造方法吗?加载的类放在哪?
Class.forName()会执行执行类构造器()方法
不会调用构造方法
加载的类放在方法区
类的加载流程
创建对象有几种方法
Java反射创建效率高还是new创建高?
new高
如何利用反射机制访问类的方法或获取私有属性