2023-03-13  阅读(3)
原文作者:青花鱼罐头 原文地址:https://blog.csdn.net/qq_32782279/article/details/107826317

1. 概述

① MyBatis 为简化配置文件提供了别名机制,该机制是类型转换模块的主要功能之一。

② 类型转换模块的另一个功能是实现 JDBC 类型与 Java 类型之间的转换,该功能在为 SQL 语句绑定实参以及映射查询结果集时都会涉及:

在为 SQL 语句绑定实参时,会将数据由 Java 类型转换成 JDBC 类型。
而在映射结果集时,会将数据由 JDBC 类型转换成 Java 类型

2. TypeHandler

org.apache.ibatis.type.TypeHandler ,类型转换处理器。代码如下:

    public interface TypeHandler<T> {
    
        /**
         * 设置 PreparedStatement 的指定参数
         *
         * Java Type => JDBC Type
         *
         * @param ps PreparedStatement 对象
         * @param i 参数占位符的位置
         * @param parameter 参数
         * @param jdbcType JDBC 类型
         * @throws SQLException 当发生 SQL 异常时
         */
        void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    
        /**
         * 获得 ResultSet 的指定字段的值
         *
         * JDBC Type => Java Type
         *
         * @param rs ResultSet 对象
         * @param columnName 字段名
         * @return 值
         * @throws SQLException 当发生 SQL 异常时
         */
        T getResult(ResultSet rs, String columnName) throws SQLException;
    
        /**
         * 获得 ResultSet 的指定字段的值
         *
         * JDBC Type => Java Type
         *
         * @param rs ResultSet 对象
         * @param columnIndex 字段位置
         * @return 值
         * @throws SQLException 当发生 SQL 异常时
         */
        T getResult(ResultSet rs, int columnIndex) throws SQLException;
    
        /**
         * 获得 CallableStatement 的指定字段的值
         *
         * JDBC Type => Java Type
         *
         * @param cs CallableStatement 对象,支持调用存储过程
         * @param columnIndex 字段位置
         * @return 值
         * @throws SQLException
         */
        T getResult(CallableStatement cs, int columnIndex) throws SQLException;
    }

一共有两类方法,分别是:
#setParameter(…) 方法,是 Java Type => JDBC Type 的过程。
#getResult(…) 方法,是 JDBC Type => Java Type 的过程。

202303132325161761.png
左边是 #setParameter(…) 方法,是 Java Type => JDBC Type 的过程,从上往下看。
右边是 #getResult(…) 方法,是 JDBC Type => Java Type 的过程,从下往上看。

2.1 BaseTypeHandler

org.apache.ibatis.type.BaseTypeHandler ,实现 TypeHandler 接口,继承 TypeReference 抽象类,TypeHandler 基础抽象类。

2.1.1 setParameter

setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) 方法,代码如下:

    @Override
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        // <1> 参数为空时,设置为 null 类型
        if (parameter == null) {
            if (jdbcType == null) {
                throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
            }
            try {
                ps.setNull(i, jdbcType.TYPE_CODE);
            } catch (SQLException e) {
                throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
                        "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
                        "Cause: " + e, e);
            }
        // 参数非空时,设置对应的参数
        } else {
            try {
                setNonNullParameter(ps, i, parameter, jdbcType);
            } catch (Exception e) {
                throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
                        "Try setting a different JdbcType for this parameter or a different configuration property. " +
                        "Cause: " + e, e);
            }
        }
    }
2.1.2 getResult

getResult(…) 方法,代码如下:

    // BaseTypeHandler.java
    
    @Override
    public T getResult(ResultSet rs, String columnName) throws SQLException {
        try {
            return getNullableResult(rs, columnName);
        } catch (Exception e) {
            throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
        }
    }
    
    @Override
    public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        try {
            return getNullableResult(rs, columnIndex);
        } catch (Exception e) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);
        }
    }
    
    @Override
    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        try {
            return getNullableResult(cs, columnIndex);
        } catch (Exception e) {
            throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + e, e);
        }
    }

调用 #getNullableResult(…) 抽象方法,获得指定结果的字段值。代码如下:

    public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
    
    public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
    
    public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException
2.2 子类

TypeHandler 有非常多的子类,当然所有子类都是继承自 BaseTypeHandler 抽象类。考虑到篇幅,我们就挑选几个来聊聊。
org.apache.ibatis.type.IntegerTypeHandler ,继承 BaseTypeHandler 抽象类,Integer 类型的 TypeHandler 实现类。代码如下:

2.2.1 IntegerTypeHandler
    // IntegerTypeHandler.java
    
    public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
                throws SQLException {
            // 直接设置参数即可
            ps.setInt(i, parameter);
        }
    
        @Override
        public Integer getNullableResult(ResultSet rs, String columnName)
                throws SQLException {
            // 获得字段的值
            int result = rs.getInt(columnName);
            // 先通过 rs 判断是否空,如果是空,则返回 null ,否则返回 result
            return (result == 0 && rs.wasNull()) ? null : result;
        }
    
        @Override
        public Integer getNullableResult(ResultSet rs, int columnIndex)
                throws SQLException {
            // 获得字段的值
            int result = rs.getInt(columnIndex);
            // 先通过 rs 判断是否空,如果是空,则返回 null ,否则返回 result
            return (result == 0 && rs.wasNull()) ? null : result;
        }
    
        @Override
        public Integer getNullableResult(CallableStatement cs, int columnIndex)
                throws SQLException {
            // 获得字段的值
            int result = cs.getInt(columnIndex);
            // 先通过 cs 判断是否空,如果是空,则返回 null ,否则返回 result
            return (result == 0 && cs.wasNull()) ? null : result;
        }
    }
2.2.2 DateTypeHandler

org.apache.ibatis.type.DateTypeHandler ,继承 BaseTypeHandler 抽象类,Date 类型的 TypeHandler 实现类。代码如下:

    // DateTypeHandler.java
    
    public class DateTypeHandler extends BaseTypeHandler<Date> {
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
                throws SQLException {
            // 将 Date 转换成 Timestamp 类型
            // 然后设置到 ps 中
            ps.setTimestamp(i, new Timestamp(parameter.getTime()));
        }
    
        @Override
        public Date getNullableResult(ResultSet rs, String columnName)
                throws SQLException {
            // 获得 Timestamp 的值
            Timestamp sqlTimestamp = rs.getTimestamp(columnName);
            // 将 Timestamp 转换成 Date 类型
            if (sqlTimestamp != null) {
                return new Date(sqlTimestamp.getTime());
            }
            return null;
        }
    
        @Override
        public Date getNullableResult(ResultSet rs, int columnIndex)
                throws SQLException {
            // 获得 Timestamp 的值
            Timestamp sqlTimestamp = rs.getTimestamp(columnIndex);
            // 将 Timestamp 转换成 Date 类型
            if (sqlTimestamp != null) {
                return new Date(sqlTimestamp.getTime());
            }
            return null;
        }
    
        @Override
        public Date getNullableResult(CallableStatement cs, int columnIndex)
                throws SQLException {
            // 获得 Timestamp 的值
            Timestamp sqlTimestamp = cs.getTimestamp(columnIndex);
            // 将 Timestamp 转换成 Date 类型
            if (sqlTimestamp != null) {
                return new Date(sqlTimestamp.getTime());
            }
            return null;
        }
    
    }
2.2.3 EnumTypeHandler

org.apache.ibatis.type.EnumTypeHandler ,继承 BaseTypeHandler 抽象类,Enum 类型的 TypeHandler 实现类。代码如下:

    // EnumTypeHandler.java
    
    public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
    
        /**
         * 枚举类
         */
        private final Class<E> type;
    
        public EnumTypeHandler(Class<E> type) {
            if (type == null) {
                throw new IllegalArgumentException("Type argument cannot be null");
            }
            this.type = type;
        }
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
            // 将 Enum 转换成 String 类型
            if (jdbcType == null) {
                ps.setString(i, parameter.name());
            } else {
                ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); // see r3589
            }
        }
    
        @Override
        public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
            // 获得 String 的值
            String s = rs.getString(columnName);
            // 将 String 转换成 Enum 类型
            return s == null ? null : Enum.valueOf(type, s);
        }
    
        @Override
        public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            // 获得 String 的值
            String s = rs.getString(columnIndex);
            // 将 String 转换成 Enum 类型
            return s == null ? null : Enum.valueOf(type, s);
        }
    
        @Override
        public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            // 获得 String 的值
            String s = cs.getString(columnIndex);
            // 将 String 转换成 Enum 类型
            return s == null ? null : Enum.valueOf(type, s);
        }
    
    }
2.2.4 EnumOrdinalTypeHandler

org.apache.ibatis.type.EnumOrdinalTypeHandler ,继承 BaseTypeHandler 抽象类,Enum 类型的 TypeHandler 实现类。代码如下:

    public class EnumOrdinalTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
    
        /**
         * 枚举类
         */
        private final Class<E> type;
        /**
         * {@link #type} 下所有的枚举
         *
         * @see Class#getEnumConstants()
         */
        private final E[] enums;
    
        public EnumOrdinalTypeHandler(Class<E> type) {
            if (type == null) {
                throw new IllegalArgumentException("Type argument cannot be null");
            }
            this.type = type;
            this.enums = type.getEnumConstants();
            if (this.enums == null) {
                throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
            }
        }
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
            // 将 Enum 转换成 int 类型
            ps.setInt(i, parameter.ordinal());
        }
    
        @Override
        public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
            // 获得 int 的值
            int i = rs.getInt(columnName);
            // 将 int 转换成 Enum 类型
            if (i == 0 && rs.wasNull()) {
                return null;
            } else {
                try {
                    return enums[i];
                } catch (Exception ex) {
                    throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
                }
            }
        }
    
        @Override
        public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            // 获得 int 的值
            int i = rs.getInt(columnIndex);
            // 将 int 转换成 Enum 类型
            if (i == 0 && rs.wasNull()) {
                return null;
            } else {
                try {
                    return enums[i];
                } catch (Exception ex) {
                    throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
                }
            }
        }
    
        @Override
        public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            // 获得 int 的值
            int i = cs.getInt(columnIndex);
            // 将 int 转换成 Enum 类型
            if (i == 0 && cs.wasNull()) {
                return null;
            } else {
                try {
                    return enums[i];
                } catch (Exception ex) {
                    throw new IllegalArgumentException("Cannot convert " + i + " to " + type.getSimpleName() + " by ordinal value.", ex);
                }
            }
        }
    
    }

3. TypeReference

org.apache.ibatis.type.TypeReference ,引用泛型抽象类。目的很简单,就是解析类上定义的泛型。代码如下:

    // TypeReference.java
    
    public abstract class TypeReference<T> {
    
        /**
         * 泛型
         */
        private final Type rawType;
    
        protected TypeReference() {
            rawType = getSuperclassTypeParameter(getClass());
        }
    
        Type getSuperclassTypeParameter(Class<?> clazz) {
            // 【1】从父类中获取 <T>
            Type genericSuperclass = clazz.getGenericSuperclass();
            if (genericSuperclass instanceof Class) {
                // 能满足这个条件的,例如 GenericTypeSupportedInHierarchiesTestCase.CustomStringTypeHandler 这个类
                // try to climb up the hierarchy until meet something useful
                if (TypeReference.class != genericSuperclass) { // 排除 TypeReference 类
                    return getSuperclassTypeParameter(clazz.getSuperclass());
                }
    
                throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. "
                        + "Remove the extension or add a type parameter to it.");
            }
    
            // 【2】获取 <T>
            Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
            // TODO remove this when Reflector is fixed to return Types
            // 必须是泛型,才获取 <T>
            if (rawType instanceof ParameterizedType) {
                rawType = ((ParameterizedType) rawType).getRawType();
            }
    
            return rawType;
        }
    
        public final Type getRawType() {
            return rawType;
        }
    
        @Override
        public String toString() {
            return rawType.toString();
        }
    
    }

4. 注解

type 包中,也定义了三个注解,我们逐个来看看。

4.1 @MappedTypes

org.apache.ibatis.type.@MappedTypes ,匹配的 Java Type 类型的注解。代码如下:

    // MappedTypes.java
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE) // 注册到类
    public @interface MappedTypes {
    
        /**
         * @return 匹配的 Java Type 类型的数组
         */
        Class<?>[] value();
    
    }
4.2 @MappedJdbcTypes

org.apache.ibatis.type.@MappedJdbcTypes ,匹配的 JDBC Type 类型的注解。代码如下:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE) // 注册到类
    public @interface MappedJdbcTypes {
    
        /**
         * @return 匹配的 JDBC Type 类型的注解
         */
        JdbcType[] value();
    
        /**
         * @return 是否包含 {@link java.sql.JDBCType#NULL}
         */
        boolean includeNullJdbcType() default false;
    
    }
4.3 @Alias

org.apache.ibatis.type.@Alias ,别名的注解。代码如下:

    // Alias.java
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Alias {
    
        /**
         * @return 别名
         */
        String value();
    }

5. JdbcType

org.apache.ibatis.type.JdbcType ,Jdbc Type 枚举。代码如下:

    public enum JdbcType {
    
        /*
         * This is added to enable basic support for the
         * ARRAY data type - but a custom type handler is still required
         */
        ARRAY(Types.ARRAY),
        BIT(Types.BIT),
        TINYINT(Types.TINYINT),
        SMALLINT(Types.SMALLINT),
        INTEGER(Types.INTEGER),
        BIGINT(Types.BIGINT),
        FLOAT(Types.FLOAT),
        REAL(Types.REAL),
        DOUBLE(Types.DOUBLE),
        NUMERIC(Types.NUMERIC),
        DECIMAL(Types.DECIMAL),
        CHAR(Types.CHAR),
        VARCHAR(Types.VARCHAR),
        LONGVARCHAR(Types.LONGVARCHAR),
        DATE(Types.DATE),
        TIME(Types.TIME),
        TIMESTAMP(Types.TIMESTAMP),
        BINARY(Types.BINARY),
        VARBINARY(Types.VARBINARY),
        LONGVARBINARY(Types.LONGVARBINARY),
        NULL(Types.NULL),
        OTHER(Types.OTHER),
        BLOB(Types.BLOB),
        CLOB(Types.CLOB),
        BOOLEAN(Types.BOOLEAN),
        CURSOR(-10), // Oracle
        UNDEFINED(Integer.MIN_VALUE + 1000),
        NVARCHAR(Types.NVARCHAR), // JDK6
        NCHAR(Types.NCHAR), // JDK6
        NCLOB(Types.NCLOB), // JDK6
        STRUCT(Types.STRUCT),
        JAVA_OBJECT(Types.JAVA_OBJECT),
        DISTINCT(Types.DISTINCT),
        REF(Types.REF),
        DATALINK(Types.DATALINK),
        ROWID(Types.ROWID), // JDK6
        LONGNVARCHAR(Types.LONGNVARCHAR), // JDK6
        SQLXML(Types.SQLXML), // JDK6
        DATETIMEOFFSET(-155); // SQL Server 2008
    
        /**
         * 类型编号。嘿嘿,此处代码不规范
         */
        public final int TYPE_CODE;
    
        /**
         * 代码编号和 {@link JdbcType} 的映射
         */
        private static Map<Integer, JdbcType> codeLookup = new HashMap<>();
    
        static {
            // 初始化 codeLookup
            for (JdbcType type : JdbcType.values()) {
                codeLookup.put(type.TYPE_CODE, type);
            }
        }
    
        JdbcType(int code) {
            this.TYPE_CODE = code;
        }
    
        public static JdbcType forCode(int code) {
            return codeLookup.get(code);
        }
    
    }

6. TypeHandlerRegistry

org.apache.ibatis.type.TypeHandlerRegistry ,TypeHandler 注册表,相当于管理 TypeHandler 的容器,从其中能获取到对应的 TypeHandler 。

6.1 构造方法
    // TypeHandlerRegistry.java
    
    /**
     * 空 TypeHandler 集合的标识,即使 {@link #TYPE_HANDLER_MAP} 中,某个 KEY1 对应的 Map<JdbcType, TypeHandler<?>> 为空。
     *
     * @see #getJdbcHandlerMap(Type)
     */
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
    
    /**
     * JDBC Type 和 {@link TypeHandler} 的映射
     *
     * {@link #register(JdbcType, TypeHandler)}
     */
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);
    /**
     * {@link TypeHandler} 的映射
     *
     * KEY1:JDBC Type
     * KEY2:Java Type
     * VALUE:{@link TypeHandler} 对象
     */
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<>();
    /**
     * 所有 TypeHandler 的“集合”
     *
     * KEY:{@link TypeHandler#getClass()}
     * VALUE:{@link TypeHandler} 对象
     */
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<>();
    
    /**
     * {@link UnknownTypeHandler} 对象
     */
    private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
    /**
     * 默认的枚举类型的 TypeHandler 对象
     */
    private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
    
    public TypeHandlerRegistry() {
        // ... 省略其它类型的注册
    
        // <1>
        register(Date.class, new DateTypeHandler());
        register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
        register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
        // <2>
        register(JdbcType.TIMESTAMP, new DateTypeHandler());
        register(JdbcType.DATE, new DateOnlyTypeHandler());
        register(JdbcType.TIME, new TimeOnlyTypeHandler());
    
        // ... 省略其它类型的注册
    }
6.2 getInstance

#getInstance(Class javaTypeClass, Class typeHandlerClass) 方法,创建 TypeHandler 对象。代码如下:

    // TypeHandlerRegistry.java
    
    public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
        // 获得 Class 类型的构造方法
        if (javaTypeClass != null) {
            try {
                Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
                return (TypeHandler<T>) c.newInstance(javaTypeClass); // 符合这个条件的,例如 EnumTypeHandler
            } catch (NoSuchMethodException ignored) {
                // ignored 忽略该异常,继续向下
            } catch (Exception e) {
                throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
            }
        }
        // <2> 获得空参的构造方法
        try {
            Constructor<?> c = typeHandlerClass.getConstructor();
            return (TypeHandler<T>) c.newInstance(); // 符合这个条件的,例如 IntegerTypeHandler
        } catch (Exception e) {
            throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
        }
    }
6.3 register

#register(…) 方法,注册 TypeHandler 。TypeHandlerRegistry 中有大量该方法的重载实现,大体整理如下:

202303132325181492.png
除了 ⑤ 以外,所有方法最终都会调用 ④ ,即 #register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) 方法,代码如下:

    // TypeHandlerRegistry.java
    
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        // <1> 添加 handler 到 TYPE_HANDLER_MAP 中
        if (javaType != null) {
            // 获得 Java Type 对应的 map
            Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
            if (map == null || map == NULL_TYPE_HANDLER_MAP) { // 如果不存在,则进行创建
                map = new HashMap<>();
                TYPE_HANDLER_MAP.put(javaType, map);
            }
            // 添加到 handler 中 map 中
            map.put(jdbcType, handler);
        }
        // <2> 添加 handler 到 ALL_TYPE_HANDLERS_MAP 中
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }
6.4 getTypeHandler

#getTypeHandler(…) 方法,获得 TypeHandler 。TypeHandlerRegistry 有大量该方法的重载实现,大体整体如下:

202303132325214973.png


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文