FosseryWeb Min

Java Object-Oriented Programming (OOP)

Class

Define a class

public class Person {
  // VARIABLES
  // instance variables (aka. attributes)
  protected String name;
  protected int age;

  // class variables
  public static String breed = "human";

  // CONSTRUCTOR(S)
  public Person() {
    name = "John Doe";
    age = 18;
  }

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

  // METHODS
  // getters and setters
  public String getName() {
    return name;
  }

  public void setName(String newName) {
    name = newName;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int newAge) {
    age = newAge;
  }

  // custom methods
  public boolean isAdult() {
    return age >= 18;
  }

  public void say(String whatToTell) {
    System.out.println(whatToTell);
  }

  // static methods
  public static boolean isMammal() {
    return true;
  }

  // overriding methods of superclass (Object class in this case, because no superclass is specified)
  @Override
  public String toString() {
    return this.getName() + ", " + this.getAge() + " years old";
  }
}

(The order for defining the elements of a class can vary based on personal or project preferences.)

instance variables (attributes): store different values of a specific type per instance (object)

class variables: store a value directly in the class instead of an instance (object)

constructor(s): special method(s) with no specified return type, automatically run each time a new instance (object) is created, usually used to assign attribute values

a default empty constructor is automatically created at compile time if none is defined in the code

this keyword: refers to current instance (object), often used to differentiate attribute names from parameter names

getters and setters: read and write (get and set) values of attributes

static methods: can be called on the class itself instead of an individual instance (object), can't access attributes (instance variables)

Use a class

Instantiate a class (create a new object from it) - constructor with the appropriate number of parameters is called:

Person johnDoe = new Person();
Person rms = new Person("Richard Matthew Stallman", 72);

Use instance variables (attributes) and methods:

String rmsAge = rms.age; // 72
String rmsAge2 = getAge(); // 72

Use class variables and static methods - without instantiation, using the name of class:

String breedOfPerson = Person.breed;
boolean isPersonMammal = Person.isMammal();

Define a subclass

public class AmericanGuy extends Person {
  // VARIABLES/ATTRIBUTES
  private String homeState;

  // CONSTRUCTORS
  public AmericanGuy() {
    super();
    homeState = "Alabama";
  }

  public AmericanGuy(String name, int age) {
    super(name, age);
    homeState = "Alabama";
  }

  public AmericanGuy(String name, int age, String homeState) {
    super(name, age);
    this.homeState = homeState;
  }

  // METHODS
  // getters and setters
  public String getHomeState() {
    return homeState;
  }

  public void setHomeState(String newHomeState) {
    homeState = newHomeState;
  }

  // custom methods
  public boolean isAdultInEurope() {
    return super.isAdult();
  }

  // override methods of superclasses (Person and Object)
  @Override
  public boolean isAdult() {
    return age >= 22; // age attribute inherited from Person is used
  }

  @Override
  public String toString() {
    return this.getName() + ", " + this.getAge() + " years old, living in " + this.getHomeState(); // getName() and getAge() methods inherited from Person are used
  }
}

AmericanGuy inherits all attributes and methods of Person.

super() method: calls the constructor of the closest superclass (Person in this case) with the appropriate number of parameters; needs to be called at the start of the subclass constructor's body, before all other statements

super keyword: refers to the closest superclass, can be used to call its methods, usually used when the "original" variant of an overridden method needs to be called

Important! A subclass can only inherit from (extend) one superclass. Multiple inheritance isn't supported in Java!

Access modifiers

public: element is accessible anywhere inside the program

protected: element is accessible only for the class containing it and its subclasses, other classes in the package; can't be applied on classes

no modifier ("package-private"): element is accessible only within the class containing it, and other classes in the package

private: element is accessible only within the class containing it; can't be applied on classes, not recommended to use on constructors

Pro tip: the best practice is to use protected access modifier on attributes which need to be inherited by subclasses; private on attributes which don't need to be inherited by any subclass, and methods which only need to be accessed by other methods of the class; public on methods which do need to be accessed from outside the class, and on all constructors (see encapsulation)

Built-in methods of Object class

clone()

Create a copy of the object (shallow or deep copy), class must implement Cloneable interface for its instances to become cloneable.

Note that the clone() method of Object has protected access modifier, but the overridden method can have different access modifier.

Shallow copy (attributes of the object containing references to other objects are copied by reference, if those objects are changed, it also affects the clones of the object):

public class Project implements Cloneable {
  private Leader leader;

  public Project(Leader leader) {
    this.leader = leader;
  }

  @Override
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

public class Leader {
  private String name;
  private int age;

  public Leader(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

Deep copy (other objects referred to by the attributes of the object are also cloned, changes to a referred object don't affect clones):

public class Project implements Cloneable {
  private Leader leader;

  public Project(Leader leader) {
    this.leader = leader;
  }

  public Leader getLeader() {
    return leader;
  }

  public void setLeader(Leader newLeader) {
    leader = newLeader;
  }

  @Override
  public Object clone() throws CloneNotSupportedException {
    Project projectClone = (Project) super.clone();
    projectClone.setLeader(new Leader(getLeader().getName(), getLeader().getAge()));
    return projectClone;
  }
}

public class Leader {
  private String name;
  private int age;

  public Leader(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }
}

equals(Object obj)

Determine whether the object is equal to the object passed as argument (casting of the passed object is necessary!).

@Override
public boolean equals(Object obj) {
  return this.getName().equals(((Leader) obj).getName()) && this.getAge() == ((Leader) obj).getAge();
}

getClass()

Return the name of the class.

@Override
public String getClass() {
  return "Leader";
}

hashCode()

Generate a (mostly) unique numeric code for the object.

@Override
public int hashCode() {
  return name.hashCode() * age;
}
@Override
public int hashCode() {
  int hash = 7;
  hash = 31 * hash + (name == null ? 0 : name.hashCode());
  hash = 31 * hash + age;
  return hash;
}

General tips for generating hash codes:

toString()

Define a String representation for the object, implicitly called at runtime when a non-String object is passed to a method where a String argument is expected (e.g. System.out.println(String str)).

@Override
public String toString() {
  return this.getName() + ", " + this.getAge() + " years old";
}

General concepts of OOP

Inheritance: element is accessible anywhere inside the program

In Java, a subclass can only have one superclass, but an interface can implement multiple interfaces

Encapsulation: data stored in the attributes of a class shouldn't be direcly accessible from outside the class (all attributes should have private or protected access modifier), only via getter and setter methods

Method implementations of individual classes can be hidden using interfaces

Hint: In the example codes of this cheatsheet, the concept of encapsulation is applied (almost) everywhere.

Polymorphism: when a superclass type object is expected, a subclass type object can also be used, this way subclass-specific implementations of methods can also be accessed

Person person = new AmericanGuy("Lil Timmy", 20);
System.out.println(person.isAdult()); // false

(This example is based on the code example given in the first (Class) section of this cheatsheet)

Here, AmericanGuy is a subclass of Person, the original implementation of isAdult() in Person would return the result of age >= 18 which would be true in this case, but AmericanGuy has its own implementation of isAdult(), overriding the isAdult() of Person, it returns the result of age >= 22, which is false in this case. Poor Timmy can't drink beer :(

Abstract class

public abstract class TechGuy {
  protected String os;
  public TechGuy(String os) {
    this.os = os;
  }

  // abstract method
  public abstract void changeOS(String newOS);

  // normal method
  public void learnProgramming() {
    System.out.println("Writing a Hello World program...");
  }
}

Cannot be instantiated (TechGuy tehcGuy = new TechGuy() would throw error!)

Abstract methods must be implemented in subclasses which aren't abstract (parameter types must be the same, but parameter names can differ)!

public class LinuxUser extends TechGuy {
  public LinuxUser(String distro) {
    super(distro);
  }

  @Override
  public void changeOS(String newDistro) {
    os = newDistro;
    System.out.println("Distrohopped into " + newDistro);
  }
}

Interface

Can't be instantiated (similar to abstract class), can't have constructor

public interface Animal {
  boolean canTalk = false;

  void eat();
  void makeNoise();
}

Its attributes are public, static and final by default (they must have a value provided within the interface definition)

Its methods are abstract and public by default

It can extend (inherit from) other interfaces, similar to how a subclass extends a superclass

Non-abstract classes implementing an interface must implement all its methods, similar to abstract classes! Attributes can be overridden, but it isn't necessary

public class Parrot implements Animal {
  private boolean canTalk = true;

  public void eat() {
    System.out.println("Parrot ate some seeds.");
  }

  public void makeNoise() {
    System.out.println("Parrot said the f word it heard from its owner.");
  }
}

A class can implement multiple interfaces, thus interfaces can be used instead of multiple superclasses, when multiple inheritance would be beneficial. (e.g. public class Dog implements Animal, FamilyMember { })

Enum

public enum DayOfWeek {
  // CONSTANTS (which can optionally override methods of the enum itself)
  MONDAY,
  TUESDAY,
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY() {
    @Override
    public boolean isWeekend() {
      return true;
    }
  },
  SUNDAY() {
    @Override
    public boolean isWeekend() {
      return true;
    }
  };

  // ATTRIBUTES (optional)

  // CONSTRUCTOR (optional)

  // METHODS (optional)

  public boolean isWeekend() {
    return false;
  }
}

public class Day {
  private DayOfWeek dayOfWeek;

  public Day(DayOfWeek dayOfWeek) {
    this.dayOfWeek = dayOfWeek;
  }

  public DayOfWeek getDayOfWeek() {
    return dayOfWeek;
  }
}

dayOfWeek parameter of Day constructor (and also the attribute of Day with the same name) can't receive value other than the constants defined in the DayOfWeek enum (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY).

Enum definition can also be included inside the class definition.

Use enum in switch statement

public String waitingForLove() {
  return switch(dayOfWeek) {
    case MONDAY -> "Left me broken";
    case TUESDAY -> "I was through with hopin'";
    case WEDNESDAY -> "My empty arms were open";
    case THURSDAY -> "Waiting for love, waiting for love";
    case FRIDAY -> "Thank the stars";
    case SATURDAY -> "I'm burning like a fire gone wild";
    case SUNDAY -> "Guess I won't be coming to church";
  };
}

Iterate through the enum

Using traditional for loop:

public void printAllDays() {
  DayOfWeek[] daysOfWeek = DayOfWeek.values();
  for (int i = 0; i < daysOfWeek.length; i++) {
    System.out.println(daysOfWeek[i]);
  }
}

Using traditional for loop:

public void printAllDays() {
  for (DayOfWeek day : DayOfWeek.values()) {
    System.out.println(day);
  }
}

Record (available in Java 16 and higher)

A special class which generates accessors, constructor, equals, hashCode, and toString methods automatically

Public and final by default

All attributes are private and final

e.g.:

record Book(String author, String title) { }

is equivalent to:

public final class Book {
  private final String author;
  private final String title;

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

  // ACCESSORS

  String author() {
    return this.author;
  }

  String title() {
    return this.title;
  }

  // OVERRIDING METHODS OF OBJECT CLASS

  // Two objects are equal if all their attribute values are equal
  public boolean equals(Object obj) { ... }
  public int hashCode() { ... }

  public String toString() {
    return "Book[author=" + author() + ", title=" + title() + "]";
  }
}

Adding extra operations to constructor:

record Book(String author, String title) {
  public Book {
    if (author.length() < 2 || title.length() < 2) {
      throw new IllegalArgumentException("Author name and title must be at least 2 characters long!");
    }
  }
}

is equivalent to:

record Book(String author, String title) {
  public Book(String author, String title) {
    if (author.length() < 2 || title.length() < 2) {
      throw new IllegalArgumentException("Author name and title must be at least 2 characters long!");
    }
    this.author = author;
    this.title = title;
  }
}

Accessors can be explicitly provided in the code

Class variables (static attributes) and custom methods can be added in the traditional way (inside curly braces { })

Note that only static attributes can be added inside curly braces { }, all non-static attributes go into parentheses ( )!

Can implement interfaces, but can't inherit from a superclass

Sealed class/interface (available in Java 17 and higher)

a class which can only be a superclass of specific subclasses, or an interface which can only be implemented by specific classes or records (as mentioned above, records can't extend classes)

public sealed class Elephant permits AfricanElephant, IndianElephant { ... }

Subclass of sealed class, or class/record implementing sealed interface must be accessible from the sealed class/interface; must have final, sealed or non-sealed modifier.

final: can't be extended

sealed: can only be extended by the specified classes

non-sealed: can be extended by any class, its subclasses don't need to have any of these three modifiers