Revisão da linguagem Java Laboratório de Programação Pedro Vasconcelos, Manuel Barbosa, DCC/FCUP Fevereiro 2016 Java • linguagem de programação de propósito genérico inicialmente desenvolvida pela Sun, atualmente pela Oracle • suporta tipos estáticos, classes, objetos e concorrência • compilada para bytecode duma máquina virtual (“write-once, run anywhere”) • sintaxe baseada no C/C++ • mais alto nível: – – – – modelo de objetos mais simples que o de C++ libertação automática de memória (garbage collection) não suporta aritmética de apontadores tratamento de erros de execução usando exceções Platforma Java Java Standard Edition (JSE): • • • • compilador: javac interpretador JVM: java conjunto de biliotecas padrão ferramentas (jar, javadoc, etc.) Java Enterprise Edition (JEE): • JSE + componentes extra para aplicações empresariais 1 Evolução da linguagem 1996 2000 2005 2011 2014 JDK 1.0 J2SE 1.3 J2SE 5 JSE 7 JSE 8 primeira versão HotspotVM tipos genéricos, enumerações, ciclos for implícitos melhorias na inferência de tipos genéricos expressões lambda, streams funcionais Em Laboratório de Programação vamos usar o JSE 7. Outras linguagens A máquina virtual JVM pode também ser usada para executar outras linguagens; algums exemplos: Scala Clojure Python Ruby http://www.scala-lang.org http://clojure.org/ http://www.jython.org/ http://jruby.org/ NB: não suportadas oficialmente pela Oracle! Princípios da linguagem • combina programação imperativa com objetos • distingue entre valores básicos e objetos – objetos são usados para estruturar programas – valores básicos não são objectos • execução em máquina virtual (facilita portabilidade) • gestão automática de memória: – a alocação de objetos é explícita – a libertação é implícita (garbage collection) Objetos, classes e packages • objetos são agregados de dados e operações associadas 2 • as classes são “moldes” para criar objectos • objectos da mesma classe suportam um conjunto de operações comuns • packages são agregados de classes relacionadas (para facilitar distribuição) NB: em Java, classes também são usadas apenas para agrupar métodos e atributos estáticos. Ficheiros Java O nome do ficheiro duma classe pública deve ser igual ao nome da classe. // num ficheiro Rectangle.java public class Rectangle { ... } NB: pode ter outras classes desde que não sejam públicas. Método main • programa completo deve ter uma classe com um método estático main • os argumentos de linha de comando são passados como um array de String // num ficheiro Hello.java class Hello { public static void main(String args[]) { System.out.println("Hello world!"); } } Compilação e execução 3 $ javac Hello.java # compilar $ java Hello # executar # alternativa com CLASSPATH $ java -cp ~/myclasses Hello Tipos básicos principais tipo valores literais int long float double boolean char inteiros com sinal 32 bits inteiros com sinal 64 bits vírgula-flutuante simples vírgula-flutuante dupla valores lógicos carateres Unicode 0, -7, 12345 -12345L 1.234f, -10.5e6f -0.25, 1.234d true, false 'A', '\u2764' Arrays int[] anArray; // declaração anArray = new int[10]; // inicialização (zeros) // inicialização com constantes int[] anArray = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 }; Arrays (2) • os índices começam em zero (como em C/C++) • arrays em Java têm associado um atributo length (ao contrário do C/C++) int[] somePrimes = { 2, 3, 5, 7, 11, 13 }; for (int i=0; i<somePrimes.length; i++) { System.out.println(somePrimes[i]); } 4 Ciclo for implícito Também podemos percorrer arrays usando um ciclo for com o índice implícito (“for-each” ou “enhanced for”): int[] somePrimes = { 2, 3, 5, 7, 11, 13 }; for (int prime : somePrimes) { System.out.println(prime); } NB: estes ciclos podem ser usados também noutras coleções (listas, conjuntos, etc.). Arrays (3) • não há arrays multi-dimensionais • alternativa: arrays de arrays • permite dimensões internas não homogéneas String[][] names = { {"Mr. ", "Mrs. ", "Ms. "}, {"Smith", "Jones"} }; System.out.println(names[0][0] + names[1][0]); // Mr. Smith System.out.println(names[0][2] + names[1][1]); // Ms. Jones Cadeias de texto Cadeias são objetos da classe String. String s1 = "Hello"; // declarar e inicializar String s2 = new String("Hello") // alternativa Podemos obter o comprimento e concatenar. System.out.println(s1.length()); // output: 5 // NB: length() é um método System.out.println(s1 + " world!"); // output: "Hello world!" 5 Cadeias de texto (2) • não são arrays de carateres • mas podemos aceder aos carateres individuais usando charAt() String pal = "Dot saw I was Tod"; int len = pal.length(); for (int i = 0; i < len; i++) { System.out.printlen(pal.charAt(i)); } Cadeias de texto (3) • não podemos modificar o comprimento ou os carateres duma cadeia (é um objeto imutável) • mas podemos construir novas cadeias por concatenação • alternativa: classe StringBuilder (mais tarde) Programação com objetos • definimos classes para novos tipos objetos • na definição da classe incluimos: – atributos (i.e. valores de estado) – métodos (i.e. operações suportadas) Exemplo: contas bancárias class Account { int balance = 0; // balanço (atributo) // depositar uma quantia void deposit(int amount) { balance = balance + amount; } } // obter o saldo atual int getBalance() { return balance; } 6 Exemplo de uso class AccountDemo { public static void main(String args[]) { Account acc1 = new Account(); Account acc2 = new Account(); } System.out.println(acc1.getBalance()); System.out.println(acc2.getBalance()); acc1.deposit(100); acc2.deposit(50); System.out.println(acc1.getBalance()); System.out.println(acc2.getBalance()); } Controlo de acesso Podemos limitar o acesso a métodos ou atributos usando modificadores; os mais comuns são: public visível em todas as classes private vísivel apenas na própria classe Outros: protected visível na classe, sub-classes e no package (sem modificador) visível na classe e no package Contas bancárias (2) class Account { private int balance = 0; // depositar uma quantia public void deposit(int amount) { balance = balance + amount; } // obter o saldo atual 7 } public int getBalance() { return balance; } Encapsulamento • não podemos modificar balance fora da classe Account • podemos apenas usar os métodos públicos // no método main() da classe AccountDemo acc = new Account() ... acc.balance = acc.balance + 100; // ERRO acc.deposit(100); // OK Construtores • devemos definir construtores especializados para inicializar objetos • um construtor tem o nome da classe e não tem tipo de retorno • pode haver mais do que um construtor (com diferentes números de argumentos ou tipos) class Account { private int balance = 0; // construtor com saldo inicial public Account(int amount) { balance = amount; } ... } Métodos e atributos estáticos Por omissão, os atributos e métodos são associados a cada instância particular (i.e. cada objeto). O modificador static permite associar métodos ou atributos à classe em vez das instâncias. 8 class Account { // balanço (um por cada conta) private int balance = 0; // taxa de câmbio (todas as contas) static private float rate = ...; ... } Herança • uma classe pode extender outra por herança • se A extende B então A é sub-classe de B • uma sub-classe: – herda atributos e métodos da classe original – pode definir novos métodos ou redefinir métodos existentes (override) class SavingsAccount extends Account { // novos atributos e métodos private float interestRate; ... } A classe Object • a classe Objet está no topo da hierarquia • todas as outras classes herdam de Object (direta ou indiretamente) • se não declaramos herança explícita para um nova classe, esta herda de Object: class Account { ... } é equivalente a class Account extends Object { ... } 9 Métodos da classe Object Alguns métodos definidos em Object: Object clone() criar uma cópia do objeto boolean equals(Object other) comparar igualdade com outro objeto String toString() converte o objeto numa string int hashCode() retornar o código de “hash” inteiro NB: por vezes é necessário re-definir estes métodos nas sub-classes. Cuidados com herança O (ab)uso da herança é desaconselhado: • implica perda de flexibilidade porque compromete o programa com implementações específicas • aumenta o acouplamento entre partes distintas do código • uma alteração numa classe pode ter efeitos imprevistos noutras • a integridade da relação de herança é bastante subtil: https://isocpp.org/ wiki/faq/proper-inheritance#circle-ellipse Interfaces • • • • especificações de um conjunto métodos relacionados representam um contrato entre uma classe e o mundo exterior uma interface pode ser implementada por várias classes uma classe pode implementar várias interfaces Exemplo interface Sizeable { float getArea(); // calcular área } class Rect implements Sizeable { private float width, height; ... public float getArea() { 10 } } return width*height; Exemplo (cont.) class Circle implements Sizeable { private float radius; ... public float getArea() { return Math.PI*Math.pow(radius,2.0); } } Classes vs. interfaces • • • • classes podem conter atributos e métodos interfaces só podem declarar métodos (sem implementação) ou constantes uma classe só pode extender uma outra classe uma interface pode extender múltiplas interfaces public interface GroupedInterface extends Interface1, Interface2, Interface3 { // declarações de constantes // e assinaturas de métodos ... } Enumerações • tipos enum permitem definir valores constantes dum conjunto finito de alternativas • o compilador verifica que valores dum tipo enum são corretamente atribuidos • podem também ser usados em instruções switch public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } 11 Exemplo public void tellItLikeItIs(Day day) { switch (day) { case MONDAY: System.out.println("Mondays are bad."); break; case SATURDAY: case SUNDAY: System.out.println("Weekends are best."); break; default: System.out.println("Midweek days are so-so."); break; } } Outros cuidados • • • • valores null comparações de igualdade anotações de override implementações do método equals Valores null • • • • um array ou objeto sem inicialização toma o valor null podemos também atribuir explicitamente null null representa a ausência de um objeto invocar métodos ou atributos de null causa um erro: NullPointerException String s1; // inicializada com null System.out.println(s1.length()); // NullPointerException Comparações de igualdade O operador == compara: • igualdade de valores para tipos básicos (int, char, etc.) • igualdade de referências para arrays e objetos 12 int x = 1+2+3; int y = 2*3; // x==y dá true String s1 = "Hello " + "world"; String s2 = "He" + "llo world"; // s1 == s2 dá false Comparações de igualdade (2) Para comparar igualdade de valores entre objetos devemos usar o método equals: String s1 = "Hello " + "world"; String s2 = "He" + "llo world"; boolean r1 = s1.equals(s2); // true boolean r2 = s2.equals(s1); // true Igualdade entre Arrays • Arrays são objetos da classe Array • a igualdade está definida em java.util.Arrays (não é o método equals) import java.util.Arrays; ... int a[] = {1, 2, 3} ; int b[] = {1, 2, 3} ; boolean r1 = a == b; boolean r2 = a.equals(b); boolean r3 = Arrays.equals(a,b); // false // false // true Anotação de Override • as anotações indicam meta-informação para o compilador, IDEs e outras ferramentas • quando queremos redefinir um método numa sub-classe devemos anotá-lo com @Override 13 • permite ao compilador/IDE avisar se nos enganarmos no nome do método ou tipos de argumentos class Foo { ... @Override public String toString() { // redefinir um método herdado de Object ... } } Implementação de equals • o método equals definido em Object compara as referências dos objetos (como o operador ==) • para comparar valores devemos redefinir este método • possiveis armadilhas: – – – – assinatura de tipos demasiado restrita definir equals dependendo de atributos mutáveis definir equals sem respeitar uma relação de equivalência devemos também redefinir hashCode Mais informação: How to Write an Equality Method in Java Exemplo: rectângulos class Rect { private int width, height; // largura, altura ... @Override public boolean equals(Object other) { if (other == null || !(other instanceof Rect)) return false; Rect that = (Rect)other; // coerção return (this.width == that.width && this.height == that.height); } } 14 Redefinir também hashCode Para usar Rect em coleções baseadas em funções de hash (e.g. HashMap ou HashSet) devemos também redefinir hashCode. class Rect { ... @Override public int hashCode() { // produtos com um primo return (41 * (41 + width) + height); } } Os IDEs podem gerar automaticamente métodos equals e hashCode — mas nem sempre corretos! 15