Sunday, April 12, 2026

SWIFT FOR JAVA DEVELOPERS



INTRODUCTION: BRIDGING TWO WORLDS


If you are reading this article, you are probably a Java developer who has heard about Swift and wants to explore what Apple’s modern programming language has to offer. Perhaps you are building an iOS or macOS application, or maybe you are just curious about how Swift compares to the language you know and love. Whatever your motivation, you are in the right place.


Swift and Java share many conceptual similarities. Both are object-oriented languages with strong type systems. Both support protocols or interfaces, generics, and modern programming paradigms. However, Swift brings some unique features to the table that might surprise you. Features like optionals, value types, protocol extensions, and a more concise syntax can make Swift feel both familiar and refreshingly different.


This tutorial will take you on a journey through Swift from a Java developer’s perspective. We will compare and contrast the two languages, highlighting what is similar and what is different. By the end of this guide, you will have a solid foundation in Swift and will be ready to write your own applications with confidence.


GETTING STARTED WITH SWIFT


Before we dive into code, you need to set up your Swift development environment. If you are on a Mac, you can download Xcode from the Mac App Store. Xcode includes everything you need: the Swift compiler, debugger, and an excellent IDE. If you are not on a Mac, you can use Swift on Linux or Windows, though the ecosystem is more mature on macOS.


For quick experimentation, Xcode provides Playgrounds, which are interactive environments where you can write Swift code and see results immediately. Think of them as a sophisticated REPL. For this tutorial, you can use a Playground or create a simple command-line Swift project.


THE BASICS: SYNTAX AND STRUCTURE


Let us start with the most fundamental difference you will notice: Swift has no semicolons required at the end of statements. Coming from Java, this might feel strange at first, but you will quickly appreciate the cleaner look.


In Java, you might write:



String greeting = "Hello, World";

System.out.println(greeting);



In Swift, the equivalent is:



let greeting = "Hello, World"

print(greeting)



Notice that we used “let” instead of a type declaration. Swift has powerful type inference, which means the compiler can figure out that greeting is a String without you explicitly stating it. However, you can still specify types when you want to be explicit.


Another immediate difference is how Swift handles its entry point. In Java, you need a main method inside a public class. In Swift, if you are writing a simple script or using a Playground, you can write code at the top level without any ceremony. For a full application, you would use the @main attribute or AppDelegate/App struct depending on whether you are building a command-line tool or a GUI application.


VARIABLES AND CONSTANTS: LET VERSUS VAR


One of Swift’s core philosophies is immutability by default. In Swift, you declare constants with “let” and variables with “var”. The language encourages you to use “let” whenever possible, making your code safer and more predictable.


In Java, you might write:



final String name = "Alice";

int age = 30;

age = 31;



In Swift:



let name = "Alice"

var age = 30

age = 31



The “let” keyword declares an immutable constant. Once you assign a value, you cannot change it. The “var” keyword declares a mutable variable. Swift’s compiler is smart enough to warn you if you declare something as “var” but never actually mutate it, suggesting you use “let” instead.


This might seem like a small change, but it has profound implications for how you write code. In Java, everything is mutable by default unless you explicitly use “final”. In Swift, you must consciously choose mutability by using “var”. This leads to fewer bugs and makes your intentions clearer.


TYPE SYSTEM: STRONG BUT FLEXIBLE


Swift has a strong, static type system, just like Java. However, Swift’s type inference makes the code feel more dynamic while maintaining all the safety of static typing. Let me show you some examples:



let integer = 42                    // Inferred as Int

let decimal = 3.14                  // Inferred as Double

let text = "Hello"                  // Inferred as String

let isValid = true                  // Inferred as Bool



You can also be explicit about types:



let integer: Int = 42

let decimal: Double = 3.14

let text: String = "Hello"

let isValid: Bool = true



Swift has several built-in types that correspond to Java types. Int is like Java’s int (though Swift’s Int is architecture-dependent). Double and Float are like their Java counterparts. String works similarly to Java’s String class. Bool is like Java’s boolean.


One significant difference is that Swift’s basic types are actually structs, not primitives. This means they have methods and properties, just like objects. You can write:



let number = 42

let doubled = number.advanced(by: number)



Swift does not have primitive types versus reference types in the same way Java does. Instead, it has value types and reference types, which we will explore in detail later.


THE ELEPHANT IN THE ROOM: OPTIONALS


If there is one feature that defines Swift and differentiates it most from Java, it is optionals. In Java, any reference type can be null, which is the source of countless NullPointerException errors. Swift takes a radically different approach.


In Swift, a variable cannot be nil unless you explicitly declare it as optional. An optional is a type that can either contain a value or be nil. You declare an optional by adding a question mark after the type:



let name: String = "Alice"          // Cannot be nil

let nickname: String? = nil         // Can be nil



If you try to assign nil to a non-optional variable, Swift will not compile. This catches entire categories of bugs at compile time rather than runtime.


Working with optionals requires unwrapping them to access the underlying value. Swift provides several ways to do this safely. The most straightforward is optional binding with if-let:



let possibleName: String? = "Bob"


if let actualName = possibleName {

    print("The name is \(actualName)")

} else {

    print("No name available")

}



This is roughly equivalent to Java’s pattern:



String possibleName = getPossibleName();

if (possibleName != null) {

    System.out.println("The name is " + possibleName);

} else {

    System.out.println("No name available");

}



But Swift’s approach is more explicit and type-safe. You can also use guard statements for early returns:



func greet(person: String?) {

    guard let name = person else {

        print("Cannot greet without a name")

        return

    }

    print("Hello, \(name)!")

}



Swift also provides the nil-coalescing operator for providing default values:



let nickname: String? = nil

let displayName = nickname ?? "Anonymous"



And optional chaining for safely accessing properties and methods on optional values:



let possiblePerson: Person? = getPerson()

let uppercaseName = possiblePerson?.name.uppercased()



This is far more elegant than the verbose null checks you would need in Java. Optional chaining returns nil if any part of the chain is nil, preventing crashes.


There is also force unwrapping with the exclamation mark, but this should be used sparingly:



let definitelyAString: String? = "Hello"

let unwrapped = definitelyAString!  // Force unwrap



Force unwrapping is similar to assuming a value is not null in Java. If you are wrong, your program will crash. Use it only when you are absolutely certain a value exists.


FUNCTIONS: MORE THAN JUST METHODS


Functions in Swift are first-class citizens and have some interesting characteristics that differ from Java methods. Let us start with a basic function:



func greet(name: String) -> String {

    return "Hello, \(name)!"

}


let greeting = greet(name: "Alice")



The syntax is different from Java. You use “func” instead of specifying a return type first, and the return type comes after an arrow. The parameter label “name” is part of the function signature, making calls more readable.


Swift functions support external and internal parameter names:



func greet(person name: String) -> String {

    return "Hello, \(name)!"

}


let greeting = greet(person: "Bob")



Here, “person” is the external name (used at the call site) and “name” is the internal name (used inside the function). This makes function calls read like natural language.


You can also make parameters optional by providing default values:



func greet(name: String, enthusiastically: Bool = false) -> String {

    let greeting = "Hello, \(name)"

    return enthusiastically ? greeting + "!!!" : greeting + "."

}


print(greet(name: "Charlie"))

print(greet(name: "Diana", enthusiastically: true))



Swift functions can return multiple values using tuples:



func getMinMax(numbers: [Int]) -> (min: Int, max: Int)? {

    guard let first = numbers.first else {

        return nil

    }

    var currentMin = first

    var currentMax = first

    for number in numbers {

        if number < currentMin {

            currentMin = number

        }

        if number > currentMax {

            currentMax = number

        }

    }

    return (currentMin, currentMax)

}


if let bounds = getMinMax(numbers: [3, 1, 4, 1, 5, 9, 2, 6]) {

    print("Min: \(bounds.min), Max: \(bounds.max)")

}



This is much cleaner than Java’s approach of creating a custom class just to return multiple values.


CONTROL FLOW: FAMILIAR YET DIFFERENT


Swift’s control flow statements will look familiar to Java developers, but with some nice enhancements. The basic if statement works as you would expect:



let temperature = 72


if temperature > 80 {

    print("It's hot!")

} else if temperature > 60 {

    print("It's nice.")

} else {

    print("It's cold.")

}



Notice that parentheses around the condition are optional. Braces, however, are always required, even for single-line statements. This prevents the infamous “goto fail” bug.


Swift’s switch statement is much more powerful than Java’s. It does not fall through by default, supports pattern matching, and can switch on any type:



let character = "a"


switch character {

case "a":

    print("The first letter")

case "z":

    print("The last letter")

default:

    print("Some other letter")

}



You can switch on ranges:



let score = 85


switch score {

case 90...100:

    print("Grade: A")

case 80..<90:

    print("Grade: B")

case 70..<80:

    print("Grade: C")

default:

    print("Grade: F")

}



And you can use where clauses for additional conditions:



let point = (x: 1, y: 1)


switch point {

case (0, 0):

    print("Origin")

case (let x, 0):

    print("On x-axis at \(x)")

case (0, let y):

    print("On y-axis at \(y)")

case (let x, let y) where x == y:

    print("On diagonal at (\(x), \(y))")

default:

    print("Somewhere else")

}



Loops in Swift are similar to Java but with cleaner syntax. The for-in loop is the primary way to iterate:



let names = ["Alice", "Bob", "Charlie"]


for name in names {

    print("Hello, \(name)")

}


for i in 0..<5 {

    print("Iteration \(i)")

}



Swift also has while and repeat-while loops (equivalent to Java’s while and do-while):



var count = 0

while count < 5 {

    print(count)

    count += 1

}


repeat {

    print("This executes at least once")

} while false



CLASSES: THE OBJECT-ORIENTED FOUNDATION


Classes in Swift work similarly to Java classes, but with some syntactic differences. Let us create a simple class:



class Person {

    var name: String

    var age: Int

    

    init(name: String, age: Int) {

        self.name = name

        self.age = age

    }

    

    func introduce() {

        print("Hi, I'm \(name) and I'm \(age) years old.")

    }

}


let alice = Person(name: "Alice", age: 30)

alice.introduce()



Several things to note here. Swift uses “init” instead of a constructor with the class name. You use “self” instead of “this”. Properties must be initialized either with a default value or in the initializer.


Swift supports inheritance with the same single-inheritance model as Java:



class Student: Person {

    var studentId: String

    

    init(name: String, age: Int, studentId: String) {

        self.studentId = studentId

        super.init(name: name, age: age)

    }

    

    override func introduce() {

        super.introduce()

        print("My student ID is \(studentId).")

    }

}



You use “override” to explicitly mark methods that override parent methods. This prevents accidental overrides and makes your intentions clear.


Swift classes are reference types, just like Java objects. When you assign a class instance to a new variable, both variables reference the same object:



let person1 = Person(name: "Bob", age: 25)

let person2 = person1

person2.age = 26

print(person1.age)  // Prints 26



STRUCTS: THE VALUE TYPE ALTERNATIVE


Here is where Swift really diverges from Java. Swift has structs, which are similar to classes but are value types instead of reference types. When you assign a struct to a new variable, it creates a copy:



struct Point {

    var x: Double

    var y: Double

    

    func distanceFromOrigin() -> Double {

        return (x * x + y * y).squareRoot()

    }

}


var point1 = Point(x: 3, y: 4)

var point2 = point1

point2.x = 10

print(point1.x)  // Still prints 3



Structs get a free memberwise initializer, so we did not need to define init. Structs cannot inherit from other structs or classes, but they can adopt protocols (more on that later).


When should you use a class versus a struct? Apple’s general guidance is to prefer structs unless you need reference semantics, inheritance, or the ability to deinitialize resources. Many of Swift’s built-in types, including String, Array, and Dictionary, are actually structs.


Structs can have computed properties:



struct Rectangle {

    var width: Double

    var height: Double

    

    var area: Double {

        return width * height

    }

    

    var perimeter: Double {

        return 2 * (width + height)

    }

}


let rect = Rectangle(width: 5, height: 3)

print("Area: \(rect.area)")

print("Perimeter: \(rect.perimeter)")



You can also have property observers that run code when a property changes:



struct StepCounter {

    var totalSteps: Int = 0 {

        willSet {

            print("About to set totalSteps to \(newValue)")

        }

        didSet {

            print("Added \(totalSteps - oldValue) steps")

        }

    }

}



PROTOCOLS: SWIFT’S INTERFACES


Protocols in Swift are similar to Java interfaces, but with some additional capabilities. They define a blueprint of methods, properties, and other requirements:



protocol Identifiable {

    var id: String { get }

    func displayInfo()

}


class User: Identifiable {

    let id: String

    let username: String

    

    init(id: String, username: String) {

        self.id = id

        self.username = username

    }

    

    func displayInfo() {

        print("User: \(username) (ID: \(id))")

    }

}



Both classes and structs can adopt protocols. You use a colon to indicate protocol adoption, just like inheritance:



struct Product: Identifiable {

    let id: String

    let name: String

    

    func displayInfo() {

        print("Product: \(name) (ID: \(id))")

    }

}



Protocols can have optional requirements and can be extended with default implementations. This is similar to Java’s default methods in interfaces but more powerful:



protocol Greetable {

    var name: String { get }

    func greet()

}


extension Greetable {

    func greet() {

        print("Hello, I'm \(name)")

    }

}


struct Employee: Greetable {

    let name: String

}


let emp = Employee(name: "Sarah")

emp.greet()  // Uses the default implementation



This feature enables protocol-oriented programming, a paradigm that Swift strongly encourages. You can compose behavior by adopting multiple protocols, creating flexible and reusable code.


ENUMS: MORE THAN JUST CONSTANTS


If you think Swift enums are like Java enums, prepare to be amazed. Swift enums can have associated values, making them incredibly powerful for modeling complex data:



enum Result {

    case success(String)

    case failure(Error)

}


enum NetworkError: Error {

    case timeout

    case noConnection

    case serverError(code: Int)

}


func fetchData() -> Result {

    let success = Bool.random()

    if success {

        return .success("Data loaded successfully")

    } else {

        return .failure(NetworkError.noConnection)

    }

}


let result = fetchData()


switch result {

case .success(let message):

    print("Success: \(message)")

case .failure(let error):

    print("Error: \(error)")

}



Notice how each case can carry associated data. This makes enums perfect for representing states, results, or any situation where you have a fixed set of possibilities with varying data.


Enums can also have raw values like Java enums:



enum Direction: String {

    case north = "N"

    case south = "S"

    case east = "E"

    case west = "W"

}


let dir = Direction.north

print(dir.rawValue)  // Prints "N"



And they can have methods and computed properties:



enum Compass {

    case north, south, east, west

    

    func opposite() -> Compass {

        switch self {

        case .north:

            return .south

        case .south:

            return .north

        case .east:

            return .west

        case .west:

            return .east

        }

    }

}



ERROR HANDLING: DO, TRY, CATCH


Swift has a sophisticated error handling model that is cleaner than Java’s checked exceptions. Functions that can throw errors are marked with “throws”:



enum FileError: Error {

    case notFound

    case permissionDenied

    case invalidFormat

}


func readFile(named filename: String) throws -> String {

    guard filename.hasSuffix(".txt") else {

        throw FileError.invalidFormat

    }

    

    if filename == "missing.txt" {

        throw FileError.notFound

    }

    

    return "File contents here"

}



To call a throwing function, you use try within a do-catch block:



do {

    let contents = try readFile(named: "data.txt")

    print(contents)

} catch FileError.notFound {

    print("File not found")

} catch FileError.invalidFormat {

    print("Invalid file format")

} catch {

    print("Unknown error: \(error)")

}



You can also use try? to convert the result to an optional:



if let contents = try? readFile(named: "data.txt") {

    print(contents)

} else {

    print("Could not read file")

}



Or try! if you are certain the call will not throw:



let contents = try! readFile(named: "data.txt")



Unlike Java, Swift does not have checked exceptions in the method signature. You do not need to list every possible error type, giving you more flexibility while still maintaining explicit error handling.


COLLECTIONS: ARRAYS, DICTIONARIES, AND SETS


Swift’s collection types are similar to Java’s but with value semantics. Arrays are ordered collections:



var numbers: [Int] = [1, 2, 3, 4, 5]

numbers.append(6)

numbers.insert(0, at: 0)

numbers.remove(at: 1)


for number in numbers {

    print(number)

}


let doubled = numbers.map { $0 * 2 }

let evens = numbers.filter { $0 % 2 == 0 }

let sum = numbers.reduce(0, +)



Dictionaries are key-value pairs:



var ages: [String: Int] = ["Alice": 30, "Bob": 25]

ages["Charlie"] = 35


if let aliceAge = ages["Alice"] {

    print("Alice is \(aliceAge) years old")

}


for (name, age) in ages {

    print("\(name) is \(age) years old")

}



Sets are unordered collections of unique values:



var uniqueNumbers: Set<Int> = [1, 2, 3, 4, 5]

uniqueNumbers.insert(3)  // Already exists, no effect

uniqueNumbers.insert(6)


let isPresent = uniqueNumbers.contains(4)


let set1: Set = [1, 2, 3, 4]

let set2: Set = [3, 4, 5, 6]

let intersection = set1.intersection(set2)

let union = set1.union(set2)



All Swift collections are value types, meaning they are copied on assignment. However, Swift optimizes this with copy-on-write, so copies are cheap until you actually modify them.


CLOSURES: SWIFT’S LAMBDAS


Closures in Swift are similar to Java lambdas but with a more flexible syntax. A closure is a self-contained block of functionality:



let numbers = [1, 2, 3, 4, 5]


let doubled = numbers.map({ (number: Int) -> Int in

    return number * 2

})



Swift allows you to simplify this syntax in several ways. If the types can be inferred:



let doubled = numbers.map({ number in

    return number * 2

})



If the closure is a single expression, the return is implicit:



let doubled = numbers.map({ number in number * 2 })



If the closure is the last argument, you can use trailing closure syntax:



let doubled = numbers.map { number in number * 2 }



And Swift provides shorthand argument names:



let doubled = numbers.map { $0 * 2 }



This progression from verbose to concise is one of Swift’s design principles. You can be explicit when clarity matters and concise when the intent is obvious.


Closures capture values from their surrounding context:



func makeIncrementer(increment: Int) -> () -> Int {

    var total = 0

    let incrementer: () -> Int = {

        total += increment

        return total

    }

    return incrementer

}


let incrementByTwo = makeIncrementer(increment: 2)

print(incrementByTwo())  // 2

print(incrementByTwo())  // 4

print(incrementByTwo())  // 6



EXTENSIONS: ADDING FUNCTIONALITY TO EXISTING TYPES


Extensions allow you to add functionality to existing types, even types you do not own. This is more powerful than Java’s extension methods:



extension String {

    func withPrefix(_ prefix: String) -> String {

        return "\(prefix)\(self)"

    }

    

    var isValidEmail: Bool {

        return self.contains("@") && self.contains(".")

    }

}


let greeting = "World".withPrefix("Hello, ")

print(greeting)


let email = "test@example.com"

if email.isValidEmail {

    print("Valid email")

}



You can extend your own types, built-in types, and even types from frameworks. Extensions can add methods, computed properties, and protocol conformances:



extension Int {

    func squared() -> Int {

        return self * self

    }

    

    var isEven: Bool {

        return self % 2 == 0

    }

}


print(5.squared())

print(4.isEven)



Extensions are a key part of protocol-oriented programming, allowing you to provide default implementations for protocol methods.


MEMORY MANAGEMENT: AUTOMATIC REFERENCE COUNTING


Swift uses Automatic Reference Counting (ARC) instead of garbage collection. This is similar to Java’s garbage collection in that you do not manually allocate and deallocate memory, but the mechanisms are different.


In most cases, ARC just works and you do not need to think about it. However, you need to be aware of strong reference cycles, which occur when two objects hold strong references to each other:



class Department {

    let name: String

    var manager: Employee?

    

    init(name: String) {

        self.name = name

    }

}


class Employee {

    let name: String

    weak var department: Department?

    

    init(name: String) {

        self.name = name

    }

}



The “weak” keyword breaks the reference cycle by creating a weak reference that does not increase the reference count. When the referenced object is deallocated, the weak reference automatically becomes nil.


There is also “unowned” for cases where you know the reference will never be nil during its lifetime:



class Customer {

    let name: String

    var card: CreditCard?

    

    init(name: String) {

        self.name = name

    }

}


class CreditCard {

    let number: String

    unowned let customer: Customer

    

    init(number: String, customer: Customer) {

        self.number = number

        self.customer = customer

    }

}



For closures that capture self, you need to be careful about reference cycles. Use capture lists to specify weak or unowned captures:



class NetworkManager {

    var data: String = ""

    

    func fetchData(completion: @escaping () -> Void) {

        DispatchQueue.global().async { [weak self] in

            guard let self = self else { return }

            self.data = "Downloaded data"

            completion()

        }

    }

}



GENERICS: TYPE-SAFE FLEXIBILITY


Swift’s generics work similarly to Java’s, allowing you to write flexible, reusable functions and types:



func swap<T>(_ a: inout T, _ b: inout T) {

    let temp = a

    a = b

    b = temp

}


var x = 5

var y = 10

swap(&x, &y)

print("x: \(x), y: \(y)")



The “inout” keyword allows you to modify parameters, similar to passing by reference. You use an ampersand when calling the function.


You can create generic types:



struct Stack<Element> {

    private var items: [Element] = []

    

    mutating func push(_ item: Element) {

        items.append(item)

    }

    

    mutating func pop() -> Element? {

        return items.isEmpty ? nil : items.removeLast()

    }

    

    func peek() -> Element? {

        return items.last

    }

    

    var isEmpty: Bool {

        return items.isEmpty

    }

}


var intStack = Stack<Int>()

intStack.push(1)

intStack.push(2)

print(intStack.pop() ?? 0)



You can constrain generic types using protocols:



func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {

    for (index, value) in array.enumerated() {

        if value == valueToFind {

            return index

        }

    }

    return nil

}



PUTTING IT ALL TOGETHER: A COMPLETE RUNNING EXAMPLE


Now that we have covered the fundamentals of Swift, let us build a complete, production-ready application that demonstrates these concepts in action. We will create a Library Management System that handles books, members, lending operations, and provides a clean interface for managing a library.


This system will demonstrate classes, structs, protocols, enums, error handling, collections, optionals, and other Swift features we have discussed. The code follows clean architecture principles with clear separation of concerns.



// MARK: - Core Domain Models


/// Represents a unique identifier for any entity in the system

struct EntityID: Hashable, CustomStringConvertible {

    let value: String

    

    init() {

        self.value = UUID().uuidString

    }

    

    init(value: String) {

        self.value = value

    }

    

    var description: String {

        return value

    }

}


/// Represents the genre of a book

enum BookGenre: String, CaseIterable {

    case fiction

    case nonFiction

    case science

    case history

    case biography

    case mystery

    case romance

    case fantasy

    case technology

    

    var displayName: String {

        switch self {

        case .nonFiction:

            return "Non-Fiction"

        default:

            return self.rawValue.capitalized

        }

    }

}


/// Represents the condition of a book

enum BookCondition: String, Comparable {

    case excellent

    case good

    case fair

    case poor

    

    static func < (lhs: BookCondition, rhs: BookCondition) -> Bool {

        let order: [BookCondition] = [.poor, .fair, .good, .excellent]

        guard let lhsIndex = order.firstIndex(of: lhs),

              let rhsIndex = order.firstIndex(of: rhs) else {

            return false

        }

        return lhsIndex < rhsIndex

    }

}


/// Errors that can occur in the library system

enum LibraryError: Error, CustomStringConvertible {

    case bookNotFound

    case memberNotFound

    case bookAlreadyBorrowed

    case bookNotBorrowed

    case memberHasOverdueBooks

    case memberBorrowLimitReached

    case invalidOperation(String)

    

    var description: String {

        switch self {

        case .bookNotFound:

            return "The requested book was not found in the library"

        case .memberNotFound:

            return "The requested member was not found in the system"

        case .bookAlreadyBorrowed:

            return "This book is currently borrowed by another member"

        case .bookNotBorrowed:

            return "This book is not currently borrowed"

        case .memberHasOverdueBooks:

            return "Member has overdue books and cannot borrow more"

        case .memberBorrowLimitReached:

            return "Member has reached the maximum borrowing limit"

        case .invalidOperation(let message):

            return "Invalid operation: \(message)"

        }

    }

}


/// Protocol for entities that can be identified

protocol Identifiable {

    var id: EntityID { get }

}


/// Protocol for entities that can provide a detailed description

protocol Describable {

    func detailedDescription() -> String

}


/// Represents a book in the library

struct Book: Identifiable, Describable, Hashable {

    let id: EntityID

    let title: String

    let author: String

    let isbn: String

    let genre: BookGenre

    let publicationYear: Int

    var condition: BookCondition

    var isAvailable: Bool

    var currentBorrowerId: EntityID?

    

    init(title: String, author: String, isbn: String, genre: BookGenre, 

         publicationYear: Int, condition: BookCondition = .good) {

        self.id = EntityID()

        self.title = title

        self.author = author

        self.isbn = isbn

        self.genre = genre

        self.publicationYear = publicationYear

        self.condition = condition

        self.isAvailable = true

        self.currentBorrowerId = nil

    }

    

    func detailedDescription() -> String {

        let status = isAvailable ? "Available" : "Borrowed"

        return """

        Book Details:

          Title: \(title)

          Author: \(author)

          ISBN: \(isbn)

          Genre: \(genre.displayName)

          Publication Year: \(publicationYear)

          Condition: \(condition.rawValue.capitalized)

          Status: \(status)

        """

    }

    

    static func == (lhs: Book, rhs: Book) -> Bool {

        return lhs.id == rhs.id

    }

    

    func hash(into hasher: inout Hasher) {

        hasher.combine(id)

    }

}


/// Represents a library member

struct Member: Identifiable, Describable, Hashable {

    let id: EntityID

    let name: String

    let email: String

    let phoneNumber: String

    var borrowedBooks: Set<EntityID>

    let maxBorrowLimit: Int

    var membershipDate: Date

    

    init(name: String, email: String, phoneNumber: String, 

         maxBorrowLimit: Int = 5) {

        self.id = EntityID()

        self.name = name

        self.email = email

        self.phoneNumber = phoneNumber

        self.borrowedBooks = []

        self.maxBorrowLimit = maxBorrowLimit

        self.membershipDate = Date()

    }

    

    var canBorrowMore: Bool {

        return borrowedBooks.count < maxBorrowLimit

    }

    

    func detailedDescription() -> String {

        return """

        Member Details:

          Name: \(name)

          Email: \(email)

          Phone: \(phoneNumber)

          Books Borrowed: \(borrowedBooks.count)/\(maxBorrowLimit)

          Member Since: \(formatDate(membershipDate))

        """

    }

    

    private func formatDate(_ date: Date) -> String {

        let formatter = DateFormatter()

        formatter.dateStyle = .medium

        return formatter.string(from: date)

    }

    

    static func == (lhs: Member, rhs: Member) -> Bool {

        return lhs.id == rhs.id

    }

    

    func hash(into hasher: inout Hasher) {

        hasher.combine(id)

    }

}


/// Represents a borrowing transaction

struct BorrowingRecord: Identifiable {

    let id: EntityID

    let bookId: EntityID

    let memberId: EntityID

    let borrowDate: Date

    var returnDate: Date?

    let dueDate: Date

    

    init(bookId: EntityID, memberId: EntityID, borrowDate: Date = Date(), 

         lendingPeriodDays: Int = 14) {

        self.id = EntityID()

        self.bookId = bookId

        self.memberId = memberId

        self.borrowDate = borrowDate

        self.returnDate = nil

        self.dueDate = Calendar.current.date(

            byAdding: .day, 

            value: lendingPeriodDays, 

            to: borrowDate

        ) ?? borrowDate

    }

    

    var isOverdue: Bool {

        guard returnDate == nil else { return false }

        return Date() > dueDate

    }

    

    var daysOverdue: Int {

        guard isOverdue else { return 0 }

        let calendar = Calendar.current

        let components = calendar.dateComponents(

            [.day], 

            from: dueDate, 

            to: Date()

        )

        return components.day ?? 0

    }

    

    func detailedDescription() -> String {

        let formatter = DateFormatter()

        formatter.dateStyle = .medium

        

        var description = """

        Borrowing Record:

          Borrow Date: \(formatter.string(from: borrowDate))

          Due Date: \(formatter.string(from: dueDate))

        """

        

        if let returnDate = returnDate {

            description += "\n  Return Date: \(formatter.string(from: returnDate))"

        } else {

            description += "\n  Status: Currently Borrowed"

            if isOverdue {

                description += " (OVERDUE by \(daysOverdue) days)"

            }

        }

        

        return description

    }

}


// MARK: - Repository Protocol and Implementation


/// Protocol defining repository operations

protocol Repository {

    associatedtype Entity: Identifiable

    

    func add(_ entity: Entity) throws

    func remove(id: EntityID) throws

    func find(id: EntityID) -> Entity?

    func findAll() -> [Entity]

    func update(_ entity: Entity) throws

}


/// Generic in-memory repository implementation

class InMemoryRepository<T: Identifiable>: Repository {

    typealias Entity = T

    

    private var storage: [EntityID: T] = [:]

    

    func add(_ entity: T) throws {

        storage[entity.id] = entity

    }

    

    func remove(id: EntityID) throws {

        guard storage[id] != nil else {

            throw LibraryError.invalidOperation("Entity not found")

        }

        storage.removeValue(forKey: id)

    }

    

    func find(id: EntityID) -> T? {

        return storage[id]

    }

    

    func findAll() -> [T] {

        return Array(storage.values)

    }

    

    func update(_ entity: T) throws {

        guard storage[entity.id] != nil else {

            throw LibraryError.invalidOperation("Entity not found")

        }

        storage[entity.id] = entity

    }

    

    func clear() {

        storage.removeAll()

    }

    

    var count: Int {

        return storage.count

    }

}


// MARK: - Library Service


/// Main library service that manages all operations

class LibraryService {

    private let bookRepository: InMemoryRepository<Book>

    private let memberRepository: InMemoryRepository<Member>

    private let borrowingRepository: InMemoryRepository<BorrowingRecord>

    

    init() {

        self.bookRepository = InMemoryRepository<Book>()

        self.memberRepository = InMemoryRepository<Member>()

        self.borrowingRepository = InMemoryRepository<BorrowingRecord>()

    }

    

    // MARK: - Book Management

    

    func addBook(_ book: Book) throws {

        try bookRepository.add(book)

    }

    

    func removeBook(id: EntityID) throws {

        guard let book = bookRepository.find(id: id) else {

            throw LibraryError.bookNotFound

        }

        

        if !book.isAvailable {

            throw LibraryError.invalidOperation(

                "Cannot remove a borrowed book"

            )

        }

        

        try bookRepository.remove(id: id)

    }

    

    func findBook(id: EntityID) -> Book? {

        return bookRepository.find(id: id)

    }

    

    func findBooks(by criteria: (Book) -> Bool) -> [Book] {

        return bookRepository.findAll().filter(criteria)

    }

    

    func getAllBooks() -> [Book] {

        return bookRepository.findAll()

    }

    

    func searchBooks(byTitle title: String) -> [Book] {

        let lowercasedTitle = title.lowercased()

        return findBooks { book in

            book.title.lowercased().contains(lowercasedTitle)

        }

    }

    

    func searchBooks(byAuthor author: String) -> [Book] {

        let lowercasedAuthor = author.lowercased()

        return findBooks { book in

            book.author.lowercased().contains(lowercasedAuthor)

        }

    }

    

    func searchBooks(byGenre genre: BookGenre) -> [Book] {

        return findBooks { $0.genre == genre }

    }

    

    func getAvailableBooks() -> [Book] {

        return findBooks { $0.isAvailable }

    }

    

    // MARK: - Member Management

    

    func registerMember(_ member: Member) throws {

        try memberRepository.add(member)

    }

    

    func removeMember(id: EntityID) throws {

        guard var member = memberRepository.find(id: id) else {

            throw LibraryError.memberNotFound

        }

        

        if !member.borrowedBooks.isEmpty {

            throw LibraryError.invalidOperation(

                "Cannot remove member with borrowed books"

            )

        }

        

        try memberRepository.remove(id: id)

    }

    

    func findMember(id: EntityID) -> Member? {

        return memberRepository.find(id: id)

    }

    

    func getAllMembers() -> [Member] {

        return memberRepository.findAll()

    }

    

    func searchMembers(byName name: String) -> [Member] {

        let lowercasedName = name.lowercased()

        return memberRepository.findAll().filter { member in

            member.name.lowercased().contains(lowercasedName)

        }

    }

    

    // MARK: - Borrowing Operations

    

    func borrowBook(bookId: EntityID, memberId: EntityID) throws {

        guard var book = bookRepository.find(id: bookId) else {

            throw LibraryError.bookNotFound

        }

        

        guard var member = memberRepository.find(id: memberId) else {

            throw LibraryError.memberNotFound

        }

        

        guard book.isAvailable else {

            throw LibraryError.bookAlreadyBorrowed

        }

        

        guard member.canBorrowMore else {

            throw LibraryError.memberBorrowLimitReached

        }

        

        if hasOverdueBooks(memberId: memberId) {

            throw LibraryError.memberHasOverdueBooks

        }

        

        let record = BorrowingRecord(bookId: bookId, memberId: memberId)

        try borrowingRepository.add(record)

        

        book.isAvailable = false

        book.currentBorrowerId = memberId

        try bookRepository.update(book)

        

        member.borrowedBooks.insert(bookId)

        try memberRepository.update(member)

    }

    

    func returnBook(bookId: EntityID) throws {

        guard var book = bookRepository.find(id: bookId) else {

            throw LibraryError.bookNotFound

        }

        

        guard !book.isAvailable else {

            throw LibraryError.bookNotBorrowed

        }

        

        guard let borrowerId = book.currentBorrowerId else {

            throw LibraryError.invalidOperation("No borrower recorded")

        }

        

        guard var member = memberRepository.find(id: borrowerId) else {

            throw LibraryError.memberNotFound

        }

        

        let activeRecords = borrowingRepository.findAll().filter { record in

            record.bookId == bookId && record.returnDate == nil

        }

        

        guard var record = activeRecords.first else {

            throw LibraryError.invalidOperation(

                "No active borrowing record found"

            )

        }

        

        record.returnDate = Date()

        try borrowingRepository.update(record)

        

        book.isAvailable = true

        book.currentBorrowerId = nil

        try bookRepository.update(book)

        

        member.borrowedBooks.remove(bookId)

        try memberRepository.update(member)

    }

    

    func getBorrowingHistory(bookId: EntityID) -> [BorrowingRecord] {

        return borrowingRepository.findAll().filter { 

            $0.bookId == bookId 

        }.sorted { $0.borrowDate > $1.borrowDate }

    }

    

    func getMemberBorrowingHistory(memberId: EntityID) -> [BorrowingRecord] {

        return borrowingRepository.findAll().filter { 

            $0.memberId == memberId 

        }.sorted { $0.borrowDate > $1.borrowDate }

    }

    

    func getActiveBorrowings(memberId: EntityID) -> [BorrowingRecord] {

        return borrowingRepository.findAll().filter { record in

            record.memberId == memberId && record.returnDate == nil

        }

    }

    

    func hasOverdueBooks(memberId: EntityID) -> Bool {

        let activeRecords = getActiveBorrowings(memberId: memberId)

        return activeRecords.contains { $0.isOverdue }

    }

    

    func getOverdueRecords() -> [BorrowingRecord] {

        return borrowingRepository.findAll().filter { $0.isOverdue }

    }

    

    // MARK: - Statistics

    

    func getStatistics() -> LibraryStatistics {

        let totalBooks = bookRepository.count

        let availableBooks = getAvailableBooks().count

        let borrowedBooks = totalBooks - availableBooks

        let totalMembers = memberRepository.count

        let activeRecords = borrowingRepository.findAll().filter { 

            $0.returnDate == nil 

        }

        let overdueRecords = getOverdueRecords()

        

        return LibraryStatistics(

            totalBooks: totalBooks,

            availableBooks: availableBooks,

            borrowedBooks: borrowedBooks,

            totalMembers: totalMembers,

            activeBorrowings: activeRecords.count,

            overdueBooks: overdueRecords.count

        )

    }

}


/// Statistics about the library

struct LibraryStatistics: CustomStringConvertible {

    let totalBooks: Int

    let availableBooks: Int

    let borrowedBooks: Int

    let totalMembers: Int

    let activeBorrowings: Int

    let overdueBooks: Int

    

    var description: String {

        return """

        Library Statistics:

          Total Books: \(totalBooks)

          Available: \(availableBooks)

          Borrowed: \(borrowedBooks)

          Total Members: \(totalMembers)

          Active Borrowings: \(activeBorrowings)

          Overdue Books: \(overdueBooks)

        """

    }

}


// MARK: - Demo Usage


func demonstrateLibrarySystem() {

    print("=== Library Management System Demo ===\n")

    

    let library = LibraryService()

    

    // Create some books

    let book1 = Book(

        title: "The Swift Programming Language",

        author: "Apple Inc.",

        isbn: "978-0-13-468599-1",

        genre: .technology,

        publicationYear: 2024,

        condition: .excellent

    )

    

    let book2 = Book(

        title: "Clean Architecture",

        author: "Robert C. Martin",

        isbn: "978-0-13-449416-6",

        genre: .technology,

        publicationYear: 2017,

        condition: .good

    )

    

    let book3 = Book(

        title: "The Pragmatic Programmer",

        author: "Andrew Hunt and David Thomas",

        isbn: "978-0-13-595705-9",

        genre: .technology,

        publicationYear: 2019,

        condition: .excellent

    )

    

    // Add books to library

    do {

        try library.addBook(book1)

        try library.addBook(book2)

        try library.addBook(book3)

        print("Successfully added \(library.getAllBooks().count) books\n")

    } catch {

        print("Error adding books: \(error)\n")

    }

    

    // Register members

    let member1 = Member(

        name: "Alice Johnson",

        email: "alice@example.com",

        phoneNumber: "555-0101"

    )

    

    let member2 = Member(

        name: "Bob Smith",

        email: "bob@example.com",

        phoneNumber: "555-0102"

    )

    

    do {

        try library.registerMember(member1)

        try library.registerMember(member2)

        print("Successfully registered \(library.getAllMembers().count) members\n")

    } catch {

        print("Error registering members: \(error)\n")

    }

    

    // Demonstrate borrowing

    print("Alice borrows 'The Swift Programming Language':")

    do {

        try library.borrowBook(bookId: book1.id, memberId: member1.id)

        print("Success!\n")

        

        if let updatedBook = library.findBook(id: book1.id) {

            print(updatedBook.detailedDescription())

            print()

        }

    } catch {

        print("Error: \(error)\n")

    }

    

    // Try to borrow the same book again

    print("Bob tries to borrow the same book:")

    do {

        try library.borrowBook(bookId: book1.id, memberId: member2.id)

        print("Success!\n")

    } catch {

        print("Error: \(error)\n")

    }

    

    // Bob borrows a different book

    print("Bob borrows 'Clean Architecture':")

    do {

        try library.borrowBook(bookId: book2.id, memberId: member2.id)

        print("Success!\n")

    } catch {

        print("Error: \(error)\n")

    }

    

    // Search for books by author

    print("Searching for books by 'Martin':")

    let martinBooks = library.searchBooks(byAuthor: "Martin")

    for book in martinBooks {

        print("  - \(book.title) by \(book.author)")

    }

    print()

    

    // Get available books

    print("Available books:")

    let availableBooks = library.getAvailableBooks()

    for book in availableBooks {

        print("  - \(book.title)")

    }

    print()

    

    // Return a book

    print("Alice returns 'The Swift Programming Language':")

    do {

        try library.returnBook(bookId: book1.id)

        print("Success!\n")

        

        if let member = library.findMember(id: member1.id) {

            print(member.detailedDescription())

            print()

        }

    } catch {

        print("Error: \(error)\n")

    }

    

    // Show statistics

    let stats = library.getStatistics()

    print(stats.description)

    print()

    

    // Show borrowing history

    print("Bob's borrowing history:")

    let bobHistory = library.getMemberBorrowingHistory(memberId: member2.id)

    for record in bobHistory {

        print(record.detailedDescription())

        print()

    }

}


// Run the demonstration

demonstrateLibrarySystem()



This complete example demonstrates all the key concepts we covered in this tutorial. The Library Management System is production-ready code that handles real-world scenarios including error handling, state management, search functionality, and comprehensive statistics.


The code follows clean architecture principles with clear separation between domain models, repository layer, and service layer. Every function has a single responsibility, making the code maintainable and testable. The use of protocols allows for future extensibility, such as swapping the in-memory repository for a persistent database.


Notice how Swift’s features make the code safer and more expressive than equivalent Java code would be. Optionals prevent null pointer errors. Structs with value semantics prevent unintended mutations. Enums with associated values model complex states cleanly. Error handling is explicit and type-safe.


CONCLUSION: YOUR JOURNEY WITH SWIFT


You have now completed a comprehensive tour of Swift from a Java developer’s perspective. We covered the fundamental differences in syntax and structure, explored Swift’s unique features like optionals and value types, and built a complete application demonstrating these concepts in practice.


Swift is a modern language that learns from decades of programming language evolution. It combines the safety and performance of compiled languages with the expressiveness typically associated with dynamic languages. Features like type inference, optionals, powerful enums, and protocol-oriented programming make Swift code safer, more concise, and more maintainable than traditional object-oriented approaches.


As you continue your Swift journey, remember that the best way to learn is by building real applications. Start with small projects and gradually increase complexity. Explore Apple’s frameworks like SwiftUI for user interfaces, Combine for reactive programming, and Swift Concurrency for asynchronous code.


The Swift community is vibrant and welcoming. Take advantage of resources like the official Swift documentation, Swift forums, and open-source Swift projects on GitHub. Apple’s WWDC videos are excellent for learning advanced topics and best practices.


Most importantly, embrace Swift’s philosophy of safety and clarity. Use immutability by default with let. Leverage the type system to catch errors at compile time. Write expressive, self-documenting code that clearly communicates intent. These practices will make you not just a Swift developer, but a better programmer overall.


Welcome to the Swift community. Happy coding!​​​​​​​​​​​​​​​​