Java Arrays: Unexpected behavior regarding size?

102 views Asked by At

Take a look at the following code used for constructing a card deck. I was using the The xFactor "variable" (hard coded) to subtract 1 off the array size to adjust for the zero index. I noticed that I could actually subtract a total of 40 before I encountered a 'java.lang.ArrayIndexOutOfBoundsException: 12' error.

If I enter 39 as the xFactor, it runs perfectly fine. It constructs the entire deck, all 52 cards, although strangely, cards.length will equal deckTotal - xFactor on BOTH sides of the for each loop?!? That doesn't make sense because it would need to reach an index of 51 in order to construct the entire deck, which it does...

public void constructDeck(){
    int deckTotal = SUITS.length * CARDS_PER_SUIT;
    System.out.println(deckTotal); // 52
    Card[] cards = new Card[deckTotal - xFactor]; 
    System.out.println(cards.length); // Prints deckTotal - xFactor
    for(String cardSuit : SUITS){
        int cardNum = 1;
        while(cardNum <= CARDS_PER_SUIT){
            String cardDesc = cardDescription(cardNum);
            int cardValue = cardNum;
            if(cardNum >= VALUE_CEILING){
                cardValue = VALUE_CEILING;
            }
            cards[cardNum -1] = new Card(cardDesc,cardSuit,cardValue);

            this.deck.addCard(cards[cardNum -1]);
            //System.out.println(cardDesc + " of " + cardSuit + " added to the deck");
            cardNum++;
        }
    }
    System.out.println(cards.length);  // prints: deckTotal - xFactor
}

The only thing I can think is that memory is being trimmed off the start of the array to make room new cards being added to the back. So that would mean the ArrayIndexOutOfBoundsException is really just a function of how quickly my computer can process the loop and/or collect the garbage.

I only wrote this code to test a really basic assignment for an intro to Java class. Coming from a JS background, declaring the size of an array before you use it seems a little odd to begin with (although I get why). The behavior here was really unexpected though, and I'm curious what is happening behind the scenes.

Update: Thank you for the advice. Here's my final code based on your advice.

public void constructDeck(){
    for(String cardSuit : SUITS){
        int cardNum = 1;
        while(cardNum <= CARDS_PER_SUIT){
            String cardDesc = cardDescription(cardNum);
            int cardValue = cardNum;
            if(cardNum >= VALUE_CEILING){
                cardValue = VALUE_CEILING;
            }
            this.deck.addCard(new Card(cardDesc,cardSuit,cardValue));
            cardNum++;
        }
    }
}
2

There are 2 answers

3
odedsh On BEST ANSWER

You access the cards array with.. cards[cardNum -1] This means that you only use the first 12 elements in the array. (different suits are written over previous suits)

As a result as long as xFactor allows for at least 12 cards to exist the code works.. (probably not as you expect though :) )

looking at the code you really don't need the array at all

       this.deck.addCard( new Card(cardDesc,cardSuit,cardValue) );
0
Persixty On

You're only populating the cards array in the inclusive range [0,CARDS_PER_SUIT-1] Look at this line:

cards[cardNum -1] = new Card(cardDesc,cardSuit,cardValue);

The variable cardNum is reset to 1 for each suit so you just keep overwriting the same range.

I think you've misunderstood how arrays are allocated. You should allocate the size. It's only the index that should stop short. An array for 52 cards should be allocated a size of 52 and the index range inclusively [0,51] which is 52 slots.

I think this is nearer what you want:

public void constructDeck(){
    int deckTotal = SUITS.length * CARDS_PER_SUIT;
    System.out.println(deckTotal); // 52
    Card[] cards = new Card[deckTotal]; //Allocate all the cards. 
    System.out.println(cards.length); // Prints deckTotal
    int suitOffset=0;
    for(String cardSuit : SUITS){
        int cardNum = 1;
        while(cardNum <= CARDS_PER_SUIT){
            String cardDesc = cardDescription(cardNum);
            int cardValue = cardNum;
            if(cardNum >= VALUE_CEILING){
                cardValue = VALUE_CEILING;
            }
            cards[suitOffset*CARDS_PER_SUIT+cardNum -1] = new Card(cardDesc,cardSuit,cardValue);

            this.deck.addCard(cards[cardNum -1]);
            //System.out.println(cardDesc + " of " + cardSuit + " added to the deck");
            cardNum++;
        }
        ++suitOffset;
    }
    System.out.println(cards.length);  // prints: deckTotal 
    //Now lets dump the cards to output and show we're dealing with a full deck!
    //I'm assuming Card.toString() has been overridden to return something useful like
    //the description.
    for(int i=0;i<cards.length;++i){
        System.out.println("cards["+i+"]="+cards[i].toString()); 
    }
}

However I'm not sure why you're both adding them to the object 'this.deck' object and a local array. I'm guessing, but it's not clear from this snippet if you even need the array cards if you can access this.deck.