关于反射的基本概念这里不多介绍,记得自己开始学习Java的时候,是在dos下执行命令来运行程序。下面来重温下:
在记事本中写一个简单的类,然后在dos下执行,我想基本的命令使用过得都很清楚:
文件夹中会看到a.class文件,编译后生成的Java字节码文件,那么里面到底是啥呢?用Java提供的一个反编译工具javap命令查看:
上面生成的Java字节码,可以被JVM加载运行。
学习异常可以很清楚的看到一张图:
关于Error异常不多说,是系统内部错误,我们是解决不了的,比如资源耗尽。但是Exception下面分为检查异常和不检查异常,检查异常比如IOException, JAXBException, JMException, KeySelectorException等,具体的可以去API上查看,
常见的是文件不存在等。不检查异常就是运行时异常,比如空指针,数组越界等,下面通过简单的来用代码测试下:
简单的修改:
public class a{
public static void main(String []args){
System.out.println("hello world");
D d = new D();
}
}
编译:
说明编译都通不过,找不到类D,属于检查异常
再简单修改下:
public class a{
public static void main(String []args){
System.out.println(args[0]);
}
}
然后编译运行:
发现上面编译没有报错,但是运行时就报数组越界错误,上面很直观的说明什么是检查异常和非检查异常了吧!
当然我们并不是奔着异常,但是想通过次说明Java中编译和运行是两个不同的概念。
java编译后生成的.class文件,然后在JVM中加载运行,在JVM中每个Java类都表现为一个Class对象,但是Class对象又是java.lang.Class一个对象,表示类的类型信息,那么如何获取类的Class对象呢:
public class test {
public void print(){
System.out.println("test");
}
}
public class Main {
public static void main(String[] args) {
Class a = test.class; //第一种方法
try {
((test) a.newInstance()).print();//通过类的Class对象可以生成类的实例,前提是类有默认的非private空构造器
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
try {
Class b = Class.forName("com.java.c.test");//第二种 forName 动态加载类
try {
((test) b.newInstance()).print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
test t = new test();
Class c = t.getClass(); //第三种 getClass 静态加载类,在编译时就需要加载所有需要用到的类
try {
((test) c.newInstance()).print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
只有类有类类型么?当然不是,看API中的注释:
/**
* Instances of the class {@code Class} represent classes and
* interfaces in a running Java application. An enum is a kind of
* class and an annotation is a kind of interface. Every array also
* belongs to a class that is reflected as a {@code Class} object
* that is shared by all arrays with the same element type and number
* of dimensions. The primitive Java types ({@code boolean},
* {@code byte}, {@code char}, {@code short},
* {@code int}, {@code long}, {@code float}, and
* {@code double}), and the keyword {@code void} are also
* represented as {@code Class} objects.
基本的数据类型以及void都有类类型,int.class,short.class等等
反射是发生在运行时,当我们编写一个类时,JVM就会帮我们编译成class对象,存放在同名的.class文件中。在运行时,当需要生成这个类的对象,JVM就会检查此类是否已经装载内存中,若没有装载,则把.class文件装入到内存中。若是装载,则根据class文件生成实例对象。
下面通过反射,再运行时获取类信息,比如类名称,类成员变量,成员函数,构造函数,以及注解。还会介绍在泛型中如何使用反射,跳过编译,往容器中放入不同类型的值。下面涉及到自定义注解,后续文章会涉及。
@Target({TYPE,METHOD,FIELD,CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAnn {
public int id() default 1;
public String description() default "no description";
}
/**
* Created by diy_os on 2016/11/5.
*/
@UserAnn(id = 1, description = "类注解")
public class msg {
@UserAnn(id = 2, description = "成员变量注解")
private int i = 1; //私有成员变量,通过反射取不到该变量信息
@UserAnn(id = 1, description = "成员变量注解")
private int j = 2;
@UserAnn(id = 1, description = "成员变量注解")
public int z = 3;
@UserAnn(id = 3, description = "构造函数注解")
public msg() {
}
@UserAnn(id = 3, description = "构造函数注解")
public msg(int i, int j) {
}
@UserAnn(id = 4, description = "成员函数注解")
public int getI() {
return i;
}
@UserAnn(id = 5, description = "成员函数注解")
public void setI(int i) {
this.i = i;
}
@UserAnn(id = 6, description = "成员函数注解")
public int getJ() {
return j;
}
@UserAnn(id = 7, description = "成员函数注解")
public void setJ(int j) {
this.j = j;
}
@UserAnn(id = 8, description = "成员函数注解")
private int getij() { //私有成员函数,通过反射取不到该函数信息
return i + j;
}
@UserAnn(id = 9, description = "成员函数注解")
public static void main(String[] args) {
Class<msg> s = msg.class;
System.out.println("类名称:" + s.getName());
Method[] methods = s.getMethods();
for (Method c : methods) {
System.out.print("方法" + c.getName() + ",");
}
System.out.print("\n");
Constructor constructor[] = s.getConstructors();
for (Constructor c : constructor) {
System.out.print("构造函数" + c.getName() + ",");
}
System.out.print("\n");
Field[] field = s.getFields();
for (Field c : field) {
System.out.print("成员变量" + c.getName());
}
System.out.print("\n");
Annotation annotation[] = s.getAnnotations();
for (Annotation c : annotation) {
UserAnn userAnn = (UserAnn) c;
System.out.print(userAnn.description() + " " + userAnn.id());
}
System.out.print("\n");
for (Method msg : methods) {
boolean isAnn = msg.isAnnotationPresent(UserAnn.class);
if (isAnn) {
UserAnn userAnn = msg.getAnnotation(UserAnn.class);
System.out.print(userAnn.description() + userAnn.id() + ",");
}
}
System.out.print("\n");
for (Constructor con : constructor) {
boolean isAnn = con.isAnnotationPresent(UserAnn.class);
if (isAnn) {
UserAnn userAnn = (UserAnn) con.getAnnotation(UserAnn.class);
System.out.print(userAnn.description() + userAnn.id() + ",");
}
}
System.out.print("\n");
for (Field f : field) {
boolean isAnn = f.isAnnotationPresent(UserAnn.class);
if (isAnn) {
UserAnn userAnn = f.getAnnotation(UserAnn.class);
System.out.print(userAnn.description() + userAnn.id() + ",");
}
}
System.out.print("\n");
msg msg = new msg();
try {
Method mm = s.getMethod("getI");
try {
System.out.print(mm.invoke(msg)); //通过方法的反射操作,用方法的对象调用函数
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
System.out.print("\n");
ArrayList<String> list1 = new ArrayList<>();
ArrayList list2 = new ArrayList();
System.out.print(list1.getClass() == list2.getClass());
Class class1 = list1.getClass();
System.out.print("\n");
try {
Method method1 = class1.getMethod("add", Object.class);
method1.invoke(list1, 1);
} catch (Exception E) {
E.printStackTrace();
}
System.out.println(list1.size());
// System.out.println(list1.get(0));//如果取出通过反射放入的int值,会导致错误
}
}
上面需要简单介绍:
通过方法对象,来调用方法,首先是类类型通过方法名获取方法对象,然后方法对象调用invoke调用方法,其实和通过类对象调用方法是一样的。
然后是泛型在容器中,利用反射可以动态的绕过编译,往容器中写入不同类型的值,上面的list1 == list2返回时true,说明编译之后集合的泛型是去泛型化的,JAVA中集合泛型为了防止错误输入,只是在编译阶段有效,绕过编译就无效了。