Java反射基础
Java作为一门动态的语言,有非常成熟的框架技术以供我们使用,而这一切都离不开Java反射技术的支持。通过Java反射,我们可以动态的创建和使用对象,使用灵活,没有反射机制,就无法实现框架技术。
Java程序在计算机的三个阶段
首先,需要了解一下Java程序在计算机的三个阶段,如图所示,图片来源——【零基础 快速学Java】韩顺平 零基础30天学会Java
[https://www.bilibili.com/video/BV1fh411y7R8?p=1]:
Java反射机制
- 通过Java反射机制,程序在执行期间可以借助于 RefelectionAPI 取得任何类的内部信息,如成员变量、构造器、成员方法等,并能够操作对象的属性及方法。反射在设计模式和框架底层都会用到;
- 加载完类之后,在堆中就产生了一个类的 Class 的对象,这个对象包含了类的完整结构信息,通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构。
比如:
Cat.java
public class Cat {
public String name = "喵喵";
public String age = "20";
public Cat(String name){
this.name = name;
}
public Cat(){}
public void cry(){
System.out.println(name + "喵喵叫...");
}
}
Cat.class
public class Cat {
public String name = "喵喵";
public String age = "20";
public Cat(String name) {
this.name = name;
}
public Cat() {
}
public void cry() {
System.out.println(this.name + "喵喵叫...");
}
}
Java反射相关的类
java.lang.Class//代表一个类,Class对象表示某个类加载后在堆中的对象
java.lang.refelect.Method//代表类的方法,Methon对象表示某个类的方法
java.lang.refelect.Field//代表类的成员变量,Field对象表示某个类的成员变量
java.lang.refelect.Constructor//代表类的构造器,Construvtor表示构造器
//getFiled不能得到私有的属性
反射调用优化
反射的优点和缺点
优点:反射可以动态的创建和使用对象,使用灵活,没有反射机制,就无法实现框架技术;
缺点:使用反射基本是解释执行,对执行速度有影响
反射调用优化 — — 关闭访问检查
- Method和Field、Constructor对象都有setAcceseible()方法
- setAcceseible作用是启动和禁用访问安全检查的开关
- 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查
示例代码:
package com.blue.reflection;
import com.blue.Cat;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
@SuppressWarnings({"all"})
public class Reflection2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
m1();
m2();
m3();
}
//传统方法来调用hi
public static void m1(){
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 90000000; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("m1= "+(end - start));
}
//反射机制调用方法hi
public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("com.blue.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 90000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("m2= "+(end - start));
}
//反射调用优化
public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("com.blue.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
//取消在反射调用方法时的访问检测
hi.setAccessible(true);
long start = System.currentTimeMillis();
for (int i = 0; i < 90000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("m3= "+(end - start));
}
}
Java Class类分析
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是系统创建的
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个Class类所生成
- 通过Class可以完整地得到一个类的完整结构,通过一系列的API
- Class对象是存放在堆的
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码、变量名、方法名、访问权限等)
Java Class类的继承类与实现接口关系
Class常用方法
package com.blue.class_;
import com.blue.Car;
import java.lang.reflect.Field;
/**
* User: Blueshadow
* Date&Time: 2021/11/14 20:49
* 演示Class类常用的方法
*/
public class class_ {
public static void main(String[] args) throws Exception {
String classAllPath = "com.blue.Car";
//获取到Car类对应的Class对象
//<?>表示不确定的Java类型
Class<?> cls = Class.forName(classAllPath);
//输出cls
System.out.println(cls);//显示cls对象,是哪个类的Class对象(class com.blue.Car)
System.out.println(cls.getClasses());//输出运行类型([Ljava.lang.Class;@4554617c)
System.out.println(cls.getPackage());//得到包名(package com.blue)
System.out.println(cls.getName());//得到类名(com.blue.Car)
//通过cls创建对象实例
Car car = (Car)cls.newInstance();
System.out.println(car);//(Car{brand='宝马', price=500000, color='red'})
//通过反射获取属性
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//私有属性则会报错
//通过反射给属性赋值
brand.set(car,"奔驰");//重新赋值
System.out.println(brand.get(car));
//得到所有的属性
Field[] fields = cls.getFields();
for (Field f : fields){
System.out.println(f.getName());//依次输出所有字段属性的名称
//brand
//price
//color
}
}
}
获取Class类对象的六种方式
package com.blue.class_;
import com.blue.Car;
import java.util.Calendar;
/**
* User: Blueshadow
* Date&Time: 2021/11/15 16:18
* 演示得到Class对象的各种方式
*/
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
//前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出 ClassNotFoundException。
//应用场景:多用于配置文件,读取类全路径,加载类。
String classAllPath = "com.blue.Car";
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1);
//前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高。
//应用场景:多用于参数传递,比如通过反射得到对应构造器对象。
Class cls2 = Car.class;
System.out.println(cls2);
//前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
//应用场景:通过创建好的对象,获取Class对象
Cat cat = new Cat();
Class cls3 = cat.getClass();
System.out.println(cls3);
//前提:通过类加载器来获取到类的Class对象
//有四种类加载器
//先得到类加载器
ClassLoader classLoader = cat.getClass().getClassLoader();
//通过类加载器得到Class对象
Class<?> cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);
//基本数据类型
Class<Integer> integerClass = int.class;
System.out.println(integerClass);//int
//基本数据类型对应的包装类,可以通过.TYPE 得到Class类对象
Class<Integer> type = Integer.TYPE;
System.out.println(type);//int
//输出对象哈希值
System.out.println(integerClass.hashCode());//1163157884
System.out.println(type.hashCode());//1163157884
//说明两者自动进行装箱和拆箱
}
}
哪些类型有Class对象
- 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
- interface接口
- 数组
- enum枚举
- annotation注释
- 基本数据类型
- void
package com.blue.class_;
import java.io.Serializable;
/**
* User: Blueshadow
* Date&Time: 2021/11/15 17:06
* 演示哪些类型有Class对象
*/
public class AllTypeClass_ {
public static void main(String[] args) {
Class<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
Class<Thread.State> cls6 = Thread.State.class;//枚举//State是指线程状态
Class<Void> cls7 = void.class;//void
Class<Class> cls8 = Class.class;//Class类
System.out.println(cls1);//class java.lang.String
System.out.println(cls2);//interface java.io.Serializable
System.out.println(cls3);//class [Ljava.lang.Integer;
System.out.println(cls4);//class [[F
System.out.println(cls5);//interface java.lang.Deprecated
System.out.println(cls6);//class java.lang.Thread$State
System.out.println(cls7);//void
System.out.println(cls8);//class java.lang.Class
}
}
类加载
基本说明
反射机制是Java实现动态语言的关键,也就是通过反射实现类动态加载。
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强;
- 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性。
类加载时机
- 当类 创建对象时(new)
- 当子类被加载时
- 调用类中的静态成员时
- 通过反射
package com.blue.classload;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Scanner;
/**
* User: Blueshadow
* Date&Time: 2021/11/15 17:33
*/
public class ClassLoad_ {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入key:");
String key = scanner.next();
switch (key){
case "1":
Dog dog = new Dog();//静态加载
dog.cry();
break;
case "2":
Class<?> person = Class.forName("Person");//加载类
Object o = person.newInstance();
Method m = person.getMethod("hi");
m.invoke(o);
System.out.println("ok");
break;
default:
System.out.println("do nothing..");
}
}
}
反射暴破创建实例
通过反射创建对象
- 方式一:调用类中public修饰的无参构造器
- 方式二:调用类中的指定构造器
- Class类相关方法
- newInstance:调用类中的无参构造器,获取对应类的对象
- getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象
- getDecalaredConstrictor(Class...clazz):根据参数列表,获取对应的所有构造器对象
- Constructor类相关方法
- setAccessible:暴破
- newInstance(Object...onj):调用构造器
package com.blue;
import java.lang.reflect.Constructor;
/**
* User: Blueshadow
* Date&Time: 2021/11/15 21:41
* 演示通过反射机制创建实例
*/
public class RefelectCreateInstance {
public static void main(String[] args) throws Exception {
//先获取到User类的Class对象
Class<?> userClass = Class.forName("com.blue.User");
//通过public的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);
//通过public的有参构造器创建实例
/**
* constructor 就是
* public User(String name){
* this.name = name;
* }
*/
//先得到对应的构造器,再创建实例,传入形参
Constructor<?> constructor = userClass.getConstructor(String.class);
Object o1 = constructor.newInstance("王帅");
System.out.println(o1);
//通过非公有的有参构造器创建实例
//先得到私有的构造器对象
Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
declaredConstructor.setAccessible(true);//反射暴破,使用反射可以访问私有构造器
Object o2 = declaredConstructor.newInstance(100, "王昭君");
System.out.println(o2);
}
}
class User{
private int age = 18;
private String name = "王帅";
public User(){}
public User(String name){
this.name = name;
}
private User(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
通过反射访问类中的属性
- 根据属性名获取Field对象
- 暴破
- 访问
- 如果是静态属性,则set和get中的参数 o 可以写成null
package com.blue;
import java.lang.reflect.Field;
/**
* User: Blueshadow
* Date&Time: 2021/11/16 19:21
* 通过反射访问类中的成员
*/
public class RefelctAccessProperty {
public static void main(String[] args) throws Exception {
//得到Student类对应的Class对象
Class<?> cls = Class.forName("com.blue.Student");
//创建对象
Object o = cls.newInstance();//o的运行类型就是Student类型
System.out.println(o.getClass());
Field age = cls.getField("age");
//通过反射来操作属性
age.set(o,88);//通过反射来操作属性
System.out.println(o);
System.out.println(age.get(o));//直接返回属性的值
//使用反射操作静态、私有属性
//得到Field对象
Field name = cls.getDeclaredField("name");
//对 private name 进行暴破
name.setAccessible(true);
name.set(o,"blue");
System.out.println(o);
}
}
通过反射访问类中的方法
- 根据方法名和参数列表获取Method方法对象
- 获取对象
- 暴破
- 访问
- 注意:如果是静态方法,则invoke的参数o,可以写成null
package com.blue;
import java.lang.reflect.Method;
/**
* User: Blueshadow
* Date&Time: 2021/11/16 20:33
* 演示通过反射调用方法
*/
public class RefelctAccessMethod {
public static void main(String[] args) throws Exception {
//得到Class对象
Class<?> cls = Class.forName("com.blue.Boos");
//创建一个对象
Object o = cls.newInstance();
//调用public的方法
// Method hi = cls.getMethod("hi",String.class);
//得到Method对象
Method hi = cls.getDeclaredMethod("hi",String.class);
//调用
hi.invoke(o,"blue");
//调用private方法
//获取到方法对象
Method say = cls.getDeclaredMethod("say", int.class, String.class, char.class);
//因为say方法是私有的,所以需要暴破磁能使用
say.setAccessible(true);
System.out.println(say.invoke(o,18,"blue",'男'));
//因为say方法是static,所以可以...可以给对象参数传值为空
System.out.println(say.invoke(null,200,"李四",'女'));
//返回值问题
//在反射中,如果方法有返回值,统一返回Object对象
Object returnValue = say.invoke(null, 300, "王五", '男');
System.out.println(returnValue.getClass());//运行类型和方法中定义返回类型是一样的
}
}
class Boos{
public int age;
private static String name;
public Boos(){}
private static String say(int n,String s,char c){
return n + ""+s+""+c;
}
public void hi(String s){
System.out.println("hi "+s);
}
}