黑马程序员 Java基础之反射

来源:互联网 发布:猪饲料配方软件 编辑:程序博客网 时间:2024/05/19 08:41

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

反射概述

Java中的类主要是描述相同特性的一类事物,比如我们用Person类来描述人的共有特性,那么Class这个类就是用来描述Java中的各个类,它主要描述Java类中所包含类所具有的共性,比如说类的名称,所属的基类,所属的包等,比如String,Integer,int,等,我们在实例化对象时,首先是将已经编译的类的字节码文件从硬盘加载到内存中,然后虚拟机用这一份字节码创建多个实例对象,而类的这份字节码文件就用Class的实例对象表示(字节码对象);
/*
*获取一个对象所对应的字节码实例对象有三种方式
1类名.class(),比如int.class
2对象.getClass(),比如person.getClass();
3Class.forName(类名),这个类名必须是所对应具体的包中的具体对象。比如Class.forName("java.util.ArrayList");
返回的方式有两种:
1种是已经被加载过,呆在虚拟机中,直接返回。
2中是还没有被加载到内存中,那么就会被类加载器加载进内存,缓存到虚拟机中,然后再返回。
九个预定义的Class实例对象
八个基本数据类型,加上一个返回类型void.class。

演示代码如下:

class  ReflectDemo1_1{public static void main(String[] args) throws ClassNotFoundException{Person p=new Person("xt",12);Class c1=Integer.class;//用于获取Integer包装类所对应的字节码文件Class c2=p.getClass();//用person实例对象的getClass()方法获取实例对象p所对应的字节码文件。Class c3=int.class;System.out.println(Class.forName("java.lang.Integer"));//使用Class类的静态方法forName()来描述一个对象所对应的字节码文件。String s1="xtiongtao";System.out.println(s1.getClass());System.out.println(c1);System.out.println(c2);System.out.println(c3);//使用isPrimitive()来判断对象所对应的字节码文件是否是基本类型的字节码文件System.out.println(int.class.isPrimitive());//返回是trueSystem.out.println(Integer.class.isPrimitive());//基本类型包装类所对应的字节码文件不是基本类型的字节码System.out.println(int.class==Integer.TYPE);//包装类的常量字段TYPE是用来返回所包装的基本类型的字节码。System.out.println(int[].class.isArray())//用来判断一个int类型数组所对应的字节码文件是否是数组类型的。System.out.println("******************************");fun();}//下面的方法用来同一个类所对应的不同对象是否是用的同一个字节码对象。public static void fun()throws ClassNotFoundException{String s1="xt";Class c1=s1.getClass();Class c2=String.class;Class c3=Class.forName("java.lang.String");System.out.println(c1==c2);System.out.println(c2==c3);//两个返回的都是true,这样我们这个发现相同类所对应的不同的实例对象所公用的是一份字节码对象,}}class Person{private String name;private int age;Person(String name,int age){this.name=name;this.age=age;}}
反射的定义:反射就是将Java类中的每一中成分映射成对应的Java类。
比如Person中的有成员变量,成员函数,构造函数等成分,而Class类中定义了一些方法来描述这些成分,Method, Contructor,Field,Package等
Constructor表示一份字节码中的构造方法。
程序在编译时,只检查语法错误,不会去进行运算。

构造函数的反射

演示代码如下:

import java.lang.reflect.*;class ReflectDemo1_2 {public static void main(String[] args) throws Exception{//比如我们要使用的String构造方法创建一个字符串,是接受的参数是一个,并且参数类型是StringBuilder的,那么我们怎么将这个想法让JVM知道呢。那么我们可以这样定义Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuilder.class);//获取String类型并且参数类型是StringBuilder类型的构造方法。//注意:java编译器在编译时只是严格的检查语法,不会去执行等号左右两边的赋值,只知道是Constructor类型,但是具体的是什么类型的构造方法,参数是几个,java编译器都不知道,而在运行的时候才知道。String  s=(String)constructor.newInstance(new StringBuilder("xiongtao"));System.out.println(s.charAt(2));//通过查阅Java文档我们发现Class类有一个方法://newInstance()使用这个方法可以创建一个Class对象所表示的类的一个无参的实例String s1=(String)Class.forName("java.lang.String").newInstance();//获取String 类型的默认的无参实例对象。s1="xiongtao";System.out.println(s1.charAt(2));//同样用到了缓存的机制来保存默认构造方法的实例对象,下一次在调用该对象的无参构造方法创建该对象就可以直接使用,不需要再去加载,因此这样会降低程序的性能。}}class Person{private String name;private int age;Person(String name,int age){this.name=name;this.age=age;}public void showMsg(){System.out.println(name+"……"+age);}}
成员变量的反射

<span style="font-size:10px;">import java.lang.reflect.*;class ReflectDemo1_4 {public static void main(String[] args) throws Exception{TextClass tc=new TextClass();letterReplace(tc);}public static void letterReplace(Object obj)throws Exception{Field[] fields=obj.getClass().getDeclaredFields();//获取实例对所对应的字节码文件所对应的所有的成员变量for(Field field :fields)//{if(field.getType()==String.class)//注意这里是为了判断成员变量中的类型是否是String类型,注意这里必须使用==,因为多个String变量公用的是一份字节码文件。不能使用equals()方法{String s=(String)field.get(obj);String temp=s.replace('b','a');field.set(obj,temp);}}System.out.println(obj);}}class TextClass{String str1="bball";String str2="basketball";String str3="itcast";public String toString(){return str1+"\n"+str2+"\n"+str3;}}</span>
成员方法的反射

演示代码如下:

/***接下来我们来了解java中方法的反射的使用*/import java.lang.reflect.*;class ReflectDemo1_6 {public static void main(String[] args) throws Exception{String s="xiongtao";methodFun(s);}public static void methodFun(String s)throws Exception{Method m=Class.forName("java.lang.String").getMethod("charAt",int.class);//利用反射的方式,得到字节码中的方法,然后在拿到这个方法去作用实例对象上。System.out.println(m.invoke(s,2));}}
在实际开发时,我们往往会接受其它的开发人员传来的一个类名,然后我们根据类名调用这个类名的主函数,这时我们就就需要方法的反射来完成这个功能。

演示代码如下:

import java.lang.reflect.*;class ReflectDemo1_7 {public static void main(String[] args)throws Exception {/*TextDemo1_1.main(new String[]{"xiongtao","zhangsan","lisi"});*///平时我们调用时是直接根据类名来调用一个指定类的main方法,//接下来我们将使用反射的原理来完成这个功能。//获取用户传入的类名的字符串String clsName=args[0];//根据用户传入的类名获取主函数Method m=Class.forName(clsName).getMethod("main",String[].class);//获得方法,然后将该方法作用到具体的对象上去,因为这个方法时静态方法,所以就不用指定具体的对象。然后传入参数。因为在传入参数时,Java编译器就会自动的拆一次包,所以不能直接传入参数new String[]{"xt","ctt","non"};因为直接传入的字符串数组将会拆成三个字符串,//下面第一种就是告诉编译器,这个数组是一个对象,是一个整体,不能够将其拆分//第二种就是相当于对这个字符串数组中再进行一次包装,那么编译器解封时也只会解封一次new Object[]这个数组,所以留下的也是一个字符串数组。m.invoke(null,(Object)new String[]{"xt","ctt","non"});m.invoke(null,new Object[]{(new String[]{"xt","ctt","non"})});}}class TextDemo1_1{public static void main(String[]args){for(String arg:args){System.out.println(arg);}}}

接下来,我们将使用一个方法。如果传入的是一个数组,那么就将数组中的每个元素都打印出来,如果是一个单个元素,那么就直接将这个元素打印出来.演示实例如下:

/***/import java.lang.reflect.*;class ReflectDemo1_9 {public static void main(String[] args) {int[] arr=new int[]{1,23,4,5};String s="xiongtao";printFun(arr);printFun(s);}public static void printFun(Object obj){if(obj.getClass().isArray())//如果对象对应的字节码是数组类型,{int len=Array.getLength(obj);//使用java.lang.reflect类中的一个Array来获数组中的每个元素。for(int i=0;i<len;i++){System.out.println(Array.get(obj,i));//}}else{System.out.println(obj);}}}
接下来我们来看一段实例:

import java.util.*;class ReflectDemo1_10{public static void main(String[] args) {Person p1=new Person("xt",12);Person p2=new Person("ctt",12);Person p3=new Person("ctt",12);Person p4=new Person("xt",12);Collection c=new ArrayList();c.add(p1);c.add(p2);c.add(p3);c.add(p4);System.out.println(c.size());//List集合中允许存入相同的元素    RerefPoint p5=new RerefPoint(1,2);RerefPoint p6=new RerefPoint(3,4);Collection s=new HashSet();s.add(p5);s.add(p6);System.out.println(s.size());p5.x=4;s.remove(p5);//这里我们已经移除了p5这个对象,但是为什么集合的长度仍然是2呢,原因是对象中的这个x,y都参与了hashCode值的计算,如果修改以后就会导致元素存储集合中的位置区域发生了变化,再也无法找到以前的对象存储的位置。所以删除就会失败,这样也就会存在隐患,导致内存泄露。所以存储到集合中的对象的元素一旦存储成功以后就不要去修改,System.out.println(s.size());//哈希算法就是将集合根据哈希值进行若干个区域的划分,每一个区域对应不同的哈希值,然后在存储对象时,先根据对象的hash值将其存储到对应的区域中,这样每次存一个对象时,就不会一个一个的去遍历整个集合,去看是否有重复的,而是只在这一片区域中查找是否有相同的元素,这样也可以提高查找的性能。//Set集合中不能存入相同的元素,先是根据传入的对象算出一个hash值,而这个值一般都是内存地址,然后根据这个值将对象存储在,如果地址相同,那么在比较对象本身的equals()这个方法。}}class RerefPoint{public int x;public int y;RerefPoint(int x,int y){this.x=x;this.y=y;}public int hashCode(){    int prim=31;return prim*x+y;}}
运行结果如下:

Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。

比如我们而反射的机制一般用在开发框架中,比如我们开发框架时,框架要调用外面传入的类,但是框架并不知道这个类的具体名称,但是我们可以使用反射的思想,在程序运行时动态的去加载用户传入的类,然后去调用类的成员或者方法等。

下面的实例:

/*前面的程序中我们已经简单的利用了接受用户传入一个类名,然后获取这个类名的主函数,然后去执行用户传入的这个类,接下来利用反射的原理,动态的从配置文件中获取用户传入的信息,并进行动态的操作。这也可以算是一个小小的框架,注意,框架中不会出现具体的类名,*/import java.io.*;import java.util.*;class ReflectDemo1_11{public static void main(String[] args) throws Exception{BufferedReader br=new BufferedReader(new FileReader("TextProperty.properties"));Properties p=new Properties();p.load(br);//将流加载到Map集合中String clsName=p.getProperty("className");//获取用户输入的类名Collection c=(Collection)Class.forName(clsName).newInstance();//创建一个无参的集合类型实例对象。Person p1=new Person("xt",12);Person p2=new Person("ctt",12);Person p3=new Person("ctt",12);Person p4=new Person("xt",12);c.add(p1);c.add(p2);System.out.println(c.size());p1.setAge(22);c.remove(p1);System.out.println(c.size());}}
比如当我们在配置文件中将className设置成Java.util.HashSet时,获得的结果是


当我们将className改成Java.util.List时获得结果是:

通过上面一个小小的框架的使用,我们可以总结出框架和工具的区别在于,框架会调用外部输入的类型,但是工具就不依赖,他们之间的关系就是框架相当于一个房子,工具相当于一个们,我们不用自己去盖一套完整的房子,我们可以直接买毛坯房,然后对它进行装修,但是房子也依赖我们的装修,不同的人买同样的房子装修的结果就有可能不一样,而工具就像门一样,不依赖人。



0 0