JAVA笔记七:反射

能够分析类能力的程序称为反射(reflective)。

反射库(reflection library)提供了与丰富且精巧的工具集,可以用来编写能够动态操纵Java代码的程序。

反射机制可以用来:

        1、在运行时分析类的能力

        2、在运行时检查对象

        3、实现泛型数组操作代码

        4、利用Method对象,这个对象很像C++中的函数指针。

 一、Class类

在程序运行期间,Java运行时系统始终为所有的对象维护一个运行时类型标签。这个信息会跟踪每个对象所属的类。虚拟机利用运行时类型信息选择要执行的正确的方法。保存这些信息的类名为Class。Object类中的getClass()方法会返回一个Class类型的实例。

Employee e;

.........

Class cl = e.getClass();

 Class对象会描述一个特定类的属性。,

Object类中的getClass方法将返回一个Class类型的实例。获得Class类对象常用方法:

1、常用的方法是getClass,当使用getName()时这个方法返回类的名字

e.getClass();                           //结果为:class Employee

e.getClass.getName();           //结果为:Employee

 如果类在一个包里,包的名字也作为类名的一部分。

2、还可以使用静态方法forName获得类名对应的Class对象。

String classname = "java.util.Random"

Class cl = Class.forName(class name);

3、如果T是任意的Java类型(或void关键字),T.class将代表类的对象。

Class cl1 = int.class;

Class cl2 = Double[].class;

Class cl3 = Random.class;

Class cl4 = Employee.class;

在启动时,包含main方法的类被加载。它会加载所有需要的类,这些被加载的类再加载它们需要的类。 

注意:一个Class对象实际上表述的是一个类型,这可能是类,与可能不是类。例如,int不是类,但int.class是一个Class类型的对象。

虚拟机为每个类型管理一个唯一的Class对象,可以用“==”运算符实现两个类对象的比较。

if(e.getClass()==Employee.class)      

如果e是一个Employee实例,则为真,与条件e instancoef Employee不同,如果e是某个子类则该测试为假。

如果有一个Class类型的对象,可以用它来构造类的实例。调用getConstructor方法将得到一个Constructor类型的对象,然后使用newInstance方法构建一个实例。

String className = "Employee";

Class cl = Class.forName(className);

Object obj = cl.getConsturctor().newInstance();

如果这个类没有无参数的构造器,getConsturctor方法抛出一个异常。

  • static Class forName(String className)        //返回一个Class对象,表示名为className的类
  • Constructor getConstructor(Class...parameterTypes)   //生成一个对象,描述有指定参数的构造器
  • Object newInstance(Object...params)    //将params传递到构造器,来构造这个构造器声明的一个新实例。

二、声明异常入门

当运行发生错误时,程序就会“抛出一个异常”。抛出异常比终止程序灵活,因为可以提供一个处理器(handler)“捕获”这个异常并处理。

异常有两种类型:非检查型(unchecked)异常和检查型(checked)异常。对于越界错误或者访问null引用,都属于非检查型异常。

不是所有的错误都是可以避免的,例如Class.forName方法,没有办法确保有指定名字的类一定存在。如果一个方法包含一条可能抛出检查型异常的语句,则在方法名上增加一个throws子句。

public static void doSomethingWithClass(String name)

       throws ReflectiveOperationException

{

        Class cl = Class.forName(name);

}

 调用这个方法的任何方法也都需要一个throws声明。也包括main方法。如果一个异常确实出现,main方法将终止并提供一个堆栈轨迹。

三、资源

类通常有一些关联的数据文件:图像和声音文件,包含消息字符串和按钮标签的文本文件。在Java中,这些关联的文件被称为资源。

Class类中提供了一个可以查找资源文件的服务。步骤如下:

1、获得拥有资源的类的Class对象,例如,Resource.class

2、有些方法,如ImageIcon类的getImage方法,接收描述资源位置的URL。则要调用如下:

                URL url = cl.getResource("about.gif");

3、使用getResouceAsStream方法得到一个输入流来读取文件中的数据。

import javax.swing.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class ResourceTest {
    public static void main(String args[]) throws IOException {
        Class cl = ResourceTest.class;
        //获取图片的URL
        URL aboutURL = cl.getResource("java核心技术(1).jpg");
        //ImageIcon()接收图片的url
        var icon = new ImageIcon(aboutURL);

        //处理txt文字
        InputStream stream = cl.getResourceAsStream("data.txt");
        var about = new String(stream.readAllBytes(),"UTF-8");

        InputStream stream1 = cl.getResourceAsStream("title.txt");
        var title = new String(stream1.readAllBytes(), StandardCharsets.UTF_8).trim();

        JOptionPane.showMessageDialog(null,about,title,JOptionPane.INFORMATION_MESSAGE,icon);
    }
}

四、利用反射分析类的能力

反射机制最重要的内容:检查类的结构

在java.lang.refect包中三个类Field、Method、Constructor用于描述类的字段、方法、构造器。

这三个类都有一个叫做getName的方法,用来返回字段、方法与构造器的名称。

这三个类都有一个名为getModifiers的方法,它将返回一个整数,用不同的0/1位描述所使用的修饰符,如public和static。我们还可以使用Modifier类中的isPublic、isPrivate、isFinal判断方法后构造器是public、private、final。

Field类有一个getType方法,用来返回描述字段类型的一个对象,这个对象的类型也为Class。

/*--------------------------------------------------------分割线------------------------------------------------------------*/

Class类中的getFields、getMethods、getConstructors方法将返回这个类支持的公共字段、方法和构造器的数组其中包括超类的成员。

Class类getDeclareFields、getDeclareMotheds、getDeclaredConstructor方法分别返回类中声明的全部字段、方法、构造器的数组,其中包括私有成员,包成员,受保护成员,但不包括超类成员

  • Field[] getFields()     
  • Field[] getDeclaredField()

  //返回一个包含Field对象的数组,这个对象对应这个类或其超类的公共字段。getDeclareField方法也返回一个包含Field对象的数组,这些对象对应这个类的全部字段。如果类中没有字段,或者Class对象描述的是基本类型或数组类型,这些方法将返回一个长度为0的数组。

  • Method[] getMethod()
  • Method[] getDeclareMethod()

返回包含Method对象的数组:getMethod将返回所有的公共方法,包括从超类继承来的公共方法;getDeclareMethod返回这个类或接口的所有方法,但不包括由超类继承的方法。

  • Constructor[] getConstructors()
  • Constructor[] getDeclareConstructors()

返回包含Constructor对象的数组,其中包含Class对象所表示的类的所有公共构造器(getConstructors)或全部构造器(getDeclaredConstructors)

  •  String getName()        //返回一个表示构造器名、方法名或字段名的字符串
  • Class getSuperclass()     //获取其超类,返回值为ClassA
  • Class[] getParmeterTypes()   //返回一个Class对象数组,其中各个对象表示参数的类型
  • Class getReturnType()       //在Method类中,返回一个用于表示返回类型的Class对象
  • int getModifiers()       //返回一个整数,描述这个构造器、方法、字段的修饰符
  • Modifier.toString()        //这个方法将修饰符打印出来

import java.lang.reflect.*;
import java.util.Scanner;
import cn.edu.abc.corejava.*;
import javax.swing.*;

public class ReflectionTest
{
    public  static void main(String args[])
            throws ClassNotFoundException
    {
        String name;
        //接收name字符串,若main方法中无参数,则接收输入的字符串
        if(args.length>0)
            name = args[0];
        else
        {
            var in = new Scanner(System.in);
            System.out.println("Enter class name(e.g. java.util,Date):   ");
            name = in.next();
        }
        Class cl = Class.forName(name);
        Class super_cl = cl.getSuperclass();
        //获取类的修饰符
        String modifiers = Modifier.toString(cl.getModifiers());
        System.out.println("name:"+ name);
        if(modifiers.length()>0)
            System.out.println(modifiers+ " ");
        else
            System.out.println("该"+name+"类无修饰符");
        if (super_cl!=null&&super_cl!=Object.class)
            System.out.println("extends "+super_cl.getName());
        else
            System.out.println("\n除Object超类外,无其他超类");
        //System.out.println("\n");
        System.out.println("构造器:\n");
        printConstructor(cl);
        System.out.println("方法:\n");
        printMethod(cl);
        System.out.println("字段:\n");
        printFields(cl);


    }
    public static void printConstructor(Class cl)
    {
        Constructor[] constructors = cl.getDeclaredConstructors();
        for(Constructor c:constructors)
        {
            String name = c.getName();
            System.out.print("  ");
            String modifiers = Modifier.toString(cl.getModifiers());
            if(modifiers.length()>0)
                System.out.print(modifiers+" ");
            System.out.print(name+"(");
            //获取参数类型
            Class[] paramType = c.getParameterTypes();
            for (int j = 0;j<paramType.length;j++)
            {
                if(j>0)
                    System.out.print(",");
                System.out.print(paramType[j].getName());
            }
            System.out.println(");");
        }
    }
    public static void printMethod(Class cl)
    {
        Method[] methods = cl.getDeclaredMethods();
        for(Method f:methods)
        {
            Class retType = f.getReturnType();
            String name = f.getName();
            //获取修饰符
            System.out.print("  ");
            String modifiers = Modifier.toString(f.getModifiers());
            if (modifiers.length()>0)
                System.out.print(modifiers+"  ");
            System.out.print(retType.getName()+" "+name+"(");
            //System.out.println("方法的修饰符:"+modifiers+"\t\t"+"方法名为:"+name);
            //获取方法的参数类型
            Class[] paramTypes = f.getParameterTypes();
            for (int j = 0;j<paramTypes.length;j++)
            {
                if(j>0)
                    System.out.print(",");
                System.out.print(paramTypes[j].getName());
            }
            System.out.println(");");
        }

    }
    public static void printFields(Class cl)
    {
        //获取除超类外的所有字段
        Field[] fields = cl.getDeclaredFields();
        for(Field f:fields)
        {
            //获取字段数据类型、字段名、字段修饰符
            Class type = f.getType();
            String name = f.getName();
            System.out.print("  ");
            String modifiers = Modifier.toString(f.getModifiers());
            if(modifiers.length()>0) System.out.print(modifiers+" ");
            System.out.println(type.getName()+" "+name+" "+";");
        }

    }
}

 综上:

        这三个类均有getName()、getModifiers()、Modifiers.toString()方法,分别为获取其名字、修饰符,一般选用Modifiers.toString()方法来将获取到的修饰符转为字符串输出。

        获取字段的参数类型:

                                            Class.gerType()

        获取方法、构造器的参数类型:

                                            Class[] getParamTypes()

                                            Class getReturnType()

        获取(不含超类)所有字段、方法、构造器的方法为 :

`                                           Field[] getDeclareFields()      

                                            Method[] getDeclaredMethods()    

                                            Constructor[] getDeclaredConstructors() 

                                              

五、利用反射在运行时分析对象

1、查看字段的具体内容。使用Field类的get方法, 如果f是一个Field类型的对象,通过getDeclareField的得到的对象,obj是某个包含f字段的类的对象

在前面利用反射机制可以查看在编译时还不知道的对象字段。

var harry = new Employee("Harry Hacker",50000,10,1,1989);
Class cl = harry.getClass();
Field f = cl.getDeclaredField("name");
f.setAccessible(ture);
Object v = f.get(harry);

注意:由于name为私有字段,所以get与set方法抛出异常,可以使用Field、Method、Constructor的setAccessible方法,允许访问私有字段。

Object get(Object obj)        //返回obj对象中用这个Field对象描述字段的值

void set(Obejct obj,Object newValue)         //将obj对象的这个Field字段值设置为一个新值

 2、调用任意方法与构造器

Method类中的invoke方法,允许调用包装在当前Method对象中的方法。

Method m = cl.getMethod("getId");
m.invoke(harry);

public Object invoke(Object implicitParameter,Object[] explicitParameters)

调用这个对象描述的方法,传入给定参数,并返回方法的返回值。

 六、继承的设计技巧

1、将公共操作与公共字段放在超类中。

2、不要受保护的字段。

3、使用继承“is-a”关系

4、除非所有继承的方法都有意义,否则不要使用继承

5、在覆盖方法不要改变预期行为

6、使用多态而不适用类型信息

7、不要滥用反射

猜你喜欢

转载自blog.csdn.net/m0_61598337/article/details/128427461