Skip to main content

Menu

System onlineAI Tutorials / 2026

AI Tutorials
Knowledge search

Recent · Popular · Tutorials

  • dart3 tutorials
  • java1 tutorial
  • Javascript3 tutorials
  • Node.js Introduction2 tutorials
  • dartdart · General · In depth
  • JavascriptJavascript · Advanced · In depth
  • JavascriptJavascript · Advanced · Mid
  • JavascriptJavascript · Advanced · Short

Find a tutorial

Tell the assistant what you want to learn. It suggests published chapters.

For example: “I want to learn Rust ownership from the start.” The conversation is kept only in this tab.

0 / 2,000 characters

Enter to send, Shift+Enter for a new line. Do not include personal information.

Chapter 9

Introducing Classes and Objects

So far in this tutorial, you have worked with simple values: numbers, strings, lists. That works fine for small programs. But real programs juggle many pieces of information that belong together. A music player, for example, tracks a song's title, artist, duration, and play count — and it needs to d
Level: GeneralLength: In depth4 min read

Read at another depth

Introducing Classes and Objects

So far in this tutorial, you have worked with simple values: numbers, strings, lists. That works fine for small programs. But real programs juggle many pieces of information that belong together. A music player, for example, tracks a song's title, artist, duration, and play count — and it needs to do things like play and pause.

A class is a blueprint for bundling related data and behavior together. An object is one concrete thing built from that blueprint. This chapter explains what that means, why it matters, and how to build your first classes in Dart.

Why Bundle Data and Behavior?

Imagine tracking songs with loose variables:

Code example in dart
String title1 = 'No Surprises';
String artist1 = 'Radiohead';
int plays1 = 42;

Two songs means numbered variables. A hundred songs means chaos. Worse, nothing stops you from mixing things up — setting plays1 to a string, or forgetting which title goes with which artist.

A class solves this by defining one idea — "a song has a title, an artist, and a play count, and you can record a play" — once. Then you create as many song objects as you like, and each one keeps its own data safely inside.

Your First Class

Here is a complete, runnable program:

Code example in dart
class Song {
  String title;
  String artist;
  int plays = 0;

  Song(this.title, this.artist);

  void play() {
    plays++;
    print('Now playing "$title" by $artist (play #$plays)');
  }
}

void main() {
  final first = Song('No Surprises', 'Radiohead');
  final second = Song('Bags', 'Clairo');

  first.play();
  first.play();
  second.play();
}

Run it and you will see:

Code example in text
Now playing "No Surprises" by Radiohead (play #1)
Now playing "No Surprises" by Radiohead (play #2)
Now playing "Bags" by Clairo (play #1)

Let's take this apart piece by piece.

Fields: The Data

Lines like String title; inside the class declare fields (also called properties). Each field holds one piece of data. Every object created from the class gets its own copy — first and second each have their own title and plays.

The initializer int plays = 0; gives a starting value, so every new song begins with zero plays.

Constructors: Building an Object

This line is a constructor:

Code example in dart
Song(this.title, this.artist);

A constructor is a special function that runs when you create an object. Song('No Surprises', 'Radiohead') calls it. The shorthand this.title means "the field named title on this object." Dart lets you write Song(this.title, this.artist) as a compact way of saying: take two parameters and store them in the fields with the same names. Without the shorthand, you would write Song(String title, String artist) { this.title = title; this.artist = artist; } — same result, more typing.

Methods: The Behavior

play() is a method: a function that belongs to the class. Inside a method, plays++ refers to the field on the current object — whichever song you called it on. first.play() bumps first's counter; second.play() bumps second's.

Objects and References

One detail trips up many learners: a variable holds a reference to an object, not the object itself. That means two variables can point at the same object.

Code example in dart
void main() {
  final a = Song('Bags', 'Clairo');
  final b = a; // b and a refer to the SAME song

  b.play(); // changes the object both variables share
  print(a.plays); // prints 1, not 0
}

If you wanted b to be a separate copy, you would construct a new Song with the same values instead of assigning.

Private Fields and Control

By default, anyone using your class can change any field:

Code example in dart
final song = Song('Bags', 'Clairo');
song.plays = -500; // nothing stops this

Sometimes that is fine. Other times you want rules — a play count should never go negative, for instance. Dart's rule for privacy is simple: names starting with an underscore (_) are private to the file (technically, to the library) they are declared in. Here is a version that protects the play count:

Code example in dart
class Counter {
  int _count = 0; // private field

  int get count => _count; // read-only view

  void increment() => _count++;
  void reset() => _count = 0;
}

void main() {
  final c = Counter();
  c.increment();
  c.increment();
  print(c.count); // 2
  // c._count = 99; // not allowed outside this file
}

The get count line is a getter: it looks like a field from the outside (c.count, no parentheses) but runs code underneath. This gives you a dial you can turn in one place later — for example, logging every read — without changing the code that uses the class.

Multiple Constructors and Named Parameters

Classes often need more than one way to be built. Dart supports named parameters, written in curly braces, which make constructor calls self-explanatory:

Code example in dart
class Song {
  final String title;
  final String artist;
  final int plays;

  Song({required this.title, required this.artist, this.plays = 0});

  @override
  String toString() => '"$title" by $artist ($plays plays)';
}

void main() {
  final s = Song(title: 'Dreams', artist: 'Fleetwood Mac');
  print(s);
}

Two new things appear here:

  • final marks fields that never change after construction. If a song's title cannot change, say so — the compiler will then enforce it.
  • required means the caller must supply the value; this.plays = 0 gives a default for anything not marked required.

The @override String toString() line customizes how the object prints. toString is inherited from every Dart object's shared ancestor; overriding it (and marking the override with @override) is the idiomatic way to make print(s) show something useful.

How It Fits Together

A diagram showing a class box named Song with fields title, artist, and plays and a play method, with arrows to two object boxes, each holding its own copy of the values.
100%
One class blueprint, two independent objects.
Show diagram source
classDiagram
    class Song {
        +String title
        +String artist
        +int plays
        +play()
    }
    Song <|-- first : object
    Song <|-- second : object
    class first {
        title = "No Surprises"
        plays = 2
    }
    class second {
        title = "Bags"
        plays = 1
    }

The class exists once; each object carries its own data and shares the methods.

Where You Will Take This Next

You now know how to define a class, construct objects, protect data with privacy and getters, and print objects nicely. The next chapter goes deeper into constructors and members — including initializer lists and factory constructors — and after that you will learn how classes relate to each other through inheritance. Objects are the backbone of almost everything in Dart, including Flutter, so the time you spend here pays off everywhere.

Exercise

You write final b = a; where a is a Song object, then call b.play(). Does a.plays change, and why?

Show answer

Yes. b and a refer to the same object, so any method called through either variable affects that one shared object.

Exercise

Why does a field named _count behave differently from one named count?

Show answer

The leading underscore makes the field private to the library (in practice, the file) where it is declared, so outside code cannot read or write it directly. A public getter can still expose a read-only view.

Exercise

Write a Timer class with a private seconds field (starting at 0), a public getter, and an add(int s) method that only accepts positive values.

Show answer

class Timer { int seconds = 0; int get seconds => seconds; void add(int s) { if (s > 0) _seconds += s; } }

Spotted a mistake, or code that does not work?

Report an issue

Chapter: Introducing Classes and Objects

0 / 2,000 characters

Plain text, for example: the second code sample does not compile. Do not include personal information.

You can't do that