Class类的使用
Java动态加载类
通过反射机制获取成员及方法
通过invoke调用对象方法
通过反射了解集合泛型的本质
Class类的使用
在Java的世界里,或者说对象的世界里,万事万物皆对象,类也是对象,也是此片文章的中心,类的对象称为类类型(class type),Class对象(java.lang.Class)在Java反射机制里面必不可少,其十分强大。下面看一下其简单的使用,以下是一个demo(通过class获取对象实例,为方便观察,后面所有的实例均对exception进行了回避):
package org.devsong.reflectdemo;
public class ClassDemo {
public static void main(String[] args) throws Exception {
Foo foo = new Foo();
// 获取类类型的方式有三种
// 方式一(在已知对象实例的情况下)
Class c = foo.getClass();
// 方式二
Class c1 = Foo.class;
// 方式三
Class c2 = Class.forName("org.devsong.reflectdemo.Foo");
// 获取到类类型之后可以通过类类型获得对象实例(前提是该类要有无参的构造方法)
Foo foo1 = (Foo) c2.newInstance();
// 一个类的类类型只有一个,所以通过以上三个方式获得的类类型是等价的,所以以下结果为 true
System.out.println(c == c1 && c == c2);
}
}
class Foo {
public Foo() {
System.out.println("foo constructor...");
}
}
Java动态加载类
类的加载有动态加载和静态加载,通过new关键字实例化类的过程为静态加载,而通过反射机制得到类对象的过程为动态加载。静态加载在编译过程执行,而动态加载在程序执行且需要时才进行加载。由于以下示例在IDE里面不好演示,所以均采用命令行来操作。
看下面的一个demo:假设现在需要开发一款绘图app,里面需要提供钢笔、铅笔、水彩笔等各种笔的效果,假设现在只实现了钢笔这一个效果,里面有一个draw方法:新建Pen.java:
public class Pen{
public Pen(){
}
public void draw(){
System.out.println("pen:draw...
");
}
}
然后新建测试类:Demo.java
public class Demo {
public static void main(String[] args) {
if(args[0].equals("pen")) {
Pen pen = new Pen();
pen.draw();
}else if(args[0].equals("pencil")) {
Pencil pencil = new Pencil();
pencil.draw();
}
}
}
然后用命令行先编译pen.java: javacPen.java 接着编译Demo.java: javac Demo.java 很显然编译不通过,找不到Pencil类。这就值得我们思考了,假设现在我只想用pen,而不想用pencil, pen已经完整开发结束,但是因为pencil没开发完成,导致我连Pen都用不了。这也暴露了静态加载类的弊端,假设现在需要开发100个功能,已经开发完99个,但是因为还有一个未开发完成,剩下的99个都不能使用,这显然是存在很大问题的。所以动态加载类的作用就出现了。 下面新建一个DrawAble接口:DrawAble.java
interface DrawAble{
public void draw();
}
然后让所有需要实现的“笔”都实现该接口。最后改造一下Demo存为Demo1.java:
public class Demo1 {
public static void main(String[] args) throws Exception{
Class c = Class.forName(args[0]);
DrawAble da = (DrawAble)c.newInstance();
da.draw();
}
}
保存并编译,可以发现编译通过,然后执行: java Demo1 Pen 可以在控制台看到输出了Pen:draw...。 完全没问题,然后执行java Demo1 pencil, 直到这一步才抛出异常:java.lang.ClassNotFoundException。要想拓展更多的笔,只需让新添加的笔实现DrawAble接口即可。这也就是动态加载的好处,在运行程序时才加载需要的类,编译过程完全不影响,再也不会出现前面的一个影响99个的情况。通过反射机制获取成员及方法
class类用非常多的get方法,可以获取包括成员、方法等共有或私有的属性。使用方法大同小异,可以参看API文档,此处只是简单展示了获取一个类的所有方法,如下:
package org.devsong.reflectdemo;
import java.lang.reflect.Method;
import org.devsong.reflectdemo.util.ClassUtil;
public class ReflectDemo01 {
public static void main(String[] args) {
ReflectDemo01.getMethodList(new String("123"));
}
public static void getMethodList(Object obj) {
Class c = obj.getClass();
Method[] methods = c.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);
}
}
}
运行后可以看到控制台输出了String的所有方法。通过IDE我们也可以看到class还用非常多的get方法,可以获取非常多的信息,这里就不一一展示了。 
通过invoke调用对象方法
上面展示了获取方法,那么获取到了方法肯定是有调用的办法的。下面介绍以下invoke方法来执行对象的内部方法。依然是一个简单的例子,定义了一个类Foo1,其中实现了两个方法,下面展示如何通过Java反射机制调用这两个方法:
package org.devsong.reflectdemo;
import java.lang.reflect.Method;
public class ReflectDemo02 {
public static void main(String[] args) throws Exception {
Foo1 foo = new Foo1();
Class c = foo.getClass();
Method m = c.getMethod("sayHello", String.class);
// 通过Method的invoke方法执行foo对象的方法
m.invoke(foo, "jack");
// 在获取方法和执行方法传参的时候,具有两种方式,由于是可变参数,所以可以传数组,也可以传单独的参数,效果是一致的
// Method m1 = c.getMethod("say", new Class[] {String.class, String.class}); //通过数组
Method m1 = c.getMethod("say", String.class, String.class);
m1.invoke(foo, new String[] { "hello", "world" }); // 通过数组传递参数
// m1.invoke(foo, "hello", "world"); //分别传递参数
}
}
class Foo1 {
public Foo1() {
}
public void sayHello(String name) {
System.out.println("hello " + name);
}
public void say(String str1, String str2) {
System.out.println(str1 + str2);
}
}
可以明确的是确定的名称及确定的参数可以确定一个唯一的方法,所以getMethod方法第一个参数是我们需要的方法名,后面的不定参数是实际方法中包含的参数类类型,可以利用数组也可以分开依次填入,在获取和执行时都是如此。保存运行可以看到结果如预期,两个方法都被正常调用。通过反射了解集合泛型的本质
这里借用ArrayList来展示,很明显,实际开发中如果给ArrayList规定了泛型,那么简单调用ArrayList的add方法是不能添加其他类型的对象进去的。而通过Java的反射机制,我们可以做一些正常手段不能操作的东西。例子如下,先给ArrayList指定泛型为String,我们用正常的方式往其中添加一个Integer数据的时候发现是行不通的,那就通过反射机制试试吧:
package org.devsong.reflectdemo;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ReflectDemo03 {
public static void main(String[] args) throws Exception {
ArrayList list = new ArrayList();
list.add(123);
list.add("sdsd");
ArrayList list1 = new ArrayList<>();
list1.add("hello");
// list1.add(100); //错误的操作
// 编译之后集合是去泛型化的,虽然list明确指明存放String,但是以下结果是true
// java中集合的泛型是防止错误输入的,只在编译阶段有效,绕过编译就无效了
System.out.println(list.getClass() == list1.getClass());
// 利用反射机制,可以在list1中存入String之外的其他类型
Class c = list1.getClass();
Method m = c.getMethod("add", Object.class);
m.invoke(list1, 100);
System.out.println(list1);
}
}
保存并运行后可以看到100成功添加到了list1中。这说明Java编译之后集合是去泛型化的,java中集合的泛型是防止错误输入的,只在编译阶段有效,绕过编译就无效了,所以我们能够将100添加进去而不抛出异常。