Departamento de Engenharia Informática - Histria da linguagem...

86
Programação Orientada por Objectos 1 História da linguagem Java Os microprocessadores revolucionaram a indústria dos computadores. Pensa-se que a próxima área na qual os microprocessadores terão um profundo impacto, será nos dispositivos electrónicos de consumo inteligentes. Por essa razão, a empresa Sun Microsystems financiou em 1991, um projecto de investigação, designado “Green project”, cujo objectivo era desenvolver software para controlar dispositivos electrónicos de consumo tais como electrodomésticos, televisões interactivas, sistemas de iluminação, etc, tornando-os inteligentes e permitindo a comunicação entre eles. O protótipo desenvolvido designou-se por “star7”, podia ser controlado à distância e comunicar com outros do mesmo tipo. O projecto usava a linguagem C++, o que se tornou muito complicado. Um dos membros, James Gosling, criou uma nova linguagem para o star7, a que chamou “oak”, porque do gabinete dele via essa árvore. Os programas deviam ser pequenos, fiáveis, eficientes e facilmente portáveis entre diferentes dispositivos de hardware. O projecto foi apresentado a representantes de electrónica de consumo que não o aceitaram, porque o mercado para os dispositivos electrónicos de consumo não desenvolveu tão rapidamente como a Sun antecipou. O projecto esteve para ser cancelado mas em 1993 a World Wide Web ganha muita popularidade e o grupo de investigação viu o potencial de usar a linguagem para criar páginas dinâmicas. Os programas eram pequenos, facilmente carregados, seguros e portáveis, para além de ser uma linguagem de uso genérico que pode correr em diferentes plataformas. Para demonstrar o potencial da linguagem, construíram em 1994, com a própria linguagem um browser, que podia correr applets, e designaram-no por WebRunner. Em Janeiro de 1995, pressionados para lançar o produto, verificaram que “oak” era uma marca registada da “Oak Technologies” e “WebRunner” da “Taligent”. Fizeram uma reunião do tipo “brainstorm”com todos os elementos do grupo de investigação designado por “Live Oak” para escolherem um novo nome para a linguagem. Surgiram muitos nomes, um dos quais Java por um dos elementos estar a beber café Pete’s Java, e este foi o escolhido para a linguagem e HotJava para o browser. Em Maio de 1995 a Sun anuncia formalmente a linguagem Java, mas esta linguagem só alcançou muita popularidade depois da Netscape a licenciar em Agosto de 1995. A revista Time designou Java um dos dez melhores produtos de 1995. Desenvolvimento de programas: Software de desenvolvimento da Sun: http://java.sun.com Java Development Kit (JDK), gratuito. NetBeans (ambiente de desenvolvimento integrado – IDE), gratuito. BlueJ: http://www.bluej.org gratuito. DEI - ISEP Fernando Mouta

Transcript of Departamento de Engenharia Informática - Histria da linguagem...

  • Programação Orientada por Objectos 1

    História da linguagem Java Os microprocessadores revolucionaram a indústria dos computadores. Pensa-se que a próxima área na qual os microprocessadores terão um profundo impacto, será nos dispositivos electrónicos de consumo inteligentes. Por essa razão, a empresa Sun Microsystems financiou em 1991, um projecto de investigação, designado “Green project”, cujo objectivo era desenvolver software para controlar dispositivos electrónicos de consumo tais como electrodomésticos, televisões interactivas, sistemas de iluminação, etc, tornando-os inteligentes e permitindo a comunicação entre eles. O protótipo desenvolvido designou-se por “star7”, podia ser controlado à distância e comunicar com outros do mesmo tipo. O projecto usava a linguagem C++, o que se tornou muito complicado. Um dos membros, James Gosling, criou uma nova linguagem para o star7, a que chamou “oak”, porque do gabinete dele via essa árvore. Os programas deviam ser pequenos, fiáveis, eficientes e facilmente portáveis entre diferentes dispositivos de hardware. O projecto foi apresentado a representantes de electrónica de consumo que não o aceitaram, porque o mercado para os dispositivos electrónicos de consumo não desenvolveu tão rapidamente como a Sun antecipou. O projecto esteve para ser cancelado mas em 1993 a World Wide Web ganha muita popularidade e o grupo de investigação viu o potencial de usar a linguagem para criar páginas dinâmicas. Os programas eram pequenos, facilmente carregados, seguros e portáveis, para além de ser uma linguagem de uso genérico que pode correr em diferentes plataformas. Para demonstrar o potencial da linguagem, construíram em 1994, com a própria linguagem um browser, que podia correr applets, e designaram-no por WebRunner. Em Janeiro de 1995, pressionados para lançar o produto, verificaram que “oak” era uma marca registada da “Oak Technologies” e “WebRunner” da “Taligent”. Fizeram uma reunião do tipo “brainstorm”com todos os elementos do grupo de investigação designado por “Live Oak” para escolherem um novo nome para a linguagem. Surgiram muitos nomes, um dos quais Java por um dos elementos estar a beber café Pete’s Java, e este foi o escolhido para a linguagem e HotJava para o browser. Em Maio de 1995 a Sun anuncia formalmente a linguagem Java, mas esta linguagem só alcançou muita popularidade depois da Netscape a licenciar em Agosto de 1995. A revista Time designou Java um dos dez melhores produtos de 1995. Desenvolvimento de programas: Software de desenvolvimento da Sun: http://java.sun.com Java Development Kit (JDK), gratuito. NetBeans (ambiente de desenvolvimento integrado – IDE), gratuito. BlueJ: http://www.bluej.org gratuito.

    DEI - ISEP Fernando Mouta

    http://java.sun.com/http://www.bluej.org/

  • 2 Programação Orientada por Objectos

    O Modelo de Compilação Quando se compila um programa escrito em C ou noutras linguagens o compilador traduz o código fonte em código máquina – instruções específicas do processador do computador. O programa compilado só correrá em computadores com esse processador. Se se pretender usar o mesmo programa noutra plataforma é necessário transferir o código fonte para a nova plataforma e recompilá-lo para produzir código específico para esse sistema. Por vezes é necessário fazer pequenas alterações. Compiladores tradicionais: Código fonte --> Compilador para um dado CPU -- > Código específico para um dado CPU O processo de compilação de programas em Java usa uma nova concepção. Bytecodes – conjunto de bytes formatados (para uma máquina virtual Java). Compilação de programas em Java: Código fonte --> Compilador -- > Bytecodes (conjunto de bytes formatados) --> Interpretador para um dado CPU --> Código específico para um dado CPU Características: Execução mais lenta, Grande portabilidade do código em bytecodes, desde que um computador disponha de interpretador Java. A linguagem Java permite criar 2 tipos de programas:

    • Applets: programas escritos em Java e compilados para ser executados num browser ou num emulador de browser - appletviewer.

    • Aplicações: programas escritos em Java e compilados que correm sozinhos. Classes e objectos Os programas em Java são construídos a partir de classes. Depois de efectuada a definição de uma classe pode-se criar qualquer número de objectos. Criar um objecto a partir da definição de uma classe designa-se por instanciação, daí que os objectos são muitas vezes designados por instâncias. Cada classe pode conter 3 tipos de membros: campos de dados, métodos e classes ou interfaces. Embora classes ou interfaces possam ser membros de outras classes ou interfaces, vamos começar por considerar só os dois primeiros tipos de membros: campos de dados e métodos. Campos de dados são dados que pertencem ou à própria classe ou a objectos da classe. Eles representam o estado do objecto ou da classe. São implementados por variáveis que vão conter informação relativa aos objectos da classe. Métodos são conjuntos de instruções que operam nos campos de dados para manipular o estado do objecto ou da classe. Contêm o código correspondente às acções que os objectos da classe podem executar.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 3 Definição de uma classe: class NomeDaClasse {

    camposDeDados métodos }

    Definição de uma classe como sendo uma subclasse de outra: class NomeDaClasse extends NomeDaSuperclasse { camposDeDados métodos }

    Declaração de uma classe que representa um ponto num plano a 2 dimensões: class Ponto { double x, y; public void limpar() { x = 0; y = 0; } } A classe Ponto tem 2 campos de dados representando as coordenadas x e y de um ponto, e 1 método limpar() que coloca x e y (do objecto ao qual é aplicado) a zero. Extensão da classe Ponto para representar um pixel que possa ser mostrado no ecrã (Pixel estende dados e comportamento da classe Ponto): class Pixel extends Ponto { Color cor; public void limpar() { super.limpar(); cor = null; } } Criação de objectos: Os objectos são criados usando uma expressão contendo a palavra-chave new. Em Java os objectos criados são alocados numa zona de memória designada heap. Ponto p1 = new Ponto(); Ponto p2 = new Ponto(); p1.x = 10.0; p1.y = 20.0; p1.limpar(); // invocação de um método Em geral pretende-se que um campo de dados (atributo) num objecto seja diferente do campo de dados com o mesmo nome em qualquer outro objecto da mesma classe (e por isso esses atributos implementam-se como variáveis de instância). Mas por vezes pretendemos campos de dados de dados que sejam partilhados por todos os objectos da classe – implementam-se com variáveis estáticas ou de classe. public static double xMax = 10.0; public static double yMax = 10.0;

    DEI - ISEP Fernando Mouta

  • 4 Programação Orientada por Objectos

    Exemplo de uma aplicação Aplicação (programa HelloWorld para construir uma aplicação: Hello.java): public class Hello { public static void main (String args []) { System.out.println(“Hello World !”); } } para compilar: javac Hello.java para correr o programa: java Hello O programa declara uma classe com o nome Hello a qual não contém campos de dados e tem um único método com o nome main. O método main recebe um parâmetro que é um array de objectos String que são os argumentos com os quais o programa pode ser invocado da linha de comandos.O método main é declarado void porque não retorna qualquer valor. Neste exemplo, o método main contém uma única instrução que invoca o método println() no objecto out da classe System. Exemplo de uma applet HelloApplet.java: import java.applet.Applet; import java.awt.Graphics; public class HelloApplet extends Applet { public void paint (Graphics g) { g.drawString(“Hello World !”, 50, 20); } } Os browsers mostram o conteúdo de documentos que contêm texto. Para executar um applet Java num browser, deve-se fornecer um ficheiro de texto HTML com a indicação do applet que o browser deve carregar e executar. HelloApplet.html:

    O ficheiro HTML diz ao browser o applet específico a carregar e o tamanho da área na qual o applet vai ser mostrado. O browser cria um objecto dessa classe e para esse objecto invoca determinados métodos numa determinada ordem. Um dos métodos mais importantes é o método paint, o qual é chamado automaticamente pelo browser quando executa o applet. O método paint desenha gráficos (linhas, rectângulos, ovais e strings de caracteres) no ecrã. A única maneira de executar um applet é a partir de um documento HTML (ou através de um emulador de browser – appletviewer).

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 5

    Classes e Objectos Java – linguagem de programação orientada por objectos. A programação orientada por objectos é uma forma de organização lógica de programas através de um nível de abstracção adequado para modelizar as entidades de um modo semelhante ao modo como se relacionam no mundo real. Trata-se de uma simulação do mundo real conceptualizado como um sistema de objectos em interacção. As entidades reais são modelizadas através de objectos e classes. Classes – são definições de características comuns a um conjunto de objectos. As características dos objectos podem-se exprimir através de:

    Atributos e Comportamentos.

    • Os atributos dos objectos pertencentes a uma classe implementam-se através de variáveis.

    • Os comportamentos dos objectos pertencentes a uma classe implementam-se através de métodos que são funções que podem receber (ou não) parâmetros, realizam uma determinada acção e podem retornar (ou não) resultados.

    Criar um objecto a partir de uma classe é instanciar a classe. Um objecto é uma instância de uma classe. Todo o objecto em Java pertence a uma classe que define os seus dados e comportamento. Cada classe pode ter 3 tipos de membros:

    • Campos de dados – são variáveis de dados associadas à classe e aos objectos. Os campos de dados também servem para armazenar resultados de cálculos realizados pelos métodos da classe.

    • Métodos – são conjuntos de instruções que contêm o código executável de uma classe.

    • Classes e interfaces. Introdução à Programação Orientada por Objectos em Java De um modo semelhante a muitas actividades económicas, em que um produto é fabricado através da integração de vários componentes seleccionados e adquiridos, também em programação a utilização de módulos integráveis com características e funcionalidades independentes do contexto, torna mais económica e de melhor qualidade a produção de software assim como a sua manutenção. Um programa realizado segundo o paradigma da Programação Orientada por Objectos (POO) é constituído por objectos, possuidores de características específicas e capazes de realizar determinadas operações. Cada objecto é responsável por realizar um conjunto de tarefas. Se uma tarefa depende de outra que não é da sua responsabilidade tem de ter acesso a um outro objecto capaz de a realizar. No entanto um objecto nunca deve manipular directamente os dados internos de outro objecto, mas apenas comunicar através de mensagens. Deste modo promove-se a reutilização de código, reduz-se a dependência dos dados e facilita-se a detecção de erros, características importantes da POO.

    DEI - ISEP Fernando Mouta

  • 6 Programação Orientada por Objectos

    Agregando dados e comportamentos num único módulo esconde-se do utilizador a implementação dos dados atrás das operações neles realizadas. Assim, separa-se a estrutura e mecanismos internos do programa, da interface utilizada pelo programador cliente, o que facilita a compreensão e uso na reutilização de código, representando outra vantagem do paradigma da POO. A reutilização do código pode ser efectuada por 2 processos diferentes: Por composição, se o objecto a construir é composto por outros objectos, permitindo utilizar as funcionalidades dos seus componentes; Por herança, se o objecto a construir deve ter a estrutura e o comportamento de outro, mas com algumas adições ou modificações, correspondendo a uma especialização ou derivação. A herança possibilita assim a programação incremental que facilita a extensão dos programas assim como a programação genérica através do polimorfismo, pois código que funciona com um tipo genérico também funciona com novos subtipos que se criem. Estas são as outras características importantes da POO. A POO é um modelo de concepção e desenvolvimento de programas que aproxima a programação do modo como os humanos representam e operam os conceitos e objectos do mundo, facilitando o desenvolvimento de aplicações reais. A POO baseia-se num pequeno número de conceitos fundamentais do senso-comum, isto é, em ideias simples. Os quatro princípios chave em que se baseia a POO são: abstracção, encapsulamento, herança e polimorfismo. Vamos descrever estes conceitos em termos de exemplos do mundo real e das correspondentes abstracções de programação. Abstracção Abstracção é o processo de extracção das características essenciais de um conceito ou entidade no mundo real para o poder processar num computador. As características que escolhemos para representar o conceito ou entidade dependem do que se pretende fazer. Estas características fornecem um tipo de dados abstracto. Tomando como exemplo um automóvel, para o registo, as características importantes são o número de identificação do veículo atribuído pelo fabricante, a matrícula, e o dono, enquanto que para a reparação numa garagem, as características essenciais seriam a matrícula, o dono, a descrição do serviço a efectuar e a factura. A abstracção é o processo de determinar quais são as características apropriadas dos dados excluindo os detalhes não importantes para a resolução do problema, constituindo essas características um tipo de dados abstracto. Encapsulamento Todas as linguagens têm uma maneira de armazenar pedaços de informação relacionada juntos, normalmente designados estrutura ou registo. Na programação orientada por objectos reconhece-se que tão importante como os dados são as operações que neles são realizadas. O encapsulamento consiste na agregação dos dados e seus comportamentos numa única unidade organizacional. Em termos de linguagem de programação isto significa que os dados e as funções que os manipulam devem ser agrupados num tipo de dados, de modo que se possa determinar as características de objectos desse tipo assim como as únicas operações que neles se possam realizar.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 7 As linguagens não orientadas por objectos já suportam encapsulamento para tipos de dados primitivos ou internos, tais como inteiro (“int”) ou real (“float”), para os quais existem um conjunto bem definido de operações que se podem aplicar. As linguagens orientadas por objectos suportam o encapsulamento também para tipos definidos pelo utilizador. Vamos mostrar exemplos, partindo da linguagem de programação C (muito popular, mas não orientada por objectos). Vamos supor que pretendíamos representar circunferências num plano a duas dimensões. As características que abstraíamos eram as coordenadas do centro e o raio, as quais podiam ser armazenadas numa estrutura como a seguinte:

    typedef struct { float x; float y; float raio; } Circunferencia;

    Consideremos que tínhamos ainda uma função que calculava a área, dado um apontador para uma estrutura circunferência.

    float area( Circunferencia *c) { return 3.14 * (c->raio) * (c->raio); }

    A estrutura Circunferencia tem três campos que são “float” para armazenar a coordenada x, a coordenada y, e o valor do raio. A função “area” tem um parâmetro designado “c” que é um apontador para uma variável Circunferencia. No corpo da função multiplica-se 3.14 pelo quadrado do valor do campo raio da variável apontada por “c”, e retorna-se o resultado como valor da função. A invocação desta função poderia ser feita como a seguir:

    float a; Circunferencia c1; c1.x = 5.0; c1.y = 6.0; c1.raio = 3.0; a = area( &c1 );

    A função “area” recebe um apontador para uma variável Circunferencia (e não uma variável Circunferencia), por isso é-lhe passado o endereço da variável “c1”. Num programa em C poderíamos ter outras definições de estruturas e de funções, todas ao mesmo nível. A função “area” e a estrutura de dados sobre a qual a função opera não estão agregadas numa unidade organizacional. Seria possível a um programador que usasse este programa adicionar mais funções que operassem sobre esta estrutura de dados, assim como aceder directamente aos campos da estrutura e alterá-los. Para fazermos a transição para a programação orientada por objectos, vamos começar por agrupar a definição da estrutura com todas as funções que operam sobre esta estrutura. Como isto não é possível na linguagem de programação C, vamos designar o seguinte código por pseudo-código C:

    DEI - ISEP Fernando Mouta

  • 8 Programação Orientada por Objectos

    struct Circunferencia { float x; float y; float raio;

    float area( Circunferencia *c) { return 3.14 * (c->raio) * (c->raio); }

    } ; Vamos impor agora a seguinte convenção: todas as funções que operam sobre uma variável do tipo de dados Circunferencia recebem um apontador para essa variável como primeiro argumento; a variável apontada pelo primeiro argumento será a variável sobre a qual a função realiza a operação. Com esta convenção poderemos retirar o apontador para a variável sobre a qual a função opera, quer da lista de parâmetros, quer antes dos campos para os quais aponta (no corpo da função) considerando que existe implicitamente. A definição do tipo de dados Circunferencia fica então:

    struct Circunferencia { float x; float y; float raio;

    float area( ) { return 3.14 * raio * raio; }

    } ; Assim chegamos à definição de uma classe na linguagem orientada por objectos C++, bastando substituir a palavra struct por class:

    class Circunferencia { float x; float y; float raio;

    float area( ) { return 3.14 * raio * raio; }

    } ; Na programação orientada por objectos as funções no interior de classes designam-se por métodos, no sentido de que definem o método (o plano, o esquema, as acções) para fazer qualquer coisa. A invocação do método para calcular a área de uma circunferência poderia ser feita como a seguir:

    Circunferencia c1; c1.x = 5.0; c1.y = 6.0; c1.raio = 3.0; float a = c1.area();

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 9 Na execução do método “area” aplicado ao objecto c1, o campo de dados raio referido no corpo do método é o campo de dados da variável c1. A linguagem de programação é que se encarrega de, ao invocar o método “area”, enviar um apontador para a variável “c1” como primeiro parâmetro implícito, e usar esse apontador para descobrir os campos de dados referenciados no corpo do método. Deste modo os campos de dados são implicitamente reconhecidos dentro dos métodos, sem ter que dizer a que estruturas pertencem. A linguagem de programação Java apresenta uma diferença importante relativamente ao C++. Java trata as variáveis de dois modos diferentes, dependendo se são variáveis de tipos de dados internos ou primitivos, tais como int ou char, ou se são variáveis de tipos de dados definidos como classes. Em Java existem oito tipos de dados primitivos: byte, short, int, long, float, double, char, boolean. Quando se declara uma variável de um tipo de dados primitivo pode-se processá-la imediatamente. A declaração de uma variável de um tipo de dados primitivo corresponde à alocação de memória para essa variável (na stack, se é uma variável local, no heap se é uma variável de instância), e essa localização de memória pode ser usada imediatamente Quando se declara uma variável referência de objecto (variável de tipo de dados definido como classe) não se obtém uma instância (objecto) de uma dada classe e portanto não se pode ler, escrever ou processar essa instância. É necessário criar essa instância (objecto). Em Java a declaração “Circunferencia c1;” apenas aloca memória para uma variável de nome “c1” que pode referenciar um objecto do tipo Circunferencia, mas enquanto não for criado o objecto, e atribuído a c1 essa referência, não se pode aceder aos campos de dados do objecto referenciado por c1, porque ele não existe. Assim não se pode aceder a c1.raio, porque nem sequer existe memória alocada para os campos de dados do objecto Circunferência. Os objectos são criados utilizando a palavra-chave new seguida do tipo de objectos que se pretende criar e de parênteses. Os objectos criados são alocados numa área de memória designada heap. Todos os objectos são acedidos por referências a objectos, sendo esta a única maneira de acedermos aos objectos. Em geral há imprecisão na distinção entre os objectos e as referências a esses objectos. Uma variável referência de um objecto com o valor null não referencia qualquer objecto. Assim um objecto Circunferencia poderia ser criado assim:

    Circunferencia c1; // declaração da variável c1 = new Circunferencia(); // criação do objecto e retorno da

    // referência que é atribuída a c1 ou assim:

    Circunferencia c1 = new Circunferencia();

    DEI - ISEP Fernando Mouta

  • 10 Programação Orientada por Objectos

    O exemplo considerado, poderia ser assim codificado em Java:

    class Circunferencia { float x; float y; float raio;

    float area( ) { return 3.14 * raio * raio;

    } }

    A criação de um objecto, inicialização e invocação do método “area” poderia ser feita como a seguir:

    Circunferencia c1 = new Circunferencia(); c1.x = 5.0; c1.y = 6.0; c1.raio = 3.0; float a = c1.area();

    Cada objecto Circunferência tem a sua própria cópia dos campos de dados x, y e raio. Se alterarmos o valor de um destes campos de dados num objecto, não alteramos o valor do campo com o mesmo nome noutro objecto. Estes campos de dados designam-se por variáveis de instância porque há uma cópia do campo para cada instância (objecto) da classe. Mas, às vezes, precisamos de campos de dados que sejam partilhados por todos os objectos da classe. Em Java obtêm-se estes campos de dados específicos da classe declarando-os static, pelo que normalmente se designam por campos de dados estáticos ou variáveis de classe (porque são específicos da classe e não aos objectos da classe). Haverá apenas uma cópia correspondente a um campo estático independentemente da quantidade de objectos criados, e mesmo que não seja criado nenhum. Um campo estático pode ser referenciado através da referência de qualquer objecto ou do nome da classe (preferencial). Construtores Para criar um objecto, é necessário declarar uma variável do tipo de uma classe e em seguida criar um novo (new) objecto dessa classe usando um construtor (dessa classe) conjuntamente com o operador "new". Ainda aos objectos criados é necessário dar um estado inicial. Esse estado inicial pode ser dado pela inicialização dos campos de dados expressos na própria declaração, mas muitas vezes é necessário realizar operações que não podem ser expressas como simples atribuições. Para isso usam-se os construtores. Os construtores têm o mesmo nome que a classe que inicializam, recebem zero ou mais parâmetros, mas não são métodos e não têm tipo de retorno. Pode-se associar o tipo de retomo ao nome do construtor (igual ao nome da classe).

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 11 Os construtores apresentam a seguinte estrutura:

    NomeDaClasse ( listaDeParametros ) { ...

    } Os construtores servem para colocar o objecto criado ( depois de alocada memória no heap para os campos de dados) num estado inicial. Os construtores são invocados depois das variáveis instância (dos objectos criados da classe) terem sido instanciados aos seus valores iniciais por omissão e depois da execução dos seus inicializadores explícitos (atribuições directamente aos campos de dados). Vejamos uma nova definição da classe Circunferencia com um construtor com parâmetros:

    class Circunferencia { float x; float y; float raio; Circunferencia( float x1, float y1, float raio1) { x = x1; y = y1; raio = raio1;

    } float area( ) {

    return 3.14 * raio * raio; }

    } A criação de um objecto e invocação do método “area” poderia agora ser feita assim:

    Circunferencia c1 = new Circunferencia(5.0, 6.0, 3.0); float a = c1.area();

    Os argumentos dos construtores permitem fornecer parâmetros para a inicialização de um objecto. Suponhamos agora que pretendíamos atribuir um número único a cada objecto circunferência criado. Para isso incluímos mais um campo de dados do tipo “int” designado “numId” como variável de instância da classe Circunferencia. Cada objecto criado deverá ter um valor único para “numId”. Para que esta atribuição de um número único a cada objecto criado seja automática, necessitámos ainda de mais um campo de dados que contenha a quantidade de objectos criados até um dado momento. Vamos designar esse campo de dados por “proxNumId”. Não é necessário que em todos os objectos se aloque memória para um campo de dados “proxNumId”, basta que haja um para toda a classe, por isso vamos declará-lo como estático.

    DEI - ISEP Fernando Mouta

  • 12 Programação Orientada por Objectos

    A classe Circunferencia fica agora com a seguinte definição:

    class Circunferencia { int numId; float x; float y; float raio; static int proxNumId=0; Circunferencia( float x1, float y1, float raio1) { numId = proxNumId++; x = x1; y = y1; raio = raio1;

    } float area( ) {

    return 3.14 * raio * raio; }

    } A criação de objectos continuaria a ser feita do modo definido anteriormente. Mas após ser criado um objecto, continua a ser possível aceder ao campo de dados numId e alterá-lo (não ficando garantida a existência de um número de identificação único para cada objecto) . Para não permitir que código fora da classe possa alterar um campo de dados, devemos declarar o campo de dados privado (com o modificador de acesso private). Membros de uma classe declarados “private” só podem ser acedidos por código dentro da própria classe. No entanto para o campo de dados ser útil e podermos utilizar (ler) o seu valor em código fora da classe, devemos acrescentar à classe um método público de acesso que retorne o valor do campo de dados. A definição da classe passaria a ser a seguinte.

    public class Circunferencia { private int numId; float x; float y; float raio; private static int proxNumId=0; public Circunferencia( float x1, float y1, float raio1) { numId = proxNumId++; x = x1; y = y1; raio = raio1;

    } public int getNumId() {

    return numId; } public float area( ) {

    return 3.14 * raio * raio; }

    }

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 13 Construtor por omissão ( “default constructor” ) A maioria das classes têm pelo menos um construtor. Se não é definido nenhum explicitamente, o compilador cria automaticamente um construtor por omissão - construtor sem argumentos (“no-arg constructor”). Por isso é normal declarar e inicializar objectos com chamadas do tipo:

    Bicicleta b1 = new Bicicleta(); Cerveja cheers = new Cerveja();

    Um construtor não pode ser invocado explicitamente pelo programa a não ser na criação de um objecto, porque um construtor está associado à criação do objecto (embora pudesse ser útil invocá-lo após a criação para fazer o reset do objecto, isto é colocá-lo no estado inicial). Mas uma classe pode ter mais que um construtor, com diferentes assinaturas (isto é, diferentes listas de parâmetros, porque o nome é sempre o da classe).

    public class Circunferencia { private int numId; float x; float y; float raio; private static int proxNumId=0; public Circunferencia() { numId = proxNumId++; } public Circunferencia( float x1, float y1, float raio1) { this(); x = x1; y = y1; raio = raio1;

    } public int getNumId() {

    return numId; } public float area( ) {

    return 3.14 * raio * raio; }

    } Invocação explícita de um construtor A palavra chave this com uma lista de argumentos faz uma chamada explícita ao construtor da mesma classe com essa lista de argumentos. this() só pode ser usado para um construtor invocar outro construtor da mesma classe e tem de ser a primeira instrução executável. Agora temos duas maneiras de criar objectos:

    Circunferencia c1 = new Circunferencia(); Circunferencia c2 = new Circunferencia(7.0, 8.0, 5.0);

    DEI - ISEP Fernando Mouta

  • 14 Programação Orientada por Objectos

    O construtor por omissão só é criado pelo compilador, se não se fornece nenhum construtor de qualquer tipo numa classe, e isto porque há classes para as quais um construtor sem argumentos seria incorrecto. Se se pretende ter, para além de um ou mais construtores com argumentos, um construtor sem argumentos, é necessário fornecê-lo explicitamente. O construtor por omissão é equivalente ao seguinte:

    public class Exemplo { public Exemplo() { } }

    A acessibilidade do construtor por omissão é igual à da classe. Integridade dos tipos de dados O encapsulamento permite reforçar a integridade dos tipos de dados, não permitindo aos programadores o acesso, de um modo inapropriado, aos campos de dados individuais. Consideremos o exemplo de uma classe que permita criar objectos representativos de um determinado instante de tempo caracterizados por três atributos: hora, minuto e segundo. A classe só deve permitir colocar os objectos com valores válidos de hora (0..23), minuto (0..59), e segundo (0..59). Para que os atributos de um objecto só possam ter valores válidos é necessário:

    declará-los como privados, para não poderem ser acedidos directamente de fora da classe, e

    providenciar código público que permita colocar valores nos campos de dados mas apenas valores válidos.

    A classe poderia ser definida do seguinte modo:

    public class Tempo { private int hora; // 0 - 23 private int minuto; // 0 - 59

    private int segundo; // 0 - 59 public Tempo() { hora = 0; minuto = 0; segundo = 0; } public Tempo( int h, int m, int s ) { setHora( h ); setMinuto( m ); setSegundo( s ); } public void setHora( int h ) { hora = ( ( h >= 0 && h < 24 ) ? h : 0 ); } public void setMinuto( int m ) { minuto =( ( m >= 0 && m < 60 ) ? m : 0 ); }

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 15

    public void setSegundo( int s ) { segundo =( ( s >= 0 && s < 60 ) ? s : 0 ); } public int getHora() { return hora; } public int getMinuto() { return minuto; } public int getSegundo() { return segundo; } }

    Os campos de dados devem ser o mais privados possível, para manter a consistência dos dados. Os benefícios da programação orientada por objectos resultam de esconder a implementação da classe atrás de operações realizadas nos seus dados. As operações de uma classe são declaradas através dos seus métodos – instruções que operam nos dados de um objecto para obter um dado resultado. Os métodos acedem aos dados que estão escondidos de outros objectos. Métodos Os métodos são invocados como operações em objectos através das suas referências usando o operador ponto.

    referência.método ( listaDeParâmetros ) Cada parâmetro tem um tipo específico: tipo primitivo ou tipo referência. Os métodos determinam as mensagens que os objectos podem receber. Os métodos em Java só podem ser criados como parte de uma classe. Um método só pode ser chamado para um objecto (excepto os métodos estáticos). O acto de chamar um método para um objecto é normalmente referido como o envio de uma mensagem a um objecto. Os métodos também têm um tipo de retorno que é declarado antes do nome do método. A palavra-chave return causa o abandono do método e se existe uma expressão à frente da instrução return, o valor dessa expressão é o resultado da execução do método. Quando o tipo de retorno é void, a palavra-chave return (sem expressão à frente) só é usada para sair do método, ou pode não ser necessária se a execução atinge o fim do método. Consideremos a necessidade de criar objectos representativos de astros através do nome, número de identificação único, e astro em torno do qual orbita. A classe poderia ser definida do seguinte modo:

    public class Astro { private int numId; private String nome; private Astro orbitaEmVolta; private static int proxNumId = 0;

    public Astro() { numId = proxNumId ++;

    }

    DEI - ISEP Fernando Mouta

  • 16 Programação Orientada por Objectos

    public Astro( String nomeAstro) { this();

    nome = nomeAstro; } public Astro( String nomeAstro, Astro orbita) { this(nomeAstro);

    orbitaEmVolta = orbita; } public String toString() { String descr = numId + “(“ + nome + “)”; if ( orbitaEmVolta != null ) descr += “ orbita em volta de “ +

    orbitaEmVolta.toString(); return descr;

    } }

    Criação e inicialização de objectos que representam astros:

    Astro a1 = new Astro(“Sol”); Astro a2 = new Astro(“Terra”, a1); Astro a3 = new Astro(“Lua”, a2); Astro a4 = new Astro(“Marte”, a1);

    O método toString() é um método especial: se um objecto tem um método público designado toString que não recebe argumentos e retorna uma String, quando esse objecto é usado numa concatenação de strings, o método toString é implicitamente invocado para obter uma String. O resultado da execução do seguinte código:

    System.out.println(“Astro “ + a1); System.out.println(“Astro “ + a2); System.out.println(“Astro “ + a3);

    Será:

    Astro 0 (Sol) Astro 1 (Terra) orbita em volta de 0 (Sol) Astro 2 (Lua) orbita em volta de 1 (Terra) orbita em volta de 0 (Sol)

    Em todas as classes que construímos deveríamos definir o método toString(). No entanto se nenhum método toString() está definido numa classe, essa classe herda o método toString() da classe Object, o qual retorna uma string relativa ao tipo de objecto, mas pouco expressiva.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 17 Passagem de parâmetros na chamada de métodos Em muitas linguagens de programação há dois modos de passar argumentos na chamada de métodos:

    • por valor • por referência

    Quando um parâmetro é passado por valor, uma cópia do valor do argumento é usada pelo método chamado. Quando um parâmetro é passado por referência, o método chamado acede directamente esse parâmetro e através dele pode modificar os dados do parâmetro. A passagem por referência aumenta a eficiência porque elimina a sobrecarga de copiar grandes quantidades de dados mas enfraquece a segurança, porque o método chamado acede directamente os dados. Em Java todos os parâmetros são passados por valor: • Os tipos de dados primitivos são sempre passados por valor. • Os objectos são passados através dos valores das variáveis referência (que

    referenciam os respectivos objectos). Esses valores das variáveis referência são também passados por valor. Assim o método chamado usa uma cópia do valor da variável que referencia o objecto e acede directamente os dados do objecto podendo modificar esses dados.

    Por isso, algumas vezes se diz incorrectamente que os objectos são passados por referência, mas não é verdade, porque o método chamado não acede directamente à variável referência do objecto usada na invocação do método. Se o método chamado alterar o valor da variável referência do objecto (deixando de referenciar o objecto, passando a ter o valor “null” ou a referenciar outro objecto), no programa que chamou o método, a variável que referencia o objecto não sofre alteração. Para passar um objecto a um método como parâmetro: • na chamada do método especifica-se simplesmente a referência ao objecto • dentro do método o valor da variável parâmetro (variável referência) é uma cópia do

    valor especificado na invocação como argumento. Quando se retorna informação de um método através de uma instrução “return”, os tipos de dados primitivos são sempre retornados por valor e os objectos são retornados através da referência ao objecto. Um método pode retornar mais que um resultado por uma qualquer das seguintes formas: • Retornando uma referência a um objecto que armazena os resultados como campos

    de dados. • Possuindo um ou mais parâmetros que referenciam objectos nos quais se podem

    armazenar os resultados. • Retornando um array que contém os resultados.

    DEI - ISEP Fernando Mouta

  • 18 Programação Orientada por Objectos

    Nomes dos argumentos dos métodos Quando se declara um argumento de um método especifica-se um nome para esse argumento. Esse nome é usado dentro do corpo do método. Um argumento de um método pode ter o mesmo nome que as variáveis membros da classe. Neste caso, o argumento esconde a variável membro da classe. Exemplo num construtor:

    class Circulo { int x, y, raio; public Circulo( int x, int y, int raio) { . . . } }

    O uso de x, y e raio dentro do construtor refere-se aos argumentos e não aos campos de dados da classe. Para aceder aos campos de dados da classe deve-se referenciar através de this (o objecto corrente):

    class Circulo { int x, y, raio; public Circulo( int x, int y, int raio) { this.x = x; this.y = y; this.raio = raio; } }

    Esconder identificadores deste modo deliberadamente só é considerado uma boa prática de programação neste uso em construtores e em métodos de acesso aos campos de dados de um objecto. this é usado no sentido de este objecto, o objecto corrente. this só pode ser usada dentro de um construtor ou método e produz uma referência para o objecto corrente (o objecto construído ou o objecto para o qual o método foi chamado).

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 19 Esta referência (this) pode ser usada como qualquer outra referência de um objecto. No entanto se dentro de um método se chama outro método da mesma classe não é necessário usar this – basta chamar o método. A palavra-chave this só deve ser usada quando é necessário usar explicitamente a referência ao objecto corrente. A palavra-chave this é usada normalmente nos seguintes casos: • Dentro de um método não estático ou dentro de um construtor para referenciar o

    objecto corrente no qual o método foi invocado se algum dos argumentos do método tem o mesmo nome que um campo do objecto.

    • Para passar uma referência do objecto corrente como um parâmetro a outros métodos.

    • Em instruções return para retornar a referência ao objecto corrente. No exemplo apresentado a seguir this é usado numa instrução return para retornar a referência ao objecto corrente:

    // Folha.java public class Folha { private int i = 0; Folha incremento() { i++; return this; } void mostrar() { System.out.println(“i = “ + i); }

    public static void main(String args []) { Folha f = new Folha(); f.incremento().incremento().incremento().mostrar();

    } }

    Métodos estáticos São métodos que não estão associado com qualquer objecto particular de uma classe. Um uso importante dos métodos estáticos é permitir chamar esses métodos sem criar qualquer objecto. Um exemplo é o método main() – ponto de entrada para correr uma aplicação. Os métodos estáticos não podem aceder directamente membros não estáticos (campos de dados ou métodos) – chamando simplesmente esses outros membros sem referir um nome de um objecto - dado que os membros não estáticos devem estar ligados a objectos particulares.

    class Exemplo { int x; public static void setX( int xx) [ x = xx; } }

    DEI - ISEP Fernando Mouta

  • 20 Programação Orientada por Objectos

    O compilador daria o seguinte erro:

    não pode fazer uma referência estática a uma variável não estática.

    Para corrigir, poderíamos alterar a definição do método setX passando-o para método de instância ou alterar o campo de dados x colocando-o estático. Vamos corrigir a definição da classe através da alteração do campo de dados x:

    class Exemplo { static int x; public static void setX( int xx) [ x = xx; } }

    Consideremos o seguinte código: Exemplo ex1 = new Exemplo(); Exemplo ex2 = new Exemplo(); ex1.x = 1; ex2.x = 2; Exemplo.setX(3); System.out.println(“ex1.x = “ + ex1.x); System.out.println(“ex2.x = “ + ex2.x); Produz: ex1.x = 3; ex2.x = 3; Um método estático, como qualquer outro método, pode criar ou usar objectos com nome, incluindo objectos do seu tipo. Há 2 maneiras de referenciar um método estático: através de um objecto como para qualquer outro método ou através do nome da classe (preferível). Um método estático é invocado em nome de toda a classe e não em nome de um objecto específico da classe, e por isso se designa por método da classe. Num método estático não se pode usar a plavra-chave this. Overloading de Métodos Um nome de um método é um nome para uma acção ou conjunto de acções. Um nome apropriado torna o programa mais fácil de ser comprendido e mantido. Na linguagem natural uma mesma palavra pode ter vários significados, que são deduzidos pelo contexto. Não é necessário ter identificadores únicos para cada conceito. Em muitas linguagens de programação, como em C, é necessário ter um identificador único para cada função. Em Java podem existir métodos ou construtores com o mesmo nome e diferente lista de tipos de parâmetros.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 21 Cada método tem uma assinatura. Assinatura de um método – é o seu nome em conjunto com a lista dos tipos dos seus

    parâmetros. “Overloading” – é a existência de mais que um método com o mesmo nome mas

    diferente lista de tipos de parâmetros e portanto diferentes assinaturas, na mesma classe ou numa hierarquia de superclasse e subclasses.

    Designa-se por “overloading” porque o nome do método tem um significado sobrecarregado (“overloaded”), isto é, tem mais que um significado. A assinatura não inclui o tipo de retorno ou a lista de “thrown exceptions”, e portanto não se pode usar métodos “overload” baseado nesses factores. É um erro de sintaxe se um método numa classe e outro método nessa classe ou numa sua subclasse ou superclasse têm a mesma assinatura mas diferentes tipos de retorno. Ex.: public boolean orbita(Astro corpo) { return (orbitaEmVolta == corpo); } public boolean orbita(int id) { return (orbitaEmVolta != null && orbitaEmVolta.numId == id); } Uso de Métodos para Controlar o Acesso No exemplo da classe Astro o campo numId é colocado (recebe um valor) de um modo automático e correcto pelo construtor quando se cria um objecto. No entanto este campo numId, declarado com public, permite o acesso de fora da classe. Este acesso é útil para leitura do valor mas não deveria ser possível alterar o valor. Para tornar o campo só de leitura (“read only”) fora da classe deve-se esconder o campo declarando-o private e fornecer um novo método público para permitir a leitura do campo fora da classe. Ex.:

    public class Astro { private int numId; private String nome; private Astro orbita; private static int proxNumId = 0; Astro() { numId = proxNumId ++;

    DEI - ISEP Fernando Mouta

  • 22 Programação Orientada por Objectos

    } public int getNumId() { return numId; } }

    Deste modo o campo numId só pode ser modificado por métodos dentro da classe Astro. Métodos que regulam o acesso a dados internos designam-se por métodos de acesso. Normalmente os campos de dados de uma classe declaram-se como private e adicionam-se métodos para colocar (set) e retribuir (get) os valores desses campos de dados. Os métodos para colocar valores nos campos de dados devem verificar a consistência dos dados só permitindo alterações se adequadas. A representação interna dos dados (usada dentro da classe) não interessa aos clientes da classe e por isso deve estar escondida deles, o que facilita a possibilidade de modificação da implementação da classe e também simplifica a percepção que os clientes têm da classe. Os métodos “public” apresentam aos clientes da classe os serviços que a classe fornece. Na programação orientada por objectos as variáveis instância e os métodos estão encapsulados dentro de um objecto e por isso os métodos podem aceder às variáveis instância. Os acesso de fora da classe a dados “private” devem ser cuidadosamente controlados por métodos da classe. Por exemplo um método que permita modificar dados “private” deve validar os valores dos dados e traduzir a representação dos dados para a forma usada na implementação. Poderá parecer equivalente entre disponibilizar métodos “public” para ler e alterar valores de variáveis instância ou declarar as variáveis instância “public”. A diferença reside no facto de que, se as variáveis instância são “public”, podem ser lidas e alteradas por qualquer método do programa, enquanto que se forem “private” e se se incluir métodos “public” para ler e alterar, essas variáveis podem ser lidas por qualquer método do programa (mesmo de outras classes), mas o método que efectua a sua alteração (método da classe) deve controlar o valor e o formato dos dados. Ainda um método para alterar o valor de uma variável instância pode retornar valores indicando que foi feita uma tentativa de atribuição de valores inválidos a um objecto da classe. Isto permite aos clientes da classe testar os valores retornados por estes métodos para determinar se os objectos que estão a manipular são válidos e tomar acções apropriadas se eles não são válidos. Libertação da memória ocupada por objectos

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 23 A memória ocupada por um objecto criado com new, pode ser libertada quando já não se precisa do objecto, deixando de o referenciar (terminando o âmbito de validade (“scope “) da variável que o referencia, ou alterando o valor dessa variável). Variáveis declaradas dentro de um bloco ({ ... }) perdem o âmbito de validade quando a execução do programa sai do bloco. Ex.: { String s = “Uma string”; System.out.println(s); } Em Java, a memória ocupada por objectos não referenciados é automaticamente libertada pelo “garbage collector”, que corre em background, e verifica para todos os objectos criados com new os que já não estão referenciados. Um objecto não é atingível, quando não existe nenhuma referência ao objecto em qualquer variável de qualquer método em execução, ou em qualquer campo ou elemento de array de uma dessas variáveis. O garbage collector elimina a necessidade de libertar explicitamente a memória ocupada pelos objectos, mas só realiza essa libertação de memória quando é necessário evitar que a memória fique cheia ou porque necessita de mais espaço de memória. Inicialização de variáveis por omissão Variáveis membros de classes são automaticamente inicializadas quando se criam objectos. Para cada objecto é alocado memória no heap e preenchida com 0´s do que resultam os seguintes valores conforme os tipos dos campos de dados: Tipos: Valores por omissão: byte, short, int, long 0 float 0.0f double 0.0 char ‘\u0000’ boolean false referência de objectos null Variáveis locais – não campos de dados de uma classe, mas declaradas em métodos – não são automaticamente inicializadas. Estas variáveis são armazenadas na stack e é necessário inicializá-las antes de as usar senão dá erro de compilação. Arrays - Para tipos primitivos de dados: int a [] = new int [2]; a[0] = 7; a[1] = 10;

    DEI - ISEP Fernando Mouta

  • 24 Programação Orientada por Objectos

    Criação por inicialização directa: int a[] = {7, 10}; Arrays de objectos Considerando a seguinte definição da classe Ponto: class Ponto { double x, y; public Ponto(int x, int y) { this.x = x; this.y = y; System.out.println("Ponto (" + x + ", " + y + ")"); } } Das instruções seguintes, a 1.ª e a 3.ª criam objectos, enquanto que a 2.ª não cria qualquer objecto, como se poderia verificar pela execução do programa através das mensagens emitidas pelo construtor.

    Ponto p1 = new Ponto(2, 3); Ponto p2 = p1; new Ponto(4, 5);

    Quando se cria um array de objectos o que realmente fica criado é um array de referências inicializadas a “null”. Depois é necessário atribuir objectos às referências. Ex. Ponto [] pontos = new Ponto[3]; pontos[0] = new Ponto(0, 0); pontos[1] = new Ponto(1, 1); pontos[2] = new Ponto(2, 2); ou, de outro modo, se se pretendesse ler os valores do teclado: Ponto [] pontos = new Ponto[3]; int x,y; for (int i=0; i < pontos.length ; i++) { x = lerNumero(“Coordenada x do ponto “ + (i+1)); y = lerNumero(“Coordenada y do ponto “ + (i+1)); pontos[i] = new Ponto(x, y); } Criação de um array de objectos por inicialização directa: Ponto [] pontos = { new Ponto(0, 0), new Ponto(1, 1), new Ponto(2, 2) }; Em Java 1.1:

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 25

    Ponto [] pontos = new Ponto [] { new Ponto(0, 0), new Ponto(1, 1), new Ponto(2, 2) }

    Uma vírgula final na lista de inicializadores é opcional (para fácil manutenção):

    Ponto [] pontos = { new Ponto(0, 0), new Ponto(1, 1), new Ponto(2, 2), }

    Array de arrays: Ponto [][] paresDePontos = {

    { new Ponto(0, 0), new Ponto(0,1) }, { new Ponto(1, 1), new Ponto(1,2) }, { new Ponto(2, 2), new Ponto(2,3) },

    }

    Reutilização de Código

    Uma das características mais importantes do Java é a possibilidade de reutilização de código. A aproximação tradicional de reutilização de código consiste em copiar e adaptar. Em Java a reutilização de código consiste na criação de classes que usam outras classes já existentes. Para isso uma classe deve ser projectada para resolver problemas genéricos e não casos específicos. Há 2 maneiras de reutilizar código: 1. Dentro de uma nova classe criam-se objectos de classes já existentes. Este método

    designa-se por composição porque a nova classe é composta de objectos de classes existentes.

    2. Criando uma nova classe como um tipo de uma classe existente. Usa-se a forma da classe existente e adiciona-se código. Este método designa-se por herança.

    São dois modos de criar novos tipos a partir de tipos existentes. Mas enquanto na composição apenas se utiliza a funcionalidade do código e não a sua forma, na herança utiliza-se a forma do código. Criação de Subclasses Declara-se que uma classe é um asubclasse de outra na declaração da classe: class SubClasse extends SuperClasse { . . . }

    DEI - ISEP Fernando Mouta

  • 26 Programação Orientada por Objectos

    Uma subclasse herda variáveis e métodos da superclasse e de todas as superclasses desta. Ex.: import java.awt.Color; class Ponto { double x, y; public Ponto(int x, int y) { this.x = x; this.y = y; System.out.println("Ponto (" + x + ", " + y + ")"); } } class Pixel extends Ponto { Color cor; public Pixel() { super(0,0); cor = null; } }

    Herança Herança é uma forma de “reusar software” (“software reusability”) na qual novas classes são criadas a partir de classes existentes absorvendo (herdando) os seus atributos e comportamentos e acrescentando novos atributos e comportamentos que as novas classes necessitem. Assim, ao criar uma nova classe, em vez de incorporar todas as variáveis instância e métodos instância novos que a classe necessita, o programador pode designar (especificar) que a nova classe herda variáveis instância e métodos instância de uma outra classe previamente definida. A nova classe designa-se por subclasse e a classe da qual herda variáveis e métodos instância designa-se por superclasse. Todo o objecto da subclasse é também um objecto da superclasse, mas o inverso não é verdade. Como uma subclasse normalmente adiciona variáveis instância e métodos instância próprios, uma subclasse é geralmente maior que a sua superclasse (tem mais características: atributos e comportamentos) mas também é mais específica e representa um menor grupo de objectos. “Overriding” de Métodos (Reescrita de Métodos)

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 27 Uma subclasse começa do mesmo que a superclase mas tem a possibilidade de definir adições ou substituições das características herdadas da superclasse. Pode acontecer que uma subclasse possa herdar variáveis instância ou métodos instância que não necessita ou que expressamente não deva ter. Quando um membro de uma superclasse não é apropriado para uma subclasse, esse membro pode ser “overriden” (sobreposto, reescrito) na subclasse através da definição de uma variável instância com o mesmo nome e tipo ou de um método com a mesma assinatura. Quando um método “overrides” (se sobrepõe) a um método da superclasse, o método da superclasse pode ser acedido da subclasse precedendo o nome do método da superclasse com “super” seguido de ponto. Quando o método é invocado da subclasse sem a referência “super.” a versão da subclasse é automaticamente seleccionada. Ex.: import java.awt.Color; class Ponto { double x, y; public void limpar() { x = 0; y = 0; } } class Pixel extends Ponto { Color cor; public void limpar() { super.limpar(); cor = null; } } Construtores em Classes Estendidas Quando se estende uma classe a nova classe deve escolher um dos construtores da superclasse para invocar. Um objecto terá uma parte controlada pela superclasse que deve ser iniciada e outra parte controlada pela subclasse relativa aos campos de dados adicionados. Num construtor de uma subclasse, pode-se invocar directamente um construtor da superclasse usando a instrução super(), que é uma invocação explícita de um construtor da superclasse. Se não se invoca explicitamente um construtor da superclasse como primeira instrução executável de um construtor, o construtor sem argumentos da superclasse é automaticamente invocado antes de qualquer instrução ser executada.

    DEI - ISEP Fernando Mouta

  • 28 Programação Orientada por Objectos

    Se a superclasse não tem um construtor sem argumentos deve-se invocar explicitamente um dos construtores da superclasse ou então outro construtor da mesma classe usando this(). Se uma classe estendida não declara qualquer construtor, o compilador cria um construtor por omissão (sem argumentos) que começa por invocar o cosntrutor sem argumentos da superclasse: public class SubClasse extends SuperClasse { public SubClasse () { super(); } } Conversão implícita e explicita entre referências de objectos Java efectua a conversão implícita de uma referência a um objecto de uma classe para uma referência a um objecto de uma superclasse da classe. Esta conversão designa-se por “upcasting” (“casting up” na hierarquia de classes) ou conversão segura (“safe casting”) porque é sempre válida. Ex.: Como todas as classes são subclasses da classe Object, Object é um tipo referência

    genérico que pode referenciar qualquer objecto de qualquer classe: Object o = new Ponto(); o = “Objecto String”; A conversão de uma referência a um objecto de uma classe para uma referência a uma subclasse só é possível se o objecto é realmente um objecto da subclasse e é necessário efectuar uma conversão explícita. Ex.: Object o1 = new Ponto(); Ponto o2 = (Ponto) o1; Esta conversão designa-se por “downcasting” (“casting down” na hierarquia de classes) ou conversão insegura (“unsafe casting”) porque nem sempre é válida. No entanto esta conversão é necessária para aceder às funcionalidades que a subclasse adiciona. Uma conversão explícita para uma classe que não é subclasse da classe a que o objecto pertence dá erro de compilação. Uma conversão explícita para uma subclasse é potencialmente correcta, mas se em tempo de execução não é válida, porque o objecto não é realmente um objecto da subclasse, uma excepção “ClassCastException” é lançada. Embora um objecto de uma subclasse possa ser tratado como um objecto da superclasse os tipos subclasse e superclasse são diferentes.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 29 No entanto, é possível referenciar um objecto de uma subclasse com uma referência da superclasse ( Java efectua uma conversão implícita ). Através da referência da superclasse só se pode aceder a membros da superclasse. Referência a membros só da subclasse (membros não herdados da superclasse), através da referência da superclasse causa erro de sintaxe. Referenciar um objecto de uma superclasse com uma referência de uma subclasse causa erro de sintaxe a não ser que o objecto seja realmente um objecto da subclasse e após um "cast" do tipo superclasse para o tipo subclasse. Se o objecto referenciado não é um objecto da subclasse, não tem valores para as variáveis instância que pertencem só à subclasse, pelo que não faz sentido referenciá-lo através de uma variável referência da subclasse. Polimorfismo O mecanismo de polimorfismo do Java permite que usando uma referência de uma superclasse para referenciar um objecto, instância de uma subclasse, e invocando um método que exista na superclasse e que também exista (“overriden”) em uma subclasse, o programa escolherá dinamicamente (isto é, em tempo de execução) o método correcto da subclasse. Isto designa-se por ligação dinâmica de método ("dynamic method binding"). Através do uso de polimorfismo, uma invocação de um método pode causar diferentes acções dependendo do tipo de objecto que recebe a chamada. Ex.: class FormaGeom { void desenhar() { System.out.println(“FormaGeom.desenhar()”) } } class Circunferencia extends FormaGeom { void desenhar() { System.out.println(“Circunferencia.desenhar()”) } } class Quadrado extends FormaGeom { void desenhar() { System.out.println(“Quadrado.desenhar()”) } } class Triangulo extends FormaGeom { void desenhar() { System.out.println(“Triangulo.desenhar()”) } }

    DEI - ISEP Fernando Mouta

  • 30 Programação Orientada por Objectos

    public class Teste { public static FormaGeom formaGeomAleat() { switch ( (int) (Math.random() * 3) ) { case 0 : return new Circunferencia(); case 1 : return new Quadrado(); case 2 : return new Triangulo(); } } public static void main( String [] args ) { FormaGeom [] fg = new FormaGeom[6]; for (int i= 0; i < fg.length; i++) fg[i] = formaGeomAleat(); for (int i= 0; i < fg.length; i++) fg[i].desenhar();

    }

    } Exemplo de saída produzida pelo programa:

    Circunferencia.desenhar() Triangulo.desenhar() Circunferencia.desenhar() Quadrado.desenhar() Triangulo.desenhar() Circunferencia.desenhar()

    Programas que invocam comportamento polimórfico podem ser escritos independentemente dos tipos de objectos para os quais as mensagens (isto é, as chamadas de métodos) são enviadas. Podem-se adicionar novos tipos de objectos que respondem a mensagens existentes sem modificar o sistema, o que promove a facilidade de extensão (extensibilidade). Como uma classe implementa um tipo, normalmente serão instanciados (criados) objectos dessa classe. Contudo há casos nos quais é útil definir classes para as quais não se pretende instanciar objectos mas que se destinam apenas a ser usadas como superclasses, para permitir usar os mecanismos de herança e polimorfismo. Estas classes designam-se por abstractas e não podem ser instanciados objectos destas classes. Uma classe abstracta tem de ser declarada com a palavra-chave "abstract". Uma classe que contenha um método abstracto (declarado com a palavra chave "abstract") é uma classe abstracta e por isso deve ser explicitamente declarada como uma classe abstracta.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 31 Se uma subclasse deriva de uma superclasse com um método abstracto, e se na subclasse não se fornece nenhuma definição para esse método abstracto (esse método não é "overriden" na subclasse) então esse método permanece abstracto na subclasse, pelo que a subclasse é também uma classe abstracta e assim deve ser explicitamente declarada. A declaração de um método como abstracto numa classe obriga que esse método seja “overriden” (reescrito) nas subclasses para que se possam instanciar objectos dessas subclasses. Embora não se possam instanciar objectos de superclasses abstractas, pode-se declarar referências de superclasses abstractas, as quais podem ser usadas para permitir manipulações polimórficas de objectos de subclasses. Polimorfismo é particularmente adequado para implementar sistemas de software por camadas ("layered"). Ex.: abstract class FormaGeom { public abstract void desenhar(); public String toString() { return “Forma Geometrica”; } } class Ponto { protected double x,y; public Ponto( double x, double y ) {this.x = x; this.y = y; } public String toString() { return “Ponto de coordenadas = (“ + x + “, “ + y + “)”; } } class SegmRecta extends FormaGeom { protected Ponto p1, p2; public SegmRecta( double x1, double y1, double x2, double y2 ) { p1 = new Ponto(x1, y1); p2 = new Ponto(x2, y2); } public double comprimento() { double dx = p1.x - p2.x; double dy = p1.y - p2.y; return Math.sqrt(dx*dx + dy*dy); } public void desenhar() { System.out.println("\tDesenhar um segmento de recta"); } public String toString() { return super.toString() + ": Segmento de Recta de comprimento = " + comprimento(); }

    DEI - ISEP Fernando Mouta

  • 32 Programação Orientada por Objectos

    } class Circunferencia extends FormaGeom { protected double raio; public Circunferencia( double r ) { raio = r ; } public void desenhar() { System.out.println("\tDesenhar uma circunferencia"); } public String toString() { return super.toString() + “: Circunferencia de raio = “ + raio; } } class Quadrado extends FormaGeom { protected double lado; public Quadrado( double l ) { lado = l; } public void desenhar() { System.out.println("\tDesenhar um quadrado"); } public String toString() { return super.toString() + “: Quadrado de lado = “ + lado; } } class Triangulo extends FormaGeom { protected double base, altura; public Triangulo( double b, double a ) { base = b; altura = a; } public void desenhar() { System.out.println("\tDesenhar uma triangulo"); } public String toString() { return super.toString() + “: Triangulo de base = “ + base + “ e de altura = “ + altura; } } public class Teste { public static void main(String args []) throws java.io.IOException { FormaGeom fg []= new FormaGeom [3]; fg[0] = new SegmRecta(4, 5, 8, 9); fg[1] = new Circunferencia(4); fg[2] = new Quadrado(3); fg[3] = new Triangulo(4, 3); for (int i= 0; i

  • Programação Orientada por Objectos 33 } Saída produzida pelo programa: Forma Geometrica: Segmento de Recta de comprimento = 5.65685 Desenhar um segmento de recta Forma Geometrica: Circunferencia de raio = 4 Desenhar uma circunferencia Forma Geometrica: Quadrado de lado = 3 Desenhar um quadrado Forma Geometrica: Triangulo de base = 4 e de altura = 3 Desenhar uma triangulo Pretendemos que a classe FormaGeom contenha todas as características que as formas geométricas têm em comum: campos de dados e métodos. Mas a classe genérica FormaGeom não deve implementar o método desenhar, porque não representa uma forma concreta. Este método deve ser declarado abstracto. Os métodos abstractos são definidos sem a implementação; não têm corpo, só têm assinatura terminada por ;.

    Classes abstractas Qualquer classe com pelo menos 1 método abstracto é uma classe abstracta e tem de ser declarada como tal. Uma classe abstracta não pode ser instanciada. Uma classe abstracta refere-se a conceitos tão genéricos que não é útil criar objectos dese tipo. Uma classe também pode ser declarada abstracta mesmo sem métodos abstractos, ficando inibida a possibilidade de ser instanciada. Uma subclasse de uma classe abstracta pode ser instanciada se reescreve todos os métodos abstractos da superclasse e fornece uma implementação para eles. Se uma subclasse de uma classe abstracta não implementa pelo menos 1 método abstracto que herde, a subclasse é abstracta e como tal deve ser declarada.

    Interfaces Interfaces, como classes e métodos abstractos, fornecem declarações de comportamentos que outras classes devem implementar. A herança simples é restritiva quando pretendemos usar um determinado comportamento em diferentes ramos de uma árvore hierárquica. Com herança múltipla, uma classe pode herdar de mais que uma superclasse obtendo ao mesmo tempo atributos e comportamentos de todas as superclasses. No entanto a múltipla herança torna a linguagem de programação muito mais complicada. Java só possui herança simples. Para resolver o problema da necessidade de comportamentos comuns em classes em diferentes ramos de uma árvore hierárquica, Java tem outra hierarquia de comportamentos de classes, a hierarquia de interfaces.

    DEI - ISEP Fernando Mouta

  • 34 Programação Orientada por Objectos

    Assim quando criámos uma classe, essa classe só tem uma superclasse, mas pode escolher diferentes comportamentos da hierarquia de interfaces. Um interface em Java é uma colecção de comportamentos abstractos que podem ser incluídos em qualquer classe para adicionar a essa classe comportamentos que não são fornecidos pelas superclasses. Um interface em Java só contém definições de métodos abstractos e constantes estáticas – não contém variáveis instância nem implementação de métodos. Os interfaces são como as classes, declarados em ficheiros fonte, compilados em ficheiros .class e podem ser usados para tipos de dados de variáveis. São diferentes das classes porque não podem ser instanciados. Ex.: public interface Imposto { double calculoDoImposto(); } Ao contrário das classes um interface pode ser adicionado a uma das classes que já é uma subclasse de outra classe. Pode-se pretender usar este interface em muitas classes diferentes: roupa, comida, carros, etc. Seria inconveniente que todos estes objectos derivassem de uma única classe. Para além disso cada tipo diferente poderá ter um modo diferente de cálculo de imposto. Assim define-se o interface Imposto e cada classe deve implementar esse interface. Um interface pode conter vários métodos (incluindo métodos “overloaded”) e campos de dados, mas estes têm de ser static final. Ex.: abstract class FormaGeom { public abstract void desenhar(); public String toString() { return "Forma Geometrica"; }; } interface Dimensoes { double area(); double perimetro(); } class Ponto { protected double x,y; public Ponto( double x, double y ) {this.x = x; this.y = y; } public String toString() { return super.toString() + ": Ponto de Coordenadas = (" + x + ", " + y + ")"; } } class SegmRecta extends FormaGeom { protected Ponto p1, p2; public SegmRecta( double x1, double y1, double x2, double y2 ) { p1 = new Ponto(x1, y1); p2 = new Ponto(x2, y2); } public double comprimento() { double dx = p1.x - p2.x; double dy = p1.y - p2.y;

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 35 return Math.sqrt(dx*dx + dy*dy); } public void desenhar() { System.out.println("\tDesenhar um segmento de recta"); } public String toString() { return super.toString() + ": Segmento de Recta de comprimento = " + comprimento(); } } class Circunferencia extends FormaGeom implements Dimensoes { protected double raio; public Circunferencia( double r ) { raio = r; } public double area() { return Math.PI * raio * raio; } public double perimetro() { return 2 * Math.PI * raio; } public void desenhar() { System.out.println("\tDesenhar uma circunferencia"); } public String toString() { return super.toString() + ": Circunferencia de raio = " + raio; } } class Quadrado extends FormaGeom implements Dimensoes { protected double lado; public Quadrado( double l ) {lado = l; } public double area() { return lado * lado; } public double perimetro() { return 4 * lado; } public void desenhar() { System.out.println("\tDesenhar um quadrado"); } public String toString() { return super.toString() + ": Quadrado de lado = " + lado; } } class Triangulo extends FormaGeom implements Dimensoes { protected double base, altura; public Triangulo( double b, double a ) { base = b; altura = a; } public double area() { return base * altura / 2; } public double perimetro() { return base+2*Math.sqrt(base*base/4+altura*altura); } public void desenhar() { System.out.println("\tDesenhar uma triangulo"); } public String toString(){ return super.toString() + ": Triangulo de base = "+base+" e de altura = "+altura; }

    DEI - ISEP Fernando Mouta

  • 36 Programação Orientada por Objectos

    } public class Teste { public static void main(String args []) { FormaGeom fg []= new FormaGeom [4]; fg[0] = new SegmRecta(4, 5, 8, 9); fg[1] = new Circunferencia(4); fg[2] = new Quadrado(3); fg[3] = new Triangulo(4, 3); for(int i= 0; i

  • Programação Orientada por Objectos 37 Outro exemplo do uso de interfaces: Faça um programa que construa uma tabela para os quadrados dos inteiros de 1 a 5 e outra para os cubos dos inteiros dos múltiplos de 10 entre 10 e 50. interface Funcao { public abstract int f(int x); } class Quadrado implements Funcao{ public int f(int x) { return x*x; } } class Cubo implements Funcao { public int f(int x) { return x*x*x; } } class Tabela { public static void tabela(Funcao t, int a, int b, int incr) { System.out.println( "x\t| f(x)\n--------------"); for (int x=a; x

  • 38 Programação Orientada por Objectos

    50 | 125000 Polimorfismo A herança é usada com 2 objectivos: • reutilização de código, e • implementação de polimorfismo. O benefício do polimorfismo é permitir que numa hierarquia de tipos com o mesmo interface (os mesmos métodos públicos), código que funciona com um tipo genérico também funcione com qualquer objecto que seja subtipo desse tipo. Isto simplifica a escrita de código, a leitura e compreensão e a manutenção. Os comportamentos comuns definem-se na classe base (superclasse). Qualquer objecto de um subtipo pode receber as mesmas mensagens que a classe base, e pode ser tratado como um objecto da classe base. Ainda permite uma fácil extensão de um programa com um mínimo de modificações, pois código que funciona com um tipo genérico também funciona com novos subtipos que se criem. Composição vs Herança Usa-se a composição quando se pretende as características de uma classe existente dentro da nova classe, mas não o interface da classe existente. Usa-se a herança quando se pretende especializar uma classe existente para um fim particular. Um modo claro de determinar se se deve usar composição ou herança obtém-se determinando a relação entre os objectos da classe que se pretende criar e da classe existente: A relação tem-um (“has-a”) é expressa através da composição e a relação é-um (“is-a”) exprime-se através da herança. Ex. de composição: Uma circunferência tem um ponto que é o centro

    mas uma circunferência não é um ponto. Ex. de herança: Um carro é um veículo mas um carro não contém

    um veículo. Packages Os packages permitem criar um grupo de classes relacionadas (logicamente). Usam-se para:

    • tornar mais fácil encontrar e usar classes, • evitar conflitos de nomes entre classes, • controlar o acesso de umas classes às outras.

    Um package é uma unidade de uma biblioteca (“library”) de classes formada por um dado conjunto de classes.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 39 Conflitos entre nomes Os nomes de todos os membros de uma classe estão isolados dos nomes dos membros de outras classes. Mas os nomes de classes podem coincidir. Para criar um package coloca-se uma classe dentro dele. Para incluir uma classe num package coloca-se a instrução package no início do ficheiro fonte no qual a classe está definida.

    package utilitarios; O nome do package é implicitamente prefixado a cada nome de classe (tipo) contido no package. Uma instrução package deve ser a primeira instrução de um ficheiro (para além dos comentários). O âmbito de validade de um instrução package estende-se a todo o ficheiro fonte. Em cada ficheiro só pode existir uma declaração de um package. Se não se usa a instrução package a classe pertence ao “unnamed package”. Todos os tipos que não especificam nome de package são tratados como pertencendo ao mesmo package, único para a máquina virtual em execução. Ficheiros fonte e ficheiros classe Um ficheiro fonte constitui uma unidade de compilação, pode conter mais que uma classe, mas só pode haver uma classe pública, que deve ter o mesmo nome que o ficheiro fonte (incluindo letras maiúsculas, mas excluindo a extensão .java). Depois de compilado obtém-se um ficheiro para cada classe, com o nome da classe e a extensão .class. Se um ficheiro fonte não contém nenhuma classe pública, pode ter qualquer nome. Os ficheiros classe devem ser colocados num directório cujo nome reflecte o nome do package, para que o compilador e o interpretador possam encontrar as classes. A variável de ambiente CLASSPATH (colocada através do sistema operativo ou do ambiente de desenvolvimento) contém um ou mais directórios que são usados como raízes para pesquisa de ficheiros .class. No ambiente de desenvolvimento Visual J++ a variável CLASSPATH é colocada seguindo os menus Tools, Options, Directories. Ex.: CLASSPATH=.;G:\JAVA\biblio Classes pertencentes ao: package utilitarios; devem ser colocadas no directório G:\JAVA\biblio\utilitarios Classes pertencentes ao: package curso.graficos.trab2; devem ser colocadas no directório G:\JAVA\biblio\curso\graficos\trab2 Para nomes de packages usam-se normalmente sempre letras minúsculas.

    DEI - ISEP Fernando Mouta

  • 40 Programação Orientada por Objectos

    Acesso a packages Ao escrever código fora do package que necessite de classes (tipos) declarados dentro de um package pode-se:

    • preceder cada nome de um tipo pelo do package, ou • importar parte ou todo o package.

    Ex.: utilitarios.Classe1 c1 = new utilitarios.Classe1(); ou: import utilitarios.Classe1; import utilitarios.*; Classe1 c1 = new Classe1(); Código de um package importa outro código do package que necessite implicitamente. A instrução import deve ser colocada depois de uma declaração de package, mas antes de outro código. Colisão entre nomes de classes Se duas bibliotecas são importadas e incluem classes com o mesmo nome, mas não é efectuado nenhum acesso a qualquer uma dessas classes com o mesmo nome, não há problema. Se é necessário aceder a algumas dessas classes temos de especificar o nome completo da classe prefixando o nome do package. Compilação automática A primeira vez que se cria um objecto ou se acede a um membro estático de uma classe importada (seja por ex. classe A), o compilador procura o ficheiro com o nome da classe e extensão .class (A.class) no directório apropriado. Se também encontra um ficheiro com o nome da classe e extensão .java (A.java) e se este ficheiro é mais recente que o .class, automaticamente o recompila para gerar um ficheiro .class actualizado. Acesso a classes e membros de classes Java possui especificadores de acesso para permitir ao criador de uma biblioteca de classes especificar o que está disponível para o programador cliente e o que não está. Deste modo o criador da biblioteca pode fazer modificações (melhoramentos) sem afectar o código do programador cliente. Normalmente os campos de dados e alguns métodos não se destinam a ser usados directamente pelo programador cliente.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 41 Os níveis de acesso são: • public • friendly ou package (sem palavra-chave) • protected • private Acesso a classes Alguns especificadores de acesso podem ser usados para determinar que classes de uma biblioteca estão disponíveis para um utilizador dessa biblioteca. Uma classe pode ter 2 tipos de acesso: público e package ou friendly. public: declaradas com a palavra-chave “public”, tornam a classe acessível ao

    programador cliente a partir de qualquer package. friendly: não se coloca nenhum especificador de acesso, a classe só é acessível de

    outra qualquer do mesmo package, mas não de fora do package. No entanto os membros estáticos públicos de uma classe friendly são acessíveis de outros packages.Este tipo de acesso usa-se para classes que apenas suportam tarefas realizadas por outras classes.. Deste modo o criador da classe não necessita de criar documentação e mantém a possibilidade de poder alterá-la porque nenhum programa cliente depende da implementação particular da classe.

    Acesso a membros de classes Os especificadores de acesso public, protected e private colocados à frente na definição de cada membro (campo ou método) de uma classe controlam o acesso a esses membros. Acesso public Um membro de uma classe declarado com o modificador de acesso public pode ser acedido por qualquer programa que tenha acesso à classe desse membro. Acesso friendly É o tipo de acesso por omissão, quando não se especifica nenhum modificador de acesso. Todas as classes no mesmo package têm acesso a membros friendly, mas classes de outros packages não têm acesso. Acesso private Membros declarados com o modificador de acesso private só podem ser acedidos por métodos dentro da própria classe.. Normalmente campos de dados e métodos que apenas auxiliam outros métodos são declarados private para assegurar que não são usados acidentalmente e assim podem ser modificados sem que afectem outras classes no mesmo package.

    DEI - ISEP Fernando Mouta

  • 42 Programação Orientada por Objectos

    Acesso protected Um membro declarado com o modificador de acesso protected pode ser acedido por todas as classes no mesmo package e por classes, mesmo que noutros packages, que herdem da classe a que o membro pertence. Uma classe que herde de outra classe (superclasse) herda todos os campos de dados e métodos dessa superclasse. Os membros privados e friendly da superclasse embora herdados não são acessíveis da subclasse, enquanto que os membros públicos são acessíveis não só da subclasse como também de todas as outras classes. O modificador de acesso protected é útil para permitir acesso às subclasses, sem permitir aceso a todas as outras classes não relacionadas. No entanto se uma classe B é uma subclasse de A pertencente a um package diferente, a classe B pode aceder a membros protected (campos de dados ou métodos) de A mas só em objectos do tipo B ou de subclasses de B. A classe B não pode aceder a membros protected de A em objectos do tipo A. Ex.: package primeiro; class A { protected int i; protected void f() {

    System.out.println(“Classe A – metodo protected”); } } package segundo; import primeiro.*; class B extends A { public static void main(String args [])

    throws java.io.IOException { A a = new A(); B b = new B(); b.i = 5; b.f(); // a.i = 5; ilegal // a,f(); ilegal

    System.in.read(); } } Encapsulamento O encapsulamento, característico da programação orientada por objectos, consiste: • na inclusão de dados e métodos dentro de classes, e • no controlo do acesso aos membros da classe.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 43 O controlo do acesso estabelece o que o programador cliente pode usar separando-o das estruturas e mecanismos internos, isto é, separa o interface da implementação. Inicialização de um objecto de uma subclasse Quando se cria um objecto de uma subclasse (classe derivada) ele contém dentro dele um sub-objecto da classe base. Este sub-objecto contém o mesmo conteúdo que teria se se criasse um objecto da classe base. Para que o sub-objecto da classe base seja inicializado correctamente Java automaticamente insere uma chamada a um construtor da classe base na sublasse (um construtor da classe base tem o conhecimento e privilégios apropriados para efectuar essa inicialização). Se o construtor da classe base não tem uma invocação explícita do construtor da superclasse, Java automaticamente chama o construtor por omissão (sem argumentos) da superclasse. Em qualquer dos casos o sub-objecto da classe base é inicializado (com a execução de um ou mais construtores da classe base) antes dos construtores da subclasse acederem ao objecto. Ex.: class A { B b1 = new B(1); A() { System.out.println("Construtor A()"); } } class B { B(int i) { System.out.println("Construtor B("+i+")"); } } class SubA extends A { B b2 = new B(2); SubA() { System.out.println("Construtor SubA()"); } } public class Sub1 { public static void main(String args[]) { SubA sa = new SubA(); } } Saída produzida pelo programa: Construtor B(1) Construtor A() Construtor B(2) Construtor SubA()

    DEI - ISEP Fernando Mouta

  • 44 Programação Orientada por Objectos

    Ex.: class A { A(int i) { System.out.println("Construtor A("+i+")"); } } class B { B(int i) { System.out.println("Construtor B("+i+")"); } } class SubA extends A { B b1, b2 = new B(2); SubA(int i) { super(i); b1 = new B(i); } } public class Sub2 { public static void main(String args[]) { SubA sa = new SubA(1); } } Saída produzida pelo programa: Construtor A(1) Construtor B(2) Construtor B(1) Ex: class Pao { Pao() { System.out.println("Pao()"); } } class Queijo { Queijo() { System.out.println("Queijo()"); } } class Alface { Alface() { System.out.println("Alface()"); } } class Refeicao { Refeicao() { System.out.println("Refeicao()"); } } class Almoco extends Refeicao { Almoco() { System.out.println("Almoco()"); } } class Sandwich extends Almoco { Pao p = new Pao(); Queijo q = new Queijo(); Alface a = new Alface(); Sandwich() { System.out.println("Sandwich()"); } public static void main(String args []) throws java.io.IOException { new Sandwich(); System.in.read();

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 45 } } Saída produzida pelo programa:

    Refeicao() Almoco() Pao() Queijo() Alface() Sandwich()

    Sumariando o processo de inicialização: Carregamento da classe: • Inicialização estática da classe base. • Inicialização estática da classe derivada. Se um objecto da subclasse é criado: • Todos os campos de dados (da classe base e da subclasse) são colocados nos valores

    por omissão (0’s binários); • Inicializações dos campos de dados da classe base; • Chamada do construtor da classe base (o construtor da classe base invocado

    explicitamente na subclasse ou o construtor por omissão); • Inicializações dos campos de dados da classe derivada; • Execução do resto do construtor da classe derivada; Ex.: class Flor { Flor(int i) { System.out.println("Flor(" + i + ")"); } void cor(int i) { System.out.println("cor(" + i + ")"); } } class Jarra { Flor f1 = new Flor(1); static Flor f2 = new Flor(2); Jarra() { System.out.println("Jarra()"); f3.cor(3); } static Flor f3 = new Flor(3); } public class InicializacaoEstatica { public static void main(String[] args) { System.out.println("No main"); new Jarra(); //(2) Jarra.f2.cor(2); //(2) } static Jarra j = new Jarra(); //(1) }

    DEI - ISEP Fernando Mouta

  • 46 Programação Orientada por Objectos

    Saída produzida pelo programa: | Flor(2) | Sem (1): | Sem | Flor(3) | | (1) | Flor(1) | | e (2) | Jarra() | Flor(2) | | cor(3) | Flor(3) | | No main | No main | | Flor(1) | Flor(1) | | Jarra() | Jarra() | | cor(3) | cor(3) | No | cor(2) | cor(2) | main A palavra-chave final A palavra-chave final significa que não pode ser alterado. Para campos de dados:

    static final tipo var - um único armazenamento que não pode ser mudado. final tipo-primitivo var – constante. final tipo-objecto var – esta referência não pode ser alterada para apontar para

    outro objecto, mas o objecto pode ser modificado. Em Java não é possível impor que um objecto seja uma constante.

    Todos os campos de dados declarados “final” ou são inicializados quando declarados ou então têm de ser inicializados antes de serem usados (blank finals). Para argumentos de métodos:dentro do método não podem ser mudados os valores dos

    argumentos. Ex.: int f(final int i) { return i+1; } Para métodos: uma classe que herde um método final não pode mudar o

    seu significado (não o pode reescrever). Métodos private numa classe são implicitamente final.

    Para classes: não permite criar subclasses desta classe.

    Fernando Mouta DEI - ISEP

  • Programação Orientada por Objectos 47

    Exemplos de classes em Java 1. Classe Leitura // Ficheiro Leitura.java import java.io.*; public class Leitura {

    public static String lerString(String msg) throws IOException { System.out.print(msg);

    BufferedReader d=new BufferedReader( new InputStreamReader(System.in));

    return d.readLine(); } public static int lerInteiro(String msg) throws IOException {

    int x=Integer.parseInt( lerString(msg) ); return x; }

    public static float lerFloat(String msg) throws IOException { float x=Float.v