Hello World & Running
Hello, World
class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
} print("Hello, World!") Swift needs no class, no
main method, and no System.out — a top-level print() is a complete program (the file's top-level statements are the entry point). Statements need no semicolons, and print adds a newline like println. The ceremony a Java program requires around main simply does not exist.Building & running
// Compile then run (two steps):
// javac Main.java
// java Main
// Or build a jar:
// jar cfe app.jar Main Main.class && java -jar app.jar // Run a script directly (no separate compile):
// swift main.swift
// Compile to a binary:
// swiftc main.swift -o app && ./app
// Or use the package manager:
// swift run Swift compiles to a native binary like Java compiles to bytecode, but
swift main.swift runs a file directly in script mode with no separate javac step, and the result is a standalone executable that needs no JVM. On Apple platforms Xcode and Swift Package Manager (swift build/swift run) replace Maven and Gradle.Comments
class Main {
public static void main(String[] args) {
// Single-line comment
/* Block comment */
/** Javadoc comment */
System.out.println("done");
}
} // Single-line comment
/* Block comment — these nest in Swift */
/// Documentation comment (Markdown)
print("done") Swift shares Java's
// and /* */ comments, but Swift block comments nest correctly, so you can comment out a region that already contains a block comment. Documentation uses /// (or /** */) with Markdown, the counterpart to Javadoc, surfaced in Xcode's Quick Help.Variables & Types
let and var
class Main {
public static void main(String[] args) {
final int limit = 100; // immutable
int count = 0; // mutable
count = 42;
System.out.println(count + " " + limit);
}
} let limit = 100 // immutable (like final)
var count = 0 // mutable
count = 42
print(count, limit) Swift inverts Java's default:
let declares a constant (Java's final) and var declares a mutable variable, and the immutable form is the one with the shorter keyword — so idiomatic Swift reaches for let first. The compiler even warns when a var is never mutated, nudging you toward let.Type inference
class Main {
public static void main(String[] args) {
var name = "Swift"; // inferred String (Java 10+)
var count = 42; // inferred int
var ratio = 3.14; // inferred double
System.out.println(name + " " + count + " " + ratio);
}
} let name = "Swift" // inferred String
let count = 42 // inferred Int
let ratio = 3.14 // inferred Double
let typed: Int64 = 9 // explicit annotation when you want it
print(name, count, ratio, typed) Both languages infer types from the initializer — Java's
var (since 10) and Swift's let/var. Swift's inference is more pervasive: it flows through generic calls and closures, so annotations are rarer. When you do annotate, the type follows the name after a colon (let typed: Int64), the reverse of Java's Int64 typed order.No implicit numeric conversion
class Main {
public static void main(String[] args) {
int count = 100;
long wider = count; // implicit widening — fine in Java
double ratio = count / 8.0; // int promoted to double
System.out.println(wider + " " + ratio);
}
} let count = 100
let wider = Int64(count) // explicit — Swift never auto-widens
let ratio = Double(count) / 8.0 // explicit Int -> Double
print(wider, ratio) Java silently widens numeric types (
int to long, int to double). Swift never converts between numeric types implicitly — you wrap the value in the target type (Int64(count), Double(count)) every time. This is stricter than Java but eliminates surprises from automatic promotion in mixed-type arithmetic.Tuples
class Main {
public static void main(String[] args) {
// Java has no tuple; use a record or an array.
record Pair(String name, int age) {}
var person = new Pair("Alice", 30);
System.out.println(person.name() + " " + person.age());
}
} let person = (name: "Alice", age: 30) // a labeled tuple
print(person.name, person.age)
let (name, age) = person // destructure
print(name, age) Swift has built-in tuples — lightweight, optionally labeled groupings written with parentheses — where Java reaches for a
record or an array. A tuple is ideal for a transient pairing you do not want to name a type for, and it destructures directly into multiple bindings (let (name, age) = person), which Java records do not.Optionals vs null
Optionals vs null
class Main {
public static void main(String[] args) {
String username = null; // any reference can be null
// System.out.println(username.length()); // NullPointerException at runtime
System.out.println(username == null);
}
} var username: String? = nil // Optional<String> — explicitly nullable
let always: String = "set" // can NEVER be nil — compiler enforced
print(username == nil)
// print(username.count) ❌ compile error — must unwrap first This is the single biggest safety difference. In Java any reference can be
null, and dereferencing it throws a NullPointerException at runtime. Swift bakes nullability into the type: String? may be nil, a plain String can never be, and the compiler refuses to let you use an optional without unwrapping it first. The NPE class of bug is eliminated at compile time.Optional binding (if let)
import java.util.Optional;
class Main {
public static void main(String[] args) {
Optional<String> name = Optional.of("Alice");
if (name.isPresent()) {
System.out.println("Hello, " + name.get());
} else {
System.out.println("Hello, stranger");
}
}
} let name: String? = "Alice"
if let actualName = name {
print("Hello, \(actualName)")
} else {
print("Hello, stranger")
} Java added
Optional<T> as a library type with isPresent()/get(), but it is opt-in and ordinary references stay nullable. Swift's if let actualName = name unwraps the optional into a non-optional binding scoped to the if block — a language feature, not a wrapper class. Since Swift 5.7 you can shorten it to if let name when the names match.Early exit with guard let
class Main {
static void process(String name) {
if (name == null) return; // manual guard
System.out.println("Processing: " + name);
}
public static void main(String[] args) {
process("Alice");
process(null);
}
} func process(name: String?) {
guard let name = name else { return }
// `name` is unwrapped and in scope for the rest of the function
print("Processing: \(name)")
}
process(name: "Alice")
process(name: nil) Swift's
guard let is the idiomatic early-exit unwrap: unlike if let, the binding stays in scope for the rest of the function, so the "happy path" continues unindented. The else branch must leave the scope (return, throw, break, or continue). It turns Java's scattered null-guards into one declarative statement that both checks and unwraps.Nil coalescing (??)
class Main {
public static void main(String[] args) {
String setting = null;
String value = setting != null ? setting : "default";
System.out.println(value);
}
} let setting: String? = nil
let value = setting ?? "default"
print(value)
let score: Int? = nil
print("Score: \(score ?? 0)") The
?? operator returns the optional's value if present, or the right-hand default if it is nil — replacing Java's x != null ? x : fallback ternary (or Optional.orElse). The compiler verifies the default matches the unwrapped type, and the operators chain, so a ?? b ?? c tries each in turn.Optional chaining (?.)
class Main {
record Address(String city) {}
record User(Address address) {}
public static void main(String[] args) {
User user = null;
// Manual null checks at every hop, or NPE.
String city = (user != null && user.address() != null)
? user.address().city() : "Unknown";
System.out.println(city);
}
} struct Address { let city: String }
struct User { let address: Address? }
let user: User? = nil
print(user?.address?.city ?? "Unknown") Swift's
?. short-circuits a whole chain to nil if any link is absent, so user?.address?.city is safe even when user is nil. Java has no optional-chaining operator: you write nested null checks or chain Optional.map calls. Combined with ??, the entire safe-navigation-with-default collapses to one readable line.Strings
String interpolation
class Main {
public static void main(String[] args) {
String name = "World";
int count = 3;
System.out.println(String.format("Hello, %s! x%d", name, count));
}
} let name = "World"
let count = 3
print("Hello, \(name)! x\(count)") Swift interpolates any expression directly into a string literal with
\(expression) — no String.format and no positional %s/%d specifiers to keep in sync with their arguments. The interpolation is type-checked, and any type that is printable can be embedded, including the result of a method call or arithmetic.String methods
class Main {
public static void main(String[] args) {
String sentence = "the quick brown fox";
System.out.println(sentence.toUpperCase());
System.out.println(sentence.contains("quick"));
System.out.println(sentence.split(" ").length);
}
} let sentence = "the quick brown fox"
print(sentence.uppercased())
print(sentence.contains("quick"))
print(sentence.split(separator: " ").count) Swift's string methods read much like Java's, with small naming differences (
uppercased() vs toUpperCase(), .count vs .length). A deeper difference: a Swift String is a collection of grapheme clusters (user-perceived characters), so .count reflects what a reader sees, and you cannot index a string with a plain integer — emoji and combined characters count as one.Multi-line strings
class Main {
public static void main(String[] args) {
String json = """
{
"name": "Alice"
}
""";
System.out.println(json);
}
} let json = """
{
"name": "Alice"
}
"""
print(json) Both languages use triple-quoted text blocks for multi-line strings, and both strip the indentation up to the closing delimiter. Java added text blocks in 17; Swift has had them since 5.0. Inside a Swift multi-line string you can still interpolate with
\(...), and a trailing \ joins a line to the next without a newline.Collections
Arrays / Lists
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>(List.of("apple", "banana"));
fruits.add("cherry");
System.out.println(fruits.get(0) + " " + fruits.size());
}
} var fruits = ["apple", "banana"]
fruits.append("cherry")
print(fruits[0], fruits.count) A Swift
Array is the everyday growable sequence, written as a literal with no new ArrayList<>() ceremony and no separate List interface. Mutability comes from the binding: a var array can append, a let array is fixed. It uses .count and subscripting fruits[0] rather than .size() and .get(0).map / filter / reduce
import java.util.List;
class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
int total = numbers.stream()
.filter(number -> number % 2 == 0)
.mapToInt(number -> number * number)
.sum();
System.out.println(total);
}
} let numbers = [1, 2, 3, 4, 5]
let total = numbers
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.reduce(0, +)
print(total) Swift's
map/filter/reduce are methods directly on the array — there is no separate .stream() to open or terminal operation to close as in Java. The $0 shorthand names the first closure argument, and reduce(0, +) passes the + function itself. The result is eager (a new array), not a lazy stream, unless you ask for .lazy.Dictionaries / Maps
import java.util.HashMap;
import java.util.Map;
class Main {
public static void main(String[] args) {
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
Integer bob = ages.getOrDefault("Bob", 0);
System.out.println(ages.get("Alice") + " " + bob);
}
} var ages = ["Alice": 30]
ages["Bob"] = 25
let alice = ages["Alice"] // Optional<Int> — key may be missing
print(alice ?? 0, ages["Carol"] ?? 0) A Swift
Dictionary uses a brace literal with colons and subscript access. Crucially, subscripting returns an optional (ages["Alice"] is Int?) because the key might be absent — the type system forces you to handle the missing case, where Java's get silently returns null. Pair it with ?? 0 for Java's getOrDefault behavior.Sets
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class Main {
public static void main(String[] args) {
Set<Integer> numbers = new HashSet<>(List.of(1, 2, 2, 3));
System.out.println(numbers.size()); // 3 — duplicates collapse
System.out.println(numbers.contains(2)); // true
}
} let numbers: Set = [1, 2, 2, 3]
print(numbers.count) // 3
print(numbers.contains(2)) // true
let evens: Set = [2, 4]
print(numbers.intersection(evens)) Swift's
Set is a built-in collection like Java's, but it shares the array literal syntax, so you annotate the type (let numbers: Set = [...]) to choose a set over an array. It offers the same algebra — intersection, union, subtracting — as methods rather than Java's retainAll/addAll mutators, returning new sets.Control Flow
Conditionals
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");
}
}
} let score = 85
if score >= 90 {
print("A")
} else if score >= 80 {
print("B")
} else {
print("C")
} Swift conditionals drop the parentheses around the condition but require braces even for a single statement — there is no brace-less
if, which removes a class of bug. The condition must be a genuine Bool; Swift has no truthiness, so if someInt or if someOptional alone is a compile error (use != 0 or != nil).switch is exhaustive
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);
}
} let day = 6
let kind = switch day {
case 6, 7: "weekend"
default: "weekday"
}
print(kind) Both have switch expressions with no fall-through (Java since 14 with
->; Swift always). Swift's switch is exhaustive over its type, so a switch on an enum must cover every case or the compiler rejects it. Swift cases also match ranges (1...5), tuples, and bind associated values — far more than constant labels.for-in & ranges
class Main {
public static void main(String[] args) {
for (int index = 0; index < 3; index++) {
System.out.println(index);
}
for (String fruit : new String[]{"apple", "banana"}) {
System.out.println(fruit);
}
}
} for index in 0..<3 { // half-open range
print(index)
}
for fruit in ["apple", "banana"] {
print(fruit)
} Swift has no C-style counting
for; you iterate a range instead. 0..<3 is a half-open range (0, 1, 2) and 1...5 is closed (1 through 5). The enhanced-for over a collection looks like Java's for (x : items) with in instead of :. To get an index alongside the value, iterate array.enumerated().while & repeat-while
class Main {
public static void main(String[] args) {
int count = 0;
while (count < 3) {
count++;
}
do {
count++;
} while (count < 3);
System.out.println(count);
}
} var count = 0
while count < 3 {
count += 1
}
repeat {
count += 1
} while count < 3
print(count) Swift's
while matches Java's. The do-while loop is spelled repeat { ... } while in Swift — do was reserved for error handling (do/catch), so the bottom-tested loop got a different keyword. Note count += 1: Swift has compound assignment but deliberately removed the ++ and -- operators in Swift 3.Functions
Defining functions
class Main {
static String greet(String name) {
return "Hello, " + name;
}
public static void main(String[] args) {
System.out.println(greet("Swift"));
}
} func greet(name: String) -> String {
return "Hello, \(name)"
}
print(greet(name: "Swift")) A Swift
func declares its return type after ->, and a top-level function needs no enclosing class or static — functions are first-class and can live anywhere. The parameter type follows the name after a colon. Note the call site uses the parameter name (greet(name:)) — Swift's argument labels are part of a function's identity.Argument labels
class Main {
// Java parameter names are not part of the call.
static int move(int fromIndex, int toIndex) {
return toIndex - fromIndex;
}
public static void main(String[] args) {
System.out.println(move(2, 9));
}
} func move(from start: Int, to end: Int) -> Int {
return end - start
}
print(move(from: 2, to: 9)) // reads like a sentence
func add(_ first: Int, _ second: Int) -> Int { first + second }
print(add(2, 3)) // _ omits the label Swift functions have external argument labels distinct from internal parameter names:
move(from: 2, to: 9) reads like prose at the call site while the body uses start and end. An underscore (_ first: Int) suppresses the label for a positional call. Java parameter names are invisible to callers, so this expressiveness has no Java equivalent.Default parameters
class Main {
// Java has no default parameters; overload instead.
static String connect(String host, int port) {
return host + ":" + port;
}
static String connect(String host) {
return connect(host, 5432);
}
public static void main(String[] args) {
System.out.println(connect("localhost"));
}
} func connect(host: String, port: Int = 5432) -> String {
return "\(host):\(port)"
}
print(connect(host: "localhost"))
print(connect(host: "localhost", port: 8080)) Swift parameters can declare default values (
port: Int = 5432), so a single function covers what Java needs a family of overloads for. Callers omit any defaulted argument, and combined with argument labels this removes most of the telescoping-overload and builder-pattern boilerplate common in Java APIs.Returning multiple values
class Main {
record MinMax(int min, int max) {}
static MinMax minMax(int[] numbers) {
int low = numbers[0], high = numbers[0];
for (int value : numbers) {
low = Math.min(low, value);
high = Math.max(high, value);
}
return new MinMax(low, high);
}
public static void main(String[] args) {
var result = minMax(new int[]{3, 1, 4, 1, 5});
System.out.println(result.min() + " " + result.max());
}
} func minMax(_ numbers: [Int]) -> (min: Int, max: Int) {
return (numbers.min()!, numbers.max()!)
}
let result = minMax([3, 1, 4, 1, 5])
print(result.min, result.max) Swift returns multiple values as a labeled tuple —
(min: Int, max: Int) — with no named record or wrapper class to declare, the way Java now does with a record. The caller reads result.min by label or destructures with let (low, high) = .... The built-in min()/max() on the array return optionals, force-unwrapped here with !.Closures
Closures vs lambdas
import java.util.function.Function;
class Main {
public static void main(String[] args) {
Function<Integer, Integer> doubler = number -> number * 2;
System.out.println(doubler.apply(21));
}
} let doubler: (Int) -> Int = { number in number * 2 }
print(doubler(21))
let tripler = { (number: Int) in number * 3 }
print(tripler(7)) Swift closures are written
{ parameters in body } and have a plain function type (Int) -> Int — there is no Function/BiFunction/Supplier interface zoo as in Java, and you call a closure directly (doubler(21)) rather than through .apply(). Any function type is first-class, so closures, methods, and free functions are interchangeable.Trailing closure syntax
import java.util.List;
import java.util.stream.Collectors;
class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(5, 2, 8, 1);
var sorted = numbers.stream()
.sorted((left, right) -> right - left)
.collect(Collectors.toList());
System.out.println(sorted);
}
} let numbers = [5, 2, 8, 1]
let sorted = numbers.sorted { left, right in
left > right
}
print(sorted) When a closure is a function's last argument, Swift lets you write it after the parentheses (or omit empty parentheses entirely) —
numbers.sorted { ... }. This "trailing closure" syntax is why Swift APIs read so fluently and underpins SwiftUI's declarative blocks. Java lambdas always sit inside the call's argument list.Capturing variables
import java.util.function.Supplier;
class Main {
static Supplier<Integer> makeCounter() {
int[] count = {0}; // Java closures capture effectively-final values
return () -> ++count[0];
}
public static void main(String[] args) {
var counter = makeCounter();
System.out.println(counter.get() + " " + counter.get());
}
} func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
print(counter(), counter()) A Swift closure captures variables by reference and can freely mutate them, so the counter's
count survives and increments across calls. Java captures only effectively final variables, forcing the one-element-array trick to hold mutable state. Swift's capture is more direct, though you use a capture list ([weak self]) to avoid reference cycles when capturing objects.Structs & Classes
Structs are value types
class Main {
static class Point { int x; int y; }
public static void main(String[] args) {
Point first = new Point();
first.x = 3;
Point second = first; // reference — same object
second.x = 99;
System.out.println(first.x + " " + second.x); // 99 99
}
} struct Point {
var x: Int
var y: Int
}
var first = Point(x: 3, y: 0)
var second = first // COPY — an independent value
second.x = 99
print(first.x, second.x) // 3 99 This is a defining Swift difference. A
struct is a value type: assigning it copies the whole value, so mutating second leaves first untouched. Every Java object is a reference type, where second = first aliases one object. Swift favors structs for data because copies are independent, inherently thread-safe, and free of aliasing surprises.Classes are reference types
class Main {
static class Counter {
private int count = 0;
void increment() { count++; }
int value() { return count; }
}
public static void main(String[] args) {
var counter = new Counter();
counter.increment();
counter.increment();
System.out.println(counter.value());
}
} class Counter {
private var count = 0
func increment() { count += 1 }
var value: Int { count }
}
let counter = Counter()
counter.increment()
counter.increment()
print(counter.value) Swift
class instances are reference types like every Java object — multiple variables can share one instance. Notice let counter still allows calling increment(): for a class, let fixes the reference, not the object's mutable state. Swift instantiates by calling the type name (Counter()) with no new, and uses private/internal/public access control like Java.Memberwise initializer
class Main {
// A record gives a canonical constructor for free.
record Person(String name, int age) {}
public static void main(String[] args) {
var person = new Person("Alice", 30);
System.out.println(person.name() + " " + person.age());
}
} struct Person {
let name: String
let age: Int
}
let person = Person(name: "Alice", age: 30)
print(person.name, person.age) A Swift
struct gets a memberwise initializer for free — Person(name:age:) is synthesized from the stored properties, much like a Java record's canonical constructor. Unlike a record, a struct's properties can be var (mutable) and you can add methods freely without it ceasing to be a value type, so it covers far more than immutable data carriers.Mutating methods
class Main {
static class Counter {
int count = 0;
void increment() { count++; } // a class freely mutates fields
}
public static void main(String[] args) {
var counter = new Counter();
counter.increment();
System.out.println(counter.count);
}
} struct Counter {
var count = 0
mutating func increment() {
count += 1
}
}
var counter = Counter()
counter.increment()
print(counter.count) Because a struct is a value type, a method that changes its own properties must be marked
mutating — the keyword makes the mutation visible and is only callable on a var instance, never a let. Java classes mutate fields silently. This is Swift signalling, at the type level, exactly which operations alter a value versus merely read it.Properties
Computed properties
class Main {
static class Circle {
double radius;
Circle(double radius) { this.radius = radius; }
double area() { return Math.PI * radius * radius; }
}
public static void main(String[] args) {
System.out.println(new Circle(5).area());
}
} struct Circle {
let radius: Double
var area: Double {
Double.pi * radius * radius
}
}
let circle = Circle(radius: 5)
print(circle.area) A Swift computed property looks like a stored property to callers (
circle.area, no parentheses) but runs code each time it is read. Java models this as a getter method (area()). Computed properties can also have a setter, letting you expose a derived value that, when assigned, updates the underlying stored properties — a clean alternative to paired getter/setter methods.Property observers (didSet)
class Main {
static class Account {
private int balance;
void setBalance(int newBalance) {
this.balance = newBalance;
System.out.println("Balance changed to " + newBalance);
}
}
public static void main(String[] args) {
var account = new Account();
account.setBalance(100);
}
} struct Account {
var balance: Int = 0 {
didSet {
print("Balance changed to \(balance)")
}
}
}
var account = Account()
account.balance = 100 Swift's
willSet/didSet observers run code automatically whenever a stored property changes, without routing every access through a setter method as Java requires. Callers assign the property directly (account.balance = 100) and the observer fires — keeping the natural assignment syntax while still reacting to changes.Static & type properties
class Main {
static class Temperature {
static final double ABSOLUTE_ZERO = -273.15;
static double toKelvin(double celsius) {
return celsius - ABSOLUTE_ZERO;
}
}
public static void main(String[] args) {
System.out.println(Temperature.toKelvin(0));
}
} struct Temperature {
static let absoluteZero = -273.15
static func toKelvin(_ celsius: Double) -> Double {
celsius - absoluteZero
}
}
print(Temperature.toKelvin(0)) Swift's
static properties and methods belong to the type, like Java's static members, and are accessed through the type name (Temperature.toKelvin). A static let is also lazily initialized and thread-safe on first access with no extra effort, addressing the double-checked-locking ceremony Java sometimes needs for lazy static singletons.Protocols
Protocols vs interfaces
class Main {
interface Animal {
String name();
void speak();
}
record Dog(String name) implements Animal {
public void speak() { System.out.println(name + " says: Woof!"); }
}
public static void main(String[] args) {
new Dog("Rex").speak();
}
} protocol Animal {
var name: String { get }
func speak()
}
struct Dog: Animal {
let name: String
func speak() { print("\(name) says: Woof!") }
}
Dog(name: "Rex").speak() A Swift
protocol is close to a Java interface: a compile-time contract a type adopts after a colon. The big difference is that protocols can require properties (var name: String { get }), and — unlike Java interfaces — value types (structs and enums) adopt them freely. Swift leans on protocols as its primary abstraction, a style called "protocol-oriented programming."Default implementations
class Main {
interface Describable {
String name();
default String describe() { return "I am " + name(); }
}
record Cat(String name) implements Describable {}
public static void main(String[] args) {
System.out.println(new Cat("Whiskers").describe());
}
} protocol Describable {
var name: String { get }
}
extension Describable {
func describe() -> String { "I am \(name)" }
}
struct Cat: Describable {
let name: String
}
print(Cat(name: "Whiskers").describe()) Java interfaces gained
default methods in 8. Swift supplies defaults through a protocol extension — a separate extension block — which keeps the protocol's contract clean and lets you add behavior to a protocol you do not own. Conforming types inherit the implementation and may override it, and it works for structs and enums, not just classes.Multiple conformance
class Main {
interface Wheeled { int wheels(); }
interface Motorized { int engineCC(); }
record Car() implements Wheeled, Motorized {
public int wheels() { return 4; }
public int engineCC() { return 2000; }
}
public static void main(String[] args) {
var car = new Car();
System.out.println(car.wheels() + " " + car.engineCC());
}
} protocol Wheeled { var wheels: Int { get } }
protocol Motorized { var engineCC: Int { get } }
struct Car: Wheeled, Motorized {
let wheels = 4
let engineCC = 2000
}
let car = Car()
print(car.wheels, car.engineCC) Like Java, a Swift type can adopt several protocols by listing them with commas, composing capabilities from independent contracts instead of inheriting from one base class. Swift can also compose protocols on the fly with
some Wheeled & Motorized as a parameter type, requiring a value that satisfies both — a lightweight intersection Java expresses only with generic bounds.Programming to a protocol
import java.util.List;
class Main {
interface Shape { double area(); }
record Circle(double radius) implements Shape {
public double area() { return Math.PI * radius * radius; }
}
record Square(double side) implements Shape {
public double area() { return side * side; }
}
public static void main(String[] args) {
List<Shape> shapes = List.of(new Circle(2), new Square(3));
for (Shape shape : shapes) {
System.out.printf("%.2f%n", shape.area());
}
}
} import Foundation
protocol Shape { func area() -> Double }
struct Circle: Shape {
let radius: Double
func area() -> Double { Double.pi * radius * radius }
}
struct Square: Shape {
let side: Double
func area() -> Double { side * side }
}
let shapes: [Shape] = [Circle(radius: 2), Square(side: 3)]
for shape in shapes {
print(String(format: "%.2f", shape.area()))
} An array typed
[Shape] holds any conforming value and dispatches area() dynamically, exactly like a Java List<Shape>. The structs here are value types yet behave polymorphically through the protocol — Swift boxes them in an "existential" automatically. Note that String(format:) comes from Foundation, imported at the top — Swift's core library keeps C-style formatting out of the base language.Enums
Simple enums
class Main {
enum Direction { NORTH, SOUTH, EAST, WEST }
public static void main(String[] args) {
Direction heading = Direction.NORTH;
System.out.println(heading);
}
} enum Direction {
case north, south, east, west
}
let heading = Direction.north
print(heading) Both languages have true enum types whose cases are known at compile time, enabling exhaustive
switch. Swift cases are lowercase by convention and are written case north, south. When the type is known, you can abbreviate Direction.north to just .north — a leading-dot shorthand that appears throughout Swift wherever the expected type is unambiguous.Raw values
class Main {
enum Status {
ACTIVE(1), INACTIVE(0), PENDING(2);
final int code;
Status(int code) { this.code = code; }
}
public static void main(String[] args) {
System.out.println(Status.ACTIVE.code);
}
} enum Status: Int {
case active = 1
case inactive = 0
case pending = 2
}
print(Status.active.rawValue)
print(Status(rawValue: 1) == .active) // failable init A Swift enum can declare a raw backing type (
: Int, : String) and a literal per case, accessed via .rawValue — far less boilerplate than Java's field-plus-constructor pattern. The synthesized failable initializer Status(rawValue: 1) converts a raw value back to a case, returning an optional (nil if no case matches), which is ideal for decoding.Associated values
class Main {
// Java enums cannot carry per-case data of different shapes.
// A sealed interface is the closest equivalent.
sealed interface Outcome permits Success, Failure {}
record Success(int value) implements Outcome {}
record Failure(String message) implements Outcome {}
public static void main(String[] args) {
Outcome result = new Success(42);
if (result instanceof Success success) {
System.out.println("Success: " + success.value());
}
}
} enum Outcome {
case success(Int)
case failure(String)
}
let result = Outcome.success(42)
switch result {
case .success(let value): print("Success:", value)
case .failure(let message): print("Failure:", message)
} This is one of Swift's most powerful features. An enum case can carry its own associated data —
success(Int) versus failure(String) — making the enum a true algebraic data type. A Java enum cannot do this; the nearest equivalent is a sealed interface with record implementations. A Swift switch then binds the payload (let value) and is checked for exhaustiveness.Enum methods
class Main {
enum Planet {
EARTH(9.81), MARS(3.71);
final double gravity;
Planet(double gravity) { this.gravity = gravity; }
double weight(double mass) { return mass * gravity; }
}
public static void main(String[] args) {
System.out.println(Planet.EARTH.weight(10));
}
} enum Planet: Double {
case earth = 9.81
case mars = 3.71
func weight(mass: Double) -> Double {
mass * rawValue
}
}
print(Planet.earth.weight(mass: 10)) Like Java enums, Swift enums can have methods and computed properties, and they can also conform to protocols and have static members. The method here reaches the case's own
rawValue. Because Swift enums are value types with full method support and associated values, they replace many small class hierarchies a Java codebase would otherwise need.Error Handling
throws / try / catch
class Main {
static class MathException extends Exception {
MathException(String message) { super(message); }
}
static int divide(int numerator, int denominator) throws MathException {
if (denominator == 0) throw new MathException("division by zero");
return numerator / denominator;
}
public static void main(String[] args) {
try {
System.out.println(divide(10, 2));
System.out.println(divide(10, 0));
} catch (MathException error) {
System.out.println("Error: " + error.getMessage());
}
}
} enum MathError: Error { case divisionByZero }
func divide(_ numerator: Int, by denominator: Int) throws -> Int {
if denominator == 0 { throw MathError.divisionByZero }
return numerator / denominator
}
do {
print(try divide(10, by: 2))
print(try divide(10, by: 0))
} catch MathError.divisionByZero {
print("Error: division by zero")
} Swift's error handling resembles Java's try/catch, but errors are values conforming to the
Error protocol (an enum here, not a class hierarchy), and you mark each throwing call with try so propagation is visible at every site. The block is do { } catch { }, and catch can pattern-match specific cases. Unlike Java, Swift has no checked exceptions across the whole call graph — see the next note.No checked exceptions
class Main {
// Java forces every caller to declare or catch checked exceptions.
static void risky() throws Exception {
throw new Exception("boom");
}
static void caller() throws Exception { // must propagate
risky();
}
public static void main(String[] args) throws Exception {
try { caller(); } catch (Exception error) {
System.out.println(error.getMessage());
}
}
} struct MyError: Error { let message: String }
func risky() throws {
throw MyError(message: "boom")
}
func caller() throws { // marked throws, but no exception TYPE list
try risky()
}
do { try caller() } catch let error as MyError {
print(error.message)
} A Swift function is simply
throws or not — it never declares which error types it throws, so there is no checked-exception bookkeeping rippling through every signature. Any conforming Error can be thrown, and callers either try within their own throwing function or handle it. This removes the "declare-or-catch" friction Java programmers know well, while still forcing errors to be acknowledged with try.try? — error as optional
class Main {
static Integer parse(String text) {
try {
return Integer.parseInt(text);
} catch (NumberFormatException error) {
return null; // swallow and return null
}
}
public static void main(String[] args) {
System.out.println(parse("42"));
System.out.println(parse("oops"));
}
} enum ParseError: Error { case invalid }
func parse(_ text: String) throws -> Int {
guard let number = Int(text) else { throw ParseError.invalid }
return number
}
let good = try? parse("42") // Optional(42)
let bad = try? parse("oops") // nil
print(good ?? -1, bad ?? -1) try? turns a throwing call into an optional — the value on success, nil on any thrown error — which is exactly the Java idiom of wrapping a call in try/catch and returning null on failure, compressed to one keyword. Its sibling try! asserts the call cannot fail and crashes if it does; reserve it for cases where an error is logically impossible.Generics
Generic functions
class Main {
static <T extends Comparable<T>> T maxOf(T first, T second) {
return first.compareTo(second) >= 0 ? first : second;
}
public static void main(String[] args) {
System.out.println(maxOf(3, 7));
System.out.println(maxOf("apple", "banana"));
}
} func maxOf<T: Comparable>(_ first: T, _ second: T) -> T {
first > second ? first : second
}
print(maxOf(3, 7))
print(maxOf("apple", "banana")) Swift generics look much like Java's — a type parameter
<T> with a constraint — but the constraint is a protocol (T: Comparable) that enables the actual > operator, rather than Java's T extends Comparable<T> plus compareTo. Crucially Swift generics are reified: the concrete type is known at runtime and specialized, with none of Java's type erasure.Generic types
class Main {
static class Stack<T> {
private final java.util.List<T> items = new java.util.ArrayList<>();
void push(T item) { items.add(item); }
T pop() { return items.remove(items.size() - 1); }
boolean isEmpty() { return items.isEmpty(); }
}
public static void main(String[] args) {
var stack = new Stack<Integer>();
stack.push(1);
stack.push(2);
System.out.println(stack.pop());
}
} struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element { items.removeLast() }
var isEmpty: Bool { items.isEmpty }
}
var stack = Stack<Int>()
stack.push(1)
stack.push(2)
print(stack.pop()) A generic Swift type declares its parameter as
Stack<Element> and can be a value type — a generic struct — which Java cannot express, since Java generics are class-based and erased. Because the element type is reified, a Stack<Int> stores unboxed Int values, avoiding the boxing that Java's Stack<Integer> forces.Constraints with where
import java.util.List;
class Main {
static <T> boolean allEqual(List<T> items) {
for (T item : items) {
if (!item.equals(items.get(0))) return false;
}
return true;
}
public static void main(String[] args) {
System.out.println(allEqual(List.of(2, 2, 2)));
}
} func allEqual<T: Equatable>(_ items: [T]) -> Bool {
guard let first = items.first else { return true }
return items.allSatisfy { $0 == first }
}
print(allEqual([2, 2, 2])) Swift constrains a type parameter to a protocol (
T: Equatable gives the == operator), and for richer requirements a where clause can constrain associated types — for example where Element: Comparable, Element == Other.Element. This expresses relationships between multiple type parameters that Java's bounded wildcards (? extends) struggle to capture.Extensions
Extending existing types
class Main {
// Java cannot add methods to Integer; use a static helper.
static boolean isEven(int value) {
return value % 2 == 0;
}
public static void main(String[] args) {
System.out.println(isEven(4));
}
} extension Int {
var isEven: Bool { self % 2 == 0 }
}
print(4.isEven)
print(7.isEven) Swift
extensions add methods and computed properties to any type, including built-ins like Int and String and types you do not own. Java cannot do this — utilities live in static helper methods (isEven(value)) instead of reading as 4.isEven. Extensions cannot add stored properties, but otherwise let you grow an API where it naturally belongs.Organizing code with extensions
class Main {
static class Temperature {
double celsius;
Temperature(double celsius) { this.celsius = celsius; }
double fahrenheit() { return celsius * 9 / 5 + 32; }
double kelvin() { return celsius + 273.15; }
}
public static void main(String[] args) {
System.out.println(new Temperature(100).fahrenheit());
}
} struct Temperature {
let celsius: Double
}
extension Temperature {
var fahrenheit: Double { celsius * 9 / 5 + 32 }
var kelvin: Double { celsius + 273.15 }
}
print(Temperature(celsius: 100).fahrenheit) Beyond extending other people's types, Swift programmers use extensions to organize their own types — splitting a type's stored properties, its conformance to each protocol, and groups of related methods into separate
extension blocks (often across files). This keeps a large type readable in a way Java's single monolithic class body does not encourage.Retroactive conformance
class Main {
// Java cannot make an existing type implement a new interface.
interface Summable { int total(); }
// int[] can never be made to implement Summable retroactively.
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(new int[]{1, 2, 3}));
}
} protocol Summable {
func total() -> Int
}
extension Array where Element == Int {
func total() -> Int { reduce(0, +) }
}
extension Array: Summable where Element == Int {}
print([1, 2, 3].total()) An extension can make an existing type — even one from the standard library — conform to a new protocol after the fact, optionally constrained (here only arrays of
Int). Java has no equivalent: you cannot retrofit an interface onto a type you do not control. This "retroactive modeling" lets Swift adapt existing types to new abstractions without wrappers or adapters.