在最近的学习中,越来越多的接触到了反射机制,找资料学习了一下,将学习过程以及知识点记录下来,方便以后学习
(1)Class类的使用
在面向对象的世界中,万物皆对象。
我们平常定义的类,类中的属性,类中的方法都可以看成是对象,是java.lang.Class类的实例对象
这里先介绍类类型:Class类的对象就是类类型(叫类类型只是为了区别是类的对象还是是Class的对象), 一个类只可能是Class类的一个对象。(这里感觉和单例模式挺相像的)
首先看一段代码:在代码中了解概念:
package com.feng.reflect;
public class ClassDemo1 {
public static void main(String[] args)
{
//我们可以创建一个Student类的对象
Student student = new Student();
//Student这个类其实也是一个对象,是java。lang.Class类的实例对象
//这里介绍三种可以获得类类型的方式
//(1)类名。class,说明任何一个类都有一个隐含的static成员变量
Class c1 = Student.class;
//(2)类对象。getClass()
Class c2 = student.getClass();
//(3)Class.forName("全名包含报名");
try {
Class c3 = Class.forName("com.feng.reflect.Student");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(c1 == c2); //这里是相等的,因为每个类的类类型只能有一个对象
//其中Class类的对象就叫做类类型,上面定义的c1, c2, c3都是类类型
//我们可以通过类类型来创建类的对象
try {
//newInstance调用的是类的无参的构造方法
Student s = (Student)c1.newInstance();
s.show();
} catch (InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Student
{
public void show()
{
System.out.println("我是一个学生");
}
}
注意的几点:
(1)获取类类型的三种方法
(2)通过类类型创建类对象,创建出来的对象需要强制类型转换
(2)动态加载类
在我们以往的学习中,一般都是用IDE去写程序,直接点击运行,不会去关注编译,运行的问题。但是在这里我们就要分清编译和运行了
首先,我们要区分静态加载类和动态加载类的区别
静态加载类:在编译时就需要加载所有可能使用到的类。(换句话说,如果要用的类没有的话,在编译阶段就会报错),使用new方法都是静态加载
动态加载类:在运行的时候才关注加载那些类,用到哪个类加载哪个类。用不到的类不用加载(换句话说,用不到的类没有也可以)
如果到这里还看不懂,没有关系,看一个例子:
这里我们为了区分编译和运行,不再使用IDE了,而是使用命令行来执行类
在编辑工具中写好程序:
public class Office {
public static void main(String[] args)
{
if(args.equals("Word"))
{
Word word = new Word();
word.start();
}
else if(args.equals("Excel"))
{
Excel excel = new Excel();
excel.start();
}
}
}
我们现在只有Office这一个类,功能很简单,只要写过程序的人都能看懂,大家也都能知道这个程序时错误的,因为我们没有Word,Excel类,更没有里面的start方法
我们执行命令 javac Office.java 这是对类文件进行编译,new是静态加载,编译时加载所需要的类,因为没有Word,Excel类,所以报错了
但是现在就有问题了,我们真的需要Word,Excel这些类吗,比如我们传参数,传一个Word那我只需要一个Word类就可以了,Excel根据没有用到啊,按常理说我只要有一个Word类就应该让我运行啊。除此之外,如果我有100个类难道我要写上100个else if?????
答案是当然不需要,如果我们改为动态加载,用到哪一个类就加载哪一个类不就得了。
对程序进行修改如下:
public class Office
{
public static void main(String[] args)
{
try
{
//动态加载类
Class c1 = Class.forName(args[0]);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
这样我们使用命令 javac Office.java编译时是不会报错的。
但是使用命令java Office Word 运行时,会提示找不到Word类。所以动态加载类是在运行时用到了这个类才会去找
创建出Word类,在Office中穿件Word实例,并调用Word的方法
Word类代码
public class Word
{
public void start()
{
System.out.println("开始写Word。。。");
}
}
Office类代码
public class Office
{
public static void main(String[] args)
{
try
{
//动态加载类
Class c1 = Class.forName(args[0]);
//创建实例
Word word = (Word)c1.newInstance();
word.start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
javac *.java
java Office Word 就可以打印输出结果了
这样编译就运行都可以了。我们用什么类,写什么类就可以。用Word的时候,不需要Excel类的存在这还是不够完美,如果我想换成构造Excel,那我还要去更改main函数中的代码,很不方便,那怎么办。
这个好解决,我们可以给在Office类中实现的类建立一个统一的接口Tool
代码如下:
public interface Tool
{
void start();
}
Word ,Excel实现这个接口
public class Word implements Tool
{
public void start()
{
System.out.println("开始写Word。。。");
}
}
public class Excel implements Tool
{
public void start()
{
System.out.println("开始写Excel。。。");
}
}
Office类
public class Office
{
public static void main(String[] args)
{
try
{
//动态加载类
Class c1 = Class.forName(args[0]);
//创建实例
Tool tool = (Tool)c1.newInstance();
tool.start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
执行结果:
实现了我们要的功能。
(3)获取方法信息
基本的数据类型也有类类型:
看一段简单的代码就知道了:
package com.feng.reflect;
public class ClassDemo2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Class c1 = double.class;
Class c2 = Double.class;
System.out.println(c1.getName());
System.out.println(c2.getSimpleName());
}
}
如何获取方法信息,直接看代码
package com.feng.reflect;
import java.lang.reflect.Method;
public class ClassUtil {
/*
* 打印对象对应的类的方法
*/
public static void printClassMethodMessage(Object object)
{
//首先获取类类型
Class c1 = object.getClass(); //传进来的类型,获取到的是子类的类类型。换句话说会获取到具体类的类类型
//获取类名
System.out.println("类名为:"+c1.getSimpleName());
//获取方法对象,Method类
Method[] methods = c1.getMethods(); //获取的是所有的public的函数,包括继承来的函数
//Method[] methods = c1.getDeclaredMethods(); //获取的是自己的方法,不管权限
//获取方法的信息
for(Method m:methods)
{
//获取方法的返回值
Class c2 = m.getReturnType(); //返回的是类类型
System.out.print(c2.getName()+"(");
//获取参数类型
Class[] cs = m.getParameterTypes();
for(Class c: cs)
{
System.out.print(c.getName()+", ");
}
System.out.println(")");
}
}
}
测试类代码如下:
package com.feng.reflect;
public class ClassDemo2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Class c1 = double.class;
Class c2 = Double.class;
System.out.println(c1.getName());
System.out.println(c2.getSimpleName());
}
}
都是写类类型中的方法,只要了解会用即可。
(4)获取成员变量构造函数信息
获取成员变量的信息如下所示,测试就不写了
/**
* 打印成员变量的信息
*/
public static void printClassFieldMessage(Object object)
{
//获取类对象
Class c = object.getClass();
//获取成员变量
System.out.println("类类型:"+c.getSimpleName());
Field[] field = c.getDeclaredFields();
for(Field f: field)
{
System.out.println(f.getType()+" "+f.getName());
}
}
获取构造函数的信息
/**
* 获取构造函数的信息
*/
public static void printConstructMessage(Object object)
{
//获取类对象
Class c = object.getClass();
//获取构造函数
System.out.println("类类型:"+c.getSimpleName());
Constructor[] cs = c.getDeclaredConstructors();
for(Constructor c1: cs)
{
System.out.print(c.getSimpleName()+ "(");
//获得参数列表
Class[] cls = c1.getParameterTypes();
for(Class cl: cls)
{
System.out.print(cl.getSimpleName()+", ");
}
System.out.println(")");
}
}
都是一样道理的东西,非常简单
(5)方法的反射操作
方法是由方法的名称和方法的参数列表唯一确定的
方法反射的操作 method.invoke(对象,参数)
/**
* 获取构造函数的信息
*/
public static void printConstructMessage(Object object)
{
//获取类对象
Class c = object.getClass();
//获取构造函数
System.out.println("类类型:"+c.getSimpleName());
Constructor[] cs = c.getDeclaredConstructors();
for(Constructor c1: cs)
{
System.out.print(c.getSimpleName()+ "(");
//获得参数列表
Class[] cls = c1.getParameterTypes();
for(Class cl: cls)
{
System.out.print(cl.getSimpleName()+", ");
}
System.out.println(")");
}
}
方法的执行必须和具体的对象相联系的,因为方法输出的值是和对象有关的。
(6)集合范型的实质
先看一段程序:
package com.feng.reflect;
import java.util.ArrayList;
public class ClassDemo5 {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2);
}
}
定义了两个ArrayList对象,这两个对象的类类型是不是一样呢,答案是肯定的,不管定义多少个不同的ArrayList独享,他们的类类型都是一个。
范型在这的作用只是在编译的时候加以控制,在最终的运行的时候其实是没有范型的事情的,范型只是用来防止输入错误的,绕过编译就无效了。
下面看一个例子:
package com.feng.reflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ClassDemo5 {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList<String> list = new ArrayList<String>();
list.add("nihao");
//list.add(20); //这肯定是错的
//我们可不可以跳过程序的编译阶段,在list中添加一个整形数据呢,我们使用反射来试一下
Class c = list.getClass();
Method m;
try {
m = c.getMethod("add", Object.class);
m.invoke(list,20);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//不能使用foreach进行遍历
System.out.println(list);
}
}
输出结果为: