黑马程序员基础加强---反射

黑马基础加强之反射

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

1.   Class类与Java反射

public final class Class<T>
extends Object
implements Serializable, GenericDeclaration, Type, AnnotatedElement

1.1.什么是Class类

在Java中,每个class都有一个相应的Class对象。也就是说当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。Class类是用来描述字节码的。

1.2.关于Class类的小知识

1)  Class的源代码声明为 public finalclass Class<T>…{…} , 说明Class类是一个泛型类型,参数T说明某个Class类对象属于哪一个类;

2)  final 修饰Class类,说明Class类不会被继承;

3)  Class类是不能被new 出来,只能上述的三种方法创建。

4)  Class类可以获取字节码文件中的所有信息。

5)  可以使用isPrimitive方法来判断该对象是否是预定义对象,方法格式如下所示:

public boolean isPrimitive():判定指定的 Class 对象是否表示一个基本类型,是true,否false

6)  关于数组类型的Class实例对象,可以通过isArray()方法来判断,方法格式如下所示:

public boolean isArray():判定此 Class 对象是否表示一个数组类,是true,否false

1.3 九种预定义对象

在Class类中共创建了九个预定义对象,表示八个基本类型和void(void也有对应的class);这些类对象由Java虚拟机常见,与其表示的基本类型同名,即boolean,byte,char,short,int,long,float和double。

这些对象仅能通过声明为public staticfinal的变量访问。

1.4.获取Class实例的三种方式

   1)利用对象调用getClass()方法获取该对象的Class实例;

     1.该方法为非静态方法,继承自Object的getClass()方法;

     2.这个方法在反射中不常使用,如果这需要知道明确的类名并建立相应的对象,使用起来非常麻烦。

2)运用类名.class方式来获取Class实例。

   1.该方法为静态方法。

   2.这个方法也不太常用,因为这方法也是需要明确的类名。

3) 使用Class类的静态方法forName(),用类的名字获取一个Class实例(Class.forName() 中”()”内为类的全名, 如Class.foName(“java.lang.Date”)”);

   1.该方法仅仅需要以字符串形式给出类名即可获取这个类的Class对象,这个形式最为常用,在反射编程中经常使用,因为一开始时,源序是不知类名的。

   2.使用forName()有两种返回字节码文件的两种方式。

       1)如果这份字节码文件已经被加载过的,此时forName()直接返回

       2)如果JVM没有找到要加载的字节码文件,那么JVM就指示类加载器去加载该字节码文件。JVM将加载完的字节吗放到自己的缓存中,再返回。

   用以下代码表示有这三种获得Class代码的方式。

 代码实例:

public class TestClass {
	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception{
		// TODO Auto-generated method stub
		Person p1 = new Person(20,"胡志平");   
		Person p2 = new Person();
		//根据具体对象来获得Class对象
		Class cla1 = p1.getClass();
		Class cla2 = p2.getClass();
//输出true
		System.out.println(cla1 == cla2); 
 //使用类名.class获得Class对象   
		Class cla3 = Person.class; 
//使用forName()获得Class对象
		Class cla4 = Class.forName("com.ping.test.Person"); 
//输出true
		System.out.println(cla1 == cla3);   
//输出true
		System.out.println(cla1 == cla4);   
	}
}
   //创建一个对象
class Person{
	private int age;
	private String name;
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Person(){}
	public Person(int age, String name) {
		super();
		this.age = age;
		this.name = name;
	}
}

由上面的代码可以看出:一个类无论有多少个对象,他们的Class对象是同一个对象,也就是 同一份字节码文件。     

1.5 通过反射访问的主要描述信息


组成部分

访问方法

返回值类型

说明

包路径

getPackage()

Package对象

获得该类存入路径

类名称

getName()

String对象

获得该类的名称

继承类

getSuperclass()

Class对象

获得该类继承的类

实现接口

getInterfaces()

Class对象数组

获得该类实现的所有接口

构造方法

getConstructeors()

Constructor型数组

获得所有权限为public的指定构造函数

getConstructor(Class<?>…parameterTypes)

Constructor对象

获得权限为public的指定构造函数,

getDeclaredConstructors()

Constructor型数组

获得所有构造函数,按声明的顺序返回

getDeclaredConstructor(Class<?>parameterTypers)

Constructor对象

获得指定构造方法

函数

getMethods()

Method型数组

获得所有权限为public的方法

getMethod(String name,Class<?>…parameterTypes)

Method对象

获得权限为public的指定方法

getDeclareMethods()

Method型数组

获得所有方法,按声明顺序返回

getDeclareMethod(String name , Class<?>….parmeterTypes)

Method对象

获得指定方法

成员变量

getFields()

Field型数组

获得所有权限为public的成员变量

getField(String name)

Field对象

获得权限为public的指定成员变量

getDeclaredFields()

Field对象数组

获得所有成员变量,按声明顺序返回

getDeclareField(String name)

Field对象

获得指定成员变量

内部类

getClasses()

Class型数组

获得所有权限为public的内部类

getDeclaredClasses()

Class型数组

获得所有内部类

内部类的声明类

getDeclaringClass()

Class对象

如果该类为内部类,则返回它的成员类,否则返回null

  说明:通过方法getFields()和getMethods()依次获得权限为public的成员变量和方法时,将包含从父类中继承到的成员变量与方法,而通过方法getDeclaredFields()和getDeclaredMethods()只是获得在本类中定义的所有成员变量和方法。

总结:反射就是Java类中的各种成分映射成相应的Java类。

2.Constructor类  -->代表字节码中的构造函数

public final class Constructor<T>
extends AccessibleObject
implements GenericDeclaration, Member

在通过下列一组方法访问构造函数时,将返回Constructor类型的对象或数组。每个Constructor对象代表一个构造函数,利用Constructor对象可以操纵相应的构造函数。

2.1相应的构造函数

问指定的构造方法,需要根据该函数的入口参数来访问。例如,访问一个入口参数依次为String和int 型的构造函数,通过下面两种方式均可实现。

Class.getDeclaredConstructor(String.class,int.class);

Class.getDeclaredConstructor(newClass[]{String.class,int.class});

即jdk.1.4与jdk1.5的差别,参数数组与可变参数的差别也。

2.2 Constructor类常用方法

实例代码:访问构造函数

import java.lang.reflect.Constructor;
public class ConstructorReflectDemo {
    /**
     * @param args
     */
    public static void main(String[] args){
        Example example = new Example("我",200);
        //获得Class对象
        Class exampleC = example.getClass();
        //获得所有构造方法
        Constructor[]declaredConstructors = exampleC.getDeclaredConstructors();
        //遍历构造方法
        for(int i = 0 ; i < declaredConstructors.length ; i ++){
            //每个构造方法的对象
            Constructor constructor = declaredConstructors[i];
            //可变参数
            System.out.println("查看是否允许带有可变数量的参数:" + constructor.isVarArgs());
            //获得所有参数类型
            System.out.println("该构造函数的入口参数类型依次为:");
            Class[]parameterTypes = constructor.getParameterTypes();
            for(int j = 0 ; j < parameterTypes.length  ; j++){
                System.out.println(" " + parameterTypes[j]);
            }
            //获得所有可能抛出异常的信息类型
            System.out.println("该构造函数抛出异常的类型为:");
            Class[]exceptionTypes = constructor.getExceptionTypes();
            for(int j = 0 ; j < exceptionTypes.length ; j ++ ){
                System.out.print(" " + exceptionTypes[j]);
            }
            Example example2 = null;
            while(example2 == null){
                    try {
                        if(i == 0){
                            //通过执行默认没有参数的构造方法创建对象
                        example2 = (Example)constructor.newInstance();}
                        else if(i == 1){
                            //通过执行两个参数的构造方法创建对象
                            example2 = (Example)constructor.newInstance("7",5);
                        }else{
                            //通过执行具有可变参数的构造方法创建对象
                            Object[]parameters = new Object[]{new String[]{"100","200","300"}};
                            example2 = (Example)constructor.newInstance(parameters);
                        }
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        System.out.println("创建对象时出现抛出异常,下面执行setAccessible()方法");
                        constructor.setAccessible(true);//设置为允许访问
                        //e.printStackTrace();
                    }
            }
            example2.print();
            System.out.println();
        }
    }
}
class Example{
    String s ;
    int i1,i2 ,i3;
    private Example() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Example(String s , int i1){
        this.s = s;
        this.i1 = i1;
    }
    public Example(String...strings){
        if(0<strings.length){
            i1 = Integer.valueOf(strings[0]);
        }
        if(1<strings.length){
            i2=Integer.valueOf(strings[1]);
        }if(2<strings.length){
            i3 = Integer.valueOf(strings[2]);
        }
    }
    public void print(){
        System.out.println("s = " + s);
        System.out.println("i1 = " + i1);
        System.out.println("i2 = " + i2);
        System.out.println("i3 = " + i3);
    }
}
输出:
查看是否允许带有可变数量的参数:false
该构造函数的入口参数类型依次为:
该构造函数的抛出异常的类型为:
创建对象时出现抛出异常,下面执行setAccessible()方法
s = null
i1 = 0
i2 = 0
i3 = 0
查看是否允许带有可变数量的参数:false
该构造函数的入口参数类型依次为:
 class java.lang.String
 int
该构造函数的抛出异常的类型为:
s = 7
i1 = 5
i2 = 0
i3 = 0
查看是否允许带有可变数量的参数:true
该构造函数的入口参数类型依次为:
 class [Ljava.lang.String;
该构造函数的抛出异常的类型为:
s = null
i1 = 100
i2 = 200
i3 = 300

3.Field类

public final class Field
extends AccessibleObject
implements Member

3.1 得到Field类的方法

每个Field对象代表一个成员变量,利用Field对象操纵相应的成员变量

3.2 Field类中提供的常用方法

代码实例:访问成员变量

import java.lang.reflect.Field;
public class FiledDemo {
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Example_02 example = new Example_02();
        Class exampleC = example.getClass();
        //获得所有成员变量
        Field [] declaredFields = exampleC.getDeclaredFields();
        for(int i = 0 ; i < declaredFields.length ; i ++){
            //遍历成员变量
            Field field = declaredFields[i];
            //得到成员变量的名称
            String fieldName = field.getName();
            //得到成员变量的类型
            System.out.println("名称:" + fieldName);
            Class fieldType = field.getType();
            System.out.println("类型为:" + fieldType);
            boolean isTurn = true;
            while(isTurn){
                //如果该成员变量的访问权限为private,则抛出异常,即不允许访问
                try {
                    //设置循环条件,第循环一次即跳出循环
                    isTurn = false;
                    //获得成员变量
                    System.out.println("修改前的值为:" + field.get(example));
                    //判断成员变量是否为int型
                    if(fieldType.equals(int.class)){
                        System.out.println("利用方法setInt()修改成员变量的值");
                        //为int型赋值
                        field.setInt(example, 168);
                        //判断成员变量是否为float型
                    }else if(fieldType.equals(float.class)){
                        System.out.println("利用setFloat()修改成员变量的值");
                        //为float型赋值
                        field.setFloat(example, 63.52f);
                        //判断成员变量是否为boolean型
                    }else if(fieldType.equals(boolean.class)){
                        System.out.println("利用setBoolean()修改成员变量的值");
                        //为boolean型数据赋值
                        field.setBoolean(example, true);
                    }else{
                        //为剩下来的变量赋值
                        System.out.println("利用方法set()修改成员变量的值");
                        field.set(example, "Hello");
                    }
                    //输出修改的后的值
                    System.out.println("修改后的值:"+field.get(example));
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    System.out.println("在设置成员变量时抛出异常!");
                    //设置访问权限
                    field.setAccessible(true);
                    isTurn = true;
                    e.printStackTrace();
                }
            }
        }
    }
}
class Example_02{
    int i;
    public float f;
    protected boolean b;
    private String s;
}
输出:
名称:i
类型为:int
修改前的值为:0
利用方法setInt()修改成员变量的值
修改后的值:168
名称:f
类型为:float
修改前的值为:0.0
利用setFloat()修改成员变量的值
修改后的值:63.52
名称:b
类型为:boolean
修改前的值为:false
利用setBoolean()修改成员变量的值
修改后的值:true
名称:s
类型为:class java.lang.String
在设置成员变量时抛出异常!
java.lang.IllegalAccessException: Class com.ping.test.FiledDemo can not access a member of class com.ping.test.Example_02 with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
    at java.lang.reflect.Field.doSecurityCheck(Field.java:960)
    at java.lang.reflect.Field.getFieldAccessor(Field.java:896)
    at java.lang.reflect.Field.get(Field.java:358)
    at com.ping.test.FiledDemo.main(FiledDemo.java:32)
修改前的值为:null
利用方法set()修改成员变量的值
修改后的值:Hello

5. Method类

  在通过下列一组方法访问方法,将返回Method类型的对象或数组。每个Method对象代表一个方法,利用Method对象可以操作相应的方法。

5.1得到Method类的方法

如果是访问指定的方法,需要根据该方法的名称和入口参数的类型来访问。例如,访问一个名称为print、入口参数类型依次为String和int类型的方法,可以通过如下两种方式来实现。

ObjectClass.getDeclaredMethod(“print”,String.class,int.class);

objectClass.getDeclaredMethod(“print”,newClass[]{String.class,int.class});

即jdk.1.4与jdk1.5的差别,参数数组与可变参数的差别也。

5.2 Method类中提供的常用方法

实例代码:访问方法

import java.lang.reflect.Method;
public class MethodDemo {
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Example_03 example = new Example_03();
        //获得Class对象
        Class exampleC = example.getClass();
        //获得所有非构造方法
        Method[]declaredMethods = exampleC.getDeclaredMethods();
        for(int i = 0 ; i < declaredMethods.length ; i++){
            Method method = declaredMethods[i];
            System.out.println("方法名称为:" + method.getName());
            //判断是否允许带可变数量的参数
            System.out.println("是否允许带有可变数量的参数:" + method.isVarArgs());
            System.out.println("入口参数类型依次是:"  );
            //获得入口参数类型集合
            Class [] parameterTypes = method.getParameterTypes();
            //循环输出入口参数
            for(int j = 0 ; j < parameterTypes.length ; j ++ ){
                System.out.println(" " + parameterTypes[j]);
            }
            //获得返回值类型
            System.out.println("返回值类型为:" + method.getReturnType());
            System.out.println("可能抛出的异常为:" );
            //获得方法可能发生的异常集合
            Class [] exceptionTypes = method.getExceptionTypes();
            for(int j = 0 ; j < exceptionTypes.length ; j ++){
                System.out.println(" " + exceptionTypes[j]);
            }
            boolean isTurn = true;
            while(isTurn){
                //若该方法的访问权限为private,则抛出异常,即不允许访问
                isTurn = false;
                                try {  
                        if(i == 0){
                            //执行方法
                        method.invoke(example);}
                        else if(i == 1){
                            //执行方法
                            System.out.println("返回值为:" + method.invoke(example, 167));
                        }
                        else if(i == 2){
                            //执行方法
                            System.out.println("返回值为:" + method.invoke(example, "7" , 5));
                        }else{
                            //定义二维数组
                            Object [] parameters = new Object[]{new  String[]{"P","I","N","G"}};
                            //执行方法
                            System.out.println("返回值为:" + method.invoke(example, parameters));
                        }
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        System.out.println("在执行方法时出现异常:" );
                        //暴力反射,设置允许访问
                        method.setAccessible(true);
                        isTurn = true;
                        e.printStackTrace();
                    }
            }
            System.out.println();
        }
    }
}
//一个带典型方法的类
class Example_03{
    static void staticMethod(){
        System.out.println("执行staticMethod方法");
    }
    public int publicMethod(int i){
        System.out.println("执行publicMethod()方法");
        return i *100;
    }
    protected int protectedMethod(String s , int i){
        System.out.println("执行protectedMetho()方法");
       
        return Integer.valueOf(s) + i;
    }
    private String privateMethod(String...strings){
        System.out.println("执行privateMethod()方法");
        StringBuffer sb = new StringBuffer();
        for(int i = 0 ; i < strings.length ; i ++ ){
            sb.append(strings[i]);
        }
        return sb.toString();
    }
}
输出:
方法名称为:staticMethod
是否允许带有可变数量的参数:false
入口参数类型依次是:
返回值类型为:void
可能抛出的异常为:
执行staticMethod方法
方法名称为:publicMethod
是否允许带有可变数量的参数:false
入口参数类型依次是:
 int
返回值类型为:int
可能抛出的异常为:
执行publicMethod()方法
返回值为:16700
方法名称为:protectedMethod
是否允许带有可变数量的参数:false
入口参数类型依次是:
 class java.lang.String
 int
返回值类型为:int
可能抛出的异常为:
执行protectedMetho()方法
返回值为:12
方法名称为:privateMethod
是否允许带有可变数量的参数:true
入口参数类型依次是:
 class [Ljava.lang.String;
返回值类型为:class java.lang.String
可能抛出的异常为:
在执行方法时出现异常:
java.lang.IllegalAccessException: Class com.ping.test.MethodDemo can not access a member of class com.ping.test.Example_03 with modifiers "private transient"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
    at java.lang.reflect.Method.invoke(Method.java:588)
    at com.ping.test.MethodDemo.main(MethodDemo.java:57)
执行privateMethod()方法
返回值为:PING

6. 执行main方法

(1)目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?

(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,newString[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。

(3)解决办法:

mainMethod.invoke(null,new Object[]{newString[]{"xxx"}});

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

示例代码:

class TestArguments
{
    public static void main(String[] args)
    {
        for (String arg : args)
        {
            System.out.println(arg);
        }
    }
}

通过传统方式调用代码如下所示:

TestArguments.main(new String[]{"111","222","333"});

运行结果如下所示:

通过反射的方式调用TestArguments这个类的main方法,代码如下所示:

String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
//下面语句使用会出现异常见下图
mainMethod.invoke(null, new String[]{ "111", "222", "333" });

运行时错误结果如下所示:

通过第(3)步中的解决方法运行时代码如下所示:

String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
//编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
mainMethod.invoke(null, new Object[] { new String[] { "111", "222", "333" } });
mainMethod.invoke(null, (Object) new String[] { "111" });

运行时结果如下所示:

提示:

(1)获取一个类的完整名称:使用功能键“F2”,如图所示:

 

(2)在MyEclipse中如何传递一个参数运行,步骤如下图所示:

7.数组的反射

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

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

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

(2)Arrays.asList()方法处理int[]和String[]时的差异。

int[] a1 = new int[] { 1, 2, 3 };
String[] a4 = new String[] { "a", "b", "c" };
System.out.println("直接打印出对象中内容");
System.out.println(a1);
System.out.println(a4);
System.out.println("Arrays.asList()方法输出");
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));

运行结果如下所示:


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

// 通过反射打印数组
private 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);
    }
}

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值