在一般操作数据的时候,我们都是知道并且依赖于数据类型的,比如:
1)根据类型使用new创建对象。
2)根据类型定义变量,类型可能是基本类型、类、接口或数组。
3)将特定类型的对象传递给方法。
4)根据类型访问对象的属性,调用对象的方法。
编译器也是根据类型进行代码的检查编译的。
反射不一样,它是在运行时,而非编译时,动态获取类型的信息,比如接口信息、成员信息、方法信息、构造方法信息等,根据这些动态获取到的信息创建对象、访问/修改成员、调用方法等。
1. 反射和new
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
基本上效果差不多,但是new对象,无法调用该类里面私有的东西,反射反之,具体怎么做请参考java AIP,不过反射需要以牺牲性能做代价。
在不知道类名的情况下,你怎么去new?我相信很多人看到这句话都迷糊了(新手),肯定有这样的疑问,不知道类名,你怎么反射啊?
那么接下来在讲讲new和反射本质上的区别,new属于静态编译,而反射属于动态编译,意思就说只有到运行时他才会去获得该对象的实例,可能讲的有些抽象(也有可能讲的不太正确)
举例:spring框架是事先就写好的框架,他内部的处理并不知道用户要写哪些类,应为那是以后由用他的人来定的,这时候你还能在spring内部去new吗?所以用户在用的时候才去配置文件中配置类路径
1.1 静态编译和动态编译
静态编译就是在编译的时候把你所有的模块都编译进exe里去,当你启动这个exe的时候所有模块都加载进来了。你写小程序没问题,但程序一大,加载的过程(就是当你运行程序时初始化的过程)就比较费力了。。大多数ppc的硬件配置还是很一般的。
动态编译就不一样了,你编译的时候那些模块都没有编译进去,一般情况下你可以把那些模块都编译成dll,这样你启动程序(初始化)的时候这些模块不会被加载,而是在运行的时候,用到那个模块就调用哪个模块。
class类
每一个java类都会被编译成class类,在运行时才会使用的可以利用反射进行加载并使用。
1.2 获取Class对象的四种方式
如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象:
1.知道具体类的情况下可以使用:
Class alunbarClass = TargetObject.class; // Copy to clipboardErrorCopied
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化
2.通过 Class.forName()传入类的路径获取:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");Copy to clipboardErrorCopied
3.通过对象实例instance.getClass()获取:
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();Copy to clipboardErrorCopied
4.通过类加载器xxxClassLoader.loadClass()传入类路径获取:
Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");Copy to clipboardErrorCopied
通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行
2. 反射实例
// 测试类
public class test_1 {
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
static class Student{
String name;
int age;
Double score;
}
public static void main(String[] args) throws Exception{
Student z = new Student("张三",18,89d);
String str = SimpleMapper.toString(z);
Student z2 = (Student)SimpleMapper.fromString(str); // 根据名称创建对象
System.out.println(z2);
}
}
// 序列化和反序列化类
public class SimpleMapper {
public static String toString(Object obj){
try{
Class<?> cls = obj.getClass();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(cls.getName()+"\n");
for(Field f: cls.getDeclaredFields()){
if(!f.isAccessible()){
f.setAccessible(true);
}
stringBuilder.append(f.getName() + "=" + f.get(obj).toString() + "\n");
}
return stringBuilder.toString();
}catch (IllegalAccessException e){
throw new RuntimeException(e);
}
}
// 类型转换,根据字段的类型,将字符串形式的值转换为了对应类型的值。
private static void setFieldValue(Field f, Object obj, String value) throws Exception {
Class<?> type = f.getType();
if (type == int.class) {
f.setInt(obj, Integer.parseInt(value));
} else if (type == byte.class) {
f.setByte(obj, Byte.parseByte(value));
} else if (type == short.class) {
f.setShort(obj, Short.parseShort(value));
} else if (type == long.class) {
f.setLong(obj, Long.parseLong(value));
} else if (type == float.class) {
f.setFloat(obj, Float.parseFloat(value));
} else if (type == double.class) {
f.setDouble(obj, Double.parseDouble(value));
} else if (type == char.class) {
f.setChar(obj, value.charAt(0));
} else if (type == boolean.class) {
f.setBoolean(obj, Boolean.parseBoolean(value));
} else if (type == String.class) {
f.set(obj, value);
} else {
Constructor<?> ctor = type.getConstructor(new Class[] { String.class });
f.set(obj, ctor.newInstance(value));
}
}
public static Object fromString(String str){
try{
String[] lines = str.split("\n");
if(lines.length < 1){
throw new IllegalStateException(str);
}
Class<?> cls = Class.forName(lines[0]);
Object obj = cls.newInstance();
if(lines.length > 1){
for (int i = 1; i < lines.length; i++) {
String[] fv = lines[i].split("=");
if (fv.length != 2) {
throw new IllegalArgumentException(lines[i]);
}
Field f = cls.getDeclaredField(fv[0]);
if(!f.isAccessible()){
f.setAccessible(true);
}
setFieldValue(f, obj, fv[1]);
}
}
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
3. 反射与泛型
在介绍泛型的时候,我们提到,泛型参数在运行时会被擦除,这里,我们需要补充一下,在类信息Class中依然有关于泛型的一些信息,可以通过反射得到。泛型涉及一些更多的方法和类。
3.1 反射获取泛型实例
static class GenericTest<U extends Comparable<U>, V>{
U u;
V v;
List<String> list;
public U test(List<? extends Number> numbers){
return null;
}
}
public static void main(String[] args) throws Exception {
Class<?> cls = GenericTest.class;
// 类的类型参数
for(TypeVariable t: cls.getTypeParameters()){
System.out.println(t.getName() + "extends" + Arrays.toString(t.getBounds()));
}
// 字段:泛型类型
Field fu = cls.getDeclaredField("u");
System.out.println(fu.getGenericType());
// 字段:参数化的类型
Field flist = cls.getDeclaredField("list");
Type listType = flist.getGenericType();
if (listType instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) listType;
System.out.println("raw type: " + pType.getRawType() + ",type arguments:"
+ Arrays.toString(pType.getActualTypeArguments()));
}
// 方法的泛型参数
Method m = cls.getMethod("test", new Class[] { List.class });
for (Type t : m.getGenericParameterTypes()) {
System.out.println(t);
}
}
反射虽然是灵活的,但一般情况下,并不是我们优先建议的,主要原因是:
-
反射更容易出现运行时错误,使用显式的类和接口,编译器能帮我们做类型检查,减少错误,但使用反射,类型是运行时才知道的,编译器无能为力。
-
反射的性能要低一些,在访问字段、调用方法前,反射先要查找对应的Field/Method,要慢一些。
简单地说,如果能用接口实现同样的灵活性,就不要使用反射。
本文探讨了Java中的反射机制,包括反射与new的区别、静态编译与动态编译的概念,以及获取Class对象的四种方式。反射允许在运行时动态获取类信息,创建对象并访问/修改成员、调用方法。虽然反射提供了灵活性,但可能导致运行时错误和性能下降,通常不作为首选方案。
874

被折叠的 条评论
为什么被折叠?



