Skip to content

Classes

Classes#

A class is a blueprint that defines the structure and behavior of objects. It specifies what data objects will hold (attributes) and what operations they can perform (methods). Classes enable abstraction and code reusability by allowing you to define common structure and behavior once, then create multiple instances that share these characteristics.

Think of a class like a guitar factory blueprint—it defines the manufacturing process, materials, and specifications, but each guitar (object) that comes off the production line can have different finishes, colors, or customizations while maintaining the core design.

Class Structure:

graph TD
    A[Class Blueprint] --> B[Properties/Attributes]
    A --> C[Methods/Behaviors]
    A --> D[Constructor]

    B --> E[brand: String]
    B --> F[model: String]
    B --> G[numberOfStrings: int]

    C --> H["playChord()"]
    C --> I["tune()"]
    C --> J["changeStrings()"]

    D --> K[Initialize Object]

    style A fill:#e3f2fd

Class-Object Relationship:

graph LR
    A[Class] -->|Creates| B[Object Instance 1]
    A -->|Creates| C[Object Instance 2]
    A -->|Creates| D[Object Instance 3]

    style A fill:#e3f2fd
    style B fill:#c8e6c9
    style C fill:#c8e6c9
    style D fill:#c8e6c9

Let's explore how different programming languages implement classes:

public class Guitar {
    private String brand;
    private String model;

    public Guitar(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }
}
class Guitar(val brand: String, val model: String)
class Guitar {
    constructor(public brand: string, public model: string) {}
}
class Guitar {
    String brand;
    String model;

    Guitar(this.brand, this.model);
}
class Guitar {
    var brand: String
    var model: String

    init(brand: String, model: String) {
        self.brand = brand
        self.model = model
    }
}
class Guitar:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

Constructor Overloading#

Constructor overloading allows a class to have multiple constructors with different parameter lists, providing flexibility in object initialization.

public class Guitar {
    private String brand;
    private String model;

    public Guitar(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }

    public Guitar(String brand) {
        this.brand = brand;
        this.model = "Standard Model";
    }
}

Guitar guitar1 = new Guitar("Fender", "Stratocaster");
Guitar guitar2 = new Guitar("Gibson");
class Guitar {
    val brand: String
    val model: String

    constructor(brand: String, model: String) {
        this.brand = brand
        this.model = model
    }

    constructor(brand: String) {
        this.brand = brand
        this.model = "Standard Model"
    }
}

val guitar1 = Guitar("Fender", "Stratocaster")
val guitar2 = Guitar("Gibson")
class Guitar {
    brand: string;
    model: string;

    constructor(brand: string, model: string = "Standard Model") {
        this.brand = brand;
        this.model = model;
    }
}

const guitar1 = new Guitar("Fender", "Stratocaster");
const guitar2 = new Guitar("Gibson");
class Guitar {
    String brand;
    String model;

    Guitar(this.brand, this.model);
    Guitar.withDefaultModel(this.brand) : model = "Standard Model";
}

Guitar guitar1 = Guitar("Fender", "Stratocaster");
Guitar guitar2 = Guitar.withDefaultModel("Gibson");
class Guitar {
    var brand: String
    var model: String

    init(brand: String, model: String) {
        self.brand = brand
        self.model = model
    }

    convenience init(brand: String) {
        self.init(brand: brand, model: "Standard Model")
    }
}

let guitar1 = Guitar(brand: "Fender", model: "Stratocaster")
let guitar2 = Guitar(brand: "Gibson")
class Guitar:
    def __init__(self, brand, model="Standard Model"):
        self.brand = brand
        self.model = model

guitar1 = Guitar("Fender", "Stratocaster")
guitar2 = Guitar("Gibson")

Class Attributes#

Class attributes (also called instance variables or properties) store the data that defines the state of each object. They represent the characteristics that make each instance unique while sharing the same structure defined by the class.

Access Modifiers#

Access modifiers control where attributes can be accessed from, implementing the principle of encapsulation—one of the core OOP principles. They help protect data integrity and control how the class interacts with the outside world.

  • private: Accessible only within the same class. This is the most restrictive level and is commonly used for internal data that shouldn't be directly modified from outside the class.

  • public: Accessible from anywhere in the program. This is the default visibility in most languages and is used for data that needs to be freely accessible.

  • protected: Accessible within the class and by its subclasses (inheritance). This provides a middle ground for data that subclasses need access to but shouldn't be public.

Why Use Access Modifiers?#

  • Data Protection: Prevents external code from accidentally corrupting object state
  • Interface Control: Defines what parts of your class are meant for external use
  • Maintainability: Makes it easier to change internal implementation without breaking external code
  • Design Clarity: Makes the intended usage of class members explicit

To better illustrate class attributes (sometimes called instance variables or properties) and the use of access modifiers for encapsulation, here are equivalent examples for each language using the same features:

  • Private data members: The brand and model attributes are private (where supported) to encapsulate the data.
  • Public getters: Controlled access to private attributes via getter methods or properties.
  • Private helper method: A simple method (isValidBrand) to show an internal/helper method.
  • Constructor: Public or default constructor to initialize the attributes.
public class Guitar {
    // Private attributes - only accessible within this class
    private String brand;
    private String model;

    // Public constructor
    public Guitar(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }

    // Public getter methods
    public String getBrand() { return brand; }
    public String getModel() { return model; }

    // Private helper method
    private boolean isValidBrand(String brand) {
        return brand != null && !brand.isEmpty();
    }
}
class Guitar(private val brand: String, private val model: String) {
    // Public getters
    fun getBrand() = brand
    fun getModel() = model

    // Private helper method
    private fun isValidBrand(brand: String): Boolean =
        brand.isNotEmpty()
}
class Guitar {
    // Private attributes
    private brand: string;
    private model: string;

    // Public constructor
    constructor(brand: string, model: string) {
        this.brand = brand;
        this.model = model;
    }

    // Public getter methods
    getBrand(): string {
        return this.brand;
    }
    getModel(): string {
        return this.model;
    }

    // Private helper method
    private isValidBrand(brand: string): boolean {
        return brand !== null && brand !== "";
    }
}
class Guitar {
    // Private attributes (Dart uses _ to mark fields private to library)
    String _brand;
    String _model;

    // Public constructor
    Guitar(this._brand, this._model);

    // Public getter methods
    String get brand => _brand;
    String get model => _model;

    // Private helper method
    bool _isValidBrand(String brand) {
        return brand.isNotEmpty;
    }
}
class Guitar {
    // Private properties
    private var brand: String
    private var model: String

    // Public initializer
    init(brand: String, model: String) {
        self.brand = brand
        self.model = model
    }

    // Public getter computed properties
    var getBrand: String { return brand }
    var getModel: String { return model }

    // Private helper method
    private func isValidBrand(_ brand: String) -> Bool {
        return !brand.isEmpty
    }
}
class Guitar:
    def __init__(self, brand, model):
        self.__brand = brand  # double underscore for name mangling (pseudo-private)
        self.__model = model

    # Public getter methods
    @property
    def brand(self):
        return self.__brand

    @property
    def model(self):
        return self.__model

    # Private helper method
    def __is_valid_brand(self, brand):
        return isinstance(brand, str) and len(brand) > 0

Static Attributes#

Static attributes belong to the class itself, not to any instance. They're shared across all instances.

public class Guitar {
    static String instrumentType = "Electric Guitar";
    String brand;
    String model;

    Guitar(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }
}
class Guitar(val brand: String, val model: String) {
    companion object {
        const val instrumentType = "Electric Guitar"
    }
}
class Guitar {
    static instrumentType: string = "Electric Guitar";
    constructor(public brand: string, public model: string) {}
}
class Guitar {
    static String instrumentType = "Electric Guitar";
    String brand;
    String model;

    Guitar(this.brand, this.model);
}
class Guitar {
    static var instrumentType = "Electric Guitar"
    var brand: String
    var model: String

    init(brand: String, model: String) {
        self.brand = brand
        self.model = model
    }
}
class Guitar:
    instrument_type = "Electric Guitar"

    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

Class Methods#

Methods define the behavior of a class and can access instance attributes.

public class Guitar {
    String brand;
    String model;

    Guitar(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }

    public void play() {
        System.out.println("Playing the guitar!");
    }
}
class Guitar(val brand: String, val model: String) {
    fun play() {
        println("Playing the guitar!")
    }
}
class Guitar {
    constructor(public brand: string, public model: string) {}

    play(): void {
        console.log("Playing the guitar!");
    }
}
class Guitar {
    String brand;
    String model;

    Guitar(this.brand, this.model);

    void play() {
        print("Playing the guitar!");
    }
}
class Guitar {
    var brand: String
    var model: String

    init(brand: String, model: String) {
        self.brand = brand
        self.model = model
    }

    func play() {
        print("Playing the guitar!")
    }
}
class Guitar:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def play(self):
        print("Playing the guitar!")

Static Methods#

Static methods belong to the class itself and can be called without creating an instance. They're useful for utility functions and factory methods.

class GuitarUtils {
    static void printStandardTunings() {
        System.out.println("Standard guitar tunings: E A D G B e");
    }
}

GuitarUtils.printStandardTunings();
class GuitarUtils {
    companion object {
        fun printStandardTunings() {
            println("Standard guitar tunings: E A D G B e")
        }
    }
}

GuitarUtils.printStandardTunings()
class GuitarUtils {
    static printStandardTunings() {
        console.log("Standard guitar tunings: E A D G B e");
    }
}

GuitarUtils.printStandardTunings();
class GuitarUtils {
    static void printStandardTunings() {
        print("Standard guitar tunings: E A D G B e");
    }
}

GuitarUtils.printStandardTunings();
class GuitarUtils {
    static func printStandardTunings() {
        print("Standard guitar tunings: E A D G B e")
    }
}

GuitarUtils.printStandardTunings()
class GuitarUtils:
    @staticmethod
    def print_standard_tunings():
        print("Standard guitar tunings: E A D G B e")

GuitarUtils.print_standard_tunings()

String Representation#

String representation methods convert objects to human-readable strings for debugging and display.

For comprehensive details, see String Representation.

public class Guitar {
    private String brand;
    private String model;

    public Guitar(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }

    @Override
    public String toString() {
        return brand + " " + model;
    }
}

Guitar guitar = new Guitar("Fender", "Stratocaster");
System.out.println(guitar);  // Output: Fender Stratocaster
class Guitar(val brand: String, val model: String) {
    override fun toString(): String {
        return "$brand $model"
    }
}

val guitar = Guitar("Fender", "Stratocaster")
println(guitar)  // Output: Fender Stratocaster
class Guitar {
    brand: string;
    model: string;

    constructor(brand: string, model: string) {
        this.brand = brand;
        this.model = model;
    }

    toString(): string {
        return `${this.brand} ${this.model}`;
    }
}

const guitar = new Guitar("Fender", "Stratocaster");
console.log(guitar.toString());  // Output: Fender Stratocaster
class Guitar {
    String brand;
    String model;

    Guitar(this.brand, this.model);

    @override
    String toString() {
        return "$brand $model";
    }
}

Guitar guitar = Guitar("Fender", "Stratocaster");
print(guitar);  // Output: Fender Stratocaster
class Guitar {
    let brand: String
    let model: String

    init(brand: String, model: String) {
        self.brand = brand
        self.model = model
    }

    var description: String {
        return "\(brand) \(model)"
    }
}

let guitar = Guitar(brand: "Fender", model: "Stratocaster")
print(guitar)  // Output: Fender Stratocaster
class Guitar:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def __str__(self):
        return f"{self.brand} {self.model}"

guitar = Guitar("Fender", "Stratocaster")
print(guitar)  # Output: Fender Stratocaster

Type Checking#

Type checking allows you to determine if an object is an instance of a specific class or subclass.

Guitar guitar = new ElectricGuitar("Fender", "Stratocaster", "SSS");

if (guitar instanceof ElectricGuitar) {
    System.out.println("The guitar is an ElectricGuitar.");
}
val guitar: Guitar = ElectricGuitar("Fender", "Stratocaster", "HSS")

if (guitar is ElectricGuitar) {
    println("The guitar is an ElectricGuitar.")
}
const guitar: Guitar = new ElectricGuitar("Fender", "Stratocaster", "SSS");

if (guitar instanceof ElectricGuitar) {
    console.log("The guitar is an ElectricGuitar.");
}
var guitar = ElectricGuitar("Fender", "Stratocaster", "HSS");

if (guitar is ElectricGuitar) {
    print("The guitar is an ElectricGuitar.");
}
let guitar: Guitar = ElectricGuitar(brand: "Fender", model: "Stratocaster", pickupConfiguration: "SSS")

if guitar is ElectricGuitar {
    print("The guitar is an ElectricGuitar.")
}
guitar = ElectricGuitar("Fender", "Stratocaster", "HSS")

if isinstance(guitar, ElectricGuitar):
    print("The guitar is an ElectricGuitar.")