赞
踩
本文更好的观看体验在笔者的个人博客中~
项目中利用Mybatis来获取数据,使用Map来对结果进行封装,但是在获取某个具体的值时抛出java.lang.Integer cannot be cast to java.lang.String
异常,在debug过程中对为什么要这样解决产生了疑惑,所以想记录一下~
项目代码如下:
HashMap<String, String> getEntityByName(String entityName);
<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的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。
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)替换。
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; } }
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那么多次,竟然从来没有关心过它底层是怎么实现的,真是惭愧,借着这个机会简单了解下吧~
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
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}
Object
类型的对象强制转换为String
类型,对于空格、空字符串、null都可以转换,但是[Object
对象的值类型不是字符串,比如Integer
类型时,会存在类型转换异常错误]{.yellow}这也是我开头的代码出错的原因!!!String s1 = "null";
String s2 = null;
System.out.println(String.valueOf(s1)); // null
System.out.println(Strin.valueOf(s2)); // null
Stirng.valueOf(object)
类似不用考虑空格、空字符串、null、和其他数据类型,但是需要注意当值为null的时候会转换为"null"好了,说完了,所有要理解这个问题发生的原因和解决方法的东西都说完了,在我一开头的程序中,如果不使用String.valueOf()
方法,你查看.class
文件的时候会看到它自动调用了String(Object)
方法,而我也说过了,这个转换方法在转换不是String
类型的数据的时候会抛出标题所说的异常信息~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。