能够分析类能力的程序称为反射(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、不要滥用反射