Practice Problem 1: 🎮 Professional Virtual Pet Evolution
System with Access Control
Topics Covered: Access Modifiers, Encapsulation, JavaBean Standards, Constructor
Chaining, final Keyword
Requirements: Design a VirtualPet class system that demonstrates professional Java
development standards with proper access control, data hiding, and immutable configuration
objects.
Core Tasks:
a. Create VirtualPet class with four access levels:
● private final String petId, PetSpecies species, long
birthTimestamp (Immutable core)
● private String petName, int age, happiness, health (Controlled mutable
state)
● protected static final String[] DEFAULT_EVOLUTION_STAGES (Package
accessible)
● static final int MAX_HAPPINESS = 100, MAX_HEALTH = 100
(Package-private constants)
● public static final String PET_SYSTEM_VERSION = "2.0" (Global access)
b. Implement immutable PetSpecies class:
● final class that cannot be extended
● All fields must be final: speciesName, evolutionStages[], maxLifespan,
habitat
● Only getters (no setters) - defensive copying for arrays
● Constructor validation with IllegalArgumentException for invalid data
c. Constructor chaining with validation:
● Default constructor: Creates random pet with default species
● Constructor with name only: Uses default species and moderate stats
● Constructor with name and species: Uses default moderate stats
● Main constructor: Full parameter validation, all others chain to this
d. JavaBean compliance:
● Proper getter/setter naming conventions
● Validated setters with range checking (0-100 for stats)
● toString(), equals(), hashCode() implementations
● Private helper methods: validateStat(), generatePetId(), checkEvolution()
e. Access-controlled methods:
● public feedPet(String foodType), playWithPet(String gameType) - main
interface
● protected calculateFoodBonus(), calculateGameEffect() - internal
calculations
● private modifyHappiness(), modifyHealth(), updateEvolutionStage() -
internal logic
● Package-private getInternalState() for debugging within package
f. Create separate specialized pet classes (no inheritance):
● DragonPet class with private final String dragonType, breathWeapon
● RobotPet class with private boolean needsCharging, batteryLevel
● Each class follows same access patterns and JavaBean standards
● Use composition with VirtualPet reference if needed for shared functionality
Program:
import java.util.*;
// 🔹 Immutable PetSpecies
final class PetSpecies {
private final String speciesName;
private final String[] evolutionStages;
private final int maxLifespan;
private final String habitat;
public PetSpecies(String speciesName, String[] evolutionStages, int maxLifespan, String
habitat) {
if (speciesName == null || speciesName.isBlank()) {
throw new IllegalArgumentException("Species name cannot be null/blank");
}
if (evolutionStages == null || evolutionStages.length == 0) {
throw new IllegalArgumentException("Evolution stages cannot be empty");
}
if (maxLifespan <= 0) {
throw new IllegalArgumentException("Max lifespan must be positive");
}
if (habitat == null || habitat.isBlank()) {
throw new IllegalArgumentException("Habitat cannot be null/blank");
}
this.speciesName = speciesName;
this.evolutionStages = Arrays.copyOf(evolutionStages, evolutionStages.length);
this.maxLifespan = maxLifespan;
this.habitat = habitat;
}
public String getSpeciesName() { return speciesName; }
public String[] getEvolutionStages() { return Arrays.copyOf(evolutionStages,
evolutionStages.length); }
public int getMaxLifespan() { return maxLifespan; }
public String getHabitat() { return habitat; }
@Override
public String toString() {
return "PetSpecies{" +
"speciesName='" + speciesName + '\'' +
", maxLifespan=" + maxLifespan +
", habitat='" + habitat + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (!(o instanceof PetSpecies)) return false;
PetSpecies other = (PetSpecies) o;
return maxLifespan == other.maxLifespan &&
Objects.equals(speciesName, other.speciesName) &&
Arrays.equals(evolutionStages, other.evolutionStages) &&
Objects.equals(habitat, other.habitat);
}
@Override
public int hashCode() {
return Objects.hash(speciesName, maxLifespan, habitat) +
Arrays.hashCode(evolutionStages);
}
}
// 🔹 VirtualPet Core Class
class VirtualPet {
// Immutable core
private final String petId;
private final PetSpecies species;
private final long birthTimestamp;
// Mutable state
private String petName;
private int age;
private int happiness;
private int health;
// Protected default stages
protected static final String[] DEFAULT_EVOLUTION_STAGES = {"Egg", "Child", "Adult"};
// Package-private constants
static final int MAX_HAPPINESS = 100;
static final int MAX_HEALTH = 100;
// Public constant
public static final String PET_SYSTEM_VERSION = "2.0";
// 🔹 Constructor chaining
public VirtualPet() {
this("Defaulty", new PetSpecies("DefaultSpecies", DEFAULT_EVOLUTION_STAGES, 100,
"VirtualWorld"), 1, 50, 50);
}
public VirtualPet(String petName) {
this(petName, new PetSpecies("DefaultSpecies", DEFAULT_EVOLUTION_STAGES, 100,
"VirtualWorld"));
}
public VirtualPet(String petName, PetSpecies species) {
this(petName, species, 1, 50, 50);
}
public VirtualPet(String petName, PetSpecies species, int age, int happiness, int health) {
this.petId = generatePetId();
this.species = Objects.requireNonNull(species);
this.birthTimestamp = System.currentTimeMillis();
this.petName = Objects.requireNonNull(petName);
setAge(age);
setHappiness(happiness);
setHealth(health);
}
// 🔹 Getters/Setters
public String getPetId() { return petId; }
public PetSpecies getSpecies() { return species; }
public long getBirthTimestamp() { return birthTimestamp; }
public String getPetName() { return petName; }
public void setPetName(String petName) { this.petName = petName; }
public int getAge() { return age; }
public void setAge(int age) { this.age = Math.max(0, age); }
public int getHappiness() { return happiness; }
public void setHappiness(int happiness) { this.happiness = validateStat(happiness); }
public int getHealth() { return health; }
public void setHealth(int health) { this.health = validateStat(health); }
// 🔹 Access-controlled methods
public void feedPet(String foodType) {
int bonus = calculateFoodBonus(foodType);
modifyHealth(bonus);
}
public void playWithPet(String gameType) {
int bonus = calculateGameEffect(gameType);
modifyHappiness(bonus);
}
protected int calculateFoodBonus(String foodType) {
return foodType.equalsIgnoreCase("fruit") ? 10 : 5;
}
protected int calculateGameEffect(String gameType) {
return gameType.equalsIgnoreCase("fetch") ? 15 : 8;
}
private void modifyHappiness(int delta) {
happiness = Math.min(MAX_HAPPINESS, happiness + delta);
updateEvolutionStage();
}
private void modifyHealth(int delta) {
health = Math.min(MAX_HEALTH, health + delta);
updateEvolutionStage();
}
private void updateEvolutionStage() {
// Placeholder for evolution logic
}
// Package-private for debugging
String getInternalState() {
return "Pet{" + petName + ", age=" + age + ", health=" + health + ", happiness=" +
happiness + "}";
}
// Helpers
private int validateStat(int value) {
if (value < 0 || value > 100) return 50;
return value;
}
private String generatePetId() {
return UUID.randomUUID().toString();
}
@Override
public String toString() {
return "VirtualPet{" +
"petId='" + petId + '\'' +
", petName='" + petName + '\'' +
", species=" + species +
", age=" + age +
", happiness=" + happiness +
", health=" + health +
'}';
}
@Override
public boolean equals(Object o) {
if (!(o instanceof VirtualPet)) return false;
VirtualPet other = (VirtualPet) o;
return Objects.equals(petId, other.petId);
}
@Override
public int hashCode() {
return Objects.hash(petId);
}
}
// 🔹 Specialized Pets via Composition
class DragonPet {
private final String dragonType;
private final String breathWeapon;
private final VirtualPet corePet;
public DragonPet(String petName, String dragonType, String breathWeapon) {
this.dragonType = dragonType;
this.breathWeapon = breathWeapon;
this.corePet = new VirtualPet(
petName,
new PetSpecies("Dragon", new String[]{"Egg", "Wyrmling", "Adult Dragon"}, 500,
"Cave")
);
}
public VirtualPet getCorePet() { return corePet; }
public String getDragonType() { return dragonType; }
public String getBreathWeapon() { return breathWeapon; }
}
class RobotPet {
private boolean needsCharging;
private int batteryLevel;
private final VirtualPet corePet;
public RobotPet(String petName) {
this.needsCharging = false;
this.batteryLevel = 100;
this.corePet = new VirtualPet(
petName,
new PetSpecies("Robot", new String[]{"Prototype", "AdvancedBot", "AI Overlord"},
1000, "Lab")
);
}
public VirtualPet getCorePet() { return corePet; }
public boolean isNeedsCharging() { return needsCharging; }
public void setNeedsCharging(boolean needsCharging) { this.needsCharging =
needsCharging; }
public int getBatteryLevel() { return batteryLevel; }
public void setBatteryLevel(int batteryLevel) {
this.batteryLevel = Math.max(0, Math.min(100, batteryLevel));
}
}
// 🔹 Demo
public class Main {
public static void main(String[] args) {
VirtualPet pet = new VirtualPet("Buddy");
System.out.println(pet);
pet.feedPet("fruit");
pet.playWithPet("fetch");
System.out.println("After activities: " + pet.getInternalState());
DragonPet draco = new DragonPet("Smaug", "Fire", "Flame Breath");
System.out.println("DragonPet core: " + draco.getCorePet());
RobotPet robo = new RobotPet("R2D2");
robo.setBatteryLevel(80);
System.out.println("RobotPet core: " + robo.getCorePet() + ", battery=" +
robo.getBatteryLevel());
}
}
Output:
VirtualPet{petId='a6e2c52e-7d8c-4c1a-bc4e-8fa3e3b3c1d1', petName='Buddy',
species=PetSpecies{speciesName='DefaultSpecies', maxLifespan=100, habitat='VirtualWorld'},
age=1, happiness=50, health=50}
After activities: Pet{Buddy, age=1, health=60, happiness=65}
DragonPet core: VirtualPet{petId='4b3912b7-bad5-44e1-92a5-7f25bb2cf2ab',
petName='Smaug', species=PetSpecies{speciesName='Dragon', maxLifespan=500,
habitat='Cave'}, age=1, happiness=50, health=50}
RobotPet core: VirtualPet{petId='dc2213a2-62b4-4af6-9ed2-3f42acaa73a9', petName='R2D2',
species=PetSpecies{speciesName='Robot', maxLifespan=1000, habitat='Lab'}, age=1,
happiness=50, health=50}, battery=80
Practice Problem 2: 🏰 Medieval Kingdom Management
with Security Architecture
Topics Covered: Access Modifiers, Immutable Objects, instanceof, Constructor
Overloading
Requirements: Build a magical kingdom system with proper access control and immutable
configuration management using separate classes for different structure types.
Core Tasks:
a. Create immutable KingdomConfig class:
● final class with all final fields: kingdomName, foundingYear,
allowedStructureTypes[], resourceLimits Map
● Constructor with full validation and defensive copying
● Only getters, no setters - return clones for mutable references
● Factory methods: createDefaultKingdom(), createFromTemplate(String
type)
b. Base MagicalStructure class (no inheritance hierarchy):
● private final String structureId, long constructionTimestamp
(Immutable identity)
● private final String structureName, location (Immutable properties)
● private int magicPower, boolean isActive, String
currentMaintainer (Controlled state)
● static final int MIN_MAGIC_POWER = 0, MAX_MAGIC_POWER = 1000
(Package constants)
● public static final String MAGIC_SYSTEM_VERSION = "3.0" (Global
constant)
c. Constructor chaining in base class:
● public MagicalStructure(String name, String location) - basic
constructor
● public MagicalStructure(String name, String location, int power) -
with power
● Main constructor: public MagicalStructure(String name, String
location, int power, boolean active)
● All constructors validate inputs and chain to main constructor
d. Four separate specialized structure classes:
● WizardTower: private final int maxSpellCapacity, private
List<String> knownSpells, private String currentWizard
● EnchantedCastle: private final String castleType, private int
defenseRating, private boolean hasDrawbridge
● MysticLibrary: private final Map<String, String> bookCollection,
private int knowledgeLevel
● DragonLair: private final String dragonType, private long
treasureValue, private int territorialRadius
e. Each structure class with unique constructor patterns:
● WizardTower: Empty tower, basic spells, fully equipped options
● Castle: Simple fort, royal castle, impregnable fortress variations
● Library: Few books, moderate collection, ancient archives options
● DragonLair: Different dragon types with specific lair requirements
f. KingdomManager class with instanceof usage:
● private final List<Object> structures (stores different structure types)
● private final KingdomConfig config
● public static boolean canStructuresInteract(Object s1, Object s2)
- use instanceof for type checking
● public static String performMagicBattle(Object attacker, Object
defender)
● public static int calculateKingdomPower(Object[] structures)
● private String determineStructureCategory(Object structure) - type
identification
g. JavaBean compliance for all classes:
● Proper getter/setter patterns where appropriate
● Immutable classes with only getters
● Standard toString(), equals(), hashCode() methods
Program:
import java.util.*;
// 🔹 Immutable KingdomConfig
final class KingdomConfig {
private final String kingdomName;
private final int foundingYear;
private final String[] allowedStructureTypes;
private final Map<String, Integer> resourceLimits;
public KingdomConfig(String kingdomName, int foundingYear, String[]
allowedStructureTypes, Map<String, Integer> resourceLimits) {
if (kingdomName == null || kingdomName.isBlank())
throw new IllegalArgumentException("Kingdom name cannot be empty");
if (foundingYear <= 0)
throw new IllegalArgumentException("Invalid founding year");
if (allowedStructureTypes == null || allowedStructureTypes.length == 0)
throw new IllegalArgumentException("Must allow at least one structure type");
if (resourceLimits == null || resourceLimits.isEmpty())
throw new IllegalArgumentException("Resource limits must be provided");
this.kingdomName = kingdomName;
this.foundingYear = foundingYear;
this.allowedStructureTypes = Arrays.copyOf(allowedStructureTypes,
allowedStructureTypes.length);
this.resourceLimits = new HashMap<>(resourceLimits); // defensive copy
}
public String getKingdomName() { return kingdomName; }
public int getFoundingYear() { return foundingYear; }
public String[] getAllowedStructureTypes() { return Arrays.copyOf(allowedStructureTypes,
allowedStructureTypes.length); }
public Map<String, Integer> getResourceLimits() { return new HashMap<>(resourceLimits); }
// Factory Methods
public static KingdomConfig createDefaultKingdom() {
return new KingdomConfig("Avaloria", 1024, new String[]{"Tower", "Castle", "Library",
"Lair"},
Map.of("Gold", 10000, "Mana", 5000));
}
public static KingdomConfig createFromTemplate(String type) {
switch (type.toLowerCase()) {
case "military":
return new KingdomConfig("Ironhold", 1200, new String[]{"Castle", "Lair"},
Map.of("Gold", 20000, "Mana", 2000));
case "magical":
return new KingdomConfig("Mystica", 800, new String[]{"Tower", "Library"},
Map.of("Gold", 5000, "Mana", 10000));
default:
return createDefaultKingdom();
}
}
@Override
public String toString() {
return "KingdomConfig{" + "kingdomName='" + kingdomName + '\'' +
", foundingYear=" + foundingYear +
", resources=" + resourceLimits + '}';
}
@Override
public boolean equals(Object o) {
if (!(o instanceof KingdomConfig)) return false;
KingdomConfig other = (KingdomConfig) o;
return foundingYear == other.foundingYear &&
Objects.equals(kingdomName, other.kingdomName) &&
Arrays.equals(allowedStructureTypes, other.allowedStructureTypes) &&
Objects.equals(resourceLimits, other.resourceLimits);
}
@Override
public int hashCode() {
return Objects.hash(kingdomName, foundingYear, resourceLimits) +
Arrays.hashCode(allowedStructureTypes);
}
}
// 🔹 Base MagicalStructure (no inheritance hierarchy, but shared logic)
class MagicalStructure {
private final String structureId;
private final long constructionTimestamp;
private final String structureName;
private final String location;
private int magicPower;
private boolean isActive;
private String currentMaintainer;
static final int MIN_MAGIC_POWER = 0;
static final int MAX_MAGIC_POWER = 1000;
public static final String MAGIC_SYSTEM_VERSION = "3.0";
// Constructor chaining
public MagicalStructure(String name, String location) {
this(name, location, 100, true);
}
public MagicalStructure(String name, String location, int power) {
this(name, location, power, true);
}
public MagicalStructure(String name, String location, int power, boolean active) {
if (name == null || name.isBlank()) throw new IllegalArgumentException("Invalid structure
name");
if (location == null || location.isBlank()) throw new IllegalArgumentException("Invalid
location");
if (power < MIN_MAGIC_POWER || power > MAX_MAGIC_POWER)
throw new IllegalArgumentException("Invalid power");
this.structureId = UUID.randomUUID().toString();
this.constructionTimestamp = System.currentTimeMillis();
this.structureName = name;
this.location = location;
this.magicPower = power;
this.isActive = active;
this.currentMaintainer = "Unknown";
}
// Getters/Setters
public String getStructureId() { return structureId; }
public long getConstructionTimestamp() { return constructionTimestamp; }
public String getStructureName() { return structureName; }
public String getLocation() { return location; }
public int getMagicPower() { return magicPower; }
public void setMagicPower(int magicPower) {
this.magicPower = Math.max(MIN_MAGIC_POWER, Math.min(MAX_MAGIC_POWER,
magicPower));
}
public boolean isActive() { return isActive; }
public void setActive(boolean active) { isActive = active; }
public String getCurrentMaintainer() { return currentMaintainer; }
public void setCurrentMaintainer(String currentMaintainer) { this.currentMaintainer =
currentMaintainer; }
@Override
public String toString() {
return structureName + " at " + location + " (Power=" + magicPower + ")";
}
@Override
public boolean equals(Object o) {
if (!(o instanceof MagicalStructure)) return false;
return Objects.equals(structureId, ((MagicalStructure) o).structureId);
}
@Override
public int hashCode() {
return Objects.hash(structureId);
}
}
// 🔹 Specialized Structures
class WizardTower {
private final int maxSpellCapacity;
private List<String> knownSpells;
private String currentWizard;
private final MagicalStructure core;
// Constructor variations
public WizardTower(String name, String location) {
this(name, location, 100, new ArrayList<>(), "No wizard");
}
public WizardTower(String name, String location, int maxCapacity, List<String> spells, String
wizard) {
this.core = new MagicalStructure(name, location);
this.maxSpellCapacity = maxCapacity;
this.knownSpells = new ArrayList<>(spells);
this.currentWizard = wizard;
}
public MagicalStructure getCore() { return core; }
public List<String> getKnownSpells() { return new ArrayList<>(knownSpells); }
public void addSpell(String spell) { if (knownSpells.size() < maxSpellCapacity)
knownSpells.add(spell); }
@Override public String toString() { return "WizardTower{" + core + ", wizard=" +
currentWizard + "}"; }
}
class EnchantedCastle {
private final String castleType;
private int defenseRating;
private boolean hasDrawbridge;
private final MagicalStructure core;
public EnchantedCastle(String name, String location, String type) {
this(name, location, type, 100, true);
}
public EnchantedCastle(String name, String location, String type, int defense, boolean
drawbridge) {
this.core = new MagicalStructure(name, location);
this.castleType = type;
this.defenseRating = defense;
this.hasDrawbridge = drawbridge;
}
public MagicalStructure getCore() { return core; }
@Override public String toString() { return "EnchantedCastle{" + core + ", type=" + castleType
+ "}"; }
}
class MysticLibrary {
private final Map<String, String> bookCollection;
private int knowledgeLevel;
private final MagicalStructure core;
public MysticLibrary(String name, String location) {
this(name, location, Map.of("Basics", "Magic 101"), 50);
}
public MysticLibrary(String name, String location, Map<String, String> collection, int
knowledge) {
this.core = new MagicalStructure(name, location);
this.bookCollection = new HashMap<>(collection);
this.knowledgeLevel = knowledge;
}
public MagicalStructure getCore() { return core; }
@Override public String toString() { return "MysticLibrary{" + core + ", knowledge=" +
knowledgeLevel + "}"; }
}
class DragonLair {
private final String dragonType;
private long treasureValue;
private int territorialRadius;
private final MagicalStructure core;
public DragonLair(String name, String location, String type) {
this(name, location, type, 1000, 50);
}
public DragonLair(String name, String location, String type, long treasure, int radius) {
this.core = new MagicalStructure(name, location);
this.dragonType = type;
this.treasureValue = treasure;
this.territorialRadius = radius;
}
public MagicalStructure getCore() { return core; }
@Override public String toString() { return "DragonLair{" + core + ", dragon=" + dragonType +
"}"; }
}
// 🔹 KingdomManager
class KingdomManager {
private final List<Object> structures;
private final KingdomConfig config;
public KingdomManager(KingdomConfig config) {
this.config = config;
this.structures = new ArrayList<>();
}
public void addStructure(Object structure) { structures.add(structure); }
public static boolean canStructuresInteract(Object s1, Object s2) {
return s1.getClass() == s2.getClass(); // only same type interact
}
public static String performMagicBattle(Object attacker, Object defender) {
if (attacker instanceof WizardTower && defender instanceof DragonLair) {
return "Wizard casts spells at the dragon!";
} else if (attacker instanceof DragonLair && defender instanceof EnchantedCastle) {
return "Dragon breathes fire on the castle!";
} else if (attacker instanceof MysticLibrary && defender instanceof WizardTower) {
return "Library knowledge empowers the tower!";
}
return "No significant battle.";
}
public static int calculateKingdomPower(Object[] structures) {
int total = 0;
for (Object s : structures) {
if (s instanceof WizardTower) total += ((WizardTower) s).getCore().getMagicPower();
else if (s instanceof EnchantedCastle) total += ((EnchantedCastle)
s).getCore().getMagicPower();
else if (s instanceof MysticLibrary) total += ((MysticLibrary)
s).getCore().getMagicPower();
else if (s instanceof DragonLair) total += ((DragonLair) s).getCore().getMagicPower();
}
return total;
}
private String determineStructureCategory(Object structure) {
if (structure instanceof WizardTower) return "Tower";
if (structure instanceof EnchantedCastle) return "Castle";
if (structure instanceof MysticLibrary) return "Library";
if (structure instanceof DragonLair) return "Lair";
return "Unknown";
}
@Override
public String toString() { return "KingdomManager{" + "config=" + config + ", structures=" +
structures + "}"; }
}
// 🔹 Demo
public class Main {
public static void main(String[] args) {
KingdomConfig config = KingdomConfig.createDefaultKingdom();
KingdomManager manager = new KingdomManager(config);
WizardTower tower = new WizardTower("Arcane Spire", "North Hill");
EnchantedCastle castle = new EnchantedCastle("Iron Keep", "Central Plains", "Royal");
MysticLibrary library = new MysticLibrary("Grand Archives", "East Grove");
DragonLair lair = new DragonLair("Smaug's Den", "Mountain Peak", "Fire Dragon");
manager.addStructure(tower);
manager.addStructure(castle);
manager.addStructure(library);
manager.addStructure(lair);
System.out.println(config);
System.out.println(tower);
System.out.println(castle);
System.out.println(library);
System.out.println(lair);
System.out.println("Can Tower & Lair interact? " +
KingdomManager.canStructuresInteract(tower, lair));
System.out.println("Battle: " + KingdomManager.performMagicBattle(tower, lair));
Object[] structures = {tower, castle, library, lair};
System.out.println("Total Kingdom Power: " +
KingdomManager.calculateKingdomPower(structures));
}
}
Output:
KingdomConfig{kingdomName='Avaloria', foundingYear=1024, resources={Mana=5000,
Gold=10000}}
WizardTower{Arcane Spire at North Hill (Power=100), wizard=No wizard}
EnchantedCastle{Iron Keep at Central Plains (Power=100), type=Royal}
MysticLibrary{Grand Archives at East Grove (Power=100), knowledge=50}
DragonLair{Smaug's Den at Mountain Peak (Power=100), dragon=Fire Dragon}
Can Tower & Lair interact? false
Battle: Wizard casts spells at the dragon!
Total Kingdom Power: 400