AI智能
改变未来

Java基础系列:反射


俗世游子:专注技术研究的程序猿

说在前面的话

作为Java中又古老又基础的一种技术,我觉得我们还是有必要了解一下

反射

反射就是通过二进制字节码分析类的属性和方法的技术

类加载过程

loading

  • 将class文件通过
    java

    加载到内存中,其实加载的是class中的二进制数据

  • 创建
    Class

    对象,并且将引用指向开辟的二进制内存空间

linking

这里分为三个步骤:

  • 校验class文件是否符合标准

通过sublime打开class文件,可以看到16进制格式,这里不写那么多了,大家自己打开一下就知道了

// 前4个字节标准格式, 后面是jdk的版本号,后面还有常量池数量,常量池等等cafe babe 0000 0034
  • 成员变量赋默认值
public class T {int i = 8;public static void main(String[] args) {}}

这里的

i

在这一步会赋值成int的默认值

0

,也就是

int i = 0;

  • 常量池中用到的符号引用转换成可使用的内存内容

initializing

这里是类真正初始化完成的过程,在这一步:

  • 成员变量赋初始值
  • <init>()

    方法被调用,整个

    Class

    加载完成

看不懂没关系,这里只是提一下。

真正loading的过程其实还有很多过程,比如‘类加载器’,‘双亲委派机制’等等在jvm的时候更加详细的聊

注解

@

开头,标记在类,字段,方法等地方上的描述,就是注解,比如:

// 就是这个东西@Overridepublic int hashCode() {return super.hashCode();}

这里的

@Override

就是注解,是jdk提供的。注解我们是可以自定义的,我们通过

@Override

来看看自定义注解包含那些元素

@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}

首先,通过

@interface

来修饰,然后还有其他东西

  • @Target

@Target

表示我们定义的注解的作用范围,通过

ElementType

可以查看到全部范围:

public enum ElementType {/** 作用在类,接口,枚举类上 */TYPE,/** 表示字段,变量,包括枚举属性 */FIELD,/** 方法上 */METHOD,/** 参数 */PARAMETER,/** 构造方法 */CONSTRUCTOR,/** 局部变量 */LOCAL_VARIABLE,/** 注解类型上 */ANNOTATION_TYPE,/** 不作用在一般类中,作用在固定文件package-info.java中 */PACKAGE,/*** 类型参数声明** @since 1.8*/TYPE_PARAMETER,TYPE_USE}

作用范围是可以定义多个的

  • @Retention

指示带注释类型的注释将保留多长时间。 如果注释类型声明上没有保留注释,则保留策略默认为

RetentionPolicy.CLASS

,包含以下部分:

public enum RetentionPolicy {/*** 标注注解被编译器丢弃*/SOURCE,/*** 保留标注注解,但是不会被jvm加载*/CLASS,/*** 在jvm运行期间都保留,所以也可以通过反射来得到被标注的注解*/RUNTIME}

上面两个是最重要的,还有其他两个也看一下吧:

  • @Documented

生成javadoc文档会使用到

  • @Inherited

子类可以继承父类中的该注解

反射具体方法

基础对象

public class Person {private String kkals;public String getKkals() {return kkals;}public void setKkals(String kkals) {this.kkals = kkals;}}public class Emp extends Person implements Comparable<Emp>{public String azk = \"121\";private Long id;private String name;private Date birthd;private Emp(Long id) {this.id = id;}public Emp() {}public Emp(Long id, String name, Date birthd) {this.id = id;this.name = name;this.birthd = birthd;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Date getBirthd() {return birthd;}public void setBirthd(Date birthd) {this.birthd = birthd;}@Overridepublic java.lang.String toString() {return \"Emp{\" +\"azk=\" + azk +\", id=\" + id +\", name=\" + name +\", birthd=\" + birthd +\'}\';}@Overridepublic int compareTo(Emp o) {return 0;}}

获取Class对象

private static void getClazz() throws ClassNotFoundException {// 通过完整包名 + 类名的方式加载Class对象Class<?> clazz = Class.forName(\"java.lang.String\");printInfo(clazz);// 通过类.class的方式加载Class对象clazz = Emp.class;printInfo(clazz);// 这种方式,有点low,这么写的话idea还会报黄,然后就会修改成类.class的方式clazz = new Emp().getClass();printInfo(clazz);// 不适合普通对象Class<Integer> type = Integer.TYPE;printInfo(type);}private static void printInfo(Class<?> clazz) {System.out.println(\"=======================================\");System.out.println(\"包名:\" + clazz.getPackage());System.out.println(\"类全名:\" + clazz.getName());System.out.println(\"类名:\" + clazz.getSimpleName());System.out.println(\"符合java规范的名称:\" + clazz.getCanonicalName());System.out.println(\"类修饰符:\" + clazz.getModifiers());System.out.println(\"=======================================\");}

上面四种方式,更多的是推荐采用第一和第二种方式

关于类修饰符,如果我们想判断这个类或属性是不是我们想要的,我们可以这样对比:

(clazz.getModifiers() & Modifier.PRIVATE) != 0;

返回true,表示是指定的修饰符修饰的,否则就不是

获取成员变量

private static void getField()  throws ClassNotFoundException{Class<?> clazz = Class.forName(\"Emp\");// 主要方法Field[] fields = clazz.getFields();for (Field field : fields) {printFiledInfo(field);}System.out.println(\"==================华丽丽的分隔符=====================\");// 主要方法Field[] declaredFields = clazz.getDeclaredFields();for (Field declaredField : declaredFields) {printFiledInfo(declaredField);}}private static void printFiledInfo(Field field) {System.out.println(\"=======================================\");System.out.println(\"变量名:\" + field.getName());    // 变量名称 azkSystem.out.println(\"变量类型:\" + field.getType());    // 变量类型 class java.lang.StringSystem.out.println(\"变量修饰符:\" + field.getModifiers());   // 修饰符 1System.out.println(\"=======================================\");}

关于

getFields()

getDeclaredFields()

的对比:

  • 前者只能获取Class对象中
    public

    修饰的变量,其他的都无法修饰

  • 后者可以获取到Class对象中所有类型的变量,但是无法获取到父类的变量

获取变量的数据

// public类型的话,可以直接获取Field fieldId = clazz.getDeclaredField(\"azk\");final Object o = clazz.newInstance();System.out.println(fieldId.get(o));// private类型需要将setAccessible设置为true才能够获取,否则会报错,// 在获取完之后,推荐将其设置为falseField fieldId = clazz.getDeclaredField(\"id\");final Object o = clazz.newInstance();// 重要fieldId.setAccessible(true);System.out.println(fieldId.get(o));// 重要fieldId.setAccessible(false);

获取普通方法

private static void getMethods() {Class<Emp> empClass = Emp.class;// 可以获取到对象中所有的对象,包括父类对象和Object中的方法Method[] methods = empClass.getMethods();for (Method method : methods) {System.out.println(method.getName());System.out.println(method.getReturnType());System.out.println(method.getModifiers());}System.out.println(\"======================================\");// 只能获取到当前类中的方法methods = empClass.getDeclaredMethods();for (Method method : methods) {System.out.println(method.getName());System.out.println(method.getReturnType());System.out.println(method.getModifiers());}}

操作方法

// 如果有参数,那么getMethod紧随其后跟上参数的类型Method setId = empClass.getMethod(\"setId\", Long.class);setId.invoke(emp, 1L);// 这里是没有参数Method method = empClass.getMethod(\"getId\");System.out.println(method.invoke(emp));

如果不需要父类和Object方法的话,那么更多的是推荐采用

getDeclaredMethod()

方法,

获取构造方法

Constructor<?>[] constructors = clazz.getConstructors();for (Constructor<?> constructor : constructors) {System.out.println(constructor.getName());}System.out.println(\"========================\");constructors = clazz.getDeclaredConstructors();for (Constructor<?> constructor : constructors) {System.out.println(constructor.getName());}

根据构造器生成对象

Constructor<?> constructor = clazz.getDeclaredConstructor(Long.class, String.class, Date.class);System.out.println(((Emp)constructor.newInstance(1L, \"kkals\", new Date())).toString());

获取注解

// 获取到类上全部的注解Annotation[] annotations = clazz.getDeclaredAnnotations();for (Annotation annotation : annotations) {System.out.println(annotation);}// 获取到指定的注解对象Annotation declaredAnnotation = clazz.getDeclaredAnnotation(Annotation.class);

判断是否存在指定注解

if (clazz.isAnnotationPresent(Annotation.class)) {System.out.println(\"存在该注解\");}

实例:ORM框架的简单实现

定义设置表名注解

@Target(value = ElementType.TYPE)@Retention(value = RetentionPolicy.RUNTIME)public @interface Table {public String tableName() default \"\";}

定义设置列名注解

@Target(value = {ElementType.FIELD, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface Column {String columnName() default \"\";}

定义忽略列名注解

@Target(value = ElementType.FIELD)@Retention(value = RetentionPolicy.RUNTIME)public @interface Transate {}

工具类: 缓存类

public class EntityCacheUtil {private static final Map<Class<?>, Field[]> FIELD_MAP = new HashMap<>();private static final Map<Class<?>, String> TABLE_NAME_MAP = new HashMap<>();static {try {EntityCacheUtil.init();} catch (Exception e) {e.printStackTrace();}}/*** 获取表名*/public static String getTableName(Class<?> clazz) {String tableName = TABLE_NAME_MAP.get(clazz);// 这里是在判断当前tableName是否不为空if (StringUtils.isNotBlank(tableName)) {return tableName;}Table annotation = clazz.getDeclaredAnnotation(Table.class);if (null != annotation) {tableName = annotation.tableName();} else {tableName = toLine(clazz.getSimpleName());}TABLE_NAME_MAP.put(clazz, tableName);return tableName;}/*** 将驼峰标识的类名转换成下划线的样子*/public static String toLine(String simpleName) {final char[] chars = simpleName.toCharArray();StringBuilder sb = new StringBuilder();for (char c : chars) {if (Character.isUpperCase(c)) {sb.append(\"_\");}sb.append(c);}if (sb.toString().startsWith(\"_\")) {sb.delete(0, 1);}return sb.toString().toLowerCase();}/*** 获取到该类下所有的字段* @throws IOException* @throws ClassNotFoundException*/public static void init() throws IOException, ClassNotFoundException {// 包名修改成自己的final List<Class<?>> fileList = PackageUtil.INSTANCE.getFileList(\"zopx.top.study.reflect.entity\", Serializable.class, true);fileList.forEach(item -> FIELD_MAP.put(item, item.getDeclaredFields()));}public static Map<Class<?>, Field[]> getFieldMap() {return FIELD_MAP;}}

关于

PackageUtil

这个类,这个类主要是扫描某个包下的所有的Class对象并缓存起来,具体代码就不贴出来了,太长,下面给出pom依赖

&lt;dependency&gt;&lt;groupId&gt;top.zopx&lt;/groupId&gt;&lt;artifactId&gt;tools-boot-starter&lt;/artifactId&gt;&lt;version&gt;1.1.4&lt;/version&gt;&lt;/dependency&gt;

具体实现方式

先定义接口,这基本就是我们最常用的方法了

public interface BaseDao<T extends Serializable, K> {/*** 保存*/int save(T entity);/*** 修改*/int update(T entity, K id);/*** 删除*/int deleteById(K id);/*** 获取列表*/List<T> getList();/*** 通过ID获取详情*/T getById(K id);}

实现类

public abstract class BaseDaoImpl<T extends Serializable, K> implements BaseDao<T, K> {Class<?> clazz;public BaseDaoImpl() {// 获取到类中泛型对象的类型Type type = this.getClass().getGenericSuperclass();if (type instanceof ParameterizedType) {ParameterizedType p = (ParameterizedType) type;clazz = (Class<?>) p.getActualTypeArguments()[0];}}@Overridepublic int save(T entity) {// 1. 获取表名和列String tableName = EntityCacheUtil.getTableName(entity.getClass());Field[] fields = EntityCacheUtil.getFieldMap().get(entity.getClass());// insert into user_acc(user_id, login_name, login_pwd) value(?,?,?);StringBuilder sql =new StringBuilder(\"insert into \").append(tableName).append(\"(\");List<Object> valueList = new ArrayList<>(fields.length);for (Field field : fields) {// 如果字段中存在Transate 表示跳过设置Transate transate = field.getDeclaredAnnotation(Transate.class);if (null != transate)continue;Column column = field.getDeclaredAnnotation(Column.class);String columnName = \"\";if (null == column) {// 说明该字段上没有设置字段名,那么就修改驼峰columnName = EntityCacheUtil.toLine(field.getName());} else {columnName = column.columnName();}sql.append(columnName).append(\",\");// 通过field获取值field.setAccessible(true);try {valueList.add(field.get(entity));} catch (IllegalAccessException e) {System.out.println(e.getMessage());}field.setAccessible(false);// 第二种方式:就是通过get方法调用invoke来得到具体的数据//            try {//                Method method = entity.getClass().getMethod(getMethodName(field.getName()));//                valueList.add(method.invoke(entity));//            } catch (Exception e) {//                System.out.println(e.getMessage());//            }}sql.deleteCharAt(sql.length() - 1).append(\") value (\");valueList.forEach(value -> sql.append(\"?,\"));sql.deleteCharAt(sql.length() - 1);sql.append(\")\");System.out.println(sql.toString());System.out.println(valueList);return 0;}private String getMethodName(String fieldName) {return \"get\" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);}@Overridepublic int update(T entity, K id) {return 0;}@Overridepublic int deleteById(K id) {return 0;}@Overridepublic List<T> getList() {return null;}@Overridepublic T getById(K id) {System.out.println(clazz.getCanonicalName());return null;}}

其实能看到,我们在最后只是打印出了sql语句,没有其他操作(不懂那个sql意思也不要紧),接下来我们就会聊到MySQL和JDBC,到时候我们再回来继续完善这个例子

其他的方法没有写,通过

save()

一个方法也能将上面的东西串起来了,大家可以自己尝试着将其他方法补充完整

实际调用方式

public interface UserAccDao extends BaseDao<UserAcc, Long> {}public class UserAccDaoImpl extends BaseDaoImpl<UserAcc, Long> implements UserAccDao {}public class Test {public static void main(String[] args) {UserAccDao userAccDao = new UserAccDaoImpl();UserAcc entity = new UserAcc();entity.setUserId(1L);entity.setLoginName(\"123\");entity.setLoginPwd(\"12345\");userAccDao.save(entity);userAccDao.getById(1L);}}// insert into user_acc(user_id,login_name,login_pwd) value (?,?,?)// [1, 123, 12345]// zopx.top.study.reflect.entity.UserAcc

最后的话

好了,反射到这里也就结束了,下面我们聊 MySQL和JDBC

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Java基础系列:反射