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的时候更加详细的聊

注解

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

// 就是这个东西
@Override
public 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;
    }

    @Override
    public java.lang.String toString() {
        return "Emp{" +
                "azk=" + azk +
                ", id=" + id +
                ", name=" + name +
                ", birthd=" + birthd +
                '}';
    }

    @Override
    public 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());    // 变量名称 azk
    System.out.println("变量类型:" + field.getType());    // 变量类型 class java.lang.String
    System.out.println("变量修饰符:" + field.getModifiers());   // 修饰符 1
    System.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才能够获取,否则会报错,
// 在获取完之后,推荐将其设置为false
Field 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依赖

<dependency>
    <groupId>top.zopx</groupId>
    <artifactId>tools-boot-starter</artifactId>
    <version>1.1.4</version>
</dependency>

具体实现方式

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

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];
        }
    }

    @Override
    public 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);
    }

    @Override
    public int update(T entity, K id) {
        return 0;
    }

    @Override
    public int deleteById(K id) {
        return 0;
    }

    @Override
    public List<T> getList() {
        return null;
    }

    @Override
    public 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整全套资源下载地址:https://download.csdn.net/download/qq_27595745/70898746 【完整课程列表】 完整版 Java基础入门教程 Java程序语言设计 01 java语言基础 Java语言概述(共24页).ppt 完整版 Java基础入门教程 Java程序语言设计 02 java语法基础 Java语法基础(共32页).ppt 完整版 Java基础入门教程 Java程序语言设计 02 java语法基础 Java语法基础2(共24页).ppt 完整版 Java基础入门教程 Java程序语言设计 03 java对象 集合框架(共18页).ppt 完整版 Java基础入门教程 Java程序语言设计 03 java对象 类和对象1(共23页).ppt 完整版 Java基础入门教程 Java程序语言设计 03 java对象 类与对象2(共23页).ppt 完整版 Java基础入门教程 Java程序语言设计 03 java对象 类与对象3(共24页).ppt 完整版 Java基础入门教程 Java程序语言设计 03 java对象 类与对象4(共35页).ppt 完整版 Java基础入门教程 Java程序语言设计 04 IO流 输入输出流(共31页).ppt 完整版 Java基础入门教程 Java程序语言设计 05 GUI AWT 事件模型(共27页).ppt 完整版 Java基础入门教程 Java程序语言设计 05 GUI GUI 编程(共37页).ppt 完整版 Java基础入门教程 Java程序语言设计 05 GUI Swing(共41页).ppt 完整版 Java基础入门教程 Java程序语言设计 06 异常处理 异常和垃圾收集(共27页).ppt 完整版 Java基础入门教程 Java程序语言设计 07 网络编程 JDBC(共21页).ppt 完整版 Java基础入门教程 Java程序语言设计 07 网络编程 反射机制(共23页).ppt 完整版 Java基础入门教程 Java程序语言设计 07 网络编程 网络编程(共32页).ppt 完整版 Java基础入门教程 Java程序语言设计 07 网络编程 线程(共23页).ppt

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值