Arrays vs ArrayLists โ When to Use Which
ARRAY = Fixed locker row โ rent 20 slots, use 20 or waste them. ARRAYLIST = Expanding backpack โ magically grows as you stuff items in. Pick the right one and your code gets faster and cleaner.
The Mental Model
int[]Quick Decision Guide
| Scenario | Use | Why |
|---|---|---|
| Known max size (inventory slots, tile map) | Array | Zero overhead, cache-friendly |
| Unknown/growing size (loot drops, particles) | ArrayList | Auto-resize, convenient methods |
| High-frequency add/remove (bullets, particles) | Array + pooling | No allocation pressure |
| Need contains(), indexOf(), remove(Object) | ArrayList | Built-in search/remove |
| Primitive types in hot path (int[], float[]) | Array | No boxing/unboxing overhead |
Side-by-Side: Player Inventory
// ARRAY โ Fixed 20 slots (classic RPG style) String[] arrayInv = new String[20]; arrayInv[0] = "Iron Sword"; arrayInv[1] = "Health Potion"; // arrayInv[20] = "Shield"; // ๐ฅ ArrayIndexOutOfBoundsException! // ARRAYLIST โ Grows as needed (modern RPG) ArrayList<String> listInv = new ArrayList<>(); listInv.add("Iron Sword"); listInv.add("Health Potion"); listInv.add("Shield"); // Auto-expands! listInv.add(0, "Wooden Bow"); // Insert at front, shifts others right
Arrays Deep Dive โ Fixed-Size Power
Arrays are the bedrock of Java. Contiguous memory, zero overhead, and direct index access make them the fastest collection in the language. Every tile map, frame buffer, and hot-path data structure starts here.
Declaration Patterns
// 1. Declare + allocate (all null/0/false by default) Item[] equipment = new Item[5]; // 5 null references int[] stats = new int[6]; // {0,0,0,0,0,0} STR,DEX,INT,WIS,CON,CHA boolean[] flags = new boolean[32]; // 32 false flags // 2. Declare + initialize inline String[] classes = {"Warrior", "Mage", "Rogue", "Cleric"}; int[] dmgPerLevel = {5, 10, 15, 20, 25, 30}; float[] knockback = {0.5f, 1.0f, 1.5f, 2.0f}; // 3. Anonymous array (pass directly to a method) spawnEnemies(new String[]{"Goblin", "Orc", "Troll"});
๐ฎ Example 1: Tile Map (2D Array)
// Legend: 0=Grass, 1=Wall, 2=Water, 3=Chest, 4=Enemy int[][] dungeonFloor = { {1,1,1,1,1,1,1,1,1,1}, {1,0,0,0,2,0,0,3,0,1}, {1,0,1,0,2,0,1,0,0,1}, {1,0,0,0,0,0,0,0,4,1}, {1,1,1,1,1,1,1,1,1,1} }; final String[] TILES = {"๐ข","๐งฑ","๐","๐ฆ","๐น"}; void renderMap(int[][] map, int playerX, int playerY) { for (int y = 0; y < map.length; y++) { for (int x = 0; x < map[y].length; x++) { if (x == playerX && y == playerY) System.out.print("๐ง "); else System.out.print(TILES[map[y][x]] + " "); } System.out.println(); } } // ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ // ๐งฑ ๐ข ๐ข ๐ข ๐ ๐ข ๐ข ๐ฆ ๐ข ๐งฑ // ๐งฑ ๐ข ๐งฑ ๐ข ๐ ๐ข ๐งฑ ๐ข ๐ข ๐งฑ // ๐งฑ ๐ข ๐ข ๐ข ๐ข ๐ข ๐ข ๐ข ๐น ๐งฑ // ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ ๐งฑ
๐ฎ Example 2: Stat Growth Table
// [Level][STR, DEX, INT, HP, MP] int[][] growthTable = { { 10, 8, 6, 100, 20}, // Level 1 { 12, 9, 7, 120, 25}, // Level 2 { 14, 10, 8, 145, 30}, // Level 3 { 17, 12, 10, 175, 40}, // Level 4 { 20, 14, 12, 210, 50} // Level 5 }; void printStatBlock(int level) { int[] stats = growthTable[level - 1]; String[] names = {"STR","DEX","INT","HP ","MP "}; System.out.println("=== LEVEL " + level + " BASE STATS ==="); for (int i = 0; i < stats.length; i++) System.out.println(names[i] + ": " + stats[i]); }
โก Array Utilities (java.util.Arrays)
myArray[myArray.length]?ArrayIndexOutOfBoundsException โ one of the most common Java bugs. The last element is always at arr[arr.length - 1].ArrayLists Unleashed โ Dynamic Collections
When your data grows and shrinks โ quest logs, active enemies, inventory with no fixed cap โ ArrayList is your tool. It wraps an array internally, automatically resizing by 1.5x when full.
Creation Patterns
// 1. Empty, default initial capacity (10) ArrayList<String> loot = new ArrayList<>(); // 2. With initial capacity (avoids early resizes) ArrayList<Particle> particles = new ArrayList<>(500); // 3. From an existing collection var items = List.of("Sword", "Shield", "Potion"); ArrayList<String> inv = new ArrayList<>(items); // 4. Diamond operator (Java 7+) โ type inferred from left side ArrayList<Quest> questLog = new ArrayList<>();
Core Methods
๐ฎ Example 1: Quest Log System
record Quest(String id, String title, boolean completed, int xpReward) {} ArrayList<Quest> questLog = new ArrayList<>(); void addQuest(String id, String title, int xp) { questLog.add(new Quest(id, title, false, xp)); System.out.println("๐ New Quest: " + title); } void completeQuest(String id) { for (Quest q : questLog) { if (q.id().equals(id) && !q.completed()) { int idx = questLog.indexOf(q); questLog.set(idx, new Quest(q.id(), q.title(), true, q.xpReward())); System.out.println("โ Completed: " + q.title() + " (+" + q.xpReward() + " XP)"); return; } } } void showActiveQuests() { System.out.println("๐ ACTIVE QUESTS:"); for (Quest q : questLog) if (!q.completed()) System.out.println(" โก " + q.title() + " [" + q.xpReward() + " XP]"); } // Usage: addQuest("q1", "Kill 10 Rats", 100); addQuest("q2", "Find Lost Amulet", 250); addQuest("q3", "Defeat Goblin Chief", 500); completeQuest("q1"); showActiveQuests();
When removing items from a list while iterating, always iterate backwards: for (int i = list.size()-1; i >= 0; i--). Forward removal shifts indices and causes skipped elements or exceptions.
list.remove(2) and list.remove("sword")?remove(int index) removes the element at that position. remove(Object o) searches for the first occurrence using .equals() and removes it. For ArrayList<Integer>, use remove(Integer.valueOf(2)) to remove the value 2, not index 2.Common Operations โ Search, Sort & Transform
The real power of collections comes from operating on them. Sort a leaderboard in one line, filter loot by rarity with streams, convert a grid into an adjacency list for pathfinding โ this is where game data comes alive.
๐ฎ Example 1: Leaderboard Sorting
record PlayerScore(String name, int score, int level, String class_) {} ArrayList<PlayerScore> leaderboard = new ArrayList<>(List.of( new PlayerScore("Arcane", 98750, 45, "Mage"), new PlayerScore("BladeMaster", 87650, 42, "Warrior"), new PlayerScore("ShadowStep", 87650, 40, "Rogue"), new PlayerScore("HolyLight", 75300, 38, "Cleric") )); // Multi-key sort: score desc โ level desc โ name asc leaderboard.sort(Comparator .comparingInt((PlayerScore p) -> -p.score()) .thenComparingInt(p -> -p.level()) .thenComparing(PlayerScore::name) ); for (int i = 0; i < leaderboard.size(); i++) { PlayerScore p = leaderboard.get(i); String medal = switch(i) {case 0->"๐ฅ";case 1->"๐ฅ";case 2->"๐ฅ";default->" ";}; System.out.printf("%s #%-2d %-15s %,7d XP | Lv%-2d | %s%n", medal, i+1, p.name(), p.score(), p.level(), p.class_()); } // ๐ฅ #1 Arcane 98,750 XP | Lv45 | Mage // ๐ฅ #2 BladeMaster 87,650 XP | Lv42 | Warrior // ๐ฅ #3 ShadowStep 87,650 XP | Lv40 | Rogue
๐ฎ Example 2: Loot Filter with Streams (Java 8+)
record Item(String name, String type, int rarity, int value) {} // rarity: 0=Common, 1=Uncommon, 2=Rare, 3=Epic, 4=Legendary ArrayList<Item> allLoot = new ArrayList<>(List.of( new Item("Rusty Sword", "Weapon", 0, 10), new Item("Health Potion","Consumable", 0, 25), new Item("Flame Blade", "Weapon", 3, 5000), new Item("Dragon Scale", "Material", 4, 50000) )); // Filter: Only Rare+ weapons worth over 1000 gold List<Item> valuableWeapons = allLoot.stream() .filter(i -> i.type().equals("Weapon")) .filter(i -> i.rarity() >= 2) .filter(i -> i.value() > 1000) .sorted(Comparator.comparingInt(Item::value).reversed()) .toList(); for (Item i : valuableWeapons) { String[] icons = {"โช", "๐ข", "๐ต", "๐ฃ", "๐ "}; System.out.println(" " + icons[i.rarity()] + " " + i.name() + " โ " + i.value() + "g"); } // ๐ฃ Flame Blade โ 5000g
Streams don't modify the original list. Each .filter() creates a new stream, and .toList() collects the final result. Chain as many operations as you need โ it reads like plain English.
Comparator.comparingInt(Enemy::hp) sorts ascending (lowest HP first). Adding .reversed() flips it to descending (highest HP first). The third option would also work but also sorts ascending.2D & Jagged Arrays โ Grids & Uneven Data
2D arrays power every tile map, dungeon floor, and pixel buffer in game dev. Jagged arrays โ where each row has a different length โ are perfect for skill trees, dialog trees, and non-uniform data structures.
All rows same length
int[5][10] โ 5 rows, 10 cols
Tile maps, images, game boards
Each row can differ in length
int[][] with varying rows
Skill trees, dialog trees, loot tables
๐ฎ Example 1: Skill Tree (Jagged Array)
// Each tier has different number of skills! String[][] skillTree = { {"Slash"}, // Tier 1: 1 skill {"Power Strike", "Cleave"}, // Tier 2: 2 skills {"Whirlwind", "Execute", "Rend"}, // Tier 3: 3 skills {"Bladestorm", "Annihilate"} // Tier 4: 2 skills }; for (int tier = 0; tier < skillTree.length; tier++) { System.out.print("Tier " + (tier+1) + ": "); for (int s = 0; s < skillTree[tier].length; s++) { System.out.print("[" + skillTree[tier][s] + "] "); } System.out.println(" (Requires " + (tier * 5) + " SP)"); } // Tier 1: [Slash] (Requires 0 SP) // Tier 2: [Power Strike] [Cleave] (Requires 5 SP) // Tier 3: [Whirlwind] [Execute] [Rend] (Requires 10 SP)
๐ฎ Example 2: Dialog Tree (NPC Conversation)
record DialogNode(String npcText, String[] playerOptions, int[] nextNodes) {} DialogNode[] dialog = { // Node 0: Greeting new DialogNode( "Ah, an adventurer! Need supplies?", new String[]{"Buy", "Sell", "Rumors", "Goodbye"}, new int[]{1, 2, 3, -1} ), // Node 1: Shop new DialogNode( "Take a look at my wares!", new String[]{"Back"}, new int[]{0} ), // Node 2: Rumors โ leads to quest hook (node 4) new DialogNode( "Heard goblins in the north cave...", new String[]{"Tell me more", "Back"}, new int[]{4, 0} ) }; void runDialog() { int current = 0; while (current != -1) { DialogNode node = dialog[current]; System.out.println("\n๐ฌ " + node.npcText()); for (int i = 0; i < node.playerOptions().length; i++) System.out.println(" [" + (i+1) + "] " + node.playerOptions()[i]); if (node.playerOptions().length == 0) break; int choice = getInput(1, node.playerOptions().length) - 1; current = node.nextNodes()[choice]; } }
Dungeon Room Map
Create a jagged array representing dungeon floors where each floor has a different number of rooms:
- Floor 1: 3 rooms ("Entrance", "Hall", "Armory")
- Floor 2: 4 rooms ("Barracks", "Kitchen", "Library", "Vault")
- Floor 3: 2 rooms ("Boss Chamber", "Escape Tunnel")
- Print each floor and its rooms with nested loops
- Bonus: mark the last room on each floor as the "boss room"
Performance & Memory โ Under the Hood
Understanding memory layout is what separates a 30fps game from a 60fps one. Arrays and ArrayLists have vastly different memory costs โ and in game loops that run thousands of times per second, that difference matters enormously.
Memory Layout Comparison
int[1000] = 4,000 bytes. ArrayList<Integer>(1000) โ 24,000+ bytes (6ร overhead from boxing)
๐ฎ Object Pooling Pattern (Critical for Games)
class Pool<T> { private final ArrayList<T> available = new ArrayList<>(); private final ArrayList<T> active = new ArrayList<>(); private final Supplier<T> factory; Pool(Supplier<T> factory, int prewarm) { this.factory = factory; for (int i = 0; i < prewarm; i++) available.add(factory.get()); } T acquire() { T e = available.isEmpty() ? factory.get() // Create new only if needed : available.remove(available.size() - 1); // Reuse! active.add(e); return e; } void release(T e) { active.remove(e); // Remove from active // e.reset() โ reset state here available.add(e); // Return to pool } } // Usage in game loop โ ZERO allocation after warmup! Pool<Bullet> bullets = new Pool<>(Bullet::new, 200); Pool<Enemy> enemies = new Pool<>(Goblin::new, 50); void spawnBullet(float x, float y, float dir) { Bullet b = bullets.acquire(); b.init(x, y, dir); }
Use int[] / float[] for hot-path data (particle positions, vertex data). Pre-size ArrayLists with expected capacity. Object pools eliminate GC pauses. Profile before optimizing โ measure, don't guess.
int[] faster than ArrayList<Integer> for a hot loop?int to an Integer object on the heap (16+ bytes each). An int[1000] is 4KB of contiguous memory the CPU cache can devour. ArrayList<Integer> with 1000 elements is 24KB+ scattered on the heap.Capstone โ RPG Inventory Manager
The ultimate test. Build a full inventory system combining arrays, ArrayLists, enums, records, sorting, and streams โ exactly how a real game's item system works. Equipment slots, rarity tiers, stacking, and value queries all in one.
System Architecture
Data Model
enum Rarity { COMMON, UNCOMMON, RARE, EPIC, LEGENDARY } enum Slot { WEAPON, HELMET, CHEST, GLOVES, BOOTS, RING, AMULET, CONSUMABLE } record Item( String id, String name, Rarity rarity, Slot slot, int value, Map<String, Integer> stats, int maxStack ) { static final Map<Rarity, String> ICONS = Map.of( COMMON, "โช", UNCOMMON, "๐ข", RARE, "๐ต", EPIC, "๐ฃ", LEGENDARY, "๐ " ); String icon() { return ICONS.get(rarity); } } record Stack(Item item, int count) { boolean canAdd(int amount) { return count + amount <= item.maxStack(); } Stack add (int amount) { return new Stack(item, count + amount); } Stack remove(int amount) { return new Stack(item, count - amount); } }
Core Inventory Operations
class Inventory { private final int capacity; private final ArrayList<Stack> items = new ArrayList<>(); private final Map<Slot, Stack> equipped = new EnumMap<>(Slot.class); boolean add(Item item, int count) { // Try to merge with existing stack first for (int i = 0; i < items.size(); i++) { Stack s = items.get(i); if (s.item().id().equals(item.id()) && s.canAdd(count)) { items.set(i, s.add(count)); return true; } } if (items.size() >= capacity) return false; // Full! items.add(new Stack(item, count)); return true; } // Stream-powered queries List<Stack> getByRarity(Rarity minRarity) { return items.stream() .filter(s -> s.item().rarity().ordinal() >= minRarity.ordinal()) .sorted(Comparator.comparing(s -> -s.item().value())) .toList(); } int totalValue() { return items.stream() .mapToInt(s -> s.item().value() * s.count()) .sum(); } }
Quick Reference Cheatsheet
Practice Exercises
You've built the foundation. Next: combine Arrays + Loops for game systems, explore HashMaps for O(1) item lookups, and Generics to make your Pool and Inventory work with any type.