Pular para o conteúdo principal

Construtores

Tudo sobre o uso de construtores em Dart.

Construtores são funções especiais que criam instâncias de classes.

O Dart implementa muitos tipos de construtores. Exceto pelos construtores padrão, essas funções usam o mesmo nome de sua classe.

Construtores generativos

Cria novas instâncias e inicializa variáveis de instância.

Construtores padrão

Usado para criar uma nova instância quando um construtor não foi especificado. Ele não aceita argumentos e não é nomeado.

Construtores nomeados

Esclarece o propósito de um construtor ou permite a criação de múltiplos construtores para a mesma classe.

Construtores constantes

Cria instâncias como constantes de tempo de compilação.

Construtores factory

Cria uma nova instância de um subtipo ou retorna uma instância existente do cache.

Construtor de redirecionamento

Encaminha chamadas para outro construtor na mesma classe.

Tipos de construtores

#

Construtores generativos

#

Para instanciar uma classe, use um construtor generativo.

dart
class Point {
  // Instance variables to hold the coordinates of the point.
  double x;
  double y;
​
  // Generative constructor with initializing formal parameters:
  Point(this.x, this.y);
}

Construtores padrão

#

Se você não declarar um construtor, o Dart usa o construtor padrão. O construtor padrão é um construtor generativo sem argumentos ou nome.

Construtores nomeados

#

Use um construtor nomeado para implementar múltiplos construtores para uma classe ou para fornecer clareza extra:

dart
const double xOrigin = 0;
const double yOrigin = 0;
​
class Point {
  final double x;
  final double y;
​
  // Sets the x and y instance variables
  // before the constructor body runs.
  Point(this.x, this.y);
​
  // Named constructor
  Point.origin() : x = xOrigin, y = yOrigin;
}

Uma subclasse não herda o construtor nomeado de uma superclasse. Para criar uma subclasse com um construtor nomeado definido na superclasse, implemente esse construtor na subclasse.

Construtores constantes

#

Se sua classe produz objetos imutáveis, torne esses objetos constantes de tempo de compilação. Para tornar objetos constantes de tempo de compilação, defina um construtor const com todas as variáveis de instância definidas como final.

dart
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);
​
  final double x, y;
​
  const ImmutablePoint(this.x, this.y);
}

Construtores constantes nem sempre criam constantes. Eles podem ser invocados em um contexto não-const. Para saber mais, consulte a seção sobre usando construtores.

Construtores de redirecionamento

#

Um construtor pode redirecionar para outro construtor na mesma classe. Um construtor de redirecionamento tem um corpo vazio. O construtor usa this em vez do nome da classe após dois pontos (:).

dart
class Point {
  double x, y;
​
  // The main constructor for this class.
  Point(this.x, this.y);
​
  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

Construtores factory

#

Ao encontrar um dos seguintes dois casos de implementação de um construtor, use a keyword factory:

  • O construtor nem sempre cria uma nova instância de sua classe. Embora um construtor factory não possa retornar null, ele pode retornar:

    • uma instância existente de um cache em vez de criar uma nova
    • uma nova instância de um subtipo
  • Você precisa executar trabalho não trivial antes de construir uma instância. Isso pode incluir verificar argumentos ou fazer qualquer outro processamento que não pode ser tratado na lista de inicializadores.

O exemplo a seguir inclui dois construtores factory.

  • O construtor factory Logger retorna objetos de um cache.
  • O construtor factory Logger.fromJson inicializa uma variável final de um objeto JSON.
dart
class Logger {
  final String name;
  bool mute = false;
​
  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache = <String, Logger>{};
​
  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }
​
  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }
​
  Logger._internal(this.name);
​
  void log(String msg) {
    if (!mute) print(msg);
  }
}

Use um construtor factory como qualquer outro construtor:

dart
var logger = Logger('UI');
logger.log('Button clicked');
​
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);

Construtores factory de redirecionamento

#

Um construtor factory de redirecionamento especifica uma chamada para um construtor de outra classe para usar sempre que alguém fizer uma chamada para o construtor de redirecionamento.

dart
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable

Como construtores factory comuns também podem criar e retornar instâncias de outras classes, os factories de redirecionamento parecem desnecessários. No entanto, construtores factory de redirecionamento têm várias vantagens:

  • Uma classe abstract pode fornecer um construtor constante que usa o construtor constante de outra classe.
  • Um construtor factory de redirecionamento evita a necessidade de forwarders para repetir os parâmetros formais e seus valores padrão.

Tear-offs de construtor

#

O Dart permite que você forneça um construtor como parâmetro sem chamá-lo. Chamado de tear-off (como você arranca os parênteses) serve como um closure que invoca o construtor com os mesmos parâmetros.

Se o tear-off é um construtor com a mesma assinatura e tipo de retorno que o método aceita, você pode usar o tear-off como parâmetro ou variável.

Tear-offs diferem de lambdas ou funções anônimas. Lambdas servem como um wrapper para o construtor, enquanto um tear-off é o construtor.

Use Tear-Offs

bom dart
// Use a tear-off for a named constructor:
var strings = charCodes.map(String.fromCharCode);
​
// Use a tear-off for an unnamed constructor:
var buffers = charCodes.map(StringBuffer.new);

Não Lambdas

ruim dart
// Instead of a lambda for a named constructor:
var strings = charCodes.map((code) => String.fromCharCode(code));
​
// Instead of a lambda for an unnamed constructor:
var buffers = charCodes.map((code) => StringBuffer(code));

Para mais discussão, assista a este vídeo Decoding Flutter sobre tear-offs.

Assistir no YouTube em uma nova aba: "Dart Tear-offs | Decoding Flutter"

Sintaxe concisa de construtores

#

A partir do Dart 3.13, você pode omitir o nome da classe ao declarar um construtor generativo ou factory dentro do corpo da classe, usando diretamente o modificador new ou factory:

  • new ou new nomeado
  • factory ou factory nomeado

Diferentemente dos construtores nomeados tradicionais (como Point.origin()), construtores nomeados concisos não usam ponto entre a palavra-chave (modificador) e o nome.

dart
class Point {
  double x, y;
​
  // Concise unnamed generative constructor.
  new(this.x, this.y);
​
  // Concise named generative constructor.
  new origin() : x = 0, y = 0;
​
  // Equivalent to `factory Point.clone(Point other)`.
  factory clone(Point other) => Point(other.x, other.y);
}

Essa sintaxe reduz a verbosidade e facilita a refatoração de nomes de classes.

A tabela a seguir mostra como mapear a sintaxe tradicional de construtores para a sintaxe concisa de construtores (para uma classe chamada LongClassName):

Original Dart syntaxAbbreviated syntax
LongClassName() {}new() {}
LongClassName.name() {}new name() {}
const LongClassName();const new();
const LongClassName.name();const new name();
LongClassName(): this.other();new(): this.other();
LongClassName.name(): this();new name(): this();
const LongClassName(): this.other(); const new(): this.other();
const LongClassName.name(): this(); const new name(): this();
factory LongClassName() { ... }factory() { ... }
factory LongClassName.name() { ... } factory name() { ... }
factory LongClassName() = D;factory() = D;
factory LongClassName.name() = D;factory name() = D;
const factory LongClassName() = D;const factory() = D;
const factory LongClassName.name() = D; const factory name() = D;

Para uma sintaxe ainda mais concisa, em que você define campos e construtores em uma única linha, veja Construtores primários.

Inicialização de variáveis de instância

#

O Dart oferece várias maneiras de inicializar variáveis de instância. Você pode atribuir valores na declaração, usar parâmetros formais de inicialização ou usar uma lista de inicializadores.

Inicializar variáveis de instância na declaração

#

Inicialize as variáveis de instância quando você declarar as variáveis.

dart
class PointA {
  double x = 1.0;
  double y = 2.0;
​
  // The implicit default constructor sets these variables to (1.0,2.0)
  // PointA();
​
  @override
  String toString() {
    return 'PointA($x,$y)';
  }
}

Usar parâmetros formais de inicialização

#

Para simplificar o padrão comum de atribuir um argumento de construtor a uma variável de instância, o Dart tem parâmetros formais de inicialização.

Na declaração do construtor, inclua this.<propertyName> e omita o corpo. A keyword this refere-se à instância atual.

Quando o conflito de nomes existe, use this. Caso contrário, o estilo Dart omite o this. Uma exceção existe para o construtor generativo onde você deve prefixar o nome do parâmetro formal de inicialização com this.

Como observado anteriormente neste guia, certos construtores e certas partes de construtores não podem acessar this. Estes incluem:

  • Construtores factory
  • O lado direito de uma lista de inicializadores
  • Argumentos para um construtor de superclasse

Parâmetros formais de inicialização também permitem que você inicialize variáveis de instância non-nullable ou final. Ambos os tipos de variáveis requerem inicialização ou um valor padrão.

dart
class PointB {
  final double x;
  final double y;
​
  // Sets the x and y instance variables
  // before the constructor body runs.
  PointB(this.x, this.y);
​
  // Initializing formal parameters can also be optional.
  PointB.optional([this.x = 0.0, this.y = 0.0]);
}

Isso também funciona com variáveis nomeadas.

dart
class PointC {
  double x; // must be set in constructor
  double y; // must be set in constructor
​
  // Generative constructor with initializing formal parameters
  // with default values
  PointC.named({this.x = 1.0, this.y = 1.0});
​
  @override
  String toString() {
    return 'PointC.named($x,$y)';
  }
}
​
// Constructor using named variables.
final pointC = PointC.named(x: 2.0, y: 2.0);

Todas as variáveis introduzidas de parâmetros formais de inicialização são tanto final quanto apenas no escopo das variáveis inicializadas.

Para executar lógica que você não pode expressar na lista de inicializadores, crie um construtor factory ou método static com essa lógica. Você pode então passar os valores computados para um construtor normal.

Você pode definir parâmetros do construtor como nullable para evitar inicializá-los.

dart
class PointD {
  double? x; // null if not set in constructor
  double? y; // null if not set in constructor
​
  // Generative constructor with initializing formal parameters
  PointD(this.x, this.y);
​
  @override
  String toString() {
    return 'PointD($x,$y)';
  }
}

Parâmetros nomeados privados

#

Em Dart, campos que começam com sublinhado são privados à sua biblioteca. Para inicializar um campo privado usando um parâmetro nomeado, você pode escrever a atribuição manual na lista de inicializadores:

dart
class Point {
  final double _x;
  Point({required double x}) : _x = x;
}

Você também pode inicializar campos privados diretamente na lista de parâmetros do construtor. Quando você prefixa o parâmetro nomeado com this._, o compilador remove automaticamente o sublinhado para quem chama, permitindo que use um nome público limpo:

dart
class Point {
  final double _x;
  Point({required this._x});
}

Nos dois casos, quem chama usa o nome público x na chamada:

dart
var p = Point(x: 1.0);

Assim como os parâmetros nomeados comuns, você pode tornar os parâmetros nomeados privados opcionais ou obrigatórios. Você também pode fornecer valores padrão explícitos.

No exemplo a seguir, o parâmetro _x é opcional e o padrão é null. O parâmetro _y também é opcional, mas tem o valor padrão explícito 0.0:

dart
class PointPrivate {
  final double? _x; // Nullable field
  final double _y; // Non-nullable field
​
  PointPrivate({this._x, this._y = 0.0});
​
  @override
  String toString() => 'PointPrivate($_x, $_y)';
}
​
void testPrivate() {
  var p = PointPrivate(x: 1.0, y: 2.0);
  print(p);
}

Restrições

#
  • Sem conflitos: nem o nome privado nem o nome público gerado podem coincidir com outro nome de parâmetro no mesmo construtor.
  • Somente parâmetros formais de inicialização: parâmetros nomeados em Dart, em geral, não podem ser privados. Essa capacidade é uma exceção que se aplica apenas a parâmetros nomeados que são parâmetros formais de inicialização (this._campo). Você não pode usar identificadores privados para parâmetros nomeados comuns.
  • Nome público válido: o nome privado precisa mapear para um identificador público válido. Por exemplo, this._ ou this._2x são inválidos porque não têm equivalentes públicos válidos.

Uso na lista de inicializadores

#

Dentro da lista de inicializadores do construtor, referencie o parâmetro usando seu nome privado:

dart
class PointPrivateAssert {
  final double _x;
​
  PointPrivateAssert({required this._x}) : assert(_x >= 0);
}

Interação com super parâmetros

#

Ao estender uma classe que usa parâmetros nomeados privados, as subclasses usam o nome público para super parâmetrosSuper parameterUm parâmetro de construtor que passa automaticamente um argumento para o construtor da superclasse. Saiba mais.

No exemplo a seguir, a classe Tool define o campo privado _price. Mesmo o campo sendo privado, seu parâmetro nomeado correspondente é público (price, não _price). Para repassar o valor, a subclasse Hammer usa o identificador público price:

dart
class Tool {
  final int _price;
  Tool({required this._price});
}
​
class Hammer extends Tool {
  // Forwards to the public 'price' argument
  Hammer({required super.price});
}

Usar uma lista de inicializadores

#

Antes do corpo do construtor ser executado, você pode inicializar variáveis de instância. Separe os inicializadores com vírgulas.

dart
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}

Para validar entradas durante o desenvolvimento, use assert na lista de inicializadores.

dart
Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

Listas de inicializadores ajudam a configurar campos final.

O exemplo a seguir inicializa três campos final em uma lista de inicializadores. Para executar o código, clique em Run.

import 'dart:math';

class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(double x, double y)
    : x = x,
      y = y,
      distanceFromOrigin = sqrt(x * x + y * y);
}

void main() {
  var p = Point(2, 3);
  print(p.distanceFromOrigin);
}

Herança de construtor

#

Subclasses, ou classes filhas, não herdam construtores de sua superclasse, ou classe pai imediata. Se uma classe não declarar um construtor, ela só pode usar o construtor padrão.

Uma classe pode herdar os parâmetros de uma superclasse. Estes são chamados super parâmetros

Construtores funcionam de maneira um tanto semelhante a como você chama uma cadeia de métodos static. Cada subclasse pode chamar o construtor de sua superclasse para inicializar uma instância, como uma subclasse pode chamar um método static de uma superclasse. Este processo não "herda" corpos ou assinaturas de construtores.

Construtores de superclasse não padrão

#

O Dart executa construtores na seguinte ordem:

  1. lista de inicializadores
  2. construtor sem nome e sem argumentos da superclasse
  3. construtor sem argumentos da classe principal

Se a superclasse não tiver um construtor sem nome e sem argumentos, chame um dos construtores na superclasse. Antes do corpo do construtor (se houver), especifique o construtor da superclasse após dois pontos (:).

No exemplo a seguir, o construtor da classe Employee chama o construtor nomeado para sua superclasse, Person. Para executar o código a seguir, clique em Run.

class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call super.fromJson().
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
  // Prints:
  // in Person
  // in Employee
  // Instance of 'Employee'
}

Como o Dart avalia os argumentos para o construtor da superclasse antes de invocar o construtor, um argumento pode ser uma expressão como uma chamada de função.

dart
class Employee extends Person {
  Employee() : super.fromJson(fetchDefaultData());
  // ···
}

Super parâmetros

#

Para evitar passar cada parâmetro para a invocação super de um construtor, use parâmetros super-initializer para encaminhar parâmetros para o construtor de superclasse especificado ou padrão. Você não pode usar este recurso com construtores de redirecionamento. Parâmetros super-initializer têm sintaxe e semântica como parâmetros formais de inicialização.

Se a invocação do super-constructor inclui argumentos posicionais, parâmetros super-initializer não podem ser posicionais.

dart
class Vector2d {
  final double x;
  final double y;
​
  Vector2d(this.x, this.y);
}
​
class Vector3d extends Vector2d {
  final double z;
​
  // Forward the x and y parameters to the default super constructor like:
  // Vector3d(final double x, final double y, this.z) : super(x, y);
  Vector3d(super.x, super.y, this.z);
}

Para ilustrar ainda mais, considere o exemplo a seguir.

dart
  // If you invoke the super constructor (`super(0)`) with any
  // positional arguments, using a super parameter (`super.x`)
  // results in an error.
  Vector3d.xAxisError(super.x): z = 0, super(0); // BAD

Este construtor nomeado tenta definir o valor x duas vezes: uma vez no construtor super e uma vez como um parâmetro super posicional. Como ambos endereçam o parâmetro posicional x, isso resulta em um erro.

Quando o construtor super tem argumentos nomeados, você pode dividi-los entre parâmetros super nomeados (super.y no próximo exemplo) e argumentos nomeados para a invocação do construtor super (super.named(x: 0)).

dart
class Vector2d {
  // ...
  Vector2d.named({required this.x, required this.y});
}
​
class Vector3d extends Vector2d {
  final double z;
​
  // Forward the y parameter to the named super constructor like:
  // Vector3d.yzPlane({required double y, required this.z})
  //       : super.named(x: 0, y: y);
  Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}

Encontrou essa página sem tradução ou que precisa de correção? Abra uma issue e nos ajude a manter esse site em PT-BR para ajudar pessoas como você.