Java核心API-反射机制和注解

标题目录

  • 反射机制
    • 类对象
    • 获取类对象的方式
    • 反射机制实例化对象
    • 变长参数
    • 反射机制调用方法 ( Method 类 )
      • 获取 Method
      • Method 提供的方法
    • 反射机制操作属性 ( Field 类 )
    • 暴力反射
  • 注解
    • 注解的定义
    • 注解的应用
    • 元注解
    • boolean isAnnotationPresent(Class cls)
    • 注解参数
    • Annotation getAnnotation(Class cls)

反射机制

反射是Java的动态机制,可以在 [ 程序运行期间 ] 再确定如:对象实例化,方法调用,属性操作等

  • 反射可以提高代码的灵活性,可扩展性,但是带来了较多的系统开销和较慢的运行效率
  • 反射机制不能被过度依赖

类对象

  • 反射的第一步是获取一个类的类对象: java.lang.Class类。它的每一个实例被称为一个类的类对象。JVM每当需要使用一个类时就会加载它的.class文件(字节码文件)并同时实例化一个Class实例来记录加载的类的相关信息,并且每个被加载的类都有且只有一个Class的实例与之绑定,这个Class的实例就可以理解为是刚加载的类的类对象
  • 通过类对象可以反映出其表示的类的一切信息(类名,包信息,构造器信息,方法信息,属性信息),从而在程序运行期间进行动态调用

获取类对象的方式

  • 类名.class
Class cls = String.class;
Class cls = int.class;

注意:基本类型仅有上述这种方式可以获取

  • Class.forName(“类的完全限定名”) ---- 完全限定名:包名.类名
Class cls = Class.forName("java.lang.String");
Class cls = Class.forName("java.util.ArrayList");
  • 对象.getClass()
Class cls = o.getClass()   //o可以是任意对象,比如o是String实例,那么就可以获取String的类对象
  • ClassLoader.loadClass(“类的完全限定名”)
    通过类加载器来获取类对象

反射机制实例化对象

  • Class提供的newInstance方法可以使用无参构造器实例化对象
    • 类必须提供公开且无参的构造器
    • 无法抛出构造器实际抛出的异常,只能是:InstantiationException
    • 在JDK9中不再建议使用
Class cls =Class.forName("java.lang.String");
Object obj = cls.newInstance();//必须有无参构造器
  • 使用指定的构造器实例化对象,Constructor类:
    • 该类的每一个实例用于反映一个类中某个指定的构造器
    • 通过构造器对象可以得知其表达的构造器的相关信息:访问修饰符,参数列表等并且可以通过该构造器实例化对象
Class cls = Class.forName("reflect.Person");

//Person()
Constructor c = cls.getConstructor();	//获取无参构造器
c.newInstance();						//new Person();

//Person(String)
Constructor c = cls.getConstructor(String.class);
Object obj = c.newInstance("王五");
System.out.println(obj);

//Person(String,int)
Constructor c2 = cls.getConstructor(String.class,int.class);
Object obj2 = c2.newInstance("赵六", 20);
System.out.println(obj2);

变长参数

JDK5之后Java提供了的特性

  • 一个方法只能有一个变长参数
  • 变长参数只能是方法的最后一个参数
  • 变长参数是编译器认可的,最终编译后会被改为数组
package reflect;

/**
 * JDK5之后Java提供了一个特性:变长参数
 */
public class ArgsDemo {
    public static void main(String[] args) {
        /*
        doSome(1, new String[]{});
        doSome(1, new String[]{"one"});
        doSome(1, new String[]{"one", "two"});
        doSome(1, new String[]{"one", "two", "three"});
        doSome(1, new String[]{"one", "two", "three", "four"});
        doSome(1, new String[]{"one", "two", "three", "four", "five"});
         */
        doSome(1);
        doSome(1, "one");
        doSome(1, "one", "two");
        doSome(1, "one", "two", "three");
        doSome(1, "one", "two", "three", "four");
        doSome(1, "one", "two", "three", "four", "five");
    }

    // public static void doSome(int a, String[] args) {
    public static void doSome(int a, String... args) {
        System.out.println(args.length);
    }
}

反射机制调用方法 ( Method 类 )

Method类:反射对象之一,它的每一个实例用于表示一个类中的某个方法通过Method对象可以反应出该方法的相关信息:

  • 访问修饰符,返回值类型,方法名,参数信息等
  • 可以通过方法对象执行这个方法

获取 Method

  • Method getMethod(String name, Class… parameterTypes)
    获取指定公共方法,可获取从超类继承的内容
Class cls = Class.forName("reflect.Person");
Method method = cls.getMethod("say",String.class,int.class);
  • Method getMethod(String name, Class… parameterTypes)
    获取本类自己定义的相关内容,包含私有成员,不获取从超类继承的内容
Method method = cls.getDeclaredMethod("hehe");	//私有方法
  • Method[ ] getMethods()
    获取一个类所有公开方法
    • 不含私有方法
    • 包含从超类继承的方法
Class cls = Class.forName("reflect.Person");
Method[] methods = cls.getMethods();
  • Method[ ] getDeclaredMethods()
    获取方法参数个数
    • 包含私有方法
    • 不包含从超类继承的方法
Class cls = Class.forName("reflect.Person");
Method[] methods = cls.getDeclaredMethods();

Method 提供的方法

方法 作用
String getName() 获取方法名
int getModifiers 获取方法的修饰符
int getParameterCount() 获取方法参数个数
Class getReturnType 获取方法返回值类型
Class[ ] getParamerTypes() 获取方法的参数列表
Object invoke(Object obj,Object… args) 执行该方法并获取返回值(如果有)


public class ReflectDemo6 {
    public static void main(String[] args) throws Exception {
    
        //查看Person类中say(String,int)的相关信息
        Class cls = Class.forName("reflect.Person");
        Object obj = cls.newInstance();
        Method method = cls.getMethod("say",String.class,int.class);
        
        //获取方法名
        String name = method.getName();
        System.out.println("方法名:"+name);
        
        //获取参数列表
        Class[] parametersTypes = method.getParameterTypes();
        for(Class pt : parametersTypes){
            System.out.println("参数:"+pt);
        }
        
        //获取参数的个数
        int parameterCounter = method.getParameterCount();
        System.out.println("参数的个数:"+parameterCounter);
        
        //获取返回值类型
        Class returnType = method.getReturnType();
        System.out.println("返回值类型:"+returnType);
        
        //获取访问修饰符
        int modifiers = method.getModifiers();
        switch (modifiers){
            case Modifier.PUBLIC:
                System.out.println("访问修饰符:"+"public");
                break;
            case Modifier.PRIVATE:
                System.out.println("访问修饰符:"+"private");
                break;
            case Modifier.PROTECTED:
                System.out.println("访问修饰符:"+"protected");
                break;
            default:
                System.out.println("访问修饰符:"+"default");
                break;
        }

		//执行该方法
        method.invoke(obj,"hello",3);
    }
}

反射机制操作属性 ( Field 类 )

Field也是反射对象之一,用于表示类中的某个属性,很少会直接操作属性,因为属性建议都是私有的

  • getField(String name)
    获取一个类指定名字的公开属性
  • void set(Obiect obj,Object value)
    为指定属性设置值
public static void main(String[] args) throws Exception{
        Teacher t = new Teacher();
        t.name = "李老师";
        System.out.println(t.name);

        Class cls = Class.forName("reflect.Teacher");
        Object obj = cls.newInstance();
        /*
            Filed也是反射对象之一,Field的每一个实例表示的是一个属性的信息
         */
        Field field =cls.getField("name");	//获取Teacher中的name属性
        field.set(obj,"王老师");				//obj.name = "王老师"
        System.out.println(obj);
        System.out.println(field.get(obj));	//System.out.println(obj.name)
    }

暴力反射

像Field,Constructor,Method都提供了一个方法:setAccessible()。
该方法如果传入true,可以强行打开访问权限,用于访问本不可访问的内容。比如私有方法是不能在类的外部访问的,但是通过调用对应的Method对象的setAccessible方法可以强制打开访问权限

public static void main(String[] args) throws Exception {

        String a = "abc";			//字面量创建的字符串对象会缓存在常量池
        String b = "abc";			//重用a对象
        System.out.println("a:"+a);	//a:abc
        System.out.println("b:"+b);	//b:abc

        Class cls = String.class;	//Class cls = Class.forName("java.lang.String")
        //获取对象a内部的value属性重新赋值
        Field field = cls.getDeclaredField("value");
        //打开访问权限的同时也破除了final的限制
        field.setAccessible(true);	//暴力反射,打开访问权限
        field.set(a,new char[]{'h','e','l','l','o'});	//a.value = new char[]{'h','e','l','l','o'};

        System.out.println("a:"+a);	//a:hello
        System.out.println("b:"+b);	//b:hello

        String c = "abc";			//依然重用常量池中a对象
        System.out.println("c:"+c);	//c:hello
    }

注解

注解(Annotation)是一种元数据(metadata)机制,可以使用注解来为代码中的各个部分添加额外
的信息,以帮助程序的编译、运行或者其他处理过程

  • 注解功能一般都是使用Java反射API解析实现
  • Java注解为Java开发带来了很多好处,可以提高代码的可读性、可维护性和可靠性,从而使开发变得更加高效和轻松

注解的定义

  • 注解必须先定义后使用
  • 定义注解的语法:
//		关键字		注解名
public @interface AutoRunClass {}

注解的应用

  • 为代码提供元数据信息,例如文档、版本号等
  • 为编译器提供指示,例如抑制警告、生成代码等
  • 为运行时提供指示,例如启用事务、配置参数等

注解可以在类的各个组成部分上使用,常见的位置:

  • 类上使用
  • 方法上使用
  • 构造器上使用
  • 属性上使用
  • 方法参数上使用
  • 在不限定注解使用位置时,任何可以被应用的地方都可以使用注解

元注解

@Target:用于标注当前注解可以被应用的位置.它的值对应枚举类:ElementType

  • ElementType.TYPE 类上,接口上使用
  • ElementType.FIELD 在属性上使用
  • ElementType.METHOD 在方法上使用
  • ElementType.CONSTRUCTOR 在构造器上使用
  • ElementType.PARAMETER 在参数前使用
  • ElementType.LOCAL_VARIABLE 在局部变量上使用
  • ElementType.ANNOTATION_TYPE 在其他注解上使用
  • ElementType.PACKAGE 在包上使用,通常需要在 package-info.java 文件中使用
  • ElementType.TYPE_PARAMETER 在类型参数(泛型类型)上使用
  • ElementType.TYPE_USE 在任何使用类型的地方(类型转换、泛型类型参数等)使用

@Retention:表示当前注解的保留级别,可选项是使用枚举RetentionPolicy表示的

  • RetentionPolicy.SOURCE :当前注解近保留在源代码中,编译后的字节码文件中没有该注解
  • RetentionPolicy.CLASS :注解可以保留在字节码文件中,但是不能为反射机制访问
  • RetentionPolicy.RUNTIME :该注解保留在字节码文件中且可以被反射机制访问
@Target({ElementType.TYPE,ElementType.PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoRunClass {
}

注:在反射机制使用时必须是RUNTIME级别

boolean isAnnotationPresent(Class cls)

所有反射对象都提供了一个方法: isAnnotationPresent(Class cls)用于判断反射对象表示的内容是否被指定的注解标注,参数为一个类对象,表示指定的注解的类对象

//获取Person的类对象
Class cls = Class.forName("reflect.Person");
//通过类对象判断是否被特定注解标注
boolean mark = cls.isAnnotationPresent(AutoRunClass.class);
System.out.println(mark?"被标注了":"没有被标注");

常见的反射对象:

  • Class 它的每一个实例用于反映一个类的相关信息
  • Package 它的每一个实例用于反映一个包的信息
  • Method 每个实例反映一个方法的信息
  • Field 反映一个属性的信息
  • Constructor 反映一个构造器的信息
  • Parameter 反映一个参数的信息

注解参数

注解可以指定参数,格式:类型 参数名() [DEFAULT 默认值]
使用注解传递参数时,使用的格式为: 参数名=参数值

当使用注解时,传参方式:
@AutoRunMethod(参数1名=参数1值,参数2名=参数2值,…)
注意:参数的顺序可以与定义时不一致

//以两个参数为例:
public @interface AutoRunMethod {
	int num();
	String name();
}

//实际使用时,传参可以:
@AutoRunMethod(num=1,name="张三")
@AutoRunMethod(name="张三",num=1)

有默认值时

public @interface AutoRunMethod {
	int num() default 1;
	String name();
}

//实际使用时可以忽略有默认值的参数,此时该参数使用默认值,传参可以:
@AutoRunMethod(name="张三")

当注解只有一个参数时(或者其他参数都有默认值时),参数名使用 value ,那么使用时可以忽略参数名

public @interface AutoRunMethod {
	int value();
}

//实际使用时,传参可以:
@AutoRunMethod(1) 	//使用时可以忽略参数名

Annotation getAnnotation(Class cls)

Annotation getAnnotation(Class cls)

  • 返回当前指定对象表示的内容指定的注解
  • 参数:指定注解所对应的类对象
  • 所有反射对象都支持获取其表示内容上指定的注解
@AutoRunMethod(5)
public void sayHello() {
	System.out.println(name + ":hello!");
}

//获取Person类中sayHello()方法上的注解@AutoRunMethod中的value参数的参数值
public static void main(String[] args) throws Exception {
	Class cls = Class.forName("reflect.Person");
	Method method = cls.getMethod("sayHello");
	if(method.isAnnotationPresent(AutoRunMethod.class)){
		AutoRunMethod arm = method.getAnnotation(AutoRunMethod.class);
		int value = arm.value();
		System.out.println("参数value的值为:"+value);
	}
}

你可能感兴趣的:(java,开发语言)