记一次MyBatis封装结果集java.lang.Integer cannot be cast to java.lang.String异常处理

本文更好的观看体验在笔者的个人博客中~

问题描述

项目中利用Mybatis来获取数据,使用Map来对结果进行封装,但是在获取某个具体的值时抛出java.lang.Integer cannot be cast to java.lang.String异常,在debug过程中对为什么要这样解决产生了疑惑,所以想记录一下~
项目代码如下:

  1. Dao.java
HashMap<String, String> getEntityByName(String entityName); 
  1. Mapper.xml
<select id="getEntityByName" resultType="java.util.HashMap">
    SELECT entity_id, entity_table FROM entity WHERE entity_name=#{entityName};
</select>
  1. 使用
HashMap<String, String> entityInfo = DAO.getEntityByName(type);
int entityId = Integer.parseInt(entityInfo.get("entity_id"));  // 抛出异常

解决方法

先说结论,解决方法很简单,将使用处代码修改为:

int entityId = Integer.parseInt(String.valueOf(entityInfo.get("entity_id")));

虽然这时候IDEA会告诉你String.valueOf()方法很多余,但是相信我,真的不多余…

原理解析

Java中的泛型与类型擦除

之前对泛型并没有特别多的了解,类型擦除也是没有听说过,但是在解释这个问题上还是需要了解一下这方面的知识的~

Java泛型的实现方法:类型擦除

Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除

  1. 原始类型相等:通过a对象和b对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型StringInteger都被擦除掉了,只剩下原始类型。
public static void main(String[] args) {  
    List<String> a = new ArrayList<String>();  
    List<Integer> b = new ArrayList<Integer>();  
    System.out.println(a.getClass() == b.getClass());  // true
}
  1. 通过反射添加其他类型元素:定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。
public class Test {
    public static void main(String[] args) throws Exception{
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(1);  //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
        list.getClass().getMethod("add", Object.class).invoke(list, "asd");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

类型擦除后保留的原始类型

原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

  1. 原始类型Object
public class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}  

Pair的原始类型为:

public class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}

因为在Pair<T>中,T 是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类,如同泛型加入Java语言之前的已经实现的样子。在程序中可以包含不同类型的Pair,如Pair<String>或Pair<Integer>,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object
[所以我们的集合类,如ArrayList类型擦除后的原始类型为Object,通过反射就可以存储各种类型]{.yellow}
但是如果用这样声明的话那原始类型就是Comparable

public class Pair<T extends Comparable> {}

要区分原始类型泛型变量的类型。在调用泛型方法时,可以指定泛型,也可以不指定泛型。

  • 在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到 Object
  • 在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类
public class Test {  
    public static void main(String[] args) {  

        /**不指定泛型的时候*/  
        int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型  
        Number f = Test.add(1, 1.2); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number  
        Object o = Test.add(1, "asd"); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Object  

        /**指定泛型的时候*/  
        int a = Test.<Integer>add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类  
        int b = Test.<Integer>add(1, 2.2); //编译错误,指定了Integer,不能为Float  
        Number c = Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float  
    }  

    //这是一个简单的泛型方法  
    public static <T> T add(T x,T y){  
        return y;  
    }  
}
  1. Object泛型
    注意看这个例子,我们这个问题的解释主要就是要看明白这个例子,如果不指定泛型,那么什么都能存
public static void main(String[] args) {  
    ArrayList list = new ArrayList();  
    list.add(1);  
    list.add("121");  
    list.add(new Date());  
}  

private void viewDetail(){ 
    Map map1 = new HashMap();
    Map<String,Object> map2 = new HashMap<String,Object>();
    Map<Object,Object> map3 = new HashMap<Object,Object>();
    Map<String,String> map4 = new HashMap<String,String>();
    test1(map1);
    test1(map2);
    test1(map3);  // 编译错误
    test1(map4);  // 编译错误
}
private void test1(Map<String,Object> map){}

泛型是后加入的,早期的版本没有,但是java的开发者希望不能因为加入泛型就要修改成千上万的现有应用,所以默认他可以通融老版本不加泛型的变量,这就是为什么我们的map1不会出错了。就是说[如果我们的泛型类不指定泛型,那么可以和任何指定泛型的变量赋值]{.yellow}

类型擦除的问题

Q: 既然说类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList 创建的对象中添加整数会报错呢?不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
A: Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。

来看这两种情况:

ArrayList<String> list1 = new ArrayList(); //第一种 情况,与完全使用泛型一样效果
ArrayList list2 = new ArrayList<String>(); //第二种 情况,没有效果

因为类型检查就是编译时完成的,new ArrayList()只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正设计类型检查的是它的引用,因为我们是使用它引用list1来调用它的方法,比如说调用add方法,所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型,所以不行。

public class Test {  
    public static void main(String[] args) {  

        ArrayList<String> list1 = new ArrayList();  
        list1.add("1"); //编译通过  
        list1.add(1); //编译错误  
        String str1 = list1.get(0); //返回类型就是String  

        ArrayList list2 = new ArrayList<String>();  
        list2.add("1"); //编译通过  
        list2.add(1); //编译通过  
        Object object = list2.get(0); //返回类型就是Object  

        new ArrayList<String>().add("11"); //编译通过  
        new ArrayList<String>().add(22); //编译错误  
    }  
}  

MyBatis结果集封装源码浅析

前面说了那么多,终于开始看我们的MyBatis源码了,用了MyBatis那么多次,竟然从来没有关心过它底层是怎么实现的,真是惭愧,借着这个机会简单了解下吧~

  1. DefaultResultSetHandlerhandleResultSets方法
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").   object(mappedStatement.getId());
    // 要返回的结果
    final List<Object> multipleResults = new ArrayList<>();
    // 迭代变量,结果集的个数
    int resultSetCount = 0;
    // 获取第一个结果集,并包装成ResultSetWrapper对象,
    // ResultSetWrapper对象含有已映射和未映射的列名和属性的对应关系
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 获取所有的ResultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps    ();
    // ResultMap的个数
    int resultMapCount = resultMaps.size();
    // 校验:如果结果集有数据,但是没有定义返回的结果类型,就会报错
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      // 依次获取ResultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 处理结果集,这里是重点
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 获取下一个结果集
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
    //...
    //...
    return collapseSingleResultList(multipleResults);
}
  1. handleResultSet(rsw, resultMap, multipleResults, null)
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        if (parentMapping != null) {
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
        } else {
            if (resultHandler == null) {
                // 如果结果处理器为空,则使用默认的结果处理器,没有自定义的情况下,都是走这个流程
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                // 处理每一行的值
                handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                // 将处理结果放到list集中
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
                handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
        }
    } finally {
        // issue #228 (close resultsets)
        closeResultSet(rsw.getResultSet());
    }
  }
  1. handleRowValues
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException{
    // 如果有嵌套的ResultMap
    if (resultMap.hasNestedResultMaps()) {
        ensureNoRowBounds();
        checkResultHandler();
        // 处理含有嵌套ResultMap的结果
        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
        // 处理不含有嵌套ResultMap的结果
        handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}
  1. handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    // 略过偏移的行数
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        // 获取行数据,应在此深追,从 debugger 看,getRowValue 是解析属性 mapping 的函数 
        Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        // 存储对象
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
}
  1. getRowValue
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    // 懒加载相关
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 创建行数据对象
    // 这里java.util.Map 就会被实例化为 new HashMap() 没有指定范型
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        // 获取元数据,为了赋值 mapping 对应的值
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        // 到这里 也就是说 MetaObject 被实例化成功,
        // 其中的 objectWrapper 则被实例化成了 MapWrapper
        // MapWrapper 中有个 Map<String, Object> map 属性
        // rowValue 则被赋值给了 map
        // 而 map 则可以作为 rowValue 的引用胡作非为
        if (shouldApplyAutomaticMappings(resultMap, false)){
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        // applyPropertyMappings: 应用属性映射
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}
//实例化 MetaObject
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

    // 各式各样
    if (object instanceof ObjectWrapper) {
        this.objectWrapper = (ObjectWrapper) object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
        this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
        // Map的实例化
        this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
        this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
        this.objectWrapper = new BeanWrapper(this, object);
    }
}
public class MapWrapper extends BaseWrapper {

    // 设定了范型
    private final Map<String, Object> map;

    public MapWrapper(MetaObject metaObject, Map<String, Object> map) {
        super(metaObject);
        this.map = map;
    }
    // ...
    // ...
}

晕了?别慌我们捋一捋~

其实就是这么一回事:

Map<String,String> myResultMap = null;  // myResultMap,我MyBatis要返回的那个Map
Map rowValue = new HashMap();  // rowValue, 没有使用泛型
Map<String,Object> metaObject = rowValue;  // metaObject,使用泛型但是是Object可以存任何类型数据
metaObject.put("entity_id", 1);
metaObject.put("entity_table", "tableName");

myResultMap = rowValue;
String s = myResultMap.get("entity_id"); // 报错一行

这样是不是就清楚多了?说白了其实就是利用了一个不用泛型的Map作为中转站,把<String,Object>的Map赋值给了<String,String>的Map

Object转String方法

okkkk,来到了我们的最后一步了,我知道了我的myResultMap获取的是其实是个Object对象了,那么怎么转化成String对象呢?
我们先来看个小示例哈~

// 先定3个变量 : Integer一个, String内容为字母的一个, String内容为数字的一个
Integer integerNumAAA = 123;
String alphaBBB = "abc";
String stringNumCCC = "321";
-------------------------------
Map map = new HashMap<>();  // 没用泛型,什么都能存
map.put("AAA", integerNumAAA );
map.put("BBB", alphaBBB );
map.put("CCC", stringNumCCC );
-------------------------------
// 塞好后接下来进行取值
Integer getAAA = (Integer)map.get("AAA");
// 可以看到塞进去的时候是Integer类型的话,拿出来的时候用强转可以顺利拿到值;
 
Integer getCCC = (Integer)map.get("CCC");
// 这个会报错
// 意思就是,如果你map.put的时候是String类型的,但是内容还是数字的话就会报错
// 备注:这里报的String→Integer强转的报错,和本文的都是ClsasCastException强转问题
 
// 解决
// 也是先转为String类型后再用Integer.parseInt转即可,如下:
Integer.parseInt(map.get("stringNumCCC ").toString());

这就是说[我的Map原来存的什么类型,如果我用那个类型来取是不会有问题的,如果我用别的类型来取,就会发生类型转换,就容易出错!]{.yellow}

  1. (String)Object
    Object类型的对象强制转换为String类型,对于空格、空字符串、null都可以转换,但是[Object对象的值类型不是字符串,比如Integer类型时,会存在类型转换异常错误]{.yellow}这也是我开头的代码出错的原因!!!
  2. Object.toString()
    对空格、空字符串、其他数据类型都可以进行转换,值为null时的空指针异常
  3. String.valueOf(Object)
    可以对null、空格、空字符串、其他数据类型进行转换,是一个比较安全的转换方法。当值为null的时候该方法会转换为"null",这个值会影响业务后续的非空判断。什么意思?
String s1 = "null";
String s2 = null;
System.out.println(String.valueOf(s1));  // null
System.out.println(Strin.valueOf(s2));  // null
  1. Object + “”
    此方法是利用字符串的拼接特点将其他类型的数据转换为字符串,它和Stirng.valueOf(object)类似不用考虑空格、空字符串、null、和其他数据类型,但是需要注意当值为null的时候会转换为"null"

总结

好了,说完了,所有要理解这个问题发生的原因和解决方法的东西都说完了,在我一开头的程序中,如果不使用String.valueOf()方法,你查看.class文件的时候会看到它自动调用了String(Object)方法,而我也说过了,这个转换方法在转换不是String类型的数据的时候会抛出标题所说的异常信息~

参考

  1. Java泛型类型擦除及类型擦除带来的问题
  2. MyBatis一个有意思的东西
  3. MyBatis原理:结果集封装详解
  4. 【异常】java.lang.Integer cannot be cast to java.lang.String
  5. 一篇带你Object转String
  6. java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer map里string转integer错误
  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值