注解和反射
注解入门
什么是注解
1.Annotation的作用:
不是程序本身,可以对程序作出解释。(相当注释的作用)
可以被其他程序(比如:编译器)读取。
2.Annotation的格式:
注解是以“@注释名”在代码中存在的,还可以添加一些参数值。
3.Annotation在哪里使用
可以附加在package,class,method,field等上面,相当于给他们添加额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问。
Java内置注解
@Overried:定义在java.lang.Override中,此注释只适用于修饰方法,表示一个方法声明打算重写父类中的另一个方法声明。
@Deprecated:此注解通常用来修饰方法,属性,类,表示不建议使用,或使用有危险。
@SuppressWarning:用来抑制编译时的警告信息。需要添加信息。
例:
@SuppressWarnings(“all”)
@SuppressWarnings(value={“unchecked”,“deprecation”})
元注解
元注解的作用就是负责注解其他注解,java定义了4个标准的元注解,他们被用来提供对其他annotation类型作说明。
@Target:用来描述注解使用范围
@Retention :表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE<CLASS<RUNTIME)所以一般都使用RUNTIMME。
@Documented :说明该注解将被包含在Javadoc中
@Inherited:说明子类可以继承父类中该注解
自定义注解
@interface用来声明一个注解,格式:public @interface 注解名{定义内容}。
每个方法实际上是声明了一个配置参数。
方法的名称就是参数的名称
返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)
可以通过default来声明参数的默认值
如果只有一个参数成员, 一般参数名为value
注解原始必须有值,我们定义注解元素时,经常使用空字符串,0作为默认值。
@Target({ElemntType.TYPE,ElemntType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation{
//注解参数:参数类型+参数名();
String name() default "";//设置默认值
int age() default 0;
int id() default -1;//如果默认值为-1表示不存在。
String[] schools() default {"清华","北大"}
}
class test1{
//自定义注解使用
@MyAnnotation(age=18,name="baba")
public static void test(){
}
}
反射学习
静态语言和动态语言
静态语言:运行时结构不可变的语言就是静态语言。如Java,c…Java不是动态语言,但Java可以称为”准动态语言“。即Java具有一定的动态性。
动态语言:在运行时可以改变其结构的语言:例如新的函数、对象、代码插入,已有的函数可以被删除或是其他结构上的变化。如:C#、js、PHP、Python…
Java Reflection
Reflection是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflect API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Class c = Class.forName("java.lang.String");
(JDBC的driver:类加载和导包还是不清楚)
加载完类后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的架构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以称为反射。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rKYX5HOi-1601609485904)(C:\Users\86198\AppData\Roaming\Typora\typora-user-images\image-20200929194411522.png)]
反射的作用:判断所属的类、创建对象、获取泛型、处理注解、动态代理(AOP的基础)
Java反射的优点:可以实现动态创建对象和编译,体现出很大的灵活性。
Java反射的缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM我们的操作。这类操作通常比较慢。
Class 类
在Object类中定义了以下方法。
public final Class getClass()
以上的方法返回值的类型是一个Class类,是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解。每一个类只有一个Class对象
特点:
Class本身也是一个类
Class对象只能由系统创建
一个加载的类在JVM中只会由一个Class实例
一个Class对象对应JVM一个.class文件
每个类的实例都可以获取它的Class
常用方法:
static ClassforName(String name)返回指定类名name的Classu对象
Object newInstance()调用缺省构造函数,返回Class对象的一个实例
getName()返回此Class对象所表示的实体的名称
Class getSuperClass()返回当前Class对象父类的Class
Class[] getinterfaces()获取当前Class对象的接口Class
ClassLoader getClassLoader()返回该类的类加载器
Construct[] getConstructors()返回包含某些Construct对象的数组
Method getMothed(String name,Class… T)返回一个Method对象,此对象的形参为paramType
Field[] getDeclaredFields()返回Field对象的一个数组
//方式一:通过对象获取
Class c1 = person.getClass();
//方式二:forname获取
Class c2 = Class.forName("com.fbw.pojo.Student");
//方式三:通过类名.Class获取
Class c3 = Student.class;
//方式四:基本内置类型的包装类的Type属性
Class c4 = Integer.TYPE;
/*源码解读
* The {@code Class} instance representing the primitive type
* {@code int}.
*
* @since JDK1.1
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
*/
//获取父类类型
Class c5 = c1.getSuperclass();
哪些类型可以有Class对象
注意几个特别的:interface、[]数组、enum枚举、annotation注解。
测试:
结果:
注意:
Java内存分析
当程序主动使用某个类时,如果该类还未被加载到内存,则系统会通过如下步骤进行初始化。
**加载:**将class文件字节码内容加载到内存,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个Class对象。
**链接:**将Java类的二进制代码合并到JVM的运行状态中的过程。
验证:确保加载的类信息符合JVM规范,没有安全问题。
准备:正式为类变量分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化:
执行类构造器<clinit>()方法过程。这个方法是由编译期自动收集类中所有类变量(static)的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造对象的)
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先初始化父类。
虚拟机会保证一个类的<clinit>()方法在多线程中被正确加锁和同步。
分析类初始化
类的主动引用(一定会发生类的初始化)
类的被动引用(不会发生类的初始化)
当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
(在编译链接初始化时,静态的东西已经加载)
通过数组定义类引用,不会触发此类的初始化
(开辟空间)
引用常量不会触发此类的初始化
(常量在编译链接时,已经加载)
类加载器的作用
类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转会为方法区的运行时数据结构,然后在堆中生成Class的对象,作为方法区中类数据的访问入口。
类缓存:(不常听到)标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持一段时间(缓存)。不过JVM垃圾回收机制可以回收这些Class对象。
类加载器作用是用来把类装载进内存。JVM规范定义了如下类型。
总结:Java的核心类库是由Bootstap Classloader(根加载器),根加载器是由c/c++编写的,Java无法直接获取它的对象。用户自定义类是由System Classloader(系统加载器)加载的。
机制:
双亲委派机制,类加载是由上自下的,当父类可以加载时就不会给子类加载。例如:自定义一个String类是无法被加载的。
创建运行时类的完整结构
Class c1 = Class.forName("com.fbw.test01");
//获取Class对象
c1.getName()//获取包名+类名
c1.getSimpleName()//获取类名
/*****************************************************/
//属性获取
File[] fields = c1.getFileds()//获取public属性
fields = c1.getDeclaredFields()//找到全部属性
Field name = c1.getDeclaredField("name")
/*****************************************************/
//方法获取
Method[] methods = c1.getMethods()//获取本来及其父类的全部public方法
methods = c1.getDeclaredMethods()//获得本类的所有方法
c1.getMethod("getName",null)//获取指定方法,指定参数类型的Class
c1.setMethod("setName",String.calss)//参数的类型的Class
//重载
/*****************************************************/
//构造器获取
Constructor[] constructors = c1.getConstructors()
//获取public的构造方法
constructors = c1.getDeclaredConstructors()
//获取全部的构造方法
Constructor constructor = c1.getDeclaredConstructor(String.calss,int.class)
//获取指定class,传入参数类型Class
反射创建对象
创建类的对象:调用Class对象的newInstance()方法
类必须有一个无参构造器
类的构造器的访问权限必须足够
Class c1 = Class.forName("");
User user = (User)c1.newInstance();//本质上是调用了类的无参构造
//如果没有无参构造器就会报错
创建类的对象:当上一种条件无法满足,调用getDeclaredConstructor(Class …)取得本类的指定形式构造器
通过Construct实例化对象
Constructor constructor = c1.getDeclaredConstructor(String.class);
User user2 = constructor.newInstance("String");
通过反射调用普通方法
Method setName = c1.getDeclaredMethod("setName",String.class);
setName.invoke(user2,"name");
注意:invoke方法,传入调用的对象,传入方法 参数。返回值为调用方法的返回值,需要强转。
method.getGenericParameterTypes();//获取方法参数类型,返回一个Type数组
method.getGenericExceptionTypes();//获取方法抛出异常类型,返回Type数组
method.getGenericReturnTypes();//获取方法返回值类型,返回一个Type
Type对象下面在泛类介绍时分析。
通过反射操作属性
Field name = c1.getDclaredField("name");
name.set(user2,"newName");
注意:set方法也需要传入对象和参数。但是,当属性是private修饰时不能访问修改。
setAccessible方法修改权限管理。
Field name = c1.getDclaredField("name");
name.setAccessible(true);//设置权限管理,可直接访问
name.set(user2,"newName");
Method、Field和Construct对象都有setAccessible方法。
性能对比分析
可见关闭检测也能提高运行速率。
反射操作泛型
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全和免去强转类型转换问题,但是,一旦编译完成,所有和泛型相关的类型全部被擦除。(简而言之,泛型是保证编译安全,运行时被擦除)
为了提高反射操作这些类型,Java新增加了ParameterizedType,GenericArrayType,TypeVariable和WildcaedType几种类型代表不能被归一到Class类中的类型,但是又和原始类型齐名的类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PpIXZVOx-1601609485919)(C:\Users\86198\AppData\Roaming\Typora\typora-user-images\image-20201002111300350.png)]
这些方法的父类是Type 通过类型判断,来获取各类Type。
反射获取注解信息
Class c1 = Class.forName("com.fbw.User");
//获取的是类层面的注解
Annotation[] annotations = c1.getAnnotations();
TableKuang tableKuang = (TableKuang)c1.getAnnotation(TableKuang.class);//TableKuang为自定义注解
tableKuang.value();//获取注解值
//获取参数的注解
Field f = c1.getDeclaredField("name");
FieldKuang fieldKuang = f.getAnnotation(FieldKuang.class);//自定义注解
fieldKuang.name();//注解属性
fieldKuang.type();//注解属性
总结
学习注解、反射、Class、泛类等,以及它们在内存中过程。这篇学习的主要目的是为了学习理解AOP(面向切面编程)做准备。