I was looking into how to create a multichoice checkbox dialog while working in Android Studio and naturally started by looking at the android developers page and came across this guide. The code example they show is this:
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
selectedItems = new ArrayList(); // Where we track the selected items
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Set the dialog title
builder.setTitle(R.string.pick_toppings)
// Specify the list array, the items to be selected by default (null for none),
// and the listener through which to receive callbacks when items are selected
.setMultiChoiceItems(R.array.toppings, null,
new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which,
boolean isChecked) {
if (isChecked) {
// If the user checked the item, add it to the selected items
selectedItems.add(which);
} else if (selectedItems.contains(which)) {
// Else, if the item is already in the array, remove it
selectedItems.remove(which);
}
}
})
// Set the action buttons
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// User clicked OK, so save the selectedItems results somewhere
// or return them to the component that opened the dialog
...
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
...
}
});
return builder.create();
}
Now what Im interested in looking at is this:
.setMultiChoiceItems(R.array.toppings, null,
new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which,
boolean isChecked) {
if (isChecked) {
// If the user checked the item, add it to the selected items
selectedItems.add(which);
} else if (selectedItems.contains(which)) {
// Else, if the item is already in the array, remove it
selectedItems.remove(which);
}
}
})
Now I am missing something here or is there a mistake in the code here? What I mean is wouldn't the call to remove()
in selectedItems.remove(which)
interpret which
as an int
rather than an Object
and thus remove the element at the position specified by the int
stored in which
rather than removing the actual int
from the array? This is what I assumed would happen when I read the code and also what did happen for me when I tried to run the code leading to an IndexOutOfBoundsError
.
However as the code comes from a resource that Google itself provides I feel like its likely that it must be me that is missing something. If that is the case what am I missing? If that isn't the case and there is in fact an error in the code then I apologise as the topic no longer seems relevant for stackoverflow.
When it comes to how to solve the issue Im encountering that isn't an issue as I realise that I can simply do something like selectedItems.remove((Integer) which)
or selectedItems.remove(Integer.valueOf(which))
. I am simply bewildered as to what is happening.
CodePudding user response:
Your diagnosis is correct. Given two overloads List.remove(int)
and List.remove(Object)
and a call with an int
argument, the Java compiler will statically resolve the call to the first overload rather than the second one1. But the semantics of this context require the second overload; i.e. removal by value rather than by position.
It's a bug in the documentation. Report it through the normal channels ...
1 - This is specified in JLS 15.12. This part of the spec is complicated and technical, but the gist is that the compiler first looks for overloads that match the argument types strictly; i.e. without boxing or unboxing. It only considers non-strict matches if there are no strict ones. Informally, int
-> int
is a "closer match" than int
-> Object
which involves boxing to Integer
followed by reference widening to Object
.