Swift
Java
toString
Swift programming
code conversion

Swift equivalent of Java toString

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In Java, every class inherits toString() from Object, which returns a string representation of the instance. In Swift, the equivalent is the description property provided by the CustomStringConvertible protocol. Conforming to this protocol lets you control what print(), string interpolation, and debugger output show for your types. Swift also provides CustomDebugStringConvertible for debug-specific representations and String(describing:) as a universal conversion function.

Java toString vs Swift description

java
1// Java
2public class Person {
3    private String name;
4    private int age;
5
6    public Person(String name, int age) {
7        this.name = name;
8        this.age = age;
9    }
10
11    @Override
12    public String toString() {
13        return "Person(name=" + name + ", age=" + age + ")";
14    }
15}
16
17System.out.println(new Person("Alice", 30));
18// Person(name=Alice, age=30)
swift
1// Swift equivalent
2struct Person: CustomStringConvertible {
3    let name: String
4    let age: Int
5
6    var description: String {
7        return "Person(name=\(name), age=\(age))"
8    }
9}
10
11print(Person(name: "Alice", age: 30))
12// Person(name=Alice, age=30)

In Java, toString() is inherited from Object — every class has it. In Swift, CustomStringConvertible is opt-in. Without it, print() uses a default representation that shows the type name and stored properties.

Default Behavior Without CustomStringConvertible

swift
1struct Point {
2    let x: Double
3    let y: Double
4}
5
6let p = Point(x: 3.0, y: 4.0)
7print(p)
8// Point(x: 3.0, y: 4.0)  — Swift generates a default description for structs
9
10// String interpolation also uses the default
11let msg = "Location: \(p)"
12print(msg)
13// Location: Point(x: 3.0, y: 4.0)

Swift structs get a reasonable default output. Classes print their type and memory address without CustomStringConvertible.

CustomDebugStringConvertible

swift
1struct Matrix: CustomStringConvertible, CustomDebugStringConvertible {
2    let rows: Int
3    let cols: Int
4    let data: [[Double]]
5
6    // Used by print() and string interpolation
7    var description: String {
8        return "Matrix(\(rows)x\(cols))"
9    }
10
11    // Used by debugPrint() and the debugger (po command)
12    var debugDescription: String {
13        let content = data.map { row in
14            row.map { String(format: "%.2f", $0) }.joined(separator: ", ")
15        }.joined(separator: "\n  ")
16        return "Matrix(\(rows)x\(cols)) [\n  \(content)\n]"
17    }
18}
19
20let m = Matrix(rows: 2, cols: 2, data: [[1, 2], [3, 4]])
21print(m)
22// Matrix(2x2)
23
24debugPrint(m)
25// Matrix(2x2) [
26//   1.00, 2.00
27//   3.00, 4.00
28// ]

Use CustomStringConvertible for user-facing output and CustomDebugStringConvertible for developer-facing debug output. This mirrors Java's toString() (user) vs debugger views.

String Conversion Methods

swift
1// String(describing:) — uses description if available
2let p = Person(name: "Alice", age: 30)
3let s = String(describing: p)
4// "Person(name=Alice, age=30)"
5
6// String(reflecting:) — uses debugDescription if available
7let d = String(reflecting: p)
8
9// String interpolation — calls description automatically
10let msg = "User: \(p)"
11
12// Converting numbers and primitives
13let n = 42
14String(n)           // "42"
15String(3.14)        // "3.14"
16String(true)        // "true"
17
18// Formatting numbers
19String(format: "%.2f", 3.14159)   // "3.14"
20String(format: "%05d", 42)        // "00042"

Enums and CustomStringConvertible

swift
1enum Direction: CustomStringConvertible {
2    case north, south, east, west
3
4    var description: String {
5        switch self {
6        case .north: return "North"
7        case .south: return "South"
8        case .east:  return "East"
9        case .west:  return "West"
10        }
11    }
12}
13
14print(Direction.north)  // "North"
15
16// Without CustomStringConvertible, enums print their case name
17enum Color { case red, green, blue }
18print(Color.red)  // "red"

Using with Collections

swift
1struct Task: CustomStringConvertible {
2    let title: String
3    let done: Bool
4
5    var description: String {
6        return "\(done ? "✓" : "○") \(title)"
7    }
8}
9
10let tasks = [
11    Task(title: "Write code", done: true),
12    Task(title: "Write tests", done: false),
13    Task(title: "Deploy", done: false)
14]
15
16print(tasks)
17// [✓ Write code, ○ Write tests, ○ Deploy]
18
19// Arrays call description on each element when printing

Common Pitfalls

  • Forgetting that CustomStringConvertible is opt-in: Unlike Java where every class inherits toString(), Swift requires explicit protocol conformance. Without it, classes show their type and memory address (e.g., MyClass 0x600000123abc). Structs get a better default showing stored property values.
  • Implementing description instead of debugDescription for debugging: print() uses description. The LLDB po command and debugPrint() use debugDescription. If you only implement description, debugPrint() falls back to it, but if you want richer output in the debugger, implement CustomDebugStringConvertible separately.
  • Using String(describing:) on optionals: String(describing: someOptional) produces "Optional(value)" or "nil", not just the unwrapped value. Unwrap first or use someOptional.map { String(describing: $0) } ?? "nil".
  • Expensive computation in description: The description property is called every time the object is printed, logged, or interpolated into a string. Avoid heavy computation, database queries, or network calls in this property — keep it simple and fast.
  • Confusing String() initializer with String(describing:): String(42) works for types that conform to LosslessStringConvertible (Int, Double, Bool). For custom types, use String(describing: myObject) or conform to CustomStringConvertible.

Summary

  • Swift's CustomStringConvertible protocol with description is the equivalent of Java's toString()
  • CustomDebugStringConvertible with debugDescription provides a separate debug representation
  • Swift structs get a default description showing stored properties; classes show type and address
  • Use String(describing:) for explicit conversion, string interpolation calls description automatically
  • Unlike Java, CustomStringConvertible is opt-in — you must explicitly conform to the protocol

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.