文章目录
注解
相关概念
- 注解 :
Annotation
,是从JDK5.0开始引入得新技术 - 注解的作用
(1)不是程序本身,可以对程序作出解释,这一点和注释没什么区别
(2)可以被其他程序(编译器等)读取 - 注解的格式:注解是以
@Name
在代码中存在的,还可以i添加一些参数值,例如:@Value(value="Spring")
- 注解的使用:注解可以附加在package、class、method、field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
内置注解
@Override
表示一个方法生命打算重写超类中的另一个方法声明;@Deprecated
此注释可以用于描述 方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者有更好的选择;但是最新版本已经淘汰了@SuppressWarnings
用来抑制编译时的警告信息,可用于方法,变量,类等,需要添加参数,例如:
@SuppressWarnings("all")
@SuppressWarnings("unchecked")
@SuppressWarnings(value={"unchecked","deprecation"})
自定义注解,元注解
-
元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,可在
java.lang.annotation
中找到,他们被用来提供对其他annotation类型作说明。
(1)@Target
用以描述注解的使用范围;
(2)@Retention
表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE 源文件< CLASS 字节码 < RUNTIME 运行时
);
(3)@Document
说明该注解将被包含在javadoc中;
(4)@Inherited
说明子类可以集成父类中的该注解;//定义一个注解 //Target表示注解只可用在method和type上,type即类 @Target(value = {ElementType.METHOD,ElementType.TYPE}) //表示该注解在运行时有效 @Retention(RetentionPolicy.RUNTIME) //表示是否将我们的注解生成在javadoc中 @Document //子类可以集成父类的该注解 @Inherited @interface MyAnnotation{ }
-
自定义注解:使用@interface自定义注解时,自动继承了
java.lang.annotation.Annotation
接口 -
自定义注解的例子:
//自定义注解 public class AnnotationTest { // 注解可以显示赋值 @MyAnnotation(name = "demo",age = 18,id = 1) public void test(){ } } @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation{ //如果只有一个参数,建议使用value作为参数名,使用的时候可省略’value =‘ //注解的参数:参数类型 + 参数名 //有默认值就可以不写参数,如果没有默认值就一定要写参数 String name() default ""; int age() default 0; //如果默认值为-1,代表不存在,与 indexof找不到就返回-1 类似 int id() default -1; }
反射机制
动态语言和静态语言
- 动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要的动态语言有:Object-C、C#、JavaScript、PHP、Python等 - 静态语言
与动态语言相对应的,运行时结构不改变的语言就是静态语言,如Java、C、C++。
Java不是动态语言,但Java可以称之为“准动态语言”。也就是说,Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让变成的时候更加灵活。
获得反射对象
- 反射
Reflection
是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API
取得任何类的内部信息(包括类名,方法,接口等),并能直接操作任意对象的内部属性及方法;
Class c = CLass.forName("java.lang.String")
- 在加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为反射。
- 但是要注意object.getClass()和Object.class的区别,点击可跳转至另一博客
- 反射机制的应用
- 反射的优点:可以实现动态创建对象和编译,体现出很大的灵活性;
- 反射的缺点:对性能有影响,使用反射基本上是一种解释操作我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作;
Class类的概述
- Class类即描述类的类,一个类在内存中只有一个Class对象,一个类被加载后,类的整个结构都会被封装在Class对象中
- Object类中定义了
public final Class getClass()
方法,此方法也会被所有子类继承 - 此方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即可以通过对象反射求出类的名称
- Class类的常用方法
获取Class类的实例的方法
- 获取Class类的实例的几种方法:
(1)若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
Class c1 = Person.class;
(2)已知某个类的实例,调用该实例的getCClass()
方法获取Class对象
Class c2 = person.getClass();
(3)已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()
获取,可能抛出ClassNotFoundException
Class c3 = Class.forName("com.demo.pojo.Student");
(4)内置基本数据类型可以直接用类名.Type
Class c4 = Integer.TYPE;
(5)还可以利用ClassLoader
- 还可以通过获得的Class类得到对象的父类,例如:
Class c5 = c1.getSuperclass();
- 有Class对象的类型:
- 多维元素之间的class的区别:只要元素类型与维度一样,就是同一个class
类的加载过程分析
- Java内存分析
- 类的加载过程
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象;
- 链接:将Java类的二进制代码合并到JVM的运行状态之中
(1)验证:确保加载的类信息符合JVM规范,没有安全方面的问题
(2)准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法去中进行分配
(3)解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程 - 初始化:
(1)执行类构造器<clinit>()
方法的过程。类构造器<clinit>()
方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器时构造类信息的,不是构造该类对象的构造器)
(2)当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
(3)虚拟机会保证一个类的<clinit>()
方法在多线程环境中被正确加锁和同步
- 什么时候会发生类初始化?
类加载器( ClassLoader )
- 类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
- 类加载器的作用及类型
- 代码实现
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类加载器-->根加载器(C/C++)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//测试当前类是由哪个加载器加载的
ClassLoader classLoader1 = Class.forName("com.learn.test.Test").getClassLoader();
System.out.println(classLoader1);
//测试JDK内置的类是哪个加载器加载的
ClassLoader classLoader2 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader2);
//如何获得系统类加载器可以加载的路径
String property = System.getProperty("java.class.path");
System.out.println(property);
}
}
/*
输出结果:
jdk.internal.loader.ClassLoaders$AppClassLoader@3fee733d
jdk.internal.loader.ClassLoaders$PlatformClassLoader@27bc2616
null //根加载器无法直接获取,所以显示为null
jdk.internal.loader.ClassLoaders$AppClassLoader@3fee733d //为用户类加载器
null //Object是在rt.jar包内的,是java的核心类,由根加载器加载
*/
- 扩充:双亲委派机制
例如要加载java.lang.String
,首先会一级一级向上查找,然后去用户类加载器,再去扩展类加载器,最后再看根加载器有没有这个包,如果在根加载器找到了这个包,那么自己定义的这个包就会无效,因为双亲委派机制会检测安全性,向上逐级查找加载器的包,如果已经存在了,就会加载器里面的包而不用自定义的包,这就是双亲委派机制,会多重检测保证安全性。
获取运行时类的对象
- 通过反射可获取运行时类的完整结构有:
public class Test {
@SuppressWarnings("all") //忽略所有的警告
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class c1 = Class.forName("com.learn.test.User");
//获得类的名字
System.out.println(c1.getName()); //全限定类名
System.out.println(c1.getSimpleName()); //获得类名
System.out.println("===================================");
//获得类的属性
Field[] fields = c1.getFields(); //只能获得public的属性
for (Field field : fields) {
System.out.println(field);
}
System.out.println("===================================");
Field[] declaredFields = c1.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("===================================");
//获得类的方法
Method[] methods = c1.getMethods(); //获得本类及其父类的public方法
for (Method method : methods) {
System.out.println("getMethods:"+method);
}
System.out.println("===================================");
Method[] declaredMethods = c1.getDeclaredMethods(); //获得本类的所有方法
for (Method declaredMethod : declaredMethods) {
System.out.println("getDeclaredMethods:"+declaredMethod);
}
System.out.println("===================================");
//获得指定方法
//因为重载机制,需要输入参数
Method getName = c1.getMethod("getName",null);
Method setName = c1.getMethod("setName", String.class);
System.out.println("getName:"+getName);
System.out.println("setName:"+setName);
System.out.println("===================================");
//获得构造器
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("constructor: "+constructor);
}
System.out.println("===================================");
Constructor[] declaredConstructors = c1.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println("declaredConstructor: "+declaredConstructor);
}
System.out.println("===================================");
//获得指定构造器
Constructor constructor = c1.getConstructor(null);
System.out.println("指定:"+constructor);
}
}
通过反射动态的创建对象并操作其属性和方法
- 可以通过调用
newInstance()
方法来创建对象,但是前提是类必须有无参的构造器且类的构造器的访问权限需要足够 - 如果没有无参的构造方器,需要在操作时明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作;实现方法:通过Class类的
getDeclaredConstructor()
取得指定形参类型的构造器,再向构造器的形参中传递一个对象数组进去,最后通过Constructor实例化对象)
- 关于setAccessible提高反射效率的验证:经过代码的检测,当我们反复的调用一个语句,执行结果显示所用到的时间差距非常大,所以如果会多次的反射调用,可以关闭检测以提高效率。
public class Test02 {
public static void main(String[] args) throws Exception {
//获得Class对象
Class c1 = Class.forName("com.learn.test.User");
/* 构造一个对象 */
//本质是调用了一个无参构造器
// 因此必须要有一个无参的构造器,且类得构造器的访问权限需要足够
User user1 = (User)c1.newInstance();
System.out.println(user1);
//通过构造器创建对象
Constructor constructor = c1.getConstructor(String.class, int.class);
User user2 = (User) constructor.newInstance("user2", 1);
System.out.println(user2);
//通过反射调用普通方法
User user3 = (User)c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke : 激活的意思 (要传入的对象,传入值)
//调用user3的setName方法并传入一个字符串
setName.invoke(user3,"user3");
System.out.println(user3.getName());
//通过反射操作属性
User user4 = (User)c1.newInstance();
Field name = c1.getDeclaredField("name");
//如果直接调用set方法会报错,因为name属性是私有的,权限不够
//调用setAccessible方法并传入true,表示设置name属性为允许访问
name.setAccessible(true);
name.set(user4,"user4");
System.out.println(user4.getName());
}
}
反射操作泛型
- Java采用泛型擦除的机制来引入泛型,java中的泛型仅仅是给编译器
javac
使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除 - 为了通过反射操作这些类型,java新增了
ParameterizedType
,GenericArrayYType
,TypeVariable
和WildcardType
几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型
- 定义User类
public class User {
private String name;
private int id;
public User() {
}
public User(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
- 通过反射操作泛型的测试类
public class Test03 {
public void test01(Map<String,User> map, List<User> list){
System.out.println("test01");
}
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws Exception {
//反射获取方法
Method method = Test03.class.getMethod("test01", Map.class, List.class);
//获得泛型参数类型
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("# "+genericParameterType);
//判断泛型的参数类型是否等于结构化参数类型(参数化类型)
if(genericParameterType instanceof ParameterizedType){
//如果是就强制转换成结构化参数类型并获得真实参数信息
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
System.out.println("=========================");
//反射获取方法
method = Test03.class.getMethod("test02", null);
//获得返回的泛型类型
Type genericParameterType = method.getGenericReturnType();
//判断泛型的返回类型是否等于结构化参数类型(参数化类型)
if(genericParameterType instanceof ParameterizedType){
//如果是就强制转换成结构化参数类型并获得真实参数信息
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
反射操作注解
- 以下代码分为几大模块:
(1)定义注解,分别为类名的注解和属性的注解
(2)定义一个实体类,并使用上面定义的注解
(3)通过反射操作注解并获得注解的值
/**
* @author Hey
* 类名的注解,获得表名
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tabledemo{
String value();
}
/**
* @author Hey
* 属性的注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fielddemo{
String columnName();
String type();
int length();
}
@Tabledemo("db_student")
class Student{
@Fielddemo(columnName = "db_id",type = "int",length = 10)
private int id;
@Fielddemo(columnName = "db_name",type = "varchar",length = 3)
private String name;
@Fielddemo(columnName = "db_age",type = "int",length = 10)
private int age;
public Student() {
}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
/**
* @author Hey
* 测试类
*/
public class Test04 {
public static void main(String[] args) throws Exception {
Class c1 = Class.forName("com.learn.test.Student");
//通过反射获得类的注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("# "+annotation);
}
//获得注解的value的值
Tabledemo tabledemo = (Tabledemo)c1.getAnnotation(Tabledemo.class);
System.out.println(tabledemo.value());
//获得指定的注解并获得注解的各个值
Field field = c1.getDeclaredField("name");
Fielddemo annotation = field.getAnnotation(Fielddemo.class);
System.out.println("# "+annotation);
System.out.println(annotation.columnName());
System.out.println(annotation.length());
System.out.println(annotation.type());
}
}
- 测试类输出结果
/**
* 输出结果:
* # @com.learn.test.Tabledemo(value="db_student")
* db_student
* # @com.learn.test.Fielddemo(columnName="db_name", type="varchar", length=3)
* db_name
* 3
* varchar
*/