Free Response Questions

Question 1 - Pojos and Access Control:

Situation: The school librarian wants to create a program that stores all of the books within the library in a database and is used to manage their inventory of books available to the students. You decided to put your amazing code skills to work and help out the librarian!

a. Describe the key differences between the private and public access controllers and how it affects a POJO

Private access means that a variable or method can only be directly accessed within the object's own data and methods, NOT by directly calling them outside of it. Private variables can't even be accessed by subclasses. For example, if an object Book has a private String author, you could NOT access an instance's book.author directly; you'd have to access through a getter like book.getAuthor().

Public access means that a variable or method can be accessed from any other class. This can be used for easier implementation if the integrity of the object's data is not of great importance, for example. It may lead to reduced encapsulation if used incorrectly.

Using private access in a POJO ensures that its internal state is only accessible and modifiable by methods defined within the same class, which helps maintain data integrity and encapsulation principles. The scope of a class, variable or method in the context of a POJO primarily affects the ability of other classes to interact with it. Using private can help to control the manipulation of data by ensuring that it only occurs within the specific conditions of the object.

b. Identify a scenario when you would use the private vs. public access controllers that isn’t the one given in the scenario above.

One example is a pretty common one: a BankAccount class vs. a Bank class. A BankAccount object would have a lot of important info that should not be modifiable outside of the specifications of the BankAccount class, ensuring that the data is safe and accessed in limited ways. However, since we do want people to be able to access their bank accounts within limited capacity, the class Bank would contain public methods to interact with BankAccounts, particularly given proper credentials and valid arguments.

c. Create a Book class that represents the following attributes about a book: title, author, date published, person holding the book and make sure that the objects are using a POJO, the proper getters and setters and are secure from any other modifications that the program makes later to the objects

import java.util.Date;

public class Book {
    // specified attributes
    private String title;
    private String author;
    private Date datePublished;
    private String bookHolder; // this attribute could be its own class but for simplicity it's a String

    public Book(String title, String author, Date datePublished, String bookHolder) {
        this.title = title;
        this.author = author;
        this.datePublished = datePublished;
        this.bookHolder = bookHolder;
    }

    // getters to ensure the data is fetched securely
    public String getTitle() {return this.title;}
    public String getAuthor() {return this.author;}
    public Date getDatePublished() {return this.datePublished;}
    public String getBookHolder() {return this.bookHolder;}

    // setters to esnure that the data is modified exclusively within the object
    public void setTitle(String title) {this.title = title;}
    public void setAuthor(String author) {this.author = author;}
    public void setDatePublished(Date datePublished) {this.datePublished = datePublished;}
    public void setBookHolder(String bookHolder) {this.bookHolder = bookHolder;} // this one may actually be used

    // main method to show functionality
    public static void main(String[] args) {
        // creating a new book
        Book myBook = new Book("The Great Gatsby", "F. Scott Fitzgerald", new Date(), "John Doe");

        // displaying book information
        System.out.println("Title: " + myBook.getTitle());
        System.out.println("Author: " + myBook.getAuthor());
        System.out.println("Date Published: " + myBook.getDatePublished());
        System.out.println("Book Holder: " + myBook.getBookHolder());

        // changing book information using setter methods
        myBook.setTitle("To Kill a Mockingbird");
        myBook.setAuthor("Harper Lee");
        myBook.setDatePublished(new Date(1000000000000L)); // 1 trillion milliseconds after Jan 1, 1970, we like to have fun
        myBook.setBookHolder("Jane Smith");

        // displaying updated book information
        System.out.println("\nUpdated Information:");
        System.out.println("Title: " + myBook.getTitle());
        System.out.println("Author: " + myBook.getAuthor());
        System.out.println("Date Published: " + myBook.getDatePublished());
        System.out.println("Book Holder: " + myBook.getBookHolder());
    }
}

Book.main(null);
Title: The Great Gatsby
Author: F. Scott Fitzgerald
Date Published: Tue Mar 26 12:21:28 PDT 2024
Book Holder: John Doe

Updated Information:
Title: To Kill a Mockingbird
Author: Harper Lee
Date Published: Sat Sep 08 18:46:40 PDT 2001
Book Holder: Jane Smith

Question 2 - Writing Classes:

(a) Describe the different features needed to create a class and what their purpose is.

Classes technically only need to be declared with a unique name (preferably capitalized and camel-case) to be "created," but there are many components to classes. For a class to be the kind that College Board has us writing usually (like a POJO), it will often need attributes (class instance variables), constructors (which can include both argument constructors that use arguments to assign attributes AND non-argument constructors that set attributes to defaults), and/or unique class methods (which interact with the object/instance).

Classes are used as blueprints/instructions for the creation of object instances. This allows multiple different objects of the same class to be created, but to be interacted with in similar ways. For example, if a class for Person exists in a health database/program, it will likely contain info like the person's name, height, weight, age, etc. Even though multiple different instances of that person will have different values for their attributes, the same class methods can be used to access, modify, and utilize those values.

(b) Code:

Create a Java class BankAccount to represent a simple bank account. This class should have the following attributes:

  • accountHolder (String): The name of the account holder. balance (double): The current balance in the account. Implement the following mutator (setter) methods for the BankAccount class:
  • setAccountHolder(String name): Sets the name of the account holder.
  • deposit(double amount): Deposits a given amount into the account.
  • withdraw(double amount): Withdraws a given amount from the account, but only if the withdrawal amount is less than or equal to the current balance. Ensure that the balance is never negative.
public class BankAccount {
    // string attribute for account holder
    private String accountHolder;
    // double attribute for the bank account's balance (in dollars, presumably)
    private double balance;

    // a constructor used for testing and also to show mastery
    public BankAccount(String accountHolder, double balance) {
        this.accountHolder = accountHolder;
        // conditional to ensure that the balance is not negative
        this.balance = balance;
    }

    // getter methods for testing
    public String getAccountHolder() {return this.accountHolder;}
    public double getBalance() {return this.balance;}

    // setAccountHolder method, does what it says
    public void setAccountHolder(String name) {
        this.accountHolder = name;
    }

    // depositing method, which acts similar to a setter but adds a double
    public void deposit(double amount) {
        // conditional to ensure that the amount deposited is not negative
        if (amount > 0) {
            this.balance += amount;
        } else {
            System.out.println("You must deposit a valid amount.");
        }
    }

    // withdrawing method, which ensures that the amount withdrawn is valid
    public void withdraw(double amount) {
        // conditional to ensure that a valid amount is withdrawn
        if (this.balance >= amount) {
            this.balance -= amount;
        } else {
            System.out.println("You must withdraw a valid amount.");
        }
    }

    // main method to show functionality
    public static void main(String[] args) {
        // creating an instance of the BankAccount class
        BankAccount testAccount = new BankAccount("Mike Bankaccount", 399.98);
        // using the setter to replace its name
        System.out.println("Prior to the setter, the account name is " + testAccount.getAccountHolder() + ".");
        testAccount.setAccountHolder("John Bankaccount");
        System.out.println("After using the setter, the account name is " + testAccount.getAccountHolder() + ".\n");
        // using the deposit and withdraw methods
        System.out.println("Current amount: $" + testAccount.getBalance());
        testAccount.deposit(50.32);
        System.out.println("Amount after deposit: $" + testAccount.getBalance());
        testAccount.withdraw(79.83);
        System.out.println("Amount after withdrawal: $" + testAccount.getBalance());
    }
}

BankAccount.main(null);
Prior to the setter, the account name is Mike Bankaccount.
After using the setter, the account name is John Bankaccount.

Current amount: $399.98
Amount after deposit: $450.3
Amount after withdrawal: $370.47

Question 3 - Instantiation of a Class

(a) Explain how a constructor works, including when it runs and what generally is done within a constructor.

A constructor is a method innate to all objects in some form, though they can work within different specifications. A constructor takes any arguments provided (if applicable) and uses them (assuming that they are present) to assign values to instance variables. In the case that the constructor does not interact with the instance variables, it may instead be used to instantiate objects/variables in some way. No matter what, a Constructor "constructs" the object, making it interactible and accessible in some way. It runs when an object is being created/assigned (for example: Scanner scanner = new Scanner()).

(b) Create an example of an overloaded constructor within a class. You must use at least three variables. Include the correct initialization of variables and correct headers for the constructor. Then, run the constructor at least twice with different variables and demonstrate that these two objects called different constructors.

// class that will contain the constructors
public class Person {
    // i'm making these public so that I don't have to use getters to show their status
    public String name;
    public int age;
    public String hometown;
    public double currentGpa;

    // full argument constructor
    public Person(String name, int age, String hometown, double currentGpa) {
        this.name = name;
        this.age = age;
        this.hometown = hometown;
        this.currentGpa = currentGpa;
    }

    // name and age only
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        this.hometown = "unknown";
        this.currentGpa = -1.0;
    }

    // no-argument constructor
    public Person() {
        this.name = "No Argument";
        this.age = -1;
        this.hometown = "unknown";
        this.currentGpa = -1.0;
    }

    // main method to show functionality
    public static void main(String[] args) {
        // using no argument constructor
        Person noArgument = new Person();
        // using two-argument constructor
        Person twoArgument = new Person("Two Argument", 18);
        // using the full argument constructor
        Person fullArgument = new Person("Full Argument", 19, "Sunnyvale, California", 2.12);
        Person[] personArray = {noArgument, twoArgument, fullArgument}; // array for iteration

        // printing out data
        for (Person person : personArray) {
            System.out.println(person.name + ": \n\tAge: " + person.age + "\n\tHometown: " + person.hometown + "\n\tGPA: " + person.currentGpa);
        }
    }
}

Person.main(null);
No Argument: 
	Age: -1
	Hometown: unknown
	GPA: -1.0
Two Argument: 
	Age: 18
	Hometown: unknown
	GPA: -1.0
Full Argument: 
	Age: 19
	Hometown: Sunnyvale, California
	GPA: 2.12

TELESCOPING

public class Person {
    private String name;
    public int age;
    public String hometown;
    public double currentGpa;

    public Person(String name, int age, String hometown, double currentGpa) {
        this.name = name;
        this.age = age;
        this.hometown = hometown;
        this.currentGpa = currentGpa;
    }

    public Person(String name, int age, String hometown) {
        this(name, age, hometown, -1.0);
    }

    public Person(String name, int age) {
        this(name, age, "unknown");
    }

    public Person(String name) {
        this(name, -1);
    }

    public static void main(String[] args) {
        Person personOne = new Person("Person One", 1, "Oneville", 1.0);
        Person personTwo = new Person("Person Two", 2, "Twoson");
        Person personThree = new Person("Person Three", 3);
        Person personFour = new Person("Person Four");
        Person[] 
    }
}

Question 4 - Wrapper Classes:

(a) Provide a brief summary of what a wrapper class is and provide a small code block showing a basic example of a wrapper class.

A wrapper class is a class that "wraps" a primitive data type within an object. This lets you use primitives as objects, which allows for additional functionality like methods, constructors, and compatibility with collections like ArrayLists. Wrapper classes are especially useful in scenarios where objects are required instead of primitives. An example of that kind of scenario can be seen in the code block below:

public class Main {
    public static void main(String[] args) {
        // an ArrayList is a collection type taht requires an object type
        ArrayList<Integer> numbers = new ArrayList<>();

        // int value to be added to the Integer arraylist
        int intValue1 = 10;
        Integer integerValue1 = intValue1; // autoboxing: int to Integer
        numbers.add(integerValue1);

        Integer intValue2 = 20; // directly defining the int as the object Integer
        numbers.add(intValue2);

        // printing the arraylist
        System.out.println("Integers in ArrayList:");
        for (Integer number : numbers) {
            System.out.println("\t" + number);
        }
    }
}

Main.main(null);
Integers in ArrayList:
	10
	20

(b) Create a Java wrapper class called Temperature to represent temperatures in Celsius. Your Temperature class should have the following features:

Fields:

A private double field to store the temperature value in Celsius.

Constructor:

A constructor that takes a double value representing the temperature in Celsius and initializes the field.

Methods:

getTemperature(): A method that returns the temperature value in Celsius. setTemperature(double value): A method that sets a new temperature value in Celsius. toFahrenheit(): A method that converts the temperature from Celsius to Fahrenheit and returns the result as a double value.

public class Temperature {
    // single double attribute in Celcius (C)
    private double tempInC;

    // constructor that takes the temp and initializes
    public Temperature(double tempInC) {
        this.tempInC = tempInC;
    }

    // specified getter method that returns temp
    public double getTemperature() {
        return this.tempInC;
    }

    // specified setter method that sets its temp in celcius
    public void setTemperature(double value) {
        this.tempInC = value;
    }

    // unique method: converts celcius to farenheit
    // this is a good example of how this object would wrap a double well
    // providing access to unique functionality
    public double toFahrenheit() {
        // Celsius to Fahrenheit formula: (C × 9/5) + 32
        double tempInF = (this.tempInC * 9 / 5) + 32;
        return tempInF;
    }

    // main method for functionality
    public static void main(String[] args) {
        // creating temperature instance
        Temperature testTemperature = new Temperature(32.8);
        // using getter and setter
        System.out.println("Current temperature (in degrees C): " + testTemperature.getTemperature());
        testTemperature.setTemperature(33.7);
        System.out.println("Temperature after using setter (in degrees C): " + testTemperature.getTemperature());
        // using the fahrenheit converter
        System.out.println("New temperature converted to degrees F: " + testTemperature.toFahrenheit());
    }
}

Temperature.main(null);
Current temperature (in degrees C): 32.8
Temperature after using setter (in degrees C): 33.7
New temperature converted to degrees F: 92.66

Question 5 - Inheritence:

Situation: You are developing a program to manage a zoo, where various types of animals are kept in different enclosures. To streamline your code, you decide to use inheritance to model the relationships between different types of animals and their behaviors.

(a) Explain the concept of inheritance in Java. Provide an example scenario where inheritance is useful.

Inheritence allows POJOs to be able to use attributes and methods from other classes that act as parents to them. This can also allow a more specific instance of a broader class to be used (in the examples above, a Textbook being a more specific type of Book (parent class)). Inheritence is implemented by declaring a class a subclass of another class (the parent class), which will allow the class to utilize attributes, methods, and (using super()) constructors of the parent.

Inheritance can be useful, for example, if creating a subset of a broader object type that must handle a greater number of variables/attributes and/or must handle parent variables/attributes in a way that is different from the parent. If a Sandwich class, for example, was being used, a Hamburger subclass may be helpful if we wanted more specific data about the hamburger. It might have a meatWeight attribute or a cookType (medium, well, etc.) attribute that a sandwich would not need/have. However, by extending Sandwich, the hamburger class could utilize the attribtes like the contents of the Sandwich that apply to the burger, too.

(b) Code:

You need to implement a Java class hierarchy to represent different types of animals in the zoo. Create a superclass Animal with basic attributes and methods common to all animals, and at least three subclasses representing specific types of animals with additional attributes and methods. Include comments to explain your code, specifically how inheritance is used.

// here is the basic base Animal class
public class Animal {
    // these attributes can apply to any animal that exists
    private String speciesName;
    private int massInKg;
    private int speedInKmph;
    private String[] habitats;
    // this constructor will be inherited by the subclasses
    public Animal(String speciesName, int massInKg, int speedInKmph, String[] habitats) {
        this.speciesName = speciesName;
        this.massInKg = massInKg;
        this.speedInKmph = speedInKmph;
        this.habitats = habitats;
    }
    // getter for other classes
    public String getSpeciesName() {
        return this.speciesName;
    }
    // all animals move at some speed, so this method works for all animals
    public void move() {
        System.out.println("The " + this.speciesName + " moves at " + this.speedInKmph + " kilometers per hour!");
    }
}

public class PetDog extends Animal {
    // an additional attribute for all types of dogs
    private String owner;
    // this constructor will use super() to implement the Animal() constructor
    public PetDog(String speciesName, int massInKg, int speedInKmph, String[] habitats, String owner) {
        // calling super(), which calls the parent class's constructor
        super(speciesName, massInKg, speedInKmph, habitats);
        // setting new attribute separate from super
        this.owner = owner;
    }
    // getter for testing
    public String getOwner() {
        return this.owner;
    }
    // new method: bark (because dogs bark idk bro)
    public void bark() {
        System.out.println("The " + this.getSpeciesName() + " barks! The owner, " + this.owner + ", tells his/her dog to stop!");
    }
}

public class Fish extends Animal {
    // an additional attribute for all types of fish
    private int numberOfEggs;
    // this constructor will use super() to implement the Animal() constructor
    public Fish(String speciesName, int massInKg, int speedInKmph, String[] habitats, int numberOfEggs) {
        // calling super(), which calls the parent class's constructor
        super(speciesName, massInKg, speedInKmph, habitats);
        // setting new attribute separate from super
        this.numberOfEggs = numberOfEggs;
    }
    // getter for testing
    public int getNumberOfEggs() {
        return this.numberOfEggs;
    }
    // new method: lay eggs (because fish lay eggs)
    public void layEggs() {
        System.out.println("The " + this.getSpeciesName() + " lays about " + this.numberOfEggs + " eggs.");
    }
}

public class Lizard extends Animal {
    // an additional attribute for all types of lizards
    private boolean hasTail;
    // this constructor will use super() to implement the Animal() constructor
    public Lizard(String speciesName, int massInKg, int speedInKmph, String[] habitats, boolean hasTail) {
        // calling super(), which calls the parent class's constructor
        super(speciesName, massInKg, speedInKmph, habitats);
        // setting new attribute separate from super
        this.hasTail = hasTail;
    }
    // getter for testing
    public boolean getHasTail() {
        return this.hasTail;
    }
    // new method: remove tail (works together with the following method)
    public void removeTail() {
        if (this.hasTail) {
            this.hasTail = false; // the lizard no longer has a tail
            System.out.println("The lizard's tail was removed!");
        } else { // if the tail was already removed
            System.out.println("The lizard's tail has already been removed.");
        }
    }
    // new method: remove tail (lizards can regrow their tails)
    public void regrowTail() {
        if (!(this.hasTail)) {
            this.hasTail = true; // the tail grows back, so it has a tail now
            System.out.println("The lizard's tail regrew!");
        } else { // if it already has a tail
            System.out.println("The lizard already has a tail.");
        }
    }
}

public class Main {
    // main method used for functionality; see subclasses below
    public static void main(String[] args) {
        // creating a base animal object
        String[] animalHabitats = {"savannah", "marshland"};
        Animal animal = new Animal("creature", 100, 20, animalHabitats);
        animal.move(); // showing method functionality
        System.out.println();

        // instance of petdog subclass to show functionality
        String[] goldenRetrieverHabitats = {"suburban home"};
        PetDog goldenRetriever = new PetDog("Golden Retriever", 65, 30, goldenRetrieverHabitats, "John Goldenretriever");
        System.out.println("The owner's name is " + goldenRetriever.getOwner() + "."); // unique attribute to this subclass
        goldenRetriever.move(); // parent method is called
        goldenRetriever.bark(); // unique subclass method
        System.out.println();

        // instance fish subclass to show functionality
        String[] goldfishHabitats = {"freshwater lakes", "freshwater bogs", "freshwater swamps"};
        Fish goldfish = new Fish("goldfish", 1, 1, goldfishHabitats, 1000);
        goldfish.move(); // parent method is being called
        goldfish.layEggs(); // both a unique subclass method and one that shows a unique attribute
        System.out.println();

        // instance of lizard subclass to show functionality
        String[] blackIguanaHab = {"forests"};
        Lizard blackIg = new Lizard("black iguana", 5, 20, blackIguanaHab, true);
        blackIg.move(); // parent method is being called
        blackIg.removeTail(); // subclass method using new attribute hasTail
        blackIg.regrowTail(); // subclass method that uses that same attribute again with more logic
    }
}

Main.main(null);
The creature moves at 20 kilometers per hour!

The owner's name is John Goldenretriever.
The Golden Retriever moves at 30 kilometers per hour!
The Golden Retriever barks! The owner, John Goldenretriever, tells his/her dog to stop!

The goldfish moves at 1 kilometers per hour!
The goldfish lays about 1000 eggs.

The black iguana moves at 20 kilometers per hour!
The lizard's tail was removed!
The lizard's tail regrew!