课程笔记Day22
- 反射
- 注解
- 正则表达式
第一章 反射
第01节 类加载器
Java执行的过程
HelloWorld.java ---javac 编译 ---> HelloWorld.class ----java 运行 ---> 可见的程序。
编译的过程: 检查,校验,预编译的过程。
运行的过程: 将字节码,加载到内存的过程。会通过 类加载器加载。
简单一点说: 类加载器是用来加载字节码文件的。 ClassLoader
理解类加载器(双亲委派机制)
ClassLoader的两个应用案例
案例1:
//类加载器的一个API需要大家掌握
@SuppressWarnings("all")
public class Test02 {
public static void main(String[] args) throws IOException {
//获取到类加载器的对象
ClassLoader loader = ClassLoader.getSystemClassLoader();
//可以去加载来自于 src 文件夹下面的文件信息,获取到流对象
//强调: 可以将 src 文件夹下面的文件,转换成为字节输入流 InputStream
InputStream is = loader.getResourceAsStream("config.properties");
//采用Properties去加载数据
Properties pp = new Properties();
pp.load(is);
is.close();
//获取到数据
String username = pp.getProperty("username");
String password = pp.getProperty("password");
System.out.println("username = " + username); //username = zhangsan
System.out.println("password = " + password); //password = 123456
}
}
案例2:
//类加载器的一个API需要大家掌握
@SuppressWarnings("all")
public class Test03 {
public static void main(String[] args) throws IOException {
//获取到类加载器的对象,可以直接获取到流对象
//强调: 可以将 src 文件夹下面的文件,转换成为字节输入流 InputStream
InputStream is = ClassLoader.getSystemResourceAsStream("config.properties");
//采用Properties去加载数据
Properties pp = new Properties();
pp.load(is);
is.close();
//获取到数据
String username = pp.getProperty("username");
String password = pp.getProperty("password");
System.out.println("username = " + username); //username = zhangsan
System.out.println("password = " + password); //password = 123456
}
}
第02节 反射原理
原理图
第03节 字节码对象
获取字节码对象的三种方式
1. 如果别人给你的是当前类的对象,如何获取到字节码?
例如:
我们有个手机类,名称叫做 Phone 现在别人给你了这个手机类的对象,名称叫做 one
那么如何获取到当前类的字节码对象呢?
Class clazz = one.getClass();
2. 如果别人给你的是当前类的名称,如何获取到字节码?
例如:
我们有个手机类,名称叫做 Phone
那么如何获取到当前类的字节码对象呢?
Class clazz = Phone.class;
3. 如果别人给你的是当前类的全名字符串,如何获取到字节码?
例如:
我们有个手机类,全类名(也就是说类名称和包名称的字符) blb.chc.Phone
那么如何获取到当前类的字节码对象呢?
Class clazz = Class.forName("blb.chc.Phone");
注意事项:三种字节码方式获取的字节码对象,是同一个对象。
第04节 操作构造方法
常用API
获取构造方法
1. Constructor[] array1 = clazz.getConstructors(); //获取所有 public 修饰的构造方法
2. Constructor c2 = clazz.getConstructor(String.class, int.class); //获取指定的某个public修饰的构造方法。
3. Constructor[] array3 = clazz.getDeclaredConstructors(); //获取所有的构造方法,包括私有
4. Constructor c4 = clazz.getDeclaredConstructor(int.class); //获取指定的某个构造方法,包括私有
使用构造方法
1. Object o = c.newInstance("家海"); //采用构造方法 c 去创建对象 o 根据需求传递参数
2. c.setAccessible(true); //暴力访问,取消私有 private 权限检查。
3. Object o = clazz.newInstance(); //采用字节码去创建对象,跳过了获取构造方法的过程。调用的是 系统默认无参数构造方法
第05节 操作成员变量
常用API
获取成员变量
//获取所有 public 修饰的成员变量
1. Field[] array1 = clazz.getFields();
2. //获取指定的某个public修饰的成员变量
3. Field f2 = clazz.getField(String);
4. //获取所有的成员变量,包括私有
5. Field[] array3 = clazz.getDeclaredFields();
6. //获取指定的某个成员变量,包括私有
7. Field f4 = clazz.getDeclaredField(String);
使用成员变量
1. //参数1:对象 参数2:值
2. 设置值 genderField.set(stu,"男");
3. //参数: 对象, 返回值: 获取到成员变量里面的值
4. 获取值 Object value1 = genderField.get(stu);
5. //暴力访问,取消私有权限检查
6. 暴力访问 genderField.setAccessible(true);
反射练习
配置文件 config.properties
username=zhangsanpassword=123456className=blb.chc03.User
用户类
package blb.chc03;
//定义用户类
public class User {
private String username;
private String password;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' + ",
password='" + password + '\'' + '}';
}}
测试类
public class Test {
//读取文件的数据,给成员变量自动赋值,展示结果
public static void main(String[] args) throws Exception{
//获取到来自于文件当中的数据。
//config.properties 文件存在于 src 目录下面的
InputStream is = ClassLoader.getSystemResourceAsStream("config.properties");
Properties pp = new Properties();
pp.load(is);
String username = pp.getProperty("username");
String password = pp.getProperty("password");
String className = pp.getProperty("className");
is.close();
//采用反射的方式,获取到对象, 创建对象
Class clazz = Class.forName(className);
Constructor c = clazz.getDeclaredConstructor();
Object o = c.newInstance();
//给成员变量赋值
Field usernameFiled = clazz.getDeclaredField("username");
usernameFiled.setAccessible(true);
usernameFiled.set(o,username);
Field passwordField = clazz.getDeclaredField("password");
passwordField.setAccessible(true);
passwordField.set(o,password);
//展示结果 System.out.println("o = " + o);
//o = User{username='zhangsan', password='123456'}
}}
配图说明
第06节 操作成员方法
常用API
获取成员方法
1. Method[] array1 = clazz.getMethods();
2. //获取所有 public 修饰的成员方法, 包括父类public
3. 方法2. Method m2 = clazz.getMethod(String,Class...);
4. //获取指定的某个public修饰的成员方法,可变参数是:方法参数的字节码3.
5. Method[] array3 = clazz.getDeclaredMethods();
6. //获取所有的成员方法,包括私有,只有自己的方法,没有父类
7. 方法4. Method m4 = clazz.getDeclaredMethod(String, int.class, int.class);
8. //获取指定的某个成员方法,包括私有
使用成员方法
1. 调用方法 Object 返回值 = 方法对象.invoke(方法所在类的对象, 方法参数1, 方法参数2);
2. 2. 暴力访问 方法对象.setAccessible(true);
练习案例
需求
写一个反射案例,可以调用 任意类,任意的无参数无返回值的方法。
config.properties 里面包含有className=包名称.类名称methodName=方法名称
配置文件 config.properties
className=blb.chc04.PhonemethodName=call
学生类
package blb.chc04;public class Student {
public void study(){
System.out.println("我爱撸代码");
}}
手机类
package blb.chc04;public class Phone {
public void call(){
System.out.println("打电话");
}}
测试类
//测试类
public class Test {
public static void main(String[] args) throws Exception{
//1.获取到数据
InputStream is = ClassLoader.getSystemResourceAsStream("config.properties");
Properties pp = new Properties();
pp.load(is);
is.close();
String className = pp.getProperty("className");
String methodName = pp.getProperty("methodName");
//2. 通过反射,准备调用方法
Class clazz = Class.forName(className);
Object o = clazz.getConstructor().newInstance();
//获取到成员方法
Method m = clazz.getDeclaredMethod(methodName);
m.setAccessible(true);
//调用方法
m.invoke(o);
}}
第二章 注解
第01节 基础理论
什么是注解呢?
注解是用来对程序进行标注说明的。问题是:注释好像也是这么回事啊?
(1)注释是 给程序员(人)去看的
(2)注解是 给程序(JVM)去看的
注解有什么作用呢?
1. 可以去校验程序正确性
2. @Override 校验是否是正确的方法重写,作用在方法上面的
3. @FunctionalInterface 校验是否是正确的函数式接口,作用在接口上面的
4. 2. 可以去辅助程序使用,说明程序
5. @SuppressWarnings("all") 压制警告,取消波浪线,作用在类,接口,方法上面
6. @author 表示作者的信息,作用在注释当中
7. @since 表示版本信息,作用在注释当中
第02节 自定义注解
操作步骤
1. 写一个注解,修饰符是 Annotation 2. 添加元注释3. 使用注解
定义注解
//这就是一个注解(添加元注解)
//@Retention(RetentionPolicy.RUNTIME) 表示当前的注解在什么地方生效:运行时生效
//@Target(ElementType.TYPE) 表示当前的注解可以作用在什么地方:作用在类上面
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ZhuJie {}
使用注解
@ZhuJiepublic class LeiOne {}
我们可以去反编译注解,查看具体底层的实现。
第03节 常见的元注解
什么是元注解呢?
给我们自定义的注解,去添加注解的过程。这里添加的注解,我们叫做元注解。
(作用:指明注解的使用范围、注解的生命周期)
四类常见的注解
@Target:用于指定自定义注解的使用范围。
ElementType.FIELD:应用于全局属性
ElementType.METHOD:应用于方法
ElementType.PARAMETER:应用于方法的参数
ElementType.TYPE:应用于类、接口或者枚举声明
------------------------------------------------------------
@Retention:用于修饰自定义注解的生存周期,或者可以保留多久
RetentionPolicy.SOURCE:源代码时保留,其他直接丢弃
RetentionPolicy.CLASS:默认值,编译器将把注解记录在class文件中,程序运行时,不会留下
RetentionPolicy.RUNTIME:编译器将把注解记录在class文件中,当运行java程序时,
虚拟机保留注解,程序可以通过反射获取该注解;
------------------------------------------------------------
@Documented:执行javadoc命令时,被该元注解修饰的自定义注解也会生成在文档中
------------------------------------------------------------
@Inherited:如果父类所使用的注解有
@Inherited修饰,则子类可以继承该注解,否则不能继承。
第04节 注解解析
定义注解
//自己定义的注解。本质是:接口,接口里面有什么?
//添加元注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ZhuJie {
//定义抽象方法。(在注解里面叫做属性)
String username();
//定义抽象方法。(在注解当中叫做属性)
String password();
}
使用注解
//自己定义的一个类
@ZhuJie(username = "zhangsan",password = "1234")
public class User {}
解析注解
//测试类(注解解析)
@SuppressWarnings("all")
public class Test {
public static void main(String[] args) {
//获取到注解上面的数据值
//我们可以拿到这个类的字节码对象
Class userClass = User.class;
//通过字节码,可以得到注解的对象
Annotation a = userClass.getDeclaredAnnotation(ZhuJie.class);
//向下转型
ZhuJie zhu = (ZhuJie) a;
System.out.println(zhu.username());
//zhangsan
System.out.println(zhu.password());
//1234
}}
第05节 综合案例
马类
package blb.chc07;public class Ma {
public void eat(){
System.out.println("马吃草");
}}
鹿类
package blb.chc07;public class Lu {
public void tiao(){
System.out.println("鹿儿跳");
}}
注解类
//自定义的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Zhujie {
String className();
String methodName();
}
测试类
//测试类
@SuppressWarnings("all")
public class Test {
//给方法添加注解
@Zhujie(className = "blb.chc07.Lu",methodName = "tiao")
public static void main(String[] args) throws Exception{
//解析注解,读取注解上面的信息
Method mainMethod = Test.class.getDeclaredMethod("main", String[].class);
Zhujie zhu = mainMethod.getDeclaredAnnotation(Zhujie.class);
//获取数据
String className = zhu.className();
String methodName = zhu.methodName();
//采用反射去调用方法
Class<?> clazz = Class.forName(className);
Object o = clazz.getDeclaredConstructor().newInstance();
Method m = clazz.getDeclaredMethod(methodName);
m.setAccessible(true);
m.invoke(o);
}}
第06节 两个细节
细节一:value 属性可省略
注意:什么时候可以省略 value 呢?
有且仅有一个属性,这个属性的名称叫做 value的时候,
在使用注解的时候,可以省略value不写。
注意:如果其他的属性,指定了默认值,只有 value 没有指定默认值的情况下,
也可以省略 value 不写
案例
数组的情况
细节二:属性的数据类型和默认值
对 注解当中的 属性他的数据类型有要求:
1、八种基本数据类型
2、字符串 String
3、枚举 enum以上数据类型以及他们的 数组形式都可以
可以去定义默认值
第07节 自定义单元测试
需求
给一些 无参数,无返回值的方法,添加注解。
1. 如果有注解的方法,则运行起来。2. 没有注解的方法,则不让其运行。
代码
注解类
//自定义的单元测试的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {}
人类
public class Person {
@MyTest
public void up(){
System.out.println("up...");
}
public void down(){
System.out.println("down...");
}
@MyTest
public void left(){
System.out.println("left...");
}
@MyTest
public void right(){
System.out.println("right...");
}}
测试类
//测试类
@SuppressWarnings("all")
public class Test {
//思路:通过反射,获取到类的所有的方法对象
//再去根据每一个方法的对象,判断是否加上了注解
//如果有注解则运行,没有就不运行。
public static void main(String[] args) throws Exception{
Class clazz = Person.class;
Object o = clazz.getDeclaredConstructor().newInstance();
//获取到Person里面的所有方法,包括私有方法。
Method[] methodArray = clazz.getDeclaredMethods();
//循环遍历数组,获取到每一个元素
for (Method m : methodArray) {
//判断,方法上面是否添加注解 MyTest
if (m.isAnnotationPresent(MyTest.class)){
//调用方法
m.invoke(o);
}
}
}}