day20_ 反射_ 注解
复习
BIO: 同步阻塞的IO, 调用某个方法时,该方法没有完成程序不能继续向下执行
NIO: 同步非阻塞的IO,调用某个方法时,无论该方法是否完成,程序可以继续向下执行,后期需要自己写代码判断
AIO: 异步非阻塞的IO,调用某个方法时,无论该方法是否完成,程序可以继续向下执行,后期会通过方法回调机制通知
今日内容
反射【反射是以后基本上所有框架的底层,这是重点】
注解【也是给我们以后的框架使用,重点,今天我们只介绍注解的语法】
反射
类的加载器
类的加载
当我们的程序在运行后,第一次使用某个类的时候,会将此类的class文件读取到内存,并将此类的所有信息存储到一个Class对象中
- 上图:Class对象是指:java.lang.Class类的对象,此类由Java类库提供,专门用于存储类的信息。
- 我们程序中可以通过:"类名.class",或者"对象.getClass()"方法获取这个Class对象
类的加载器
- 创建类的实例
- 调用类的静态变量, 或者为静态变量赋值
- 类的静态方法。
- 使用反射方式来强制创建某个类或接口对应java.lang.Class对象。
- 初始化某个类的子类.
- 直接使用java.exe命令来运行某个主类
以上六种情况的任何一种,都可以导致JVM将一个类加载到方法区。
类加载器的作用和分类
用到的类是由类加载器加载到内存中的
-
类加载器分类
启动类加载器(Bootstrap ClassLoader):用于加载系统类库
\bin目录下的class,例如:rt.jar。 扩展类加载器(Extension ClassLoader):用于加载扩展类库
\lib\ext目录下的class。 应用程序类加载器(Application ClassLoader):用于加载我们自定义类的加载器。
双亲委派机制
上图展示了"类加载器"的层次关系,并不是父子类关系,这种关系称为类加载器的"双亲委派模型":
- "双亲委派模型"中,除了顶层的启动类加载器外,其余的类加载器都应当有自己的"父级类加载器"。
- 这种关系不是通过"继承"实现的,通常是通过"组合"实现的。通过"组合"来表示父级类加载器。
- "双亲委派模型"的工作过程:
- 某个"类加载器"收到类加载的请求,它首先不会尝试自己去加载这个类,而是把请求交给父级类加载器。
- 因此,所有的类加载的请求最终都会传送到顶层的"启动类加载器"中。
- 如果"父级类加载器"无法加载这个类,然后子级类加载器再去加载。
双亲委派机制的好处
其主要作用是为了让一个类只会被加载一次例如:java.lang.Object。它存放在rt.jar中。无论哪一个类加载器要加载这个类,最终都是委派给处于顶端的"启动类加载器"进行加载,因此java.lang.Object类在程序的各种类加载器环境中都是同一个类
反射的概述
反射就是在程序运行时, 获取一个类的class对象(字节码对象)
反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)
反射在实际开发中的作用
研发一些开发工具. 如IDEA,Eclipse等
各种框架的设计和学习底层原理. 比如Spring, SpringMVC, Struct, Mybaits.....
反射中万物皆对象的概念
反射中的万物皆对象思想: 对类进行解刨之后所获取的所有成分都可以看做是一个单独的对象.
-
接类解刨(反射)后获取的
一个类编译后生成的一个字节码文件, 就是一个类对象.
Class
类对象每个成员变量都是一个单独的对象, 是
Field
类型对象每个成员方法都是一个单独的对象, 是
Method
类型对象每个构造方法都是一个单独的对象, 是
Constructor
类型对象
-
两个需要记住的单词
-
newInstance
是用来创建对象 -
invoke
是用来调用/执行
-
-
反射语法
- 创建对象
- 正常语法: new 构造方法(参数);
- 反射语法: 构造方法对象.newInstance(参数);
- 调用方法
- 正常语法: 对象名.成员方法名(参数);
- 成员方法对象.invoke(对象名, 参数);
- 创建对象
Class对象的获取方式
三种获取方法
方式1: 通过类名.class获得
方式2:通过对象名.getClass()方法获得
-
方式3:通过Class类的静态方法获得: static Class forName("类全名")
注意: 每一个类的Class对象都只有一个。
Java定义了三种获取Class对象的方式
public class GetClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
//获取一个类的Class对象的三种方式
//1.通过类的一个静态成员 class
Class clazz1 = Dog.class;
System.out.println(clazz1);
//2.通过该类的一个对象,获取该类的Class对象
Dog dd = new Dog();
Class clazz2 = dd.getClass();
System.out.println(clazz2);
//3.通过反射强制加载该类,并获取该类的Class对象
Class clazz3 = Class.forName("com.itheima.demo02_GetClass.Dog");
System.out.println(clazz3);
//注意:以上是三种获取Dog类Class对象的方式,并不是获取三个Class对象,实际上他们获取的是同一个Class对象
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
System.out.println(clazz2 == clazz3);
}
}
输出结果:
class com.itheima.demo02_GetClass.Dog
class com.itheima.demo02_GetClass.Dog
class com.itheima.demo02_GetClass.Dog
true
true
true
Class类的方法
-
String getSimpleName();
获得类名字符串:类名 -
String getName();
获得类全名:包名+类名 -
T newInstance() ;
创建Class对象关联类的对象(底层使用了关联类对象的无参构造)
public class ReflectDemo02 {
public static void main(String[] args) throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得类名字符串:类名
System.out.println(c.getSimpleName());
// 获得类全名:包名+类名
System.out.println(c.getName());
// 创建Class对象所代表的那个类的对象, 底层实际使用Stucent的无参构造
Student stu = (Student) c.newInstance();
System.out.println(stu);
}
}
反射之操作构造方法
- 反射之操作构造方法的目的
* 获得Constructor对象来创建类的对象。- Constructor类概述
- 类中的每一个构造方法都是一个Constructor类的对象
- Constructor类概述
反射获取构造方法
-
public Constructor getConstructor(Class... parameterTypes);
只能是public修饰的构造方法
构造方法有参和无参都分别是一个对象, 根据参数类型获得对应的Constructor对象。这个方法的参数就是构造方法的参数类型.class
Dog dd = new dog(); Constructor con = dd.getclass().getConstructor(int.class, String.class)
-
public Constructor getDeclaredConstructor(Class... parameterTypes);
- 可以是public、protected、(默认)、private修饰符的构造方法。
- 根据参数类型获得对应的Constructor对象
public Constructor[] getConstructors();
获得类中的所有构造方法对象,只能获得public的
-
public Constructor[] getDeclaredConstructors();
获得类中的所有构造方法对象
可以是public、protected、(默认)、private修饰符的构造方法。
使用构造方法对象创建对象
语法:
构造方法对象.newInstance(参数根据该构造方法参数而定/这里为实际参数);
// 获得有参数的构造方法对象
Constructor con2 = c.getConstructor(String.class, String.class,int.class);
// 创建对象
Object obj2 = con2.newInstance("jack", "男",18);
System.out.println(obj2);
如果获取的是私有构造对象
需要先设置权限, 否则会抛出异常
void setAccessible(true);
设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消
public void test02() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得两个参数构造方法对象
Constructor con = c.getDeclaredConstructor(String.class,String.class);
// 取消权限检查(暴力反射)
con.setAccessible(true);
// 根据构造方法创建对象
Object obj = con.newInstance("rose","女");
System.out.println(obj);
}
反射之操作成员方法
- 反射之操作成员方法的目的
操作Method对象来调用成员方法 - Method类概述
每一个成员方法都是一个Method类的对象。
反射获取成员方法对象
-
Method getMethod(String name,Class...args);
- 根据方法名和参数类型获得对应的构造方法对象,只能获得public的
-
Method getDeclaredMethod(String name,Class...args);
- 根据方法名和参数类型获得对应的构造方法对象,包括public、protected、(默认)、private的
-
Method[] getMethods();
- 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
-
Method[] getDeclaredMethods();
- 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的
使用成员方法对象调用Method类方法
-
Object invoke(Object obj, Object... args)
- 调用指定对象obj的该方法
- args:调用方法时传递的参数
-
void setAccessible(true)
设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消
代码演示
public class ReflectDemo04 {
// 反射操作静态方法
@Test
public void test04() throws Exception {
// 获得Class对象
Class c = Student.class;
// 根据方法名获得对应的公有成员方法对象
Method method = c.getDeclaredMethod("eat",String.class);
// 通过method执行对应的方法
method.invoke(null,"蛋炒饭");
}
/*
* Method[] getMethods();
* 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
* Method[] getDeclaredMethods();
* 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
*/
@Test
public void test03() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
// Method[] methods = c.getMethods();
// 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
Method[] methods = c.getDeclaredMethods();
for (Method m: methods) {
System.out.println(m);
}
}
/*
Method getDeclaredMethod(String name,Class...args);
* 根据方法名和参数类型获得对应的构造方法对象,
*/
@Test
public void test02() throws Exception {
// 获得Class对象
Class c = Student.class;
// 根据Class对象创建学生对象
Student stu = (Student) c.newInstance();
// 获得sleep方法对应的Method对象
Method m = c.getDeclaredMethod("sleep");
// 暴力反射
m.setAccessible(true);
// 通过m对象执行stuy方法
m.invoke(stu);
}
/*
Method getMethod(String name,Class...args);
* 根据方法名和参数类型获得对应的构造方法对象,
*/
@Test
public void test01() throws Exception {
// 获得Class对象
Class c = Student.class;
// 根据Class对象创建学生对象
Student stu = (Student) c.newInstance();
// 获得study方法对应的Method对象
Method m = c.getMethod("study");
// 通过m对象执行stuy方法
m.invoke(stu);
/// 获得study方法对应的Method对象
Method m2 = c.getMethod("study", int.class);
// 通过m2对象执行stuy方法
m2.invoke(stu,8);
}
}
public class Student{
private void eat(String str){
System.out.println("我吃:" + str);
}
private void sleep(){
System.out.println("我睡觉...");
}
public void study(int a){
System.out.println("我学习Java,参数a = " + a);
}
}
通过反射获取成员属性
所有的成员属性一般都是由private封装, 有get set方法, 所以一般不推荐使用
注解(Annotation)
注解的概念
- 注解是JDK1.5的新特性。
- 注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
- 标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
- 注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
注解的三个作用
-
给程序带入参数(生成帮助文档)。
- @author用来标识作者姓名
- @version用于标识对象的版本号,适用范围:文件、类、方法
-
编译检查
- @Override
框架的配置(框架=代码+配置)(后期学框架需要)
常见注解
- @author:用来标识作者名,eclipse开发工具默认的是系统用户名。
- @version:用于标识对象的版本号,适用范围:文件、类、方法。
- @Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。
- @deprecated: 用来表示过期的API
- @Test: 用于单元测试
自定义注解
自定义注解概念
自定义类: public class 类名
自定义接口: public interface 接口
自定义枚举: public enum 枚举
自定义注解: public @interface 注解
public @interface 注解名{
}
如:定义一个名为Student的注解
public @interface Student {
}
给自定义注解添加属性(只能添加属性)
- 格式
public @interface 注解名{
//注解内部只有属性
数据类型 属性名();
数据类型 属性名() [default 默认值];
}
-
示例:
// 姓名 String name(); // 年龄 int age() default 18; // 爱好 String[] hobby();
-
属性适用的数据类型
- 八种数据数据类型(int,short,long,double,byte,char,boolean,float)
- 引用类型: String,Class,注解类型,枚举类
- 以上12种类型的数组形式
自定义注解练习
定义
定义一个注解:Book
包含属性:String value() 书名
包含属性:double price() 价格,默认值为 100
包含属性:String[] authors() 多位作者代码实现
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
String value();
double price() default 100;
String[] authros();
}
使用
public class AnnotationDemo01 {
@Book(value = "JavaEE开发详解",authros = {"黑马程序员","传智学院"})
public void show(){
}
}
- 注意
- 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
- 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
自定义注解中的特殊属性名value
-
特殊属性value
如果注解中只有一个属性且名字叫value,则在使用该注解时可以直接给该属性赋值,而不需要给出属性名。
如果注解中除了value属性之外还有其他属性且只要有一个属性没有默认值,则在给属性赋值时, value属性名也不能省略了。如果其他属性有了默认值, 在赋值时如果只给value赋值, 则也不用写value属性名
小结:如果注解中只有一个属性时,一般都会将该属性名命名为value
/**
特殊属性value
*/
@interface TestA{
String[] value();
int age() default 100;
String name();
}
@interface TestB{
String name();
}
@TestA(name = "yyy",value = {"xxx","xxx"})
@TestB(name = "zzz")
public class AnnotationDemo02 {
}
注解的注解--元注解
-
元注解概述
- Java官方提供的注解
- 用来定义注解的注解
- 任何官方提供的非元注解的定义都使用到了元注解。
-
常用的元注解
-
@Target
作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。
-
属性值取值必须使用定义在ElementType枚举类中的值,常用值如下
TYPE,类,接口
FIELD, 成员变量
METHOD, 成员方法
PARAMETER, 方法参数
CONSTRUCTOR, 构造方法
LOCAL_VARIABLE, 局部变量
-
@Retention
作用:用来标识注解的生命周期(有效范围)
-
可使用的值定义在RetentionPolicy枚举类中,常用值如下
SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段
-
注解的解析
什么是注解解析
使用Java技术获得某个注解中属性值的过程则称为注解解析。
注解解析的步骤
-
步骤
- 获取注解所在类的class对象
- 获取注解所在的对象(可能是Field, Method, Constructor)
- 判断获取的对象中是否有该注解存在
- 如果有需要的注解, 取出注解对象,
- 从注解中取出属性值
-
与注解解析相关的接口
Annotation: 注解类,该类是所有注解的父类
-
AnnotatedElement:该接口定义了与注解解析相关的方法.Field,Method, Constructor,Class等类都是实现了AnnotatedElement接口.
该接口都定义了以下方法
-
boolean isAnnotationPresent(Class
判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false (参数为需要判断的注解对象)annotationClass) -
T getAnnotation(Class
根据注解类型获得对应注解对象annotationClass) -
Annotation[] getAnnotations()
获得当前对象上使用的所有注解,返回注解数组,包含父类继承的 -
Annotation[] getDeclaredAnnotations()
获得当前对象上使用的所有注解,返回注解数组,只包含本类的
-
注解解析代码实现
- 注解Book
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
String value();
double price() default 100;
String[] authros();
}
- BookShelf类
@Book(value = "红楼梦",authros = {"曹雪芹"})
public class BookShelf {
@Book(value = "西游记",authros = {"吴承恩","白求恩"},price = 200)
public void showBook(){
}
}
- TestAnnotation类
/**
什么是注解解析
* 使用Java技术获得注解上数据的过程则称为注解解析。
与注解解析相关的接口
* Annotation: 注解类,该类是所有注解的父类。
* AnnotatedElement:该接口定义了与注解解析相关的方法
T getAnnotation(Class annotationClass) 根据注解类型获得对应注解对象
Annotation[] getAnnotations()
* 获得当前对象上使用的所有注解,返回注解数组,包含父类继承的
Annotation[] getDeclaredAnnotations()
* 获得当前对象上使用的所有注解,返回注解数组,只包含本类的
boolean isAnnotationPresent(Class annotationClass)
* 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
获取注解数据的原理
* 注解作用在哪个成员上就会得该成员对应的对象来获得注解
* 比如注解作用成员方法,则要获得该成员方法对应的Method对象
* 比如注解作用在类上,则要该类的Class对象
* 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象。
* Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口
*/
public class AnnotationDemo04 {
/*
获得类上使用的注解数据
*/
@Test
public void test02() throws Exception {
// 获得Class对象
Class c = BookShelf.class;
// 判断类上是否使用Book注解
if(c.isAnnotationPresent(Book.class)){
// 根据注解的Class对象获得对应的注解对象
Book annotation = (Book) c.getAnnotation(Book.class);
// 获得书名
System.out.println(annotation.value());
// 获得作者
System.out.println(Arrays.toString(annotation.authros()));
// 获得价格
System.out.println(annotation.price());
}
// 获得当前对象上使用的所有注解,返回注解数组
// Annotation[] annotations = c.getAnnotations();
Annotation[] annotations = c.getDeclaredAnnotations();
System.out.println(Arrays.toString(annotations));
}
/*
获得成员方法上注解的数据
*/
@Test
public void test01() throws Exception {
// 获得Class对象
Class c = BookShelf.class;
// 获得成员方法对应的Method对象
Method m = c.getMethod("showBook");
// 根据注解的Class对象获得对应的注解对象
Book annotation = m.getAnnotation(Book.class);
// 获得书名
System.out.println(annotation.value());
// 获得作者
System.out.println(Arrays.toString(annotation.authros()));
// 获得价格
System.out.println(annotation.price());
}
}
注解的案例实现
需求
模拟Junit测试的@Test
案例代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}
public class TestMyTest {
@MyTest
public void tests01(){
System.out.println("test01");
}
public void tests02(){
System.out.println("test02");
}
@MyTest
public void tests03(){
System.out.println("test03");
}
}
/**
* @author pkxing
* @version 1.0
* @Package com.itheima
*/
public class AnnotationDemo05 {
public static void main(String[] args) throws Exception {
// 获得Class对象
Class c = TestMyTest.class;
Object obj = c.newInstance();
// 获得所有成员方法
Method[] methods = c.getMethods();
for(Method m:methods){
// 判断m方法是否使用了MyTest注解
if(m.isAnnotationPresent(MyTest.class)){
// 调用方法
m.invoke(obj);//或者 m.invoke(new TestMyTest());
}
}
}
}
今日总结
能够通过反射技术获取Class字节码对象。【重点】
Class clazz1 = 类名.class
Class clazz2 = 对象名.getClass();
Class clazz3 = Class.forName("包名.类名");
能够通过反射技术获取构造方法对象,并创建对象。【重点】
获取构造:
public Constructor getConstructor(参数的类型.class,...);//获取单个"public"构造
public Constructor getDeclaredConstructor(参数的类型.class,...);//获取单个"任意修饰"构造
public Constructor[] getConstructors();//获取所有"public"构造【了解】
public Constructor[] getDeclaredConstructors();//获取所有"任意修饰"构造【了解】
使用构造:
构造方法对象.newInstance(实际参数);
如果是私有构造: 必须在使用之前设置暴力权限
构造方法对象.setAccessible(true);
能够通过反射获取成员方法对象,并且调用方法 【重点】
获取方法:
public Method getMethod(String name,参数的类型.class,...);//获取单个"public"方法
public Method getDeclaredMethod(String name,参数的类型.class,...);//获取单个"任意修饰"方法
public Method[] getMethods();//获取所有"public"方法,包含父类继承的【了解】
public Method[] getDeclaredMethods();//获取所有"任意修饰"方法,不包含父类的【了解】
使用方法:
成员方法对象.invoke(对象名,方法的实际参数);
如果该方法是私有的,那么必须在使用之前设置暴力权限
成员方法对象.setAccessible(true);
能够通过反射获取属性对象,并且能够给对象的属性赋值和取值【了解,有兴趣自学】
能够说出注解的作用
a。给程序带入参数 b。编译检查 c。给框架做配置文件
能够自定义注解和使用注解【重点】
自定义注解:
public @interface 注解名{
数据类型 属性名();
数据类型 属性名() default 默认值;
数据类型[] 属性名() default {默认值1,默认值2,...}
}
这里的数据类型,只能是三大类(a.8大基本类型 b.四大引用类型 c.以上12中的数组)
特殊的属性:value(怎么特殊???)
能够说出常用的元注解及其作用 【理解】
@Target
@Retension
能够解析注解并获取注解中的数据 【理解】
写代码把某个注解上的那些属性值获取打印出来
a.获取注解所在的那个类的Class对象
b.获取注解所在的对象(可能Field,Method,Constructor)
c.判断获取的对象中是否有该注解存在
d.如果有我们要的注解,取出我们要的注解即可
e.从注解中取出属性值即可
能够完成注解的MyTest案例 【理解】
本质上也是注解的解析,只是没有获取注解的属性值,而是调用含有该注解的方法