反射
java数据类型分为原始类型和引用类型。对于每种类型的对象java虚拟机会实例化不可变的java.lang.Class对象,它提供了在运行时检查对象属性的方法,这些属性包括它的成员和类型信息。
注:Class是泛型类,可以使用@SuppressWarnings("unchecked")忽略泛型或者使用Class<?>类型,?表示任意类型。
获取class对象的五种方法
- Object.getClass():如果一个类的对象可用,则最简单的获的Class的方法是使用Object.getClass()(只对引用类型有用);
- .class:若果类型可用但没有对象,可以在类型后加上".class"来获的Class对象;
- Class.forName():如果知道类的全名,可使用静态方法Class.forName来获的Class对象,但它不能用在原始类型上,抛出ClassNotFoundException异常;
- 包装类的Type域:每个原始类型和void都有包装类,利用其Type域就可以获的Class对象(注:Double Integer分别是double以及int的包装类);
- 以Class返回值得方法:参考反射API。
getName的返回值
对不同类型的对象或类,class对象的名称是不同的,从这个名称就可以判断原来类型的,所有数组对象都有"["。具体返回值为:
如果此类对象表示的是非数组类型的引用,则返回该类的二进制名称(如java.lang.Class);
如果此类对象表示一个基本类型(如int,long)或void,则返回的名字是一个基本类型或void所对应的java语言关键字相同的字符串;
如果此类对象表示一个数组,名字的内部形式为:表示该数组嵌套深度的一个或多个"["字符加元素类型名,元素类型名的编码为:
元素类型 | 编码 | 元素类型 | 编码 | 元素类型 | 编码 |
boolen | Z | int | I | char | C |
byte | B | float | F | long | J |
double | D | short | S | class or interface | LclassName |
//1. 获得引用类型名称 String dateName = new Date().getClass().getName();// 对象获的Clas对象 String dateClassName = Date.class.getName();//类获的Class对象 String dateClassForName = Class.forName("java.util.Date").getName(); --他们都返回类的二进制名称:java.util.Date //2. 获得原始类型名称 String byteName = int.class.getName();// 获得原始类型名称 --返回值为int //3. 引用类型数组 String oneDimensionArray = new Date[4].getClass().getName(); --一维:[Ljava.util.Date String twoDimensionArray = new int[4][4].getClass().getName(); --二维:[[I |
查看类声明
通常类的声明包括常见修饰符(public,protected,private,abstract,static,final) 类的名称、类的泛型参数、类的继承类(实现的接口)、类的注解等信息。
Class类的实例表示正在运行的java应用程序中的类和接口。枚举是一个类,注释是一个接口,每个数组属于被映射为Class对象的一个类,所有具有相同元素类型和维数的数组都共享该Class对象。
Class类常用方法:
注:Java语言预定义的注解只有@Deprecated可以在运行时获得。
Class<?> clazz = Class.forName("java.util.ArrayList");// 获得ArrayList类对象 // 1. 输出类的泛型参数 TypeVariable<?>[] typeVariables = clazz.getTypeParameters(); // 2. 输出类所实现的所有接口 Type[] interfaces = clazz.getGenericInterfaces(); //3. 输出类的直接继承类,如果是继承自Object则返回空 Type superClass = clazz.getGenericSuperclass(); //4. 输出类的所有注释信息,有些注释信息是不能用反射获得的 Annotation[] annotations = clazz.getAnnotations(); |
一、Type类型
Type是Java编程语言中所有类型的普通的父接口。这些类型包括原生类型(raw types),参数化类型(parameterized types),数组类型(array types),类型变量(type variables)和 原始类型(primitive types)。我们一般不直接操作Type类型,但了解一下Type类型的层次结构还是有必要的。
1、Type层次结构
2、Class,Method和Field的继承体系
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息保存着每个对象所属的类足迹。虚拟机利用运行时信息选择相应的方法执行。然而,可以通过专门的Java类访问这些信息。保存这些信息的类称为Class,泛型形式为Class。Class是反射机制的基础,反射API通过操作Class来获取其完整结构。
查看类成员
类的成员包括成员变量、方法、构造器以及内部类等,常用方法如下:
另:getDeclaredClasses() 返回该对象的内部类
classObject.getDeclareField("域的字符串形式")
getDeclareField与getField区别:前者获取所有成员变量,后者只获取public成员变量。
Class<?> clazz = Class.forName("java.util.ArrayList");// 获得ArrayList类对象 //1. 获得该类对象的所有构造方法 Constructor[] constructors = clazz.getConstructors(); //2. 获得该类对象的所有非继承域 Field[] fields = clazz.getDeclaredFields(); //3. 获得该类对象的所有非继承方法 Method[] methods =clazz.getDeclaredMethods(); |
按继承层次对类排序
Class类中的isAssignableFrom方法用来判断当前Class对象所表示的类与给定的Class对象所表示的类之间的关系,如果相同或者是其父类,则返回true,如clazz1.isAssignableFrom(clazz2) 若clazz1是否是clazz2父类…
排序:
TreeSet<E>是基于TreeMap的NavigableSet实现的,它使用元素的自然顺序或创建TreeSet<E>对象时提供的Comparator对元素进行排序。
//重写Comparator接口—指定其泛型类型为Class<?> public class ClassComparator implements Comparator<Class<?>> { @Override // 通过实现Comparator接口来实现比较功能 public int compare(Class<?> clazz1, Class<?> clazz2) { if (clazz1.equals(clazz2)) {// 如果两个类对象相同则返回0 return 0; } if (clazz1.isAssignableFrom(clazz2)) { return -1; // 如果clazz1所表示的类是clazz2所表示的类的父类则返回-1 } if (clazz2.isAssignableFrom(clazz1)) { return 1; // 如果clazz1所表示的类是clazz2所表示的类的子类则返回1 } throw new IllegalArgumentException("两个类之间没有关系");// 其他情况抛出异常 } } //创建TreeSet对象时指定排序方式,以及确定泛型为Class<?> TreeSet<Class<?>> treeSet = new TreeSet<Class<?>>(new ClassComparator()) treeSet.add(JPanel.class);// 向树集中添加JPanel.class System.out.println(treeSet.last());// 获得树集的最后一个元素 |
动态设置类的私有域
Field类提供有关类或接口的的单个字段信息,以及对他的动态访问权限,反射的字段可能是一个类(静态)字段或实例字段。Field类常用方法有:
注:对于私有域,首先要用setAccessible()将其可见性设置为true才能设置新值。
在set(Object obj,Object value)函数中,obj为字段所在的对象,若为静态字段,则obj为null,value为设置的值。
Student student =new Student(); //创建实例对象 Class<?> stuClass = student.getClass();//获取反射对象 Field accountField = stuClass.getDeclaredField("account");//获取指定字段的值 accountField.setAccessible(true);//字段的可见性设为true accountField.set(student, 200);//设置值为200 accountField.setDouble(student, 300); System.out.println(student.getAccount()); |
动态调用类中的方法
Java中调用类的方法有两种方式:对于静态方法,可以直接使用类名调用,对于非静态方法,必须使用类的对象调用。
Method类提供类或接口上单独某个方法(以及如何方法该方法)的信息。它通过public Object invoke(Object obj, Object …args)调用方法,obj为方法所在的对象,静态方法为null,…args为参数,返回值为Object类型。
1.获取指定名称的无参数方法 Method showMethod = stuClass.getDeclaredMethod("show"); 2.调用指定对象的无参方法 showMethod.invoke(student); 3.获取指定名称的方法,后面的参数是参数的类型 Method showInfo = stuClass.getDeclaredMethod ("showInfo",String.class,int.class); 4.调用指定参数的方法,后面的参数是参数的值,要与参数类型相对应 String rString=(String)showInfo.invoke(student,"DSL",20); |
动态实例化对象
java通常使用构造方法来创建对象,构造方法分为有参数和无参数两种,如果类中没有定义构造方法,编译器会自动添加一个无参数的构造方法。Constructor类提供类的单个构造方法的信息以及对它访问权限。它允许在将实参与带有底层构造方法的形参的newInstrance()匹配时进行转换。
public T newInstance(Object …args)
//获取类的无参数构造器属性 Constructor<Student> constructor = Student.class.getDeclaredConstructor(); //调用无参数的构造器创建对象 Student studentCreate = constructor.newInstance(); //获取类的有参数构造器属性 Constructor<Student> constructorArgs = Student.class.getDeclaredConstructor(String.class,int.class,double.class); //传入参数创建对象 Student studentCreateArgs = constructorArgs.newInstance("DSL",28,7000); |
创建可变长度数组
ArrayList可以动态添加和删除数组。
Array提供了动态创建和访问java数组的方法,允许在执行get或set操作期间进行扩展转换,它也提供获取/删除指定位置元素的方法。
对于Class类实例,classObject.isArray()判断classObject是否是数组类型。
public static Object increaseArray(Object array) { Class<?> clazz = array.getClass();// 获得代表数组的Class对象 if (clazz.isArray()) {// 判断其对应的类型是否是数组 // 获得数组元素的类型 Class<?> componentType = clazz.getComponentType(); int length = Array.getLength(array);// 获得输入的数组的长度 // 新建数组—指定数组元素类型以及长度,创建新的数组对象 Object newArray = Array.newInstance(componentType, length + 5); System.arraycopy(array, 0, newArray, 0, length);// 复制原来数组中的所有数据 return newArray;// 返回新建数组 } return null;// 如果输入的不是数组就返回空 } |
反射与动态代理
使用代理可以在运行时创建一个实现了一组给定接口的新类,这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。
Invocationhandler接口是代理实例的调用处理程序实现的接口,对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke()方法,该方法声明如下:
Object invoke(Object proxy,Method method,Object[] args)
method:所代理的代理类中的方法,由代理类调用其方法时传入
args:代理类方法的参数,由代理类代用其方法时传入
Poxy接口提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。本实例使用该接口中定义的newProxyInstance()方法获的一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
public static Object newProxyInstance(ClassLoader loader,Class<?> interfaces, InvocationHhandler)
loader:定义代理类的类加载器;
interfances:一个Class对象数组,代理类要实现的接口列表;
InvocationHander:指派方法调用的调用处理程序。
分别通过接口实现以及代理实现接口Seller:
public interface Seller { void sell();// 简单的测试方法 } |
普通方法:
public class HouseSeller implements Seller { // 实现接口的方法,用输出来区别该类 public void sell() { System.out.println("销售人员在卖房子"); } }
//在main方法中调用,创建对象,在调用方法 Seller seller = new HouseSeller(); eller.sell();// 普通方式调用sell()方法 |
通过代理实现接口
//1.首先实现InvocationHandler接口,并重写invoke方法,用Proxy创建的代理类实例无论调用什么方法都是在调用Agency中的 invoke()方法 public class Agency implements InvocationHandler { private Object target;//在构造函数中赋值 /// 用来处理代理类,正真实现代理类的方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理人员在卖房子"); //在此处正真调用target中的method方法,要求target实现Seller接口 method.invoke(target,args); return null; } }
//在main函数中 // 获得接口Seller类的类加载器 ClassLoader loader = Seller.class.getClassLoader(); //生成代理类实例 seller = (Seller) Proxy.newProxyInstance(loader, new Class[] { Seller.class }, new Agency()); seller.sell();// 代理方式调用sell()方法,其实在调用Agency中的invoke方法 seller.sell(100,"DSL")//有参数的方法 |
异常
异常分类
所有异常都是由Throwable继承而来,在下一层分解为两个分支:Error和Exception。
- Error类层次结构描述了java运行时系统内部错误和资源耗尽错误(无力解决)。
- Exception类层次分为两个分支,如下:
- 由程序自身错误导致的异常属于RuntimeException,包括错误的类型转换、数组访问越界、访问空指针、除数为零等。
- 程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常,如试图在文件尾部后面读取数据、试图打开一个不存在的文件等。
未检查异常(unchecked):派生于Error类或RuntimeException类的所有异常称;
已检查(checked):所有其他的异常。
声明已检查异常
方法上抛出异常:一个方法必须声明所有可能抛出的已检查异常,这样可以从首部反映出这个方法可能抛出哪类已检查异常(如果暂时不处理这些异常)。当重写继承的方法时,抛出的异常不能比超类的范围大,或者不抛出异常。
public Image loadImage(String s) throws FileNotFoundException,EOFException
{
}
方法中抛出异常:对于系统预定义的异常,一般至少有两个构造方法,即空参数构造方法和字符串参数构造方法。使用字符串参数构造方法可以让用户为该构造方法增加提示信息。
public Image loadImage(String s)
{
throw FileNotFoundException("Hello world")
}
自定义异常类
自定义异常类派生于Exception或它的子类,包含两个构造函数,可以用getMessage()获取传入的信息:
class FileFormatException extends IOException
{
pubic FileFormatException()
{
}
public FileFormatException(String str)
{
super(str);
}
}
捕获异常
如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈信息。如果调用了一个抛出已检查异常的方法,就必须对它进行处理,或者通过throws/throw将它继续进行传递。
通过try/catch/finally语句块捕获异常。
再次抛出异常与异常链
在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型,如将SQLException异常转为ServletException异常:
try{…}
catch(SQLException e)
{
throw new SerleException("database error:"+e.getMesssage());//再次抛出异常
}
将原始异常设置为新异常的"原因":
new SerleException("database error").initCause(e).
重新获取原始异常:Throwable e = se.getCause();
finally子句
不管是否有异常被捕获,finally子句中的代码被执行。
问题:当finally与try同时发生异常时,finally中抛出的异常会覆盖try中抛出的异常。
try{…}
finally
{
in.close();
}
finally子句包含return语句时。假设利用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行,如果finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值,如下例,返回值为8。
try{
return 4;
}
finally
{
return 8;
}
带资源的try语句
Java SE 7的AutoCloseable接口有一个方法:
void close() throws Exception;
假设资源属于一个实现了AutoCloseable接口的类,可以用带资源的try语句(try-with-resources),它会自动关闭资源:
try(Scanner in = newScanner(new FileInputStream("/usr/share.dict"),
PrintWriter out = new PrintWriter("out.txt"))
{
}
不论这个块如何退出,in和out都会关闭。带资源的try语句可以使原来try中的异常重新抛出,而close方法抛出的异常会"被抑制"。
分析堆栈跟踪元素
堆栈跟踪(stack trace)是一个调用过程的列表,它包含了程序执行过程中方法调用的特定位置。
Throwable类的printStackTrace()方法访问堆栈跟踪的文本信息,显示异常类型、异常信息、异常发生位置;
getMessage ()获取传入异常类的异常信息;
StackTraceElemen类含有能够获得文件名、类名、方法名和当前执行的代码行号;
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for (StackTraceElement f : frames)
System.out.println(f);
静态的Thread.getAllStackTrace方法,它可以产生所有线程的堆栈跟踪。