Java · lesson 12 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Introduction to Object-Oriented Programming in Java
Start Java OOP with runnable examples: define a class, create objects, write constructors, use this, and see how static and instance members differ.
By SkillByExample · Updated
Object-oriented programming organises a program around objects: values that bundle data
(fields) with the code that works on it (methods). A class is the blueprint; new
builds objects from it. Every example is a single file run with the JDK 21 source launcher
(java File.java).
A class and two objects
public class DogDemo {
static class Dog {
String name; // field: each Dog has its own
int age;
void bark() { // method: behaviour
System.out.println(name + " (" + age + ") says Woof!");
}
}
public static void main(String[] args) {
Dog rex = new Dog();
rex.name = "Rex";
rex.age = 3;
Dog luna = new Dog();
luna.name = "Luna";
rex.bark();
luna.bark();
}
}
$ java DogDemo.java
Rex (3) says Woof!
Luna (0) says Woof!
rex and luna run the same bark method but print different text, because each object carries
its own name and age. luna.age was never set, so it has the default value for an int: 0.
Constructors set up a valid object
Setting fields one by one lets an object exist half-built. A constructor runs as part of new
and can insist on the values it needs.
public class Constructors {
static class Account {
private final String owner;
private double balance;
Account(String owner, double opening) {
if (opening < 0) throw new IllegalArgumentException("negative opening balance");
this.owner = owner;
this.balance = opening;
}
Account(String owner) {
this(owner, 0); // delegate to the main constructor
}
void deposit(double amount) { balance += amount; }
String describe() { return owner + ": " + balance; }
}
public static void main(String[] args) {
Account a = new Account("Ada", 100);
Account b = new Account("Bo");
a.deposit(50);
System.out.println(a.describe());
System.out.println(b.describe());
new Account("Cy", -5);
}
}
$ java Constructors.java
Ada: 150.0
Bo: 0.0
Exception in thread "main" java.lang.IllegalArgumentException: negative opening balance
at Constructors$Account.<init>(Constructors.java:7)
at Constructors.main(Constructors.java:27)
A constructor has the class’s name and no return type. Overloaded constructors usually delegate with
this(...), so validation lives in one place — which is why new Account("Cy", -5) is rejected
before an invalid account can exist.
this: the current object
Inside a constructor or method, this refers to the object it is running on. Forget it and a
parameter silently shadows the field.
public class ThisDemo {
static class Point {
int x, y;
Point(int x, int y) {
x = x; // assigns the parameter to itself
this.y = y; // assigns the field
}
Point moveRight() {
this.x++;
return this; // return the same object for chaining
}
}
public static void main(String[] args) {
Point p = new Point(5, 7);
System.out.println(p.x + "," + p.y);
p.moveRight().moveRight();
System.out.println(p.x + "," + p.y);
}
}
$ java ThisDemo.java
0,7
2,7
x = x compiles but does nothing useful: both names refer to the parameter, so the field x stays
0. Returning this lets calls chain on the same object.
Static vs instance members
An instance field belongs to each object. A static field belongs to the class and is shared by all
its objects.
public class Counting {
static class Ticket {
static int issued = 0; // one copy, shared
final int number; // one per object
Ticket() {
issued++;
number = issued;
}
static int issuedSoFar() { return issued; }
}
public static void main(String[] args) {
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
System.out.println("numbers: " + t1.number + " " + t2.number + " " + t3.number);
System.out.println("issued: " + Ticket.issuedSoFar());
}
}
$ java Counting.java
numbers: 1 2 3
issued: 3
Each Ticket remembers its own number, but issued is a single counter on the class. Static
methods like issuedSoFar are called on the class name and cannot use this, because there is no
object involved.
Variables hold references, not objects
Assigning an object variable copies the reference. Two variables can then name the same object.
public class References {
static class Box {
int value;
Box(int value) { this.value = value; }
}
public static void main(String[] args) {
Box a = new Box(1);
Box b = a; // same object, second name
Box c = new Box(1); // different object, same contents
b.value = 99;
System.out.println("a.value = " + a.value);
System.out.println("a == b ? " + (a == b));
System.out.println("a == c ? " + (a == c));
Box empty = null;
System.out.println(empty.value);
}
}
$ java References.java
a.value = 99
a == b ? true
a == c ? false
Exception in thread "main" java.lang.NullPointerException: Cannot read field "value" because "<local4>" is null
at References.main(References.java:18)
Changing the object through b is visible through a. == compares references, so two separate
boxes are never == even with equal contents — that is what equals is for. A null reference
points at no object, and using it throws NullPointerException. The message names the failing
expression; it says <local4> here because the source launcher compiles without local-variable
names — compile with javac -g and it says "empty".
toString: how an object describes itself
Printing an object calls its toString(). The inherited version from Object is rarely useful.
public class Printing {
static class Plain {
String name = "plain";
}
static class Book {
String title;
int pages;
Book(String title, int pages) { this.title = title; this.pages = pages; }
@Override
public String toString() { return "Book[" + title + ", " + pages + " pages]"; }
}
public static void main(String[] args) {
String plain = new Plain().toString();
System.out.println(plain.startsWith("Printing$Plain@"));
System.out.println(new Book("Dune", 412));
}
}
$ java Printing.java
true
Book[Dune, 412 pages]
The default prints the class name, @ and a hash code that differs between runs — hence the
program only checks its prefix. Overriding toString is the first taste of polymorphism: the
println code is the same, but each class decides how it is shown. The four pillars —
encapsulation, inheritance, polymorphism and abstraction — each have their own lesson; see the
Java OOP guide.
Practice
- Give
Doga constructor takingnameandage, and abirthday()method that adds one toage. Createrexaged 3, callbirthday()twice, and print him.
Solution
public class Birthday {
static class Dog {
final String name;
int age;
Dog(String name, int age) {
this.name = name;
this.age = age;
}
void birthday() { age++; }
@Override
public String toString() { return name + " is " + age; }
}
public static void main(String[] args) {
Dog rex = new Dog("Rex", 3);
rex.birthday();
rex.birthday();
System.out.println(rex);
}
}
$ java Birthday.java
Rex is 5
name is final because it never changes after construction; age is not, because birthday
changes it.
- Fix the
Pointconstructor inThisDemosonew Point(5, 7)really storesx = 5.
Solution
public class ThisFixed {
static class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public static void main(String[] args) {
Point p = new Point(5, 7);
System.out.println(p.x + "," + p.y);
}
}
$ java ThisFixed.java
5,7
this.x names the field; the bare x names the parameter that shadows it.
- Add a static
counttoDogthat tracks how many dogs were created, and print it after creating three.
Solution
public class DogCount {
static class Dog {
static int count = 0;
final String name;
Dog(String name) {
this.name = name;
count++;
}
}
public static void main(String[] args) {
new Dog("Rex");
new Dog("Luna");
new Dog("Max");
System.out.println("dogs created: " + Dog.count);
}
}
$ java DogCount.java
dogs created: 3
One shared counter on the class sees every constructor call, whichever object it builds.
- Write a
Rectangleclass withwidth,height, anarea()method and atoString(), and print a list of three rectangles sorted by area.
Solution
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Rectangles {
static class Rectangle {
final int width, height;
Rectangle(int width, int height) { this.width = width; this.height = height; }
int area() { return width * height; }
@Override
public String toString() { return width + "x" + height + "=" + area(); }
}
public static void main(String[] args) {
List<Rectangle> rs = new ArrayList<>(List.of(
new Rectangle(4, 5), new Rectangle(2, 3), new Rectangle(10, 1)));
rs.sort(Comparator.comparingInt(Rectangle::area));
System.out.println(rs);
}
}
$ java Rectangles.java
[2x3=6, 10x1=10, 4x5=20]
Rectangle::area is a method reference: the comparator asks each object for its own area.