(9) - 反射 (图)

---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流! ----------------------


1、 反射概述

反射的概念主要指类可以访问、检测和修改它本身状态或行为的一种能力,它能让java类自身进行检查,或者说是”自审”,并能直接操作类的内部属性。我们简单说就是它能把Java类中的各种成分映射成相应的Java类。

那么什么是反射机制呢?

JAVA反射机制:是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

举个简单的例子,如现在有一个普通类,那么这个类就能创建对象(正向)。如果现在要通过一个对象找到一个类的名称(反向),此时就需要用到反射机制了。这个更多的是用在运行时期的对类的装配。

 

Java反射机制主要提供了以下功能:

(1)   在运行时判断任意一个对象所属的类。

(2)   在运行时构造任意的一个类的对象。

(3)   在运行时判断任意一个类所具有的成员变量和方法。

(4)   在运行时调用任意一个对象的方法。


2、 Class类,反射的基石

现实世界的各种事物通过面向对象的思维方式,都能抽象出一个个用于描述该类事物的类,类再构造出具体的实例。而类这种抽象的事物又由谁来描述呢?java给出了首字母大写的Class类来描述类这种事物。Class描述了一个类从类名到成员的各个成分。反射既然要获得一个类的各个成分,那么反射就得依靠Class实现这样的操作。

 

(1)   Class和class的区别

class(首字母小写):即我们通常意义上的java类,用于描述一类事物的共性集合,封装了这类事物的属性和行为。并能实例化成不同的对象。

Class(首字母大写):对java中类的一种总称,用来操作类的一种类型。通过该类型能够获取一般类的结构。


(2)   字节码

java源程序(*,java文件)通过java编译器转换成class文件,class文件中保存的即为字节码,class文件也叫字节码文件。Class的一个对象就是指向一个类的字节码,如Class cl = People.class;从而用cl操作People类的成员。


(3)   获取类的字节码的三种方式

(a)    类名.class,如Person.class。

(b)    对象.getClass(),如new Person().getClass()。

(c)    Class.forName(“类的全名”),如Class.forName(“java.util.Date”)。

在开发时多采用第三种方式,因为在编码期可能只是定义一个框架,并不知道具体会用到哪个类,而在运行期,从配置文件传入一个类的全名字符串,用第三种方式获取这个类的字节码,进而获取这个类的成员变量和方法。第三种方式的灵活性显然要高于前面两种需先知道类名和对象名的方式。


(4)   各类型都有Class实例对象

九个预定义Class实例对象,即八个基本数据类型(boolean、byte、char、short、int、long、float 和 double)和一个void类型的字节码对象。也就是说他们都有自己的Calss对象,void的是void.class。

 

另外基本数据类型的包装类,虽不是基本类型,但他们内部都有一个静态的成员TYPE,它表示的是基本类型的Class对象。那么也就有int.class == Integer.TYPE的结果true,即两份Class对象相等的。

 

可以用Class下的isPrimitive()方法来判断该字节码对象是否为基本数据类型。

 

而数组类型的Class对象,用Class的isArray()方法来判断是否为数组类型。

 

总之,在源程序中出现的类型都有各自的Class对象。

下面看一个小例子:

public class ClassDemo
{
public static void main(String[] args) throws ClassNotFoundException
{
        String str1 = "abc";
//获取类字节码的三种方式
        Class cls1 = str1.getClass();
        Class cls2 = String.class;
        Class cls3 =Class.forName("java.lang.String");
       
        //两者结果都为true,证明是String类型都是相等的字节码
        System.out.println(cls1 == cls2);
        System.out.println(cls1 == cls3);
       
        System.out.println(cls1.isPrimitive());//结果false
        System.out.println(int.class.isPrimitive());//结果true,int是基本数据类型
        System.out.println(int.class ==Integer.class);//false
        System.out.println(int.class ==Integer.TYPE);//true,用Integer的TYPE字段行
        System.out.println(int[].class.isPrimitive());  //false,int数组是种类型,但不是基本类型
       System.out.println(int[].class.isArray());//true,数组有专门的判断
}
}

3、 反射的用法

像上面所述,反射就是把Java类中的各个成分映射成相应的java类。而Class类提供了一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息。这些信息就是用相应的类的实例对象来表示的,他们是Field(变量或字段),Method(方法),Contructor(构造器),Package(包)等等。

 

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。下面分别介绍几个反射API类:

(1)   Constructor类

该类代表某个类的构造方法,由Class类相应的函数返回Constructor对象,而通过该对象能反向得到构造函数的类,构造函数的修饰符和构造函数的名字等等。

Class返回类构造函数的方法

//返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法,需指定构造函数一个或多个参数的字节码。
Constructor<T>getConstructor(Class<?>... parameterTypes)
//返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
 Constructor<?>[] getConstructors()

 通过某类的Class获得构造函数:

(a)   得到某个类所有的构造方法:

如:Constructor[] cons = Class.forName(“java.lang.String”).getConstructors();

由于要得到多个构造方法,所以定义成数组接收。


(b)   得到某类的一个构造方法

如:Constructor con =Class.forName().getConstructor(StringBuffer.class);//指定了一个参数的构造函数。


(c)   得到多个参数的构造函数

Constructor con= Class.forName().getConstructor(StringBuffer.class, int.class);

传入的多个参数都要求时Class对象。


(d)   用得到的构造函数创建对象

newInstance()方法创建一个对象,若不传参,代表构造一个无参的对象,若需指定参数,需与获取con对象时指定的参数类型一致,像平时的有参构造函数一样的用法,只不过用newInstance代替了。


通常方式:String str = new String(“abc”);

反射方式:String str = (String)con.newInstance(new StringBuffer("abc"));

创建构造函数不带参数的对象:

String str2 = (String)Class.forName("java.lang.String").newInstance();

 

看下面的例子:

import java.lang.reflect.Constructor;
 
public class ConstructorDemo
{
public static void main(String[] args) throwsException
{
        Constructor  con =String.class.getConstructor(StringBuffer.class);
        String str = (String)con.newInstance(newStringBuffer("abc"));
        //获得第二个字符
        System.out.println(str.charAt(2));
       
        //创建构造函数不带参数的对象
        String str2 =(String)Class.forName("java.lang.String").newInstance();
}
}


为什么不直接用普通的方法创建对象呢?还这么麻烦,反向获取再来创建对象?

其实这是相对于应用场景来说的,在这里看不出效果,但如果放到一个框架中,先不确定类型,在运行时通过配置文件来确定使用的类时,反射方式的优势就体现出来了。


(2)   Field类

该类代表某个类中的一个成员变量。通过Class类的对应方法返回Field的对象。

Class类中主要获取方法:

//返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。传入要获取的成员变量名字符串,要求需为public修饰的变量(字段)。
public Field getField(String name);
//获取类字节码中所有的public变量。
public Field[] getFields();
获取字段后,用Field中的主要方法操作字段的内容:
//返回指定对象上此 Field 表示的字段的值,传入要字段所属对象
Object get(Object obj)
//将指定对象变量上此 Field 对象表示的字段设置为指定的新值,即改变字段的内容
void set(Objectobj, Object value)
//得到该字段所属类型
Class<?>getType()

暴力反射:

强制访问对象中的非public成员。通过Class类中的:

//返回一个 Field 对象,获取到Class对象对应字段,包括私有
Field getDeclaredField(String name)
//返回 Field 对象的一个数组.
Field[] getDeclaredFields()

获得字段后,再通过setAccessible方法设置字段的访问性,即传入参数设置成true。

看下面例子:

import java.lang.reflect.Field;
 
public class FieldDemo
{
public static void main(String[] args) throws Exception
{
        //Point一个测试类
        Point pt1 = new Point(3,5);
       
        //fieldY只代表字段名,不表内容
        Field fieldY =pt1.getClass().getField("y");
        //这样才是取变量内容
        System.out.println(fieldY.get(pt1));
       
        //暴力反射,访问类的私有变量
        Field fieldX =pt1.getClass().getDeclaredField("x");
        //将该字段,设置成可访问
        fieldX.setAccessible(true);
        System.out.println(fieldX.get(pt1));
       
        //测试例子
        Field[] fields =pt1.getClass().getFields();
        for(Field field : fields)
        {
               //field.getType() 获取字段类型
               //因String的字节码都使用同一份字节码,所以用==好些
               if(field.getType() ==String.class)
               {
                      //替换字段中的字符串内容,将b全改成a
                      String oldValue =(String)field.get(pt1);
                      StringnewValue=oldValue.replace('b', 'a');
                      field.set(pt1,newValue);//设置
               }
        }
       System.out.println(pt1.toString());
}
}
//一个测试类
class Point
{
private int x;
public int y;
public String str1 = "xxax";
public String str2 = "xbbx";
public Point(int x, int y)
{
        this.x = x;
        this.y = y;
}
public String toString()
{
        return str1+".."+str2;
}
}


(3)   Method类

该类代表某类的方法。通过Class类的对应方法返回Method的对象,其方法主要有:

//返回一个 Method 对象,返回一个public的方法,传入方法名和可变参数
Method  getMethod(String name, Class<?>... parameterTypes)
//返回一组Method 对象,返回Class对象的所有方法
Method[]  getMethods()
//获取Method对象后,调用该方法使用,参数1为方法所属对象,参数2为方法接收的参数,可接收多个,所以是可变参数
Object  invoke(Object obj, Object... args) 

看下面的例子:

import java.lang.reflect.Method;
 
public class MethodDemo
{
  public static void main(String[] args) throws Exception
  {
         String str1 = "abc";
         //通常方法str1.charAt(1);
         //反射方法如下,传入方法名,和参数
         Method methodCharAt =String.class.getMethod("charAt", int.class);
         //methodCharAt调用invoke执行methodCharAt所代表的方法
         //需传入methodCharAt所属对象和所需参数,若对象参数传入null,则指向一个静态方法。
         System.out.println(methodCharAt.invoke(str1,1));
  }
}


注意:

invoke第一个参数为null时,代表的是一个静态方法,不存在对象,但需要有参数,如invoke(null,  1,  2); 后面的1和2是该静态方法的要接收的实参。


当某类的方法参数需传入一个数组时,即调用invoke的第二个参数需要传入一个数组对象,但是编译器不会将其当做一个数组对象,而是将数组拆开,当做多个参数,这时将会出错。那么我们将数组对象用Object强转一次,打成一个Object对象,如:(Object)(new String[](“111”,”222”,”3333”));或打包成另外一个数组:new Object[]{new String[]{"111","222","333"}}。


4、 数组的反射

具有相同元素类型和相同维数的数组是同一份字节码。

 

基本数据类型不属于Object,所以装int的一维数组不能转装Object的一维数组,因为从第一个元素开始就不能转了。而二维却可以,相当于一维存入了数组类型,数组类型是可以转成Objct。

对待那些数组能否转Object数组,看同一维数下元素是否能转Object元素。像 Object[]obj1 = a1;就不行,a1存入的是int,obj1存入的是Object,而元素int不能转Object。

 

利用反射只能获取数组中某个元素的类型。

 

看下面的例子:

import java.util.Arrays;
 
public class ArrayReflect
{
public static void main(String[] args)
{
        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"};
       
        sop(a1.getClass() == a2.getClass());//true,同维数同元素类型同一份字节码
        sop(a1.getClass().equals(a4.getClass()));//false,元素不是同类型
        sop(a1.getClass().equals(a3.getClass()));//false,维数不同
        System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object,获取数组父类名
       
        Object obj = a1;//数组引用类型可转object
        //Object[] obj1 = a1;//报错,a1存入的是int,obj1存入的是Object,而元素int不能转Object
        Object[] obj2 = a3;//二维的a3,相当于在一维中存入了数组类型,所以可转
        Object[] obj3 = a4;//a4存入的是String,obj3存入的是Object,String可转Object
 
        sop(a1);//[I@1fb8ee3,打印的是a1的哈希值
       
        //[[I@1fb8ee3],因asList需传入Object可变参数,a1转obj
        //不能拆成一个int对象去对应obj[]元素,只能转一个obj
        sop(Arrays.asList(a1));
        //a4转obj,a4的元素被拆开成一个个String,可对应obj[]的元素
        sop(Arrays.asList(a4));//[a, b, c]
}
 //输出
 public static void sop(Object obj)
 {
        System.out.println(obj);
 }
}


5、 反射的作用:实现框架功能

框架功能,相当于开发者先建起房子的框架,真正装修留个客户区后期装修,或者后期在翻新,总之框架建好就,后期对框架的装饰可以变动。

体现在程序设计上,先做好程序的框架或说模板,开发者可以再后期更改框架中的功能类,不用整个重做,也利于未来维护和升级。

      

实现一个简单框架的步骤:

(1)   右键点击项目名-->选择File-->创建后写入键值对,如className=java.util.ArrayList,后期可改动。

(2)   装载(1)的文件。

(3)   用Properties对象获取(2)文件中的键的值。

(4)   根据(3)中的值className,利用Class.forName(className).newInstance()创建对象。

(5)   利用该对象操作。

(6)   测试将文件中的className=java.util.ArrayList的ArrayList该成HashSet

 

框架代码:

import java.io.FileInputStream;
import java.io.Exception;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
 
public class FrameDemo
{
//为代码紧凑,异常采取抛出
       public static void main(String[] args)throws Exception
       {
              //获取配置文件的内容
              InputStream ips = new FileInputStream("config.properties");
              //用类的加载器,类加载器,会在当前classPath环境变量指定的目录下开始寻找文件
              //InputStream ips= FrameDemo.class.getClassLoader().getResourceAsStream("config.properties");
             
              Properties props = new Properties();
              props.load(ips);
              ips.close();
              String className =props.getProperty("className");
              //利用forName获取类的字节码,并用newInstance创建对象
              Collection collections =(Collection)(Class.forName(className).newInstance());
             
              Person p1 = new Person("aaaa");
              Person p2 = new Person("bbbb");
              Person p3= new Person("cccc");
              collections.add(p1);
              collections.add(p2);
              collections.add(p3);
              collections.add(p1);
              //配置文件中为ArrayList,不去重复个数为4
              //当改成hashSet,去重复,个数为3
              System.out.println(collections.size());
             
       }
}
class Person
{
       private String name;
       Person(String name)
       {
              this.name = name;
       }
}


---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流! ----------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值