Android:Spinner的onItemSelected中判断项类型只能用instanceof吗?
嘿,这个问题我之前折腾Spinner的时候也琢磨过——明明我们自己填充的Spinner,里面项的类型门儿清,但onItemSelected里拿到的parent.getItemAtPosition(pos)却总是Object类型,每次都要写instanceof判断再强转,实在有点啰嗦。甚至还想能不能直接把方法签名改成AdapterView<Integer>这种带具体泛型的形式,对吧?
先说说核心问题:原生的OnItemSelectedListener用AdapterView<?>作为参数,是因为它要适配所有AdapterView子类(比如Spinner、ListView),通用性拉满但灵活性就打了折扣。不过我们有办法绕开这个限制,下面给你两种可行的方案:
方案1:封装泛型版的监听器(最优雅的方式)
自己写一个带泛型的监听器封装类,把原生的通配符转成我们需要的具体类型,这样就能在回调里直接用指定类型的AdapterView了:
// 泛型监听器封装类 public class GenericItemSelectedListener<T> implements AdapterView.OnItemSelectedListener { private final OnItemSelectedCallback<T> callback; // 定义带泛型的回调接口 public interface OnItemSelectedCallback<T> { void onItemSelected(AdapterView<T> parent, View view, int pos, long id); void onNothingSelected(AdapterView<T> parent); } public GenericItemSelectedListener(OnItemSelectedCallback<T> callback) { this.callback = callback; } @Override @SuppressWarnings("unchecked") public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { // 这里做一次强制类型转换,因为我们明确知道Spinner的类型 callback.onItemSelected((AdapterView<T>) parent, view, pos, id); } @Override @SuppressWarnings("unchecked") public void onNothingSelected(AdapterView<?> parent) { callback.onNothingSelected((AdapterView<T>) parent); } }
使用的时候就可以直接指定类型,不用再写instanceof了:
// 给Spinner设置监听器,直接指定Integer类型 spinner.setOnItemSelectedListener(new GenericItemSelectedListener<Integer>( new GenericItemSelectedListener.OnItemSelectedCallback<Integer>() { @Override public void onItemSelected(AdapterView<Integer> parent, View view, int pos, long id) { // 直接拿到Integer类型的选中项,不用强转! Integer selectedItem = parent.getItemAtPosition(pos); doSomeMath(selectedItem); } @Override public void onNothingSelected(AdapterView<Integer> parent) { // 处理未选中的逻辑 } } ));
这里的@SuppressWarnings("unchecked")是为了消除编译器的类型警告,只要你自己确认Spinner里的项确实是指定的类型,这个转换就是安全的。
方案2:直接强转(简单场景适用)
如果你的场景很简单,比如只有一个Spinner,而且100%确定里面的项类型,那可以直接跳过instanceof判断,直接强转:
@Override public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { // 直接强转,前提是你确定项的类型是Integer Integer selectedItem = (Integer) parent.getItemAtPosition(pos); doSomeMath(selectedItem); }
这种方式代码更少,但风险是如果类型不匹配会抛出ClassCastException,所以只适合你完全掌控数据源的场景。
补充说明
其实Android的AdapterView本身是支持泛型的(源码里是AdapterView<T extends Adapter>),只是原生的监听器为了通用性用了通配符?。我们的封装本质上就是把这个通配符还原成具体的泛型类型,让代码更符合我们的预期。
内容的提问来源于stack exchange,提问作者VerumCH

