Java_反射

Java从1.2开始就有了反射这个技术,JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Java领域像Spring、hibernate、struts都依赖于这个技术,把一个对象交给框架,它可以把你的对象通通改掉,如:spring给你注入点东西,struts2把网页提交过来的数据,自动转型封装成action返回给你,hibernate获得你的对象的信息做类与数据库表的映射。

 

反射的基石Class

Java类用来描述一类事物的共性,而Class类就是描述了一个Java类的相关信息的,Class 类的实例表示正在运行的 Java 应用程序中的类和接口。Class类描述的信息有,类的名字、类的访问属性、类锁属于的包名、字段名称的列表、方法名称的列表等。在程序的运行期间,一旦我们想生成那个类的一个对象,用于执行程序的Java 虚拟机首先就会检查那个类型的Class 对象是否已经载入。如果没有载入,JVM 就会查找同名的.class 文件,并将其载入,一旦那个类型的Class 对象进入内存,就以它为模版来创建那一类型的所有对象。

 

以java.lang.String这个类来说明,有两种方式可以获得一个Java类的字节码,就是它对应的Class对象

第一种方法是通过类中一个静态的字段class获得   String.class

第二种方法是通过Java类的基类Object所提供的getClass方法  new String().getClass()

下面的这段代码打印的结果是true,表明每个Java类的字节码在程序中只有一份,这也很容易理解,有一份就够了

 public static void main(String[] args){ 

  //通过上述的第一种方法拿到Class对象
  Class c1 = String.class;

  //通过上述的第二种方法拿到Class对象
  Class c2 = new String().getClass();

  //比较着两份字节码是否相同
  System.out.println(c1== c2
);
 }

Java 基本的 类型(booleanbytecharshortintlongfloat 和 double)和关键字 void 也有自己的 Class 对象 ,这点Java api中Class类的描述中有提到,可以通过类型+.class获得,如int.class、void.class,这九个对象时Java预定义的Class实例对象。数组类型也有他们对象的Class对象,如:int[].class、long.class等。总的来说,只要源程序中出现的类型,都有各自的Class实例对象。

可以通过Class的静态方法forName获得一个Java对象,它的参数是一个Java类的完整名(包名+类名),如果找不到这个类会抛出ClassNotFound这个异常,

public static void main(String[] args){
  try
{
   //拿到java.lang.String这个类的字节码
   Class strCls =Class.forName("java.lang.String");

   //通过这份字节码创建一个String的对象
   String str =(String) strCls.newInstance(); 

   //打印String对象str的长度
   System.out.println(str.length
());
  } catch(ClassNotFoundException e)
{
   e.printStackTrace
();
  } catch(InstantiationException e)
{
   e.printStackTrace
();
  } catch(IllegalAccessException e)
{
   e.printStackTrace
();
 
}
 }

上面这段代码演示了使用Class.ForName获得一个String对象,程序的打印结果为0,表示str不是一个空的引用,否则调用它的length方法就会抛出空指针异常,因此成功的拿到了一个String对象

Class类的常用方法有

public Annotation[] getAnnotations()  返回它所表示的类或接口上存在的所有注释的数组

public ClassLoader getClassLoader()  返回该类的类加载器

public Constructor<T> getConstructor(Class<?>... parameterTypes)  参数为构造函数的类型的Class,返回一个表示类的构造方法的 Constructor 对象

public Field getDeclaredField(String name) 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。name 参数是一个 String,它指定所需字段的简称

public Method[] getMethods()  返回一个包含某些 Method 对象的数组

public boolean isArray()判定此 Class 对象是否表示一个数组类

public boolean isInterface()判定指定的 Class 对象是否表示一个接口类型。

 public boolean isPrimitive()  判定指定的 Class 对象是否表示一个基本类型

。。。。。反射技术就是围绕这这些方法进行的。

现在知道了Class类用来是描述Java类的,java类中的,构造方法、包、成员变量、所拥有的方法,Java也都提供了相应的描述对象,分别是Constructor、Package、Field、Method。

构造函数的反射

一个Constructor就代表一个Java类的构造方法,可以使用它来创建一个Java对象,通过代码来介绍下这个类

获得一个对象身上的所有的构造方法,返回的是一个Constructor的数组,打印出这个数组

public static void main(String[] args){

  //获得String类的所有构造方法
  Constructor[] constructors = String.class.getConstructors
();
  for (Constructor c: constructors)
{
   System.out.println(c
);
 
}
 }

打印结果是

public java.lang.String()

public java.lang.String(java.lang.String)

public java.lang.String(char[])

public java.lang.String(char[],int,int)

public java.lang.String(int[],int,int)

public java.lang.String(byte[],int,int,int)

public java.lang.String(byte[],int)

public java.lang.String(java.lang.StringBuilder)

public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException

public java.lang.String(byte[],int,int,java.nio.charset.Charset)

public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException

public java.lang.String(byte[],java.nio.charset.Charset)

public java.lang.String(byte[],int,int)

public java.lang.String(byte[])

public java.lang.String(java.lang.StringBuffer)

可以看出通过String.class.getConstructors(),这个方法获得了String类的所有构造方法。如果要获得其中的一个构造方法,首先需要解决的问题是怎么样区分这些构造方法,依据是不同的构造方法的参数是不一样,看下Class类中获得指定构造方法的方法public Constructor<T> getConstructor(Class<?>... parameterTypes),它的参数是类型对应的Class,如果要获得无参的那个构造函数,就传递过去一个null值,还是用代码描述

public static void main(String[] args)throws SecurityException,NoSuchMethodException  {
  //获得String类的无参数的构造方法
  Constructor c =String.class.getConstructor(null); 

  System.out.println(c);
  //获得String类的以一个字符数组,构造出String对象的构造方法
  Constructor c1 =String.class.getConstructor(char[].class); 

  System.out.println(c1);
 }

打印结果是

public java.lang.String()

public java.lang.String(char[])

分别获得了对应的构造函数。获得构造函数的意义是,通过这个构造方法获得对象

public static void main(String[] args)throws Exception{ 
  //获得String类的以一个字符数组,构造出String对象的构造方法
  Constructor c1 =String.class.getConstructor(char[].class);

  char[] chars= {'黑','马','程','序','员','训','练','营'};
  //调用c1的newInstance方法,把chars传递过去,返回的是Object,把它强制转换为String类型
  String str1 =(String)c1.newInstance(chars
);
  System.out.println(str1
);
 }

上面程序的打印结果是:黑马程序员训练营

成功的通过构造方法拿到一个String对象,相当于char[] chars = {'黑','马','程','序','员','训','练','营'};  String s = new String(chars);,Class类提供的newInstace方法,其实使用的就是对应类的空的构造方法,为了方便我们编程而提供的此方法。

下面就来说下对Java类各个元素的反射,以下面这个类为例子:

public class Point implementsSerializable {
 private int x
;
 private int y
;
 

 public Point(int x,int y){
  this.x = x
;
  this.y = y
;
 
}

 public String toString()
{
  return "x = "+ x + "," + "y = "+ y
;
 
}
}

成员变量的反射

描述类成员变量的类为Field,它提供有关类或接口的单个字段的信息,以及对它的动态访问权限,它代表的是字节码中的一个成员变量,而不是某个对象的成员变量。反射的字段可能是一个静态字段或者是实例

字段,与获得Constructor类似, Class类也提供了一系列的获得Field的方法。

通过反射拿到 ReflectPoint对象的公共成员变量的值y

public static void main(String[] args)throws Exception{ 
  //new出测试的对象
  ReflectPoint p1 = new
ReflectPoint(520,909);
  //获得ReflectPoint类的成员变量y
  Field field =ReflectPoint.class.getField
("y");
  //得到p1对象,字段y的值
  System.out.println(field.get(p1
));
 }

 程序运行结果:909,使用getField这个方法只能拿到访问符是public的成员,面向对象的三大特性之一是封装,在程序中成员变量一般    都是私有的,获得私有成员变量和获得公共的成员变量有区别。

通过反射拿到 ReflectPoint对象的私有成员变量的值x

public static void main(String[] args)throws Exception{ 
  //new出测试的对象
  ReflectPoint p1 = new
ReflectPoint(520,909);
  //获得ReflectPoint类的成员变量y
  Field field =ReflectPoint.class.getDeclaredField
("x");
  //
私有成员默认是不可访问的,要设置为可以访问
  field.setAccessible
(true);
  //得到p1对象,字段y的值
  System.out.println(field.get(p1)); 

 }

拿到某个私有变量使用的方法getDeclaredField(name),有别于拿到公共对象。私有变量对应的Field,默认是不可见的,使用它之前首先需要通过Field类的setAccessible方法,设置下访问权限为true。这种反射方式也被成为暴利反射,在hibernate中如果把@ID这个注解设置在私有变量上,hibernate就可以获得它的值,使用的就是这个技术。

还可以通过反射把某个对象身上的值改掉

public static void main(String[] args)throws Exception{ 
  //new出测试的对象
  ReflectPoint p1 = new
ReflectPoint(520,909);
  //获得ReflectPoint类的成员变量y
  Field field =ReflectPoint.class.getDeclaredField
("x");
  //
私有成员默认是不可访问的,要设置为可以访问
  field.setAccessible
(true);
  //得到p1对象,字段y的值
  System.out.println("before: "+ field.get(p1
));
  //把p1对象的x值修改为0
  field.set(p1,
0);
  System.out.println("after: "+ field.get(p1
));
 }

方法的反射

对方法的反射使用的是Method这个类,Method类代表某个类中的一个成员方法。拿到字节码中的某个方法Method后,可以对具体一个对象调用这个方法,下面比较一下一般的方法调用和反射调用

public static void main(String[] args)throws Exception{ 
  String str= "黑马程序员训练营";
  Method charAt = Class.forName("java.lang.String").getMethod("charAt",
int.class);
  //若要获得str的第二个字符,通常的方式为
  System.out.println(str.charAt
(2));
  //
通过反射的方式
  System.out.println(charAt.invoke(str,
2));
 }

如果传递给Method对象的invoke方法的第一个参数为null,说明这个Method对象对应的是一个静态方法。

数组的反射

在对数组反射钱,首先要了解下关于数组类型有几个基础知识

1、具有相同维数和元素类型的数组属于同一个类型,既具有相同的Class实例对象

2、基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;而非基本数组,既可以当做Object类型使用,又可以当做Object[]类型使用

对于数组的反射,Array这个工具类提供了支持

现有一个数组,通过反射,获得这个数组的长度,并改掉第0个位置的值

public static void main(String[] args)throws Exception{
  //初始化一个String数组
  String[] strs ={new String("黑"),newString("马")}; 

  //通过反射拿到strs的长度
  System.out.println("反射 " + "strs的长度为"+ Array.getLength(strs)); 

  //通过反射改掉strs[0]
  Array.set(strs,0, new String("
黑黒"));
  //遍历strs数组,若打印出黑黒马,则反射成功
  for (String s: strs)
{
   System.out.print(s
);
  } 

 }

从上面的程序可以看出,对数组的反射较为简单,使用jdk为我们提供的Array工具类即可

关于反射,大致就有这么多。关于反射大家可以去看张孝祥老师的Java高新技术视频,下载地址

http://www.itcast.cn/channel/video.shtml#java

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值