黑马程序员 Java高兴技术----反射机制

----------android培训java培训java学习型技术博客、期待与您交流!------------


在此,分享一下自己学习JAVA的学习心得。有不对的地方请帮忙改正,也希望对想java的同学有帮助!


Java高兴技术

   --反射


  反射技术:

反射在本质上就是把Java类中的各种成分映射成相应的java类。其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,将字节码文件中的内容都封装成对象,这样便于操作这些成员。


  反射机制的优点与缺点 :

     静态编译:在编译时确定类型,绑定对象,即通过。 

     动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多  态的应用,有以降低类之间的藕合性。

反射的基本步骤: 

1、获得Class对象,就是获取到指定的名称的字节码文件对象。

2、实例化对象,获得类的属性、方法或构造函数。

3、访问属性、调用方法、调用构造函数创建对象。


在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中:
Class类:代表一个类。
Field 类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方法。
Constructor 类:代表类的构造方法。
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。

Class类是Reflection API 中的核心类,它有以下方法:

 1)getName():获得类的完整名字。
 2)getFields():获得类的public类型的属性。
 3)getDeclaredFields():获得类的所有属性。
 4)getMethods():获得类的public类型的方法。
 5)getDeclaredMethods():获得类的所有方法。
 6)getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
 7)getConstructors():获得类的public类型的构造方法。
 8)getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
 9)newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

 

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。 

 得到各个字节码对应的实例对象的方法:( Class类型): 

 1、类名.class。

   例如,System.class

 2、对象.getClass()。

   例如,new Date().getClass()

 3、Class.forName("类名")。

   例如,Class.forName("java.util.Date");

 知识点:.class和Class.forName的区别:

   后者可以在不确定要获得字节码的具体类的时候使用,传入的是一个字符串,经常在框架中使用。


九个预定义类型的Class对象:


       基本的 Java 类型(booleanbytecharshortintlongfloat 和double)和关键            字void 也表示为Class对象。基本类型的字节码获取方式只有一种就是类名.class。


   Java练习代码:
<span style="font-size:18px;">public class Demo {

	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");
		System.out.println(cls1 == cls2);//返回true
		System.out.println(cls3 == cls2);//返回true
		/*
		 * 有九种预定义的 Class 对象,表示八个基本类型和 void。
		 * 这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,
		 * 即 boolean、byte、char、short、int、long、float 
		 * 和 double。 
		 * */
		System.out.println(cls1.isPrimitive());//判定指定的 Class 对象是否表示一个基本类型。
		System.out.println(int.class.isPrimitive());//true
		System.out.println(int.class == Integer.class);//false
		System.out.println(int.class == Integer.TYPE);//true
		System.out.println(int[].class.isPrimitive());//false</span><span style="font-family: 宋体, 'Arial Narrow', arial, serif;"><span style="font-size:18px;">//判断是否表示一个基本类型</span></span><span style="font-size:18px;">
		System.out.println(int[].class.isArray());//true//判断是否表示一个数组类型
	}

}</span>


Field 类:代表类的成员变量(成员变量也称为类的属性)。

Java练习代码:
    ReflectPoint pt1 = new ReflectPoint(3,5);        
        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));  


Method类:代表类的方法。

得到类中的某一个方法: 

例子: 

Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class); 

调用方法: 

通常方式:System.out.println(str.charAt(1)); 

反射方式: System.out.println(charAt.invoke(str, 1));

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


Constructor 类:代表类的构造方法。

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

例子: 

Constructor [] constructors= Class.forName("java.lang.String").getConstructors(); 

得到某一个构造方法: 

例子: 

Constructor constructor=Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);

获得方法时要用到类型:

创建实例对象: 

通常方式:String str = new String(new StringBuffer("abc"));

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

调用获得的方法时要用到上面相同类型的实例对象 

Class.newInstance()方法: 

例子:String obj = (String)Class.forName("java.lang.String").newInstance(); 

该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。也就是使用String类的无参构造

方法创建了一个String对象。

以下是用参数为StringBuffer类型的构造函数创建一个String类型的实例对象:


Array类:提供了动态创建数组,以及访问数组的元素的静态方法。

数组与Object的关系及其反射类型

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

<span style="font-size:18px;">public class ReflectTest {

       public static void main(String[] args) throws Exception {
            int [] a1 = new int[3];
            int [] a2 = new int[4];
            int [][] a3 = new int[2][3];
            String[] a4 = new String[3];

            System.out.println(a1.getClass() == a2.getClass());
            //结果:true
            System.out.println(a1.getClass() == a4.getClass());
            //结果:false
            System.out.println(a1.getClass() == a3.getClass());
            //结果:false
       }
}
</span>

2. 代表数组的class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

3. 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,即可以做Object类型使用,又可以当作Object[]类型使用。

4. Array.asList()方法处理int[]和String[]时的差异

<span style="font-size:18px;">public class ReflectTest {
       public static void main(String[] args) throws Exception {
            int [] a5 = new int[]{1,2,3};
            String[] a6 = new String[]{"a" ,"b" ,"c" };
            //直接使用System.out.println无法打印出数组的内容
            System. out .println(a5);
            //结果:[I@18a992f
            System. out .println(a6);
            //结果:[Ljava.lang.String;@4f1d0d
           
            //通过Arrays.asList方法打印出数组的内容
            System. out .println(Arrays.asList(a5));
            //结果:[[I@18a992f]
            //原因是因为JDK1.4中为Arrays.asList(Object[] a),JDK1.5中为Arrays.asList(T... a)。
            //a5是int[]类型,JDK1.4中的asList方法处理不了,JDK1.5可以处理。但是JDK1.5将 int数组整体作为一个参数进行处理。
            //因此最终结果就是将 int[]进行了封装,结果类型也就成了[[I。
            System. out .println(Arrays.asList(a6));
            //结果:[a, b, c]
       }
}</span>

5. Array工具类用于完成数组的反射操作。

<span style="font-size:18px;">public class ReflectTest {

       public static void main(String[] args) throws Exception {
            String[] a1 = new String[]{"a" ,"b" ,"c" };
            String a2 = "xyz";
           
            printObject(a1);
            printObject(a2);
       }
      
       public static void printObject(Object obj){
         Class clazz = obj.getClass();
         if(clazz.isArray()){
               int len = Array.getLength(obj);
               for(int i = 0; i < len; i++){
                     System. out.println(Array.get(obj, i));
               }
         } else{
               System. out.println(obj);
         }
       }
}</span>

6. ArrayList和HashSet的比较及Hashcode分析。

ArrayList:

         <span style="font-size:18px;">  Collection collections = new ArrayList();
           ReflectPoint pt1 = new ReflectPoint(3, 3);
           ReflectPoint pt2 = new ReflectPoint(5, 5);
           ReflectPoint pt3 = new ReflectPoint(3, 3);
          
           collections.add(pt1);
           collections.add(pt2);
           collections.add(pt3);
           collections.add(pt1);
          
           System. out.println(collections.size());
           //结果:4           Collection collections = new ArrayList();
           ReflectPoint pt1 = new ReflectPoint(3, 3);
           ReflectPoint pt2 = new ReflectPoint(5, 5);
           ReflectPoint pt3 = new ReflectPoint(3, 3);
          
           collections.add(pt1);
           collections.add(pt2);
           collections.add(pt3);
           collections.add(pt1);
          
           System. out.println(collections.size());
           //结果:4</span>

HashSet:

           Collection collections = new HashSet();
           ReflectPoint pt1 = new ReflectPoint(3, 3);
           ReflectPoint pt2 = new ReflectPoint(5, 5);
           ReflectPoint pt3 = new ReflectPoint(3, 3);
          
           collections.add(pt1);
           collections.add(pt2);
           collections.add(pt3);
           collections.add(pt1);
          
           System. out.println(collections.size());
           //结果:3

分析:

由以上两示例可以看到当集合为ArrayList时,其实质是一个数组,因此放入4个元素后,集合size为4。
当集合为HashSet时,需要通过比较hashcode值以及equals方法是否返回true决定是否放入。如果hashcode值相等并且equals方法返回true,那么就不会放入。因此,集合size为3。
如果想让size为2,也就是pt1与pt3作为同一个元素存入HashSet集合,那就需要覆盖ReflectPoint类的hashCode方法以及equals方法。

hashCode()和equals()覆盖:

       @Override
       public int hashCode() {
             final int prime = 31;
             int result = 1;
            result = prime * result + x;
            result = prime * result + y;
             return result;
      }

       @Override
       public boolean equals(Object obj) {
             if (this == obj)
                   return true ;
             if (obj == null)
                   return false ;
             if (getClass() != obj.getClass())
                   return false ;
            ReflectPoint other = (ReflectPoint) obj;
             if (x != other.x)
                   return false ;
             if (y != other.y)
                   return false ;
             return true ;
      }

此时,运行ReflectTest.java结果为2。

注意:

当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。



Java练习代码:

练习:利用反射遍历ReflectPoint类,对其进行操作
private static void changeStringValue(Object obj) throws Exception {  
          
        Field[] fields = obj.getClass().getFields();//获取所有公有的成员变量  
          
        for(Field field : fields) {//遍历这些成员变量  
              
            if(field.getType() == String.class){//判断该变量的类型是不是String  
                  
                String oldValue = (String)field.get(obj);  
                  
                String newValue = oldValue.replace('b', 'a');//替换  
                field.set(obj, newValue);//设置新值  
            }  
        }  
    }  
  
ReflectPoint类:  
public class ReflectPoint {  
    private int x;       
    public int y;       
    public String str1 = "ball";      
    public String str2 = "basketball";      
    public String str3 = "itcast";   
    public ReflectPoint(int x, int y) {           
        super();            
        this.x = x;          
        this.y = y;  
    }  
  
    @Override  
    public String toString() {          
        return str1+":"+str2+":"+str3;  
    }   
}  

    

jdk1.4和jdk1.5的invoke方法的区别:


Jdk1.5:public Object invoke(Object obj,Object... args) 可变参数;

Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数 

传递给invoke方法,数组中的每个元素分别对应被调用方法中的一个参数。

    main方法的调用:  
        String startingClassName = args[0];  
        Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);          
        mainMethod.invoke(null, (Object)new String[]{"111","222","333"});  

 为了兼容1.4版本没有可变参数方法,传入的一个数组对象会被自动拆包,变为多个参数,因此要打包成

一个Object对象传入,避免被拆包。

用类加载器的方式管理资源和配置文件:

方式一:采用类加载器进行加载,使用相对路径的方式  
InputStream is = ReflectTest.class.getClassLoader().getResourceAsStream("com/mytest/config.properties");  
方式二:利用Class方式进行加载,使用相对路径的方式  
nputStream is = ReflectTest.class.getResourceAsStream("config.properties");  
方式三:利用Class方式进行加载,使用绝对路径的方式  
InputStream is = ReflectTest.class .getResourceAsStream("com/mttest/config.properties");  











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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值