1.反射
每个类都有一个唯一的类对象,该对象是 java.lang.Class
类型。【是 Java 类的元数据(metadata)对象,包含了该类的结构信息和其他相关数据】
获取类对象
1.什么是类对象
public class Daughter extends Parent{ }
public class Son extends Parent { }
Daughter 、Son 都是 parent对象,区别不同的名称,年龄,速度。
Parent 和 Person都是类,区别在于不同方法,不同属性。
类对象,就是描述类有什么属性,什么方法的。
2.获取类对象
3种方式
1. Class.forName(String className)
2. 类名.class
3. getClass()
方法
在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样的。
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("com.test.example.Parent");
System.out.println(clazz.getName()); //com.test.example.Parent
Class<Parent> pClass = Parent.class;
System.out.println(pClass.getName()); //com.test.example.Parent
Parent pp=new Parent();
Class<? extends Parent> methordClass = pp.getClass();
System.out.println(methordClass.getName()); //com.test.example.Parent
System.out.println(clazz ==pClass); //true
}
3.获取类对象的时候,会导致类属性被初始化
为 Parent 类增加一个静态属性,并且在静态初始化块里进行初始化。
按照2中再次执行。无论什么途径获取类对象,都会导致静态属性被初始化,而且只会执行一次。只会显示 [一句 parent静态代码块!]
(除了直接使用 Class c = Parent.class 这种方式,这种方式不会导致静态属性被初始化)
总结:
静态代码块初始化就一次!
类名.class 方式不会执行静态代码块!
创建对象
与传统的new 关键字 来获取对象方式不同
反射机制,先获取Parent 的 类对象,再由 类对象获取构造器对象,然后通过构造器对象创建一个对象。
try {
// 类对象
Class<?> pClass = Class.forName("com.test.example.Parent");
//构造器
Constructor pc= pClass.getConstructor();
//构造器实例化
Parent parent = (Parent) pc.newInstance();
System.out.println(parent);
} catch (Exception e) {
throw new RuntimeException(e);
}
访问属性
Parent 中 添加两个属性
private int age; // 私有 public String smart; // 公用public String getSmart() { return this.smart; } public int getAge() { return this.age; }//=================================
try { //对象-实例 Parent pObj=new Parent(); // 类对象 Class<?> pClass = pObj.getClass(); //获取 public 修饰的smart字段 Field smart = pClass.getField("smart"); System.out.println(smart.getName());// 字段名 System.out.println(smart.getType()); // 字段属性 // 获取private修饰的 age字段 Field age = pClass.getDeclaredField("age"); System.out.println(age.getName()); // 因为age字段属性private set方法赋值会报错。必须setAccessible 赋值为true age.setAccessible(true); age.set(pObj, 23); System.out.println(pObj.getAge()); } catch (Exception e) { throw new RuntimeException(e); }
1.对于private修饰的成员,需要使用setAccessible(true)才能修改。
getField和getDeclaredField的区别
这两个方法都是用于获取字段
getField 只能获取public的,包括从父类继承来的字段。
getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段。
调用方法
Parent 中 添加两个set方法
public void setSmart(String smart) { // 公共 this.smart = smart; } private void setAge(int age) { //私有 this.age = age; }//=====================================================
try { //对象-实例 Parent pObj = new Parent(); // 类对象 Class<?> pClass = pObj.getClass(); //获取 public 修饰的 方法 Method setSmart = pClass.getMethod("setSmart", String.class); //输出setSmart方法的返回值,这里返回 null,因为 setSmart 没有返回值) Object obj = setSmart.invoke(pObj, "平凡"); System.out.println(obj); // null System.out.println(pObj.getSmart()); // 平凡 // private 修饰的方法 Method ageSet = pClass.getDeclaredMethod("setAge", int.class); // 私有 设置可被访问 ageSet.setAccessible(true); ageSet.invoke(pObj, 12); System.out.println(pObj.getAge()); // 12 } catch (Exception e) { throw new RuntimeException(e); }
有什么用 [约定优于配置,配置优于实现]
反射非常强大,但是学习了之后,会不知道该如何使用,反而觉得还不如直接调用方法来的直接和方便。
需要学习了Spring 的依赖注入,反转控制之后,才会对反射有更好的理解
简单举例:两个业务类 Service1 + Service2
public class Service1 {
public void doService1(){
System.out.println("业务方法1");
}
}
package reflection;
public class Service2 {
public void doService2(){
System.out.println("业务方法2");
}
}
非反射方式
public class Test {
public static void main(String[] args) {
// new Service1().doService1();
new Service2().doService2();
}
}
当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果
反射方式
准备一个spring.txt文件,里面存类名+ 调用的方法名
class=reflection.Service1 # 类的全名称
method=doService1 #方法名
测试类代码
@SneakyThrows
public static void main(String[] args) {
//从spring.txt中获取类名称和方法名称
File springConfigFile = new File("e:\\project\\getwork\\spring.txt");
Properties springConfig = new Properties();
springConfig.load(new FileInputStream(springConfigFile));
String className = springConfig.getProperty("class"); // 返回结果只是String
String methodName = (String) springConfig.get("method"); // 返回结果Object,需要强转
//根据类名称获取类对象
Class clazz = Class.forName(className);
//根据方法名称,获取方法对象
Method m = clazz.getMethod(methodName);
//获取构造器
Constructor c = clazz.getConstructor();
//根据构造器,实例化出对象
Object obj = c.newInstance();
//调用对象的指定方法
m.invoke(obj);
}
2.注解
将注解与反射放一起,因为反射用于解析注解中的信息。
基本内置注解
@Override 用在方法上,表示这个方法重写了父类的方法,如toString()。
如果父类没有这个方法,那么就无法编译通过
@Deprecated 表示这个方法已经过期,不建议开发者使用。(暗示在将来某个不确定的版本,就有可能会取消掉)
@SuppressWarnings Suppress英文的意思是抑制的意思,这个注解的用处是忽略警告信息。
比如大家使用集合的时候,有时候为了偷懒,会不写泛型,像这样:
List heros = new ArrayList();那么就会导致编译器出现警告,而加上
@SuppressWarnings({ "rawtypes", "unused" })
就对这些警告进行了抑制,即忽略掉这些警告信息。
@SuppressWarnings 有常见的值,分别对应如下意思
1.deprecation:使用了不赞成使用的类或方法时的警告(使用@Deprecated使得编译器产生的警告);
2.unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 关闭编译器警告
3.fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
4.path:在类路径、源文件路径等中有不存在的路径时的警告;
5.serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
6.finally:任何 finally 子句不能正常完成时的警告;
7.rawtypes 泛型类型未指明
8.unused 引用定义了,但是没有被使用
9.all:关于以上所有情况的警告。
@FunctionalInterface这是Java1.8 新增的注解,用于约定函数式接口。
函数式接口概念: 如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口称为函数式接口。函数式接口其存在的意义,主要是配合 Lambda表达式来使用。
自定义注解
1.非注解方式DBUtil
通常来讲,在一个基于JDBC开发的项目里,都会有一个DBUtil这么一个类,在这个类里统一提供连接数据库的IP地址,端口,数据库名称, 账号,密码,编码方式等信息。
举个例子:获取一个连接数据库test的连接Connection实例
package util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
static String ip = "127.0.0.1";
static int port = 3306;
static String database = "test";
static String encoding = "UTF-8";
static String loginName = "root";
static String password = "admin";
static{
try {
Class.forName("com.mysql.jdbc.Driver");// 版本8.0之后用 com.mysql.cj.jdbc.Driver
// 自动加载驱动(对于 Java 6 或更高版本不需要显式加载) 无需这段代码意思
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
String url = String.format("jdbc:mysql://%s:%d/%s?characterEncoding=%s", ip, port, database, encoding);
return DriverManager.getConnection(url, loginName, password);
}
public static void main(String[] args) throws SQLException {
System.out.println(getConnection());
}
}
2.自定义注解@JDBCConfig
创建注解类型不使用class也不使用interface,而是使用@interface
public @interface JDBCConfig
元注解:
@Target({METHOD,TYPE}) 表示这个注解可以用用在类/接口上,还可以用在方法上
@Retention(RetentionPolicy.RUNTIME) 表示这是一个运行时注解,即运行起来之后,才获取注解中的相关信息,而不像基本注解如@Override那种不用运行,在编译时eclipse就可以进行相关工作的编译时注解。
@Inherited 表示这个注解可以被子类继承
@Documented 表示当执行javadoc的时候,本注解会生成相关文档
package anno;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({METHOD,TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface JDBCConfig {
String ip();
int port() default 3306;
String database();
String encoding();
String loginName();
String password();
}
3.注解方式DBUtil
有了自定义注解@JDBCConfig之后,我们就把非注解方式DBUtil改造成为注解方式DBUtil。
package util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import anno.JDBCConfig;
//相当于赋值
@JDBCConfig(ip = "127.0.0.1", database = "test", encoding = "UTF-8", loginName = "root", password = "admin")
public class DBUtil {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException, NoSuchMethodException, SecurityException {
// 相当于解析 取值
// 通过反射,获取这个DBUtil这个类上的注解对象
JDBCConfig config = DBUtil.class.getAnnotation(JDBCConfig.class);
String ip = config.ip();
int port = config.port();
String database = config.database();
String encoding = config.encoding();
String loginName = config.loginName();
String password = config.password();
String url = String.format("jdbc:mysql://%s:%d/%s?characterEncoding=%s", ip, port, database, encoding);
return DriverManager.getConnection(url, loginName, password);
}
public static void main(String[] args) throws NoSuchMethodException, SecurityException, SQLException {
Connection c = getConnection();
System.out.println(c);
}
}
元注解
元数据在英语中对应单词 metadata:解释为【为其他数据提供信息的数据】
这样元注解就好理解了,元注解 meta annotation用于注解 自定义注解 的注解。
元注解有这么几种:
@Target
@Retention
@Inherited
@Documented
@Repeatable (java1.8 新增)
@Target 表示注解的位置
可以选择的位置列表如下:
ElementType.TYPE:能修饰类、接口或枚举类型
ElementType.FIELD:能修饰成员变量
ElementType.METHOD:能修饰方法
ElementType.PARAMETER:能修饰参数
ElementType.CONSTRUCTOR:能修饰构造器
ElementType.LOCAL_VARIABLE:能修饰局部变量
ElementType.ANNOTATION_TYPE:能修饰注解
ElementType.PACKAGE:能修饰包
@Retention 表示生命周期
RetentionPolicy.SOURCE: 注解只在源代码中存在,编译成class之后,就没了。@Override 就是这种注解。
RetentionPolicy.CLASS: 注解在java文件编程成.class文件后,依然存在,但是运行起来后就没了。@Retention的默认值,即当没有显式指定@Retention的时候,就会是这种类型。
RetentionPolicy.RUNTIME: 注解在运行起来之后依然存在,程序可以通过反射获取这些信息,自定义注解@JDBCConfig 就是这样。
@Inherited 表示该注解具有继承性。
@Documented 在用javadoc命令生成API文档后,DBUtil的文档里会出现该注解说明
@Repeatable 是 Java 8 引入的一个注解,允许你在同一个元素(类、方法、字段等)上使用多个相同类型的注解
@Repeatable
注解是一个元注解,它标注在容器注解上,容器注解是用来包裹重复的注解的。
1. 创建重复注解
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) @Repeatable(Supervisors.class) // 使用 @Repeatable 注解指定容器注解 public @interface Supervisor { String value(); }
2. 创建容器注解
容器注解是用来存储多个相同类型注解的注解。它必须使用
@interface
定义,并且使用@Repeatable
来标注。@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Supervisors { Supervisor[] value(); // 容器注解内部包含多个 Supervisor 注解 }
3. 使用重复注解
public class Employee { @Supervisor("Alice") @Supervisor("Bob") public void work() { System.out.println("Working..."); } }
4. 访问重复注解
import java.lang.annotation.Annotation; import java.lang.reflect.Method; public class Test { public static void main(String[] args) throws Exception { Method method = Employee.class.getMethod("work"); // 获取所有 Supervisor 注解 Supervisor[] supervisors = method.getAnnotationsByType(Supervisor.class); // 输出注解的值 for (Supervisor supervisor : supervisors) { System.out.println(supervisor.value()); } } }
3.hutool
在大家日常工作中,都常常会做如下这些非常繁琐的工作:
日期与字符串转换
文件操作
转码与反转码
随机数生成
压缩与解压
编码与解码
CVS文件操作
缓存处理
加密解密
定时任务
邮件收发
二维码创建
FTP 上传与下载
图形验证码生成
等等等等
工欲善其事必先利其器! Hutool 就是这么一款超级强力的工具类。
任何第三方jar包先导进项目,才能使用。
编码工具
public static void main(String[] args) {
// 16进制工具
String encodeStrs = HexUtil.encodeHexStr("nihao明天!");
System.out.println(encodeStrs);
String decodeStr = HexUtil.decodeHexStr(encodeStrs);
System.out.println(decodeStr);
//转义工具
String s1 = "<script>location.href='http://baidu.com';</script>";
String s2 = EscapeUtil.escapeHtml4(s1);//转义
String s3 = EscapeUtil.unescapeHtml4(s2); // 反转
//hash工具 import cn.hutool.core.util.HashUtil;
//URL工具
URL url = URLUtil.toUrlForHttp("http:/www.baidu.com");
System.out.println(url);
String url2 = "https://blog.csdn.net/weixin_43543654/article/details/130481664";
String urle = URLUtil.encode(url2);
String urld = URLUtil.decode(urle);
System.out.println(urle);
System.out.println(urld);
//Base32_64 转换
String charset = "utf-8";
String content = "模拟课程java教程";
String code32 = Base32.encode(content, charset); //编码
content = Base32.decodeStr(code32, charset); // 解码
String code64 = Base64.encode(content, charset); //64 编码
content = Base64.decodeStr(code64, charset); // 64解码
}