黑马程序员java学习<基础加强>—反射

 

Class 

一、概述:

1、Class是Java程序中各个Java类的总称;它是反射的基石,通过Class类来使用反射。

2、Class和class的区别

1)class:Java中的类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象确定,不同的实例对象有不同的属性值。

2)Class:指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class。例如人对应的是Person类,Java类对应的就是Class。

3、属性:类名,类的访问属性,类所属包名,字段名称列表,方法名称列表等。

二、对象的创建和使用:

1、创建实例对象:不可用new Class()的方式,因为Class没有这样的构造方法。而是将字节码对象赋值给Class变量。如Class c1 =Person.class。

如Person类,它的字节码:首先要将Person的java文件编译为class文件放于硬盘上,即为二进制代码,再将这些代码加载到内存中,接着用它创建一个个对象。就是把类的字节码加载进内存中,再用此字节码创建一个个对象。当有如Person、Math、Date等等的类,那么这些字节码就是分别的一个Class对象。即Class c2 =Date.class;。

2、获得类的字节码对象:如Class.forName(”java.lang.String”)即获得String.class。得到这个字节码对象有两种情况:

1)此类已经加载进内存:若要得到此类字节码,不需要再加载。

2)此类还未加载进内存:类加载器加载此类后,将字节码缓存起来,forName()方法返回加载进来的字节码。

3、得到各字节码对应的实例对象(Class类型)的方式:

1)类名.class:如System.class,String.class等等

2)对象.class:如new Date().getClass()或者d.getClass()。(Date d = new Date())

3)Class.forName(“类名”):如Class.forName(”java.lang.String”)

当获取类名的时候,是不知道此类的名称的,forName(字符串参数)方法中传入字符串型的变量作为对外访问的入口,即传入什么类名就获得什么类名,从而得知相应的类名。

注:forName()是静态方法,是反射中使用的一种方式获取字节码的实例对象。

每个类的字节码对象只有唯一的一个,如任何字符串对象,对应唯一的String.clas字节码。

4、九个预定义的Class:

1)包括八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。

2)Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。

基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示

数组类型的Class实例对象,可以用Class.isArray()方法判断是否为数组类型的。

5、总结:只要是在源程序中出现的类型都有各自的Class实例对象,如int[].class、void.class等。

三、方法:

1、static Class forName(String className)

 ---> 返回与给定字符串名的类或接口的相关联的Class对象。

2、Class getClass()

---> 返回的是Object运行时的类,即返回Class对象即字节码对象

3、Constructor getConstructor()

---> 返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。

4、Field getField(String name)

---> 返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。

5、Field[] getFields()

---> 返回包含某些Field对象的数组,表示所代表类中的成员字段。

6、Method getMethod(String name,Class… parameterTypes)

---> 返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。

7、Method[] getMehtods()

返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。

8、String getName()

---> 以String形式返回此Class对象所表示的实体名称。

9、String  getSuperclass()

---> 返回此Class所表示的类的超类的名称

10、boolean isArray()

---> 判定此Class对象是否表示一个数组

11、boolean isPrimitive()

---> 判断指定的Class对象是否是一个基本类型。

12、T newInstance()

---> 创建此Class对象所表示的类的一个新实例。

 

 反射

1、概述:把Java类中的各种成分映射成相应的Java类。

如Class中的每一个方法返回的都是一种类(型),即Method对所有方法抽取成了这个类Method,它的每一个对象(如变量methodObj1)代表了一个方法。

2、一个类中的组成成分:

成员变量、方法、构造函数、包等信息,也用一个个java类来表示(如汽车是一个类,其中的发动机,变速箱等也是对应的一个个类),表示Java类的Class类显然要提供一系列的方法来获取其中的变量、方法、构造函数、修饰符、包等信息,这些信息就是用相应的类的实例对象来表示,他们是Field、Method、Contructor、Package等。

3、一个类中的每个成员都可用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可得到这些实例对象。

 

一、Constructor类

1、Constructor类代表某个类的构造方法

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

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

3、获取某一个构造方法:

Constructor con =String.class.getConstructor(StringBuffer.class);

4、创建实例对象:

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

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

注:1:得到构造方法需要类型(StringBuffer.class)

       2:调用这个方法也需要同样类型的实例对象(StringBuffer(“abc”))

5、Class.newInstance()方法

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

1)该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象

2)该方法内部用到了缓存机制来保存默认构造方法的实例对象

二、Field

1、Field类代表某个类中的一个成员变量

2、演示用eclipse自动生成Java类的构造方法

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。

示例代码:

public class ReflectPoint {  
    private int x;  
    public int y;  
  
    public String toString(){  
        return str1+";" + str2 + ";" + str3;  
   }  
}  
public class FieldTest(){  
ReflectPoint pt1 = new ReflectPoint(3,5);  
    //fieldX和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));  
}  


替换字符:

   
private static void changeStringValue(Object obj) throws Exception {  
    Field[] fields = obj.getClass().getFields();  
    for(Field field : fields){  
        //此处用==比较,而不用valueof。因为是同一份字节码对象                                                                              if(field.getType() == String.class){                                                                                                String oldValue = (String)field.get(obj);  
        String newValue = oldValue.replace('b','a');  
        field.set(obj, newValue);  
      }  
    }  
}  


 三、Method类

1、Method类代表某个类中的一个成员方法

2、得到类中的某一个方法

       例子:Method methodCharAt = String.class.getMethod("charAt", int.class);

3、调用方法

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

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

       如果传递给Method对象的invoke方法的第一个参数为null,则表示该Method对象对应的是一个静态方法。

4、用反射方式执行某个main方法:

原因:当程序中的某个类在运行到某处需要去调用其他类的main方法时,如果此程序并不知道此main方法所属类的名称,而只是在程序中接受某一代表此main方法所属类的名称的参数,那么这时候就不能通过“类名.main(String[] args);"这样的方式来完成调用,而需要运用Java的反射机制了,需要编写相关的反射代码来完成对其他类的main方法的调用。

例子:有一个类,类名为ReflectTest,设定在其main方法中接受的String数据参数的第一个,即args[0]为所要调用的main方法所属类的类名,则与之相关的反射代码为:

    String startingClassName  =  args[0];                  

    Method mainMethod  =  Class.forName(startingClassName).getMethod("main", "String[].class");                 

    mainMethod.invoke(null,  (Object)new String[]{"a", "b", "c"});

 

    这里解释一下(Object)new String[]{"a", "b", "c"}这段代码。

    启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法设置参数呢?

    按jdk 1.5的语法,由于使用的是可变参数(Object类型),设置的数组参数会被作为一个参数进行传递,而按jdk 1.4的语法,此处应设置一个Object数组,数组中的每个元素对应所调用方法的一个参数。 当把一个字符串数组作为参数传递给invoke方式时,编译器会兼容jdk 1.4的语法,即按照1.4的语法进行处理,即把字符串数组打散成为若干个单独的参数,这样就会产生参数个数不匹配的异常。

 

    解决方法:采用上述强制向上转型后,可以是编译器按照正确的方法进行参数处理,即将整个字符串参数作为整体传递给目标main方法。

    注:使用new Object[]{new String[]{"a", "b", "c"}}作为invoke方法的第二个参数也可以得到正确的结果。比如在目标main方法中便利字符串数组元素。

 

四、数组的反射

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

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

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

public static void arrayTest()throws Exception {  
    int[] a1 = new int[]{1,2,3};  
    int[] a2 = new int[4];  
    int[][] a3 = new int[2][3];  
    Integer[] ai = new Integer[3];  
    String[] a4 = new String[]{"a","b","c"};                                                                                      
      
    System.out.println(a1.getClass() == a2.getClass());  
    System.out.println((Object)a1.getClass() == (Object)a3.getClass());  
          
    System.out.println(a3[0].getClass() == a1.getClass());  
    System.out.println(a3[0].getClass().getSuperclass().getName());  
    System.out.println(a1.getClass().equals(a3.getClass()));  
    System.out.println(a1.getClass().equals(a4.getClass()));  
    System.out.println(a1.getClass().getName());  
    System.out.println("----:" + a1.getClass());  
    System.out.println(a1.getClass().getSuperclass().getName());  
    System.out.println(a2.getClass().getSuperclass().getName());  
           
    Object obj1 = a1;  
    Object obj2 = a2;  
   //Object[] obj3 = a1;//int基本数据类型不是Object的  
    Object[] obj4 = a3;  
    Object[] obj5 = a4;  
           
    System.out.println(a1);  
    System.out.println(a4);  
       
    System.out.println(Arrays.asList(a1));  
    System.out.println(Arrays.asList(a4));  
    System.out.println("-------");  
    int[] a = new int[3];  
    Object[] obj = new Object[]{"abc",new Integer(1)};  
    System.out.println(a.getClass().getName());  
    System.out.println(obj.getClass().getName());  
    System.out.println(obj[0].getClass().getName());  
}  


 

 五、HashSet和与hashCode的分析

1、哈希算法的由来:

若在一个集合中查找是否含有某个对象,通常是一个个的去比较,找到后还要进行equals的比较,对象特别多时,效率很低,通过哈希算法,将集合分为若干个区域,每个对象算出一个哈希值,可将哈希值分组(一般模32为一组),每组对应某个存储区域,依一个对象的哈希码即可确定此对象对应区域,从而减少每个对象的比较,只需在指定区域查找即可,从而提高从集合中查找元素的效率。

示意图:


2、如果不存入是hashCode算法的集合中,那么则不用复写此方法。

3、只有类的实例对象要被采用哈希算法进行存入和检索时,这个类才需要按要求复写hashCode()方法,即使程序可能暂时不会用到当前类的hashCode()方法,但是为提供一个hashCode()方法也不会有什么不好,没准以后什么时候就会用到这个方法,所以通常要求hashCode()和equals()两者一并被覆盖。

4、提示:

1)若同类两对象用equals()方法比较的结果相同时,他们的哈希码也必须是相等的,但反过来就不成立了,如”BB”和”Aa”两字符串用equals()比较式不相等的,但是他们的哈希值是相等的。

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

简单说,之前存入的对象和修改后的对象,是具有不同的哈希值,被认为是不同的两个对象,这样的对象不再使用,又不移除,而越来越多,就会导致内存泄露。

补充:

内存泄露:某些对象不再使用了,占用着内存空间,并未被释放,就会导致内存泄露;也就是说当程序不断增加对象,修改对象,删除对象,日积月累,内存就会用光了,就导致内存溢出。

3)对象在调用方法时,对象会先进行一次自身hashCode()方法的调用,再进行操作方法。

5、示例:

覆写hashCode()方法的意义:只有存入的是具有hashCode算法的集合的,覆写hashCode()方法才有价值。 

import java.util.ArrayList;  
import java.util.Collection;  
import java.util.HashSet;                                                                                                                                                                                                                  
   
public class ReflectTest2 {  
   public static void main(String [] args){  
        Collection cons = new HashSet();  
        ReflectPoint pt1 = new ReflectPoint(3,3);  
        ReflectPoint pt2 = new ReflectPoint(5,5);  
        ReflectPoint pt3 = new ReflectPoint(3,3);  
   
        cons.add(pt1);  
        cons.add(pt2);  
        cons.add(pt3);                                                                                                                  cons.add(pt1);  
        cons.remove(pt1);  
        System.out.println(cons.size());  
    }  
}  
   
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;  
    }  
       
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + x;  
        result = prime * result + y;  
        //System.out.println("demo...");//测试  
        return result;  
    }  
  
    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;  
     }  
 
   public String toString(){  
        return str1+";" + str2 + ";" + str3;  
    }  
} 


 

 

 

 

 

 

 


 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值