本文更好的观看体验在笔者的个人博客中~
问题描述项目中利用Mybatis来获取数据,使用Map来对结果进行封装,但是在获取某个具体的值时抛出java.lang.Integer cannot be cast to java.lang.String异常,在debug过程中对为什么要这样解决产生了疑惑,所以想记录一下~
项目代码如下:
- Dao.java
HashMapgetEntityByName(String entityName);
- Mapper.xml
SELECT entity_id, entity_table FROM entity WHERe entity_name=#{entityName};
- 使用
HashMap解决方法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的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。
- 原始类型相等:通过a对象和b对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下原始类型。
public static void main(String[] args) {
List a = new ArrayList();
List b = new ArrayList();
System.out.println(a.getClass() == b.getClass()); // true
}
- 通过反射添加其他类型元素:定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。
public class Test {
public static void main(String[] args) throws Exception{
ArrayList list = new ArrayList();
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)替换。
- 原始类型Object
public class Pair{ 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
[所以我们的集合类,如ArrayList类型擦除后的原始类型为Object,通过反射就可以存储各种类型]{.yellow}
但是如果用这样声明的话那原始类型就是Comparable:
public class Pair{}
要区分原始类型和泛型变量的类型。在调用泛型方法时,可以指定泛型,也可以不指定泛型。
- 在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到 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.add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类
int b = Test.add(1, 2.2); //编译错误,指定了Integer,不能为Float
Number c = Test.add(1, 2.2); //指定为Number,所以可以为Integer和Float
}
//这是一个简单的泛型方法
public static T add(T x,T y){
return y;
}
}
- 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 map2 = new HashMap();
Map
泛型是后加入的,早期的版本没有,但是java的开发者希望不能因为加入泛型就要修改成千上万的现有应用,所以默认他可以通融老版本不加泛型的变量,这就是为什么我们的map1不会出错了。就是说[如果我们的泛型类不指定泛型,那么可以和任何指定泛型的变量赋值]{.yellow}
类型擦除的问题Q: 既然说类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList 创建的对象中添加整数会报错呢?不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
A: Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
来看这两种情况:
ArrayListlist1 = new ArrayList(); //第一种 情况,与完全使用泛型一样效果 ArrayList list2 = new ArrayList (); //第二种 情况,没有效果
因为类型检查就是编译时完成的,new ArrayList()只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正设计类型检查的是它的引用,因为我们是使用它引用list1来调用它的方法,比如说调用add方法,所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型,所以不行。
public class Test {
public static void main(String[] args) {
ArrayList list1 = new ArrayList();
list1.add("1"); //编译通过
list1.add(1); //编译错误
String str1 = list1.get(0); //返回类型就是String
ArrayList list2 = new ArrayList();
list2.add("1"); //编译通过
list2.add(1); //编译通过
Object object = list2.get(0); //返回类型就是Object
new ArrayList().add("11"); //编译通过
new ArrayList().add(22); //编译错误
}
}
MyBatis结果集封装源码浅析
前面说了那么多,终于开始看我们的MyBatis源码了,用了MyBatis那么多次,竟然从来没有关心过它底层是怎么实现的,真是惭愧,借着这个机会简单了解下吧~
- DefaultResultSetHandler的handleResultSets方法
@Override public ListhandleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results"). object(mappedStatement.getId()); // 要返回的结果 final List multipleResults = new ArrayList<>(); // 迭代变量,结果集的个数 int resultSetCount = 0; // 获取第一个结果集,并包装成ResultSetWrapper对象, // ResultSetWrapper对象含有已映射和未映射的列名和属性的对应关系 ResultSetWrapper rsw = getFirstResultSet(stmt); // 获取所有的ResultMap List 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); }
- handleResultSet(rsw, resultMap, multipleResults, null)
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, ListmultipleResults, 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()); } }
- 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);
}
}
- handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext 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);
}
}
- 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 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 map;
public MapWrapper(MetaObject metaObject, Map map) {
super(metaObject);
this.map = map;
}
// ...
// ...
}
晕了?别慌我们捋一捋~
其实就是这么一回事:
MapmyResultMap = null; // myResultMap,我MyBatis要返回的那个Map Map rowValue = new HashMap(); // rowValue, 没有使用泛型 Map metaObject = rowValue; // metaObject,使用泛型但是是Object可以存任何类型数据 metaObject.put("entity_id", 1); metaObject.put("entity_table", "tableName"); myResultMap = rowValue; String s = myResultMap.get("entity_id"); // 报错一行
这样是不是就清楚多了?说白了其实就是利用了一个不用泛型的Map作为中转站,把
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}
- (String)Object
将Object类型的对象强制转换为String类型,对于空格、空字符串、null都可以转换,但是[Object对象的值类型不是字符串,比如Integer类型时,会存在类型转换异常错误]{.yellow}这也是我开头的代码出错的原因!!! - Object.toString()
对空格、空字符串、其他数据类型都可以进行转换,值为null时的空指针异常 - 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
- Object + “”
此方法是利用字符串的拼接特点将其他类型的数据转换为字符串,它和Stirng.valueOf(object)类似不用考虑空格、空字符串、null、和其他数据类型,但是需要注意当值为null的时候会转换为"null"
好了,说完了,所有要理解这个问题发生的原因和解决方法的东西都说完了,在我一开头的程序中,如果不使用String.valueOf()方法,你查看.class文件的时候会看到它自动调用了String(Object)方法,而我也说过了,这个转换方法在转换不是String类型的数据的时候会抛出标题所说的异常信息~
参考- Java泛型类型擦除及类型擦除带来的问题
- MyBatis一个有意思的东西
- MyBatis原理:结果集封装详解
- 【异常】java.lang.Integer cannot be cast to java.lang.String
- 一篇带你Object转String
- java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer map里string转integer错误



