O Dart possui mecanismos para "casting" (conversão de tipos ou coerção de tipos), permitindo que você trate um objeto de um tipo como se fosse de outro tipo. Isso é comum em linguagens orientadas a objetos.
Vamos exemplificar algumas das principais formas de lidar com casting em Dart:
Isso acontece quando você atribui um objeto de um tipo mais específico a uma variável de um tipo mais geral (um supertipo). Isso é sempre seguro e geralmente acontece implicitamente, sem a necessidade de qualquer operador especial.
void main() {
String meuTexto = "Olá, Dart!";
Object meuObjeto = meuTexto; // Upcast: String é um Object, isso é seguro e implícito.
int meuNumeroInteiro = 10;
num meuNumeroGeral = meuNumeroInteiro; // Upcast: int é um num, seguro e implícito.
print(meuObjeto);
print(meuNumeroGeral);
}
Isso é quando você tenta converter um objeto de um tipo mais geral para um tipo mais específico. O Downcasting pode falhar em tempo de execução se o objeto não for realmente uma instância do tipo específico para o qual você está tentando converter.
Dart oferece algumas maneiras de fazer downcasting:
Operador as (Casting Explícito e "Inseguro"): O operador as é usado para realizar um downcast explícito. Se o objeto for uma instância do tipo desejado (ou de um subtipo dele), a conversão é bem-sucedida. Caso contrário, uma exceção do tipo TypeError (anteriormente CastError) será lançada em tempo de execução. Use as quando você tem certeza (ou quase certeza, baseada na lógica do seu programa) de que o objeto é do tipo esperado.
void main() {
Object meuObjeto = "Isso é uma string";
// Sabemos que meuObjeto contém uma String neste ponto.
String textoRecuperado = meuObjeto as String;
print(textoRecuperado.toUpperCase()); // Funciona, pois é uma String
Object outroObjeto = 123;
// Se tentarmos o seguinte, teremos um TypeError em tempo de execução:
// String numeroComoTexto = outroObjeto as String; // ERRO!
// print(numeroComoTexto);
}
Verificação de Tipo com is e is! (Abordagem Segura): Antes de tentar um downcast com as, é uma boa prática verificar o tipo do objeto usando o operador is. O operador is! verifica o oposto (se não é do tipo).
void processarValor(Object valor) {
if (valor is String) {
// Dentro deste bloco, Dart é inteligente o suficiente para tratar 'valor'
// como String (isso é chamado de "smart cast" ou promoção de tipo).
print('É uma String: ${valor.toUpperCase()}');
} else if (valor is int) {
// Smart cast para int
print('É um int: ${valor + 5}');
} else if (valor is! double) {
print('Não é um double (e nem String ou int pelos ifs anteriores).');
} else {
print('É um double: $valor');
}
}
void main() {
processarValor("Teste"); // Saída: É uma String: TESTE
processarValor(100); // Saída: É um int: 105
processarValor(true); // Saída: Não é um double (e nem String ou int pelos ifs anteriores).
processarValor(3.14); // Saída: É um double: 3.14
}
Smart Casts (Promoção de Tipo): Como visto no exemplo acima, se você verificar um tipo com is em uma condicional if, o compilador Dart automaticamente "promove" a variável para aquele tipo dentro do escopo da condicional, tornando um as explícito desnecessário e o código mais seguro e limpo.
Casting de coleções, como uma List<dynamic> para List<String>, requer atenção especial.
List.from() com Map e as (para criar uma nova lista tipada): Se você tem uma List<dynamic> e tem certeza que todos os elementos são de um tipo específico.
void main() {
List<dynamic> listaDinamica = [1, 2, 3, "não é número"];
List<dynamic> numerosDinamicos = [10, 20, 30];
try {
// Maneira segura de criar uma nova lista com tipos corretos
List<int> listaDeInteiros = List<int>.from(numerosDinamicos);
print(listaDeInteiros); // [10, 20, 30]
// Se você tem certeza dos tipos e quer transformar:
List<int> outraListaDeInteiros = numerosDinamicos.map((e) => e as int).toList();
print(outraListaDeInteiros); // [10, 20, 30]
// O que acontece se um elemento não for do tipo esperado:
// List<int> listaComErro = listaDinamica.map((e) => e as int).toList(); // ERRO em tempo de execução no "não é número"
// print(listaComErro);
} catch (e) {
print('Erro ao converter lista: $e');
}
}
Método cast<T>() em Iterables: O método cast<R>() em um Iterable<E> (como List) retorna uma nova visão do Iterable como um Iterable<R>. Ele não cria uma nova lista imediatamente, mas verifica o tipo de cada elemento conforme ele é acessado. Se um elemento não for do tipo R, um TypeError será lançado.
void main() {
List<num> numeros = [1, 2.5, 3, 4.0];
// Cria uma visão da lista como Iterable<int>
// O cast é verificado quando os elementos são acessados.
Iterable<int> apenasInteirosView;
try {
apenasInteirosView = numeros.cast<int>();
// Tentar iterar ou converter para lista pode lançar erro se houver tipos incompatíveis.
// for (int i in apenasInteirosView) { // Lançaria erro no 2.5
// print(i);
// }
// Uma forma mais segura seria filtrar primeiro:
List<int> inteirosFiltrados = numeros.whereType<int>().toList();
print('Inteiros filtrados: $inteirosFiltrados'); // [1, 3]
List<int> soOsPrimeirosInteiros = [10, 20, 30];
Iterable<num> iterableNum = soOsPrimeirosInteiros.cast<num>(); // Upcasting aqui, seguro
print(iterableNum.toList());
} catch (e) {
print('Erro durante o cast da coleção: $e');
}
}
Para coleções, muitas vezes é mais seguro usar métodos como whereType<T>() para filtrar elementos do tipo desejado antes de tentar um cast, ou usar map para transformar e converter os elementos um a um, possivelmente com tratamento de erro individual.
Quando você recebe dados de fontes menos tipadas (ex: JSON decodificado que resulta em Map<String, dynamic>).
Ao interagir com APIs que retornam tipos genéricos como Object ou num.
Em hierarquias de classes, quando você sabe que uma instância de uma superclasse é, na verdade, uma instância de uma subclasse específica.
Prefira is e smart casts: É a forma mais segura e legível de lidar com diferentes tipos.
Use as com cautela: Apenas quando você tem uma forte garantia sobre o tipo do objeto para evitar TypeError em tempo de execução.
Evite downcasting excessivo: Se você se encontra fazendo muitos downcasts, pode ser um sinal de que o design do seu código pode ser melhorado, talvez com mais uso de polimorfismo ou genéricos.
Para coleções, seja explícito: Ao converter coleções, prefira criar novas coleções com os tipos corretos (usando Map, List.from, whereType) em vez de tentar fazer um cast "em bloco" de toda a coleção, o que pode ser mais propenso a erros.
O sistema de tipos do Dart, incluindo null safety e smart casts, ajuda muito a escrever código robusto e com menos chances de erros de tipo em tempo de execução.