SE 109 - Lecture 5: Class Definition, Access Control, Modifiers

Outline of Lecture 5

Class Definition / Declaration

A class definition serves as a blueprint for creating objects. It consists of:

Methods and fields can technically be arranged in any order within the class body, but common practice often lists methods before fields or groups related members together.

(Based on Slides 3, 5)

Syntax

Access-modifier class ClassName /* Often same as filename if public */ {
    // Field (Attribute) declarations
    dataType fieldName1;
    dataType fieldName2;
    // ...

    // Constructor declaration(s)
    ClassName(parameters) {
        // Initialization code
    }

    // Method declarations
    returnType methodName1(parameters) {
        // Method body
    }
    // ...
}

The order of attributes, constructors, and methods can vary, but the structure remains consistent. (Slide 4)

Example Class Definition

// Example based on Slide 6
class First {
    // --- Fields (Instance Variables) ---
    int x, y;     // Properties representing integer coordinates or values
    String ch;    // Property representing a String

    // --- Constructor ---
    // (Implicit default constructor is used if none is defined,
    // or we can define one explicitly like this to set initial values)
    public First() {
        x = 2;
        y = 3;
        ch = " "; // Initialize String to a space
    }

    // --- Methods ---
    // Mutator (Setter) method to change x and y
    void setXY(int a, int b) {
        x = a;
        y = b;
    }

    // Accessor (Getter) method to retrieve the value of x
    int getX() {
        return x;
    }

    // Accessor (Getter) method to retrieve the value of y
    int getY() {
        return y;
    }
}

Creating and Using Objects

(Based on Slides 7, 12)

Example: Multiple Objects (Slide 8)

public class Main {
    int x = 5; // Instance variable

    public static void main(String[] args) {
        Main myObj1 = new Main(); // Create Object 1
        Main myObj2 = new Main(); // Create Object 2

        // Modify the instance variable 'x' for myObj2
        myObj2.x = 25;

        // Print the value of 'x' for each object
        System.out.println(myObj1.x); // Access x of myObj1
        System.out.println(myObj2.x); // Access x of myObj2
    }
}

Output:

5
25

Shows that changing `myObj2.x` did not affect `myObj1.x`.

Example: Class with Multiple Attributes (Slide 9)

public class Main {
    // Instance variables
    String fname = "John";
    String lname = "Doe"; // Corrected variable name from 'Iname' to 'lname'
    int age = 24;

    public static void main(String[] args) {
        Main myObj = new Main(); // Create an object

        // Access the object's attributes
        System.out.println("Name: " + myObj.fname + " " + myObj.lname);
        System.out.println("Age: " + myObj.age);
    }
}

Output:

Name: John Doe
Age: 24

Using Multiple Classes / Files

It's common practice to define different classes in separate .java files for better organization. Typically:

(Based on Slides 10, 11)

Example: Two Files (Slide 11)

File 1: `FirstFile.java`

public class FirstFile {
    int x = 5; // Instance variable (default access)
}

File 2: `SecondFile.java` (Must be in the same directory/package)

class SecondFile { // Not public, so filename doesn't *have* to be SecondFile.java, but good practice
    public static void main(String[] args) {
        // Create an object of the FirstFile class
        FirstFile myObj = new FirstFile();

        // Access the 'x' variable of the myObj instance
        System.out.println(myObj.x); // Access is allowed because they are in the same package (default access)
    }
}

Compilation and Execution:

javac FirstFile.java SecondFile.java
java SecondFile

Output:

5

You must compile `FirstFile.java` first (or simultaneously) because `SecondFile.java` depends on the `FirstFile` class definition.

Class Methods

Methods define the behavior or actions associated with a class.

To call an instance method from a static context (like `main`), you first need to create an object of the class.

(Based on Slides 12, 13, 29)

Example: Calling Instance vs. Static Methods (Slide 13, 29)

public class MethodDemo {

    // Instance method (needs an object to be called)
    public void myInstanceMethod() {
        System.out.println("Instance method called.");
    }

    // Static method (can be called using the class name)
    static void myStaticMethod() {
        System.out.println("Static method called.");
    }

    public static void main(String[] args) {
        // Calling the static method using the class name
        MethodDemo.myStaticMethod();
        // Or just myStaticMethod(); if called from within the same class

        // Cannot call instance method directly from static context:
        // myInstanceMethod(); // This would cause a compilation error

        // Must create an object first to call instance methods
        MethodDemo myObj = new MethodDemo();
        myObj.myInstanceMethod(); // Correct way to call instance method
    }
}

Output:

Static method called.
Instance method called.

Scope

Class Scope: Fields (instance variables and static variables) declared directly within the class body (outside any method) have class scope. This means they are generally accessible from anywhere *within* that class (e.g., from any instance method or static method of that class, respecting static/instance rules).

However, good practice (Encapsulation) often dictates using private access for fields and manipulating them only through methods (like accessors and mutators).

(Based on Slide 14)

Accessors (Getters) and Mutators (Setters)

These are common types of methods used to control access to an object's (usually private) fields, supporting the principle of Encapsulation.

(Based on Slides 15-20)

General Form of Getters/Setters (Slide 20)

public class MyData {
    private type field; // Private instance variable

    // Public Getter for 'field'
    public type getField() {
        return field;
    }

    // Public Setter for 'field'
    public void setField(type f) {
        // Optional: Add validation logic here
        field = f;
    }

    // Constructor, other methods...
}

Access Modifiers

Access modifiers control the visibility (accessibility) of classes, fields, methods, and constructors.

(Based on Slides 21, 22, 24, 25)

For Classes

For Members (Attributes, Methods, Constructors)

Access Modifier Summary Table

Modifier Same Class Same Package Subclass (Different Pkg) Different Package (Non-Subclass)
public
protected
(default)
private

This table summarizes where a member declared with a given modifier can be accessed from. (Based on Slide 25 structure).

Example: Public Members (Slide 23)

File 1: `Main.java`

// Assume this is in package 'com.example.data'
package com.example.data;

public class Main {
    public String fname = "Mohamed";
    public String lname = "Ali"; // Corrected name
    public String email = "MA@hotmail.com";
    public int age = 24;
}

File 2: `Second.java` (Can be in a different package)

// Assume this is in package 'com.example.app'
package com.example.app;

// Import the Main class from the other package
import com.example.data.Main;

public class Second {
    public static void main(String[] args) {
        Main myObj = new Main(); // Create object of Main

        // Access public members from a different package
        System.out.println("Name: " + myObj.fname + " " + myObj.lname);
        System.out.println("Email: " + myObj.email);
        System.out.println("Age: " + myObj.age);
    }
}

Output:

Name: Mohamed Ali
Email: MA@hotmail.com
Age: 24

Example: Private Members & Need for Getters (Slides 34, 35)

File: `Account.java`

public class Account {
    private int balance = 1000; // Private instance variable

    // Public getter method for balance
    public int getBalance() {
        return balance;
    }

    // Maybe add a deposit method (setter concept)
    public void deposit(int amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
}

File: `BankApp.java`

public class BankApp {
    public static void main(String[] args) {
        Account myAccount = new Account();

        // Cannot access private member directly:
        // System.out.println(myAccount.balance); // ERROR: balance has private access in Account

        // Must use the public getter method:
        System.out.println("Current balance: " + myAccount.getBalance()); // Works! Output: 1000

        myAccount.deposit(500);
        System.out.println("New balance: " + myAccount.getBalance()); // Output: 1500
    }
}

This illustrates why `private` is used (hides internal data) and how getters provide controlled access.

Non-Access Modifiers

These modifiers provide additional information about the characteristics of classes, methods, and variables, affecting their behavior beyond just visibility.

(Based on Slides 26, 27, 30, 31)

For Classes

For Members (Attributes & Methods)

Note: abstract cannot be used with final (an abstract class must be subclassed, a final class cannot be; an abstract method must be overridden, a final method cannot be). abstract cannot be used with private (an abstract method needs to be overridden by a subclass, which wouldn't be possible if it were private).

Access vs. Non-Access Modifiers

Examples of Non-Access Modifiers

Example: `final` variable (Slide 28)

public class Constants {
    final int MAX_USERS = 100; // A final instance variable (constant for this object)
    final double PI = 3.14159;  // Another final instance variable

    static final String DEFAULT_GREETING = "Hello"; // A static final variable (class constant)

    public static void main(String[] args) {
        Constants c = new Constants();
        // c.MAX_USERS = 200; // ERROR: cannot assign a value to final variable MAX_USERS
        // c.PI = 3.0;       // ERROR: cannot assign a value to final variable PI

        System.out.println("Max Users: " + c.MAX_USERS); // Output: 100
        System.out.println("Default Greeting: " + Constants.DEFAULT_GREETING); // Output: Hello
        // Constants.DEFAULT_GREETING = "Hi"; // ERROR: cannot assign a value to final variable
    }
}

Example: `static` members (Slides 29, 36-39)

File: `Counter.java`

public class Counter {
    private static int instanceCount = 0; // Static variable: shared by all objects
    private int id; // Instance variable: unique to each object

    public Counter() {
        instanceCount++; // Increment shared counter whenever a new object is created
        this.id = instanceCount; // Assign unique ID based on the count
    }

    // Static method to get the shared count
    public static int getInstanceCount() {
        // Cannot access 'this.id' here because it's an instance variable
        return instanceCount;
    }

    // Instance method to get the object's unique ID
    public int getId() {
        return this.id;
    }
}

File: `TestCounters.java`

public class TestCounters {
    public static void main(String[] args) {
        System.out.println("Initial count: " + Counter.getInstanceCount()); // Access static method via class name

        Counter c1 = new Counter();
        System.out.println("Count after c1: " + Counter.getInstanceCount()); // Output: 1
        System.out.println("c1 ID: " + c1.getId()); // Output: 1

        Counter c2 = new Counter();
        System.out.println("Count after c2: " + Counter.getInstanceCount()); // Output: 2
        System.out.println("c2 ID: " + c2.getId()); // Output: 2

        // Accessing static member via object reference works but is discouraged:
        // System.out.println(c1.getInstanceCount()); // Still outputs 2

        // Example of accessing static variable directly (if it were public)
        // Assume 'public static int a = 12;' in class StaticVariables (Slide 37)
        // System.out.println(StaticVariables.a); // Output: 12

        // Example of accessing private static variable via static getter (Slide 39)
        // Assume 'private static int a = 12;' and 'public static int getA()' in StaticVariables
        // System.out.println(StaticVariables.getA()); // Output: 12
    }
}

Output:

Initial count: 0
Count after c1: 1
c1 ID: 1
Count after c2: 2
c2 ID: 2

Example: `abstract` class and method (Slides 40, 41)

File 1: `Animal.java`

// Abstract class - cannot be instantiated directly
abstract class Animal {
    // Instance variable
    public String name;

    // Abstract method - no implementation, must be overridden by non-abstract subclasses
    public abstract void makeSound();

    // Concrete method (regular method with implementation)
    public void sleep() {
        System.out.println("Zzz...");
    }
}

File 2: `Dog.java` (Subclass)

// Concrete subclass extending the abstract class
class Dog extends Animal {
    // Provides implementation for the abstract method
    @Override
    public void makeSound() {
        System.out.println("Woof Woof");
    }
}

File 3: `TestAbstract.java`

public class TestAbstract {
    public static void main(String[] args) {
        // Cannot create an object of the abstract class Animal:
        // Animal myAnimal = new Animal(); // ERROR: Animal is abstract; cannot be instantiated

        // Create an object of the concrete subclass Dog
        Dog myDog = new Dog();
        myDog.name = "Buddy";

        myDog.makeSound(); // Calls the implemented method in Dog
        myDog.sleep();     // Calls the concrete method inherited from Animal
    }
}

Output:

Woof Woof
Zzz...