Aprenda Comigo: Java —Parte 3
Olá, mundão velho sem porteira
Depois de dar uma olhada na lataria e fazer uma inspeção visual no motor, é natural que se dê a partida e siga-se com umas aceleradas quando se está avaliando um carro. Faremos a mesma coisa com o Java, hoje.
Instalação
O Java já vem instalado na maioria das distribuições Linux, embora haja um problema evidente de versão. Não no número da versão, mas o “fabricante” da mesma. Existe o “Open JDK”, por exemplo, e o “Oracle JDK”. Para quem declara imposto de renda e usa Linux, sabe que isso gera uma dorzinha de cabeça anual, já que os funcionários públicos da Receita não “homologam” o “Open JDK”, o que nos obriga a instalar a mesma versão do Java, mas de outro “fabricante” (a Oracle).
Vai entender…
Mas há um aspecto interessante, ainda, que é a existência do Java JDK e Java JRE. Basicamente, o JRE (Java Runtime Environment) é a máquina virtual/interpretador (JVM) e as bibliotecas relacionadas, tudo o que, a princípio, é necessário para rodar um programa escrito em Java.
O JDK é o Java Development Kit, que já contém o JRE e inclui ferramentas para desenvolvimento, como o compilador de Java ( javac
).
Olá, mundão
Quero desenvolver um pequeno programa que funciona assim: se eu executá-lo sem argumento algum, ele imprime no terminal “Olá, mundo”. Mas se eu passar parâmetros, eles devem ser unidos ( string join
) separados por um espaço em branco (``) e devem ser impressos logo após “Olá, “.
Em Python
Para essa série, usarei Python como nossa “meta-linguagem”:
#!env python3import sysdef say_hello(who):
print("Olá, {}".format(who))if __name__ == '__main__':
who_words = sys.argv[1:]
who = ' '.join(who_words) or 'mundo'
say_hello(who)
Muito simples: para imprimir o “Olá, <alguma-coisa>` nós temos uma função que recebe o <alguma-coisa>
como parâmetro. Abaixo dela, verificamos se o arquivo está sendo chamado direto da linha de comando, caso em que __name__
é igual a __main__
, já que não queremos que aconteça nenhum I/O caso este mesmo aquivo seja chamado como um módulo.
O script poderia ser ainda mais simples caso ignorássemos o __name__
e outros detalhes, mas achei interessante já fazer do jeito certo para vermos como, mesmo assim, uma tarefa simples acaba refletindo em código também simples. (Ah, e isso fará todo sentido quando escrevermos testes. Confie em mim.)
Em Java: primeira versão
class HelloWorldApp {
public static void hello(String[] who) {
System.out.println("Olá, " + who);
} public static int main(String[] args) {
StringJoiner string_joiner = new StringJoiner(" ");
for (Iterator<String> i = args.iterator(); i.hasNext();) {
if (i.nextIndex() == 1) {
continue;
}
item = i.next();
string_joiner.add(item);
} this.hello(string_joiner.toString());
}
}
Aff, como foi [desnecessariamete] difícil! Para fazer isso eu precisei pesquisar por:
- java hello world
- java string concatenation
- java string join
- java iterate through list
- java Iterator start at another position
E, apesar de tudo parecer bem correcto…
`--> javac hello.java
hello.java:7: error: cannot find symbol
StringJoiner string_joiner = new StringJoiner(" ");
^
symbol: class StringJoiner
location: class HelloWorldApp
hello.java:7: error: cannot find symbol
StringJoiner string_joiner = new StringJoiner(" ");
^
symbol: class StringJoiner
location: class HelloWorldApp
hello.java:8: error: cannot find symbol
for (Iterator<String> i = args.iterator(); i.hasNext();) {
^
symbol: class Iterator
location: class HelloWorldApp
hello.java:8: error: cannot find symbol
for (Iterator<String> i = args.iterator(); i.hasNext();) {
^
symbol: method iterator()
location: variable args of type String[]
hello.java:12: error: cannot find symbol
item = i.next();
^
symbol: variable item
location: class HelloWorldApp
hello.java:13: error: cannot find symbol
string_joiner.add(item);
^
symbol: variable item
location: class HelloWorldApp
hello.java:16: error: non-static variable this cannot be referenced from a static context
this.hello(string_joiner.toString());
^
7 errors
CARAMBA! Fazia algum tempo que eu não ficava preso nessa fase bizarra de “escreve-compila-dápau, escreve-compila-dápau…”.
Sem iteradores
Acabei descobrindo que não precisava de um Iterator
para iterar sobre os args
. Eu inferi isso lendo algum código de exemplo no StackOverflow. Para mim isso acabou sendo um tanto confuso: preciso lembrar que Java é uma linguagem “tipo baixo nível” e que tem muito mais similaridade com C/C++ do que com linguagens de altíssimo nível como Perl, Python ou Ruby.
A versão quase sem erros ficou assim:
import java.util.StringJoiner;class HelloWorldApp {
public void hello(String who) {
System.out.println("Olá, " + who);
} public static int main(String[] args) {
StringJoiner string_joiner = new StringJoiner(" "); for (int i = 1; i < args.length; i++) {
String item = args[i];
string_joiner.add(item);
} this.hello(string_joiner.toString());
}
}
E os erros ficaram assim:
` → javac hello.java
hello.java:17: error: non-static variable this cannot be referenced from a static context
this.hello(string_joiner.toString());
^
1 error
Estático versus não-estático
Também esqueci desses detalhes de alocação de memória. Enquanto o método main
é estático, meu hello
não o era.
Nova versão:
import java.util.StringJoiner;class HelloWorldApp {
public static void hello(String who) {
System.out.println("Olá, " + who);
} public static int main(String[] args) {
StringJoiner string_joiner = new StringJoiner(" "); for (int i = 1; i < args.length; i++) {
String item = args[i];
string_joiner.add(item);
} hello(string_joiner.toString());
return 0;
}
}
O compilador também me obrigou a colocar aquele return 0
no final, já que eu havia declarado meu método main
, como seria correto, como int
.
Ops! Não pode ser int! E magic!
Primeiro: quando compilei meu hello.java
o “javac” me gerou um arquivo HelloWorldApp.class
. Já não gostei disso.
Segundo: como diabos se executa o programa? Com mágica! Embora você tenha escrito teu código em hello.java
e embora o compilador tenha gerado um arquivo HelloWorldApp.class
, você executa o teu programa chamando um tal de HelloWorldApp
, que sequer é um nome de arquivo no teu sistema de arquivos local!
Terceiro: apesar de que todo programa deve retornar um status de execução, que é um número inteiro, o Java não me deixa definir um método main
que não seja do tipo void
:
`--> java HelloWorldApp
Erro: o método main deve retornar um valor do tipo void na classe HelloWorldApp;
defina o método main como:
public static void main(String[] args)
Okay…
// TODO: descobrir como retornar o status de execução do programa para o sistema operacional.
Versão final. Ou é?
import java.util.StringJoiner;class HelloWorldApp {
public static void hello(String who) {
System.out.println("Olá, " + who);
} public static void main(String[] args) {
StringJoiner string_joiner = new StringJoiner(" "); for (int i = 1; i < args.length; i++) {
String item = args[i];
string_joiner.add(item);
} hello(string_joiner.toString());
}
}
Tudo okay, agora, certo? Então vamos testar!
` → java HelloWorldApp mundo
Hello
Ops! What happened? Vamos tentar de novo:
`--> java HelloWorldApp mundão véi sem porteira
Olá, véi sem porteira
Outra descoberta incrível: ao contrário do resto do mundo, parece que Java não coloca o nome do executável em args[0]
. Legal, hein?
// TODO: descobrir como pegar o nome do executável no Java.
Nova versão:
import java.util.StringJoiner;class HelloWorldApp {
public static void hello(String who) {
System.out.println("Olá, " + who);
} public static void main(String[] args) {
StringJoiner string_joiner = new StringJoiner(" "); for (int i = 0; i < args.length; i++) {
String item = args[i];
string_joiner.add(item);
} hello(string_joiner.toString());
}
}
Agora sim! Ou não?
` → java HelloWorldApp mundão véi sem porteira
Olá, mundão véi sem porteira
Não! Como eu disse no começo, se eu não passar nenhum argumento, deve-se imprimir “Olá, mundo”. Mas…
` → java HelloWorldApp
Hello
Adicionando uns “ifs”
import java.util.StringJoiner;class HelloWorldApp {
public static void hello(String who) {
System.out.println("Olá, " + who);
} public static void main(String[] args) {
StringJoiner string_joiner = new StringJoiner(" "); if (args.length == 0) {
return hello("mundo");
} for (int i = 0; i < args.length; i++) {
String item = args[i];
string_joiner.add(item);
} hello(string_joiner.toString());
}
}
Parece correcto, não? Tem até um “early return” razoavelmente elegante, ali.
Mas, não, o compilador não gosta:
`--> javac hello.java
hello.java:13: error: incompatible types: unexpected return value
return hello("mundo");
^
1 error
Não pode ter return
em método void
…
Solução? Um “maravilhoso” else
:
import java.util.StringJoiner;class HelloWorldApp {
public static void hello(String who) {
System.out.println("Olá, " + who);
} public static void main(String[] args) {
StringJoiner string_joiner = new StringJoiner(" "); if (args.length == 0) {
hello("mundo");
}
else {
for (int i = 0; i < args.length; i++) {
String item = args[i];
string_joiner.add(item);
} hello(string_joiner.toString());
}
}
}
Argh! Mas agora, pelo menos, funciona:
`--> java HelloWorldApp
Olá, mundo
`--> java HelloWorldApp mundão
Olá, mundão
Principais dificuldades
Versões e onde encontrar ajuda
A internet está repleta de sites e tutoriais de Java, mas ao buscar por ajuda, fiquei caindo constantemente em 3 categorias:
1- StackOverflow, que é um universo à parte.
2- Sites da época que se buscava coisas via Cadê.
3- O site oficial.
O problema do StackOverflow é que há perguntas lá datadas de “circa 1997” e certas coisas, que eram corretas antigamente, hoje já não o são. Achei bem complicado achar respostas específicas para o Java 8.
Nos sites antigos é difícil conseguir algo além de exemplos muito básicos e mesmo estes não me parecem muito confiáveis.
E o site oficial, por sua vez, é muito feinho (é extremamente desagradável navegar por ele) e achei o conteúdo ou demasiado verborrágico e cheio de rodeios ou simplesmente incompleto. Por exemplo: eu comecei a tentar criar um Iterator
para iterar sobre os argumentos de linha de comando (um erro, eu sei) e a documentação oficial até dá exemplos de uso, mas tive que “chutar” um import java.utils.Iterator
que não era citado em lugar algum (ou será que só quem usar openjdk precisa disso?). O mesmo aconteceu com a StringJoiner
…
O quanto isso é um problema intrínseco do Java? Não sei. Só sei que algo não cheira bem. Deixarei para analisar isso nos próximos capítulos dessa série.
“Orientação a Objetos” e mágica
Por que diabos eu preciso criar uma classe? A dita cuja sequer será instanciada! E ela só tem dois métodos — que são estáticos!
O grande problema do Java é essa tentativa de ser “object oriented only” e, de certa forma, foi quem popularizou as “object oriented languages”. Mas é preciso entender que Orientação a Objetos não é um paradigma de programação, mas simplesmente uma forma de modelar problemas. Não existe tal coisa como uma “linguagem orientada a objetos”. Java é simplesmente mais uma linguagem procedural, como C, C++ ou Python. É procedural porque você escreve como o computador deve fazer as coisas, ao contrário, por exemplo, de dizer simplesmente o que ele deve fazer, como seria numa linguagem funcional ou descrever um universo de fatos, como em programação lógica.
É preciso ter muita cautela com essas questões de “paradigma de programação”. C, por exemplo, é uma linguagem procedural, mas permite que você programe com uma mentalidade funcional ou mesmo que programe orientado a objetos. É complicado definir limites estritos com relação a qual paradigma determinada linguagem suporta — vide as intermináveis discussões que cercam o assunto.
Todavia, me parece um erro óbvio tentar considerar Orientação a Objetos como um paradigma de programação per si e ainda por cima criar uma linguagem que tente tão escancaradamente dar “suporte completo” a esse “paradigma”. As linguagens de programação “suportam” Orientação a Objetos, não “são” orientadas a objetos como a linguagem Java tenta ou diz ser.
E essa tentativa de prender o programador em uma jaula-de-OO nos leva ao próximo ponto, que é:
Ficar agradando o compilador
Eu passei alguns anos da minha vida programando em linguagem C. E alguns anos lidando com C++, também. Eu sei bem o que é ficar tentando compilar um programa, ficar depurando erros de compilação, alterando, tentando, alterando, tentando até finalmente dar certo (e descobrir um bug em tempo de execução). Isso é até okay, mas minha pergunta é: pra quê?
Bom, eu sei bem o que acontece quando um programa em C é compilado. É uma necessidade muito básica declarar-se tipos em todo lugar, já que o compilador há de traduzir tudo para linguagem de máquina e fazer uma comparação, digamos, entre 1 (int) e 1.0 (float) requer, de fato, uma coerção de tipos, já que, no fim das contas, a comparação é feita no nível mais baixo possível, a Unidade Lógica Aritmética da CPU. Há circuitos elétricos envolvidos, sinais que devem estar altos ou baixos na hora certa para que 1
seja igual a 1
ou 1.0
seja igual a 1.0
, do jeito que deve ser.
Mas, se estamos compilando para execução em uma máquina virtual, qual é a necessidade disso? Pense além da simulação das instruções de uma CPU, pense além dos circuitos elétricos: por que um desenvolvedor que quer escrever um “hello, world” precisa declarar sua função como void
? Por que ele precisa lembrar que main
precisa ser static
, public
e void
?
Acabei passando mais tempo tentando passar pelo compilador do que gastei tentando resolver meu problema!
Para entender melhor o que eu digo, façamos uma comparação: vou pintar de verde o que é solução de problema, vermelho o que é pura burocracia, azul o que é convenção e amarelo o que é necessidade aceitável da linguagem em ambos os programas: Python e Java. Vejamos:
Em Python eu não preciso escrever nada que seja meramente agrado a compilador/interpretador. E repare, também, que eu consigo resolver meu problema com muito menos linhas de código. E o código gerado é muito mais legível.
Na verdade, eu consigo resolver meu problema com apenas duas linhas de código Python, até:
import sys
print(‘Olá, ‘ + (‘ ‘.join(sys.argv[1:]) or ‘mundo’))
Enquanto com Java eu preciso me preocupar com trivialidades, como o índice do for
, o tipo da variável string_joiner
, os tipos de retorno dos métodos, se eles são estáticos ou não e suas permissões de acesso. E isso não tem nada a ver com a resolução do problema.
Ou seja: Java cobra impostos para poder te ajudar a resolver os teus problemas.
Resumo
Temos aqui nosso “lindo” programa “Olá, mundo” escrito em Java. Não tecerei maiores considerações, ainda, porque ainda acho cedo para tal.
No próximo capítulo escreveremos testes para este mesmo programa. Fique ligado!