一篇文章带你学懂Java反射 java反射是干嘛的
yuyutoo 2024-10-12 00:56 10 浏览 0 评论
前言
对于 Java 反射,平常工作中虽然经常用到,但一直以来都没有系统总结过,所以趁着目前有空总结一下,加深一下理解。
如果发现谬误,欢迎各位批评指正。
本文相关知识点大部分总结自 Oracle 官方文档。对于英文比较好的朋友,建议直接阅读原文档。
按例,首先描述一下定义:
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
通过反射,Java 代码可以发现有关已加载类的字段,方法和构造函数的信息,并可以在安全限制内对这些字段,方法和构造函数进行操作。
简而言之,你可以在运行状态中通过反射机制做到:
- 对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
在我看来我们平时使用 Java 反射主要涉及两个类(接口) Class, Member,如果把这两个类搞清楚了,反射基本就 ok 了。
Class详解
提到反射就不得不提到 Class,Class 可以说是反射能够实现的基础;注意这里说的 Class与 class 关键字不是同一种东西。class 关键字是在声明 java 类时使用的;而 Class 是 java JDK 提供的一个类,完整路径为 java.lang.Class,本质上与 Math, String 或者你自己定义各种类没什么区别。
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement { ... }
那 Class 到底在反射中起到什么作用呢?
For every type of object, the Java virtual machine instantiates an immutable instance of java.lang.Class which provides methods to examine the runtime properties of the object including its members and type information. Class also provides the ability to create new classes and objects. Most importantly, it is the entry point for all of the Reflection APIs.
对于每一种类,Java 虚拟机都会初始化出一个 Class 类型的实例,每当我们编写并且编译一个新创建的类就会产生一个对应 Class 对象,并且这个 Class 对象会被保存在同名 .class 文件里。当我们 new 一个新对象或者引用静态成员变量时,Java 虚拟机(JVM)中的类加载器系统会将对应 Class 对象加载到 JVM 中,然后 JVM 再根据这个类型信息相关的Class 对象创建我们需要实例对象或者提供静态变量的引用值。
比如创建编译一个 Shapes 类,那么,JVM 就会创建一个 Shapes 对应 Class 类的 Class实例,该 Class 实例保存了 Shapes 类相关的类型信息,包括属性,方法,构造方法等等,通过这个 Class 实例可以在运行时访问 Shapes 对象的属性和方法等。另外通过 Class类还可以创建出一个新的 Shapes 对象。这就是反射能够实现的原因,可以说 Class 是反射操作的基础。
需要特别注意的是,每个 class(注意 class 是小写,代表普通类)类,无论创建多少个实例对象,在 JVM 中都对应同一个 Class 对象。
下面就通过一个简单的例子来说明如何通过反射实例化一个对象。
public class Animal { private String name; private int age; public Animal(String name, int age){ this.name = name; this.age = age; } @Override public String toString() { return "Animal : name = " + name + " age = " + age; } } public class TestReflection{ private static final String TAG = "Reflection"; public void testReflection(){ //获取Animal类的Class对象 Class c = Animal.class; try { //通过Class对象反射获取Animal类的构造方法 Constructor constructor = c.getConstructor(String.class, int.class); //调用构造方法获取Animal实例 Animal animal = (Animal) constructor.newInstance( "Jack", 3); //将构造出来的Animal对象打印出来 Log.d(TAG, animal.toString()); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
下面我们来看下打印值
03-28 20:12:00.958 2835-2835/? D/Reflection: Animal : name = Jack age = 3
可以看出我们确实成功构造出了 Animal 对象,而且在这过程中 Class 功不可没。有人说你这也太费事了,都知道 Animal 对象了,我分分钟就能给你 new 出来了。
Animal animal = new Animal("Jack", 3);
没错!但是如果并不能直接导入 Animal 类呢,如果构造方法都是 private 的呢?这个时候反射就能大展身手了。
如何获取Class
说 Class 是反射能够实现的基础的另一个原因是:Java 反射包 java.lang.reflect 中的所有类都没有 public 构造方法,要想获得这些类实例,只能通过 Class 类获取。所以说如果想使用反射,必须得获得 Class 对象。
下面列举了几种能够获取 Class 对象的方法。
- Object.getClass()
- 通过对象实例获取对应 Class 对象,如
//Returns the Class for String Class c = "foo".getClass(); enum E { A, B } //Returns the Class corresponding to the enumeration type E. Class c = A.getClass(); byte[] bytes = new byte[1024]; //Returns the Class corresponding to an array with component type byte. Class c = bytes.getClass(); Set<String> s = new HashSet<String>(); //Returns the Class corresponding to java.util.HashSet. Class c = s.getClass();
然而对于基本类型无法使用这种方法
boolean b; Class c = b.getClass(); // compile-time error
- The .class Syntax
- 通过类的类型获取Class对象,基本类型同样可以使用这种方法,如
//The `.class` syntax returns the Class corresponding to the type `boolean`. Class c = boolean.class; //Returns the Class for String Class c = String.class;
- Class.forName()
- 通过类的全限定名获取Class对象, 基本类型无法使用此方法
Class c = Class.forName("java.lang.String");
对于数组比较特殊
Class cDoubleArray = Class.forName("[D"); //相当于double[].class Class cStringArray = Class.forName("[[Ljava.lang.String;"); //相当于String[][].class
- TYPE Field for Primitive Type Wrappers
- 基本类型和 void 类型的包装类可以使用 TYPE 字段获取
Class c = Double.TYPE; //等价于 double.class. Class c = Void.TYPE;
- Methods that Return Classes
- 另外还有一些反射方法可以获取 Class 对象,但前提是你已经获取了一个 Class 对象。
- 有点拗口,比如说你已经获取了一个类的 Class 对象,就可以通过反射方法获取这个类的父类的 Class 对象。
Class.getSuperclass()获得给定类的父类 Class
// javax.swing.JButton的父类是javax.swing.AbstractButton Class c = javax.swing.JButton.class.getSuperclass();
类似方法还有:
Class.getClasses() Class.getDeclaredClasses() Class.getDeclaringClass() Class.getEnclosingClass() java.lang.reflect.Field.getDeclaringClass() java.lang.reflect.Method.getDeclaringClass() java.lang.reflect.Constructor.getDeclaringClass()
通过Class获取类修饰符和类型
我们知道类的声明一般如下
下面我们就以 HashMap 为例,通过一个 Demo 来说明如何获取这些信息
public class TestReflection { private static final String TAG = "Reflection"; public void testReflection() { Class<?> c = HashMap.class; //获取类名 Log.d(TAG, "Class : " + c.getCanonicalName()); //获取类限定符 Log.d(TAG, "Modifiers : " + Modifier.toString(c.getModifiers())); //获取类泛型信息 TypeVariable[] tv = c.getTypeParameters(); if (tv.length != 0) { StringBuilder parameter = new StringBuilder("Parameters : "); for (TypeVariable t : tv) { parameter.append(t.getName()); parameter.append(" "); } Log.d(TAG, parameter.toString()); } else { Log.d(TAG, " -- No Type Parameters --"); } //获取类实现的所有接口 Type[] intfs = c.getGenericInterfaces(); if (intfs.length != 0) { StringBuilder interfaces = new StringBuilder("Implemented Interfaces : "); for (Type intf : intfs){ interfaces.append(intf.toString()); interfaces.append(" "); } Log.d(TAG, interfaces.toString()); } else { Log.d(TAG, " -- No Implemented Interfaces --"); } //获取类继承数上的所有父类 List<Class> l = new ArrayList<>(); printAncestor(c, l); if (l.size() != 0) { StringBuilder inheritance = new StringBuilder("Inheritance Path : "); for (Class<?> cl : l){ inheritance.append(cl.getCanonicalName()); inheritance.append(" "); } Log.d(TAG, inheritance.toString()); } else { Log.d(TAG, " -- No Super Classes --%n%n"); } //获取类的注解(只能获取到 RUNTIME 类型的注解) Annotation[] ann = c.getAnnotations(); if (ann.length != 0) { StringBuilder annotation = new StringBuilder("Annotations : "); for (Annotation a : ann){ annotation.append(a.toString()); annotation.append(" "); } Log.d(TAG, annotation.toString()); } else { Log.d(TAG, " -- No Annotations --%n%n"); } } private static void printAncestor(Class<?> c, List<Class> l) { Class<?> ancestor = c.getSuperclass(); if (ancestor != null) { l.add(ancestor); printAncestor(ancestor, l); } } }
打印结果如下
03-29 15:04:23.070 27826-27826/com.example.ming.testproject D/Reflection: Class : java.util.HashMap 03-29 15:04:23.070 27826-27826/com.example.ming.testproject D/Reflection: Modifiers : public 03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Parameters : K V 03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Implemented Interfaces : java.util.Map<K, V> interface java.lang.Cloneable interface java.io.Serializable 03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Inheritance Path : java.util.AbstractMap java.lang.Object 03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: -- No Annotations --
Member
Reflection defines an interface java.lang.reflect.Member which is implemented by java.lang.reflect.Field, java.lang.reflect.Method, and java.lang.reflect.Constructor .
对于 Member 接口可能会有人不清楚是干什么的,但如果提到实现它的三个实现类,估计用过反射的人都能知道。我们知道类成员主要包括构造函数,变量和方法,Java 中的操作基本都和这三者相关,而 Member 的这三个实现类就分别对应他们。
java.lang.reflect.Field :对应类变量 java.lang.reflect.Method :对应类方法 java.lang.reflect.Constructor :对应类构造函数
反射就是通过这三个类才能在运行时改变对象状态。下面就让我们通过一些例子来说明如何通过反射操作它们。
首先建一个测试类
public class Cat { public static final String TAG = Cat.class.getSimpleName(); private String name; @Deprecated public int age; public Cat(String name, int age){ this.name = name; this.age = age; } public String getName(){ return name; } public void eat(String food){ Log.d(TAG, "eat food " + food); } public void eat(String... foods){ StringBuilder s = new StringBuilder(); for(String food : foods){ s.append(food); s.append(" "); } Log.d(TAG, "eat food " + s.toString()); } public void sleep(){ Log.d(TAG, "sleep"); } @Override public String toString() { return "name = " + name + " age = " + age; } }
Field
通过 Field 你可以访问给定对象的类变量,包括获取变量的类型、修饰符、注解、变量名、变量的值或者重新设置变量值,即使变量是 private 的。
- 获取 Field
Class 提供了4种方法获得给定类的 Field
- getDeclaredField(String name)
- 获取指定的变量(只要是声明的变量都能获得,包括 private)
- getField(String name)
- 获取指定的变量(只能获得 public 的)
- getDeclaredFields()
- 获取所有声明的变量(包括 private)
- getFields()
- 获取所有的 public 变量
- 获取变量类型、修饰符、注解
一个例子说明问题
public void testField(){ Class c = Cat.class; Field[] fields = c.getDeclaredFields(); for(Field f : fields){ StringBuilder builder = new StringBuilder(); //获取名称 builder.append("filed name = "); builder.append(f.getName()); //获取类型 builder.append(" type = "); builder.append(f.getType()); //获取修饰符 builder.append(" modifiers = "); builder.append(Modifier.toString(f.getModifiers())); //获取注解 Annotation[] ann = f.getAnnotations(); if (ann.length != 0) { builder.append(" annotations = "); for (Annotation a : ann){ builder.append(a.toString()); builder.append(" "); } } else { builder.append(" -- No Annotations --"); } Log.d(TAG, builder.toString()); } }
打印结果:
filed name = age type = int modifiers = public annotations = @java.lang.Deprecated() filed name = name type = class java.lang.String modifiers = private -- No Annotations -- filed name = TAG type = class java.lang.String modifiers = public static final -- No Annotations --
- 获取、设置变量值
给定一个对象和它的成员变量名称,就能通过反射获取和改变该变量的值。什么都不说了,没有什么是不能通过一个例子解决的, Easy~
仍然是上面的测试类,通过反射获取并改变 Cat 的 name 和 age.
public void testField(){ Cat cat = new Cat("Tom", 2); Class c = cat.getClass(); try { //注意获取private变量时,需要用getDeclaredField Field fieldName = c.getDeclaredField("name"); Field fieldAge = c.getField("age"); //反射获取名字, 年龄 String name = (String) fieldName.get(cat); int age = fieldAge.getInt(cat); Log.d(TAG, "before set, Cat name = " + name + " age = " + age); //反射重新set名字和年龄 fieldName.set(cat, "Timmy"); fieldAge.setInt(cat, 3); Log.d(TAG, "after set, Cat " + cat.toString()); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
嗯?竟然报错?
System.err: java.lang.IllegalAccessException: Class java.lang.Class<com.example.ming.testnestscrollview.TestReflection> cannot access private field java.lang.String com.example.ming.testnestscrollview.Cat.name of class java.lang.Class<com.example.ming.testnestscrollview.Cat> System.err: at java.lang.reflect.Field.get(Native Method) System.err: at com.example.ming.testnestscrollview.TestReflection.testField(TestReflection.java:22) System.err: at com.example.ming.testnestscrollview.MainActivity.onCreate(MainActivity.java:17)
观察一下异常信息 java.lang.IllegalAccessException,说我们没有权限操作变量 name;回到 Cat 类中查看一下 name 变量。
private String name;
原来 name 变量是 private,Java 运行时会进行访问权限检查,private 类型的变量无法进行直接访问,刚刚进行的反射操作并没有打破这种封装,所以我们依然没有权限对 private属性进行直接访问。
难道就没有办法打破这种限制吗?必须有!强大的反射早已暗中为我们准备好了一切。反射包里为我们提供了一个强大的类。
java.lang.reflect.AccessibleObject
AccessibleObject 为我们提供了一个方法 setAccessible(boolean flag),该方法的作用就是可以取消 Java 语言访问权限检查。所以任何继承 AccessibleObject 的类的对象都可以使用该方法取消 Java 语言访问权限检查。(final 类型变量也可以通过这种办法访问)
public final class Field extends AccessibleObject implements Member
Field 正是 AccessibleObject 的子类,那么简单了,只要在访问私有变量前调用 filed.setAccessible(true) 就可以了
... fieldName.setAccessible(true); //反射获取名字, 年龄 String name = (String) fieldName.get(cat); ...
打印结果
TestReflection: before set, Cat name = Tom age = 2 TestReflection: after set, Cat name = Timmy age = 3
Bingo!
注意 Method 和 Constructor 也都是继承 AccessibleObject,所以如果遇到私有方法和私有构造函数无法访问,记得处理方法一样。
Method
The java.lang.reflect.Method class provides APIs to access information about a method's modifiers, return type, parameters, annotations, and thrown exceptions. It also be used to invoke methods.
这节主要介绍如何通过反射访问对象的方法。
- 获取 Method
Class 依然提供了4种方法获取 Method:
- getDeclaredMethod(String name, Class<?>... parameterTypes)
- 根据方法名获得指定的方法, 参数 name 为方法名,参数 parameterTypes 为方法的参数类型,如 getDeclaredMethod(“eat”, String.class)
- getMethod(String name, Class<?>... parameterTypes)
- 根据方法名获取指定的 public 方法,其它同上
- getDeclaredMethods()
- 获取所有声明的方法
- getMethods()
- 获取所有的 public 方法
注意:获取带参数方法时,如果参数类型错误会报 NoSuchMethodException,对于参数是泛型的情况,泛型须当成Object处理(Object.class)
- 获取方法返回类型
- getReturnType() 获取目标方法返回类型对应的 Class 对象
- getGenericReturnType() 获取目标方法返回类型对应的 Type 对象
这两个方法有啥区别呢?
- getReturnType()返回类型为 Class,getGenericReturnType() 返回类型为 Type; Class 实现 Type。
- 返回值为普通简单类型如 Object, int, String 等,getGenericReturnType() 返回值和 getReturnType() 一样
- 例如 public String function1(),那么各自返回值为:
- getReturnType() : class java.lang.String
- getGenericReturnType() : class java.lang.String
- 返回值为泛型
- 例如 public T function2(),那么各自返回值为:
- getReturnType() : class java.lang.Object
- getGenericReturnType() : T
- 返回值为参数化类型
- 例如public Class<String> function3(),那么各自返回值为:
- getReturnType() : class java.lang.Class
- getGenericReturnType() : java.lang.Class<java.lang.String>
其实反射中所有形如 getGenericXXX()的方法规则都与上面所述类似。
- 获取方法参数类型
- getParameterTypes() 获取目标方法各参数类型对应的 Class 对象
- getGenericParameterTypes() 获取目标方法各参数类型对应的 Type 对象
- 返回值为数组,它俩区别同上 “方法返回类型的区别” 。
- 获取方法声明抛出的异常的类型
- getExceptionTypes() 获取目标方法抛出的异常类型对应的 Class 对象
- getGenericExceptionTypes() 获取目标方法抛出的异常类型对应的 Type 对象
- 返回值为数组,区别同上
- 获取方法参数名称
- .class 文件中默认不存储方法参数名称,如果想要获取方法参数名称,需要在编译的时候加上 -parameters 参数。(构造方法的参数获取方法同样)
//这里的m可以是普通方法Method,也可以是构造方法Constructor //获取方法所有参数 Parameter[] params = m.getParameters(); for (int i = 0; i < params.length; i++) { Parameter p = params[i]; p.getType(); //获取参数类型 p.getName(); //获取参数名称,如果编译时未加上`-parameters`,返回的名称形如`argX`, X为参数在方法声明中的位置,从0开始 p.getModifiers(); //获取参数修饰符 p.isNamePresent(); //.class文件中是否保存参数名称, 编译时加上`-parameters`返回true,反之flase }
获取方法参数名称的详细信息请参考 oracle 的官方例子 MethodParameterSpy
- 获取方法修饰符
方法与 Filed 等类似
method.getModifiers();
Ps:顺便多介绍几个Method方法
- method.isVarArgs() //判断方法参数是否是可变参数
public Constructor<T> getConstructor(Class<?>... parameterTypes) //返回true public Constructor<T> getConstructor(Class<?> [] parameterTypes) //返回flase
- method.isSynthetic() //判断是否是复合方法,个人理解复合方法是编译期间编译器生成的方法,并不是源代码中有的方法
- method.isBridge() //判断是否是桥接方法,桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。可以参考:
https://www.jianshu.com/u/ceba5da6bd7a
- 通过反射调用方法
反射通过Method的invoke()方法来调用目标方法。第一个参数为需要调用的目标类对象,如果方法为static的,则该参数为null。后面的参数都为目标方法的参数值,顺序与目标方法声明中的参数顺序一致。
public native Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
还是以上面测试类 Cat 为例
注意:如果方法是private的,可以使用 method.setAccessible(true) 方法绕过权限检查
Class<?> c = Cat.class; try { //构造Cat实例 Constructor constructor = c.getConstructor(String.class, int.class); Object cat = constructor.newInstance( "Jack", 3); //调用无参方法 Method sleep = c.getDeclaredMethod("sleep"); sleep.invoke(cat); //调用定项参数方法 Method eat = c.getDeclaredMethod("eat", String.class); eat.invoke(cat, "grass"); //调用不定项参数方法 //不定项参数可以当成数组来处理 Class[] argTypes = new Class[] { String[].class }; Method varargsEat = c.getDeclaredMethod("eat", argTypes); String[] foods = new String[]{ "grass", "meat" }; varargsEat.invoke(cat, (Object)foods); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); }
被调用的方法本身所抛出的异常在反射中都会以 InvocationTargetException 抛出。换句话说,反射调用过程中如果异常 InvocationTargetException 抛出,说明反射调用本身是成功的,因为这个异常是目标方法本身所抛出的异常。
Constructor
这节主要介绍如何通过反射访问构造方法并通过构造方法构建新的对象。
- 获取构造方法
和 Method 一样,Class 也为 Constructor 提供了4种方法获取
- getDeclaredConstructor(Class<?>... parameterTypes)
- 获取指定构造函数,参数 parameterTypes 为构造方法的参数类型
- getConstructor(Class<?>... parameterTypes)
- 获取指定 public 构造函数,参数 parameterTypes 为构造方法的参数类型
- getDeclaredConstructors()
- 获取所有声明的构造方法
- getConstructors()
- 获取所有的 public 构造方法
构造方法的名称、限定符、参数、声明的异常等获取方法都与 Method 类似,请参照Method。
- 创建对象
通过反射有两种方法可以创建对象:
- java.lang.reflect.Constructor.newInstance()
- Class.newInstance()
一般来讲,我们优先使用第一种方法;那么这两种方法有何异同呢?
- Class.newInstance()仅可用来调用无参的构造方法;Constructor.newInstance()可以调用任意参数的构造方法。
- Class.newInstance()会将构造方法中抛出的异常不作处理原样抛出; Constructor.newInstance()会将构造方法中抛出的异常都包装成 InvocationTargetException 抛出。
- Class.newInstance()需要拥有构造方法的访问权限; Constructor.newInstance()可以通过 setAccessible(true) 方法绕过访问权限访问 private 构造方法。
例子在 Method 一节已经写过,这里直接截取过来
Class<?> c = Cat.class; try { Constructor constructor = c.getConstructor(String.class, int.class); Cat cat = (Cat) constructor.newInstance( "Jack", 3); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); }
注意:反射不支持自动封箱,传入参数时要小心(自动封箱是在编译期间的,而反射在运行期间)
数组和枚举
数组和枚举也是对象,但是在反射中,对数组和枚举的创建、访问和普通对象有那么一丢丢的不同,所以 Java 反射为数组和枚举提供了一些特定的 API 接口。
数组
- 数组类型
数组类型:数组本质是一个对象,所以它也有自己的类型。
例如对于int[] intArray,数组类型为class [I。数组类型中的[个数代表数组的维度,例如[代表一维数组,[[ 代表二维数组;[ 后面的字母代表数组元素类型,I 代表 int,一般为类型的首字母大写(long 类型例外,为 J)。
class [B //byte类型一维数组 class [S //short类型一维数组 class [I //int类型一维数组 class [C //char类型一维数组 class [J //long类型一维数组,J代表long类型,因为L被引用对象类型占用了 class [F //float类型一维数组 class [D //double类型一维数组 class [Lcom.dada.Season //引用类型一维数组 class [[Ljava.lang.String //引用类型二维数组
//获取一个变量的类型 Class<?> c = field.getType(); //判断该变量是否为数组 if (c.isArray()) { //获取数组的元素类型 c.getComponentType() }
- 创建和初始化数组
Java 反射为我们提供了 java.lang.reflect.Array 类用来创建和初始化数组。
//创建数组, 参数componentType为数组元素的类型,后面不定项参数的个数代表数组的维度,参数值为数组长度 Array.newInstance(Class<?> componentType, int... dimensions) //设置数组值,array为数组对象,index为数组的下标,value为需要设置的值 Array.set(Object array, int index, int value) //获取数组的值,array为数组对象,index为数组的下标 Array.get(Object array, int index)
例子,用反射创建 int[] array = new int[]{1, 2}
Object array = Array.newInstance(int.class, 2); Array.setInt(array , 0, 1); Array.setInt(array , 1, 2);
注意:反射支持对数据自动加宽,但不允许数据 narrowing(变窄?真难翻译)。意思是对于上述 set 方法,你可以在 int 类型数组中 set short 类型数据,但不可以 set long 类型数据,否则会报 IllegalArgumentException。
- 多维数组
Java 反射没有提供能够直接访问多维数组元素的 API,但你可以把多维数组当成数组的数组处理。
Object matrix = Array.newInstance(int.class, 2, 2); Object row0 = Array.get(matrix, 0); Object row1 = Array.get(matrix, 1); Array.setInt(row0, 0, 1); Array.setInt(row0, 1, 2); Array.setInt(row1, 0, 3); Array.setInt(row1, 1, 4);
或者
Object matrix = Array.newInstance(int.class, 2); Object row0 = Array.newInstance(int.class, 2); Object row1 = Array.newInstance(int.class, 2); Array.setInt(row0, 0, 1); Array.setInt(row0, 1, 2); Array.setInt(row1, 0, 3); Array.setInt(row1, 1, 4); Array.set(matrix, 0, row0); Array.set(matrix, 1, row1);
枚举
枚举隐式继承自 java.lang.Enum,Enum 继承自 Object,所以枚举本质也是一个类,也可以有成员变量,构造方法,方法等;对于普通类所能使用的反射方法,枚举都能使用;另外 java 反射额外提供了几个方法为枚举服务。
- Class.isEnum()
- Indicates whether this class represents an enum type
- Class.getEnumConstants()
- Retrieves the list of enum constants defined by the enum in the order they're declared
- java.lang.reflect.Field.isEnumConstant()
- Indicates whether this field represents an element of an enumerated type
总结
没有任何一项技术是十全十美的,Java 反射拥有强大功能的同时也带来了一些副作用。
- 性能开销
- 反射涉及类型动态解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
- 安全限制
- 使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
- 内部曝光
- 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
使用反射的一个原则:如果使用常规方法能够实现,那么就不要用反射。
相关推荐
- 墨尔本一华裔男子与亚裔男子分别失踪数日 警方寻人
-
中新网5月15日电据澳洲新快网报道,据澳大利亚维州警察局网站消息,22岁的华裔男子邓跃(Yue‘Peter’Deng,音译)失踪已6天,维州警方于当地时间13日发布寻人通告,寻求公众协助寻找邓跃。华...
- 网络交友须谨慎!美国犹他州一男子因涉嫌杀害女网友被捕
-
伊森·洪克斯克(图源网络,侵删)据美国广播公司(ABC)25日报道,美国犹他州一名男子于24日因涉嫌谋杀被捕。警方表示,这名男子主动告知警局,称其杀害了一名在网络交友软件上认识的25岁女子。雷顿警...
- 一课译词:来龙去脉(来龙去脉 的意思解释)
-
Mountainranges[Photo/SIPA]“来龙去脉”,汉语成语,本指山脉的走势和去向,现比喻一件事的前因后果(causeandeffectofanevent),可以翻译为“i...
- 高考重要考点:range(range高考用法)
-
range可以用作动词,也可以用作名词,含义特别多,在阅读理解中出现的频率很高,还经常作为完形填空的选项,而且在作文中使用是非常好的高级词汇。...
- C++20 Ranges:现代范围操作(现代c++白皮书)
-
1.引言:C++20Ranges库简介C++20引入的Ranges库是C++标准库的重要更新,旨在提供更现代化、表达力更强的方式来处理数据序列(范围,range)。Ranges库基于...
- 学习VBA,报表做到飞 第二章 数组 2.4 Filter函数
-
第二章数组2.4Filter函数Filter函数功能与autofilter函数类似,它对一个一维数组进行筛选,返回一个从0开始的数组。...
- VBA学习笔记:数组:数组相关函数—Split,Join
-
Split拆分字符串函数,语法Split(expression,字符,Limit,compare),第1参数为必写,后面3个参数都是可选项。Expression为需要拆分的数据,“字符”就是以哪个字...
- VBA如何自定义序列,学会这些方法,让你工作更轻松
-
No.1在Excel中,自定义序列是一种快速填表机制,如何有效地利用这个方法,可以大大增加工作效率。通常在操作工作表的时候,可能会输入一些很有序的序列,如果一一录入就显得十分笨拙。Excel给出了一种...
- Excel VBA入门教程1.3 数组基础(vba数组详解)
-
1.3数组使用数组和对象时,也要声明,这里说下数组的声明:'确定范围的数组,可以存储b-a+1个数,a、b为整数Dim数组名称(aTob)As数据类型Dimarr...
- 远程网络调试工具百宝箱-MobaXterm
-
MobaXterm是一个功能强大的远程网络工具百宝箱,它将所有重要的远程网络工具(SSH、Telnet、X11、RDP、VNC、FTP、MOSH、Serial等)和Unix命令(bash、ls、cat...
- AREX:携程新一代自动化回归测试工具的设计与实现
-
一、背景随着携程机票BU业务规模的不断提高,业务系统日趋复杂,各种问题和挑战也随之而来。对于研发测试团队,面临着各种效能困境,包括业务复杂度高、数据构造工作量大、回归测试全量回归、沟通成本高、测试用例...
- Windows、Android、IOS、Web自动化工具选择策略
-
Windows平台中应用UI自动化测试解决方案AutoIT是开源工具,该工具识别windows的标准控件效果不错,但是当它遇到应用中非标准控件定义的UI元素时往往就无能为力了,这个时候选择silkte...
- python自动化工具:pywinauto(python快速上手 自动化)
-
简介Pywinauto是完全由Python构建的一个模块,可以用于自动化Windows上的GUI应用程序。同时,它支持鼠标、键盘操作,在元素控件树较复杂的界面,可以辅助我们完成自动化操作。我在...
- 时下最火的 Airtest 如何测试手机 APP?
-
引言Airtest是网易出品的一款基于图像识别的自动化测试工具,主要应用在手机APP和游戏的测试。一旦使用了这个工具进行APP的自动化,你就会发现自动化测试原来是如此简单!!连接手机要进行...
- 【推荐】7个最强Appium替代工具,移动App自动化测试必备!
-
在移动应用开发日益火爆的今天,自动化测试成为了确保应用质量和用户体验的关键环节。Appium作为一款广泛应用的移动应用自动化测试工具,为测试人员所熟知。然而,在不同的测试场景和需求下,还有许多其他优...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)