Java是静态语言,运行时结构不可变。但Java有一定的动态性,可以利用反射机制、字节码操作获得类似动态语言的特性。
Java中的反射(Reflection)是一种强大的机制,它允许程序在运行时获取和操作类、接口、字段和方法等元数据信息。通过反射,Java程序可以在运行时动态地创建对象、调用方法和访问字段,而不需要在编译时知道这些信息。比如登录还是注册、用支付宝还是微信支付,运行时才知道。
在编译后产生字节码文件的时候,类加载器子系统通过二进制字节流,负责从文件系统加载class文件。在执行程序(java.exe)时,将字节码文件读入JVM中,这个过程叫做类的加载。然后在内存中对应创建一个java.lang.Class类的对象,这个对象会被放入字节码信息中,这个Class对象就对应加载那个字节码信息,这个对象将被作为程序访问方法区中的这个类的各种数据的外部接口。所以,可以通过这个对象看到类的结构,这个对象像是一面镜子,透过镜子看到类的各种信息,形象的称之为反射。这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。
说明:在运行期间,要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。
举例:对于Student类:通过Java.Lang.Class类,可以得到具体的Class类的对象(Student类字节码的信息),从而获取对象内容——属性、方法、构造器。
要获取Person类的字节码信息:4种方式
//通过getClass()方法获取:
Person p = new Person();
Class c1 = p.getClass();
//通过内置class属性:
Class c2 = Person.class;
//调用Class类提供的静态方法forName:
Class c3 = Class.forName("com.ss.test02.Person");
//利用类的加载器:
ClassLoader loader = Test.class.getClassLoader();
Class c4 = loader.loadClass("com.ss.test02.Person");
类(外部类、内部类)、接口、注解、数组、基本数据类型、void
//作为一个父类
public class Person implements Serializable {
//属性
private int age;
public String name;
//方法
private void eat(){
System.out.println("Person---eat");
}
public void sleep(){
System.out.println("Person---sleep");
}
}
//Student作为子类
@MyAnnotation(value="hello")
public class Student extends Person implements MyInterface{
//属性:
private int sno;//学号
double height;//身高
protected double weight;//体重
public double score;//成绩
//方法:
@MyAnnotation(value="himethod")
public String showInfo(){
return "曾经的你";
}
public String showInfo(int a,int b){
return "重载方法--曾经的你";
}
private void work(){
System.out.println("曾梦想执杖走天涯");
}
void happy(){
System.out.println("如今你四海为家");
}
protected int getSno(){
return sno;
}
//构造器
public Student(){
System.out.println("空参构造器");
}
private Student(int sno){
this.sno = sno;
}
Student(int sno,double weight){
this.sno = sno;
this.weight = weight;
}
protected Student(int sno,double height,double weight){
this.sno = sno;
}
@Override
@MyAnnotation(value="hellomyMethod")
public void myMethod() {
System.out.println("重写了myMethod方法。。");
}
@Override
public String toString() {
return "Student{" +
"sno=" + sno +
", height=" + height +
", weight=" + weight +
", score=" + score +
'}';
}
}
public class Test01 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取字节码信息:
Class cls = Student.class;
//通过字节码信息可以获取构造器:
//getConstructors只能获取当前运行时类的被public修饰的构造器
Constructor[] c1 = cls.getConstructors();
for(Constructor c:c1){
System.out.println(c);
}
System.out.println("-------------------");
//getDeclaredConstructors:获取运行时类的全部修饰符的构造器
Constructor[] c2 = cls.getDeclaredConstructors();
for(Constructor c:c2){
System.out.println(c);
}
System.out.println("-------------------");
//获取指定的构造器:
//得到空构造器
Constructor con1 = cls.getConstructor();
System.out.println(con1);
//得到两个参数的有参构造器:
Constructor con2 = cls.getConstructor(double.class, double.class);
System.out.println(con2);
//得到一个参数的有参构造器:并且是private修饰的
Constructor con3 = cls.getDeclaredConstructor(int.class);
System.out.println(con3);
//有了构造器以后我就可以创建对象:
Object o1 = con1.newInstance();
System.out.println(o1);
Object o2 = con2.newInstance(180.5, 170.6);
System.out.println(o2);
}
}
public class Test02 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
//获取运行时类的字节码信息:
Class cls = Student.class;
//获取属性:
//getFields:获取运行时类和父类中被public修饰的属性
Field[] fields = cls.getFields();
for(Field f:fields){
System.out.println(f);
}
System.out.println("---------------------");
//getDeclaredFields:获取运行时类中的所有属性
Field[] declaredFields = cls.getDeclaredFields();
for(Field f:declaredFields){
System.out.println(f);
}
System.out.println("---------------------");
//获取指定的属性:
Field score = cls.getField("score");
System.out.println(score);
Field sno = cls.getDeclaredField("sno");
System.out.println(sno);
System.out.println("---------------------");
//属性的具体结构:
//获取修饰符
/*int modifiers = sno.getModifiers();
System.out.println(modifiers);
System.out.println(Modifier.toString(modifiers));*/
System.out.println(Modifier.toString(sno.getModifiers()));
//获取属性的数据类型:
Class clazz = sno.getType();
System.out.println(clazz.getName());
//获取属性的名字:
String name = sno.getName();
System.out.println(name);
System.out.println("-------------------------------");
//给属性赋值:(给属性设置值,必须要有对象)
Field sco = cls.getField("score");
Object obj = cls.newInstance();
sco.set(obj,98);//给obj这个对象的score属性设置具体的值,这个值为98
System.out.println(obj);
}
}
public class Test03 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//获取字节码信息:
Class cls = Student.class;
//获取方法:
//getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰)
Method[] methods = cls.getMethods();
for(Method m:methods){
System.out.println(m);
}
System.out.println("-----------------------");
//getDeclaredMethods:获取运行时类中的所有方法:
Method[] declaredMethods = cls.getDeclaredMethods();
for(Method m:declaredMethods){
System.out.println(m);
}
System.out.println("-----------------------");
//获取指定的方法:
Method showInfo1 = cls.getMethod("showInfo");
System.out.println(showInfo1);
Method showInfo2 = cls.getMethod("showInfo", int.class, int.class);
System.out.println(showInfo2);
Method work = cls.getDeclaredMethod("work",int.class);
System.out.println(work);
System.out.println("-----------------------");
//获取方法的具体结构:
/*
@注解
修饰符 返回值类型 方法名(参数列表) throws XXXXX{}
*/
//名字:
System.out.println(work.getName());
//修饰符:
int modifiers = work.getModifiers();
System.out.println(Modifier.toString(modifiers));
//返回值:
System.out.println(work.getReturnType());
//参数列表:
Class[] parameterTypes = work.getParameterTypes();
for(Class c:parameterTypes){
System.out.println(c);
}
//获取注解:
Method myMethod = cls.getMethod("myMethod");
Annotation[] annotations = myMethod.getAnnotations();
for(Annotation a:annotations){
System.out.println(a);
}
//获取异常:
Class[] exceptionTypes = myMethod.getExceptionTypes();
for(Class c:exceptionTypes){
System.out.println(c);
}
//调用方法:
Object o = cls.newInstance();
myMethod.invoke(o);//调用o对象的mymethod方法
System.out.println(showInfo2.invoke(o,12,45));;
}
}
public class Test04 {
public static void main(String[] args) {
//获取字节码信息:
Class cls = Student.class;
//获取运行时类的接口:
Class[] interfaces = cls.getInterfaces();
for(Class c:interfaces){
System.out.println(c);
}
//得到父类的接口:
//先得到父类的字节码信息:
Class superclass = cls.getSuperclass();
//得到接口:
Class[] interfaces1 = superclass.getInterfaces();
for(Class c:interfaces1){
System.out.println(c);
}
//获取运行时类所在的包:
Package aPackage = cls.getPackage();
System.out.println(aPackage);
System.out.println(aPackage.getName());
//获取运行类的注解:
Annotation[] annotations = cls.getAnnotations();
for(Annotation a:annotations){
System.out.println(a);
}
}
}
对于大多数情况,直接使用new
关键字来创建对象是最简单和最直接的方式。反射主要用于处理更复杂和动态的场景,如动态加载类、处理未知类型的对象。
try {
Class<?> clazz = Class.forName("Person"); // 获取Person类的Class对象
Constructor<?> constructor = clazz.getDeclaredConstructor();
// 获取Person类的构造方法
Object obj = constructor.newInstance(); // 创建Person类的对象
} catch (Exception e) {
e.printStackTrace(); }
首先获取了Person
类的Class
对象,然后获取了其默认构造方法,并使用newInstance
方法创建了新的对象。
反射在某种程度上确实可能破坏面向对象的封装性。面向对象的封装性主要是为了解决安全性问题,而反射主要是为了解决程序的动态性问题。通过反射,我们可以访问或调用private修饰的属性和方法,这在正常情况下是不被允许的。
一般来说,并不建议随意使用反射来访问或修改对象的私有属性和方法,因为这可能会破坏对象的封装性,降低代码的安全性和稳定性。如果确实需要使用反射,应该非常谨慎,并确保不会对程序的正常运行造成负面影响。