Java学习日记(十二)反射

本文深入讲解Java反射机制的基础概念及应用场景,包括Class类的理解、反射API的使用、Constructor与Field类的功能,以及Method类和数组的反射操作。此外,还探讨了反射技术在框架设计中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.Class类(反射的基石)

●对比提问: Person类代表人,它的实例对象就是张三,李四这样一个个具体的人, Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?

       ·人——Person

       ·Java类——Class

●Class类代表Java类,它的各个实例又对应什么呢?

       ·对应各个类在内存中的字节码,例如Person类的字节码,ArrayList类的字节码等等

·一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码文件,不同的类的字节码文件不同,所以它们在内存中的内容也是不一样的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型。

●class和Class的区别

       ·class:Java语言中的关键字,用于的定义类,把事物共性抽取出来定义成类,再通过类对象调用

       ·Class:Java中的类,Java中的各个类是归属于Class类,Class类是程序中各个java类的总称。

●如何得到各个字节码文件对应的示例对象?

       方式一:类名.class;——示例:System.class;

       方式二:对象.getClass();——示例:new Date().getClass();

       方式三:Class.forName(“类名”);——示例:Class.forName(“java.util.Date”);

示例:

class ReflectDemo {
       public static void main(String[] args) throwsException {
              //以字符串为例
              String s = "abcdefg";
              //方式一:类名.class
              Class c1 = String.class;
              //方式二:对象名.getClass()
              Class c2 = s.getClass();
              //方式三:Class.forName("类名")
              Class c3 =Class.forName("java.lang.String");
              System.out.println(c1);                   // 结果:class java.lang.String
              System.out.println(c2);                   // 结果:class java.lang.String
              System.out.println(c3);                   // 结果:class java.lang.String
              System.out.println(c1== c2);              // 结果:true
              System.out.println(c1== c3);              // 结果:true
       }
}

●九个预定义Class实例对象:

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

示例:

class ReflectDemo {
       public static void main(String[]  args) throws Exception {
              System.out.println(byte.class);                  // 结果:byte
              //通过isPrimitive方法判断是否为基本类型
              System.out.println(byte.class.isPrimitive());    // 结果:true
              System.out.println(short.class);                 // 结果:short
              System.out.println(int.class);                   // 结果:int
              System.out.println(long.class);                  // 结果:long
              System.out.println(float.class);                 // 结果:folat
              System.out.println(double.class);                // 结果:double
              System.out.println(char.class);                  // 结果:char
              System.out.println(boolean.class);               // 结果:boolean
              System.out.println(void.class);                  // 结果:void
              System.out.println("------");
              System.out.println(Byte.class);                  // 结果:class java.lang.Byte
              //通过isPrimitive方法判断是否为基本类型
              System.out.println(Byte.class.isPrimitive());    // 结果:false
              System.out.println(Short.class);                 // 结果:class java.lang.Short
              System.out.println(Integer.class);               // 结果:class java.lang.Integer
              System.out.println(Long.class);                  // 结果:class java.lang.Long
              System.out.println(Float.class);                 // 结果:class java.lang.Float
              System.out.println(Character.class);             // 结果:class java.lang.Character
              System.out.println(Boolean.class);               // 结果:class java.lang.Boolean
              System.out.println(Void.class);                  // 结果:class java.lang.Void
              System.out.println("------");
              System.out.println(Byte.TYPE);                   // 结果:byte
              //通过isPrimitive方法判断是否为基本类型
              System.out.println(Byte.TYPE.isPrimitive());     // 结果:true
              System.out.println(Short.TYPE);                  // 结果:short
              System.out.println(Integer.TYPE);                // 结果:int
              System.out.println(Long.TYPE);                   // 结果:long
              System.out.println(Float.TYPE);                  // 结果:float
              System.out.println(Character.TYPE);              // 结果:char
              System.out.println(Boolean.TYPE);                // 结果:boolean
              System.out.println(Void.TYPE);                   // 结果:void
       }
}

附注:

       根据上面的示例我们可以发现:byte.class==Byte.TYPE;int.class== Integer.TYPE……

●数组类型的Class实例对象:

       Class.isArray();

示例:

class ReflectDemo {
       public static void main(String[] args) {
              System.out.println(int[].class);                      // 结果:class [I
              //通过isPrimitive方法判断是否为基本类型
              System.out.println(int[].class.isPrimitive());        // 结果:false
              //通过isArray方法判断是否为数组
              System.out.println(int[].class.isArray());            // 结果:true
              System.out.println(int.class.isArray());              // 结果:false
              System.out.println("---------");
              System.out.println(byte[].class);                     // 结果:class [B
              //通过isPrimitive方法判断是否为基本类型
              System.out.println(byte[].class.isPrimitive());       // 结果:false
              //通过isArray方法判断是否为数组
              System.out.println(byte[].class.isArray());          // 结果:true
              System.out.println(byte.class.isArray());            // 结果:false
       }
}

总结:

只要在源程序中出现的类型,都有各自的Class实例对象,例如:int[]intvoid……

 

2.反射

●反射就是把Java类中的各种成分映射成相应的Java类,例如:一个Java类中用一个Class类的对象来表示,一个类中的组成部分包括:成员变量、方法、构造方法、包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示Java类的Class类显然要提供一系列的方法,来获取其中的变量、方法、构造方法、修饰符、包等信息,这些信息就是用相应类的实例对象来表示,它们分别是Field、Method、Contructor、Package等等。

●一个类中的每个成员都可以用相应的反射API类的一个示例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?如何用?这就是我们学习的重点。

●Class常用方法:

static Class forName(String className)——返回与给定字符串名的类或接口的相关联的Class对象

Class getClass()——返回的是Object运行时的类,即返回Class对象即字节码对象

Constructor<?>[] getConstructors()——返回Constructor对象数组,它反映此Class对象所表示的类的所有构造方法

Constructor getConstructor()——返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法

Field getField(String name)——返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段

Field[] getFields()——返回包含某些Field对象的数组,表示所代表类中的成员字段

Method getMethod(String name,Class… parameterTypes)——返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。

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

String getName()——以String形式返回此Class对象所表示的实体名称。

String getSuperclass()——返回此Class所表示的类的超类的名称

boolean isPrimitive()——判断指定的Class对象是否是一个基本类型

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

T newInstance()——创建此Class对象所表示的类的一个新实例。

 

3.Constructor类

Constructor类代表某个类中的一个构造方法

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

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

示例:

class ReflectDemo {
       public static void main(String[] args) throwsException {
              //获取String类中的构造函数
              Constructor[] cs =Class.forName("java.lang.String").getConstructors();
              //打印个数
              System.out.println("String类共有构造函数"+cs.length+"个");
              for(Constructor  c : cs) {
                     System.out.println(c);
              }
       }
}

运行结果:

String类共有构造函数15个

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

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

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

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

publicjava.lang.String(byte[],int,int,java.lang.String) throwsjava.io.UnsupportedEncodingException

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

publicjava.lang.String(java.lang.StringBuffer)

public java.lang.String(byte[])

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

public java.lang.String()

public java.lang.String(char[])

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

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

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

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

●得到某一个构造方法:(获得方法时要用到参数类型)

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

示例:

class ReflectDemo {
       public static void main(String[] args) throwsException {
              Constructor c =Class.forName("java.lang.String").getConstructor(StringBuilder.class);
              System.out.println(c);
       }
}

运行结果:

publicjava.lang.String(java.lang.StringBuilder)

●创建实例对象:(调用获得的方法时要用到上面相同类型的实例对象)

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

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

示例:

class ReflectDemo {
       public static void main(String[] args) throwsException {
              //通常方式:
              String s1 = new  String(new  StringBuilder("abcdefg"));
              System.out.println(s1.charAt(1));
              System.out.println(s1);
              //反射方式
              Constructor c =Class.forName("java.lang.String").getConstructor(StringBuilder.class);
              String s2 = (String) c.newInstance(new  StringBuilder("abcdefg"));
              System.out.println(s2.charAt(1));
              System.out.println(s2);
              //判断s1与s2是否相等
              System.out.println(s1.equals(s2));
       }
}

运行结果:

b

abcdefg

b

abcdefg

true

●Class.newInstance()方法:

       String obj=(String)Class.forName(“java.lang.String”).newInstance();

       该方法内部得到默认的构造方法,然后用该构造方法创建示例对象。

该方法内部的具体代码是怎样写的呢?——用到了缓存机制来保存默认构造方法的实例对象。

附注:

       ·创建实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。

       ·newInstance():构造出一个实例对象,每调用一次就构造一个对象。

       ·利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。

 

4.Field类

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

示例:

class ReflectPoint {
       private  int  x;
       public  int  y;
       ReflectPoint(int  x, int  y) {
              this.x =x;
              this.y= y;
       }
}
class ReflectDemo {
       public static void main(String[]  args) throws Exception{
              ReflectPoint rp1 = new ReflectPoint(4, 7);
              Field y=rp1.getClass().getField("y");
              //y不是对象上的变量,而是类上的成员
              System.out.println(y);
              //用y去取对象上面对应的值
              System.out.println(y.get(rp1));
              //因为ReflectPiont中的x成员是私有的,所以反射不成功
              //Field x=rp1.getClass().getField("x");
              //System.out.println(x);
              //通过getDeclaredField方法获取私有成员
              Field x=rp1.getClass().getDeclaredField("x");
              System.out.println(x);
              //因为x是私有成员,还必须通过setAccessible设置其可访问才可以获取对象的值
              x.setAccessible(true);
              System.out.println(x.get(rp1));
       }
}

运行结果:

publicint Demo.ReflectPoint.y

7

privateint Demo.ReflectPoint.x

4

注意:

       问:得到的Field对象是对应类上面的成员变量还是对应到对象上的成员变量呢?

       答:类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象就说不清了,所以字段filed X代表的是x的定义,而不是具体的x的变量。

练习:(将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a")

代码实现:

class ReflectPoint {
       private  int  x;
       public int  y;     
       public String  a="ball";
       public String  b="basketball";
       public String  c="itcast";  
       ReflectPoint(int x, int y) {
              this.x =x;
              this.y= y;
       }
       public String toString(){
              return  a+"--"+b+"--"+c;
       }
}
class ReflectDemo {
       publicstatic void main(String[] args) throws Exception {
              ReflectPointrp = new ReflectPoint(5, 6);
              changValue(rp);
              System.out.println(rp);
       }
       public static void changValue(Object  obj) throws Exception {
              Field[] fs = obj.getClass().getFields();
              for(Field  d : fs) {
                     if(d.getType()==String.class){
                            String old=(String)d.get(obj);
                            String n=old.replace('b', 'a');
                            d.set(obj,n);
                     }
              }
       }
}

运行结果:

aall--aasketaall--itcast

附注:

       问:changValu为什么不用equals比较?

       答:因为字节码只有一份,两个都是一份,所以用==比较

 

5.Method类

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

●得到类中的某一个方法:

MethodcharAt=Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);

示例:

class ReflectDemo {
       public static void main(String[]  args) throws Exception {
              //方法不带参数
              Method m1 =String.class.getMethod("isEmpty");
              System.out.println(m1);
              //方法带参数
              Method m2 =String.class.getMethod("indexOf", int.class);
              System.out.println(m2);
       }
}

运行结果:

public boolean java.lang.String.isEmpty()

public int java.lang.String.indexOf(int)

●调用方法:

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

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

示例:

class ReflectDemo {
       public static void main(String[] args) throwsException {
              String s = "abcdefghi";
              //通常方式
              System.out.println(s.charAt(3));                                         // 结果:d
              //反射方式(JDK1.5invoke方法)
              Method m =String.class.getMethod("charAt", int.class);
              System.out.println(m.invoke(s,3));                                       // 结果:d
              //反射方式(JDK1.4invoke方法)
              System.out.println(m.invoke(s,new Object[] { 3 }));                      // 结果:d
              //当invok方法中第一个参数为null时,表明该方法是静态方法,类名可以直接调用
              Method m2 =String.class.getMethod("valueOf", int.class);
              System.out.println(m2.invoke(null,4));                                   // 结果:4
       }
}

附注:

如果传递给Method对象的invoke方法的第一个参数为null,这有着什么含义呢?——说明该Method对象对应的一个静态方法。

●JDK1.4和JDK1.5的invoke方法的区别:

·JDK1.5:public Object invoke(Objectobj,Object…args){}

·JDK1.4:public Object invoke(Objectobj,Object[] args){}

即按JDK1.4的语法,需要将一个数组作为参数传递给invoke方法,数组中的每一个元素分别对应被调用方法的一个参数,所以调用charAt方法的代码也可以用JDK1.4改写为charAt.invoke(“str”,newObject[]{1})形式。

 

6.用反射方式执行某个类中的main方法

●目标:

       写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法,用普通方式调用完后。

示例:

class ForEach {
       public static void main(String[] args) {
              for(String  s : args) {
                     System.out.println(s);
              }
       }
}
class ReflectDemo {
       public static void main(String[] args) throwsException {
              String start = args[0];//args[0]= ForEach
              Method m =Class.forName(start).getMethod("main", String[].class);
              m.invoke(null,new  Object[] { new  String[] { "abc1-1","abc2-1","abc3-1"} });
              //或者采用下一种方式
              m.invoke(null,(Object) new  String[] {"abc1-2", "abc2-2", "abc3-2" });
       }
}

运行结果:(运行时间ForEach作为参数传递进去

abc1-1

abc2-1

abc3-1

abc1-2

abc2-2

abc3-2

●问题:

       启动Java程序的main方法的参数是一个字符串数字,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按照JDK1.5的语法,整个数组是一个参数,而按照JDK1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?JDK1.5肯定要兼容JDK1.4的语法,会按JDK1.4的语法进行处理,即把数组打散成为若干单独的参数。所以,在给main放传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只能把它当作JDK1.4的语法进行理解,而不把它当作JDK1.5的语法解释,因此会出现参数类型不对的问题。

●解决办法:

       m.invoke(null,new Object[]{“xxx”});

       m.invoke(null,(Object)new String[]{“xxx”});编译器会作特殊处理,编译时不把参数当作数组看待,也就是数组打散成若干个参数了

 

7.数组的反射

·具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象(此处比较与值无关)

·代表数组的Class实例对象的getSuperClass()方法返回的是父类Object类对应的Class。

·基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;

非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。

示例一:

class ReflectDemo {
       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[4];
              System.out.println(a1.getClass());                               // 结果:class [I
              System.out.println(a3.getClass());                               // 结果:class [[I
              System.out.println(a4.getClass());                               // 结果:class [Ljava.lang.String;
              System.out.println(a1.getClass().equals(a2.getClass()));         // 结果:true
              System.out.println(a1.getClass().equals(a3.getClass()));         // 结果:false
              System.out.println(a1.getClass().equals(a4.getClass()));         // 结果:false
              System.out.println(a3.getClass().equals(a4.getClass()));         // 结果:false
              System.out.println(a1.getClass().getSuperclass().getName());     // 结果:java.lang.Object
              System.out.println(a3.getClass().getSuperclass().getName());     // 结果:java.lang.Object
              System.out.println(a4.getClass().getSuperclass().getName());     // 结果:java.lang.Object
              //int[]、String[]类型是object
              Object obj1=a1;
              Object obj2=a2;
              Object obj3=a3;
              Object obj4=a4;
              //a1,a2中是int类型不是Object类型,编译报错
              //Object[] obj5=a1;
              //Object[] obj6=a2;
              //a3其实是数组的数组
              Object[] obj7=a3;
              //String[]中的元素属于Object
              Object[] obj8=a4;
       }
}

示例二:(数组反射)

class ReflectDemo {
       public static void main(String[] args) {
              int[] x = new  int[] { 1, 2, 3 };
              String[] y = new  String[] { "a", "b","c" };
              System.out.println(x);
              System.out.println(y);
              printArray(x);
              System.out.println("----");
              printArray(y);
              System.out.println("----");
              printArray("afeg");
              System.out.println("----");
              printArray(newObject[] { 2, x, "a", 7 });
              System.out.println("----");
              // Arrays.asList()处理int[]数组
              System.out.println(Arrays.asList(x));
              System.out.println("----");
              //Arrays.asList()处理String[]数组
              System.out.println(Arrays.asList(y));
       }
       private static void printArray(Object obj) {
              Class c = obj.getClass();
              if(c.isArray()) {
                     int len = Array.getLength(obj);
                     for(int x = 0; x < len; x++) {
                            System.out.println(Array.get(obj,x));
                     }
              }else {
                     System.out.println(obj);
              }
       }
}

运行结果:

[I@1db9742

[Ljava.lang.String;@106d69c

1

2

3

----

a

b

c

----

afeg

----

2

[I@1db9742

a

7

----

[[I@1db9742]

----

[a, b,c]

·Arrays.asList()方法处理int[]String[]时的差异?

因为此方法在JDK1.4版本中,接收的Object类型的数组,而String[]作为Object数组传入,int[]不可以作为Object数组传入,所以只能按照JDK1.5来处理,在JDK1.5中,传入的是可变参数列表,所以int[]被当作是一个Object传入,作为一个单个参数,而不是数组,所以打印的结果是哈希值。

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

通过Array.get(obj, x)获取元素。

 

8.反射的作用(实现框架功能)

●框架与框架要解决的核心问题

       我做房子卖给用户住,由用户自己安装门和窗,我做的房子就是框架,用户需要使用我的框架,把门窗插入我提供的框架中。框架与工具类有区别——工具类被用户的类调用;框架则是调用用户提供的类。

●框架要解决的核心问题:

       我在写框架(房子)时,你这个用户可能暂时还不具备使用的能力,那我写的框架程序怎样能调用到以后写的类(门窗)中呢?

       因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。

●综合案例:

       ·先直接用new语句创建ArrayList和HashSet的实例对象,覆盖equals和hashCode方法,比较两个集合的运行结果差异;

       ·然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异。

示例:(采用配置文件)

//config.properties文件:

className=java.util.HashSet

class ReflectPoint {
       private  int  x;
       public  int  y;     
       public  String  a="ball";
       public String  b="basketball";
       public String  c="itcast";  
       ReflectPoint(int x, int  y) {
              this.x= x;
              this.y= y;
       }
<span style="color:#FF0000;">        //hash算法将集合分成多干个存储区域,每个对象可以计算出一个哈希值,可以将哈希码分组,
        //每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象存储在哪个区域</span>
        @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;
              ReflectPointother = (ReflectPoint) obj;
              if(x != other.x)
                     return false;
              if(y != other.y)
                     return false;
              return true;
       }
       publicString toString(){
              return a+"--"+b+"--"+c+"--"+x+y;
       }
}
class ReflectTest {
       public static void main(String[] args) throwsException {
              FileInputStream fis = new  FileInputStream("config.properties");
              Properties p = new  Properties();
              p.load(fis);
              fis.close();
              String className =p.getProperty("className");
              Collection c = (Collection)Class.forName(className).newInstance();
              ReflectPoint rp1 = new  ReflectPoint(3, 5);
              ReflectPoint rp2 = new  ReflectPoint(4, 7);
              ReflectPoint rp3 = new  ReflectPoint(9, 6);
              c.add(rp1);
              c.add(rp2);
              c.add(rp3);
              for(Iterator  it = c.iterator();it.hasNext();) {
                     System.out.println(it.next());
              }
       }
}

运行结果:

ball--basketball--itcast--35

ball--basketball--itcast--47

ball--basketball--itcast--96

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值