Every Java class, method and variable carries a set of keywords that decide who can see it, who can extend it and whether it can be created at all. These keywords are called modifiers. This guide walks through all of them in one place: the five that apply to classes (public, default, final, abstract, strictfp) and the four access levels that apply to members (public, protected, default, private).
- Part 1 covers class-level modifiers, with the error you get when you break each rule.
- Part 2 covers member-level access and how
protectedbehaves across packages. - Code samples here are original. Put each class in the file named in its label and compile with
javac -d .so the package folders are created.
Part 1: Class-level modifiers
Which modifiers can a class have?
It depends on whether the class sits at the top of a file or is nested inside another class.
| Top-level class | Nested (inner) class |
|---|---|
| public, default (no keyword), abstract, final, strictfp | All of those, plus static, private and protected |
A top-level class cannot be private or protected, because there is no enclosing class for those words to refer to:
private class Invoice { // top-level
public static void main(String[] args) {
System.out.println("Hello");
}
}
Compile-time error: modifier private not allowed here
A note on terminology. C++ books separate "access specifiers" from "modifiers". Java does not: every one of these words, access-related or not, is simply a modifier. Also, "default" is not a keyword you type on a class. It just means you wrote no access modifier, which gives package-level access.
public
A public class can be used from any package. Here the class is left package-private, so another package cannot use it:
shop/core/Cart.java
package shop.core;
class Cart {
public void add(String item) {
System.out.println(item + " added");
}
}
shop/web/Checkout.java
package shop.web;
import shop.core.Cart;
class Checkout {
public static void main(String[] args) {
Cart c = new Cart();
c.add("Wool rug");
}
}
Compile-time error: shop.core.Cart is not public in shop.core; cannot be accessed from outside package
Add public to the class and the same code works:
shop/core/Cart.java (fixed)
package shop.core;
public class Cart {
public void add(String item) {
System.out.println(item + " added");
}
}
Compile with javac -d . shop/core/Cart.java shop/web/Checkout.java and run java shop.web.Checkout.
Output: Wool rug added
Default (package-private)
With no modifier, the class is visible only to classes in the same package. This is the right choice for helper classes you do not want other packages depending on. The failing Cart/Checkout pair above is exactly this case.
final
final works on classes, methods and variables. At class level it means two things:
- final method: subclasses cannot override it.
- final class: nobody can extend it.
class Payment {
public final void audit() {
System.out.println("Audit entry written");
}
}
class CardPayment extends Payment {
public void audit() { // not allowed
System.out.println("Skipping audit");
}
}
Compile-time error: audit() in CardPayment cannot override audit() in Payment; overridden method is final
final class Money { }
class Euro extends Money { } // not allowed
Compile-time error: cannot inherit from final Money
Two details that are easy to miss:
- Marking a class
finaldoes not make its fields final. Static or instance variables inside it can still change. finalis a security and stability tool (for example,Stringis final so nobody can change its behaviour). The cost is that you lose inheritance for final classes and polymorphism for final methods, so use it only when you have a reason.
final class Settings {
static int retries = 3;
public static void main(String[] args) {
retries = 5; // fine, the class is final, the variable is not
System.out.println(retries);
}
}
Output: 5
abstract
abstract applies to classes and methods, never to variables. An abstract method has a signature but no body, and ends with a semicolon. It is a promise that a subclass will fill it in.
abstract class Shape {
abstract double area(); // no body
}
class Square extends Shape {
double side;
Square(double side) { this.side = side; }
double area() { return side * side; }
}
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
double area() { return Math.PI * r * r; }
}
Rules to remember:
- An abstract class cannot be instantiated:
new Shape()will not compile. - If a class has even one abstract method, the class itself must be declared
abstract. - The reverse is not required. A class can be abstract with zero abstract methods, simply to stop people creating it directly. It is a common way to offer a base class with shared code.
- A subclass must implement every inherited abstract method, or be declared
abstractitself and pass the job down.
Each of these fails in a different way:
class Report { void print(); }
Compile-time error: missing method body, or declare abstract
abstract class Report { abstract void print() { } }
Compile-time error: abstract methods cannot have a body
class Report { abstract void print(); }
Compile-time error: Report is not abstract and does not override abstract method print() in Report
abstract class Report {
abstract void print();
abstract void export();
}
class SalesReport extends Report {
void print() { } // export() is missing
}
Compile-time error: SalesReport is not abstract and does not override abstract method export() in Report
Modifiers you can never combine with abstract on a method
An abstract method says "someone else will write this". Any modifier that says "this is finished here" or "nobody else can see this" contradicts it:
| Combination | Why it is illegal |
|---|---|
abstract final | final forbids overriding, abstract requires it |
abstract private | a private method is invisible to subclasses, so they could never implement it |
abstract static | static methods belong to the class and are not overridden |
abstract synchronized | synchronization needs a real body to lock around |
abstract native | native means the body already exists in another language |
abstract strictfp | strictfp controls how a body calculates, and there is no body |
abstract final void m();
Compile-time error: illegal combination of modifiers: abstract and final
final vs abstract
- On a method: abstract must be overridden, final cannot be, so the pair is illegal.
- On a class: abstract needs a subclass to be useful, final forbids one, so the pair is illegal.
- An abstract class can contain a final method. A final class cannot contain an abstract method.
abstract class Base {
public final void log() { } // valid
}
final class Done {
public abstract void run(); // invalid: no subclass can ever implement it
}
strictfp
- Applies to classes and methods, not variables. Introduced in Java 1.2.
- On a method it forces floating-point calculations to follow the IEEE-754 standard exactly, giving the same result on every platform.
- On a class it applies to every concrete method inside.
abstract strictfpis legal for a class but illegal for a method (see the table above).- Modern note: since Java 17 all floating-point math is strict by default, so the keyword has no effect and the compiler may warn that it is unnecessary. You still need to understand it for older code and exams.
abstract strictfp class Calculator { } // valid
abstract class Broken {
public abstract strictfp void m(); // invalid
}
Part 2: Member-level access
Methods and variables have four access levels. Each one is a bigger circle than the one before it:
| Modifier | Same class | Same package | Subclass in another package | Any other class |
|---|---|---|---|---|
private | Yes | No | No | No |
| default | Yes | Yes | No | No |
protected | Yes | Yes | Yes, through a subclass reference | No |
public | Yes | Yes | Yes | Yes |
Two practical points: a public member is only reachable from another package if its class is also public, and private abstract is illegal because subclasses cannot see a private method to implement it.
private
class Wallet {
private int balance = 500;
}
class Shop {
public static void main(String[] args) {
Wallet w = new Wallet();
System.out.println(w.balance); // not allowed
}
}
Compile-time error: balance has private access in Wallet
The usual pattern is a private field plus public getter and setter methods, so the class controls how its data changes.
protected
Think of protected as "default access, plus subclasses in other packages". Inside the same package it behaves like default:
shop/core/Product.java
package shop.core;
public class Product {
protected void discount() {
System.out.println("10% off");
}
}
class Bundle extends Product {
public static void main(String[] args) {
Product p = new Product();
p.discount(); // valid: same package
Bundle b = new Bundle();
b.discount(); // valid
Product pb = new Bundle();
pb.discount(); // valid: same package
}
}
From another package the rule is stricter. You can call a protected member only inside a subclass, and only through a reference whose type is that subclass (or one of its children), never through the parent type:
shop/web/FestivalItem.java
package shop.web;
import shop.core.Product;
class SaleItem extends Product { }
class FestivalItem extends SaleItem {
public static void main(String[] args) {
Product p = new Product();
p.discount(); // invalid
Product ps = new SaleItem();
ps.discount(); // invalid: reference type is Product
Product pf = new FestivalItem();
pf.discount(); // invalid: reference type is Product
SaleItem s = new SaleItem();
s.discount(); // invalid: SaleItem is not FestivalItem or a child of it
FestivalItem f = new FestivalItem();
f.discount(); // valid
}
}
Compile-time error: discount() has protected access in Product (for each invalid line)
Rule: outside the package, protected is reachable only from a subclass, and only through a reference of that subclass type.
Quick reference
| Modifier | Applies to | What it does |
|---|---|---|
public | class, member | visible everywhere (the class must be public too) |
| default | class, member | visible in the same package only |
protected | member | same package plus subclass access from other packages |
private | member (and nested class) | same class only |
final | class, method, variable | no extending, no overriding, no reassigning |
abstract | class, method | incomplete: cannot instantiate a class, a method needs an override |
strictfp | class, method | strict IEEE-754 floating point (redundant since Java 17) |
Remember: final and abstract are never legal together, and for methods abstract also rejects private, static, synchronized, native and strictfp. When you have a free choice, prefer abstract over final, because it keeps inheritance and polymorphism available.
0 Comments