Skip to content

Latest commit

 

History

History
212 lines (157 loc) · 10.7 KB

classes-and-objects.md

File metadata and controls

212 lines (157 loc) · 10.7 KB

Classes and Objects

🖥️ Slides

📖 Required Reading: Core Java for the Impatient Chapter 2: Object-Oriented Programming

🖥️ Lecture Videos

Classes are the primary programming construct for Java. A class contains fields and methods. Fields represent variables within the class and methods represent operations that the class performs. For example, if we had a class that represents a person, we might have a field called name that contains the person's name, and a method called sayName that outputs the name. An object is an instantiation, or instance, of a class that has been initialized to contain values. A class may also have a constructor that provides the default values for the fields when the class is instantiated into an object.

public class Person {
  // Field
  private String name;

  // Constructor
  public Person(String name) {
    this.name = name;
  }

  // Method
  public void sayName() {
    System.out.println(name);
  }
}

If you wanted to instantiate a Person object from the Person class, you use the new operator and pass the values you want to initialize the object with. The provided values then get passed to the class's constructor. Here is an example of a Java program that uses the Person class.

public class HelloPerson {
  public static void main(String[] args) {
    var person = new Person("James Gosling");
    person.sayName();
  }
}

This Keyword

When you use the new operator to instantiate an object it returns a pointer reference to the new object. You can then use that to call methods and access variables to the object.

var obj = new Object();
obj.toString();

That works great for code that uses your class, but sometimes you need a reference to the object within the class code. For example, consider the toString method. If you want to reference a class field as part of the output of the method then you can use the keyword this, as a substitute for the reference to the object.

public class ThisExample {
    String value = "example";

    public String toString() {
        return this.value;
    }
}

In order to keep you from having to use the this keyword every time you reference a class field, Java will infer this when you reference a class field and there is no other conflicting variable with the same name. That makes it so you only have to use this when there is a conflict. This commonly happens in a class constructor method as demonstrated above in the Person example.

Constructors

An object constructor receives parameters and executes the code necessary to completely construct an object. A constructor is specified with a method that has the same name as the class. You can have multiple, or overloaded, constructors by defining constructors that take different parameters. This is useful if you want the ability to construct an object with default values, or with explicit values.

A common pattern with constructors is to create what is called a copy constructor that takes an object of the class type, and deep copies all of the object's values to the new instance.

public class ConstructorExample {
    public String value;

    /** Default constructor */
    public ConstructorExample() {
        value = "default";
    }

    /** Overloaded explicit constructor */
    public ConstructorExample(String value) {
        this.value = value;
    }

    /** Copy constructor */
    public ConstructorExample(ConstructorExample copy) {
        this(copy.value);
    }

    public static void main(String[] args) {
        System.out.println(new ConstructorExample().value);
        System.out.println(new ConstructorExample("A").value);
        System.out.println(new ConstructorExample(new ConstructorExample("B")).value);
    }
}

If you don't provide a constructor then Java provides a default constructor that does nothing.

Getters and Setters

An important concept in object oriented programming is the idea of encapsulation. This basically means that you only expose information on a need to know basis. Encapsulation makes it easier to change unexposed code.

One common design pattern to enable encapsulation is the use of getters and setters methods to access fields instead of publicly exposing the field directly. These methods are often called accessor methods. A getter is simply a method that gets the value of the field, and a setter is a method that sets the value of a field. This makes it so you can hide how the field is implemented and protect the data integrity of your fields.

Here is a simple example of using accessor methods.

public class GetSetExample {
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // Keep fields private
    private String name;
}

Initially this may seem like overhead, but when your fields get more complex, or you decide to store your data in a different way, or even a different place, the value of accessor methods becomes clearer. Consider an example where the field type is an array, and you don't want to expose a mutable version of the array with the getter. Additionally, you want to check to make sure all the numbers in the array are less than 100 on the setter.

public class GetSetExample {
    public int[] getScores() {
        int[] copy = new int[scores.length];
        System.arraycopy(scores, 0, copy, 0, scores.length);
        return copy;
    }

    public void setScores(int[] scores) {
        for (var i = 0; i < scores.length; i++) {
            if (scores[i] > 100) {
                return;
            }
        }
        this.scores = scores;
    }

    private int[] scores = new int[10];
}

If you have accessor methods for your fields then you can make changes like this without having to worry about how the code is being used.

Enums

Enumerations allow you to create textual representations of labeled sets. You can define an enumeration in Java using the enum keyword.

public enum Peak {
    NEBO, PROVO, SANTAQUIN, TIMPANOGOS, CASCADE, SPANISH, LONE
}

Using enumerations help to make your code more readable, validate parameters, and restrict values to a closed set. Here is an example that uses the Peak enumeration to parse command line arguments.

public static void main(String[] args) {
    try {
        var e = Enum.valueOf(Peak.class, args[0].toUpperCase());

        if (e == Peak.LONE) {
            System.out.println("You chose Lone Peak");
        }
    } catch (IllegalArgumentException ex) {
        System.out.println("Unknown peak provided");
    }
}

Things to Understand

  • What object references are and why we need them
  • The differences between static methods and variables and instance methods and variables
  • How constructors work in Java
  • What constructor the compiler writes (and when it doesn't write one)
  • What code the compiler adds to some constructors and why
  • What the 'this' reference is used for
  • When the 'this' reference is required and when it is optional
  • What an enum is and how to implement one
  • The standard order of elements in a Java class

Videos (1:12:18)

Demonstration code

📁 Simple Class Example

📁 Constructor Example

📁 Enum Example

📁 Equality Example

📁 Override Example

📁 Static Example