3

In Java\Kotlin we have a String class that is final and immutable.

I tried to mark the class with final keyword but looks like it's not allowable.

So, I'm a little bit confusing, how to declare final class in Dart ?

Note: the case is - I want to instantiate this class outside, but forbid to extending it. So using the private constructor - it's not my case.

Sergey Shustikov
  • 15,377
  • 12
  • 67
  • 119

3 Answers3

0

You can achieve this final effect from java by having a private constructor for your class, it will prevent the class from being extended, BUT it will also prevent the class from being instantiated (only in the same file both will be possible):

class MyString {
    MyString._(); // use _ for private constructor.
  
    static void print(String s) {
      print(s);
    }
}

Call with

String message = "Hello World";
MyString.print(message);

Dart considers that we are all adults, preventing class extension is hence part of the design and responsability of the developers to have clear class names, and not part of the language:

AVOID extending a class that isn’t intended to be subclassed.
If a constructor is changed from a generative constructor to a factory constructor, any subclass constructor calling that constructor will break. Also, if a class changes which of its own methods it invokes on this, that may break subclasses that override those methods and expect them to be called at certain points.


Difference of meaning for final with Java

Dart has a very simple definition of what is final: a variable in dart can only be set once, id est: is immutable.

Final and const If you never intend to change a variable, use final or const, either instead of var or in addition to a type.
A final variable can be set only once; a const variable is a compile-time constant. (Const variables are implicitly final.) A final top-level or class variable is initialized the first time it’s used.

Antonin GAVREL
  • 9,682
  • 8
  • 54
  • 81
0

Additionally to the approach of making the constructor private and instantiating your object via a static factory, you could use the package meta and annotate your final class as sealed:

  @sealed
  class Z{}

This will signal users of your package that this class should not be extended or implemented. For example in vscode trying to extend the class Z:

  class Z1 extends Z{}

results in the following warning:

The class 'Z' shouldn't be extended, mixed in, 
or implemented because it is sealed.
Try composing instead of inheriting, or refer 
to its documentation for more information.dart(subtype_of_sealed_class)

The issue will also be picked up by the dart analyzer:

$ dart analyze
Analyzing test...                      0.8s

 info • lib/src/test_base.dart:3:1 • 
 The class 'Z' shouldn't be extended, mixed in, or implemented because it
 is sealed. Try composing instead of inheriting, or refer to its 
 documentation for more information. • subtype_of_sealed_class
Dan R
  • 93
  • 6
0

You can use the factory unnamed constructor along with private named constructor, like this:

class NonExtendable {
  NonExtendable._singleGenerativeConstructor();

  // NonExtendable();

  factory NonExtendable() {
    return NonExtendable._singleGenerativeConstructor();
  }

  @override
  String toString(){
    return '$runtimeType is like final';
  }
}

In a client code, in the same library, or another library, an instance can be created, an example:

  // Create an instance of NonExtendable
  print ('${NonExtendable()}');

Trying to extend it, something like

class ExtendsNonExtendableInSameLibrary extends NonExtendable {

  ExtendsNonExtendableInSameLibrary._singleGenerativeConstructor() : super._singleGenerativeConstructor();

  factory ExtendsNonExtendableInSameLibrary() {
    return ExtendsNonExtendableInSameLibrary._singleGenerativeConstructor();
  }
}

will work in the same library (same 'source file') but not in another library, making the class NonExtendable same as 'final' in Java from the perspective of any client code.

mzimmermann
  • 1,002
  • 2
  • 9
  • 14