描述:我已经事先写好了一个Person类如下
包含各个属性的setter和getter方法,以及构造器,现在我要编写一个Generate类,传入Person类的全路径,通过反射获取Person的结构,并写一个MyPerson.java文件,除了类名改为MyPerson,其余属性,方法与Person类一模一样,也就是复制。
public class Generate {
private String classFullPath;//存放Person类的全路径
private Class clazz;//Person类的Class对象
private StringBuilder sb = new StringBuilder();//整个MyPerson.java文件的内容
public Generate(String classFullPath) {
this.classFullPath = classFullPath;
try {
clazz = Class.forName(classFullPath);
} catch (ClassNotFoundException e) {
System.out.println("传入类的全路径错误");
}
}
}
class StringUtil {
/**
* 传入全路径:xxx.xxx.name ,得到名字name
*/
public static String getName(String fullName) {
int index = fullName.lastIndexOf(".");
return fullName.substring(index+1);
}
}
准备好后,开始编写各个方法
1.packgee 包名以及class 类名{
每一个.java文件第一行都要写当前包名,
/**
* 写包名:package 包名
*/
public void getPackageDesc() {
String name = clazz.getPackage().toString();//得到:package 包名
// String name = clazz.getPackage().getName();//得到:包名
sb.append(name).append(";\n\n");
}
/**
* 写第一行: class 类名 {
*/
public void getClassDesc() {
sb.append("class My").append(StringUtil.getName(classFullPath)).append("{\n\n");
}
通过clazz.getPackage()可以得到一个Package对象,可以print直接打印,输出如下
Class<?> aClass = Class.forName("work01.Person");
Package aPackage = aClass.getPackage();
System.out.println("aPackage = "+aPackage);
System.out.println("aPackage.toString() = "+aPackage.toString());
System.out.println("aPackage.getName() = "+aPackage.getName());
/*
输出结果:
aPackage = package work01
aPackage.toString() = package work01
aPackage.getName() = work01
*/
package对象直接被打印,底层自动调用本身的toString()方法,得到package 包名
package.getName直接得到 包名
2.得到属性信息
通过因为我们不知道到底有什么属性,所以
通过反射得到属性,比较常用的方法就下面几个:
- clazz.getField(fieldName) 获得public的属性的Class对象,名字为fieldName,返回一个Field 对象
- clazz.getFields() 获得所有public的属性的Class对象,返回一个 Field[] 数组
- clazz.getDeclaredField(fieldName)
- clazz.getDeclaredFields()
3,4与1,2的区别就在于3,4是获得所有属性,而1,2只能得到public修饰的属性,但是要注意 非public 修饰的field对象在使用前要打开访问权限,也就是加一句field.setAccessible(true);
我们通过field.set(obj,vale)以及field.get(obj)来给obj对象的属性赋值或者获得属性的值
修饰符:我们可以通过field.getModifiers()得到属性的修饰符,以int形式返回
修饰符 | 返回的int值 |
缺省(默认) | 0 |
public | 1 |
private | 2 |
protected | 4 |
static | 8 |
final | 16 |
当存在两个及以上的修饰符时,采用加法,比如public static 就是 1+8=9
Modifier类也提供了toSrting(mod)方法将转化为String类型的修饰符,比如Modifier.toString(17)返回String类型的 "public final"
类型:field.getType()可以得到属性的类型,返回的是一个Type类的Class对象,再调用该对象的getName()方法即可得到属性的类型
Field getAge = aClass.getDeclaredField("age");
getAge.setAccessible(true);
Class<?> type = getAge.getType();
System.out.println(type);
System.out.println(type.getTypeName());
System.out.println(type.getName());
/**
结果:
class java.lang.Integer
java.lang.Integer
java.lang.Integer
/*
属性名: 通过field.getName()即可得到属性的名字
/**
* 写属性
*/
public void getFieldDesc() {
Field[] fields = clazz.getDeclaredFields();//得到所有属性
StringBuilder str;
for (Field field : fields) {//遍历
str = new StringBuilder();
field.setAccessible(true);//开启访问权限
int modifierMod = field.getModifiers();//得到修饰符
String modifier = Modifier.toString(modifierMod);//通过特有方法转化为String表示的修饰符
String typeName = field.getType().getName();
typeName = StringUtil.getName(typeName);
String fieldName = field.getName();//得到属性名字
str.append("\t").append(modifier).append(" ").append(typeName).append(" ").append(fieldName).append(";\n");
sb.append(str);
}
}
3.得到构造器信息
其实构造器与属性,方法的信息获取方法是一样的,前面你属性都会了,那么构造器的获取修饰符,获取构造器名字,这个肯定也没有问题啦。
获取构造器:
- clazz.getConstructor()
- clazz.getConstructors()
- clazz.getDeclaredConstructor()
- clazz.getDeclaredConstructors()
用法同上面属性field一样。那怎么获取有参数的构造器?括号里面加入参数类型的Class对象即可,比如获取public Person(int age,String name)这个构造器,clazz.getConstructor(int.calss,String.class) 就这么简单。也可以传入一个数组,如下所示。
Constructor<?> constructor = aClass.getConstructor(int.class, String.class);
System.out.println(constructor);
Class[] classes={int.class,String.class};//这里也可以传入一个数组
Constructor<?> constructor1 = aClass.getConstructor(classes);
System.out.println(constructor1);
/*
结果:
public work01.Person(int,java.lang.String)
public work01.Person(int,java.lang.String)
*/
怎么获取构造函数里的参数
构造器对象:constructer,constructer.getParameters()可以返回一个Parameter[]数组,存放该构造器的每一个参数对象parameter,
我们可以通过parameter.getType().getName()得到每个参数的类型,
通过parameter.getName()得到每一个参数的名字
让我们来尝试下:
Constructor<Person> constructor = aClass.getConstructor(Integer.class, String.class);
Parameter[] parameters = constructor.getParameters();
for (Parameter parameter : parameters) {
System.out.println("名字:"+parameter.getName());
System.out.println("类型:"+parameter.getType().getName());
}
/*
打印结果:
名字:arg0
类型:java.lang.Integer
名字:arg1
类型:java.lang.String
*/
并没有如期所愿,类型倒是正确,但名字却没有得到。在java8以后,我们可以在
1)要在设置-->构建.执行.部署-->编译器-->java编译器 里面的附加命名行形参加入:-parameters
2)删除下面黄色的bin文件,再重新启动就可以获取了
获得真实名字
名字:age
类型:java.lang.Integer
名字:name
类型:java.lang.String
现在最重要的参数信息已经获取到,基本上没问题了,直接上代码:
/**
* 写构造器
*/
public void getConstructorDesc() {
Constructor[] constructors = clazz.getDeclaredConstructors();
StringBuilder str;
String className = StringUtil.getName(classFullPath);//得到类名
for (Constructor constructor : constructors) {
str = new StringBuilder();
constructor.setAccessible(true);
int modifiers = constructor.getModifiers();
String modifier = Modifier.toString(modifiers);//得到修饰符
str.append("\t").append(modifier).append(" My").append(className).append(" ").append("(");
StringBuilder strTemp = new StringBuilder();
//得到方法参数
Parameter[] parameters = constructor.getParameters();
//用String[] 来保存参数名
String[] parameterNames = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {//遍历
Parameter parameter = parameters[i];
String typeName = parameter.getType().getName();//参数类型,得到的是:java.lang.String
typeName = StringUtil.getName(typeName);//参数类型,得到:String
String parameterName = parameter.getName();//得到参数名
parameterNames[i] = parameterName;
strTemp.append("\t").append(typeName).append(" ").append(parameterName).append(",");
}
//因为str最后,多了一个",",所以去掉
if (parameterNames.length == 0)
str.append("){\n");
else
str.append(strTemp.substring(0, strTemp.length() - 1)).append(" ){\n");
//构造方法,方法体为this.属性=属性
for (String parameterName : parameterNames) {
str.append("\t\tthis.").append(parameterName).append(" = ").append(parameterName).append(";\n");
}
str.append("\t}\n");
sb.append(str);
}
}
4.获得getter和setter方法
获得方法的Class对象Method的方式与构造器一抹一样,只是把Constructor换为了Method。获取修饰符,方法名,参数列表信息如构造器和属性所示,再次就不过多赘述。
方法相比于构造器多了一个返回值,我们可以通过方法对象method.getReturnType().getName()获取返回类型。
话不多说,下面我们直接上代码:
/**
* 写setter和getter方法
* 和构造方法写法几乎一样,就是多了个返回类型
* 已经知道只有一个参数
*/
public void getMethodDesc() {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
StringBuilder str = new StringBuilder();
String modifier = Modifier.toString(method.getModifiers());//修饰符
String returnName = method.getReturnType().getName();//得到返回类型名字
if (!returnName.equals("void"))
returnName = StringUtil.getName(returnName);
String methodName = method.getName();
str.append("\n\t").append(modifier).append(" ").append(returnName).append(" ").append(methodName).append(" ( ");
if (returnName.equals("void")) {
Parameter parameter = method.getParameters()[0];//只有一个参数
String parameterTypeName = parameter.getType().getTypeName();//参数类型
parameterTypeName = StringUtil.getName(parameterTypeName);
String parameterName = parameter.getName();//参数名
str.append(" ").append(parameterTypeName).append(" ").append(parameterName).append("){\n").
append("\t\tthis.").append(parameterName).append(" = ").append(parameterName).append(";\n");
} else {
str.append("){\n");
//得到更改属性名,方法名:set属性名
String propertyName = methodName.replaceFirst("get", "");
propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);
str.append("\t\treturn ").append(propertyName).append(";");
}
str.append("\t}\n");
sb.append(str);
}
}
5.将sb写入到文件中
我们使用字节流FileOuputStream来输出,写入到文件。路径就写为:
包名+类名+".class";
public void generateOutput() {
FileOutputStream fs = null;
try {
String packageName = this.getClass().getPackage().getName();//当前包名
String path = "src/" + packageName + "/My" + StringUtil.getName(classFullPath) + ".java";
fs = new FileOutputStream(path);//覆盖式创建
fs.write(sb.toString().getBytes(StandardCharsets.UTF_8));
fs.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fs != null)
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
最后写一个方法,组装一下
public void generate() {
this.getPackageDesc();
this.getClassDesc();
this.getFieldDesc();
this.getConstructorDesc();
this.getMethodDesc();
sb.append("\n}");
this.generateOutput();
}
到此大功告成,可能对于初学者来说有点难度,但自己动手写了之后才发现原来也就是那几个方法的调用。
我们大致总结一下:
1). 属性/构造器/方法 的获得方法就是那4个,常用的还是clazz.getDeclared???()以及clazz.getDeclared???s(), 这里???可以换为Field/Constructor/Method
后面加了" s "代表获取所有 属性/构造器/方法 ,不加" Declared "代表获取全部public 属性/构造器/方法
对于非public的要用setAccessible(true)开启访问权限,不然抛出异常。
2)通过getModifires()获取修饰符,返回int型,Modifier.toString(int moddifire)得到对应的String类型的修饰符
3)通过getParameters()获得参数列表,parameter.getType().getName获取类型,parameter.getName()获取参数名字
4)通过getReturnType().getName()获取方法的返回类型
5)通过getName()获取属性/构造器/方法 的名字
等等,反射也可以动态获取泛型的信息,这里还没提及到。
上面这些,就在前几天学jdbc以及Servlet的时候也用到了,当时那个Servlet进行分层处理,什么业务层啊,管理层啊这些,然后不断降低耦合度,读取xml文件,大量使用到反射把我看蒙了,现在练习之后又去看了一下,也看得懂了,逻辑也理清楚了。所以啊,基础真的很重要,有一话怎么说来着,基础不牢,地动山摇。发现写这些东西对自己帮助也挺大的,理解得更加清楚了。
也希望各位能看到这里的大佬指出文中模糊的,说错的地方。我知道自己写的差,以后也会不断改进,欢迎大家提意见!!!