第5章 - Java反射

5- Java反射

 

14 Class类与Java反射机制

(示例: basic.reflect.ReflectDemo)

    通过Java反射机制,可以在程序中访问已经装载到JVM中的Java对象的描述,实现访问、监测、修改描述Java对象本身信息的功能。所有Java类均继承ObjectObject类中定义了getClass()方法该方法返回一个类型为Class的对象

    在通过getFields()getMethods()方法一次获取权限为public的成员变量和方法时还包括从超类中继承的变量和方法,而getDeclaredFields()getDeclaredMethods()方法只获得本类中定义的所有成员变量和方法

组成部分

访问方法

返回值类型

说明

包路径

getPackage()

Package对象

获取该类的存放路径

类名称

getName()

String对象

获取该类的名称

继承类

getSuperclass()

Class对象

获取该类继承的类

实现接口

getInterface()

Class型数组

获取该类实现的所有接口

构造方法

getConstructors()

Constructor型数组

获取所有权限为public的构造方法

getConstructor(Class<?>... parameterTypes)

Constrctor对象

获取权限为public的指定构造方法

getDeclaredConstructors()

Constructor型数组

获取所有构造方法,按声明顺序返回

getDeclaredConstructor (Class<?>... parameterTypes)

Constrctor对象

获取指定构造方法

方法

getMethods()

Method型数组

获取所有权限为public的方法

getMethod(String name, Class<?>... parameterTypes)

Method对象

获取权限为public的指定方法

getDeclaredMethods()

Method型数组

获取所有方法按声明顺序返回

getDeclaredMethod(String name, Class<?>... parameterTypes)

Method对象

获取指定方法

成员变量

getFields()

Field型数组

获取所有权限为public的成员变量

getField(String name)

Field对象

获取权限为public的指定成员变量

getDeclaredFields()

Field型数组

获取所有成员变量,按声明顺序返回

getDeclaredField(String name)

Field对象

获取指定成员变量

内部类

getClasses()

Class型数组

获取所有权限为public的内部类

getDeclaredClasses()

Class型数组

获取所有内部类

内部类的声明类

getDeclaringClass()

Class对象

如果该类为内部类则返回它的成员类,否则返回null

 

 

14.1 访问构造方法

    通过下列方法访问构造方法时将返回Constructor类型的对象或数组每个Constructor对象代表一个构造函数利用Constructor对象可以操纵相应的构造方法

    1).getConstructors()

    2).getConstructors(Class<?>... parameterType)

    3).getDeclaredConstructors()

    4).getDeclaredConstructor(Class<?>... parameterType)

    如果访问指定的构造方法需要根据该构造方法的入口参数的类型来访问例如访问一个入口参数类型依次为Stringint型的构造方法可以通过

    1).objectClass.getDeclaredConstructor(String.class, int.class);

    2).objectClass.getDeclaredConstructor(new Class[]{String.class, int.class});

Constructor类中的常用方法

方法

说明

isVarArgs()

查看该构造方法是否允许带有可变数量的参数, 允许返回true, 否则返回false

getParameterTypes()

按照声明顺序以Class数组的形式获得该构造方法的各个参数的类型

getExceptionTypes()

Class数组的形式获得该构造方法的各个参数的类型

newInstance(Object... initargs)

通过该构造方法利用指定参数创建一个该类的对象, 如果未设置参数则表示采用默认无参数的构造方法

setAccessible(boolean flag)

如果该构造方法权限为private, 默认不允许通过反射利用newInstance(Object...initargs)方法创建对象如果先执行该方法并将入口参数设为true, 则允许创建

getModifiers()

获得可以解析出该构造方法所采用修饰符的正数

    通过java.lang.reflect.Modifier类可以解析出getModifiers()方法的返回值所表示的修饰符信息在该类中提供了用来解析的静态方法既可以查看是否被指定的修饰符修饰还可以以字符串的形式获得所有修饰符.

Modifier类中的常用静态方法

静态方法

说明

isPublic(int mod)

查看是否被public修饰符修饰如果是返回true, 否则返回false

isProtected(int mod)

查看是否被protected修饰符修饰如果是返回true,否则返回false

isPrivate(int mod)

查看是否被private修饰符修饰如果是返回true,否则返回false

isStatic(int mod)

查看是否被static修饰符修饰如果是返回true, 否则返回false

isFinal(int mod)

查看是否被final修饰符修饰如果是返回true, 否则返回false

toString(int mod)

以字符串的形式返回所有修饰符

 

 

14.2 访问成员变量

   通过下列方法访问成员变量时将返回Field类型的对象或数组每个Field对象代表一个成员变量利用Field对象可以操纵相应的成员变量

    1).getFields()

    2).getField(String name)

    3).getDeclaredFields()

    4).getDeclaredField(String name)

    如果是访问指定的成员变量可以通过该成员变量的名称来访问.例如访问一个名称为birthday的成员变量:

       object.getDeclaredField("birthday")

Field类中的常用方法

方法

说明

getName()

获取该成员变量的名称

getType()

获取表示该成员变量类型的Class对象

get(Object obj)

获取指定对象obj中该成员变量的值返回值为Object

set(Object obj, Object value)

将指定对象obj中该成员变量的值设置为value

getInt(Object obj)

获取指定对象obj中类型为int的该成员变量的值

setInt(Object obj, int i)

将指定对象obj中类型为int的该成员变量的值设置为i

getFloat(Object obj)

获取指定对象obj中类型为float的该成员变量的值

setFloat(Object obj, float f)

将指定对象obj中类型为float的该成员变量的值设置为f

getBoolean(Object obj)

获取指定对象obj中类型为boolean的该成员变量的值

setBoolean(Object obj, boolean z)

将指定对象obj中类型为boolean的该成员变量的值设置为z

setAccessible(boolean flag)

如果该构造方法的权限为private, 默认不允许通过反射利用newInstance(Object... initargs)方法创建对象如果先执行该方法并将入口参数设为true,则允许创建

getModifiers()

获得可以解析出该成员变量所采用修饰符的正数

 

 

14.3 访问方法

    通过下列方法访问方法时将返回Method类型的对象或数组每个Method对象代表一个方法利用Method对象可以操作相应的方法.

    1).getMethods()

    2).getMethod(String name, Class<?>... parameterTypes)

    3).getDeclaredMethods()

    4).getDeclaredMethod(String name, Class<?>... parameterTypes)

    如果是访问指定的方法需要根据该方法的名称和入口参数的类型来访问例如访问一个名称为pring, 入口参数类型依次为Stringint型的方法可以通过下面两种方式实现

    1).objectClass.getDeclaredMethod("pring", String.class, int.class)

    2).objectClass.getDeclaredMethod("pring", new Class[]{String.class, int.class})

方法

说明

getName()

获得该方法的名称

getParameterTypes()

按照声明顺序以Class数组的形式获得该方法的各个参数的类型

getReturnType()

Class对象的形式获得该方法的返回值的类型

getExceptionTypes()

Class数组的形式获得该方法可能抛出的异常类型

invoke(Object obj, Object... args)

利用指定参数args执行指定对象obj中的该方法返回值为Object

isVarArgs()

查看构造方法是否允许带有可变数量的参数如果允许返回true, 否则返回false

getModifiers()

获得可以解析出该方法所采用修饰符的整数

 

 

14.4 注解(Annotation)的使用

(示例: basic.demo14.annotation)

14.4.1 什么是注解(Annotation)

    Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法. Annotion(注解)是一个接口程序可以通过反射来获取指定程序元素的Annotion对象然后通过Annotion对象来获取注解里面的元数据

  Annotation(注解)JDK5.0及以后版本引入的它可以用于创建文档跟踪代码中的依赖性甚至执行基本编译时检查从某些方面看, annotation就像修饰符一样被使用并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中这些信息被存储在Annotation"name=value"结构对中.

  Annotation的成员在Annotation类型中以无参数的方法的形式被声明其方法名和返回值定义了该成员的名字和类型在此有一个特定的默认语法允许声明任何Annotation成员的默认值一个Annotation可以将name=value对作为没有定义默认值的Annotation成员的值当然也可以使用name=value对来覆盖其它成员默认值这一点有些近似类的继承特性父类的构造函数可以作为子类的默认构造函数但是也可以被子类覆盖

  Annotation能被用来为某个程序元素(类、方法、成员变量等)关联任何的信息需要注意的是这里存在着一个基本的规则: Annotation不能影响程序代码的执行无论增加、删除Annotation, 代码都始终如一的执行另外尽管一些annotation通过java的反射api方法在运行时被访问java语言解释器在工作时忽略了这些annotation. 正是由于java虚拟机忽略了Annotation, 导致了annotation类型在代码中是"不起作用"只有通过某种配套的工具才会对annotation类型中的信息进行访问和处理本文中将涵盖标准的Annotationmeta-annotation类型陪伴这些annotation类型的工具是java编译器(当然要以某种特殊的方式处理它们)

 

14.4.2 metadata(元数据)

  元数据从metadata一词译来就是"关于数据的数据"的意思.

  元数据的功能作用有很多比如你可能用过Javadoc的注释自动生成文档这就是元数据功能的一种总的来说元数据可以用来创建文档跟踪代码的依赖性执行编译时格式检查代替已有的配置文件如果要对于元数据的作用进行分类目前还没有明确的定义不过我们可以根据它所起的作用大致可分为三类:

    1). 编写文档:通过代码里标识的元数据生成文档

    2). 代码分析:通过代码里标识的元数据对代码进行分析

    3). 编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查

  在Java中元数据以标签的形式存在于Java代码中元数据标签的存在并不影响程序代码的编译和执行它只是被用来生成其它的文件或针在运行时知道被运行代码的描述信息.

综上所述:

  1).元数据以标签的形式存在于Java代码中

  2).元数据描述的信息是类型安全的即元数据内部的字段都是有明确类型的

 3).元数据需要编译器之外的工具额外的处理用来生成其它的程序部件

  4).元数据可以只存在于Java源代码级别也可以存在于编译之后的Class文件内部

 

14.4.3 注解的分类:

根据注解参数的个数我们可以将注解分为三类:

    1).标记注解一个没有成员定义的Annotation类型被称为标记注解这种Annotation类型仅使用自身的存在与否来为我们提供信息比如后面的系统注解@Override

    2).单值注解

    3).完整注解

根据注解使用方法和用途,我们可以将Annotation分为三类:

    1).JDK内置系统注解

    2).元注解

    3).自定义注解

 

14.4.4 系统内置标准注解

  注解的语法比较简单除了@符号的使用外他基本与Java固有的语法一致, JavaSE中内置三个标准注解定义在java.lang:

  1).@Override:用于修饰此方法覆盖了父类的方法;

  2).@Deprecated:用于修饰已经过时的方法;

  3).@SuppressWarnnings: 用于通知java编译器禁止特定的编译警告

下面我们依次看看三个内置标准注解的作用和使用场景

 

14.4.4.1 @Override(限定重写父类方法)

    @Override是一个标记注解类型它被用作标注方法它说明了被标注的方法重载了父类的方法起到了断言的作用如果我们使用了这种Annotation在一个没有覆盖父类方法的方法时, java编译器将以一个编译错误来警示这个annotaton常常在我们试图覆盖父类方法而确又写错了方法名时发挥威力

 

14.4.4.2 @Deprecated,标记已过时:

    同样Deprecated也是一个标记注解当一个类型或者类型成员使用@Deprecated修饰的话编译器将不鼓励使用这个被标注的程序元素而且这种修饰具有一定的"延续性"

    如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated, 但编译器仍然要报警

  值得注意, @Deprecated这个annotation类型和javadoc中的@deprecated这个tag是有区别的前者是java编译器识别的而后者是被javadoc工具所识别用来生成文档(包含程序成员为什么已经过时、它应当如何被禁止或者替代的描述)

  在java5.0, java编译器仍然象其从前版本那样寻找@deprecated这个javadoc tag, 并使用它们产生警告信息但是这种状况将在后续版本中改变我们应在现在就开始使用@Deprecated来修饰过时的方法而不是@deprecated javadoc tag

 

14.4.4.3 SuppressWarnnings(抑制编译器警告)

  @SuppressWarnings被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告java5.0, sun提供的javac编译器为我们提供了-Xlint选项来使编译器对合法的程序代码提出警告此种警告从某种程度上代表了程序错误例如当我们使用一个generic collection类而又没有提供它的类型时编译器将提示出"unchecked warning"的警告通常当这种情况发生时我们就需要查找引起警告的代码如果它真的表示错误我们就需要纠正它例如如果警告信息表明我们代码中的switch语句没有覆盖所有可能的case, 那么我们就应增加一个默认的case来避免这种警告

  有时我们无法避免这种警告例如我们使用必须和非generic的旧代码交互的generic collection类时我们不能避免这个unchecked warning. 此时@SuppressWarning就要派上用场了在调用的方法前增加@SuppressWarnings修饰告诉编译器停止对此方法的警告

  SuppressWarning不是一个标记注解它有一个类型为String[]的成员这个成员的值为被禁止的警告名对于javac编译器来讲-Xlint选项有效的警告名也同样对@SuppressWarings有效同时编译器忽略掉无法识别的警告名

SuppressWarnings注解的常见参数值的简单说明:

  1).deprecation:使用了不赞成使用的类或方法时的警告

  2).unchecked:执行了未检查的转换时的警告例如当使用集合时没有用泛型(Generics)来指定集合保存的类型

  3).fallthrough:当Switch 程序块直接通往下一种情况而没有Break 时的警告;

  4).path:在类路径、源文件路径等中有不存在的路径时的警告;

  5).serial:当在可序列化的类上缺少serialVersionUID 定义时的警告;

  6).finally:任何finally 子句不能正常完成时的警告

  7).all:关于以上所有情况的警告

 

14.4.5 元注解

    元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型作说明。Java5.0定义的元注解:

        1).@Target        2).@Retention     3).@Documented  4).@Inherited

    这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明

 

14.4.5.1 @Target

    @Target说明了Annotation所修饰的对象范围: Annotation可被用于packagestypes(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数). Annotation类型的声明中使用了target可更加明晰其修饰的目标

  作用用于描述注解的使用范围(被描述的注解可以用在什么地方)

  取值(ElementType):

    1).CONSTRUCTOR: 用于描述构造器

    2).FIELD: 用于描述域

    3).LOCAL_VARIABLE: 用于描述局部变量

    4).METHOD: 用于描述方法

    5).PACKAGE: 用于描述包

    6).PARAMETER: 用于描述参数

    7).TYPE: 用于描述类、接口(包括注解类型enum声明

 

14.4.5.2 @Retention

  @Retention定义了该Annotation被保留的时间长短某些Annotation仅出现在源代码中而被编译器丢弃而另一些却被编译在class文件中编译在class文件中的Annotation可能会被虚拟机忽略而另一些在class被装载时将被读取(请注意并不影响class的执行因为Annotationclass在使用上是被分离的). 使用这个meta-Annotation可以对Annotation"生命周期"限制.

  作用表示需要在什么级别保存该注释信息用于描述注解的生命周期(被描述的注解在什么范围内有效)

  取值(RetentionPoicy)

    1).SOURCE: 在源文件中有效(即源文件保留)

    2).CLASS: class文件中有效(class保留)

    3).RUNTIME: 在运行时有效(即运行时保留,注解处理器可以通过反射获取到该注解的属性值从而去做一些运行时的逻辑处理)

    Retention meta-annotation类型有唯一的value作为成员它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值.

 

14.4.5.3 @Documented:

    @Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API, 因此可以被例如javadoc此类的工具文档化. Documented是一个标记注解没有成员

 

14.4.5.4 @Inherited

  @Inherited 元注解是一个标记注解, @Inherited阐述了某个被标注的类型是被继承的如果一个使用了@Inherited修饰的annotation类型被用于一个class, 则这个annotation将被用于该class的子类.

  注意: @Inherited annotation类型是被标注过的class的子类所继承类并不从它所实现的接口继承annotation, 方法并不从它所重载的方法继承annotation

  当@Inherited annotation类型标注的annotationRetentionRetentionPolicy.RUNTIME,则反射API增强了这种继承性如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation反射代码检查将展开工作检查class和其父类直到发现指定的annotation类型被发现或者到达类继承结构的顶层

 

14.4.6 自定义注解:

  使用@interface自定义注解时自动继承了java.lang.annotation.Annotation接口由编译程序自动完成其他细节在定义注解时不能继承其他的注解或接口. @interface用来声明一个注解其中的每一个方法实际上是声明了一个配置参数方法的名称就是参数的名称返回值类型就是参数的类型(返回值类型只能是基本类型、ClassStringenum). 可以通过default来声明参数的默认值

定义注解格式:

public @interface 注解名{

    String value() default "";

    public int id();

}

注解参数的可支持数据类型:

    1).所有基本数据类型(int,float,boolean,byte,double,char,long,short)

  2).String类型

  3).Class类型

  4).enum类型

  5).Annotation类型

  6).以上所有类型的数组

Annotation类型里面的参数该怎么设定

  第一只能用public或默认(default)这两个访问权修饰例如, [default | public] String value();

  第二参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String,Enum,Class,annotations等数据类型以及这一些类型的数组例如, String value(); 这里的参数成员就为String;  

    第三如果只有一个参数成员最好把参数名称设为"value", 后加小括号.

注解元素的默认值:

  注解元素必须有确定的值要么在定义注解的默认值中指定要么在使用注解时指定非基本类型的注解元素的值不可为null. 因此使用空字符串或0作为默认值是一种常用的做法这个约束使得处理器很难表现一个元素的存在或缺失的状态因为每个注解的声明中所有元素都存在并且都具有相应的值为了绕开这个约束我们只能定义一些特殊的值例如空字符串或者负数一次表示某个元素不存在

 

14.4.7 注解处理器

    如果没有用来读取注解的方法和工作那么注解也就不会比注释更有用处了

    使用注解的过程中很重要的一部分就是创建于使用注解处理器. Java SE5扩展了反射机制的API, 以帮助程序员快速的构造自定义注解处理器

注解处理器类库(java.lang.reflect.AnnotatedElement)

  Java使用Annotation接口来代表程序元素前面的注解该接口是所有Annotation类型的父接口除此之外, Javajava.lang.reflect包下新增了AnnotatedElement接口该接口代表程序中可以接受注解的程序元素该接口主要有如下几个实现类:

  1).Class:类定义

  2).Constructor:构造器定义

  3).Field:类的成员变量定义

  4).Method:类的方法定义

  5).Package:类的包定义

  java.lang.reflect包下主要包含一些实现反射功能的工具类实际上, java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力当一个Annotation类型被定义为运行时的Annotation该注解才能是运行时可见class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取

  AnnotatedElement接口是所有程序元素(ClassMethodConstructor)的父接口所以程序通过反射获取了某个类的AnnotatedElement对象之后程序就可以调用该对象的如下四个个方法来访问Annotation信息:

  方法1: <T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解如果该类型注解不存在则返回null

  方法2: Annotation[] getAnnotations():返回该程序元素上存在的所有注解.

  方法3: boolean is AnnotationPresent(Class<?extends Annotation> annotationClass): 判断该程序元素上是否包含指定类型的注解存在则返回true, 否则返回false.

  方法4: Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释与此接口中的其他方法不同该方法将忽略继承的注释.(如果没有注释直接存在于此元素上则返回长度为零的一个数组).该方法的调用者可以随意修改返回的数组这不会对其他调用者返回的数组产生任何影响

    Java注解的基础知识点(见下面导图)基本都过了一遍下一篇我们通过设计一个基于注解的简单的ORM框架来综合应用和进一步加深对注解的各个知识点的理解和运用

 

 

14.5 获取Class对象的三种方式

    JAVA反射机制是在运行状态中, 对于任意一个类(class文件) , 都能够知道这个类的所有属性和方法, 并能够调用它的任意一个方法和属性

    动态获取类中的信息, 就是JAVA的反射, 可以理解为对类的解剖

/**

 * 方式一

 * Object类中的getClass()方法的想要用这种方式必须要明确具体的类并创建对象

 */

publicstaticvoidgetClassObject1() {

    System.out.println("\n==========方式一==========");

    ReflectBean bean1newReflectBean();

    Classclazz1bean1.getClass();

    ReflectBean bean2newReflectBean();

    Classclazz2bean2.getClass();

    System.out.println(clazz1== clazz2);

}

/**

 * 方式二

 * 任何数据类型都具备一个静态的属性.class来获取其对应的Class对象。相对简单但是还是要明确用到类中的静态成员.

 */

publicstaticvoidgetClassObject2() {

    System.out.println("\n==========方式二==========");

    Classclazz1= ReflectBean.class;

    Classclazz2= ReflectBean.class;

    System.out.println(clazz1== clazz2);

}

/**

 * 方式三(推荐使用

 * 只要通过给定的类的字符串名称就可以获取该类更为扩展,可是用Class类中的方法完成

 * 该方法就是forName。这种方式只要有名称即可更为方便扩展性更强

 */

publicstaticvoidgetClassObject3() throwsClassNotFoundException {

    System.out.println("\n==========方式三==========");

    String className"basic.demo14.ReflectBean";

    Classclazz= Class.forName(className);

    System.out.println(clazz);

}

 

 

14.6 newClass对比

/**

 * 实例化对象的方式

 * @throwsClassNotFoundException 

 */

publicstaticvoidNewDemo() throwsException {

    System.out.println("\n==========newClass的对比==========");

    /**

     * new的方式

     * 先根据被new的类的名称找寻该类的字节码文件并加载进内存并创建该字节码文件对象并接着创建该字节文件的对应的Person对象

     */

    basic.demo14.ReflectBeanbean1newbasic.demo14.ReflectBean();

    

    /**

     * Class的方式(反射获取对象的方式)

     * 找寻该名称类文件并加载进内存并产生Class对象

     */

    Classclazz= Class.forName("basic.demo14.ReflectBean");

    Object obj1clazz.newInstance();

    ReflectBean bean2= (ReflectBean) obj1;

    bean2.setNum(1);

    bean2.setName("Class的方式");

    bean2.show();

    

    System.out.println();

    /**

     * 如果没有默认构造函数则先获取该构造函数

     * 获取到了指定的构造函数对象

     * 通过该构造器对象的newInstance方法进行对象的初始化.

     */

    Constructorconstructor=clazz.getConstructor(int.class, String.class);

    Object objconstructor.newInstance(1,"Constructor构造");

}

 

转载于:https://my.oschina.net/vwFisher/blog/2209500

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值