Output & Running
Hello, World
<?php
echo "Hello, World!\n"; class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
} Java has no top-level code: every statement lives inside a method, every method inside a class. The runtime calls
public static void main(String[] args) as the entry point. Output goes through System.out.println, which adds a newline. This boilerplate is the price of Java's structure — far heavier than PHP's bare echo.Interpreted vs Compiled to Bytecode
<?php
// PHP is interpreted — run the source directly:
// php script.php
$name = "World";
echo "Hello, {$name}!\n"; // Java compiles to JVM bytecode, then runs on the JVM:
// javac Main.java -> java Main
class Main {
public static void main(String[] args) {
String name = "World";
System.out.println("Hello, " + name + "!");
}
} Java source is compiled by
javac to portable .class bytecode, which the JVM then runs (and JIT-compiles to native code while running). Unlike PHP's direct interpretation, there is a build step — but the JVM is one of the most heavily optimized runtimes in existence.Formatted Output
<?php
$name = "Alice";
$age = 30;
printf("%s is %d\n", $name, $age);
echo "{$name} is {$age}\n"; class Main {
public static void main(String[] args) {
String name = "Alice";
int age = 30;
System.out.printf("%s is %d%n", name, age);
System.out.println(name + " is " + age);
}
} Java's
printf mirrors PHP's, with one quirk: use %n for a platform-correct newline rather than \n. Java has no string interpolation, so you build strings with + concatenation, which auto-converts the int to text here.Variables & Types
Typed Variables & var
<?php
$count = 42;
$price = 9.99;
$name = "Alice";
$active = true;
echo "{$count} {$price} {$name}\n"; class Main {
public static void main(String[] args) {
int count = 42;
double price = 9.99;
String name = "Alice";
boolean active = true;
var inferred = 42; // 'var' infers the static type (still int)
System.out.println(count + " " + price + " " + name);
}
} Every variable has a static type, declared explicitly or inferred with
var (Java 10+) for locals. There is no $ sigil. Unlike PHP's dynamic $count, the type is fixed at compile time, so reassigning a String to count would not compile.Primitives vs Objects
<?php
// PHP int/float are values; everything else is fairly uniform:
$number = 42;
$boxed = (object) ["value" => 42];
echo $number, " ", $boxed->value, "\n"; class Main {
public static void main(String[] args) {
int primitive = 42; // a raw value, stored inline
Integer boxed = 42; // an object wrapper (autoboxed)
double d = primitive; // widening is automatic
System.out.println(primitive + " " + boxed + " " + d);
}
} Java splits numbers into primitives (
int, double, boolean, …) that hold raw values and their object wrappers (Integer, Double, …). Autoboxing converts between them, but the distinction matters: only objects can be null or stored in a generic collection — and wrapper equality has a famous trap (see the gotchas).Numbers & Division
<?php
echo intdiv(7, 2), "\n"; // 3
echo 7 / 2, "\n"; // 3.5
echo 7 % 2, "\n"; // 1
echo 2 ** 10, "\n"; // 1024 class Main {
public static void main(String[] args) {
System.out.println(7 / 2); // 3 (int division truncates)
System.out.println(7 / 2.0); // 3.5 (one operand is double)
System.out.println(7 % 2); // 1
System.out.println((int) Math.pow(2, 10)); // 1024
}
} A trap for PHP developers:
7 / 2 is integer division giving 3 when both operands are int, unlike PHP where / always yields a float. Make one operand a double for 3.5. There is no ** operator — use Math.pow, which returns a double.Strings
Common Methods
<?php
$text = " Hello, World ";
echo strtoupper(trim($text)), "\n"; // HELLO, WORLD
echo str_replace("World", "Java", "Hello World"), "\n";
echo str_contains("Hello", "ell") ? "yes\n" : "no\n"; class Main {
public static void main(String[] args) {
String text = " Hello, World ";
System.out.println(text.trim().toUpperCase()); // HELLO, WORLD
System.out.println("Hello World".replace("World", "Java"));
System.out.println("Hello".contains("ell") ? "yes" : "no");
}
} Strings are objects with chainable methods (
text.trim().toUpperCase()) instead of the free functions strtoupper(trim($text)). The names are camelCase, and the methods return new strings because Java strings are immutable — see the next example.Immutability & StringBuilder
<?php
$result = "";
foreach (["a", "b", "c"] as $letter) {
$result .= $letter; // PHP strings are mutable / appended freely
}
echo $result, "\n"; // abc class Main {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
for (String letter : new String[]{"a", "b", "c"}) {
builder.append(letter); // mutate a builder, not the String
}
System.out.println(builder.toString()); // abc
}
} Java
String objects are immutable: every "modification" allocates a new string. Repeated concatenation in a loop is therefore wasteful, so the idiom is StringBuilder, a mutable buffer you append to and then convert with toString(). PHP's .= has no such concern.Text Blocks (Heredoc)
<?php
$name = "Alice";
$message = <<<TEXT
Dear {$name},
Welcome aboard.
TEXT;
echo $message, "\n"; class Main {
public static void main(String[] args) {
String name = "Alice";
String message = """
Dear %s,
Welcome aboard.""".formatted(name);
System.out.println(message);
}
} Java's text blocks (triple-quoted, Java 15+) are the counterpart to PHP heredoc/nowdoc for multi-line strings, with automatic indentation stripping. They have no interpolation, so combine them with
.formatted(...) (an instance form of String.format) to splice in values.Arrays vs List & Map
One Array vs Typed Collections
<?php
// PHP's single array is both list and map:
$list = [1, 2, 3];
$map = ["name" => "Alice", "age" => 30];
echo $list[0], " ", $map["name"], "\n"; import java.util.List;
import java.util.Map;
class Main {
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3);
Map<String, Object> map = Map.of("name", "Alice", "age", 30);
System.out.println(list.get(0) + " " + map.get("name"));
}
} PHP's one array type splits into typed collections:
List<T> for ordered sequences and Map<K, V> for key-value data, each generic over its element types. Note List.of / Map.of create immutable collections, and you access elements with .get(...), not [].List Operations
<?php
$numbers = [3, 1, 2];
$numbers[] = 4; // append
sort($numbers);
echo count($numbers), "\n"; // 4
echo in_array(2, $numbers) ? "yes\n" : "no\n"; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Main {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(List.of(3, 1, 2));
numbers.add(4);
Collections.sort(numbers);
System.out.println(numbers.size()); // 4
System.out.println(numbers.contains(2)); // true
}
} A mutable
ArrayList exposes methods — add, size, contains — replacing array_push, count, and in_array. Sorting goes through the Collections utility class. The new ArrayList<>(...) diamond infers the type argument from the variable's declaration.Map Operations
<?php
$scores = ["alice" => 90];
$scores["bob"] = 85;
echo array_key_exists("alice", $scores) ? "yes\n" : "no\n";
echo $scores["carol"] ?? 0, "\n"; // 0
foreach ($scores as $name => $score) {
echo "{$name}={$score} ";
}
echo "\n"; import java.util.HashMap;
import java.util.Map;
class Main {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
scores.put("alice", 90);
scores.put("bob", 85);
System.out.println(scores.containsKey("alice")); // true
System.out.println(scores.getOrDefault("carol", 0)); // 0
int total = 0;
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
total += entry.getValue();
}
System.out.println("total=" + total); // 175
}
} Key existence is
containsKey and the safe lookup with a default is getOrDefault — the parallel of PHP's array_key_exists and ??. Iterating uses entrySet() with getKey() / getValue(); a HashMap has no guaranteed order, so this sums rather than relying on insertion order.Control Flow
Conditionals
<?php
$score = 85;
if ($score >= 90) {
echo "A\n";
} elseif ($score >= 80) {
echo "B\n";
} else {
echo "C\n";
} class Main {
public static void main(String[] args) {
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else {
System.out.println("C");
}
}
} The structure matches PHP almost exactly. The only differences are the keyword
else if (two words, not elseif) and that a condition must be a real boolean — Java has no truthiness, so if (someString) does not compile.Switch Expressions
<?php
$day = 6;
$kind = match (true) {
$day >= 6 => "weekend",
default => "weekday",
};
echo $kind, "\n"; // weekend class Main {
public static void main(String[] args) {
int day = 6;
String kind = switch (day) {
case 6, 7 -> "weekend";
default -> "weekday";
};
System.out.println(kind); // weekend
}
} Java's switch expression (arrow form) returns a value like PHP's
match, with no fall-through and multiple labels per arm (case 6, 7). Use yield inside a { } block arm for multi-statement cases. The compiler checks exhaustiveness when switching over an enum or sealed type.Loops & Enhanced for
<?php
for ($index = 0; $index < 3; $index++) {
echo $index, " ";
}
echo "\n";
foreach (["a", "b", "c"] as $letter) {
echo $letter, " ";
}
echo "\n"; class Main {
public static void main(String[] args) {
for (int index = 0; index < 3; index++) {
System.out.print(index + " ");
}
System.out.println();
for (String letter : new String[]{"a", "b", "c"}) {
System.out.print(letter + " ");
}
System.out.println();
}
} The classic three-part
for is identical to PHP's. The enhanced for — for (String letter : items) — is Java's foreach, using a colon instead of as and a declared loop-variable type (or var).Methods
Static Methods
<?php
function add(int $first, int $second): int {
return $first + $second;
}
echo add(2, 3), "\n"; // 5 class Main {
static int add(int first, int second) {
return first + second;
}
public static void main(String[] args) {
System.out.println(add(2, 3)); // 5
}
} Java has no free functions: a "function" is a
static method on a class. The return type comes before the name and each parameter is typed. Because main is static, helper methods it calls directly must also be static (or belong to an instance).Method Overloading
<?php
// PHP has no overloading; you use a union type or optional args:
function describe(int|string $value): string {
return is_int($value) ? "int: {$value}" : "string: {$value}";
}
echo describe(42), "\n";
echo describe("hi"), "\n"; class Main {
static String describe(int value) {
return "int: " + value;
}
static String describe(String value) {
return "string: " + value;
}
public static void main(String[] args) {
System.out.println(describe(42));
System.out.println(describe("hi"));
}
} Java supports overloading: several methods may share a name if their parameter types differ, and the compiler picks the right one by the argument types. PHP forbids this — you would use a union-typed parameter and branch at runtime, as on the left.
Varargs
<?php
function total(int ...$numbers): int {
return array_sum($numbers);
}
echo total(1, 2, 3), "\n"; // 6 class Main {
static int total(int... numbers) {
int sum = 0;
for (int number : numbers) {
sum += number;
}
return sum;
}
public static void main(String[] args) {
System.out.println(total(1, 2, 3)); // 6
}
} Java's varargs
int... numbers collect trailing arguments into an array — the direct parallel of PHP's ...$numbers. A varargs parameter must be the last one, and you can also pass an existing array where it is expected.Classes & OOP
Defining a Class
<?php
class Greeter {
public function __construct(private string $name) {}
public function greet(): string {
return "Hello, {$this->name}!";
}
}
$greeter = new Greeter("Alice");
echo $greeter->greet(), "\n"; class Greeter {
private final String name;
Greeter(String name) { // constructor: same name as the class
this.name = name;
}
String greet() {
return "Hello, " + this.name + "!";
}
}
class Main {
public static void main(String[] args) {
Greeter greeter = new Greeter("Alice");
System.out.println(greeter.greet());
}
} A Java constructor is a method named after the class — there is no
__construct and no PHP-style constructor property promotion, so you declare the field and assign this.name explicitly. Member access uses a dot, and this (no $) is the receiver.Inheritance
<?php
abstract class Animal {
public function __construct(protected string $name) {}
abstract public function speak(): string;
}
class Dog extends Animal {
public function speak(): string {
return "{$this->name} says Woof";
}
}
echo (new Dog("Rex"))->speak(), "\n"; abstract class Animal {
protected final String name;
Animal(String name) { this.name = name; }
abstract String speak();
}
class Dog extends Animal {
Dog(String name) { super(name); }
@Override
String speak() {
return this.name + " says Woof";
}
}
class Main {
public static void main(String[] args) {
System.out.println(new Dog("Rex").speak());
}
} Inheritance uses
extends and abstract as in PHP, with super(...) calling the parent constructor (PHP's parent::__construct). The @Override annotation is optional but recommended — it makes the compiler verify you really are overriding a parent method.Interfaces & Default Methods
<?php
interface Shape {
public function area(): float;
}
class Circle implements Shape {
public function __construct(private float $radius) {}
public function area(): float {
return 3.14159 * $this->radius ** 2;
}
}
echo (new Circle(2))->area(), "\n"; interface Shape {
double area();
default String describe() { // default method — a body in the interface
return "area is " + area();
}
}
class Circle implements Shape {
private final double radius;
Circle(double radius) { this.radius = radius; }
public double area() {
return 3.14159 * radius * radius;
}
}
class Main {
public static void main(String[] args) {
System.out.println(new Circle(2).describe());
}
} Interfaces use
implements just like PHP, but Java interfaces may also carry default methods with a body (since Java 8) — here describe() — letting an interface supply shared behavior, which PHP achieves with its separate "trait" feature.Static Fields & Methods
<?php
class Counter {
private static int $count = 0;
public static function increment(): void {
self::$count++;
}
public static function total(): int {
return self::$count;
}
}
Counter::increment();
Counter::increment();
echo Counter::total(), "\n"; // 2 class Counter {
private static int count = 0;
static void increment() {
count++;
}
static int total() {
return count;
}
}
class Main {
public static void main(String[] args) {
Counter.increment();
Counter.increment();
System.out.println(Counter.total()); // 2
}
} Static fields and methods belong to the class, accessed with a plain dot (
Counter.increment()) rather than PHP's :: scope-resolution operator and self::. A static member is shared across all instances, exactly as in PHP.Records & Sealed Types
Records
<?php
// PHP models immutable data with a readonly class:
final class Point {
public function __construct(
public readonly int $x,
public readonly int $y,
) {}
}
$a = new Point(1, 2);
$b = new Point(1, 2);
echo ($a == $b ? "equal" : "different"), "\n"; // equal record Point(int x, int y) {}
class Main {
public static void main(String[] args) {
Point a = new Point(1, 2);
Point b = new Point(1, 2);
System.out.println(a.equals(b) ? "equal" : "different"); // equal
System.out.println(a); // Point[x=1, y=2]
System.out.println(a.x()); // 1 (accessor)
}
} A Java record (Java 16+) generates a constructor, accessors (
a.x()), value-based equals/hashCode, and a readable toString from one line — comparable to PHP 8.1's readonly class but with far less boilerplate. Records are immutable. Note value equality uses .equals(), not == (see the gotchas).Sealed Types
<?php
// PHP limits a hierarchy informally (abstract base, conventions) — there is
// no language-level "these are the only subtypes" guarantee:
abstract class Shape {}
class Circle extends Shape {
public function __construct(public float $radius) {}
}
class Square extends Shape {
public function __construct(public float $side) {}
}
echo (new Circle(2))->radius, "\n"; // 2 sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
class Main {
public static void main(String[] args) {
Shape shape = new Circle(2);
if (shape instanceof Circle c) {
System.out.println(c.radius()); // 2
}
}
} A sealed interface or class names exactly which types may extend it via
permits — a closed hierarchy the compiler knows is complete. PHP has no equivalent. Combined with records and a switch, sealed types let the compiler verify you have handled every case, as in the next section.Generics
Generic Methods
<?php
// PHP has no real generics; you accept mixed and document with docblocks:
/** @template T @param T[] $items @return T */
function firstItem(array $items): mixed {
return $items[0];
}
echo firstItem([10, 20, 30]), "\n"; // 10 import java.util.List;
class Main {
static <T> T firstItem(List<T> items) {
return items.get(0);
}
public static void main(String[] args) {
System.out.println(firstItem(List.of(10, 20, 30))); // 10
System.out.println(firstItem(List.of("a", "b"))); // a
}
} Generics are a real compile-time feature:
<T> before the return type declares a type parameter, so firstItem(List.of(10)) returns an Integer and the string call a String. PHP can only approximate this with @template docblocks read by Psalm or PHPStan. (Java erases generics at runtime — see the gotchas.)Generic Classes & Bounds
<?php
// A PHP container just holds mixed values:
class Box {
public function __construct(private mixed $value) {}
public function get(): mixed { return $this->value; }
}
$box = new Box(42);
echo $box->get(), "\n"; // 42 class Box<T> {
private final T value;
Box(T value) { this.value = value; }
T get() { return value; }
}
class Main {
static double sum(Box<? extends Number> box) {
return box.get().doubleValue();
}
public static void main(String[] args) {
Box<Integer> box = new Box<>(42);
System.out.println(box.get()); // 42
System.out.println(sum(box)); // 42.0
}
} Classes can be generic:
Box<T> holds a typed value, giving compile-time safety PHP's mixed cannot. A bounded wildcard like Box<? extends Number> accepts any numeric box, letting the method call Number methods on the contents. This is the machinery behind List<T> and Map<K, V>.Pattern Matching & Enums
Pattern Matching for instanceof
<?php
function describe(mixed $value): string {
if (is_int($value)) {
return "int: {$value}";
}
if (is_string($value)) {
return "string of length " . strlen($value);
}
return "other";
}
echo describe(42), "\n";
echo describe("hi"), "\n"; class Main {
static String describe(Object value) {
if (value instanceof Integer number) {
return "int: " + number;
}
if (value instanceof String text) {
return "string of length " + text.length();
}
return "other";
}
public static void main(String[] args) {
System.out.println(describe(42));
System.out.println(describe("hi"));
}
} Pattern matching for
instanceof tests the type and binds a typed variable in one step: value instanceof String text gives you text already typed as String, with no cast. It replaces PHP's is_string-then-use pattern and the manual casts older Java required.Switch Patterns & Record Deconstruction
<?php
// PHP's match compares values; it cannot deconstruct, so you read fields:
function area(array $shape): float {
return match ($shape["kind"]) {
"circle" => 3.14159 * $shape["radius"] ** 2,
"square" => $shape["side"] ** 2,
};
}
echo area(["kind" => "square", "side" => 4]), "\n"; // 16 sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
class Main {
static double area(Shape shape) {
return switch (shape) {
case Circle(double radius) -> 3.14159 * radius * radius;
case Square(double side) -> side * side;
};
}
public static void main(String[] args) {
System.out.println(area(new Square(4))); // 16.0
}
} Switching over a sealed type with record patterns (Java 21+) both matches the variant and deconstructs it —
case Circle(double radius) binds the component directly. Because the type is sealed, the compiler knows the switch is exhaustive and needs no default. PHP's match can only compare scalar values.Enums
<?php
enum Suit: string {
case Hearts = "hearts";
case Spades = "spades";
public function color(): string {
return match ($this) {
Suit::Hearts => "red",
Suit::Spades => "black",
};
}
}
echo Suit::Hearts->value, " ", Suit::Hearts->color(), "\n"; enum Suit {
HEARTS, SPADES;
String color() {
return switch (this) {
case HEARTS -> "red";
case SPADES -> "black";
};
}
}
class Main {
public static void main(String[] args) {
System.out.println(Suit.HEARTS + " " + Suit.HEARTS.color());
}
} Java enums are full classes that can carry fields and methods, much like PHP 8.1 enums. A method can
switch (this) over the cases, and switching over an enum is checked for exhaustiveness. To attach backing values, you give the enum a constructor and a field rather than the : string syntax PHP uses.Streams vs Array Functions
Map, Filter & Collect
<?php
$numbers = [1, 2, 3, 4, 5];
$result = array_map(
fn($n) => $n * 2,
array_filter($numbers, fn($n) => $n % 2 === 1)
);
print_r(array_values($result)); // 2, 6, 10 import java.util.List;
import java.util.stream.Collectors;
class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 1)
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println(result); // [2, 6, 10]
}
} The Stream API chains
filter and map left-to-right and is lazy until a terminal operation like collect runs — far more readable than PHP's inside-out array_map(array_filter(...)). The lambdas (n -> n * 2) are Java's arrow functions.Aggregates & Reduce
<?php
$numbers = [1, 2, 3, 4, 5];
echo array_sum($numbers), "\n"; // 15
echo max($numbers), "\n"; // 5
echo array_reduce($numbers, fn($carry, $n) => $carry * $n, 1), "\n"; // 120 import java.util.List;
class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
System.out.println(numbers.stream().mapToInt(Integer::intValue).sum()); // 15
System.out.println(numbers.stream().max(Integer::compare).get()); // 5
System.out.println(numbers.stream().reduce(1, (product, n) -> product * n)); // 120
}
} Streams provide aggregate operations —
sum, max, min, average, count — and a general reduce (the counterpart to array_reduce) taking a seed and a combiner. The Integer::intValue and Integer::compare are method references, Java's shorthand for a lambda that just calls one method.Grouping
<?php
$words = ["apple", "banana", "cherry", "avocado"];
$byLetter = [];
foreach ($words as $word) {
$byLetter[$word[0]][] = $word;
}
ksort($byLetter);
foreach ($byLetter as $letter => $group) {
echo "{$letter}: " . implode(", ", $group) . "\n";
} import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
class Main {
public static void main(String[] args) {
List<String> words = List.of("apple", "banana", "cherry", "avocado");
Map<Character, List<String>> byLetter = words.stream()
.collect(Collectors.groupingBy(word -> word.charAt(0), TreeMap::new, Collectors.toList()));
byLetter.forEach((letter, group) ->
System.out.println(letter + ": " + String.join(", ", group)));
}
} The
Collectors.groupingBy collector turns the manual "build a map of lists" loop into one declarative call, and passing TreeMap::new keeps the keys sorted (PHP's ksort). This composability — collectors nested inside collectors — is the Stream API's real power.Error Handling
Try / Catch / Finally
<?php
function parseAmount(string $input): int {
if (!is_numeric($input)) {
throw new InvalidArgumentException("not a number: {$input}");
}
return (int) $input;
}
try {
parseAmount("abc");
} catch (InvalidArgumentException $error) {
echo "Caught: ", $error->getMessage(), "\n";
} finally {
echo "Done\n";
} class Main {
static int parseAmount(String input) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException error) {
throw new IllegalArgumentException("not a number: " + input);
}
}
public static void main(String[] args) {
try {
parseAmount("abc");
} catch (IllegalArgumentException error) {
System.out.println("Caught: " + error.getMessage());
} finally {
System.out.println("Done");
}
}
} The
try/catch/finally structure matches PHP's, and you read the message with getMessage(). IllegalArgumentException here is an unchecked exception (extends RuntimeException), so it need not be declared — unlike checked exceptions, covered next.Checked Exceptions
<?php
// PHP has no checked exceptions — a function need not declare what it throws,
// and the caller is never forced by the language to handle it:
function risky(): void {
throw new RuntimeException("boom");
}
try {
risky();
} catch (RuntimeException $error) {
echo "handled: ", $error->getMessage(), "\n";
} class Main {
// 'throws' DECLARES a checked exception — callers must handle or re-declare:
static void risky() throws Exception {
throw new Exception("boom");
}
public static void main(String[] args) {
try {
risky();
} catch (Exception error) {
System.out.println("handled: " + error.getMessage());
}
}
} Java's distinctive checked exceptions have no PHP equivalent: a method that can throw a checked exception (anything not a
RuntimeException) must declare it with throws, and the compiler forces every caller to either catch it or declare throws in turn. It makes failure paths explicit, at the cost of more ceremony.Try-with-Resources
<?php
// PHP uses finally for deterministic cleanup:
$handle = fopen("php://temp", "w+");
try {
fwrite($handle, "data");
rewind($handle);
echo stream_get_contents($handle), "\n";
} finally {
fclose($handle);
} import java.io.StringWriter;
class Main {
public static void main(String[] args) {
// The resource is closed automatically at the end of the block:
try (StringWriter writer = new StringWriter()) {
writer.write("data");
System.out.println(writer.toString()); // data
} catch (Exception error) {
System.out.println("error: " + error.getMessage());
}
}
} Try-with-resources declares a resource in parentheses after
try; any object implementing AutoCloseable is closed automatically when the block exits, even on an exception. It replaces the manual try ... finally fclose() dance — the parallel of the using statement in C#.⚠ Gotchas for PHP Devs
== vs .equals()
<?php
// PHP: == compares values, === compares value + type (and identity for objects):
$a = "hello";
$b = "hel" . "lo";
var_dump($a == $b); // true (value comparison) class Main {
public static void main(String[] args) {
String a = "hello";
String b = new String("hello");
System.out.println(a == b); // false — different objects!
System.out.println(a.equals(b)); // true — same value
}
} The single most common Java bug for newcomers: for objects,
== compares reference identity (are these the same object?), not value. To compare values you must call .equals(). PHP's == compares values, so this distinction catches every PHP developer at least once — always use .equals() for strings and objects.Integer Caching
<?php
// PHP just compares integer values; there is no boxing to worry about:
$a = 1000;
$b = 1000;
var_dump($a === $b); // true class Main {
public static void main(String[] args) {
Integer small1 = 127, small2 = 127;
Integer big1 = 1000, big2 = 1000;
System.out.println(small1 == small2); // true (cached -128..127)
System.out.println(big1 == big2); // false (different objects!)
System.out.println(big1.equals(big2)); // true (correct way)
}
} A subtle consequence of
==-as-identity: Java caches boxed Integer values from −128 to 127, so == on small numbers accidentally works, while the same code on larger numbers fails. Never compare boxed numbers with == — use .equals() or unbox to int. PHP's value semantics sidestep this entirely.null & NullPointerException
<?php
// PHP tolerates null fairly gently and offers ?-> and ??:
$user = null;
echo $user?->name ?? "guest", "\n"; // guest import java.util.Optional;
class Main {
public static void main(String[] args) {
String name = null;
// name.length(); // would throw NullPointerException
Optional<String> user = Optional.ofNullable(name);
System.out.println(user.map(String::toUpperCase).orElse("GUEST")); // GUEST
}
} Any object reference can be
null, and calling a method on it throws a NullPointerException — Java has no ?-> operator. The modern idiom is Optional<T>, whose map and orElse chain safely, roughly matching PHP's ?-> and ??. Many APIs still return raw nullable references, so null-checking remains a fact of life.Integer Types & Overflow
<?php
// PHP ints are 64-bit and silently become floats on overflow:
echo PHP_INT_MAX, "\n";
echo PHP_INT_MAX + 1, "\n"; // becomes a float class Main {
public static void main(String[] args) {
int maxInt = Integer.MAX_VALUE; // 2147483647
System.out.println(maxInt);
System.out.println(maxInt + 1); // -2147483648 — silent wraparound!
long big = (long) maxInt + 1; // use long for larger values
System.out.println(big); // 2147483648
}
} Java's
int is a fixed 32-bit signed type that silently wraps around on overflow — Integer.MAX_VALUE + 1 becomes the most negative int, with no error and no promotion. Use long (64-bit) for larger values, or Math.addExact to throw on overflow. PHP's ints are 64-bit and promote to float instead.