This code tracks what missiles players buy and sets the most recent 3 at the top of the list of missile options for players to easily buy them again. After players purchase missiles, the game crashes, but only if they purchase certain ones or purchase a certain amount.
Process: com.apps.fast.counterforcetest, PID: 16063
java.lang.IndexOutOfBoundsException: Index: 5, Size: 3
at java.util.LinkedList.checkElementIndex(LinkedList.java:555)
at java.util.LinkedList.remove(LinkedList.java:525)
at com.apps.fast.launch.components.ClientDefs.SetMissilePreferred(ClientDefs.java:199)
at com.apps.fast.launch.launchviews.PurchaseLaunchableView$1$1.onClick(PurchaseLaunchableView.java:198)
Here is the code the error points to in CLientDefs.java:199
public static void SetMissilePreferred(int lMissile) {
// Set a purchased missile to go to the top of the preferred list.
if (MissilePreferences == null)
MissilePreferences = new LinkedList<>();
if (MissilePreferences.contains(lMissile))
MissilePreferences.remove(lMissile); //<---- Line 199
MissilePreferences.push(lMissile);
if (MissilePreferences.size() > PREFERENCE_LIST_SIZE)
MissilePreferences.removeLast();
}
MissilePreferences
is a List of Integers.
CodePudding user response:
Try debugging the code so that you know what are the elements in the list and what element is being removed. I am still confused as to how can you get an index out of bounds exception when you are just accessing the element from the list by its value.
CodePudding user response:
There's a bit of a mixup going on here:
if (MissilePreferences.contains(lMissile))
MissilePreferences.remove(lMissile);
lMissile
is an int
, right? Representing an ID? So when you call contains
, you're checking if that ID exists in MissilePreferences
. You want to know if the List
contains that int
.
Here's the method signature for contains
:
public boolean contains(Object o)
It doesn't take a primitive like an int
, it takes an Object
- so your int
is being autoboxed into an Integer
- which makes sense because a List
can't contain primitives either, only objects. MissilePreferences
is a list of Integer
s.
So that's all fine, it converts your int
to an Integer
and tells you if it exists in the list of Integer
s, perfect! But then you call remove
with that original int
. And there are two versions of that method that take a parameter:
public boolean remove(Object o)
Removes the first occurrence of the specified element from this list, if it is present.
public E remove(int index)
Removes the element at the specified position in this list.
Those do two very different things. The Object
version removes the first instance of that object from the list. The int
version treats the int
as an index and removes whatever is in that position.
When you use contains
, you're treating your int
as an element in the list you want to find, and it's automatically converted to an Integer
. And you want to do the same with remove
- but there's a different version of that method that takes the int
you're providing directly. It won't autobox that and call the other method, obviously! You'd have to do it yourself:
Integer missileId = new Integer(lMissile);
// explicitly passing an Integer object to both - only crucial for #remove
if (MissilePreferences.contains(missileId))
MissilePreferences.remove(missileId);
That's what you have to watch out for - outside of arrays and some special classes you might run into elsewhere, collection objects don't contain primitives, so you often want to explicitly work with the Object
wrapper versions they contain. Especially important for int
s because of situations like this!
That said, you could just do this:
MissilePreferences.remove(new Integer(lMissile));
No need to use contains
- you can just call remove
and it'll remove the first matching object if it has one. It returns true if it removed anything, in case you need to check that