Classes & Objects
Constructors, fields, encapsulation, static, final, nested classes
Class Declaration
"A class is a blueprint for objects": it describes which fields (state) and methods (behavior) objects have. new ClassName() creates an instance, and each instance has its own field values.
// User.java
public class User {
// fields (instance variables)
String name;
int age;
// methods
void hello() {
System.out.println("Hi, I'm " + name);
}
public static void main(String[] args) {
User u = new User(); // create an object
u.name = "Alice"; // assign fields
u.age = 30;
u.hello();
}
}Constructors and this
A constructor is a special method called automatically when an object is created: same name as the class, no return type. this refers to the current object, often used to disambiguate when a field and a parameter share a name.
// User.java
public class User {
String name;
int age;
public User(String name, int age) {
this.name = name; // this.name is the field, name is the parameter
this.age = age;
}
public User(String name) {
this(name, 0); // call another constructor (must be the first line)
}
public static void main(String[] args) {
User a = new User("Alice", 30);
User b = new User("Bob");
System.out.println(a.name + " " + a.age);
System.out.println(b.name + " " + b.age);
}
}Field Initialization Order
Fields are initialized in declaration order: zero values first, then literal initializers and initializer blocks, finally the constructor. static fields are initialized once at class loading.
// InitOrder.java
public class InitOrder {
int a = init("field a", 1);
{
// instance initializer block (runs on every new)
System.out.println("instance block");
}
static int s = init("static s", 99);
static {
// static initializer block (runs once at class loading)
System.out.println("static block");
}
public InitOrder() {
System.out.println("constructor");
}
static int init(String label, int v) {
System.out.println("init " + label);
return v;
}
public static void main(String[] args) {
System.out.println("--- new 1 ---");
new InitOrder();
System.out.println("--- new 2 ---");
new InitOrder();
}
}Encapsulation: private Fields + getter/setter
"Java favors "private fields + public methods" encapsulation: external access to object state must go through methods, making it easy to add validation, locking, and logging, and leaving room to change the implementation.
// Account.java
public class Account {
private String owner;
private long balance; // unit: cents
public Account(String owner, long initial) {
if (initial < 0) throw new IllegalArgumentException("initial < 0");
this.owner = owner;
this.balance = initial;
}
public String getOwner() { return owner; }
public long getBalance() { return balance; }
public void deposit(long amount) {
if (amount <= 0) throw new IllegalArgumentException("amount <= 0");
this.balance += amount;
}
public boolean withdraw(long amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
public static void main(String[] args) {
Account a = new Account("Alice", 10000);
a.deposit(5000);
System.out.println(a.getBalance()); // 15000
System.out.println(a.withdraw(20000)); // false
System.out.println(a.getBalance()); // 15000
}
}static Fields and Methods
Fields and methods marked static belong to the class, not an instance; all objects share one copy. Access via ClassName.member; a static method cannot directly access instance fields. Common for utility methods, constants, and counters.
// Counter.java
public class Counter {
static int totalCount = 0; // shared by all instances
int instanceId;
public Counter() {
totalCount++;
this.instanceId = totalCount;
}
static int getTotal() { // static method
return totalCount;
}
public static void main(String[] args) {
new Counter();
new Counter();
new Counter();
System.out.println(Counter.getTotal()); // 3
System.out.println(Counter.totalCount); // 3
}
}final Fields (Immutable)
A final field can be assigned only once (in the constructor or a literal initializer). Common for building "immutable objects" — thread-safe and safe as a Map key.
// Point.java
public final class Point { // the class itself can also be final, forbidding subclassing
private final double x;
private final double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return x; }
public double getY() { return y; }
public Point translate(double dx, double dy) {
return new Point(x + dx, y + dy); // doesn't modify itself, returns a new object
}
@Override
public String toString() {
return "(" + x + ", " + y + ")";
}
public static void main(String[] args) {
Point p = new Point(1, 2);
Point q = p.translate(3, 4);
System.out.println(p); // (1.0, 2.0)
System.out.println(q); // (4.0, 6.0)
}
}Nested and Anonymous Classes
Classes can be nested. Three common kinds: static nested class (an independent namespace), inner class (holds a reference to the outer class), anonymous class (a one-off interface/class implementation). Java 14+ also has record (see Chapter 10) replacing many anonymous-class uses.
// Nested.java
public class Nested {
// static nested class: only a namespace relation to the outer class
static class Pair {
final String key;
final int value;
Pair(String k, int v) { this.key = k; this.value = v; }
}
// inner class: each instance holds a reference to the outer Nested instance
class Inner {
void show() {
System.out.println("outer count = " + count);
}
}
int count = 10;
public static void main(String[] args) {
// use the static nested class
Pair p = new Pair("x", 1);
System.out.println(p.key + "=" + p.value);
// use the inner class
Nested outer = new Nested();
Inner inner = outer.new Inner();
inner.show();
// anonymous class: a one-off Runnable implementation
Runnable r = new Runnable() {
@Override public void run() {
System.out.println("running");
}
};
r.run();
}
}