注解和反射整理【抄的方便个人查看】

注解和反射整理

一、注解

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 ]
posted @ 2021-02-20 17:58  易不易  阅读(48)  评论(0编辑  收藏  举报