zoukankan      html  css  js  c++  java
  • 注解和反射整理【抄的方便个人查看】

    注解和反射整理

    一、注解

    JDK元注解

    java.lang下提供了5个基本的基本注解,分别是@Retention、@Target、@Documented、@Inherited,以及Java8新增的@Repeatable注解、类型注解(指定注解可以用于任何地方)。

    @Retention注解

    指定注解可以保留多长时间,包括一个RetentionPolicy(枚举)类型的value成员变量。

    • RetentionPolicy.Class: 将注解记录在class文件中,运行java程序时,JVM不可获得注解信息,为默认。
    • RetentionPolicy.RUNTIME: 编译器将注解记录在calss文件中,JVM可以获得注解信息(用的最多)
    • RetentionPolicy.SOURCE: 注解只保留在源代码中。

    @Target注解

    此注解指定被修饰注解能用于哪些单元,如是用在方法还是用在类或者参数上。

    • **ElementType.ANNOTATION_TYPE:**修饰注解
    • ElementType.CONSTRUCTOR:只能修饰构造器
    • **ElementType.FIELD:**修饰成员变量
    • ElementType.LOCAL_VARIABLE:只能修饰局部变量
    • ElementType.METHOD:修饰方法
    • ElementType.PACKAGE:修饰包定义
    • **ElementType.PARAMETER:**修饰参数
    • ElementType.TYPE:可以修饰类、接口(包括注解类型)或枚举定义。

    @Documented(个人感觉不重要)

    @Documented(指定注解具有继承性)

    自定义注解

    定义及使用注解

    直接上例子吧:自定义注解只能使用在成员变量上,使得其能初始化成员变量。

    //1.定义@PersonAnno注解,这个注解只能应用于成员变量上。
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface PersonAnno {
    	String name();
    	int age();
    }
    
    //2.提供了Person类,这里省略了
    public String name;
    	public int age;
    	public String getName() {
    		return name;
    	}
    	//set,get,toString方法....
    }
    
    //主类
    public class Demo {
        /*添加注解,指定属性值,这里只这样写肯定是不能初始化成员变量,因为注解是不能自己生效的,我们必须
        提供一个工具告诉这个注解是如何工作的,这个方法如下doAnno()方法。
        */
    	@PersonAnno(age = 20, name = "张三")
    	private Person person=new Person();
    	private Person getPerson() {
    		return person;
    	}
        
    	public static void main(String[] args) throws Exception {
    		Demo demo = new Demo();
    		demo.doAnno();
    		Person person = demo.getPerson();
    		System.out.println(person);
    	}
        
    	private void doAnno() throws Exception{
    		//注意使用getDeclaredField才可以获得私有成员变量值。
            //注意全限定类名
    		PersonAnno annoInfo = Class.forName("org.jcut.day04.Demo").getDeclaredField("person").getAnnotation(PersonAnno.class);
            //得到这个注解的两个属性值赋值给成员变量。
    		int age = annoInfo.age();
    		String name = annoInfo.name();
    		person.setAge(age);
    		person.setName(name);
    	}
    }
    
    • 1

    提取注解信息

    当自定义了注解后,这些注解不会自动生效,必须由我们开发人员提供相应的工具类来提取和处理注解信息,如上例doAnno()方法。

    java.lang.reflect包下新增了AnnoatatedElement接口,并且Class,Method,Constructor等都实现了这个接口,所以它可以通过所提供的如下方法访问注解信息:

    • getAnnoatation(Class annnotationClass)
    • getDeclaredAnnnotation(Class annotationClass)
    • Annotation[] getAnnotations()

    Java8新增的重复的注解@Repeatable

    直接看怎么使用吧(这个例子是网上粘贴的https://blog.csdn.net/weixin_42245133/article/details/99678509:

    //定义Values注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Values {
        Value[] value();
    }
    
    //定义要重复使用的注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Repeatable(Values.class)
    public @interface Value {
        String value() default "value";
    }
    
    //使用方法
    public class AnnotationClass {
    
        @Value("hello")
        @Value("world")
        public void test(String var1, String var2) {
            System.out.println(var1 + " " + var2);
        }
    }
    
    //测试用例
    // 获取使用`@Value`注解的`test`方法,并打印这个方法上的注解长度和信息
        @Test
        public void testValue() {
            Method[] methods = AnnotationClass.class.getMethods();
            for (Method method : methods){
                if (method.getName().equals("test")) {
                    Annotation[] annotations = method.getDeclaredAnnotations();
                    System.out.println(annotations.length);
                    System.out.println(method.getName() + " = " + Arrays.toString(annotations));
                }
            }
        }
    
    //运行结果:
    1
    test = [@com.example.annotations.Values(value=[@com.example.annotations.Value(value=hello), @com.example.annotations.Value(value=world)])]
    
    
    • 1

    结果显示,test方法上的注解长度为 1 , 且打印信息为@Values注解,它的值包含了使用的两个注解。
    因此可知在jdk8中,相同注解只是以集合的方式进行了保存,原理并没有变化。

    Java8新增的类型注解

    ElementType枚举增加了TYPE_PARAMETER,TYPE_USER两个枚举值,如定义的时候使用@Target(ElemntType.TYPE_USER)修饰,这种注解被称位类型注解,类型注解可以用于修饰在任何地方出现的任何类型。

    二、反射

    通过反射查看类信息

    1.通过注反射获得Class对象

    获得Class对象的方式通常有三种:

    • 使用Class.forName(String className)静态方法。className为全限定类名。
    • 调用某个类的class属性。如Person.class.
    • 调用某个对象的getClass()方法。

    相比之下,第二种方式更有优势:

    • 代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在。
    • 程序性能更好,因为无需调用方法,所以性能更好。

    2.从Class中获取信息

    Class类提供了大量的实例方法来获取Class对象所对应的详细信息,详细查看API文档。

    3.Java8新增的方法参数反射

    Java8在反射包下新增了Executable抽象基类,其提供的方法甚至可以获得形参名。如isNamePresent()方法判断生成的class文件是否包含方法的形参名信息。getName()获得形参名。

    需要注意的是:使用javac命令编译的源文件的时候,默认生成的class文件并不包含方法的形参名信息,因此调用isNamePresent()方法时返回false,调用getName()是也不能得到参数的形参名,所以在编译的时候需要为编译命令指定-parameters指令时才可以生成形参信息。

    使用反射生成并操作对象(重点)

    1.创建对象

    在很多JavaEE框架中都需要根据配置文件信息来创建对象,从配置文件读取的知识某个类的字符串类名,程序需要根据该字符串来通过反射来创建对应实例。

    下例实现了从配置文件中读取key-value对,然后创建对象并将对象放到HashMap中:

    public class ObjectPoolFactory {
    	private HashMap<String, Object> map=new HashMap<>();
    	private static Properties prop=new Properties();
    	static {
            //生成流文件
    		InputStream resource = ObjectPoolFactory.class.getClassLoader().getResourceAsStream("a.properties");
    		prop=new Properties();
    		try {
                //加载资源文件
    			prop.load(resource);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		
    	}
    	
    	//初始化对象池
    	public void initPool() throws Exception {
    		Set<String> stringPropertyNames = prop.stringPropertyNames();
    		for (String string : stringPropertyNames) {
    			map.put(string, createObject(prop.getProperty(string)));
    		}
    	}
    
    	//创建对象实例
    	private Object createObject(String property) throws Exception {
    		Class<?> class1 = Class.forName(property);
    		//调用该类构造器再使用newInstance创建实例对象。
    		return class1.getConstructor().newInstance();
    	}
    	
    	//获取实例对象
    	private Object getObject(String name){
    		//从map中获取指定name的实例。
    		Object object = map.get(name);
    		return object;
    	}
    	
    	public static void main(String[] args) throws Exception {
    		ObjectPoolFactory factory=new ObjectPoolFactory();
    		factory.initPool();
    		Object object = factory.getObject("a");
    		Object object2 = factory.getObject("b");
    		System.out.println(object);
    		System.out.println(object2);
    	}
    }
    
    • 1

    另外也可以使用指定构造器来创建java对象。

    如上例中Date类为已知类,通常情况下没有使用反射创建该种实例,毕竟通过反射创建对象时性能要稍微低一些。实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,所以在通用性框架中大量使用。

    2.调用方法

    当获得了某个类对应的Class对象,就可以通过该Class对象的getMethods()方法或者getMethod()方法获得全部或者指定的方法。

    Method里包含一个invoke()方法,Object invoke(Object obj,Object args):obj为执行该方法的主调,args是执行该方法的时传入的实参。

    上例子(这个例子直接粘贴书上的):加强上面的一个例子,(在配置文件中设置一个值,这个值由方法读取到并且利用该类去为对象对应的setter方法设置值)

    public class ExtendedObjectPoolFactory {
    
    	// 定义一个对象池,前面是对象名,后面是实际对象
    	private Map<String, Object> objectPool = new HashMap<>();
    	private Properties config = new Properties();
    
    	// 从指定属性文件中初始化Properties对象
    	public void init(String fileName) {
    		try {
    			InputStream resource = ObjectPoolFactory.class.getClassLoader().getResourceAsStream(fileName);
    			config.load(resource);
    		} catch (IOException ex) {
    			System.out.println("读取" + fileName + "异常");
    		}
    	}
    
    	// 定义一个创建对象的方法
    	// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
    	private Object createObject(String clazzName) throws Exception {
    		// 根据字符串来获取对应的Class对象
    		Class<?> clazz = Class.forName(clazzName);
    		// 使用clazz对应类的默认构造器创建实例
    		return clazz.getConstructor().newInstance();
    	}
    
    	// 该方法根据指定文件来初始化对象池
    	// 它会根据配置文件来创建对象
    	public void initPool() throws Exception {
    		for (String name : config.stringPropertyNames()) {
    			// 每取出一个key-value对,如果key中不包含百分号(%)
    			// 这就表明是根据value来创建一个对象
    			// 调用createObject创建对象,并将对象添加到对象池中
    			if (!name.contains("%")) {
    				objectPool.put(name, createObject(config.getProperty(name)));
    			}
    		}
    	}
    
    	// 该方法将会根据属性文件来调用指定对象的setter方法
    	public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
    		for (String name : config.stringPropertyNames()) {
    			// 每取出一对key-value对,如果key中包含百分号(%)
    			// 即可认为该key用于控制调用对象的setter方法设置值
    			// %前半为对象名字,后半控制setter方法名
    			if (name.contains("%")) {
    				// 将配置文件中的key按%分割
    				String[] objAndProp = name.split("%");
    				// 取出调用setter方法的参数值
    				Object target = getObject(objAndProp[0]);
    				// 获取setter方法名:set + "首字母大写" + 剩下部分
    				String mtdName = "set" + objAndProp[1].substring(0, 1).toUpperCase() + objAndProp[1].substring(1);
    				// 通过target的getClass()获取它的实现类所对应的Class对象
    				Class<?> targetClass = target.getClass();
    				// 获取希望调用的setter方法
    				Method mtd = targetClass.getMethod(mtdName, String.class);
    				// 通过Method的invoke方法执行setter方法
    				// 将config.getProperty(name)的值作为调用setter方法的参数
    				mtd.invoke(target, config.getProperty(name));
    			}
    		}
    	}
    
    	public Object getObject(String name) {
    		// 从objectPool中取出指定name对应的对象
    		return objectPool.get(name);
    	}
    
    	public static void main(String[] args) throws Exception {
    		
    		ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
    		epf.init("extObj.properties");
    		epf.initPool();
    		epf.initProperty();
    		System.out.println(epf.getObject("a"));
    	}
    }
    
    
    //配置文件内容:
    /*
         a=javax.swing.JFrame
        b=javax.swing.JLabel
        a%title=Test Title
    */
    
    • 1
    • 2

    书上所说结果是输出了一个JFrame窗口,该窗口标题为Test Title,但是我运行的话只打印了这个JFrame对象,并且标题也设置了,如下:

    javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,title=Test Title,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
    
    • 1

    访问成员变量的值

    通过Class对象getFields()或getField()方法可以获取该类所包含的所有成员变量或指定成员变量的值。

    如下例子通过设置和访问成员变量,包括私有成员变量。

    class Person
    {
    	private String name;
    	private int age;
    	public String toString()
    	{
    		return "Person[name:" + name +
    		" , age:" + age + " ]";
    	}
    }
    public class FieldTest
    {
    	public static void main(String[] args)
    		throws Exception
    	{
    		// 创建一个Person对象
    		Person p = new Person();
    		// 获取Person类对应的Class对象
    		Class<Person> personClazz = Person.class;
    		// 获取Person的名为name的成员变量
    		// 使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
    		Field nameField = personClazz.getDeclaredField("name");
    		// 设置通过反射访问该成员变量时取消访问权限检查
    		nameField.setAccessible(true);
    		// 调用set()方法为p对象的name成员变量设置值
    		nameField.set(p , "Yeeku.H.Lee");
    		// 获取Person类名为age的成员变量
    		Field ageField = personClazz.getDeclaredField("age");
    		// 设置通过反射访问该成员变量时取消访问权限检查
    		ageField.setAccessible(true);
    		// 调用setInt()方法为p对象的age成员变量设置值
    		ageField.setInt(p , 30);
    		System.out.println(p);
    	}
    }
    
    结果:
    Person[name:Yeeku.H.Lee , age:30 ]
  • 相关阅读:
    短时间内点击 同一操作,进行缓存
    git项目如何查看代码提交数量
    Python计数器collections.Counter用法详解
    SQL Server如何生成随机数
    博客园美化 | part04-添加代码复制按钮
    博客园美化 | part03-添加目录
    博客园美化 | part01-自定义代码块主题样式
    博客园美化 | part02-添加文章markdown链接一键复制按钮
    hexo | leancloud相关问题: Code 403: Access denied by API domain white list,Please check your security domain.
    关于分类和标签管理问题
  • 原文地址:https://www.cnblogs.com/yibuyi-123/p/14422584.html
Copyright © 2011-2022 走看看