Em 2013, o lançamento oficial de Dart 1.0 foi divulgado, como acontece com a maioria das ofertas do Google, mas nem todos estavam tão ansiosos como equipes internas do Google para criar aplicativos essenciais aos negócios com a linguagem Dart. Com sua reconstrução bem planejada do Dart 2 cinco anos depois, o Google parecia ter provado seu compromisso com a linguagem. Na verdade, hoje ele continua a ganhar força entre os desenvolvedores - especialmente os veteranos de Java e C #.
A linguagem de programação Dart é importante por alguns motivos:
Aqueles de nós do mundo C # ou Java de sistemas corporativos maiores já sabem por que segurança de tipo, erros de tempo de compilação e linters são importantes. Muitos de nós hesitamos em adotar uma linguagem de “script” por medo de perder toda a estrutura, velocidade, precisão e depuração a que estamos acostumados.
Mas com o desenvolvimento do Dart, não precisamos desistir de nada disso. Podemos escrever um aplicativo móvel, cliente da web e back-end na mesma linguagem - e obter todas as coisas que ainda amamos em Java e C #!
Para esse fim, vamos examinar alguns exemplos importantes da linguagem Dart que seriam novos para um desenvolvedor C # ou Java, que resumiremos em um PDF da linguagem Dart no final.
Nota: Este artigo cobre apenas o Dart 2.x. A versão 1.x não foi 'totalmente preparada' - em particular, o sistema de tipos era consultivo (como TypeScript) em vez de obrigatório (como C # ou Java).
Primeiro, veremos uma das diferenças mais significativas: como os arquivos de código são organizados e referenciados.
Em C #, uma coleção de classes é compilada em um assembly. Cada classe tem um namespace e, frequentemente, os namespaces refletem a organização do código-fonte no sistema de arquivos - mas, no final, o assembly não retém nenhuma informação sobre o local do arquivo do código-fonte.
Em Java, os arquivos de origem fazem parte de um pacote e os namespaces geralmente estão em conformidade com a localização do sistema de arquivos, mas no final, um pacote é apenas uma coleção de classes.
Portanto, ambas as linguagens têm uma maneira de manter o código-fonte um tanto independente do sistema de arquivos.
Por outro lado, na linguagem Dart, cada arquivo de origem deve importar tudo a que se refere, incluindo seus outros arquivos de origem e pacotes de terceiros. Não há namespaces da mesma maneira e você costuma se referir a arquivos por meio de sua localização no sistema de arquivos. Variáveis e funções podem ser de nível superior, não apenas classes. Dessas maneiras, o Dart é mais parecido com um script.
Portanto, você precisará mudar seu pensamento de “uma coleção de classes” para algo mais como “uma sequência de arquivos de código incluídos”.
O Dart oferece suporte à organização de pacotes e à organização ad-hoc sem pacotes. Vamos começar com um exemplo sem pacotes para ilustrar a sequência de arquivos incluídos:
// file1.dart int alice = 1; // top level variable int barry() => 2; // top level function var student = Charlie(); // top level variable; Charlie is declared below but that's OK class Charlie { ... } // top level class // alice = 2; // top level statement not allowed // file2.dart import 'file1.dart'; // causes all of file1 to be in scope main() { print(alice); // 1 }
Tudo o que você faz referência em um arquivo de origem deve ser declarado ou importado dentro desse arquivo, pois não há nível de “projeto” e nenhuma outra maneira de incluir outros elementos de origem no escopo.
O único uso de namespaces no Dart é para dar um nome às importações, e isso afeta como você se refere ao código importado desse arquivo.
// file2.dart import 'file1.dart' as wonderland; main() { print(wonderland.alice); // 1 }
Os exemplos acima organizam o código sem pacotes. Para usar pacotes, o código é organizado de forma mais específica. Aqui está um exemplo de layout de pacote para um pacote chamado apples
:
apples/
pubspec.yaml
—Define o nome do pacote, dependências e algumas outras coisaslib/
apples.dart
—Importações e exportações; este é o arquivo importado por qualquer consumidor do pacotesrc/
seeds.dart
—Todos os outros códigos aquibin/
runapples.dart
—Contém a função principal, que é o ponto de entrada (se este for um pacote executável ou incluir ferramentas executáveis)Então, você pode importar pacotes inteiros em vez de arquivos individuais:
import 'package:apples';
Os aplicativos não triviais sempre devem ser organizados como pacotes. Isso alivia muito a necessidade de repetir os caminhos do sistema de arquivos em cada arquivo de referência; além disso, eles correm mais rápido. Também torna mais fácil compartilhar seu pacote no pub.dev , onde outros desenvolvedores podem facilmente obtê-lo para seu próprio uso. Os pacotes usados pelo seu aplicativo farão com que o código-fonte seja copiado para o seu sistema de arquivos, para que você possa depurar esses pacotes tão profundamente quanto desejar.
definição de design em arte
Existem diferenças importantes no sistema de tipos de Dart a serem considerados, em relação a nulos, tipos numéricos, coleções e tipos dinâmicos.
Vindo de C # ou Java, estamos acostumados a primitivo ou valor tipos distintos de referência ou objeto tipos. Os tipos de valor são, na prática, alocados na pilha ou em registradores, e as cópias do valor são enviadas como parâmetros de função. Em vez disso, os tipos de referência são alocados no heap e apenas ponteiros para o objeto são enviados como parâmetros de função. Como os tipos de valor sempre ocupam memória, uma variável digitada por valor não pode ser nula e todos os membros de tipo de valor devem ter valores inicializados.
O Dart elimina essa distinção porque tudo é um objeto; todos os tipos derivam basicamente do tipo Object
. Então, isso é legal:
int i = null;
Na verdade, todos os primitivos são inicializados implicitamente para null
. Isso significa que você não pode assumir que os valores padrão de inteiros são zero, como costumava fazer em C # ou Java, e pode ser necessário adicionar verificações de nulos.
Curiosamente, mesmo Null
é um tipo e a palavra null
refere-se a uma instância de Null
:
print(null.runtimeType); // prints Null
Ao contrário da variedade familiar de tipos inteiros de 8 a 64 bits com sabores assinados e não assinados, o tipo inteiro principal de Dart é apenas int
, um valor de 64 bits. (Também existe BigInt
para números muito grandes.)
Como não há matriz de bytes como parte da sintaxe da linguagem, o conteúdo do arquivo binário pode ser processado como listas de inteiros, ou seja, List
.
Se você está pensando que isso deve ser terrivelmente ineficiente, os designers já pensaram nisso. Na prática, existem diferentes representações internas, dependendo do valor inteiro real usado no tempo de execução. O tempo de execução não aloca memória heap para int
objeto se ele pode otimizar isso e usar um registro de CPU no modo unboxed. Além disso, a biblioteca byte_data
ofertas UInt8List
e algumas outras representações otimizadas.
Coleções e genéricos são muito parecidos com o que estamos acostumados. A principal coisa a observar é que não há matrizes de tamanho fixo: basta usar o List
tipo de dados onde quer que você use um array.
Além disso, há suporte sintático para inicializar três dos tipos de coleção:
final a = [1, 2, 3]; // inferred type is List, an array-like ordered collection final b = {1, 2, 3}; // inferred type is Set, an unordered collection final c = {'a': 1, 'b': 2}; // inferred type is Map, an unordered collection of name-value pairs
Portanto, use o Dart List
onde você usaria um array Java, ArrayList
ou Vector
; ou uma matriz C # ou List
. Use Set
onde você usaria um Java / C # HashSet
. Use Map
onde você usaria um Java HashMap
ou C # Dictionary
.
Em linguagens dinâmicas como JavaScript, Ruby e Python, você pode fazer referência a membros, mesmo se eles não existirem. Aqui está um exemplo de JavaScript:
var person = {}; // create an empty object person.name = 'alice'; // add a member to the object if (person.age <21) { // refer to a property that is not in the object // ... }
Se você executar isso, person.age
será undefined
, mas será executado mesmo assim.
Da mesma forma, você pode alterar o tipo de uma variável em JavaScript:
var a = 1; // a is a number a = 'one'; // a is now a string
Por outro lado, em Java, você não pode escrever código como o acima, porque o compilador precisa saber o tipo e verifica se todas as operações são legais, mesmo se você usar a palavra-chave var:
var b = 1; // a is an int // b = 'one'; // not allowed in Java
Java só permite que você codifique com tipos estáticos. (Você pode usar a introspecção para realizar algum comportamento dinâmico, mas não faz parte diretamente da sintaxe.) JavaScript e algumas outras linguagens puramente dinâmicas permitem apenas que você codifique com tipos dinâmicos.
A linguagem Dart permite:
[:] em python
// dart dynamic a = 1; // a is an int - dynamic typing a = 'one'; // a is now a string a.foo(); // we can call a function on a dynamic object, to be resolved at run time var b = 1; // b is an int - static typing // b = 'one'; // not allowed in Dart
Dart tem o pseudo-tipo dynamic
o que faz com que toda a lógica de tipo seja tratada em tempo de execução. A tentativa de chamar a.foo()
não incomodará o analisador estático e o código será executado, mas falhará em tempo de execução porque esse método não existe.
C # era originalmente como Java e, posteriormente, adicionou suporte dinâmico, de modo que Dart e C # são quase os mesmos nesse aspecto.
A sintaxe da função no Dart é um pouco mais leve e divertida do que em C # ou Java. A sintaxe é qualquer uma destas:
// functions as declarations return-type name (parameters) {body} return-type name (parameters) => expression; // function expressions (assignable to variables, etc.) (parameters) {body} (parameters) => expression
Por exemplo:
void printFoo() { print('foo'); }; String embellish(String s) => s.toUpperCase() + '!!'; var printFoo = () { print('foo'); }; var embellish = (String s) => s.toUpperCase() + '!!';
Uma vez que tudo é um objeto, incluindo primitivos como int
e String
, a passagem de parâmetro pode ser confusa. Embora não haja ref
passagem de parâmetro como em C #, tudo é passado por referência e a função não pode alterar a referência do chamador. Como os objetos não são clonados quando passados para funções, uma função pode alterar as propriedades do objeto. No entanto, essa distinção para primitivos como int e String é efetivamente discutível, uma vez que esses tipos são imutáveis.
var id = 1; var name = 'alice'; var client = Client(); void foo(int id, String name, Client client) { id = 2; // local var points to different int instance name = 'bob'; // local var points to different String instance client.State = 'AK'; // property of caller's object is changed } foo(id, name, client); // id == 1, name == 'alice', client.State == 'AK'
Se você está nos mundos C # ou Java, provavelmente amaldiçoou as situações com métodos sobrecarregados e confusos como estes:
// java void foo(string arg1) {...} void foo(int arg1, string arg2) {...} void foo(string arg1, Client arg2) {...} // call site: foo(clientId, input3); // confusing! too easy to misread which overload it is calling
Ou com os parâmetros opcionais do C #, há outro tipo de confusão:
// c# void Foo(string arg1, int arg2 = 0) {...} void Foo(string arg1, int arg3 = 0, int arg2 = 0) {...} // call site: Foo('alice', 7); // legal but confusing! too easy to misread which overload it is calling and which parameter binds to argument 7 Foo('alice', arg2: 9); // better
C # não requer a nomenclatura de argumentos opcionais em sites de chamada, portanto, refatorar métodos com parâmetros opcionais pode ser perigoso. Se alguns sites de chamada forem legais após o refatorador, o compilador não os detectará.
O Dart tem uma forma mais segura e muito flexível. Em primeiro lugar, os métodos sobrecarregados são não suportado. Em vez disso, existem duas maneiras de lidar com parâmetros opcionais:
// positional optional parameters void foo(string arg1, [int arg2 = 0, int arg3 = 0]) {...} // call site for positional optional parameters foo('alice'); // legal foo('alice', 12); // legal foo('alice', 12, 13); // legal // named optional parameters void bar(string arg1, {int arg2 = 0, int arg3 = 0}) {...} bar('alice'); // legal bar('alice', arg3: 12); // legal bar('alice', arg3: 12, arg2: 13); // legal; sequence can vary and names are required
Você não pode usar os dois estilos na mesma declaração de função.
async
Posição da palavra-chaveC # tem uma posição confusa para seu async
palavra-chave:
Task Foo() {...} async Task Foo() {...}
Isso implica que a assinatura da função é assíncrona, mas na verdade apenas a implementação da função é assíncrona. Qualquer uma das assinaturas acima seria uma implementação válida desta interface:
interface ICanFoo { Task Foo(); }
Na linguagem Dart, async
está em um lugar mais lógico, denotando que a implementação é assíncrona:
Future foo() async {...}
Como C # e Java, o Dart tem escopo léxico. Isso significa que uma variável declarada em um bloco sai do escopo no final do bloco. Portanto, o Dart trata os fechos da mesma maneira.
Java popularizou o padrão get / set de propriedade, mas a linguagem não tem nenhuma sintaxe especial para ela:
// java private String clientName; public String getClientName() { return clientName; } public void setClientName(String value}{ clientName = value; }
C # tem sintaxe para isso:
// c# private string clientName; public string ClientName { get { return clientName; } set { clientName = value; } }
O Dart tem propriedades de suporte de sintaxe um pouco diferentes:
// dart string _clientName; string get ClientName => _clientName; string set ClientName(string s) { _clientName = s; }
Os construtores Dart têm um pouco mais de flexibilidade do que em C # ou Java. Um recurso interessante é a capacidade de nomear diferentes construtores na mesma classe:
class Point { Point(double x, double y) {...} // default ctor Point.asPolar(double angle, double r) {...} // named ctor }
Você pode chamar um construtor padrão apenas com o nome da classe: var c = Client();
Existem dois tipos de atalhos para inicializar membros da instância antes de o corpo do construtor ser chamado:
class Client { String _code; String _name; Client(String this._name) // 'this' shorthand for assigning parameter to instance member : _code = _name.toUpper() { // special out-of-body place for initializing // body } }
Os construtores podem executar construtores de superclasse e redirecionar para outros construtores na mesma classe:
Foo.constructor1(int x) : this(x); // redirect to the default ctor in same class; no body allowed Foo.constructor2(int x) : super.plain(x) {...} // call base class named ctor, then run this body Foo.constructor3(int x) : _b = x + 1 : super.plain(x) {...} // initialize _b, then call base class ctor, then run this body
Construtores que chamam outros construtores na mesma classe em Java e C # podem ficar confusos quando ambos têm implementações. No Dart, a limitação de que os construtores de redirecionamento não podem ter um corpo força o programador a tornar as camadas de construtores mais claras.
Há também um factory
palavra-chave que permite que uma função seja usada como um construtor, mas a implementação é apenas uma função regular. Você pode usá-lo para retornar uma instância em cache ou uma instância de um tipo derivado:
class Shape { factory Shape(int nsides) { if (nsides == 4) return Square(); // etc. } } var s = Shape(4);
Em Java e C #, temos modificadores de acesso como private
, protected
e public
. No Dart, isso é drasticamente simplificado: se o nome do membro começa com um sublinhado, ele fica visível em qualquer lugar dentro do pacote (incluindo de outras classes) e oculto de chamadores externos; caso contrário, é visível de qualquer lugar. Não há palavras-chave como private
para significar visibilidade.
Outro tipo de modificador controla a mutabilidade: As palavras-chave final
e const
são para esse propósito, mas significam coisas diferentes:
var a = 1; // a is variable, and can be reassigned later final b = a + 1; // b is a runtime constant, and can only be assigned once const c = 3; // c is a compile-time constant // const d = a + 2; // not allowed because a+2 cannot be resolved at compile time
A linguagem Dart oferece suporte a interfaces, classes e um tipo de herança múltipla. No entanto, não há interface
palavra-chave; em vez disso, todas as classes também são interfaces, portanto, você pode definir um abstract
classe e implementá-la:
abstract class HasDesk { bool isDeskMessy(); // no implementation here } class Employee implements HasDesk { bool isDeskMessy() { ...} // must be implemented here }
A herança múltipla é feita com uma linhagem principal usando extends
palavra-chave e outras classes usando a with
palavra-chave:
class Employee extends Person with Salaried implements HasDesk {...}
Nesta declaração, o Employee
classe deriva de Person
e Salaried
, mas Person
é a superclasse principal e Salaried
é o mixin (a superclasse secundária).
Existem alguns operadores de Dart divertidos e úteis aos quais não estamos acostumados.
Cascades permitem que você use um padrão de encadeamento em qualquer coisa:
emp ..name = 'Alice' ..supervisor = 'Zoltron' ..hire();
O operador spread permite que uma coleção seja tratada como uma lista de seus elementos em um inicializador:
var smallList = [1, 2]; var bigList = [0, ...smallList, 3, 4]; // [0, 1, 2, 3, 4]
O Dart não tem threads, o que permite que ele transpile para JavaScript. Em vez disso, tem “isolados”, que são mais como processos separados, no sentido de que não podem compartilhar memória. Uma vez que a programação multithread é tão sujeita a erros, essa segurança é vista como uma das vantagens do Dart. Para comunicar-se entre isolados , você precisa transmitir dados entre eles; os objetos recebidos são copiados para o espaço de memória do isolado receptor.
Se você é um desenvolvedor C # ou Java, o que já sabe o ajudará a aprender a linguagem Dart rapidamente, uma vez que foi projetada para ser familiar. Para esse fim, reunimos um Folha de dicas do Dart em PDF para sua referência, focando especificamente em diferenças importantes de equivalentes C # e Java:
As diferenças mostradas neste artigo, combinadas com seu conhecimento existente, o ajudarão a se tornar produtivo em seus primeiros dois dias de Dart. Boa codificação!
Relacionado: Energia híbrida: vantagens e benefícios da vibraçãoDart é uma linguagem moderna, segura e facilmente acessível desenvolvida pelo Google. Compila para plataformas nativas de desktop e móveis e transpila para JavaScript para aplicativos da web.
Como uma linguagem multi-plataforma de propósito geral, o Dart pode ser usado para web e mobile front-ends e back-ends, aplicativos de banco de dados e scripts. A interface do usuário do Google AdWords foi construída com Dart.
Qual destes melhor descreve um exemplo de usabilidade?
Dart é uma linguagem que combina muitos dos melhores recursos de C #, Java, Python e JavaScript, como tipagem dinâmica e estática, suporte assíncrono e funções lambda. Ele foi cuidadosamente projetado para evitar complexidades suscetíveis a bugs e para ser aprendido rapidamente.
Python e JavaScript são frequentemente recomendados como primeiras linguagens a serem aprendidas porque os scripts de prática podem ser executados rapidamente com uma curva de aprendizado baixa. No entanto, qualquer idioma pode ser o primeiro, já que a maioria das habilidades de um idioma são transferíveis para outros. Começar com C # pode ser benéfico porque ensina a segurança de tipos desde o início.
A sintaxe do Dart se parece e se comporta muito como C #, seu primo mais próximo. Ele também oferece suporte a scripts de interpretação de código-fonte, bem como Python.
O Dart pode ser compilado com antecedência (AOT) ou just in time (JIT). A implantação de um aplicativo Dart permite a compilação em uma plataforma nativa ou a execução direta do código-fonte.
O Dart opcionalmente transpila para JavaScript, permitindo que aplicativos Dart (e Flutter) sejam executados em um navegador. Ele também oferece suporte à compilação para executáveis nativos.