Java学习记录 Day23(注解、反射)
2019-06-09 22:52
686 查看
Day 23
2019年6月7日。
这是我学习Java的第二十三天。
这一天,我学到了以下的知识。
注解
注解(Annotation),是从JDK5.0开始引入的新技术。
注解的性质如下:
- 注解的作用
- 注解不是程序本身,可以对程序作出解释(这一点和注释(comment)没什么区别)
- 可以被其他程序(比如:编译器等)读取 - 注解的格式
- 注解是以“@注解名”在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value=“unchecked”) - 注解的使用范围
- 注解可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问
内置的注解如下:
- @Override:定义在java.lang.Override中,此注解只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明
- @Deprecated:定义在java.lang.Deprecated中,此注解可以用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
- @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息。与前两个注解有所不同,需要添加一个参数才能正确使用,这些参数都已经定义好的,只需要有选择性的使用就好
示例如下:
public class Test { //Override:重写方法的注解 @Override public String toString() { return "Test{}"; } //Deprecated:不推荐使用,但是可以使用 @Deprecated public static void stop(){ System.out.println("123"); } //SuppressWarnings:抑制警告信息的注解 @SuppressWarnings("all") public void test(){ } public static void main(String[] args) { stop(); } }
元注解,就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,它们被用来提供对其他annotation类型作说明,这些类型和它们所支持的类在java.lang.annotation包中可以找到(@Target,@Retention,@Documented,@Inheriter),它们的说明如下:
- @Target:用于描述注解的使用范围(即被描述的注解可以用在什么地方,例如ElementType.METHOD为作用在方法上,ElementType.TYPE为运用在类上)
- @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE < CLASS < RUNTIME)
- @Document:说明该注解被包含在javadoc中
- @Inherited:说明子类可以继承父类中的该注解
示例如下:
//测试元注解 @MyAnnotation public class Test2 { } @Target(value = {ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) //作用域 @Retention(RetentionPolicy.RUNTIME) //运行时级别 @Documented //生成Doc文档时候使用 @Inherited //子类可以继承父类的注解 @interface MyAnnotation{ }
自定义注解,就是根据自身的需要,自己定义出来的注解。使用关键字@interface自定义注解时,便自动继承了java.lang.annotation.Annotation接口。自定义注解的说明如下:
- @interface用来声明一个注解,格式:public @interface 注解名{ 定义内容 }
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值只能是基本类型:Class,String,enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
示例如下:
//测试自定义注解 @SuppressWarnings("all") public class Test3 { @MyAnnotation2(name = "123",age = 3,id = 001,schools = "aa") public void test(){ } //注解只有一个参数的时候,默认使用value当做参数名字,使用的时候可以省略参数名 @MyAnnotation3("a") public void test2(){ } } @Target(value = {ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) //作用域 @Retention(RetentionPolicy.RUNTIME) //运行时级别 @interface MyAnnotation2{ //参数类型 参数名 String name() default ""; int age() default 0; // -1 代表不存在,类似于Indexof的返回值 int id() default -1; String[] schools() default {"a","b"}; } @Target(value = {ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) //作用域 @Retention(RetentionPolicy.RUNTIME) //运行时级别 @interface MyAnnotation3{ String value(); }
反射
反射(reflection),是Java被视为动态语言的关键,反射机制允许程序在执行器借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
反射的基本格式为:
Class c = Class.forName("java.lang.String")
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子可以看到类的结构。所以,就形象的称之为:反射
反射的示意图如下:
反射机制提供的功能如下:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
- …
反射机制的优缺点如下:
- 优点
可以实现动态创建对象和编译,体现出很大的灵活性 - 缺点
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作
示例如下:
//初始反射 //Class public class Test { public static void main(String[] args) { try { //通过反射获取类的class Class c1 = Class.forName("Test2.User"); Class c2 = Class.forName("Test2.User"); System.out.println(c1.hashCode()); System.out.println(c2.hashCode()); //正常创建对象的方式 User user = new User(); Class c3 = user.getClass(); System.out.println(c3.hashCode()); / 4000 /总结:一个类无论创造多少个对象,只有一个Class对象 } catch (ClassNotFoundException e) { e.printStackTrace(); } } } //实体类 class User extends Object{ private String name; private int id; private int age; public User(String name, int id, int age) { this.name = name; this.id = id; this.age = age; } public User() { } 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; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
跟反射机制息息相关的类:Class类
在Object类中定义了一个方法:
public final Class getClass(),此方法被所有的子类继承。该方法的返回值类型Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解。即:可以通过对象反射求出类的名称。
对象照镜子可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构的有关信息,Class的特点如下所示:
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个Class所生成
- 通过Class,可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何想要动态加载、运行的类,唯有先获得相应的Class对象
- Class类的常用方法如下:
-static ClassforName(String name)
:返回指定类名name的Class对象
-Object newInstance()
:调用缺省构造函数,返回Class对象的一个实例
-getName()
:返回此Class对象所表示的实体(类,接口,数组类或void)的名称
-Class getSuperClass()
:返回当前Class对象的父类的Class对象
-Class[] getinterfaces()
:获取当前Class对象的接口
-ClassLoader getClassLoader()
:返回该类的类加载器
-Constructor[] getConstructors()
:返回一个包含某些Constructor对象的数组
-Method getMother(String name,Class.. T)
:返回一个Method对象,此对象的形参类型为paramType
-Field[] getDeclaredFields()
:返回Field对象的一个数组 - 获取Class类的实例,有以下几种方式:
- 若已知具体的类,通过类的Class属性获取,该方法最为安全可靠,且程序性能最高:Class clazz = Person.class;
- 若已知某个类的实例,调用该实例的getClass()方法获取Class对象:Class clazz = person.getClass();
- 若已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能会抛出ClassNotFoundException:Class clazz = Class.forName("demo1.Student")
- 内置基本数据类型可以直接使用类名.Type
- 通过ClassLoader
- 注意:若获取了数组类的实例,在数组类型一样的情况下,同个维度,只有一个Class对象
- 获取Class类的示例如下:
//测试如何获得class public class Test2 { public static void main(String[] args) throws ClassNotFoundException { Person person = new Student(); System.out.println("这个人是:" + person); //1.通过对象获得class Class c1 = person.getClass(); System.out.println(c1); //2.通过forname : Class c2 = Class.forName("Test2.Person"); System.out.println(c2); //3,通过类的静态成员变量class 获得 Class c3 = Person.class; System.out.println(c3); //4.只有默认的基本类型才有的方法 //八大基本数据类型 //byte,int,short,long,double,float,char,boolean Class type = Integer.TYPE; //Integer.class System.out.println(type); //5.获得父类的类型 Class c4 = person.getClass(); Class c5 = c4.getSuperclass(); System.out.println(c4); System.out.println(c5); } } class Person{ String name; public Person() { } public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } } class Student extends Person{ public Student(){ this.name = "学生"; } } class Teacher extends Person{ public Teacher(){ this.name = "老师"; } }
有了Class对象之后,可以创建类的对象:即调用Class对象的newInstance()方法,调用的要求如下:
- 该类必须要有一个无参的构造方法(只有在操作的时候明确地调用类中的构造方法,并将参数传递进去,才可以进行实例化操作)
- 类的构造方法的访问权限需要足够
满足了调用要求后,调用步骤如下:
- 通过Class类的getDeclaredConstructor(Class… parameterTypes)取得本类的指定形参类型的构造方法
- 向构造方法的形参中传递一个对象数组进去,里面包含了构造方法中所需要的各个参数
- 通过Constructor实例化对象
调用结束后,通过反射,调用类中的方法,可以使用Method类完成。步骤如下:
- 通过Class类的getMethod(String name,Class… parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型
- 之后使用Object invoke(Object obj,Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息,该方法的性质如下:
(1).Object对应原方法的返回值,若原方法无返回值,则返回null
(2).若原方法为静态方法,此时形参Object obj可为null
(3).若原方法形参为空,则Object[] args为null
(4).若原方法声明为private,则需要在调用此invoke()方法前,显示调用方法对象的setAccessible(true),将可访问private的方法
(5).一般来说,正常创建对象的运行时间 < 通过反射创建对象的运行时间(令setAccessible为true) < 通过反射创建对象的运行时间(令setAccessible为false)
示例图如下:
示例如下:
User类:
//实体类 class User extends Object{ private String name; private int id; private int age; public User(String name, int id, int age) { this.name = name; this.id = id; this.age = age; } public User() { } 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; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
示例一
//获得类的信息 @SuppressWarnings("all") public class Test4 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { Class c1 = Class.forName("Test2.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); } Field[] declaredFields = c1.getDeclaredFields(); // 获得类的所有属性 for (Field declaredField : declaredFields) { System.out.println(declaredField); } Field name = c1.getDeclaredField("name"); System.out.println(name); System.out.println("--------------------"); //获得方法 Method[] methods = c1.getMethods(); // 获得本类及其父类的所有public方法 for (Method method : methods) { System.out.println("默认:" + method); } Method[] declaredMethods = c1.getDeclaredMethods();// 获得本类的所有方法 for (Method declaredMethod : declaredMethods) { System.out.println("Declared:" + declaredMethod); } System.out.println("--------------------"); //重载,如果只知道方法的名字,是找不到具体的方法 Method getName = c1.getDeclaredMethod("getName",null); Method setName = c1.getDeclaredMethod("setName", String.class); System.out.println(getName); System.out.println(setName); System.out.println("--------------------"); //获取构造器 Constructor[] constructors = c1.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } System.out.println("--------------------"); //获得指定的构造器 Constructor constructor = c1.getConstructor(null); // 无参构造 System.out.println("默认:" + constructor); constructor = c1.getConstructor(String.class,int.class,int.class); System.out.println(constructor); } }
实例二
//动态构造对象,调用方法 public class Test5 { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { Class c = Class.forName("Test3.User"); //1.通过动态调用构造方法,构造对象 User user = (User)c.newInstance(); System.out.println(user); //2.通过有参构造创建对象 System.out.println("--------------------------------"); Constructor<User> declaredConstructor = (Constructor<User>) c.getDeclaredConstructor(String.class, int.class, int.class); User user2 = declaredConstructor.newInstance("a", 007, 18); System.out.println(user2.getName()); //3.调用普通方法 System.out.println("--------------------------------"); User user3 = (User) c.newInstance(); //获得指定的方法 Method method = c.getDeclaredMethod("setName", String.class); //invoke(方法执行的对象,方法参数的值) method.invoke(user3,"a"); System.out.println(user3.getName()); //4.操作属性 User user4 = (User)c.newInstance(); Field field = c.getDeclaredField("name"); field.setAccessible(true); // 关闭权限访问检测,默认是false[打开权限访问检测] //字段设置值(对象,值) field.set(user4,"b"); System.out.println(user4.getName()); } }
反射的其他性质
- setAccessible
- Method和Field、Construtor对象都有setAccessible()方法
- setAccessible作用是启动和禁用访问安全检查的开关
- 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查(此举会使得原本无法访问的私有成员也可以访问,提高反射的效率。如果代码中必须用反射,而该句代码需要频繁地被调用,请设置为true)
- 性能测试示例如下:
//分析性能问题 public class Test6 { //正常创建对象 public static void test1(){ User user = new User(); //获取时间 long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000L; i++) { user.getName(); } long endTime = System.currentTimeMillis(); System.out.println("正常方式调用10亿次方法" + (endTime - startTime) + "毫秒"); } //通过反射创建对象耗时 public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class c = user.getClass(); Method method = c.getMethod("getName",null); //获取时间 long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000L; i++) { method.invoke(user,null); } long endTime = System.currentTimeMillis(); System.out.println("反射方式调用10亿次方法" + (endTime - startTime) + "毫秒"); } //通过反射创建对象耗时,关闭安全检测 public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class c = user.getClass(); Method method = c.getMethod("getName",null); method.setAccessible(true); //提高性能 //获取时间 long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000L; i++) { method.invoke(user,null); } long endTime = System.currentTimeMillis(); System.out.println("关闭安全检测-->反射方式调用10亿次方法" + (endTime - startTime) + "毫秒"); } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { test1(); test2(); test3(); } }
- 反射操作泛型
- Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题。但是,一旦编译完成,所有和泛型有关的类型就会全部擦除
- 为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArray,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的数据,这四种类型介绍如下:
(1).ParameterizedType:表示一种参数化类型,比如Collection<String.>
(2).GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
(3).TypeVariable:是各种类型变量的公共父接口
(4).WildcardType:代表一种通配符类型表达式
- 示例如下:
//测试反射获取泛型 @SuppressWarnings("all") public class Test7 { //带有泛型参数的方法 public void test(Map<String,User> map,List<User> list){ System.out.println("test01"); } //带有泛型返回值的方法 public Map<Integer,User> test2(){ System.out.println("test02"); return null; } public static void main(String[] args) throws NoSuchMethodException { //1.获得制定方法的泛型信息 Method method = Test7.class.getDeclaredMethod("test", Map.class, List.class); //获得泛型参数类型信息 Type[] t = method.getGenericExceptionTypes(); for (Type type : t) { System.out.println("#" + type); if (type instanceof ParameterizedType){ //getActualTypeArguments 获得真实的类型参数 Type[] actualTypeArguments = ((ParameterizedType) 24000 type).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println("真实的泛型类型:" + actualTypeArgument); } } } //2.获得返回值泛型信息 Method method2 = Test7.class.getDeclaredMethod("test2",null); //获得泛型返回的信息 Type genericReturnType = method2.getGenericReturnType(); if (genericReturnType instanceof ParameterizedType){ Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println("真实的返回值类型:" + actualTypeArgument); } } } }
- 反射操作注解
通过反射,可以获取注解内的信息。示例如下:
package Test3; //使用反射读取注解 /* 1.定义注解 2.在类中使用注解 3.使用反射获取注解 */ public class Test09 { public static void main(String[] args) throws Exception { //通过反射获取注解信息 Class c1 = Class.forName("Test3.Student2"); Annotation[] annotations = c1.getAnnotations();//获得这个类的所有注解信息 for (Annotation annotation : annotations) { System.out.println(annotation); } //获得注解的值 //通过注解的 value方法 获得注解的值 Annotation annotation = c1.getAnnotation(TableKuang.class); TableKuang annotation1 = (TableKuang) annotation; System.out.println(annotation1.value()); Field field = c1.getDeclaredField("id"); System.out.println(field); //获得字段的注解 FieldKuang annotation2 = field.getAnnotation(FieldKuang.class); //获得注解的参数信息 System.out.println(annotation2.columnName()); System.out.println(annotation2.length()); System.out.println(annotation2.type()); } } //学生的实体类 //db : database -->数据库 @TableKuang("db_student") class Student2{ @FieldKuang(columnName = "id",type = "int",length = 10) private int id; @FieldKuang(columnName = "db_age",type = "int",length = 3) private int age; @FieldKuang(columnName = "name",type = "varchar",length = 20) private String name; public Student2() { } public Student2(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student2{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } } //表名-->类名的注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface TableKuang{ String value(); } //字段类的注解 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface FieldKuang{ //参数类型 参数名 String columnName();//列名 String type();//类型 int length();//长度 }
相关文章推荐
- Java反射学习总结五(Annotation(注解)-基础篇)
- 【知了堂学习笔记】JAVA反射及注解--轻松学会注解
- Java反射学习总结五(Annotation(注解)-基础篇)
- Java学习记录-注解
- JAVA基础学习之IP简述使用、反射、正则表达式操作、网络爬虫、可变参数、了解和入门注解的应用、使用Eclipse的Debug功能(7)
- Java反射学习总结终(使用反射和注解模拟JUnit单元测试框架)
- Java反射学习总结五(Annotation(注解)-基础篇)
- Java中与Junit,反射,注解的初次学习
- 【知了堂学习笔记】JAVA反射及注解--轻松学会反射
- Java反射学习总结终(使用反射和注解模拟JUnit单元测试框架)
- Java基础 :反射、注解、代理、线程池、依赖的学习和理解
- Apache、Tomcat、mysql与Java Web开发环境学习记录(20070410)
- 黑马程序员Java培训、Android培训-Java 学习过程记录_多线程3
- Java中的反射学习及反射解耦应用
- 黑马程序员Java培训、Android培训-Java 学习过程记录_多线程
- hardcore java 学习5 反射
- java 反射学习(hello world)
- 传智播客学习之java 反射
- java反射学习笔记三(改进笔记二放到配置文件中)
- java学习之理解反射机制