java 反射

java 反射


 

要想知道反射,就需要先了解一下类Class,这是一个java类,而不是关键字。
个人观点,类通俗的说就是描述同一类事物的抽象。比如说人这个类就表述了人。汽车这个类描述了所有汽车。
那么在java程序中java类也是同一类事物,那么用什么类描述java类,这里使用Class这个类。

Person p1 = new Person();
Person p2 = new Person();

Class cls1 = Date.class//字节码1;
Class cls2 = Person.class//字节码2;

当我们在源程序中用到Person这个类的时候,首先要把这个类的二进制代码加载到内存中,加载到内存中的这些二进制代码就叫做字节码,然后才可以用这个字节码复制出一个一个的对象。

p1 就是那个字节码文件创建的对象,那么这个对象就可以使用方法getClass()得到创建自己的字节码。

还可以用Class.forName("java.lang.String");得到类的字节码。
得到类的字节码有两种情况,第一种字节码文件已经加载到内存中了,那么这里就不用加载字节码文件了,直接找到返回就可以了。另外一种要得到一个字节码,那么就需要使用类加载器去加载,在内存中缓存起来,然后再用类加载器返回字节码文件。
返回的方式有两种,一种是这份字节码已经被加载过,直接返回。另一种JVM中没有字节码文件,那么就使用

类加载器加载字节码,然后放在缓存中缓存起来,以后就不用在加载了。

得到字节码的方式有三种:
第一:在源程序中直接写上类的名字:Date.class
第二:使用一个对象调用getClass()方法,因为一个对象是有一个类创建的,那么就可以用这个对象得到创

建自己的类。Date d1 = new Date(); Class clazz =  d1.getClass();
第三:使用Class的静态方法Class.forName("java.util.Date");这样就得到了Date这个类的字节码文件。

在用反射的时候经常用到第三种,因为程序中用到的类还不知道,所以里面的类名可以是一个变量,用到什么就传递什么。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
Class clazz = void.getClass();  这个也是一个字节码。

数组类型不是一个基本类型,它是数组类型,判断一个对象是否是数组的实例对象使用isArray()方法。
Class.isArray();

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void...

反射就是把Java类中的各种成分映射成相应的java类。
比如说: 一个类有包,那么就会有一个getPackage()的方法返回一个Package的对象。还有各种方法,返

回的方法getMethod()是Method的对象。 一个类里面有很多的成分,把每一个成分解析成一个相应的类,这个就叫反射。

类里面的所有成分都是相应的类的对象来表示。
例如:
System里面有很多方法
System.exit();
System.getProperties();
这些方法对应的Method类里面的不同的对象,methodObj1,methodObj1。这两个对象都是同一个类型。得到一个Method的对象就得到了一个类里面的方法。

Field类
类身上的字段,也就是成员变量,也是用一个类表示的,Field。
要获得一个类身上的成员变量可以使用如下方法:
首先写一个专门用来反射的类ReflectPoint 。
public class ReflectPoint {
 private int x ;
 public int y ;
 
 public ReflectPoint(int x, int y) {
  super();
  this.x = x;
  this.y = y;
 } 
 
 public String str1 = "ball" ;
 public String str2 = "basketball" ;
 public String str3 = "itcast" ;
 
 @Override
 public String toString(){
  return str1+" : "+str2 +" : "+str3;
 }
}

ReflectPoint pt1 = new ReflectPoint(3, 5) ;
Field fieldY = pt1.getClass().getField("y") ;//要得到某一个字段,那么就要先得到这个类的字节码文件,然后再用方法得到那个字节码文件成员变量。


Constructor构造方法类
这个是类的对象代表一个字节码里面的一个构造方法。
要想得到一个类身上的所有的构造方法,使用Constructor [] constructor = Class.forName("java.lang.String).getConstructors();
得到单独一个构造方法Constructor constructor = Class.forName("java.lang.String).getConstructor();

要想得到一个明确的构造方法,使用Class.getConstructor(Class<?>...) 这里的的参数可以多个,也可以是一个,这正是因为JDK1.5的新特性,可变参数带来的方便。这里通过参数的类型可以明确的得到一个你需要的构造方法。

下面看一些具体的例子:
//new String(new StringBuffer("abc"));
Constructor constructor1 =String.class.getConstructor(StringBuffer.class) ;//这里也可以写很多的参数,这是因为JDK1.5的可变参数的新特新
//上面的StringBuffer的作用是用得到参数是StringBuffer的构造方法
String str2 = (String)constructor1.newInstance(new StringBuffer("abc")) ;//这里的StringBuffer表示用这个构造方法的时候还需要穿一个StringBuffer的对象进去。
//上面的代码在编译的时候这个构造放还不知道是谁的构造方法,所以需要明确转换为String类型的构造方法。
//使用constructor1.newInstance()方法得到一个构造方法,然后用这个构造方法去创建一个实例对象。
System.out.println(str2.charAt(2));
//如果把程序改成下面的这样,看看会出什么错误
//String str3 = (String)constructor1.newInstance("abc") ;//在这里我们的constructor1这个构造方法是参数为StringBuffer的构造方法,但是这里传入的参数是String类型,所以会报错参数类型不匹配。
//argument type mismatch
//在这里主义的问题有两个,得到某个构造方法的时候需要参数的类型,调用获得的方法时要用到上面相同的实例对象。

class-->constructor-->new object,这是我们创建一个对象的流程。
也可以直接用Class.newInstance();这个方法是用不带参数的构造方法穿件一个对象。如果要用很多次这个构造方法,那么就把这个可以使用这个方法,因为这个方法会缓存在内存中。但是如果只调用一次就很麻烦,而且消耗大。所以是用很多次的时候才用这个。
反射会导致程序的性能下降。

//fieldy不是对象身上的变量值,只代表这个字节码文件里面的成员变量,不代表某一个具体对象的值。要用它去取某个对象上对应的y值。这里的fieldy只是一个类里面的成员变量,但是还不是一个对象上面的具体的类,要想获得某个具体对象的值,需要fieldY.get(pt1);调用此方法,确切的指明是那个对象的值。

获得私有成员变量的值
Field fieldX = pt1.getClass().getDeclaredField("x") ;//加入这里的x是一个私有变量,因为x这个变量

是一个私有的,外部不可见,所有要用这个方法可以获得到你申明的所有的成员变量,这里虽然获得这个x,但是不能访问这个私有成员变量。
fieldX.setAccessible(true);//这里是设置私有成员变量可以被访问,不管你的成员变量是私有的还是共有的,都可以被访问。
System.out.println(fieldX.get(pt1));

下面再看一个难一点例子
现在有很多的String类型的成员变量,把里面所有的成员变量的a改变成b;private static void

changedStringValue(Object obj) throws Exception{
  Field [] fields = obj.getClass().getFields() ;
  for (Field field : fields) {
   if((field.getType()) == String.class){//这里的比较一定要用==,因为这里的字节码文件只有一份。
    String oldValue = (String)field.get(obj) ;
    String newValue = oldValue.replace('b', 'a') ;
    //System.out.println(newValue);虽然这里已经换了,但是那个对象身上的值还没有改变,所以还需要把现在新值赋值进去。    field.set(obj, newValue);
   }
  }
 }

通过上面的方法之后,就把ReflectPoint 这个类里面原来string类型的变量的值 给改变了。
通过一个对象,改变这个类里面的其他的成员的值或者动作。

Method方法类

通过一个对象或者类得到一个类的字节码文件,通过一个Method的实例获得一个类里面的方法,这个这个方法不是某一个对象的上的方法,方法是属于类的,要想调用这个方法,必须要通过某个对象调用方法。
如何得到一个类String身上的charAt()方法。

Method methodCharAt = String.class.getMethod("charAt", int.class) ;//使用反射的方法得到String这个类的字节码文件的里面的方法,这里获得方法是charAt
System.out.println(methodCharAt.invoke(str1, 1)) ;//然后把得到方法作用到某一个具体的对象。 调用str1上的charAt方法。invoke这个方法是Method这个类里面的对象。
//首先获的Method的对象获得String这个类里面的方法,然后让这个方法作用到某个对象上,怎么用呢,这里让invoke这个方法通知str1对象来调用这个charAt方法。
//methodCharAt.invoke(null, 1)//如果代码写成这样,那么这个是调用类里面的什么方法呢,首先来看,这里的要调用的方法没有接受一个参数,说明这个方法不需要对象就可以调用,那么这个方法就是一个静态方法
//因为静态方法可以直接用类来调用。
System.out.println(methodCharAt.invoke(str1, new Object[]{2})); //这里是什么意思呢,这里使用的是JDK1.4的语法,因为在JDK1.4里面没有可变承参数
//这里的new Object[]{2} 代表的是这里的数组里面放的内容是2. 
//new int[]{2} ,相信大家这个都可以看懂,因为这里代表这个int数组里面的内容是2,那么int也属于Object,所以这里直接换成Object,那么有人就会问了,这里是一个Object,并不是一个整型数据,这里的JDK1.5有自动装箱功能,他会把这个2自动封装成integer。然后这个数组里面的内容就是2.所以相当于这里传入了一个整数。


自己写程序调用人家的main方法
为什么要用反射的方式调用其他类里面的主方法,因为你在运行程序的过程中是不知道到底你在运行哪一个类,那么你就可以通过反射的方式来调用某一个类的main方法,把你要调用的那个类当做一个参数传递进去。看如下例子:
String startingName = args[0] ;//这里通过获得完整的一个类名
Method mainMethod = Class.forName(startingName).getMethod("main", String[].class) ; //在这里通过Method获得类里面的主方法,这个主方法不是属于某一个类。
//mainMethod.invoke(null, new String[]{"abc","xyz","123"}) ;//在原来的主方法中接受的是一个参数,是一个字符串数组类型,现在的这个数组相当于是一个Object数组吗,
//main方法接受到这个数组是,他会把这个数组打开,结果就是三个参数了,而不是一个数组参数,就会认为是收到三个参数,所以这里需要改变参数。
mainMethod.invoke(null, new Object[]{ new String[]{"abc","xyz","123"}}) ;//这里把这个String数组封装成一个object数组,在main方法的参数接收到这个Object数组是打开数组,然后里面是一个数组,
//这就满足了参数的要求。
mainMethod.invoke(null, (Object)new String[]{"qwe","wew","123"}) ;//在这里的转换时直接把这个数组转换成了Object,这个是为什么呢,因为数组是一个Object,
//这里的意思是传过来的是一个Object对象,而不是一个数组。是一个数,这个数是由一个String数组组成的。


数组的反射
数组是具有相同的元素类型,也有相同的维度的数据。
int [] a1  = new int[]{1,2,3} ; //当给数组具体赋值了,就不能在制定个数了
int [] a2 = new int [4] ;
int [][] a3 = new int [2][3] ;
String [] a4 = new String []{"a","b","c"} ;//有一个数组,数组里面装的是int数组。
System.out.println(a1.getClass() == a2.getClass());//同样数据类型和维度数组是同一份字节码文件。
//System.out.println(a1.getClass() == a4.getClass());在这里我的eclipse是4.3的版本他在这里就检查不能通过了,所以不能编译。因为这两个字节码文件
//System.out.println(a1.getClass() == a3.getClass());
  
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
  
Object aObj1 = a1 ;  //int数组是一个Object对象。但是不是一个Object的数组的。
Object aObj2 = a4 ;  //
//Object [] aObj3 = a1 ;//这里会报错,为什么,因为基本类型的数组不是Object类型数组,所以不能转换。
Object [] aObj4 = a3 ; //这里的a3是一个二维的int数组,int数组是一个object对象,这个a3里面装了一个int类型的数组,所以是对的。
Object [] aObj5 = a4 ;//这里的a4是一个String数组,String数组是Object类型的数组。
System.out.println(a1);//打印结果[I@156b6b9,这表示是一个int类型的数组,数组的hashcode值是

156b6b9
System.out.println(a4);//[Ljava.lang.String;@1f66cff表示是一个String类型数组,数组的hashcode值是1f66cff
  
//适应Arrays这个类里面的方法,把一个数组转换成一个list
System.out.println(Arrays.asList(a1));//整数的不能被转换,虽然也转换成了一个数组,但是这个数组里面的内容只有一个值,[I@1f66cff;
//为什么会这样呢,因为在这里的参数接受的是一个Object的类型的数组,但是这你你传进来的是一个int类型的数组,这时Object不管这个参数。如果传递一个string
// 类型的数组,就可以被转换成数组,因为String类型的数组是一个Object类型的数组。
System.out.println(Arrays.asList(a4)); //对于字符串的数组可以直接转换陈数组打印出来对数组进行反射

private static void printObject(Object obj) {
  // TODO Auto-generated method stub
  Class clazz = obj.getClass() ;//首先得到这个对象的字节码文件
  if(clazz.isArray()){//判断这个字节码文件是否是一个数组,如果是一个数组那么就打印出这个数组里面的内容
   int len = Array.getLength(obj) ;//使用Array.getLength()方法得到数组的长度
   for(int i = 0 ;i < len ; i++){
    System.out.println(Array.get(obj, i));//Array.get(obj,i);这个方法是得到指定类型的指定位置的数组的元素的值。
   }
  }else{
   System.err.println(obj);
  }
  
 }

 


### Java反射机制使用教程及常见问题 Java反射机制是一种强大的工具,允许程序在运行时动态地检查和操作类、接口、字段和方法等内部信息[^1]。以下是关于Java反射机制的详细教程以及一些常见的问题。 #### 1. 反射机制的基本概念 Java反射机制是Java语言中一种动态访问、检测和修改自身的能力,主要作用是在运行时获取类的完整结构信息并对其进行操作[^2]。通过反射,可以实现以下功能: - 动态创建对象。 - 动态调用方法。 - 动态访问字段。 - 动态处理注解。 #### 2. 核心类与API Java反射的核心类位于`java.lang.reflect`包中,主要包括以下几类: - `Class`:表示类的运行时类对象,可以通过`Class.forName(String className)`或`Object.getClass()`获取。 - `Field`:表示类的成员变量。 - `Method`:表示类的方法。 - `Constructor`:表示类的构造方法。 #### 3. 使用示例 以下是一个简单的反射机制使用示例,展示了如何通过反射创建对象并调用方法。 ```java import java.lang.reflect.Method; public class ReflectionExample { public static void main(String[] args) throws Exception { // 获取目标类的Class对象 Class<?> cls = Class.forName("com.example.MyClass"); // 创建目标类的实例 Object obj = cls.getDeclaredConstructor().newInstance(); // 获取目标类的方法 Method method = cls.getMethod("myMethod", String.class); // 调用目标类的方法 method.invoke(obj, "Hello Reflection"); } } ``` #### 4. 性能问题 反射机制虽然强大,但在性能上存在一定的开销。例如,在大量调用反射方法时,其性能可能显著低于直接调用[^4]。因此,在实际开发中应权衡使用反射的必要性。 #### 5. 常见问题 - **安全性问题**:反射可以访问私有成员,这可能导致破坏封装性[^1]。 - **性能问题**:反射调用方法的性能比普通方法调用低[^4]。 - **异常处理**:反射操作容易抛出多种异常,如`ClassNotFoundException`、`NoSuchMethodException`等,需要妥善处理。 #### 6. 实际应用场景 Java反射机制在许多实际场景中发挥了重要作用,包括但不限于框架设计、动态代理、依赖注入、测试工具和序列化/反序列化等[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值