今天在学习 Android Adapter 中遇到一个奇怪的问题,
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
主布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="#06a" android:padding="10dip" > <TextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="myadapter" /> <ListView android:id="@+id/lv_ad" android:layout_width="match_parent" android:layout_height="wrap_content" > </ListView> <Button android:id="@+id/btn_buy" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="提交" /> </LinearLayout>
列表项Code
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <ImageView android:id="@+id/goods_pic" android:layout_width="40dip" android:layout_height="40dip" android:layout_marginLeft="20dip" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="50dp" android:orientation="vertical" > <TextView android:id="@+id/people_name" android:layout_width="50dp" android:layout_height="wrap_content" android:layout_marginLeft="20dip" android:textSize="20sp" /> <TextView android:id="@+id/goods_price" android:layout_width="50dp" android:layout_height="wrap_content" android:layout_marginLeft="20dip" android:textSize="18sp" /> </LinearLayout> <CheckBox android:id="@+id/cb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dip" /> <Button android:id="@+id/btn_deal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="查看" /> </LinearLayout>
要自定义 Adapter 就必须继承 Android基础类之BaseAdapter
在实现 MyAdpter 之前 参照以前所学的 SimpleAdapter 用法
最为关键的一句
SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.list_item, from, to);
SimpleAdapter 源码
public class SimpleAdapter extends BaseAdapter implements Filterable {
private int[] mTo;
private String[] mFrom;
private ViewBinder mViewBinder;
private List<? extends Map<String, ?>> mData;
private int mResource;
private int mDropDownResource;
private LayoutInflater mInflater;
private SimpleFilter mFilter;
private ArrayList<Map<String, ?>> mUnfilteredData;
/**
* Constructor
*
* @param context The context where the View associated with this SimpleAdapter is running
* @param data A List of Maps. Each entry in the List corresponds to one row in the list. The
* Maps contain the data for each row, and should include all the entries specified in
* "from"
* @param resource Resource identifier of a view layout that defines the views for this list
* item. The layout file should include at least those named views defined in "to"
* @param from A list of column names that will be added to the Map associated with each
* item.
* @param to The views that should display column in the "from" parameter. These should all be
* TextViews. The first N views in this list are given the values of the first N columns
* in the from parameter.
*/
public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to) {
mData = data;
mResource = mDropDownResource = resource;
mFrom = from;
mTo = to;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* @see android.widget.Adapter#getCount()
*/
public int getCount() {
return mData.size();
}
/**
* @see android.widget.Adapter#getItem(int)
*/
public Object getItem(int position) {
return mData.get(position);
}
/**
* @see android.widget.Adapter#getItemId(int)
*/
public long getItemId(int position) {
return position;
}
/**
* @see android.widget.Adapter#getView(int, View, ViewGroup)
*/
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource);
}
接下来:
MyAdapter.java
public class MyAdapter extends BaseAdapter {
private int[] mTo;
private String[] mFrom;
private List<? extends Map<String, ?>> mData;
private int mResource;
private LayoutInflater mInflater;
public MyAdapter(Context context, List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to) {
mData = data;
mResource = resource;
mFrom = from;
mTo = to;//LayoutInflater inflater = LayoutInflater.from(context); // 其实现原理就是下面这句
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//
View view = mInflater.inflate(mResource, null);
//填充组件
for (int i = 0; i < mFrom.length; i++) {
View v = view.findViewById(mTo[i]);
Object content = mData.get(position).get(mFrom[i]);
if (v instanceof ImageView) {
ImageView iv = (ImageView) v;
iv.setBackgroundResource((Integer)content);
}
else if (v instanceof TextView) {
TextView tv = (TextView)v;
tv.setText( (String)content); //tv.setText(content.toString());
}
}
}
return view;
}
}
LayoutInflater inflater = LayoutInflater.from(context); 源码
/**
* Obtains the LayoutInflater from the given context.
*/
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
TestAdapter
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.heart.listviewdemo.R;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
public class TestAdapter extends Activity {
private ListView listVIew;
private static List<Map<String, Object>> data;
static String[] from;
static int[] to;
static {
data = new ArrayList<Map<String, Object>>();
Map<String, Object> map = new HashMap<String, Object>();
map.put("pic", R.drawable.p1);
map.put("name", "移动");
map.put("price", 10086);
data.add(map);
map = new HashMap<String, Object>();
map.put("pic", R.drawable.p2);
map.put("name", "联通");
map.put("price", "10010");
data.add(map);
map = new HashMap<String, Object>();
map.put("pic", R.drawable.p3);
map.put("name", "电信");
map.put("price", "0000");
data.add(map);
map = new HashMap<String, Object>();
map.put("pic", R.drawable.p7);
map.put("name", "Me");
map.put("price", "1353");
data.add(map);
from = new String[] { "pic", "name", "price" };
to = new int[] { R.id.goods_pic, R.id.people_name,
R.id.goods_price };
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.adapter_ly);
//
MyAdapter adapter = new MyAdapter(getApplicationContext(), data, R.layout.myadapter, from, to);
listVIew = (ListView) findViewById(R.id.lv_ad);
listVIew.setAdapter(adapter);
}
}
开始测试程序,结果是
仔细查看代码流程并没有错,那好 dedug 一下程序,结果发现
content 值为Integer
所以发生了:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.CharSequence
content 值是由下面这句获得的
Object content = mData.get(position).get(mFrom[i]);
由此推断:在数据方面出错了
map.put("price", 10086);
map.put("price", "10010");
原来一不小心放了 整型 由于定义了 private static List<Map<String, Object>> data;
value 放入什么都没关系的
解决方案
1.修改 map.put("price","10086");
2.在之前的 SimpleAdapter 的用法有提过,同样的数据 在 SimpleAdapter 测试中不出错
这是why?
public class SimpleAdapterDemo extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//
ListView listView = new ListView(this);
//
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
Map<String, Object> map = new HashMap<String, Object>();
map.put("pic", R.drawable.p1);
map.put("name", "移动");
map.put("phone", 10086);
data.add(map );
map = new HashMap<String, Object>();
map.put("pic", R.drawable.p2);
map.put("name", "联通");
map.put("phone", 10010);
data.add(map );
map = new HashMap<String, Object>();
map.put("pic", R.drawable.p3);
map.put("name", "电信");
map.put("phone", 0000);
data.add(map );
map = new HashMap<String, Object>();
map.put("pic", R.drawable.p7);
map.put("name", "Me");
map.put("phone", "13532605287");
data.add(map );
String[] from = new String [] {
"pic",
"name",
"phone"
};
int[] to = new int [] {
R.id.people_pic,
R.id.people_name,
R.id.people_num
};
SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.list_item, from, to);
//
listView.setAdapter(adapter);
//
setContentView(listView);
//
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Log.d("TAG", String.valueOf(position));
Toast.makeText(SimpleAdapterDemo.this, "onItemClick " + position, Toast.LENGTH_LONG).show();
}
});
//listView
listView.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
Log.d("TAG", String.valueOf(position));
Toast.makeText(SimpleAdapterDemo.this, "OnItemSelectedListener " + position, Toast.LENGTH_LONG).show();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
}
程序能运行起来,为什么能运行起来?
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
如果把它改成这样 List<Map<String, String>> data = new ArrayList<Map<String,String>>(); 问题不就好办了
但是我们不可能都是String 类型的数据吧 如:map.put("pic", R.drawable.p1);
SimpleAdapter 如何做到的
public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to) {
mData = data;
mResource = mDropDownResource = resource;
mFrom = from;
mTo = to;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
List<? extends Map<String, ?>> data 有 Java 基础的人都知道泛型
public View getView(int position, View convertView, ViewGroup parent) //这个方法很重要,关键是它如何得到一个 View 对象的
/**
* @see android.widget.Adapter#getView(int, View, ViewGroup)
*/
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource);
}
private View createViewFromResource(int position, View convertView,
ViewGroup parent, int resource) {
View v;
if (convertView == null) {
v = mInflater.inflate(resource, parent, false);
} else {
v = convertView;
}
bindView(position, v);
return v;
}
终于:
private void bindView(int position, View view) {
final Map dataSet = mData.get(position);
if (dataSet == null) {
return;
}
final ViewBinder binder = mViewBinder;
final String[] from = mFrom;
final int[] to = mTo;
final int count = to.length;
for (int i = 0; i < count; i++) {
final View v = view.findViewById(to[i]);
if (v != null) {
final Object data = dataSet.get(from[i]);
String text = data == null ? "" : data.toString();
if (text == null) {
text = "";
}
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, data, text);
}
if (!bound) {
if (v instanceof Checkable) {
if (data instanceof Boolean) {
((Checkable) v).setChecked((Boolean) data);
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
// ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() +
" should be bound to a Boolean, not a " +
(data == null ? "<unknown type>" : data.getClass()));
}
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
// ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
if (data instanceof Integer) {
setViewImage((ImageView) v, (Integer) data);
} else {
setViewImage((ImageView) v, text);
}
} else {
throw new IllegalStateException(v.getClass().getName() + " is not a " +
" view that can be bounds by this SimpleAdapter");
}
}
}
}
}
关键是:data.toString();
String text = data == null ? "" : data.toString();
if (text == null) {
text = "";
}
// Note: keep the instanceof TextView check at the bottom of these
// ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
data 不是 Object 类型么? toString(); 不是打印哈希值么?
经过长时间的研究发现 Object 的toString() 方法 并不返回 Object 的哈希值
public String toString()
{
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
那只好调试程序了,调程序时,竟然发现 jdk 源码无法进入去,只好折腾一番,大家可以参考 这篇文章
接下来终于可以调试,jdk 源码了,为了更好的理解运行流程
public class TestToString {
public static void main(String[] args) {
@SuppressWarnings("unused")
Object obj = 1;
//String s = (String) obj; //报错
String s = obj.toString(); //通过
new TestToString().test();
}
public void test(){
Object obj = 1;
System.out.println(obj instanceof Integer);
System.out.println(obj == Integer.valueOf(1));
System.out.println(obj.getClass());
obj = obj.toString();
System.out.println(obj.getClass());
System.out.println(obj instanceof String);
System.out.println(obj);
obj = new Integer(1);
}
}
在 Object obj = 1; 打个断点
发现 obj = Integer (我想到了 Java 自动装箱与拆箱(Autoboxing and unboxing) )
Integer 类重写了 Object 父类的 toString() 方法,这在我们平时写 JavaBean 时也提供一个 toString() 便于测试,但它 的toString() 如何返回 String 的
原来这句 return new String(buf, true); 返回了对象,上图能看见 buf 值需要重新编译 jdk 部分源码
原因:这是由于Oracle公司打包jdk时,为缩减体积,去除了二进制文件里的一些东西,所以看不到;目前的解决方案是,把jdk的源码导入到eclipse中,重新编译,然后打包,把jdk路径下的rj.jar替换掉。
重新编译 jdk 比较繁琐,大家可以参考 JDK源码重新编译——支持eclipse调试JDK源码--转载 或
解决Debug JDK source 无法查看局部变量的问题方案
修改 Object obj = ”1“; 再调试,只不过 obj=String 了 调用的是 String类的toString(); 返回本身
/**
* This object (which is already a string!) is itself returned.
*
* @return the string itself.
*/
public String toString() {
return this;
}
总结:
TextView 的 setText(CharSequence text) 方法 参数是CharSequence 可读可写序列
然而我们通常这样做 tv.setText((CharSequence) content); //也许因为 IDE 提示功能,我们习惯性地强转 了。tv.setText( content.toString()); 有 java 的自动装箱拆箱,动态编译 支持。使用 toString(); 神马都能返回 String 字符串。虽然在程序上 TextView 显示的绝对是 String 类型的字符串,而不是什么 Integer ,Double 等包装类,数据流转的困难(大家都懂的 Java 是强类型的语言),大家在放数据的 一般情况下页面是放的都是 String 类型,这没什么好担心的,不过 使用 toString() 方法不是很完美么,再说 SimpleAdapter 都是这样做的, 外国人写那个 SimpleAdapter 方法时,的确写得。。。