Menu
Kevin's Guides

Chapter 15: Object Oriented Design Intro - Classes and Constructors

Write a comment

Object Oriented Design: Classes & Constructors

Welcome to the first chapter on OOP! This is the first of several chapters on this subject. To begin, we will examine basic object-oriented design principles. You will learn what object-oriented design is and its fundamental role in modern application development. By the end of this chapter, you should understand how to create basic objects with their own unique stores of information and functions.


What is Object Oriented Programming?

Object Oriented Programming (OOP) is a design paradigm which allows us to think of code in terms of virtual objects containing their own data and functions. These virtual objects may be reflections of real-world objects. Other times, objects might represent a more abstract computing concept, such as the System output stream or an Exception.

We have been using objects to achieve various things throughout this series. Scanners, BigDecimals, ArrayLists, and Exceptions are all examples of objects we've used. Now, you will learn how to create your own objects.

Four Key Principles of OOP

There are four key principles or pillars of OOP, regardless of programming language. Keep them in mind. They may make more sense as we review examples.

#1: Abstraction

  • An abstract concept is an idea without physical form. For example, the interest owed on a loan is an abstract concept. It exists on paper. We understand what it means, but you can't physically manifest interest. You can manifest cash representing interest, but not interest itself.
  • Abstraction with relation to computing refers to the way we can model parts of a system in an abstract manner. For example, an ArrayList is not a physical object, but we can still .add() other virtual objects to it and .remove() them. It's an abstract concept that's been turned into an object with functions we can use in meaningful ways.
Taxes are an abstract concept, but if you don't pay them you'll get in troubleTaxes are Abstract (character from Freepic)

#2: Encapsulation

  • Encapsulation refers to objects having their own private state and functions, which are only accessible via public functions. We can create a "Ball" object and say it has a radius and a position. We can also create a bounce method that affects the position of the ball. Each Ball created is then its own separate entity. Ball A can have a radius independent of Ball B. The program might be able to trigger the Ball's movement using its public .bounce() method, without having to directly access the internal code making up a Ball object. The program can use a Ball object without having to know the actual implementation of the object.
  • When data is attached to a certain object we say that it's encapsulated.
  • This is why we can use Scanners, ArrayLists, BigDecimals, etc. without having to directly access or modify their data. We just pop (import) them into our programs and they more or less magically start working. You don't have to fully understand the inner workings of the system output stream to use System.out.println() - all you need to understand is the .println() method from the System.out object prints a message to the screen.
  • From a security and memory management perspective, this is essential. Encapsulation prevents your program from modifying the wrong information. Since each object has data that only belongs to itself, we don't have to worry about changing the position of Ball A and it somehow interfering with the position of Ball B.
image representing encapsulation - pills with instance variables and methods, we will discuss more later

#3: Inheritance

  • Objects can have hierarchical relationships and pass down information and functions from one to another. We can create more general "parent" objects, and pass their general information and functions down to "child" objects. Inheritance describes this hierarchical relationship.
  • For example, we could have a Ball object with attributes representing the ball's color and size. We could then create a more specific Ball called a "BowlingBall" which has more attributes, like weight, material, and hand size. The BowlingBall would still have the color and size attributes, besides its class specific attributes. The Ball would be the parent and the BowlingBall would be the child. This is a parent-child relationship. You may also hear this referred to as an "IS - A" relationship. A Ball is a BowlingBall. All BowlingBalls are Balls, but not all Balls are BowlingBalls.
  •  This makes code more reusable and flexible. It also simplifies coding. Rather than having to re-write the code common to every type of Ball, you can just create one Ball class and then make specific changes in the child classes.
Image showing the parent child relationship of a general ball - the parent - and more specific balls, like bowling, soccer, and golf - the children classes

#4: Polymorphism

  • In Java, polymorphism is a feature of inheritance which enables us to implement different objects which share similar logic.
  • Polymorphic means having many shapes. A polymorphic reference in Java may reference objects of several different types.
  • For example, you could create a Player object, a SoccerBall, and a BowlingBall. The Player can hold any type of Ball, so we attach a "holdingObject" state variable to the Player object. Rather than having to specify the exact type of Ball the Player is holding, we can just say they're holding some type of Ball.
  • Polymorphism can extend to methods which are overridden in subclasses. For example, a Cylinder and a Cube could both be "Shape" objects, and each could have a "getVolume" method that calculates the volume of the given shape differently based on what type it is.
illustration of cartoon representing polymorphism - a class file is asking for a ball, doesn't care what ball it is. Gets a soccer ball.

Again, these definitions will make more sense when we see them in use. So don't worry if you don't quite understand it yet.


Classes & Constructors

A class is a template for an object. They are where we define what information an object stores and what functions (methods) the object can perform.

So far, we have been writing all the code within a single class file containing the main method and possibly a few other methods. As your applications become more complex, it makes sense to design them with multiple classes. This not only makes your code more organized and understandable, it can also make your code more reusable. For example, we could create a "Ball" class in one program, and we may use it in several other similar projects down the road.

Project/Class Structure

Let's quickly recap the structure of a Java Project.

A Java Project is a collection of files containing everything the program needs to run.

At the very least, a Java project should have 1 class file with a main method.

A class file defines an object using instance variables and methods (functions).

projectstructure

Class files are saved as .java files. They are text files containing our code.

Classes are declared at the beginning using the keywords public class ClassName

Classes are conventionally named using PascalCase in Java, where the first letter of each word is capitalized. You should get in the habit of this. It makes it easy to distinguish object types/classes from other words in your code.

classstructure

Creating a Class

Every project you've created already has at least one class file. Now, let's create a program that uses a second class file.

Consider this. We have a program that keeps track of different users. Each user has a name, a password, and an email associated with them. We want to define a new object called "User" which holds this information.

Begin by creating a new Java project with a main class and a main method, as you usually would. Next, create a new .java class file in the same directory as your main class. Call this file "User.java"

add user class idea

The Constructor

The constructor is the most important method in any class file. It's what constructs an instance of the class (go figure). It takes arguments known as parameters from wherever it was called and generates a new object using those parameters.

Let's take a look at a very simple example.

User.java
public class User {
    
    //The Constructor for a User
    public User(){
        System.out.println("You just made a user.");
    }

}

As you can see, the constructor is a method and its name is the same as that of the class it's in. It's important that you use the same name for the class file, the class name, and the constructor name.

This is a public method, meaning other files can create new instances of User. The keyword public has to deal with the scope of the method. In programming, scope refers to where you can and cannot use something, such as a function or a variable. Things that are public are accessible from outside the class file they were created in. Things that are private are only usable within the class file they belong to.

The User constructor shown above does not really do much. It takes no arguments or parameters (this is a default constructor) and stores no information. All it does is print out a message when a new User object is initialized.

To create a new User object, we must declare the object type being created, assign an identifier, and initialize it as a new object using the new keyword. Just like you would if you were creating a new Scanner.

Main.java

public class Main{
    public static void main(String[] args) {
        
        //Make a new User
        User kevin = new User();

    }
}

Now, when I run the program, it outputs:

You just made a user.

Pretty boring, but we'll expand upon it shortly.

Next, create a second User object in the main method, using whatever name you'd like as the identifier. Test it and see what happens.

Main.java
public class Main{
    public static void main(String[] args) {
        
        //Make a new User
        User kevin = new User();
        User sammy = new User();

    }
}
You just made a user.
You just made a user.

Still pretty boring. But now, there are two separate instances of the User class.

Let's see what else we can do with this class...


Instance Variables

An instance variable is a variable that belongs to each individual instance of an object. These are private variables that belong to the class, and should only be directly accessed or modified by the class to which they belong.

You may also hear instance variables referred to as instance fields, fields, or state variables.

Instance variables are not the same thing as local variables. Instance variables are specific to a certain class. Local variables are specific to an individual block of code and do not need to be constructed or assigned default values.

I stated earlier that I wanted to give the User class a few different properties - name, password, and email. These will be the instance variables for our User class.

Instance variables are declared inside the class but outside of any other methods. The convention is to place them at the start of the class, right after it begins before the first constructor. Note that, for now, we are only declaring these variables. That means we give them a name. We're not yet initializing or assigning a value to any of them.

User.java
public class User {
    
    //The instance vars
    private String name;
    private String pass;
    private String email;

    //The Constructor for a User
    public User(){
        System.out.println("You just made a user.");
    }

}

The proper place to initialize an instance variable is within the constructor. A default constructor is a constructor with no parameters that assign default values to objects for which no arguments were sent. Let's make the default constructor.

So, whenever a User is created, and we don't provide a name, password, and email to begin with, I'll just name them Johane Doe, make their password "password" and set their email to "noemail".

User.java
public class User {
    
    //The instance vars
    private String name;
    private String pass;
    private String email;

    //The Default Constructor for a User
    public User(){
        
        name = "Johane Doe";
        pass = "password";
        email = "noemail";

    }

}

Now we've assigned values to our users. They're the same values, and we're not doing anything with them, but they are there.

Parameters

Next, let's create a constructor that actually does something. I want to make it so I can assign the 3 values whenever a new User is created. To do this, I need to provide instructions in a constructor which takes parameters and uses them in our instance variables.

Keep the default constructor as is. We're going to create a second constructor below it. This constructor takes the parameters for name, password, and email.

public class User {
    
    //The instance vars
    private String name;
    private String pass;
    private String email;

    //The Default Constructor for a User
    public User(){
        
        name = "Johane Doe";
        pass = "password";
        email = "noemail";

    }

    public User(String theirName, String theirPass, String theirEmail){
        name = theirName;
        pass = theirPass;
        email = theirEmail;
    }

}

Now that we have two constructors, Java is intelligent enough to know which one to use based on the number of arguments we give it when we try to create the object. If we give it zero arguments, the default constructor is used. If we give it 3 arguments of type String, the second constructor is used. Having multiple constructors is known as overloading constructors. The second constructor may be referred to as an overloaded constructor.

You may create as many constructors as you need, provided the data types or number of fields in the arguments are different for each constructor. You cannot have two constructors that both take 3 string arguments, for example, since Java wouldn't know which one of the two constructors to use.

Main.java

public class Main{
    public static void main(String[] args) {
        
        //Make a new User
        User kevin = new User();
        User sammy = new User("Sammy","abc123","This email address is being protected from spambots. You need JavaScript enabled to view it.");

    }
}

Now I've left kevin using the default constructor. The sammy User has been assigned some basic information when initialized. If you run this, the program should execute fine, but of course we haven't done anything with this information yet.


Setters and Getters

In order to access and change the information saved in each instance of User, we need to create set methods and get methods (setters and getters) for them. Setters and getters are simple public methods which save or retrieve information from the private instance variables.

Let's start with a "getter" for the name value. Create a new public method in User.java called getName, which returns the name of the user as a String.

User.java
    //Get the user's name
    public String getName(){
        return name;
    }

Now, let's use it in the main method by printing out the name of each User.

Main.java

public class Main{
    public static void main(String[] args) {
        
        //Make a new User
        User kevin = new User();
        User sammy = new User("Sammy","abc123","This email address is being protected from spambots. You need JavaScript enabled to view it.");

        //Print their names
        System.out.println(kevin.getName());
        System.out.println(sammy.getName());

    }
}
Johane Doe
Sammy

Since kevin was created using the default constructor, the name retrieved for him was Johane Doe. Since we gave sammy an actual name, it printed the name Sammy.

Next, let's create a setter for the name. Create another method in the User class called "setName" which takes a String as an argument, and sets their name equal to whatever was provided.

I'd encourage you to try this yourself. Here's the method declaration to get started.

User.java
    //Set the user's name
    public void setName(String theirName){
        //????
    }

Note that the void keyword is used for methods which return nothing. They are void. We are setting a value without sending any information back to wherever it was called from.

Here's the solution:

    //Set the user's name
    public void setName(String theirName){
        name = theirName;
    }

Hopefully, that wasn't too complex! Of course, we must test it out. Return to the Main class and the main method. Rename your users and print out their new names.


public class Main{
    public static void main(String[] args) {
        
        //Make a new User
        User kevin = new User();
        User sammy = new User("Sammy","abc123","This email address is being protected from spambots. You need JavaScript enabled to view it.");

        //Print their names
        System.out.println(kevin.getName());
        System.out.println(sammy.getName());

        //Rename them
        System.out.println("I'm renaming them...");
        kevin.setName("Kevin");
        sammy.setName("Sammy Jameson Smirnoff");

        //Print them again
        System.out.println(kevin.getName());
        System.out.println(sammy.getName());

    }
}
Johane Doe
Sammy
I'm renaming them...
Kevin
Sammy Jameson Smirnoff

Hooray! It worked. I could change their names. Now you may wonder what the point of setters and getters is, so let me clarify.

Public Instance Variables

If all setters and getters do is set and retrieve the private instance variables, can't we just make the instance variables public so the accessing class can edit them directly?

The answer is yes. We can make the instance variables public, and this would enable us to edit the values associated with each user directly from the Main class without the use of special methods.

There are several reasons why you shouldn't do this, but let's just see what happens when we do. Change the scope from private to public for all instance variables in the User class and leave everything else the same.

User.java
    //The instance vars
    public String name;
    public String pass;
    public String email;

Now, go back to the main method and edit the values directly. You can do so by typing a period, followed by the variable after any object of type User.

Main.java
        //Rename them
        System.out.println("I'm renaming them...");
        kevin.name = "Kevin";
        sammy.name = "Sammy Samantha Samson";
        System.out.println(kevin.name);
        System.out.println(sammy.name);
Johane Doe
Sammy
I'm renaming them...
Kevin
Sammy Samantha Samson

As you can see, I could achieve a similar result without having to write a single setter or getter. You can also access .email and .pass this way. However, avoid doing this.

It is poor practice to make instance variables public. Other than for this example, your instance variables should always remain private. Here are some reasons:

  • While many set and get methods just set and return values, some do additional steps - such as formatting/sanitizing the data, converting between types, or performing other calculations.
  • You might need to check for errors or throw exceptions in these methods.
  • Not all data should be accessible or editable outside its class. You may want some fields to be read-only.
  • You may be using internal variables to perform operations within the class that have no bearing outside of it. Allowing access to this information externally only invites problems.
  • By leaving the variables private, you can change the inner-workings of the program down the road if needed, provided the methods still return the same type of information, without breaking other files that depend on it.

 

Revert your program back to using private instance variables now, if you have not done so already. And delete or change back the code which accessed variables directly in the main method.


Review Ex: printInfo()

Instructions: Create a new method in the User class which prints out the User's name and email using System.out.println. Name the method printInfo.

I will not give you a method declaration to start here. You need to write it yourself! Just remember...

  • Does the method need any arguments just to print out a value?
  • Is this a void method? Does it return anything?

Add this test code to your main method to see if it worked.


public class Main{
    public static void main(String[] args) {
        
        User kevin = new User("Kevin", "secret", "This email address is being protected from spambots. You need JavaScript enabled to view it.");
        kevin.printInfo();

        User sammy = new User("Sammy","passwd","This email address is being protected from spambots. You need JavaScript enabled to view it.");
        sammy.printInfo();

    }
}

It should print something like this when complete:

[User: Kevin - This email address is being protected from spambots. You need JavaScript enabled to view it.]
[User: Sammy - This email address is being protected from spambots. You need JavaScript enabled to view it.]

The "this" keyword

When you are working on a class, you may use the this keyword to refer to the instance of the object itself. The keyword is commonly used when constructing the object, and in any scenarios where you want to be sure you're accessing this instance's information or methods.

Take a look at the code from earlier, where we constructed a User with 3 parameters.

    //Create a user with basic info given
    public User(String theirName, String theirPass, String theirEmail){
        name = theirName;
        pass = theirPass;
        email = theirEmail;
    }

The reason I had to use "theirName" "theirPass" and "theirEmail" is because the variables given to me from the parameters are different than the instance variables. If I tried something like this...

    public User(String name, String pass, String email){
        name = name;
        pass = pass;
        email = email;
    }

The program would not work. I cannot assign a variable to itself. I don't know if the variables are referring to the instance variables, or the variables given to me by the arguments.

Let's rewrite the 3 parameter User constructor using the "this" keyword:

    //Create a user with basic info given
    public User(String name, String pass, String email){
        this.name = name;
        this.pass = pass;
        this.email = email;
    }

Now the above code will actually run properly.

Same Identifier - Different Variables (.this)

When you use the "this" keyword, Java knows you're referring to the instance variables defined at the top of the class. This allows us to use the same identifiers (name, email, pass), in the arguments for the constructor. Java can differentiate between this.name and name now.

You will commonly see constructors created this way using the "this" keyword. Some programmers use it everywhere they are referring to an instance variable, even if they don't always need it. This is to further differentiate instance variables from other variables and may make your code easier to understand.

Calling Methods

You may call any method at any time, private or public, provided it's being called from within the same class.

It is up to you if you want to make your custom methods public or private. If it's information or a method you need access to outside the class, you must make the method public. If you make it private, only the instance/class itself can use the method.

I created a new user called Bob and added the printInfo() method at the end of my constructor.

User.java
    //Create a user with basic info given
    public User(String name, String pass, String email){
        this.name = name;
        this.pass = pass;
        this.email = email;
        printInfo();
    }
Main.java
    public static void main(String[] args) {

        User kevin = new User("Kevin", "secret", "This email address is being protected from spambots. You need JavaScript enabled to view it.");
        User sammy = new User("Sammy", "passwd", "This email address is being protected from spambots. You need JavaScript enabled to view it.");
        User bob = new User("Bob", "passwd", "This email address is being protected from spambots. You need JavaScript enabled to view it.");

    }

Now, when I run the program, the constructor calls the printInfo method from the same class, and the info is printed.

[User: Kevin - This email address is being protected from spambots. You need JavaScript enabled to view it.]
[User: Sammy - This email address is being protected from spambots. You need JavaScript enabled to view it.]
[User: Bob - This email address is being protected from spambots. You need JavaScript enabled to view it.]

Since I'm accessing a method from an instance of itself, I can also use the "this" keyword here. The results would be exactly the same.

    //Create a user with basic info given
    public User(String name, String pass, String email){
        this.name = name;
        this.pass = pass;
        this.email = email;
        this.printInfo();
    }

Interacting With Same-Type Objects

It's possible, and fairly common, for objects of the same type to interact with each other.

sayHi(User otherPerson)

For this example, we will create a method in the User class which takes another User as its parameter.

All this method will do is print out the name of this user, and pretend they say hello to the name of the provided user from the parameter.

User.java
    public void sayHi(User otherPerson) {
        System.out.printf("%s : Hello, %s", this.name, otherPerson.getName());
        System.out.println();
    }
Main.java
        User kevin = new User("Kevin", "secret", "This email address is being protected from spambots. You need JavaScript enabled to view it.");
        User sammy = new User("Sammy", "passwd", "This email address is being protected from spambots. You need JavaScript enabled to view it.");
        User bob = new User("Bob", "passwd", "This email address is being protected from spambots. You need JavaScript enabled to view it.");

        bob.sayHi(kevin);
        kevin.sayHi(sammy);
        sammy.sayHi(bob);
Bob : Hello, Kevin
Kevin : Hello, Sammy
Sammy : Hello, Bob

Remember, this refers to this current instance - I can access the private instance variables for "this" User directly. The otherPerson, however, is coming from somewhere else. I cannot access their instance variables directly, and therefore must use the .getName() method if I want the other person's name.


Copy Constructor

Suppose you want to clone an object. With variables, we're able to simply set them equal to each other.

public class Main {
    public static void main(String[] args) {

        int x = 5;
        int y = 0;
        y = x;
        System.out.println("y now equals: " + y);

    }
}
y now equals: 5

Does this apply to objects as well? Examine this code, where I set userB equal to userA and print out the name.

public class Main {
    public static void main(String[] args) {

        // make users
        User userA = new User("Kevin", "pass", "email");
        User userB = new User("John", "pass", "email");
        // print B name
        System.out.println("User B's name is..." + userB.getName());
        // set equal
        userB = userA;
        // print B name
        System.out.println("User B's new name is..." + userB.getName());

    }
}
User B's name is...John
User B's new name is...Kevin

It looks like I've replaced userB's info with userA's info. However, this is not actually what happened.

Let's see what happens when I set userB equal to userA, then change userA's name.

        // make users
        User userA = new User("Kevin", "pass", "email");
        User userB = new User("John", "pass", "email");
        // print B name
        System.out.println("User B's name is..." + userB.getName());
        // set equal
        userB = userA;
        // print B name
        System.out.println("User B's new name is..." + userB.getName());
        // change A name
        userA.setName("Unknown Person");
        // print B name
        System.out.println("User B's new name is..." + userB.getName());
User B's name is...John
User B's new name is...Kevin
User B's new name is...Unknown Person

As you can see, I set userB equal to userA, then I changed userA's name, and the change was reflected in userB.

Why did this happen? Well, recall that the equal sign is the assignment operator. This means it behaves slightly differently than an equal sign in a math class. It assigns variable identifiers to a particular object.

In this case, I said userB is assigned to userA. Initially, userA was pointed at the Kevin User object. When I assigned userB to userA, I basically told the system userB is also userA. It did not copy the contents of userA's instance variables over to userB. It changed the object to which userB was referencing.

Therefore, when I changed userA towards the end, the change was also reflected in userB, since userB is now userA.

diagram showing steps with user a and user b

Equality With Primitives

Now consider this. What happens if I do the same thing I just did with userA and userB, but with ints instead of User objects?

        int x = 5; // set x to 5
        int y = 0; //y is 0
        y = x; //y is x, so y is 5?
        x = x + 1; //increase x by 1, x is 6. Is y 6 too?
        System.out.println(y);
5

I set x to 5, then y to 0. I set y = x, so y = 5. Then I add 1 to x. Does y remain 5, or does it become 6 (since y=x and now x=6)?

Unlike the User object example, which turned userB into userA, in this situation, the variable y remains the same, separate, and unaffected by the change in x even after we said x = y. This is the exact opposite behavior we just experienced with the User object. Why is this?

The answer has to do with primitive data types and how Java handles them.

When you create a variable referencing an int, it's pointing to a specific number somewhere in the computer's memory. That number, an int, is already there. You could think of this as a constant number line. If I set x = 5, the variable x is now pointing to a 5 somewhere in memory. It's not creating a new object, it's just pointing an identifier to a location which already exists.

Now I set y equal to 5. The variable identifier "y" is now pointing to that same location x is pointing at. They're both pointing at the "5" which was already there.

When I "change" the value of x by adding 1, the 5 doesn't actually turn into a 6 in the computer's memory. Our imaginary number line remains the same. Instead, the program calculates the answer and points the variable x to a different location in memory. Now x is pointing at 6, while y remains pointed at 5. Therefore, when I add 1 to the value of x, it doesn't affect the value of y, even though I said y = x.

So this behavior is more traditional. It's how you might think the equality operator works in a typical math class when dealing with primitive data types. This same logic applies to other data types, like doubles.

The difference between a primitive data type and an object bears repeating regarding how they handle things. When I changed userA's name in the previous example, the object itself changed, and since userA and userB pointed to this same object, userB appeared to change as well. When I change the value of an int, I don't actually change the information itself, but rather the number the variable is pointing at.

number line ints Illustration of ints pointing at different locations in RAM

Copy Constructor

A copy constructor allows us to take a same-type object and copy its contents over this object. This creates a true copy which we can manipulate independent of the original. In order to do this, we must have access to all the instance variables of the original object, so you must have getters for everything.

Continuing with the User example, make sure you have getters for all 3 variables. We already have getName, so we need to add getPass and getEmail.

User.java
    // Getters
    public String getName() {
        return this.name;
    }

    public String getPass() {
        return this.pass;
    }

    public String getEmail() {
        return this.email;
    }

Next, create a new constructor (in addition to the default and 3 argument constructor). This new constructor takes a User as the parameter. Then, it finds each piece of info from the User it got and sets the current info to equal that.

User.java
    // Copy Constructor
    public User(User u){
        this.name = u.getName();
        this.pass = u.getPass();
        this.email = u.getEmail();
    }

Now, let me return to the earlier example where I set userB equal to userA and printed the name. This time, I'm going to use the copy constructor to copy userA's info over to userB.

Main.java
        // make users
        User userA = new User("Kevin", "pass", "email");
        User userB = new User("John", "pass", "email");
        // print B name
        System.out.println("User B's name is..." + userB.getName());
        // copy
        userB = new User(userA);
        // print B name
        System.out.println("User B's new name is..." + userB.getName());
        // change A name
        userA.setName("Unknown Person");
        // print B name
        System.out.println("User B's new name is..." + userB.getName());
User B's name is...John
User B's new name is...Kevin
User B's new name is...Kevin

Since I used the copy constructor, this time when I copy userA over userB, I'm changing the actual values associated with userB rather than just the location it points to in memory. Now, userA and userB remain independent objects operating separately. When I make a change to userA after I copy it to userB, nothing happens to userB.

Note that "technically" this isn't updating the contents of userB. When I used the copy constructor, I created an entirely new User with the same attributes as userA. This new "copy" overwrote the old userB ("John"). So really there are 3 objects at play here - the original Kevin (who turns into unknown), the original John (who disappears), and the copy of Kevin replacing John.


What Happens to Unused Objects?

Just because I replace one object with another doesn't necessarily mean the original object is removed from the computer's memory immediately. It may linger indefinitely according to Java's automatic memory management system, often referred to as "garbage collection." If the JVM thinks it might need this piece of info shortly ahead, it might retain it. If it thinks it's trash and won't ever need it again, the memory used to hold the replaced or unused object is released.

Automatic memory management is a key feature of Java and other modern programming languages. Older, slightly lower-level languages like C and C++, require you to manage much of the memory yourself. This can cause unexpected errors and other problems (memory "leaks" and whole system crashes). Still, it's good to have a fundamental understanding of how memory management works.


The toString() Method

The toString() method is a special type of public method you may add to your objects in order to display them formatted as a String.

When added to a class, it will be called whenever the object is requested in the context of a String.

Consider this, I want to print out a User object to the screen.

        // make users
        User userA = new User("Kevin", "pass", "email");
        // print
        System.out.println("userA looks like this:");
        System.out.println(userA);

Here's the result:

userA looks like this:
User@4617c264

That's not particularly useful. All I can tell from that information is that this is a User object. I can't tell the name, email, or password from this information. The random string of characters following the the word "User" has to do with how Java manages memory. This is the object hashcode. It's an arbitrary value used by the JVM to link this identifier with a particular location in memory - but not the physical memory address itself. This information is effectively useless.

To provide more meaningful information, we can create a toString() method in the User class which returns a formatted String according to the instructions we provide.

First, let's just return a basic String to see what happens. Add this to your User class.

User.java
    public String toString(){
        return "This is a user.";
    }

Now, when I try to print userA, this is the result:

userA looks like this:
This is a user.

That's slightly more useful than "User@4617c264" but it's still not perfect.

So let's have it return a formatted String containing their name, password, and email.

    public String toString(){
        return "[User: " + this.name + "][Pass: " + this.pass + "][Email:" + this.email + "]";
    }

Now it will print out a nicer looking string when I try to print a user object.

Main.java
    public static void main(String[] args) {

        // make users
        User userA = new User("Kevin", "pass", "email");
        // print
        System.out.println("userA looks like this:");
        System.out.println(userA);
        
    }
userA looks like this:
[User: Kevin][Pass: pass][Email:email]

Finally, I'd like to show you how to return a formatted String, similar to how we use printf and format specifiers.

We can call String.format() to format a String with format specifiers (discussed back in chapter 3). In my opinion, this is cleaner looking code than concatenating the strings using + operators. Remember, to use formatted strings, first create the string, replacing each variable with a %s (string) %f (floats and doubles) or %d (ints). Then list each variable you're inserting as a separate argument in the same order they appeared in the first argument's string.

    public String toString(){
        return String.format("[User: %s][Pass: %s][Email: %s]", this.name, this.pass, this.email);
    }

Advantage of toString

You can achieve the same type of thing toString does in a number of ways. For example, earlier we made a .printInfo() method which achieved something similar. You could also make a .getString() method which returns a similar String.

The advantage of using the toString() method over other similar options is that it simplifies coding and it's an expected method to use. Rather than having to call .printInfo() each time I want to print a User's info to the screen, now I can just print the User object itself, and it's automatically turned into a String for me.

It's also especially useful for quick tests and demonstrations. If you're just testing some code out, you can quickly get a String representation of any object at any time, provided you made a toString method for each object. Even if these are objects the end user won't see, they may still contain information that's essential to you as a programmer.

I'd encourage you to get into the habit of creating a toString() method for each class you create.


Summary

Great job making it to the end of the first chapter on OOP! Let's recap some important terms.

  • Object Oriented Programming (OOP)
    • A design paradigm in which code is abstracted using virtual objects with unique properties and functions.
  • Instance
    • One individual "instance" of an object, created using a constructor.
  • Instance Variables
    • A variable belonging to a particular instance of an object.
  • Constructor
    • A type of method responsible for creating a new instance of an object.
    • May be "overloaded" - using multiple constructors with different parameters.
  • Scope
    • There area where a particular variable or method is accessible.
    • Public scope - accessible by any other class
    • Private scope - only accessible to the class it was created in
  • Setters and Getters
    • Methods responsible for modifying or retrieving instance variable information from classes beyond the current class.
    • Must be public.
  • this keyword
    • A keyword referencing "this" individual instance of an object.
    • Can be used to access the object itself, its instance variables, or its public and private methods.
  • Copy constructor
    • A special type of constructor which takes an object of the same type as its parameter and creates a new object with the same properties as the original.
  • toString Method
    • A method called automatically whenever an object is requested in string format.

Assignment: Make This Class

Directions: I have created a method which generates 4 balls using different constructors and prints their information out to the screen.

You need to create the Ball class from scratch. It must have the following:

  • Instance Variables
    • -String: name
    • -String: color
    • -double: radius
    • -double: weight
  • Methods
    • Default Constructor - use these props for default
      • name: "Generic Ball", color: "Black", radius: 1.0, weight: 0.5
  • Constructor
    • Create a constructor that takes all 4 values as parameters and assigns the instance vars to them
  • Copy Constructor
    • Create a constructor which takes another Ball as a parameter and makes a copy of all its instance variables
    • You'll want to make getters first!
  • Getters
    • Create getters for each instance variables
  • toString()
    • Create a toString method which returns a String representation of a ball in the following (or similar) format:
    • [Ball: NAME of color COLOR with radius RADIUS in and weight WEIGHT lbs.]

After you create your Ball class per the specifications above, you can test it by running this code:

public class Main {
    public static void main(String[] args) {

        // Create Test Balls

        // Should use default constructor
        Ball a = new Ball();

        // Should use full constructor
        Ball b = new Ball("Soccerball", "white", 4.3, 0.88);

        // Another complete constructor
        Ball c = new Ball("Baseball", "brown", 1.5, 0.3);

        // Make ball using copy of b
        Ball d = new Ball(b);

        // Print out results
        System.out.println("This should be a generic ball, black, rad 1 and weight 0.5");
        System.out.println(a);

        System.out.println("This should be a white soccerball with rad 4.3 weight 0.88");
        System.out.println(b);

        System.out.println("This should be a baseball brown with rad 1.5 weight 0.3");
        System.out.println(c);

        System.out.println("This should be a copy of the second ball");
        System.out.println(d);

        System.out.println("This should be the color of each ball in order:");
        System.out.printf("%s %s %s %s", a.getColor(), b.getColor(), c.getColor(), d.getColor());

    }
}

This is the expected output:

This should be a generic ball, black, rad 1 and weight 0.5
[Ball: Generic Ball of color Black with radius 1.00 in and weight 0.50 lbs.]
This should be a white soccerball with rad 4.3 weight 0.88
[Ball: Soccerball of color white with radius 4.30 in and weight 0.88 lbs.]
This should be a baseball brown with rad 1.5 weight 0.3
[Ball: Baseball of color brown with radius 1.50 in and weight 0.30 lbs.]
This should be a copy of the second ball
[Ball: Soccerball of color white with radius 4.30 in and weight 0.88 lbs.]
This should be the color of each ball in order:
Black white brown white
Write comments...
You are a guest ( Sign Up ? )
or post as a guest
Loading comment... The comment will be refreshed after 00:00.

Be the first to comment.

Main Menu
Kevin's Guides
Full size image will appear here.