文章目录
Java学习第四阶段
反射
0. 初时反射
//文件结构
reflection
|_reflection01
|_Dog
|_startReflect
|_testModel.properties
//startReflect.java
package reflection.reflection01;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class startReflect {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Properties properties = new Properties();
properties.load(new FileInputStream("E:\\WorkSpace\\JAVA\\反射学习\\src\\reflection\\reflection01\\testModel.properties"));
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//反射,cls即成为一个类
Class<?> cls = Class.forName(className);
//JDK9以后不推荐直接使用newInstance(),改用下面的方法
//目的:创建一个cls类的实例化对象
//按照以前的想法,这里会向下转型,转成自己确定的那种,但是反射的魅力就在此
Object o = cls.getDeclaredConstructor().newInstance();
//通过类名获取方法名
Method method = cls.getMethod(methodName);
//在通过方法名invoke(对象),与传统的-->对象.方法 截然不同
//调用成功
method.invoke(o); //output 旺财 hi
}
}
//Dog.java
package reflection.reflection01;
public class Dog {
private String name = "旺财";
public void hi(){
System.out.println(name+" hi");
}
}
//testModel.properties
className=reflection.reflection01.Dog //这里Dog类的所在位置可以看一下Dog类第一行package
methodName=hi
1. 反射机制
//反射相关的主要类
1、java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
2.java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
3.java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
4.java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
这些类在java.lang.reflect中
//Dog.java
package reflection.reflection01;
public class Dog {
private String name = "旺财";
public int age = 9;
public void hi(){
System.out.println(name+" hi");
}
}
//reflect.java内部分代码
Class<?> cls2 = Class.forName("reflection.reflection01.Dog");
// Field name = cls2.getField("name"); //getField()无法获取私有属性
// System.out.println(name);
Object o2 = cls.getDeclaredConstructor().newInstance();
Field age = cls2.getField("age");
System.out.println(age.get(o2)); //output 9
//无参构造器
Constructor<?> constructor = cls.getConstructor();
System.out.println(constructor);
//只有String的构造器
Constructor<?> constructor2 = cls.getConstructor(String.class);
System.out.println(constructor2);
//有String和int的构造器
Constructor<?> constructor3 = cls.getConstructor(String.class,int.class);
System.out.println(constructor3);
//然后通过这些构造器对象,可以得到其他关于构造器的相关信息,通过--> construct3.函数
反射的优点和缺点:
1.优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就会失去底层支持
2.缺点:使用反射基本是解释执行,对执行速度有影响
反射调用优化–关闭访问检查
1.Method和Field、Constructor对象都有setAccessible()方法
2.setAccessible作用是启动和禁用访问安全检查的开关
3.参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率,参数值为false则表示反射的对象执行访问检查
package reflection.reflection02;
import reflection.reflection01.Dog;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Reflect01 {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
func1();//未优化前=28
func3();//优化后=11
func2();//一般调用方法=2
}
public static void func1() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
String classpath = "reflection.reflection01.Dog";
Class<?> cls = Class.forName(classpath);
Object obj = cls.getConstructor().newInstance();
Method testMethod = cls.getMethod("test", int.class);
long start = System.currentTimeMillis();
int j = 0;
for (int i = 0; i < 100000; i++) {
testMethod.invoke(obj,j);
}
long end = System.currentTimeMillis();
System.out.println("未优化前=" + (end - start));
}
public static void func2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
String classpath = "reflection.reflection01.Dog";
Class<?> cls = Class.forName(classpath);
Dog obj = (Dog) cls.getConstructor().newInstance();
long start = System.currentTimeMillis();
int j = 0;
for (int i = 0; i < 100000; i++) {
obj.test(j);
}
long end = System.currentTimeMillis();
System.out.println("一般调用方法="+ (end - start));
}
public static void func3() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException {
String classpath = "reflection.reflection01.Dog";
Class<?> cls = Class.forName(classpath);
Object obj = cls.getConstructor().newInstance();
Method testMethod = cls.getMethod("test", int.class);
testMethod.setAccessible(true);//取消访问检查
long start = System.currentTimeMillis();
int j = 0;
for (int i = 0; i < 100000; i++) {
testMethod.invoke(obj,j);
}
long end = System.currentTimeMillis();
System.out.println("优化后=" + (end - start));
}
}
2. Class 类
第一点:Class也是类,就和Person,Dog一样
第二点:
//如 1. Dog dog = new Dog();
// 创建时,先通过Classloader方法中的loadClass方法在堆中创建一个Dog的Class类对象
/*源码:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
//---------------------------------------------------------------------------------------
// 2. Class<?> cls = Class.forName("reflectioin.reflection01.Dog");
/*源码
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
// Returns the class's class loader, or null if none.
static ClassLoader getClassLoader(Class<?> caller) {
// This can be null if the VM is requesting it
if (caller == null) {
return null;
}
// Circumvent security check since this is package-private
return caller.getClassLoader0();
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
//---------------------------------------------------------------------------------------
第三点:
//如果 已经加载过一次类了,后面加载该类就不会去loadClass了
//如
//Dog dog = new Dog();
//Class<?> dog2 = Class.forName("Dog");--->第一句已经加载了该类的Class类对象了,这句就不会再加载了
//同理继续 Dog dog3 = new Dog();--->这里也不会加载了
//可以通过hashcode发现 dog.hashCode() == dog2.hashCode() == dog3.hashCode()
第五六七点:
//Class类(加载阶段) Class类对象是一种数据结构,将方法、变量、构造器当作对象(Object)更利于操作
//而方法区中是二进制数据
//二进制数据和字节码是有区别的
常用方法
package reflection.reflection04;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class reflect {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classpath = "reflection.reflection04.Car";
Class<?> cls = Class.forName(classpath);
System.out.println(cls);//显示cls是哪个Class类的对象:class reflection.reflection04.Car
System.out.println(cls.getClass());//运行类型:class java.lang.Class
System.out.println(cls.getPackage().getName());//获取包名:reflection.reflection04
System.out.println(cls.getName());//reflection.reflection04.Car
Object obj = cls.getConstructor().newInstance();//创建一个cls类型,这里可以强转
Car obj2 = (Car) cls.getConstructor().newInstance();
System.out.println(obj);//reflection.reflection04.Car@3ac3fd8b
System.out.println(obj.getClass());//class reflection.reflection04.Car
System.out.println(obj2);//reflection.reflection04.Car@5594a1b5
System.out.println(obj2.getClass());//class reflection.reflection04.Car
Field brand = cls.getField("brand");
System.out.println(brand.get(obj));//BMW
System.out.println(brand.get(obj2));//BMW
brand.set(obj,"GTR");//给某个对象的Field重新赋值
System.out.println(brand.get(obj));//GTR
System.out.println(brand.get(obj2));//BMW
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.print(field.getName() + " ");//brand price
}
System.out.println();
for (Field field : fields) {
System.out.println(field);
}
/* output:
* public java.lang.String reflection.reflection04.Car.brand
* public int reflection.reflection04.Car.price
*/
}
}
获取Class类对象得方法
//可以在不同阶段去获取类对应得Class对象
//1.编译阶段 2.类加载器阶段 3.加载阶段 4.运行阶段
package reflection.reflection04;
/**
* 获取Class类对象的方法
*/
public class reflect2 {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class.forName
String classpath = "reflection.reflection04.Car";
Class<?> cls1 = Class.forName(classpath);
System.out.println(cls1);
//2.类名.class 应用场景:多用于参数传递
Class<?> cls2 = Car.class;
System.out.println(cls2);
//3.对象.getClass() 应用场景,有对象实例
Car car = new Car();
Class<?> cls3 = car.getClass();
System.out.println(cls3);
//4.通过类加载器(4种)来获取到类的Class对象
//(1)先得到类加载器
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)
Class<?> cls4 = classLoader.loadClass(classpath);
System.out.println(cls4);
//cls1,cls2,cls3,cls4其实是一个对象
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
//5.基本数据类型 基本数据类型.class
Class<Integer> integerClass = int.class;
System.out.println(integerClass);//int
Class<Character> characterClass = char.class;
System.out.println(characterClass);//char
//6.基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class<Integer> type = Integer.TYPE;
System.out.println(type);//int
//两者相等
System.out.println(type.hashCode());
System.out.println(integerClass.hashCode());
}
}
以下总结转载自知乎:Java反射中获取Class对象三种方式的区别? - 知乎 (zhihu.com)
哪些类有Class对象
3. 类加载
package ClassLoad;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;
/**
* 用于解释静态加载 和 动态加载
*/
public class ClassLoad01 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
System.out.println("input a choice--1 or 2");
Scanner scanner = new Scanner(System.in);
String op = scanner.next();
switch (op) {
case "1":
//这里本应该如果不输入 1,就根本不会进入这里创建Person类
//但是编译时,静态加载该类,导致你不写出Person就不能编译通过
Person man = new Person();//Error
break;
case "2":
//而这里使用反射,是动态加载,只有输入2才会进入这里,哪怕你没有实现Dog类,编译阶段仍然可以通过
//但如果输入选项2,进入这里时发现没有Dog类,则抛出异常
Class<?> cls = Class.forName("Dog");
Object obj = cls.getConstructor().newInstance();
Method m = cls.getMethod("hi");
m.invoke(obj);
break;
}
}
}
3.1 类加载过程
↓ ↓ ↓
3.2 类加载各个阶段
3.2.1 加载阶段(Loading)
JVM在该阶段得主要目的是将字节码从不同得数据源(可能是class文件,也可能是jar包,甚至是网站)转化为二进制字节流加载到内存中,并生成一个代表该类得java.lang.Class对象
3.2.2 连接阶段(Linking)
(1)验证
1.目的是为了确保Class文件得字节流中包含得信息符合当前虚拟机得要求,并且不会危害虚拟机自身的安全
2.包括:文件格式验证(是否以魔数 oxcafebabe开头),元数据验证,字节码验证和符号引用验证
3.可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
(2)准备
JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0,0L,null,false等)。这些变量所使用的内存都将在方法区中进行分配
如:
class A{
//属性 == 成员变量 == 字段
//分析类加载的连接阶段-准备,属性是如何处理的
//1.n1 是实例属性,不是静态变量,因此在准备阶段,不会分配内存
//2.n2 是静态变量,分配内存 n2 是默认初始化为 0,而不是20, 20是在initialization阶段赋值的
//3.n3 是static final是常量,它和静态变量不同,因为它一旦赋值就不变,所以 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}
(3)解析
虚拟机将常量池的符号引用替换为直接引用的过程
大致理解:
假设源文件有两个类A、B,它们之间有调用关系
编译阶段由于没有加载到内存,就没有地址映射,就以符号引用作为调用关系之间的桥梁,后进入类加载阶段,将这种桥梁改为直接引用靠地址为桥梁
3.2.3 初始化阶段(Initialization)
1. 到初始化阶段,才是真正开始执行类中定义的Java程序代码,此阶段是执行<clinit>()方法的过程
2. <clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
3. 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<linit>()方法完毕
package Initialization;
public class Initialization01 {
public static void main(String[] args) {
//分析
//1.加载A类,并生成A的Class对象
//2.连接,其中num = 0
//3.初始化
/*
* 依次自动 收集 类中的所有静态变量的赋值动作和静态代码块中的语句
* 根据代码块的调用顺序可以知道,静态代码块和静态属性的优先级一样,所以按照编写顺序进行,但他们优先级均大于构造器
*
* clinit(){
* System.out.println("static codeBlock is executing");
* //num = 300;
* num = 100; //这里就会将上面那句等同于无效,合并为num = 100;
* }
*/
System.out.println(A.num);//如果直接使用类的静态属性,也会对类进行加载
/*output
* static codeBlock is executing
* 100
*/
//4.如果要new一个对象才会继续调用构造器
new A(); //constructor is executing
}
}
class A{
static {
System.out.println("static codeBlock is executing");
num = 300;
}
public static int num = 100;
public A() {
System.out.println("constructor is executing");
}
}
4.通过反射获取类的结构信息
常用的一些
5.通过反射创建对象
/*
* 方式一:调用类中的public修饰的无参构造器
* 方式二:调用类中的指定构造器
*
* Class类相关方法
* newInstance:调用类中的无参构造器,获取对应类的对象
* getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象
* getDecalaredConstructor(Class...clazz):根据参数列表,获取对应的所有构造器对象
*
* Constructor类相关方法
* setAccessible:爆破
* newInstance(Object...obj):调用指定构造器
*/
package Initialization;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class Initialization02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1.先获取到A类的Class对象
Class<?> AClass = Class.forName("Initialization.A");
//2.通过public的无参构造器创建实例
Object o1 = AClass.getDeclaredConstructor().newInstance();
System.out.println(o1);
//3.通过 public/private 的有参构造器创建实例
/*
* 注意这里getDeclaredConstructor是可以获取到私有的构造器的
* 也可以只通过getConstructor获取共有的构造器
*/
Object o2 = AClass.getDeclaredConstructor(int.class).newInstance(100);
System.out.println(o2);
Object o3 = AClass.getConstructor(int.class, String.class).newInstance(999, "小黑");
System.out.println(o3);
//分开写
Constructor<?> constructor = AClass.getDeclaredConstructor(String.class);
//爆破,使用反射可以使用private构造器
//private 方法/属性 也可以,通过爆破获取
constructor.setAccessible(true);
Object o4 = constructor.newInstance("老哥");
System.out.println(o4);
}
}
class A{
private int num = 99;
private String str = "小王";
public A() {
System.out.println("This is none parameter constructor");
}
public A(int num){
this.num = num;
System.out.println("This is has a parameter(int) public constructor");
}
public A(int num,String str){
this.num = num;
this.str = str;
System.out.println("This is has two parameter(int,String) public constructor");
}
private A(String str){
this.str = str;
System.out.println("This is has two parameter(int,String) private constructor");
}
@Override
public String toString() {
return "A{" +
"num=" + num +
", str='" + str + '\'' +
'}';
}
}
6. 通过反射访问类中的成员
6.1 访问属性
/*
* 访问属性
* 1. 根据属性名获取Field对象
* Field f = clazz对象.getDeclaredField(属性名);
*
* 2. 爆破:f.setAccessible(true);//f 是 Field
*
* 3. 访问
* f.set(o,值); // o表示对象
* syso(f.get(o)); // o表示对象
* 4. 如果是静态属性,则set和get中的参数o,可以写成null
*
*/
package Initialization;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class Initialization03 {
public static void main(String[] args) {
}
@Test
public void test() throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> PersonClass = Class.forName("Initialization.Person");
//通过反射,利用无参构造器创建对象1
Object Person1 = PersonClass.getDeclaredConstructor().newInstance();
System.out.println(Person1);
//通过反射,利用有参构造器创建对象2
Object Person2 = PersonClass.getDeclaredConstructor(String.class,int.class).newInstance("铁幕",999);
System.out.println(Person2);
/*
* 注意 getDeclaredField获取public和非public均可以
* 而getField只能获取public
*/
//获取Person类的Class对象,并获取public属性--name
Field name = PersonClass.getField("name");
//设置Person1对象的姓名
name.set(Person1,"动员兵");
//也可以通过 属性.get(对象o) 获取对象o的该属性
System.out.println(name.get(Person1));
System.out.println(Person1);
//设置Person2对象的姓名
name.set(Person2, "核弹");
System.out.println(Person2);
//获取Person类的Class对象,并获取private属性--age
Field age = PersonClass.getDeclaredField("age");
age.setAccessible(true);//爆破
//设置Person对象的age属性
age.set(Person1,30);
System.out.println(age.get(Person1));
System.out.println(Person1);
age.set(Person2, 9999);
System.out.println(Person2);
//------------------------------------
Field price = PersonClass.getDeclaredField("price");
price.setAccessible(true);
//静态属性 set的第一个参数为null
price.set(null,8888);
System.out.println(price.get(null));//静态的才行
System.out.println(price.get(Person1));
System.out.println(Person1);
}
}
class Person{
public String name = "red2";
private int age = 2;
private static double price = 0;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age + '\'' +
", price="+ price +
'}';
}
}
//执行结果
Person{name='red2', age=2', price=0.0}
Person{name='铁幕', age=999', price=0.0}
动员兵
Person{name='动员兵', age=2', price=0.0}
Person{name='核弹', age=999', price=0.0}
30
Person{name='动员兵', age=30', price=0.0}
Person{name='核弹', age=9999', price=0.0}
8888.0
Person{name='动员兵', age=30', price=8888.0}
进程已结束,退出代码0
6.2 访问方法
/*
* 1.根据方法名和参数列表获取Method方法对象:Method m = clazz.getDeclaredMethod(方法名, XX.class)
* 2.获取对象:Object o = clazz.newInstance();
* 3.如果是 非public 则爆破:m.setAccessible(true);
* 4.访问: 返回值类型 返回值 = m.invoke(o,实参列表);
* 5.注意:如果是静态方法,则invoke的参数o,可以写成null
*
*/
package Initialization;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Initialization04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Class<?> cls = Class.forName("Initialization.Person");
//用不同构造器创建对象
Object soldier = cls.getDeclaredConstructor().newInstance();
Object soldier2 = cls.getDeclaredConstructor(String.class,int.class).newInstance("辐射工兵",50);
Method m = cls.getMethod("attack");
m.invoke(soldier);
/**
* getDeclaredMethod可以获取public和非public
* 获取非public时,要爆破
*
* getMethod()只能public
*/
//defense方法有重载,这里调用参数为String的
Method m2 = cls.getDeclaredMethod("defense", String.class);
m2.setAccessible(true);
m2.invoke(soldier,"敌军");
//defense方法有重载,这里调用参数为String, int的
Method m3 = cls.getDeclaredMethod("defense", String.class,int.class);
m3.setAccessible(true);
m3.invoke(soldier,"敌军",1000);
m3.invoke(soldier2,"敌军二",20);
//调用带返回值的方法
//在反射中如果方法有返回值,一律返回Object,但运行类型和方法定义的返回类型一致
Method getNumber = cls.getDeclaredMethod("getNumber", int.class);
Object returnVal = getNumber.invoke(soldier, 666);
System.out.println(returnVal+ " "+returnVal.getClass());//666 class java.lang.Integer
//调用静态成员方法
Method m4 = cls.getDeclaredMethod("move");
//以下两种均可
m4.invoke(null);
m4.invoke(soldier);
Person.setPrice(500.6);
m4.invoke(soldier2);
//查看静态属性
Field f = cls.getDeclaredField("price");
f.setAccessible(true);
System.out.println(f.get(null));
}
}
class Person{
private String name = "狙击手";
private int age = 20;
private static double price = 400.5;
public static void setPrice(double price) {
Person.price = price;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void attack(){
System.out.println("public func");
}
private void defense(String enemy){
System.out.println(this + " ,private func" + " ,parameter:"+enemy);
}
private void defense(String enemy,int number){
System.out.println(this + " ,private func" + " ,parameter:"+enemy+" ,"+number);
}
public static void move(){
System.out.println("static func");
}
public int getNumber(int number){
return number;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//执行结果
public func
Person{name='狙击手', age=20} ,private func ,parameter:敌军
Person{name='狙击手', age=20} ,private func ,parameter:敌军 ,1000
Person{name='辐射工兵', age=50} ,private func ,parameter:敌军二 ,20
666 class java.lang.Integer
static func
static func
static func
500.6
进程已结束,退出代码0