Java ME introdução

Comece a conhecer a tecnologia Java ME em profundidade, dos princípios fundamentais a detalhes de configurações, perfis e APIs.

Nestes mais de quatro anos de Java Magazine, todos os artigos desta coluna, cobrindo variados temas e APIs, tiveram uma coisa em comum – o requisito mínimo da plataforma Java SE, e algumas vezes também da Java EE. Mas não é só em direção ao high-end, às grandes aplicações corporativas, que a tecnologia Java evoluiu. Também é notório o sucesso do Java, em anos recentes, em toda espécie de equipamentos limitados, de smart cards a PDAs, comumente chamados "dispositivos" (devices). E esta área de aplicação tem se tornado cada vez mais importante. Em especial devido à popularização mais recente no Brasil de dispositivos de amplo consumo, como modelos acessíveis de telefones celulares, que já suportam a versão "micro" do Java.

A partir desta edição, iniciamos uma série de artigos cobrindo a plataforma Java ME. Este primeiro artigo irá apresentar a Java ME em geral. O objetivo será situar o leitor no complexo universo de padrões e implementações da Java ME, já discutir um pouco questões de projeto específico para dispositivos limitados, e "sobrevoar" diversos tópicos que mais tarde iremos tratar de forma aprofundada.

Aprender a programar para Java ME não é só uma questão de conhecer novas APIs e ferramentas. Para quem está habituado a servidores cheios de gigabytes e gigahertz, o desenvolvimento "micro" apresenta uma mudança de paradigma, sendo preciso repensar várias práticas e técnicas de design e de programação. Nesta série, procuraremos explorar também este aspecto do Java ME. Nos artigos que se seguirão, cobriremos APIs específicas, otimização e outros tópicos importantes. Nosso foco será a configuração CLDC e o perfil MIDP, ou seja, celulares, por ser a plataforma que terá o maior interesse prático para a maioria dos leitores.

A plataforma Java ME

Todo texto introdutório sobre Java ME inclui alguns conceitos e diagramas fundamentais, dos quais também não podemos fugir. Precisamos começar pela estrutura de Configurações, Perfis e Extensões da Java ME. A Figura 1 (fonte: Sun) é uma visão atualizada e detalhada da plataforma.

Figura 1. A plataforma Java ME com suas configurações, perfis, e APIs

Existem APIs definidas nos níveis de perfil (“API”) e de configuração (não mostradas), e também APIs de extensão que às vezes são compartilhadas por diferentes configurações (nas "fronteiras"). Além disso, há algumas APIs e padrões que não fazem parte oficialmente da plataforma Java ME mas podem ser oferecidas em implementações de Java ME, especialmente a RealTime Specification for Java.

A maior diferença de paradigma entre os padrões Java SE e EE e a Java ME é que esta não tem uma definição única. O problema é que o hardware ao qual a Java ME se destina é bem mais heterogêneo, em desempenho e em funcionalidades, do que computadores desktop ou servidores. E não há folga que permita impor o peso de APIs que não sejam realmente úteis em cada dispositivo, apenas para manter um ideal de portabilidade.

Por exemplo, precisamos de APIs para multimídia porque existem dispositivos (como PDAs e celulares atuais) com telas de boa resolução, câmeras, e capacidade de processamento e memória suficiente para tocar e até gravar vídeos (entre outros recursos multimídia). Mas o que fazer com dispositivos mais modestos, como um celular simples com tela em preto-e-branco, nenhuma câmera, pouca memória, talvez até CPU fraca demais para codificar vídeo? Se a plataforma Java ME obrigasse tal produto a carregar APIs de multimídia, estaria apenas desperdiçando seus já limitados recursos.

Isto é ainda mais verdadeiro considerando que estes dispositivos são muito mais fechados que um PC: embora existam algumas opções de atualização (inclusive ROMs atualizáveis), a maioria das funcionalidades, seja de software ou hardware, ou vem de fábrica ou não é disponível. Não fariam sentido imposições como "vamos obrigar todos os celulares a ter a API para Bluetooth", pois na grande maioria dos aparelhos que não tenham a capacidade de comunicação Bluetooth, isso não é um item opcional que possa ser instalado pelo usuário.

O resultado desta realidade é que a Java ME é estruturada em subconjuntos de funcionalidade. Existem três níveis de divisão: Configuração, Perfil e Extensões.

Configuração

Uma configuração é uma especificação de capacidades da plataforma subjacente (a JVM, e indiretamente, o hardware). A configuração define uma divisão vertical do mercado, orientada pela capacidade dos dispositivos.

Configuração Especificação Observações
Connected Device Configuration (CDC) v1.0: JSR 36 v1.1: JSR 218 CPU de 16 bits ou mais, 192 Kb de memória, rede intermitente / de baixa qualidade
Connected, Limited Device Configuration (CLDC) v1.0: JSR 30 v1.1: JSR 139 CPU de 32 bits, 768 Kb de memória, rede confiável e de desempenho razoável
Tabela 1. Configurações da Java ME

A Tabela 1 lista as configurações existentes atualmente. E como você pode ver pela Figura 1, a CLDC é (grosso modo) um subset da CDC; assim, a maioria das aplicações escritas para a CLDC irá funcionar em implementações da CDC, fora poucas exceções.

Bons exemplos de plataforma CDC são os set-top boxes[1], servidores embarcados (ex.: o software de gerenciamento de um roteador de alta capacidade), ou PDAs / palmtops de alta capacidade. No lado da CLDC, o melhor exemplo é o telefone celular. Os celulares modernos têm capacidade de comunicação contínua e de boa velocidade, mas ainda de alto custo, por isso se encaixam perfeitamente nos limites da CLDC.

Nota:[1] Aparelho complementar à TV / TV a cabo, um produto ainda incipiente no Brasil mas com tendência a decolar a partir da implantação de TV digital.

A exigência mínima de hardware de cada configuração determina diversas funcionalidades que esta configuração suportará ou não. Por exemplo, a CLDC não suporta a JDBC devido ao tamanho e complexidade desta API e à sua dependência de uma conexão de rede de funcionamento contínuo e qualidade e desempenho razoáveis. Já a CDC, mais folgada em ambos os quesitos, tem a possibilidade de suportar a JDBC.

Perfil

Dentro de cada configuração, temos uma divisão "horizontal" que diferencia os dispositivos pela sua aplicabilidade. Dispositivos de capacidade semelhante, mas utilização distinta, implementarão perfis (profiles) diferentes. É possível para um mesmo dispositivo implementar mais de um perfil.

Perfil CDC Especificação Observações
Foundation Profile v1.0: JSR 46 v1.1: JSR 219 Dispositivos sem nenhuma capacidade gráfica
Personal Basis Profile v1.0: JSR 129 v1.1: JSR 217 Dispositivos com display simples, com suporte à AWT e parte da Java 2D. É uma versão “light” do Personal Profile
Personal Profile v1.0: JSR 62 v1.1: JSR 216 Suporte quase total às APIs do J2SE 1.3 e algumas da J2SE 1.4. Substitui a antiga plataforma Personal Java
Tabela 2. Perfis da CDC

A CDC suporta os perfis mostrados na Tabela 2. Todos os perfis da CDC são definidos como subconjuntos da J2SE (versões 1.0: J2SE 1.3, versões 1.1: J2SE 1.4). Os perfis mais avançados são subconjuntos mais completos, ou seja, suportando um número maior de APIs. Devido à maior proximidade com a J2SE, a CDC evita a necessidade de muitas APIs específicas. Por exemplo, a AWT é suportada nos perfis que suportam GUIs, portanto não precisamos de uma API nova como a LCDUI do CLDC/MIDP. Ainda assim, a CDC suporta algumas APIs específicas ao Java ME: as de I/O (GCF) e segurança (as quais veremos na discussão da CLDC), além de uma API exclusiva à CDC, a XLet, para gerenciar aplicações ou serviços simples [2].

Nota: [2] A tradicional API java.applet presume um browser web como container da aplicação, por isso tanto a CDC quanto a CLDC/MIDP (com suas MIDlets) definem APIs semelhantes, porém adaptadas às suas necessidades. As Xlets da CDC (javax.microedition.xlet) são derivadas da antiga Java TV (javax.tv.xlet). São portanto adequadas para aplicações como TV interativa e set-top boxes.

Por exemplo, na comparação entre o Foundation Profile e o Personal Basis Profile, somente o último suporta os pacotes java.awt e java.beans; já na comparação entre este perfil e o Personal Profile, somente este último suporta os pacotes java.applet e java.awt.datatransfer.

Perfil CLDC Especificação Observações
Information Module Profile v1.0: JSR 195 futuro: JSR 228 Dispositivos sem nenhuma capacidade de display. É um subconjunto da MIDP, que basicamente, só remove os seus pacotes de GUI (LCDUI)
Mobile Information Device Profile (MIDP) v1.0: JSR 37 v2.0: JSR 118 v3.0: JSR 271 Dispositivos móveis, com display de tamanho limitado, como telefones celulares. Suporta APIs de GUI (LCDUI), I/O (Generic Connection Framework – GCF) e persistência (RMS). Prevê o suporte a aplicações de voz e telefonia em geral (SMS, etc.)
Personal Digital Assistant Profile v1.0: JSR 75 Para PDAs. Inclui APIs para acesso a dados de informação pessoal (PIM) e para acesso a sistemas de arquivos (FC – File Connection, extensão do GCF)
Digital Set Top Box Profile v1.0: JSR 242 Para set-top boxes, evolui a API pré-J2ME Java TV. Tem capacidades como controle de sintonia ou manipulação de dados de streams MPEG. É um perfil de transição, que serve à geração atual de set-top boxes, mas será obsoleto para a nova geração de aparelhos desse tipo que implementarão a plataforma OCAP (Open Cable Application Platform). Nos dispositivos OCAP, a plataforma Java preferencial será a CDC / Personal Basis acrescidas da Java TV e outras APIs deste perfil.
Tabela 3. Perfis da CLDC

A Tabela 3 relaciona os perfis da CLDC. Vemos que esta configuração é bem mais variada que a CDC. Isso porque estamos lidando com aparelhos mais restritos e – quanto mais distantes de um PC – com mais idiossincrasias, exigindo mais perfis especializados.

Extensões

Cada combinação de configuração / perfil especifica um conjunto de APIs obrigatórias. Mas para tornar as coisas ainda mais interessantes, existe também um bom número de APIs opcionais, que refletem detalhes de capacidade de cada dispositivo. Por exemplo, se o seu celular suporta Java certamente será um dispositivo CLDC/MIDP, o que significa que APIs como a LCDUI (interface gráfica) são garantidamente disponíveis. Mas alguns celulares suportam conectividade Bluetooth (radiofrequência de curto alcance) e/ou IrDA (infravermelho), enquanto outros não. Assim, as API que suportam estas formas de comunicação são opcionais.

Em outros casos a inclusão de uma API pode ser mais uma função do desempenho. Por exemplo, em tese, qualquer celular MIDP poderia suportar a API M3G (gráficos 3D), mas na prática o desempenho seria ruim demais nos aparelhos mais limitados em velocidade de processamento.

As APIs de extensão podem parecer algo semelhante às APIs opcionais das plataformas Java SE e Java EE. Por exemplo, a Java SE não inclui a JavaMail, mas você pode baixar os JARs que contêm esta API, adicioná-los ao classpath da sua aplicação, e começar a enviar e-mails. Isso sem falar, claro, nas milhares de bibliotecas de terceiros: Log4J, Struts, Hibernate, SWT, etc.

A diferença é que no mundo Java ME, é difícil fazer implementações independentes e puro-Java de muitas APIs de nível de plataforma, cuja programação exige alto desempenho e/ou acesso a recursos especiais do sistema. O primeiro problema é que as JVMs ME não têm um compilador JIT tão avançado quanto o das JVMs Java SE. Portanto é mais difícil escrever em Java puro algoritmos com grande demanda de eficiência, tais como decoders de vídeo ou protocolos de rede[3].

Nota:[3] Nos aparelhos mais avançados (e caros), que chegam a ter CPUs de 400 MHz e compiladores JIT já razoavelmente competitivos com código nativo, isso é menos verdade. Mas estes ainda representam uma fatia pequena do mercado.

A saída para este problema, e também para APIs que exigem invocar funcionalidades do sistema operacional, seria usar código nativo. Mas daí entra a segunda limitação: a Java ME não suporta a JNI (a API para invocação de código nativo do Java SE), nem qualquer interface nativa padronizada. Cada JVM tem a sua própria interface nativa; por exemplo, o CLDC HotSpot da Sun possui a KNI. Mas o modelo de segurança das JVMs Java ME proíbe a instalação de classes que definam novos métodos nativos (mesmo em aparelhos com sistemas operacionais abertos que permitem instalar código nativo). Portanto, certas APIs só podem ser implementadas pelos fornecedores dos dispositivos, que têm acesso total ao sistema.

API para Java ME JSR Comentários
Content Handler API 211 Suporte a conteúdo web e multimídia. Aplicações podem ser registradas para tratar tipos MIME específicos. Algo similar à Java Activation Framework do Java SE.
Scalable 2D Vector Graphics API 226 Renderização de gráficos 2D vetoriais. Em especial, suporta o padrão SVG do W3C
Location API 179 Permite à aplicação conhecer a localização física do dispositivo.
Web Services API 172 Suporte a web services (XML, SOAP)
Mobile 3D Graphics API (M3G) 184 Gráficos 3D de alto desempenho, para jogos ou outras aplicações como visualização de dados
Session Initiation Protocol (SIP) API 180 O protocolo SIP permite gerenciar sessões de multimídia IP; também mensagens Instantâneas e outras aplicações
Mobile Internationalization API 238 Permite criar aplicações multilíngue (com resources) e respeitar preferências culturais como formato de datas, ordenação de strings etc.
Wireless Messaging API (WMA) 205 Permite compor, enviar e receber mensagens SMS/MMS
APIs for Bluetooth 82 Suporte a comunicação Bluetooth por radiofrequência e também IrDA (OBEX – OBject Exchange Protocol)
PDA Optional Packages 75 APIs para acesso a PIM (informações pessoais, como agendas de contatos ou calendários) e extensão do GCF para suporte a arquivos
Security and Trust Services API (SATSA) 177 APIs de segurança (autenticação, criptografia etc)
Payment API 229 Suporte a transações de pagamento, por exemplo para clientes de lojas online. Será a API mais perigosa do mundo para um bug de loop infinito ;-)
Mobile Media API (MMAPI) 135 Controle de recursos básicos de áudio e multimídia
Advanced Multimedia Supplements 234 Estende a MMAPI com funcionalidades como controle de câmera, sintonizador de rádio, controles como equalizador de áudio etc.
Java Binding for OpenGL ES API 239 A Open GL ES (Embedded Subset) é uma versão “light” da API 3D OpenGL, já suportada nativamente por muitos dispositivos. Esta JSR define uma interface ultraleve à OpenGL ES, que será mais eficiente que a M3G
Event Tracking API 190 Permite gerar eventos, ex.: número de execuções da aplicação, e transmiti-los para um servidor central interessado em gerenciá-los. Interessante para jogos em rede, vendas de mídia sob demanda etc.
Device Management API 246 Dá acesso a recursos de Gerenciamento de Dispositivo como SyncML/OMA e WAP/DMA
Mobile UI Customization API 258 Permite customizar o look-and-feel da GUI do dispositivo, escolhendo "temas" e configurações mais refinadas
XML API 280 APIs SAX, StAX e DOM para processamento de XML
Mobile Broadcast Service API for Handheld Terminals 272 Manipulação de transmissões de conteúdo por difusão, como TV digital
Contactless Comunication API 257 Suporte a opções de comunicação como RFID, NFC (Near Field Communication) ou códigos de barras
Mobile Telephony API (MTA) 253 Acesso a funcionalidades de telefonia: iniciar e receber chamadas, notificações de status de chamada, controle de serviços avançados como teleconferências etc.
Data Sync API 230 Sincronização de dados da aplicação, armazenados no dispositivo, com um servidor / PC / outro dispositivo
Unified Message Box Access API (UMBA) 266 Acesso às caixas de mensagens (SMS/MMS, e-mail etc.) do dispositivo
IP Multimedia Subsystem (IMS) Services API 281 Funcionalidades de multimídia sobre IP: voz, vídeo, jogos multiplayer, etc.
Mobile Sensor API 256 Acesso a sensores disponíveis no dispositivo. Inclui sensores físicos (ex.: consumo de energia, temperatura, pulso arterial, qualidade de recepção de radiofrequência), dispositivos de entrada especiais (ex.: controles para jogos) e outros
Ad Hoc Networking API 259 Comunicação P2P (peer-to-peer)
JDBC Optional Package for CDC / Foundation Profile 169 Subconjunto da JDBC para a configuração CDC
RMI Optional Package 66 Suporte à RMI para CDC. No mundo dos dispositivos Java ME, é útil, por exemplo, para suporte à Jini
Mobile Operational Management 232 Gerenciamento (instalação, atualização etc.) de serviços, bibliotecas e aplicações, nativas e Java
Advanced Graphics and User Interface (AGUI) Optional Package 209 Especifica subconjuntos de APIs avançadas de gráficos e GUI da Java SE: Java 2D, Swing, ImageIO, Input Method Framework
Resource Management API 278 Gerenciamento de recursos utilizados pelas aplicações, especialmente útil para dispositivos suportando a execução de múltiplas aplicações Java ME simultâneas
Real-Time Specification for Java (RTSJ) 282 A RTSJ, inicialmente (v1.0: JSR 1) era orientada à J2SE. Mas como a maioria dos sistemas embarcados / de tempo real são dispositivos limitados em recursos, a RTSJ 1.1, embora continue suportando todas as edições do Java, é mais alinhada com a Java ME
Distributed Real-Time Specification 50 Extensão da RTSJ para comunicação (ex.: RMI) com latências previsíveis
Java USB API 80 Suporte a I/O via interfaces USB
Service Connection API 279 Suporte a web services / SOA e outros modelos exigindo descoberta, autenticação e identificação
Presence 186 API independente de protocolo para Presença: permite, por exemplo, determinar quais dos seus contatos de Mensagens Instantâneas estão online
SIMPLE Presence 164 Estende a JSR 186, suporta Presença com o protocolo SIMPLE (extensão do SIP, JSR 180)
Instant Messaging 187 API fundamental para Mensagens Instantâneas, independente de protocolo. Parte do grupo de APIs JAIN
SIMPLE Instant Messaging 165 Estende a JSR 187, suporta Mensagens Instantâneas com o protocolo SIMPLE (extensão do SIP, JSR 180)
Performance Metric Instrumentation 138 Permite medir o desempenho de aplicações: instrumentação, coleta de dados, correlação
Java Speech API 113 Suporte a reconhecimento e sintetização de voz
Tabela 4. APIs de extensão para a plataforma Java ME

A Tabela 4 (ufa!) relaciona todas as APIs de extensão mapeadas pela Figura 1. Existem três fatos importantes que devemos salientar. O primeiro é o seu enorme número – parece que a Java ME tem muito mais APIs que a Java SE e Java EE juntas!

Mas isso é explicado pelo segundo fato: as especificações para Java ME têm granularidade muito fina, é quase uma JSR para cada dúzia de classes. Veja, por exemplo, as APIs para Instant Messaging: estão divididas em quatro JSRs distintas (ainda que interdependentes, ex.: a JSR 164 é uma extensão da JSR 186). Se fosse na Java SE, haveria uma única "Instant Messaging API" com todas as classes e funcionalidades dessas quatro APIs. E muitas outras (JSRs 211, 226, 238, 135, 209, 280, 75, 169, 66, 186, 281, 177 – pelo menos!) são APIs que correspondem a funcionalidades fundamentais da Java SE: XML, internacionalização, gráficos 2D etc., só que na Java ME são opcionais e independentes. Na Java ME, esse particionamento maior das APIs é importante porque permite a cada dispositivo carregar somente o peso das APIs realmente necessárias.

Em terceiro lugar, observe (novamente pela Figura 1: APIs com rótulo esmaecido, in progress) que cerca de metade de todas as APIs que listamos ainda está em desenvolvimento. Isso sem falar nas que já estão finalizadas e implementadas por alguns produtos, mas são bastante recentes e por isso ainda não têm um parque instalado muito amplo. Portanto, não há porque se decepcionar se após ter gastado uma fortuna para comprar aquele celular ou PDA de última geração, a maquininha ainda não suporta 80% das APIs listadas aqui. É que há muita coisa nova mesmo. Curso de Java SEEstamos bem no meio da explosão da plataforma Java ME

A evolução da Java ME e dos dispositivos

A variedade de configurações e perfis e APIs opcionais da Java ME parece confusa, mas a situação é menos ruim do que parece porque muitas das diferenças entre perfis são pequenas – uma ou duas APIs a mais ou a menos, comparando com o perfil mais próximo.

Embora existam todas essas opções, na prática, a esmagadora maioria do mercado é dominada por uma única combinação de configuração e perfil: CLDC / MIDP. São os telefones celulares, cuja atual popularidade, somada à adesão maciça dos vendedores à Java ME, faz a MIDP superar em ordens de magnitude qualquer outro perfil da Java ME. Como o que interessa é desenvolver para o mercado, no restante deste artigo e da série como um todo iremos dar mais atenção à CLDC e MIDP.

As definições de configuração e perfil evoluem continuamente, de forma a acompanhar a crescente capacidade dos equipamentos. Por exemplo, já existem PDAs com capacidade para executar a Java SE completa, portanto, a Java ME / CDC tende a se tornar menos relevante nestes aparelhos. Mas sempre há novos aparelhos limitados, devido à integração de tecnologia digital numa variedade cada vez mais incrível de produtos. Assim, se daqui a alguns anos a configuração CLDC não for útil para celulares porque estes terão CPUs de 1 GHz e 4 Gb de memória, talvez precisemos da CLDC para roupas inteligentes, embalagem monitoradas, eletrodomésticos ligados à web, e toda espécie de produto hoje considerado futurístico. (Ficção? Aparelhos de telefonia sem fio, móveis e com vídeo apareceram primeiro em Dick Tracy; aliás, o Comunicador de Star Trek nem tinha vídeo[4])

Nota:[4] Comparando com o Comunicador, meu celular só perde na falta de capacidade de transmissão de dados mais rápida que a luz e livre de interferência pelo "sub-espaço". Talvez na 4G...

Escolhendo um dispositivo para desenvolvimento

A frequência da atualização dos equipamentos, o alto custo dos modelos de última geração, e a arquitetura relativamente fechada dos mesmos torna difícil a decisão de adquirir dispositivos para finalidades de desenvolvimento. O ideal é ter um aparelho que suporte as versões mais recentes da configuração / perfil desejada, e o maior número possível de APIs de extensão. Isso não se traduz necessariamente nos produtos mais caros, mas certamente implica em produtos atuais. Mesmo em celulares e PDAs com ROMs atualizáveis que permitem atualizar o software de sistema, inclusive a JVM, seus fornecedores praticamente nunca oferecem atualizações com acréscimo de funcionalidade, só correções. Então, se você comprar um aparelho sem aquela nova API de última geração, não tem jeito – quando quiser a nova API terá que comprar outro aparelho (ou depender de emuladores).

Conhecendo a Configuração CLDC

Vamos começar a examinar a variante principal da Java ME, que é de longe a mais popular. Aliás, alguns aparelhos, como o do autor, só suportam Java ME (não permitem instalar aplicações nativas – para que? Pegar vírus via Bluetooth?).

A configuração CLDC 1.1, definida pela JSR 139, é orientada a dispositivos com as características sumarizadas na Tabela 1. Detalhando um pouco mais, a exigência mínima de 192 Kb refere-se à memória de uso exclusivo da JVM (sem contar o espaço para o sistema operacional e outras finalidades). A implementação inteira (VM e bibliotecas) deve caber numa ROM de 160 Kb, e o heap, que exige RAM, precisa ter um tamanho mínimo de 32 Kb.

Estes requisitos de hardware são um mínimo denominador comum, e não levam em consideração nenhum tipo de otimização (como compiladores JIT) ou mesmo as APIs de qualquer perfil. Como você nunca encontrará uma implementação de CDC "puro" (sem APIs adicionais exigidas por algum perfil), na prática, a exigência de hardware será pelo menos um pouco maior, mesmo para uma JVM puramente interpretada. Mesmo assim, é um alvo bastante impressionante. Lembre-se que JVMs modernas para PCs (e também qualquer runtime de linguagens do mesmo nível: .NET, Ruby etc.) não rodam sequer um "alô, mundo" sem consumir alguns megabytes... e aquele limite de 192Kb é para executar aplicações reais, completas e úteis!

CLDC: A Linguagem Java

Junto com o patamar básico de hardware, o mais importante aspecto de uma configuração é o subconjunto da linguagem Java que ela suporta. O Java suportado pela CLDC 1.1 pode ser facilmente explicado como "o Java que você já conhece, menos os seguintes itens":

E é só isso. A versão anterior (CLDC 1.0) era bem mais limitada, pois não dava suporte a tipos numéricos de ponto flutuante (float e double), nem qualquer suporte a referências fracas[5]. Estas limitações foram eliminadas, mas isso não quer dizer que você deva esquecê-las totalmente, pois ainda são recursos de custo relativamente alto.

Nota: [5] A CLDC 1.1 suporta o tipo WeakReference, sendo que a Java SE possui tipos adicionais de referência fraca. Mas a WeakReference é suficiente para 90% das necessidades, especialmente em heaps pequenos.

Mesmo na geração CLDC 1.1, a grande maioria dos dispositivos não possui suporte de hardware para ponto flutuante (FPU). Cálculos com números float e double são muito mais lentos do que cálculos semelhantes com inteiros, pois precisam ser emulados. Por isso, aplicações CLDC (ou mesmo, Java ME em geral) que precisem de alto desempenho computacional, procuram evitar matemática de ponto flutuante. Isso é mais fácil de dizer do que fazer, mas quase sempre é possível. Por exemplo, muitos cálculos com valores financeiros podem ser feitos com inteiros, bastando multiplicar tudo por 100 (usando centavos como unidade).

Considerando as restrições dos dispositivos, a CLDC 1.1 define uma JVM surpreendente próxima da Java SE. Praticamente todas as vantagens tradicionais do Java estão lá: bytecode portável e seguro, modelo de memória robusto com garbage collection, suporte integrado a threads e monitores... recursos até poucos anos considerados luxuosos mesmo em PCs! O suporte a threads é um dos itens mais intrigantes, porque nas primeiras gerações de dispositivos CLDC, a maioria usava sistemas operacionais que nem sequer suportavam threads ou qualquer forma robusta de multitarefa. Isso obrigava as JVMs a assumir a tarefa, implementando threads por conta própria.

CLDC: A VM Java

A configuração CLDC também lista algumas limitações da especificação da VM, que são relevantes porque determinam as APIs que podem ser suportadas:

CLDC: Segurança

Os dispositivos CLDC não têm capacidade suficiente para implementar o extenso e sofisticado sistema de segurança do Java SE, que é um dos grandes responsáveis pelo sucesso da tecnologia Java. Ao mesmo tempo, no mundo wireless, segurança é mais importante do que nunca, e essa é uma das principais vantagens do Java ME na comparação com aplicações nativas[6]. Como conciliar estes fatos?

Nota: [6] A despeito do comentário estapafúrdio de Steve Jobs, explicando que seu ultra fechado iPhone não suportará Java ME (nem qualquer SDK aberto) porque não quer que alguma aplicação defeituosa "derrube toda a rede da [operadora] Singular na costa oeste dos EUA"... seria muito mais provável uma aplicação nativa fazer isso, se é que é possível.

Existem duas soluções. A primeira é simplesmente omitir as funcionalidades que criam mais riscos de segurança (e exigiriam uma infraestrutura de validação mais complexa). Isso inclui reflection, JNI, e classloaders definidos pelo usuário.

A segunda solução é o já famoso sistema de pré-verificação de bytecode. O verificador do Java SE é um algoritmo que a JVM executa para determinar se o bytecode carregado de arquivos .class é válido, e não contém nenhuma "trapaça" como tentar converter entre ponteiros e inteiros (o que daria ao programa Java acesso total à memória). O problema é que este verificador é complexo, pois precisa executar uma trabalhosa "inferência de tipos" – descobrir o tipo de cada posição do stack de cada método[7]. A solução da Java ME foi criar um novo verificador que é dividido em duas fases. O pré-verificador processa os .class já compilados no seu ambiente de desenvolvimento, e produz informações adicionais (tecnicamente: atributos StackMap) que são inseridas nos arquivos .class. Estas informações adicionais permitem à VM CLDC usar um verificador muito mais simples e rápido.

Nota:[7] Estas posições do stack armazenam variáveis locais e valores temporários como argumentos e retornos de métodos. Por exemplo, um método:
void teste () { system.out.println(10); } usa duas posições do stack, pois sua versão compilada em bytecode é algo como: push System.out push "Alô" invokestatic PrintStream.println(int)

Portanto, para o stack deste método, a posição 0 conterá uma referência para objeto que será o this do método invocado (o stream System.out), e a posição 1 conterá um int. O StackMap é uma tabelinha que, para este método, dirá: "na posição 0 há um objeto, na posição 1 há um int".

Não é possível enganar o sistema gerando atributos StackMap incorretos / maliciosos, pois o verificador da VM valida estes atributos. O segredo é que o StackMap é projetado de tal forma que sua validação é muito fácil, portanto esta etapa não elimina o ganho obtido com a verificação mais eficiente das classes.

CLDC: Bibliotecas

O número de APIs exigidas pelo CLDC é bastante pequeno. Não vamos listá-las aqui, pois depois você verá que o perfil MIDP adiciona algumas APIs, portanto será mais fácil examinar a lista de APIs do perfil, uma vez que ninguém vai programar somente com a configuração.

O perfil MIDP 2.1

O perfil MIDP estende a configuração CLDC, aumentando alguns requisitos e criando outros:

Nota: [8] Uso os termos "ROM" e "RAM" num sentido relaxado: ROM = memória não-volátil, talvez permitindo só escrita; RAM = memória livremente atualizável, mas talvez volátil. As especificações da Java ME não determinam nenhuma escolha específica de tecnologia de memória principal ou de massa, portanto, seria perfeitamente válido um dispositivo que utilizasse um microdisco rígido para implementar os 8 Kb de "RAM" exigidos pelo perfil MIDP.

O MIDP também especifica algumas APIs Java, além das exigidas pelo CLDC. Juntando ambos, temos a relação de APIs exibida na Tabela 5. As classes e pacotes em itálico são as adicionadas pela MIDP; as sem itálico são as definidas na configuração CLDC.

Package Comentários
ava.lang Object, wrappers (ex.: Integer), Class, Math, Runtime, System, String e StringBuffer, Thread e Runnable
java.lang.ref Reference, WeakReference
java.io Input/OutputStream[Reader], ByteArrayInput/OutputStream, DataInput/OutputStream, PrintStream, Reader e Writer
java.util Calendar, Date e TimeZone; Hashtable, Vector, Stack e Enumeration; Random; Timer e TimerTask
javax.microedition.io GCF (Generic Connection Framework)
javax.microedition.lcdui LCDUI, toolkit especializado em GUIs para dispositivos MIDP
javax.microedition.lcdui.game Utilitários para programação de jogos ou animações em geral.
javax.microedition.media Mobile Media API (JSR 135).
javax.microedition.media.control APIs para manipular controle de volume e tons musicais.
javax.microedition.midlet Suporte a MIDlets, ponto de entrada e controlador da aplicação.
javax.microedition.pki Certificados para estabelecimento de conexões seguras.
javax.microedition.rms RMS (Record Management System), uma base de dados simples que armazena e recupera registros chaveados por ID numérico.
Tabela 5. APIs padrão da CLDC + MIDP

A relação de APIs na Tabela 5 é definitivamente espartana. Começando pelos conhecidos java.*, basta dizer que listei todas as classes e interfaces destes três pacotes (só não as exceções). Além disso, as classes incluídas não são necessariamente completas. Por exemplo, temos a java.lang.String, mas esta classe não é igual à java.lang.String da Java SE: é um subconjunto. A versão ME não possui métodos de uso mais especializado ou menos frequente, como compareToIgnoreCase() ou contentEquals(); nem os métodos que suportam expressões regulares como split(). Mas a maioria da funcionalidade está disponível: charAt(), substring(), indexOf(), startsWith(), regionMatches()... e até mesmo os construtores que aceitam o nome da codificação Unicode (veja o quadro "O Java ME e o Unicode").

Já as APIs específicas à Java ME, em subpacotes de javax.microedition, são "completas" no sentido de não serem versões reduzidas de outra API, mas são muito simples em comparação com as que conhecemos em Java SE/EE. Por exemplo, a LCDUI, somando todos os seus pacotes, tem um total de 38 classes, interfaces e exceções. E cada uma dessas classes é bem menor do que sua equivalente num toolkit de GUI para Java SE. Por exemplo, a classe javax.microedition.lcdui.Item, que é a base de todos os componentes, tem apenas 14 métodos – compare isso com os mais de 230 métodos da java.awt.Component (e isso sem falar da Swing!).

Design de APIs ME

As APIs mais fundamentais da Java ME (java.lang.*) são subconjuntos compatíveis da Java SE. É algo que já mencionamos mas é bom definir de forma exata: pode-se remover classes e métodos menos essenciais, mas o que sobrar será igual. Ou seja, a Java ME não criará métodos em classes herdadas da Java SE, nem mesmo novas classes em pacotes da Java SE. Assim, qualquer código escrito apenas com as APIs compartilhadas irá compilar e funcionar em todas as plataformas Java, da ME à EE. Isto permite um grau de reutilização de código, por exemplo para bibliotecas utilitárias. Por exemplo, código que necessite de coleções, se quiser rodar em todas as plataformas Java, terá que se limitar às velhas collections do JDK 1.1 (Hashtable, Vector e Stack), pois são as únicas disponíveis na Java ME. Mas se algum código usar as novas (e superiores) collections do Java 2 como Map, List etc., terá que ser modificado para ser compatível com a Java ME.

O mesmo se aplica para algumas das APIs de extensão (mas não todas). Por exemplo, a JSR 280 (APIs XML para Java ME) contém subconjuntos dos pacotes javax.xml, org.xml.sax e org.w3c.dom, com suporte às APIs SAX, StAX e DOM. Esta JSR ainda está sendo finalizada enquanto escrevo, mas o release final deve estar pronto quando você ler esta edição. Até lá, há vários parsers não-oficiais para Java ME, veja developers.sun.com/techtopics/mobility/midp/articles/parsingxml. Todos têm em comum a parcimônia de recursos: alguns têm o incrível tamanho de 6 Kb. Em todos os casos, o custo é a limitação de funcionalidades. Por exemplo, nenhum parser tem o mínimo suporte para validação, seja com DTD ou mesmo linguagens de schema como XSD ou Relax.

As aplicações devem ser feitas de forma a contornar os limites da plataforma Java ME, o que muitas vezes – no caso de aplicações distribuídas – pode ser feito "descarregando" o trabalho mais pesado para fora do dispositivo. Por exemplo, digamos que você criou uma sofisticada aplicação de controle de estoque que usa um celular ou PDA como coletor de dados, utilizando sua câmera para capturar e interpretar códigos de barras. Após revisar todo o estoque, o funcionário aproxima-se de um computador com interface Bluetooth e transmite um XML com os dados coletados para o servidor das aplicações; depois, recebe do servidor outro XML com a lista de reposições. Nenhum destes documentos XML precisa ser validado no dispositivo, mesmo que haja risco de possuírem erros (digamos porque os documentos de reposição contêm dados extraídos de uma database legada que é pouco consistente). A solução é colocar as validações no servidor, na interface de comunicação via Bluetooth: todo documento recebido será validado, e todo documento a ser enviado também. Assim, não é necessário instalar no celular um parser muitíssimo maior que suporte Schemas XSD. Isso provavelmente exigiria um dispositivo mais avançado (configuração CDC), penalizando o seu projeto com um aumento de custo totalmente desnecessário.

APIs novas, híbridas, opcionais...

Se algumas APIs são enxugadas e precisamos nos habituar à ausência de muitas classes e métodos, outras são substituídas por APIs completamente novas, que temos que aprender do zero. Exemplos são as já citadas LCDUI, GCF e RMS do CLDC/MIDP. Outros exemplos são a Content Handler API para invocação de aplicações via URL (javax.microedition.content), as APIs para protocolos Bluetooth (javax.microedition.bluetooth, javax.microedition.obex), ou a Mobile 3D Graphics (javax.microedition.m3g).

Em outros casos ainda, foram criadas soluções híbridas: APIs que são em parte subconjuntos das equivalentes na Java SE, em parte novos pacotes específicos a Java ME. O melhor exemplo disso talvez sejam as APIs de segurança. A JSR 177 (Security and Trust Services API) mistura pacotes reduzidos da Java SE como java.security e javax.crypto, pacotes específicos à Java ME como javax.microedition.pki, e até pacotes para a plataforma JavaCard como javacard.security. Esta mistura da Java ME com JavaCard (que são plataformas totalmente independentes) na mesma especificação é esquisita, mas tem uma razão: o cenário muito interessante de interoperabilidade entre dispositivos de ambas as famílias. Esta interoperabilidade é habilitada por pacotes como javax.microedition.apdu, que implementa no dispositivo Java ME o protocolo APDU (Application Protocol Data Unit / ISO 7816-4), que é utilizado na comunicação com smart cards. Apesar de definidos por uma única JSR, vários destes pacotes são APIs opcionais. Portanto, se você ler no manual do celular que o aparelho "implementa a JSR 177" isso não significa necessariamente que suporte APDU – pouco provável, pois nunca vi um celular com interface física para smart cards.

Reaprendendo APIs

Todo o cenário que explicamos parece ser ruim, pois mesmo quem já conhece todas as APIs do Java SE terá que reaprender outras. Mas isso é inevitável. Examinando as APIs reduzidas (subconjuntos), a realidade é que as versões "full" do Java SE não cabem nos dispositivos. E não é só o Java. Outras plataformas de desenvolvimento para sistemas limitados – Symbian, BREW, PocketPC, Compact .NET[9] – também são bastante enxugadas em comparação com seus equivalentes em desktop como POSIX, Win32 ou .NET 3.0.

Nota: [9] O Compact .NET parece mais próximo do .NET normal (é um subconjunto, dependendo menos de APIs novas), mas isso acontece porque o Compact .NET dá suporte somente a uma classe de dispositivos mais high-end: smartphones e PDAs com vários megabytes de memória. Portanto, deve ser comparada apenas ao CDC / Personal Profile, que também é muito mais próximo à edição Standard do Java – tem quase todas as APIs do J2SE 1.3.1, a maioria das que faltam (como a JDBC) sendo disponíveis como pacotes opcionais – e executa em dispositivos do mesmo porte.

Quanto às APIs totalmente novas, algumas bibliotecas do Java SE são grandes e complexas demais para que seja possível remover muita coisa sem que desmoronem; por exemplo, a Swing[10]. Quando é possível remover apenas um pouco isso é feito, mas o resultado pode ser ainda grande demais para a configuração CLDC; por exemplo, ver a AGUI (JSR 209) para CDC.

Nota: [10] Existem implementações de Swing para CDC / Personal Profile, mas isso não é coberto por nenhum padrão. Uma alternativa de GUI mais avançada para Java ME é a SWT da Fundação Eclipse, especificamente a eSWT. Mas estas APIs, ainda não suportadas oficialmente pelo JCP, são compatíveis com um número de dispositivos muitíssimo menor do que os que rodam a LCDUI ou mesmo a AWT. Com o recente ingresso da Fundação Eclipse no JCP, talvez isso mude.

Outras APIs foram planejadas para a realidade do Java SE; por exemplo, a java.io exige classes distintas para abrir cada tipo de dispositivo de entrada e saída. Usamos FileInput/OutputStream arquivos em disco, PipedInput/OutputStream para pipes, Socket e ServerSocket para TCP/IP, URLConnection para HTTP e assim por diante. A vantagem é que existem construtores especializados em abrir cada tipo de stream, aceitando nomes de arquivos, endereços TCP/IP, URLs e outros tipos de parâmetro, e também classes utilitárias encapsulando cada caso: File, InetAddress, URL. É um bom design OO convencional, com vantagens como eficiência.

Já a Java ME, em casos como este, prefere APIs projetadas com princípios diferentes, priorizando a simplicidade. Assim, com a GCF você pode abrir streams de qualquer espécie com uma única API – Connector.open(URI). Por exemplo, para abrir um arquivo, basta uma URI como "file:///nome.txt". E o interessante é que este design não exclui, necessariamente, o grau de controle que as APIs de I/O do Java SE possuem. A URI pode conter parâmetros (após um ";") conforme cada tipo de stream, e open() retornará uma instância de uma interface como SocketConnection, que oferecerá métodos específicos para o tipo de conexão aberta. A maior desvantagem do Connector.open() é que sua implementação exige fazer parsing de vários formatos de URI, o que é menos eficiente do que usar um construtor especializado. Mas como esta pequena ineficiência só afeta o tempo de abertura de conexão – e mesmo isso é pouco significativo, em comparação com o tempo de abertura de muitos tipos de stream – é um negócio excelente[11].

Nota: [11] Tão bom, na verdade, que a JSR 197 padronizou esta API para a plataforma Java SE. Infelizmente isso ainda não foi implementado, mas há propostas de inclusão na Java SE 7.

Conclusões

Neste artigo, procuramos dar uma visão abrangente e aprofundada da plataforma Java ME inteira, a qual é hoje um verdadeiro oceano de especificações e tecnologias. É claro que você não precisa dominar tudo. As APIs que realmente terá que aprender serão aquelas disponíveis no dispositivo alvo do seu projeto e úteis para seu nicho de aplicação. Mas é muito útil ter uma noção de tudo o que está disponível (ou estará, com a introdução das JSRs hoje em desenvolvimento). Até para conhecer as possibilidades destas plataformas ainda desprezadas por muitos sistemas que poderiam se beneficiar da disponibilidade cada vez maior de dispositivos com capacidade Java.

No próximo artigo, deixaremos a teoria em segundo plano e começaremos a programar em CLDC / MIDP, examinando ferramentas, técnicas e APIs utilizadas para isto.

O Java ME e o Unicode

Em meio a tantas restrições, é notório que a Java ME (qualquer das suas configurações) suporta internacionalização, ainda que com limites. Esta plataforma pode ser utilizada para escrever aplicações em praticamente qualquer idioma. Ou seja, possui capacidades fundamentais como o suporte a Unicode, que permite representar, manipular e exibir texto em qualquer idioma – desde que o dispositivo possua fontes e mecanismos de entrada que suportem o idioma desejado. Mas as configurações mais limitadas (todas abaixo da CDC) não vêm com APIs para desenvolvimento multilíngue, como ResourceBundle. Isso ainda pode ser feito, só dá um pouco mais de trabalho. Mas em Java ME, o ideal é fazer um build separado da aplicação para cada idioma suportado. Inchar seus JARs com resources para diversos idiomas é o tipo de luxo ao qual os programadores de dispositivos com limitações de memória raramente se permitem.

Quanto à localidade, bem, se você quiser imprimir números como Reais ou datas no formato brasileiro, tem que escrever código específico para isso – abaixo da CDC, não existe o package java.text, com APIs como DateFormat ou NumberFormat. Na CLDC/MIDP, a maioria dessas necessidades de formatação será satisfeita por componentes da LCDUI, como DateField, que irão exibir tais valores da forma correta para o idioma / localidade nativos do dispositivo.

Isso melhorou com a JSR 238 (Mobile Internationalization API), mas esta API ainda não é de ampla disponibilidade. Meu celular novo, por exemplo, não a suporta, embora suporte outras coisas recentes (e pesadas) como a M3G.

Somando tudo, o nível de suporte do Java ME para as necessidades de localização e internacionalização, mesmo sem contar a JSR 238, é excelente para uma plataforma embarcada. Considerando que o suporte a Unicode já obriga todos os caracteres e Strings a ocuparem dois bytes por caractere, ao invés de um byte se suportasse apenas ASCII, o fato de o Java manter esse suporte mesmo nos dispositivos mais limitados confirma a visão pioneira do Java em adotar o Unicode. De fato, não existe nenhuma variante da plataforma Java que tenha os tipos char e String[12], mas na qual estes tipos não sejam Unicode.

Nota: [12] A qualificação não é redundante. Na JavaCard, a versão do Java para smart cards (não faz parte da família Java ME), não existem strings nem caracteres.

Devices MIDP: Fragmentação e Portabilidade

Uma das críticas que se pode encontrar em fóruns sobre Java ME é a fragmentação ainda grande desta plataforma. Até já vi enunciada uma "regra do 50/90", segundo a qual você deve fazer 50 versões da sua aplicação para cobrir 90% do mercado (dentro de uma única configuração e perfil!) Isso é assustador para desenvolvedores das plataformas Java SE / EE, habituados a escrever uma só versão para 100% das plataformas, ainda que talvez com algum trabalho, como testes para evitar bugs específicos de alguma JVM ou servidor de aplicação. Não é a portabilidade justamente uma das principais razões para usar Java, e não deveria ser o resultado de todo este esforço do JCP para criar padrões ótimos para cada classe de dispositivo inteligente?

O problema começa pela plataforma subjacente. Há centenas de combinações de fatores como dispositivos de entrada, resolução de tela, capacidade física (como tamanho máximo do heap), e capacidades específicas (como protocolos de comunicação suportados). Quem quiser tirar proveito de recursos de ponta enfrentará ainda mais dificuldade, pois poucos aparelhos serão capazes de executar o programa, e ao contrário da Java SE, não dá para embutir na sua aplicação os JARs contendo APIs recentes que não façam parte da maioria dos runtimes. E depois ainda existem problemas específicos às implementações, por exemplo, muitos celulares não permitem instalar aplicações acima de 300 Kb, mesmo que exista muito mais RAM disponível que isso.

Mesmo com tudo isso, não é tão difícil criar aplicações Java ME que executem bem em muitos aparelhos diferentes, desde que estes programas não sejam muito ambiciosos. Se você se limitar aos recursos cobertos por APIs oficiais e não demasiado recentes, poderá contar com sucesso em executar o programa numa grande variedade de aparelhos. Há o risco de esbarrar em bugs específicos de determinado fornecedor, e infelizmente a única forma ideal de prevenir tais bugs é testar o programa em vários aparelhos. Os emuladores genéricos do Wireless Toolkit da Sun (e melhor ainda, os emuladores dos SDKs específicos de fornecedores de celulares) permitirão reproduzir os dispositivos com fidelidade suficiente para pegar alguns bugs de portabilidade, mas não todos.

Outro obstáculo para a portabilidade é a limitação de desempenho do hardware, que inviabiliza muitas técnicas de programação de alto nível que seriam úteis justamente para tornar a aplicação mais flexível e adaptável.

Exemplo - Gráficos Animados

Não há melhor exemplo de tudo o que discutimos aqui que o problema da resolução gráfica, que afeta principalmente os jogos e outras aplicações com gráficos sofisticados ou animados. No meu aparelho, por exemplo, a resolução é de 176x220 com 262.144 cores, sempre. Um programa feito especificamente para funcionar em 128x128 (ex.: um jogo) irá funcionar, mas não muito bem: em geral ficará centralizado com bordas vazias, e a parte utilizada da tela poderá ser pouco legível, por conter gráficos elaborados para aparelhos com pixels maiores. Na plataforma Java SE isso seria fácil de resolver, seja com gráficos desenhados pela Java2D, seja redimensionando bitmaps. Já na CLDC não existem APIs para redimensionar imagens, e embora existam APIs para gráficos vetoriais, o desempenho poderá não ser satisfatório para gráficos animados.

Em alguns casos é possível programar a aplicação de forma a usar qualquer resolução de forma inteligente. Mas é preciso cuidar se o espaço a mais (ou a menos) não terá impactos sutis, mas importantes. Por exemplo, num jogo de labirinto, uma forma de adaptar-se a uma resolução maior seria simplesmente exibindo uma porção maior do labirinto na tela, exigindo menos scroll. Isso funciona, só que deixa o jogo mais fácil, pois o usuário consegue ver uma porção maior do caminho, e terá que usar menos a memória.

A única solução perfeita para o problema (pelo menos do ponto de vista do usuário, não do desenvolvedor!) é oferecer diferentes versões do programa para cada resolução de pixels e cores importante. Isso proporciona a melhor experiência para o usuário e o melhor desempenho. Mas se você quiser suportar muitos aparelhos, deve adaptar os gráficos para uma variedade de resoluções populares.

Assim como a resolução gráfica, outras variações nos dispositivos, como a presença ou não de APIs de extensão úteis ao seu programa, podem forçá-lo à escolha entre fazer várias versões da aplicação (mais trabalhoso), ou dotar seu código de inteligência e flexibilidade para adaptar-se a cada dispositivo. Uma excelente solução para muitas aplicações é a ferramenta open source J2ME Polish (j2mepolish.org), que contorna muitas limitações e problemas de compatibilidade – existe até suporte a cálculos em ponto flutuante para dispositivos CLDC 1.0.


Revista Java Magazine Edição 44

  • :
    Confira nesta edição de Java Magazine JAVA EE, crie uma aplicação Java EE 5 usando JSF, AJAX e EJB 3.0 começando com requisitos, modelagem e implementação da persistência com JPA.

Saiu na DevMedia!

  • Buscas semânticas com Elasticsearch:
    Elasticsearch é uma ferramenta de busca de alta performance. Com ela podemos armazenar grandes volumes de dados, analisá-los e processá-los, obtendo resultados em tempo real.

Saiba mais sobre Java ;)

  • Guia do Programador Java:
    Aprender Java não é uma tarefa simples, mas seguindo a ordem proposta nesse Guia, você evitará muitas confusões e perdas de tempo no seu aprendizado. Vem aprender java de verdade, vem!
Links

Artigos relacionados