一.反射(reflect)
1.反射的基本概念
2.java中的类反射
3.安全性和反射
4.反射的两个缺点
反射机制让java非常灵活
1.反射的基本概念
(1)反射需要掌握的类
java.lang.Class<T>(类/类型)
java.lang.reflecf.Constructor(构造方法)
java.lang.reflecf.Field(属性)
java.lang.reflecf.Method(方法)
java.lang.reflecf.Modifier(修饰符)
他们都可以创建对象
(2)反射机制的作用
反编译:可以将文件从.class-->.java
通过反射机制可以访问java类的属性,方法,构造方法等
2.java中的类反射
(1)获得某个类Class
Ⅰ.获取Class类型的对象的三种方式
①方式一:利用Class类中的forName方法
Class c1 = Class.forName("类名");
这里的类名必须写全,即要带上包名写完整
c1这个引用保存的是内存地址,指向了堆中的对象,该对象代表的是整个类
②方式二:java中每个类型都有Class属性,直接通过这个也可以
Class c2 = 类名.class;
③java中Object定义了 getClass() 方法
Class c3 = 对象.getClass();
返回的是对象的运行时类
因为类在JVM中只有一个,因此这三种方式返回的都是同一块地址
注意:Class代表的是类型,不是类,每种类型都有Class属性
Ⅱ.区别
①方式一是将类文件装载到JVM中的过程,因此类中的static语句块中的语句会被执行
但是方式二不会执行
Ⅲ.获取Class类型的对象之后,可以创建该“类/类型”的对象
方法:Class中的newInstance();
能够创建此Class对象所表示类型的新实例
Class c = Class.forName("类型");
Object o = c.newInstance();
这个方式其实是调用了类中的无参数构造方法创建了一个对象
所以要想使用反射机制创建对象,类中必须要有无参构造方法,没有会报错
(2)可变长参数
Ⅰ.语法
类型... 参数;
例如:public static void m1(int... a){}
Ⅱ.注意
①表示类型为int的参数可能有0~n个
②但如果有参数个数精确匹配方法,会优先调用参数匹配的方法
③可变长参数可以等同看作是数组,可以通过for循环遍历
④可变长参数只能在参数列表的最后的位置上且只能出现一次,跟C++中的默认参数一样,写在前面会有二义性
Ⅲ.Class可以用作变长参数
public static void m3(Class... args){
for(int i = 0;i<args.length;i++){
Class c = args[i];
System.out.println(c.newInstance);
}
}
这样可以创建传进方法中对象的类型的对象
(3)IO+Properties+reflect
Ⅰ.IO+Properties的联合应用
①Properties的介绍
这个和Map一样的
只不过它的key和value只能存储字符串类型
且它的key不能重复,如果key重复会覆盖value值
②动态代码的编写
步骤:
//创建属性对象
Properties p = new Properties();
//创建输入流
FileInputStream fis = new FileInputStream("文件路径");
//将输入流中的所有数据加载到属性对象中
p.load(fis);//这样属性对象中就有(key和value的值了)
//关闭流
fis.close();
//通过key获取value
String value = p.getPriperty("key");
Ⅱ.说明
①这样就完成了一个动态的配置文件,改变文件中的值,java代码获取到的value值也不一样
②这种代码基本用于编写软件的属性文件,获取用户的数据库用户名和密码啥的,java中建议属性文件以.properties结尾
③属性文件中数据要求
key和value之间可以用空格,冒号,等号作为分割符
如果出现多个,以第一个出现的作为分隔符
Ⅲ.IO+Properties+reflect的联合应用
这三个合起来可以实现动态创建java对象的功能
把类名写在属性文件中,只需要改变文件中的类型,就可以创建不同类型的对象,java程序不需要改动
(4)反编译某个类的所有属性Field
Ⅰ.获得所有属性的步骤
①获取整个类
Class c = Class.forName("类名");
②获取属性Field
Filed[] fs = c.getField();//这种方法只能获取所有public修饰的属性
③获取所有属性
Field[] fs = c.getDeclaredField();
④获得属性的类型方式一,利用Field中的getName()方法
fs[0].getName();
⑤获得属性的类型方式二,利用Field中的getType()方法
Class type = fs[0].getType();
type.getName();
或者 type.getSimpleName();//这个方法属性和名称都可以获取到
⑥获得属性的权限修饰符
int i = type.getModifiers();
返回值是int类型
0表示默认
1表示public
2表示private
4表示protected
⑦把刚刚获取到的权限修饰符转换成String,这样好看一点
String strModifier = Modifier.toString(i);//静态方法直接类名加.调用就行
Ⅱ.获取某个特定的属性Field
①用处
通过Field中的方法我们可以实现,动态的给不同的属性调用set和get方法
②步骤
//获取类
Class c = Class.forName("类名");
//获取属性
Field f = getDecclareField("属性名");
//通过获取到的属性调用其的set和get方法,但是私有属性这个方法不管用
Object o = c.newInstance();//要调用set和get方法我们需要先创建一个对象
f.set(对象名,值);
System.out.printfln(f.get(o));
//如果想要调用私有属性的set和get方法,我们需要在对象创建完后打破封装
Object o = c.newInstance();
//打破封装
f.setAccessible(true);
//这样就可以了,但打破封装会有安全性的问题
f.set(对象名,值);
System.out.println(f.get(o));
(5)反编译某个类的所有方法Method
Ⅰ.获取所有方法的步骤
①获取类
Class c = Class.forName("类名");
②获取所有方法
Method[] ms = c.getDeclaredMethods();
for(Method m:ms){
//获取方法修饰符
System.out.println(Modifier.toString(m.getModifiers()));
//获取方法的返回值类型
Class returnType = m.getReturnType();
System.out.println(returnType.getSimpleName());
//获取方法名
System.out.println(m.getName());
//获取形参列表
Class[] parameterTypes = m.getParameterTypes();
for(Class parameterType:parameterTypes){
System.out.println(parameterType.getSimpleName());
}
}
Ⅱ.反编译的过程
Class c = Class.forName("类名");
Method[] ms = c.getDeclaredMethods();
StringBuffer sb = new StringBuffer();
sb.append(Modifier.toString(c.getModifiers())+" class ");
sb.append(c.getSimpleName()+"{\n");
for(Method m:ms){
sb.append("\t");
sb.append(Modifier.toString(m.getModifiers())+" ");
sb.append(m.getReturnType().getSimpleName()+" ");
sb.append(m.getName+"(");
//形参
Class[] parameterTypes = m.getParameterTypes();
for(int i = 0;i<parameterTypes.length;i++){
Class parameterType = parameterTypes[i];
if(i==parameterTypes.length){
sb.append(parameterType.getSimpleName());
}else{
sb.append(parameterType.getSimpleName()+",");
}
}
sb.append("){}\n");
}
sb.append(")");
Ⅲ.获取某个特定的方法Method来执行
①获取类
Class c = Class.forName("类名");
②获取某个特定的方法并执行
//通过:方法+形参列表能唯一确定一个方法
//形参用类型.class表示,因为该方法需要传的是class参数
Method m = c.getDeclaredMethod("方法名",参数列表);
//执行方法
//先创建对象
Object o = c.newInstance();
//调用o对象的m方法
Object retValue = m.invoke(o,实参列表);
(5)反编译某个类的所有构造方法
Ⅰ.获取所有构造方法步骤
//获取类
Class c = ClassforName("类名");
//获取所有构造方法
Constructor[] cs = c.getDeclaredConstructors();
for(Constructor con:cs){
//获取修饰符
System.out.println(Modifier.toString(con.getModifiers()));
//获取构造方法名
System.out.println(c.getName());
//构造方法的形式参数列表
Class[] parameterTypes = con.getParameterTypes();
for(Class parameterType:parameterTypes){
System.out.println(parameterType.getSimpleName());
}
}
Ⅱ.反编译构造方法
过程和普通方法差不多就不写了
Ⅲ.获取某个特定的构造方法,用来创建对象
//获取类
Class c = Class.forName("类名");
//获取特定的构造方法
//构造方法只需要形参列表就可以跟其他构造方法区分开了
//同样这里的形参列表也是类型.class
Constructor con = c.getDeclaredConstructor(形参列表);
//用获取的构造方法创建对象
Object o = con.newInstance(实参列表);
(6)通过反射获取某个类的父类或父接口
Ⅰ.步骤
//获取类
Class c = Class.forName("类名");
//获取父类
Class superClass = c.getSuperclass();
System.out.println(superClass.getName());
//获取父接口
//父接口可能有多个,因此返回的是一个Class数组
Class[] ins = c.getInterfaces();
for(Class in:ins){
System.out.println(in.getName());
}
3.反射的两个缺点
(1)反射打破了封装性,会导致安全性出现问题
(2)反射的效率比一般的直接调用要慢得多,因为反射不是一个直接执行方法的操作
它是一种解释执行,它告诉JVM怎么做
因此尽量不要大量使用