本文更好的观看体验在笔者的个人博客中~
问题描述
项目中利用Mybatis来获取数据,使用Map来对结果进行封装,但是在获取某个具体的值时抛出java.lang.Integer cannot be cast to java.lang.String
异常,在debug过程中对为什么要这样解决产生了疑惑,所以想记录一下~
项目代码如下:
- Dao.java
HashMap<String, String> getEntityByName(String entityName);
- Mapper.xml
<select id="getEntityByName" resultType="java.util.HashMap">
SELECT entity_id, entity_table FROM entity WHERE entity_name=#{entityName};
</select>
- 使用
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的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。
- 原始类型相等:通过a对象和b对象的
getClass()
方法获取他们的类的信息,最后发现结果为true
。说明泛型类型String
和Integer
都被擦除掉了,只剩下原始类型。
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
}
- 通过反射添加其他类型元素:定义了一个
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)替换。
- 原始类型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;
}
}
- 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那么多次,竟然从来没有关心过它底层是怎么实现的,真是惭愧,借着这个机会简单了解下吧~
DefaultResultSetHandler
的handleResultSets
方法
@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);
}
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());
}
}
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<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);
}
}
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}
- (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
类型的数据的时候会抛出标题所说的异常信息~