Index

Encapsulation and Access Control

Java for Beginners

6.1 Getters and Setters

In Java, encapsulation means keeping the internal details of a class hidden and exposing only what is necessary through well-defined interfaces. One common way to enforce encapsulation is by making fields (variables) private and providing public getter and setter methods to access and modify these fields. This approach ensures controlled access to the data and improves code maintainability.

What Are Getters and Setters?

Together, getters and setters provide controlled access to fields without exposing them directly.

Why Use Getters and Setters?

Naming Conventions

Getters and setters follow a standard naming pattern:

If the field is a boolean, the getter can also be named isFieldName().

Example:

private int age;

public int getAge() { return age; }
public void setAge(int age) { this.age = age; }

Example Class Using Getters and Setters

Here’s a simple class Person with private fields and public getters and setters:

class Person {
    private String name;
    private int age;

    // Getter for name
    public String getName() {
        return name;
    }

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

    // Getter for age
    public int getAge() {
        return age;
    }

    // Setter for age with validation
    public void setAge(int age) {
        if (age >= 0) {
            this.age = age;
        } else {
            System.out.println("Age cannot be negative.");
        }
    }
}
 
// Using Getters and Setters in `main`
 
public class Main {
    public static void main(String[] args) {
        Person person = new Person();

        // Set values using setters
        person.setName("Alice");
        person.setAge(30);

        // Try setting an invalid age
        person.setAge(-5);  // Prints: Age cannot be negative.

        // Get values using getters
        System.out.println("Name: " + person.getName());  // Name: Alice
        System.out.println("Age: " + person.getAge());    // Age: 30
    }
}

Auto-Generating Getters and Setters

Most modern IDEs like Eclipse, IntelliJ IDEA, and NetBeans can auto-generate getters and setters for you. This saves time and ensures consistent naming and formatting.

For example, in IntelliJ IDEA, you can:

Summary

Try creating your own classes with private fields and use getters and setters to interact with them. This foundational skill will improve your ability to write clean, secure, and maintainable Java code.

Index

6.2 Access Modifiers: public, private, protected

Java uses access modifiers to control the visibility of classes, fields, methods, and constructors. These modifiers help you enforce encapsulation and protect data by restricting where different parts of your program can access certain members.

The Four Access Modifiers

Java has four access levels:

Modifier Visible in Same Class Same Package Subclass (any package) Other Packages (non-subclass)
public Yes Yes Yes Yes
protected Yes Yes Yes No
default (no modifier) Yes Yes No No
private Yes No No No

public

Example:

public class BankAccount {
    public String accountNumber;
}

private

Example:

public class BankAccount {
    private double balance;
}

protected

Example:

protected double interestRate;

Default (Package-Private)

Example:

int transactionCount;  // accessible only in the package

Practical Example: BankAccount Class

package banking;

public class BankAccount {
    private String accountNumber;    // Hidden from all except BankAccount
    private double balance;          // Private balance
    
    protected double interestRate;   // Visible to subclasses & same package
    
    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
        this.interestRate = 0.01;   // 1% interest rate
    }
    
    // Public method to access balance
    public double getBalance() {
        return balance;
    }
    
    // Public method to deposit money
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
    
    // Private method (used internally)
    private void applyInterest() {
        balance += balance * interestRate;
    }
}

Accessing Members from Different Contexts

Assuming the above BankAccount class is in package banking:

From the Same Class

Inside BankAccount, all members (private, protected, public) are accessible.

From Another Class in the Same Package

package banking;

public class BankManager {
    public void checkAccount() {
        BankAccount acc = new BankAccount("1234", 1000);
        
        // acc.accountNumber -> Not accessible (private)
        // acc.balance -> Not accessible (private)
        System.out.println(acc.getBalance());    // Accessible (public)
        System.out.println(acc.interestRate);    // Accessible (protected + same package)
    }
}

From a Subclass in a Different Package

package vipbanking;

import banking.BankAccount;

public class VIPAccount extends BankAccount {

    public VIPAccount(String accNum, double balance) {
        super(accNum, balance);
        System.out.println(interestRate);  // Accessible (protected)
    }
    
    public void showAccountNumber() {
        // System.out.println(accountNumber); // Error: private field not accessible
    }
}

From an Unrelated Class in a Different Package

package publicapp;

import banking.BankAccount;

public class App {
    public static void main(String[] args) {
        BankAccount acc = new BankAccount("9999", 5000);
        
        // acc.balance -> Not accessible (private)
        // acc.interestRate -> Not accessible (protected + different package + not subclass)
        System.out.println(acc.getBalance());  // Accessible (public)
    }
}

When to Use Each Modifier?

Modifier Use Case
private Protect sensitive data, internal logic
default Share among related classes in same package
protected Allow subclass access, hide from unrelated
public Expose API, methods intended for all clients

Summary

Try modifying the BankAccount class or create your own class using different access modifiers to observe their effects in various packages and subclasses!

Index

6.3 Packages and Import Statements

In Java, as your projects grow, organizing your code becomes essential. Packages help you group related classes and interfaces together, making your code easier to manage, reuse, and avoid naming conflicts. This section explains how to create packages, use import statements to access classes across packages, and explores special import forms.

What Are Packages?

A package is essentially a folder (namespace) that contains related Java classes and interfaces. It acts like a container to organize your code logically.

Why Use Packages?

Defining a Package

To place a class inside a package, you declare the package at the very top of your .java file.

package com.example.models;

public class Person {
    private String name;

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

    public String getName() {
        return name;
    }
}

Here, Person.java belongs to the package com.example.models.

Importing Classes from Other Packages

To use classes from another package, you need to import them. The import statement tells Java where to find these classes.

package com.example.app;

import com.example.models.Person;

public class MainApp {
    public static void main(String[] args) {
        Person person = new Person("Alice");
        System.out.println(person.getName());
    }
}

Without the import, you’d have to use the fully qualified name:

com.example.models.Person person = new com.example.models.Person("Alice");

Example: Two Packages in Action

File: Person.java

package com.example.models;

public class Person {
    private String name;

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

    public String getName() {
        return name;
    }
}

File: MainApp.java

package com.example.app;

import com.example.models.Person;

public class MainApp {
    public static void main(String[] args) {
        Person p = new Person("Bob");
        System.out.println("Hello, " + p.getName());
    }
}

Here, MainApp in com.example.app imports and uses Person from com.example.models.

Wildcard Imports

Instead of importing each class individually, you can use a wildcard (*) to import all classes in a package:

import com.example.models.*;

This imports all public classes inside com.example.models.

Note: Wildcard imports do not import subpackages automatically. For example, com.example.models.* won’t import com.example.models.subpackage.

Static Imports

Java also allows static imports to directly use static members (methods or variables) without qualifying them by class name.

Example:

import static java.lang.Math.PI;
import static java.lang.Math.sqrt;

public class Circle {
    public double getCircumference(double radius) {
        return 2 * PI * radius;  // No need to write Math.PI
    }

    public double getSquareRoot(double value) {
        return sqrt(value);      // No need to write Math.sqrt
    }
}

Summary

By structuring your code into packages and understanding imports, you’ll create clear, modular, and maintainable Java applications.

Index