Lecture from: 05.11.2024 | Video: Videos ETHZ

Visibility Modifiers (Access Modifiers)

Visibility modifiers control the accessibility of attributes and methods from other parts of the program.

ModifierAccessibility
publicAccessible from anywhere.
protectedAccessible within the same package and subclasses.
(no modifier - package-private)Accessible only within the same package.
privateAccessible only within the same class.

Example: Encapsulation with Rational

public class Rational {
    private int num;  // Numerator
    private int den;  // Denominator
 
    public Rational(int n, int d) {
        assert d != 0 : "Denominator cannot be zero"; // Assertion to enforce the invariant
        this.num = n;
        this.den = d;
    }
     // ... other methods like getters, setters, toString, etc. ...
}
 
 
Rational r = new Rational(1, 3);
r.den = 0; // Compile-time error: den is private

By making the attributes private, we prevent external code from directly modifying them, which helps ensure data integrity. Clients must use public methods (if provided) like setters to modify the state, allowing the Rational class to maintain its internal consistency (e.g., preventing a zero denominator).

Object Invariants

Object invariants are crucial for maintaining the integrity and consistency of objects. They are conditions that must be true about an object’s state throughout its lifetime (except perhaps momentarily during the execution of a method).

Definition

An object invariant is a logical assertion about the values of an object’s attributes that must always hold true. It’s a condition or set of conditions that define a valid state for an object.

Example: Rational Number Class

public class Rational {
    private int numerator;
    private int denominator;
 
    // Object invariant: denominator != 0
 
    public Rational(int n, int d) {
        assert d != 0 : "Denominator cannot be zero"; // Enforcing the invariant in the constructor
        // ...
    }
 
    // ... other methods that MUST preserve the invariant ...
}

In the Rational class, the denominator can never be zero. This is a fundamental requirement for a valid rational number.

Establishing and Maintaining Invariants

  • Constructors: Constructors are responsible for establishing the invariant. They ensure that newly created objects satisfy the invariant from the very beginning.
  • Methods: All methods (except possibly during intermediate steps within a method) must preserve the invariant. If a method modifies the object’s state, it must ensure that the invariant still holds true after the modification.

Benefits of Invariants

  • Data Integrity: Invariants help prevent invalid object states, making your code more robust.
  • Simplified Reasoning: Knowing that the invariant always holds true simplifies reasoning about the correctness of your code. You can rely on the invariant being true at any point outside of the internal workings of a method that modifies state.
  • Improved Maintainability: Invariants document the assumptions about an object’s state, making it easier to understand and modify the code later.

Documenting Invariants

Invariants should be clearly documented in the class definition, typically as comments. This documentation is essential for anyone working with the class. Including the invariants in the class definition itself makes them explicit and ensures they don’t get lost or forgotten.

Types of Invariants

  • Public Invariants: These invariants are part of the class’s public interface and should be known to clients of the class. They often relate to method preconditions or postconditions.
  • Private Invariants: These invariants are internal to the class and are not necessarily exposed to clients. They are primarily for maintaining internal data structure consistency.

Example: Public vs. Private Invariants

  • Public: In the Rational example, denominator != 0 is a public invariant because clients need to be aware of it.
  • Private: A binary search tree might have a private invariant that the left subtree contains only smaller values than the root, and the right subtree contains only larger values. This invariant is crucial for the tree’s internal structure but isn’t something clients directly interact with.

Static Methods and Attributes

Static methods and attributes belong to the class itself, not to individual objects (instances) of the class.

Static Methods (Class Methods)

Static methods are declared using the static keyword. They are called on the class itself, not on an object.

  • No this Reference: Static methods don’t have access to the this reference because they are not associated with a specific object instance. Therefore, they cannot directly access instance attributes.
  • Utility Functions: Often used for utility functions, helper methods, or factory methods that create objects.

Example:

public class MyMathLib {
    public static int gcd(int x, int y) {  // Static method (class method)
        return x == 0 ? y : gcd(y % x, x);
    }
 
    public static double sqrt(double x) {
      // ... implementation ...
    }
}
 
int d = MyMathLib.gcd(14, 21); // Call the static method on the class

Static Attributes (Class Attributes)

Static attributes are also declared using the static keyword. There is only one copy of a static attribute shared by all instances of the class.

  • Global Variables: Similar to global variables in other languages but scoped within the class.
  • Constants: Often used for constants (declared public static final).

Example:

public class MyMathLib {
    public static final double PI = 3.1415;  // Static constant
 
    // ... other methods ...
}
 
double circumference = 2 * MyMathLib.PI * radius; // Accessing a static attribute

Static Access Control

  • public static Attributes: Rarely used for mutable data. If a public static attribute is not final, any part of your program can modify it, leading to potential concurrency issues and making it difficult to track changes.

  • public static final Attributes (Constants): The most common use case for public static attributes. The final keyword makes the attribute a constant, meaning its value cannot be changed after initialization. This is typically how constants are declared in Java. Example: Math.PI.

  • private static Attributes: Useful for internal class-level data that needs to be shared among all instances of the class. Access to these attributes from outside the class should be controlled via static getter and setter methods (if modification is needed). This pattern allows you to enforce invariants or perform additional logic when accessing or modifying the static attribute.

Example: Unique IDs with private static Counter:

This example demonstrates how to use a private static counter to assign unique IDs to each object created.

public class Person {
    private static int nextId = 1; // Private static counter
    private int id;                // Instance attribute to store the ID
    // ... other attributes ...
 
    public Person(/* ... constructor parameters ... */) {
        this.id = nextId;  //Assign next available ID and increment
        nextId++;         
        // ... other constructor code ...
    }
 
    public int getId() {
        return id;
    }
    // ... other methods ...
}

final Keyword

The final keyword in Java indicates that a variable or attribute cannot be reassigned after its initial value has been set. This keyword can be applied to variables, attributes, methods, and classes, each with its own specific meaning:

  • final Variables: A final local variable or a final parameter in a method cannot be changed once it’s assigned a value.

  • final Attributes: A final attribute of a class must be initialized either at the point of declaration or in the constructor. It cannot be modified afterward. If the attribute is a reference type, this means that you can’t change what object it refers to after initialization. However, you could still modify the attributes of the object it points to if those attributes are not themselves final.

  • final Methods: A final method cannot be overridden in a subclass. This is useful to prevent subclasses from changing the behavior of critical methods.

  • final Classes: A final class cannot be extended (subclassed). This prevents inheritance and ensures that the functionality of the class remains fixed.

Typical Use Cases for Static Members

  • Utility Classes: Classes designed to hold static methods and constants. Often used to group related utility functions. Common practice is to make the constructor private to prevent instantiation of such classes. Examples: Math, Arrays, Collections.

  • Factory Methods: Static methods that create and return new instances of a class. This can be useful to encapsulate object creation logic or enforce specific creation patterns.

  • Shared Resources: Static attributes can be used to manage shared resources or state among all objects of a class (like a unique ID counter or a connection pool).

  • Combined Usage: Many classes combine both instance and static members. A class might have instance methods that operate on individual objects’ state and static methods that provide utility functions related to the class but not specific objects.

Continue here: 14 Enums, Code Style, Conventions, Refactoring, Linked Lists