java.lang.reflect,反射,Java.lang.Class,注解 2021.1.6

每日心得

前面两天的内容有点多,还没整合好,所以先把今天的完成了,二十二就放假了,会放一个月,放得有点早了,时间也久了,所以本来几个月的学习时间变得更加紧张了,老师都说应该很难讲完了,老师还说年后可能会辞职,原因有很多吧,真不希望他走,最少也要带完我们这一届吧。。。

Reflection API(反射)

一般用于框架里,较为高级的api,一般增删改查用不到。

java.lang.reflect(反射包)

在类名等未知的情况下,也能够去调用类的方法,构造方法,属性。

其优点是,当类符合某种规范,都能统一进行调用。

java.lang.Class

这个类的实例是一个类在java中运行的信息的包装,可以通过class获取很多信息,注解,类名泛型等。当类是别人传过来的,你未知的,不知道里面的属性内容,就可以使用这个类来进行信息的获取。

枚举-->类

数组-->类

注解-->接口

void-->class object

基本数据类型-->类

Class没有具体的构造方法,由JVM虚拟机自动生成,当这个类加载到JVM时通过类加载器(类加载器,把你预定好的class类加载到内存里,正常情况下放在硬盘(副存)中无法与cpu进行运算,需要内存作媒介;通过类加载器加载的过程中,会调用一个defineClass(定义类)方法,调用后就会在内存形成这个类,这里要注意一点,当你第一次调用一个x类,将类加载到内存中之后,如果你第二次又调用这一个x类,这个时候不会在加载到内存中了,因为内存中已经有了,这个与JVM中的方法区相关,这里不深究。)

都有getClass这个方法,因为时定义在Object类里面的。

.getClass();//final,返回一个运行时的class

使用类的全名再.getClass(),也能获取到这个对象

Student stu=new Student();
Class clazz=stu.getClass();
System.out.println(clazz.getName());//输出类的全名
print("abc");

void print(Object obj){
Class clazz = obj.getClass();
System.out.println(clazz.getName());//输出类的全名java.lang.String
 }

Class clazz2=stu.getClass();
Class clazz=Student.class;
System.out.println(clazz==clazz2);//结果为true,为同一个类

int[]array=new int[5];
int[]array2=new int[6];
double[] array3=new double[3];
System.out.println(array==array2);//结果为true,都为整形数组int[]
System.out.println(array==array3);//括号中会报错,编译不通过,无法比较
//Class clazz3=int[].class;


Class clazz=int.class;
System.out.println(clazz.getName());//结果为int,基本数据类型的class为本身

.forName(类的全名);加载类,手动使用类加载器

Class clazz2=Class.forName("java.lang.String");//会需要捕获一个类名找不到的异常
Class clazz=String.class;
System.out.println(clazz==clazz2);//true

获取属性

Class clazz=Student.class;
Field[]fs1=clazz.getFields();//返回所有的包括父类的public属性
clazz.getField("");//范围是所有的包括父类的public属性,通过名称指定获取
Field[]fs2=clazz.getDeclaredFields();//返回当前类所以声明的属性
clazz.getDeclaredField("");//范围是当前类所以声明的属性,通过名称指定获取

//通过属性名称拿到其所用注解与注解的值
Fild name=clazz.getDeclaredFied("name");
MyAnnotation my = (MyAnnotation)name.getAnnotation(MyAnnotation.class);
Systm.out.println(my.aa());
//根据属性获取其类型
Field name=clazz.getDeclaredField("gender");
System.out.println(name.getType());//获取属性类型

//获取修饰符
int mod =name.getModifiers();//该方法内部通过位运算获得一个整形表示类型
System.out.println(Modifier.isPrivate(mod));//结果为ture

获取方法,构造方法

Class clazz=Student.class;
//普通获取一个方法需要获取类名,方法名,形参列表
clazz.getMethods();//获取所有public方法
clazz.getMethod("setName",parameterTypes);//parameterTypes可变长度参数,放最后,形参或者形参列表
Method m =clazz.getMethod("setName",String.class);//获取单个方法
Method[] m =clazz.getMethods();
clazz.getDeclaredMethods()//获取所以有的方法
m.getReturnType().getName();//获得返回类型
System.out.println(m.getReturnType()==void.class);//判断是不是方法,没有返回类型的即void的

//获取构造方法
clazz.getConstructors();//获取全部的构造方法
Constructor c=clazz.getConstructor(String.class,int.class);//直接传形参,不需要传名称
Student stu=(Student)c.newInstance("xxxx",123);//实例化

获取注解

Class clazz=Studnet.class;
Annotation[] annos=clazz.getAnnotations();
System.out.println(annos.length);//1
Annotation myanno =annos[0];
System.out.println(myanno.getClass().getName());//返回的并不是注解名,而是动态代理的类,因为注解是接口,不能直接拿来使用,所以虚拟机会生成一个动态代理的类
if(myanno.instanceof MyAnnotation){//拿到的这个实例其实是MyAnnotation的一个实现类,所以可以使用这种方法间接判断
    System.out.println("myanno is a MyAnnotation...")
        MyAnnotation my =(MyAnnotation)myanno;//进行强转
    	System.out.println(my.属性名)//可以拿到注解中的属性值
}


System.out.println(myanno.annotationType()==MyAnnotation.class);//结果为true
myanno.annotationType().getAnnotations();//获取这个注解上面的注解

//如果要更直接一点的获取注解与里面的属性值
MyAnnotation my =(MyAnnotation)clazz.getAnnotations(MyAnnotation.class);
System.out.println(my.属性名)

获取接口,父类,泛型

clazz.getSuperClass();//父类只有一个,若是没有父类,默认为object类
class[] inter=clazz.getIntrerfaces();//接口有多个

//获取<>泛型必须固定其类型,不能获取类似<T>这种不确定的
TypeVariable[] tvs=clazz.getTypeParameters();
System.out.println(tvs[0].getNames);

反射(很强大的工具)

通过反射使用属性

Student stu1 = new Student();
stu1.setName("xxxx");
name.setAccessible(true);//因为设置为private私有的,所以默认没有访问权限,临时强行转为可访问
name.set(stu1,"ww");//设值
System.out.println(name.get(stu1));//不能直接访问私有属性,但是能通过第三行代码将权限改为可访问,获得这个name

通过反射使用方法,静态方法

Method m =clazz.getMethod("setName",String.class);
m.invoke(stu,"xxxx");//使用stu的setName()方法
System.out.println(stu.getName());//输出xxxx
Menthod getMethod = clazz.getMethod("getName",null);
Object retobj = getMethod.invoke(stu,null);//使用stu的getName方法,没有形参,但是有返回值,因为接收不确定,所以用object来接收

//静态方法
Class clazz=Student.class;
Method m =clazz.getMethod("test",null);
m.invoke(clazz,null);
Modifier.isStatic(m.getModifiers());//可通过这个判断决定是在invoke方法中放入类名还是实例

反射会对OOP造成破坏,因为可以突破各种规则,也可以破坏泛型

原理,绕过了编译器,所以没有被约束,运行时泛型会被擦除,反射是在运行时插入;

List<Stirng>list= new ArrayList<>();
list.add("abc");
//list.add(111);编译器不会通过

Class clazz=list.getClass;
Method addMethod = clazz.getMethod("add",Object.class);
addMethod.invoke(list,1111);

for(Obejct obj:list){
    System.out.println(obj);//结果为abc,111
}

类为什么没有地址,只通过类名称就可以加载类,是因为类加载器通过工程自动生成的classpath文件去加载的class

java.lang包下的不用import的,因为比较常用,默认加载。

Class Loader类加载器类,与JVM相关,可以自定义类加载器。(后期才学)

注解(Annotation)

XML ==>HTML 标记数据 在表现层

​ ==>配置文件,数据传输

XML局限性,有开始标签与结束标签,需要缩进,不直观;

java中.properties 基于key与value,但没有XML灵活,后面又演化有了.yml;

这个时候就有了Annotation注解;

声明一个注解

@Target(ElementType.TYPE)//传入一个枚举,表示应用范围,多个范围使用({ElementType.x,ElementType.y,......})
@Rentention(@RententionPolicy.RUNTIME)//保留机制
//注解必须有上面两个标明,不然无法使用,或者没有效果
public @interface MyAnnotation{
	String value() default "sss";//deafult设置默认值,如果注解中没有设置默认值,需要在调用时给一个默认值
   	int aa();
}

@MyAnnotation(aa=123)
public class Student{
    @MyAnnotation
    private String name;
    public void setName(@MyAnnotation String name){
        
    }
}

枚举-->常量的集合,默认类型为int

@Target(应用范围)

TYPE:类,接口(包括注解类型),枚举类型;

FIELD:属性(包括枚举的常量);

METHOD:方法;

PARAMETER:形参;-->在AOP中十分有用,AOP弥补OOP的短板。

CONSTRUCTOR:构造方法;

LOCAL_VARIABLE:本地变量;

。。。。。。

@Rentention(保留机制)

SOURCE:编译器编译后,将会将注解的信息丢失,我们写的注解加这个没有用,重写编译器才有用;

CLASS:编译后,注解信息会记录在class文件中,但是JVM运行后,不会保留,默认的行为,也没有用;

RUNTIME:编译后,注解信息会记录在class文件中,是JVM运行后,将会保留,它们是可读的,通过反射。

注解的属性

String aa() default "abc";default表示默认值;

如果使用的注解有属性,每个属性必须都有默认值,可以在注解中使用default设置,也可以在使用时用括号设置:@MyAnnotation(aa="abc");

如果属性名为value,则在使用时可以不用写属性名,如下

String value();

@MyAnnotation("abc")与@MyAnnotation(value="abc")效果相同,因为value是默认值。

但如果有多个,就要加上属性名了。

题目:找出包中所以被注解标记过的类,类中被注解标记的方法;

package test1.copy;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;



public class PackageScan {
	
	public static void main(String[] args) {
		Class clazz = PackageScan.class;
//		URL url=clazz.getResource("/test1");寻找路径
//		System.out.println(url);
		Package pkg=clazz.getPackage();
		System.out.println(pkg);
		String rootpkg = pkg.getName().split("\\.")[0];//包名
		System.out.println(rootpkg);
		URL url=clazz.getResource("/"+rootpkg);
		if(url.getProtocol().equals("file")){//判断协议是否符合
		String filePath=url.getFile().substring(1);//去除斜杠,获得该扫描的路径
		System.out.println("filePath:"+filePath);
		File file=new File(filePath);
		System.out.println(url);//结果为file:/D:/project2/HighMS/bin/test1,file:这个是协议
		System.out.println(url.getFile());//协议后的文件路径
		System.out.println("有注解的类为");
		selectfile(file,rootpkg);
		System.out.println("有注解的方法为");
		selectMe(file,rootpkg);
		}
	}
	
	public static void selectfile(File file,String rootpkg){
			File[]files =file.listFiles();
			for(File f:files){
				if(f.isDirectory()){
					selectfile(f,rootpkg+"."+f.getName());
				}else if(f.isFile()){
					if(f.getName().endsWith(".class")){
						String clazzName=f.getName().substring(0,f.getName().lastIndexOf("."));//获取类名
						try {
							Class targetClazz=Class.forName(rootpkg+"."+clazzName);//将包名与类名进行拼接
							//targetClazz.getAnnotation(Component.class);通过全名获取注解
							if(targetClazz.isAnnotationPresent(Component.class)){//判断是不是这个注解类型
								System.out.println(targetClazz.getName());
							}
						} catch (ClassNotFoundException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
	public static void selectMe(File file,String rootpkg){
		File[]files =file.listFiles();
		for(File f:files){
			if(f.isDirectory()){
				selectMe(f,rootpkg+"."+f.getName());
			}else if(f.isFile()){
				if(f.getName().endsWith(".class")){
					String clazzName=f.getName().substring(0,f.getName().lastIndexOf("."));
					try {
						Class targetClazz=Class.forName(rootpkg+"."+clazzName);
						Method[] m=targetClazz.getDeclaredMethods();//通过类名获取方法
						for(Method m2:m){
							if(m2.isAnnotationPresent(Auth.class)){//查找含有该注解的方法
								System.out.println(m2.getName());
							}
						}
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

上一篇:如何理解反射?


下一篇:Latex之竖排子图subfig