Baixe o app para aproveitar ainda mais
Prévia do material em texto
Aula 04: Princípios da Orientação à Objetos Objetivos Recordando... Na aula passada criamos uma classe Carro, na qual havia alguns atributos e métodos. Na classe que usamos para instanciar e testar um objeto da classe Carro, que chamamos de classe Principal, iniciamos diretamente os atributos do objeto meuCarro, logo após instanciá-lo. Entretanto, esta NÃO é a forma mais adequada de fazer isso. De fato, ao projetarmos uma classe, devemos ter em mente que o conceito de encapsulamento é muito importante na POO. Isto significa que os métodos da classe devem, geralmente, serem os únicos responsáveis pelo acesso aos atributos da classe. Isso ―esconde‖ o modo de funcionamento da classe, privilegiando o uso da interface da classe como forma única de acesso, o que facilita o teste, a manutenção e a reusabilidade das mesmas. Uma forma mais adequada Para evitarmos iniciar os atributos da classe fora da mesma, devemos criar métodos na própria classe que façam esse acesso, tanto de escrita quando de leitura, sempre que necessário. Podemos alterar a classe Carro, introduzindo um método configurarCarro, que seja responsável pela inicialização dos atributos. Entretanto, como passar os valores que desejamos para os atributos do carro? A resposta é: através de argumentos no método. Ao enviarmos uma mensagem para um objeto da classe Carro através do método configurarCarro, devemos especificar os valores desejados para o carro (que chamamos de parâmetros correspondentes aos argumentos). Ou seja, o método configurarCarro deve estar apto a receber esses parâmetros como argumentos e atribuí-los aos atributos do carro (por vezes os termos parâmetros e argumentos são usados indiferentemente, mas argumentos são tipos formais e parâmetros são valores reais). O código a seguir prepara o método configurarCarro para receber os argumentos desejados para a classe Carro: Reforçar o conceito de encapsulamento Apresentar métodos parametrizados Introduzir os níveis de acessibilidade de atributos e métodos Esclarecer o uso dos construtores void configurarCarro(String m, String c, boolean e) { marca = m; cor = c; motorEstado = e; } Na classe Principal, para realizar a chamada do método, agora devemos passar os parâmetros adequados aos argumentos, da seguinte forma: meuCarro.configurarCarro("Palio","azul",fals e); Na parte de Desafios da aula 1 foram criados o atributo rotacaoMotor e os métodos acelerar e desacelerar. Estes métodos também podem ser reescritos para receber como parâmetros os valores a serem somados ou subtraídos do atributo rotacaoMotor. A inclusão do atributo fica assim: int rotacaoMotor; O código dos métodos acelerar e desacelerar fica assim: void acelerar(int parcela) { rotacaoMotor += parcela; System.out.println("O motor foi acelerado para " + rotacaoMotor + " RPM."); } void desacelerar(int parcela) { rotacaoMotor -= parcela; System.out.println("O motor foi desacelerado para " + rotacaoMotor + " RPM."); } E as chamadas dos métodos na classe principal ficam assim: System.out.println("---------"); System.out.println("Acelerando o motor..."); meuCarro.acelerar(80); System.out.println("---------"); System.out.println("Desacelerando o motor..."); meuCarro.desacelerar(120); Edite, compile e teste o funcionamento da alteração do código. Definindo a acessibilidade De fato, a correção feita no código da classe Carro foi positiva, mas não impede o uso indevido dos atributos por uma classe externa. Para tanto, devemos utilizar o recurso de definir a acessibilidade do elemento (classe, atributo ou método). Fazemos isso, em Java, usando as palavras reservadas public, private, package e protected na declaração. Os atributos e métodos public constituem a interface da classe e são acessíveis a qualquer método de qualquer classe externa. Os atributos e métodos private são de acesso exclusivo dos métodos da classe. Os de acessibilidade protected são passíveis de acesso por métodos de classes que herdem da classe que os definiu (trabalharemos esse conceito mais adiante). O nível package, que é assumido como padrão quando nenhum nível de acessibilidade é explicitamente definido (o que é o caso do nosso exemplo da aula passada), define o atributo ou método como acessível para qualquer método de classe dentro do mesmo pacote (este conceito também será trabalhado mais adiante). No nosso caso, desejamos restringir o acesso aos atributos da classe Carro. Para tanto, vamos defini-los como private. Altere assim o código: private String marca; private String cor; private boolean motorEstado; private int rotacaoMotor; Agora tente acessar os atributos como feito na aula passada...Pois é, a linguagem impede o acesso dessa forma, pois ela passou a ser errada. Mais sobre parametrização – set e get Além de passarmos parâmetros para os métodos de uma classe durante uma mensagem, podemos também receber dados dos métodos chamados. Uma forma de fazê-lo é definir um valor de retorno para um método chamado. Os campos private de uma classe só podem ser manipulados por métodos dessa classe. Qualquer objeto de outra classe (um cliente, portanto), que deseje acessar um campo private da classe servidora, deve chamar os métodos public que a esta ofereça para manipular seus campos private. As classes costumam oferecer métodos public para permitir aos clientes da classe configurar (set - atribuir) ou obter (get - obter) valores das variáveis private. Os nomes desses métodos não precisam começar com set ou get, mas essa convenção de atribuição de nomes é altamente recomendada em Java. Por exemplo, podemos criar um método getRotacaoMotor que retorne o valor do atributo rotacaoMotor. Assim: int getRotacaoMotor () { return rotacaoMotor; } Para chamarmos o método, poderíamos usar este código: System.out.println("Rotacao do motor = " + meuCarro. getRotacaoMotor () + " RPM."); Da mesma forma, caso quiséssemos criar um método setRotacaoMotor que configurasse o valor do atributo rotacaoMotor, ele seria assim: void setRotacaoMotor (int r) { rotacaoMotor = r; } e a atribuição de uma valor à rotação teria, portanto, a seguinte sintaxe: meuCarro.setRotacaoMotor (1000) Criando objetos com o uso de construtores A criação do método configurarCarro foi uma boa idéia para melhorar o encapsulamento da classe. Entretanto, ainda não é a melhor forma de iniciar os valores dos atributos. Melhor seria utilizarmos um método especial, que chamamos Construtor. O construtor de uma classe é um método automaticamente chamado quando instanciamos um objeto dessa classe. Em Java, o método construtor tem o mesmo nome da classe e é chamado quando usamos o comando new. Reparando bem, no exemplo da classe carro temos: new Carro(); o que sugere que estamos chamando um método chamado Carro (o uso dos parêntesis é um indicativo disso). Entretanto onde está este método que não foi declarado? A resposta é que a linguagem pressupõe que toda classe tem um construtor padrão (default), que não possui argumentos e inicia os atributos numéricos com zero e os atributos lógicos com false. Entretanto, podemos definir explicitamente o construtor da classe, fazendo com que ele seja responsável pela iniciação de atributos da classe com valores distintos dos valores padrão. Assim, podemos ter: public Carro () { marca = "Palio"; cor = "azul"; motorEstado = true; rotacaoMotor = 1000; }Bem, o construtor é um método como os demais e, portanto, também pode receber argumentos. Assim, poderíamos melhorar o nosso construtor permitindo que recebesse como parâmetros os valores a serem postos inicialmente em alguns atributos. Um código possível seria: public Carro (String m, String c) { marca = m; cor = c; motorEstado = true; rotacaoMotor = 1000; } Uma classe pode possuir mais de um construtor. Eles devem ter o mesmo nome e diferenciar-se apenas pela quantidade e tipo de argumentos que recebem. Isto é chamado de sobrecarga de construtor (veremos o assunto sobrecarga de métodos, em detalhes, em uma aula mais à frente). Desta forma, podemos ter diferentes construtores para a mesma classe, permitindo flexibilizar a criação e a iniciação dos atributos dos objetos em diferentes situações que se façam necessárias. Por exemplo, além dos construtores anteriormente citados, poderíamos ter ainda: public Carro (String m, String c, float r) { marca = m; cor = c; motorEstado = true; rotacaoMotor = r; } Pacotes No desenvolvimento de pequenas atividades ou aplicações, é viável manter o código da aplicação e suas classes associadas em um mesmo diretório de trabalho — em geral, o diretório corrente. No entanto, para grandes aplicações é preciso organizar as classes de maneira a evitar problemas com nomes duplicados de classes e permitir a localização do código da classe de forma eficiente. Em Java, a solução para esse problema está na organização de classes e interfaces em pacotes. Um pacote é uma unidade de organização de código que congrega classes, interfaces e exceções relacionadas. O código-base de Java está todo estruturado em pacotes e as aplicações desenvolvidas em Java também devem ser assim organizadas. Essencialmente, uma classe Xyz que pertence a um pacote nome.do.pacote tem um ―nome completo‖ que é nome.do.pacote.Xyz. Assim, se outra aplicação tiver uma classe de mesmo nome não haverá conflitos de resolução, pois classes em pacotes diferentes têm nomes completos distintos. A organização das classes em pacotes também serve como indicação para o compilador Java para encontrar o arquivo que contém o código da classe. O ambiente Java normalmente utiliza a especificação de uma variável de ambiente CLASSPATH, a qual define uma lista de diretórios que contém os arquivos de classes Java. No entanto, para não ter listas demasiadamente longas, os nomes dos pacotes definem subdiretórios de busca a partir dos diretórios em CLASSPATH. No mesmo exemplo, ao encontrar no código uma referência para a classe Xyz, o compilador deverá procurar o arquivo com o nome Xyz.class; como essa classe faz parte do pacote nome.do.pacote, ele irá procurar em algum subdiretório nome/do/pacote. Se o arquivo Xyz.class estiver no diretório /home/java/nome/do/pacote, então o diretório /home/java deve estar incluído no caminho de busca de classes definido por CLASSPATH. Para indicar que as definições de um arquivo fonte Java fazem parte de um determinado pacote, a primeira linha de código deve ser a declaração de pacote: package nome.do.pacote; Caso tal declaração não esteja presente, as classes farão parte do ―pacote default‖, que está mapeado para o diretório corrente. Para referenciar uma classe de um pacote no código fonte, é possível sempre usar o ―nome completo‖ da classe; no entanto, é possível também usar a declaração import. Por exemplo, se no início do código estiver presente a declaração: import nome.do.pacote.Xyz; Então a classe Xyz pode ser referenciada sem o prefixo nome.do.pacote no restante do código. Alternativamente, a declaração import nome.do.pacote.*; indica que quaisquer classes do pacote especificado podem ser referenciadas apenas pelo nome no restante do código fonte. A única exceção para essa regra refere-se às classes do pacote java.lang — essas classes são consideradas essenciais para a interpretação de qualquer programa Java e, por este motivo, o correspondente import é implícito na definição de qualquer classe Java. Veja no exemplo uma maneira de receber dados a partir do teclado: import java.util.Scanner; public class SomaInteiros { public static void main(String[] args) { // cria Scanner para obter entrada a partir do teclado Scanner objEntrada = new Scanner(System.in); int num1; int num2; int soma; System.out.print("Entre o primeiro inteiro: "); num1 = objEntrada.nextInt(); System.out.print("Entre o segundo inteiro: "); num2 = objEntrada.nextInt(); soma = num1 + num2; System.out.printf("A somoa é %d\n",soma); } } COMENTARIOS: A declaração import java.util.Scanner; ajuda o compilador a localizar uma classe utilizada nesse programa. As bibliotecas de classes Java ou Java Application Programming Interface (API do Java), necessitam que se utilize a diretiva import para identificar as classes pré-definidas, utilizadas num programa em Java. Neste exemplo foi utilizada a classe Scanner pré-definida no pacote Java.util. Um Scanner permite a um programa ler dados, que podem ser provenientes de várias origens, como de um arquivo no disco ou digitado pelo usuário a partir do teclado. No exemplo em questão, a expressão Scanner objEntrada = new Scanner(System.in); cria um objeto Scanner que lê dados do teclado, pois o objeto de entrada padrão que é o teclado é o System.in. Assim, após de obtido um objeto da classe Scanner, pode-se utilizar os métodos disponíveis desta classe, neste exemplo a expressão num1 = objEntrada.nextInt(); chama o método que inicializa a variável inteira num1 com um valor inteiro digitado antes de se pressionar a tecla enter do teclado. o método nextInt() espera receber sempre um valor inteiro caso contrário será gerada uma situação de exceção (erro) e o programa terminará. Note que neste exemplo quando são escritas as mensagens na tela, através do objeto padrão de saída, System.out, são usados dois métodos distintos o print() e o printf(). Definindo Variáveis Para manipular dados, eles devem estar associados a variáveis, as quais devem ser definidas dentro de tipos( int, double, , etc.) . Na programação orientada a objetos, existem quatro tipos de variáveis, no que se refere à maneira como são armazenadas pelo interpretador Java: De instâncias (atributos de objetos); De classe; Locais (variáveis e parâmetros de métodos); Constantes. Para manipular esses tipos de variáveis, é necessário seguir os procedimentos de encapsulamento, utilizando o tipo adequado de método dentre os três disponíveis: De instância (métodos do objeto); De classe (métodos da classe); Construtores (método especial). Para definir uma variável, devemos escolher seu tipo ( de acordo com sua abrangência de tamanho e valor), um modificador(quando necessário), um nome de fácil entendimento e um valor inicial ( que pode ser do tipo primitivo ou referência). A sintaxe para definição de variável é: [modoficador] tipo identificador = [valor inicial]; Exemplos: public int idade=18; private double salário=1456.3; byte base =2; Entendendo a diferença entre “de classe’ e “de instância” Uma variável será considerada de instância quando estiver definida no corpo da classe ( ou seja, fora dos métodos) e não possuir o modificador static. Uma variável definida como parâmetro de método, ou dentro de um método, é chamada local. Uma variável definida fora dos métodos, e com modificador static, é considerada como atributo de classe. Uma variável definida fora dos métodos, e com o modificador final, é considerada constante. Métodos que não apresentam modificador staticsão considerados como de instância. Seu efeito se restringe ao objeto chamado pela sua referência. Métodos com modificador static são considerados como de classe. Seu efeito se estende a todos os objetos oriundos da classe em questão. Atributos static Até o momento só havíamos aprendido como definir atributos de instância. Cada objeto tinha seus próprios atributos e uma modificação nos atributos de um objeto não afetava os atributos de outros objetos. Neste tópico iremos apreender como definir atributos de classe. Esses atributos são os mesmos para todos os objetos de uma classe. Eles são, portanto, compartilhados pelos objetos. Uma mudança em um destes atributos é visível por todos os objetos instanciados dessa classe. Atributos de classe também são chamados de atributos static. Para exemplificar, definiremos uma classe Robot que usa um atributo static como se fosse um contador, para saber quantos objetos robots foram criados (instanciados). //Classe Robot class Robot { public int x, y; // posição do robot public static int contador; //contador de instancias public Robot(int x,int y){ this.x = x; this.y = y; contador++; } } A seguir a classe de teste Principal: class Principal { public static void main(String args[]) { Robot.contador=0; //inicializando variavel static Robot r1,r2; System.out.println(Robot.contador); r1 = new Robot(10,12); System.out.println(Robot.contador); r2 = new Robot(11,13); System.out.println(Robot.contador); } //main method } //class Principal Apesar de termos exemplificado com um inteiro, você poderia ter usado uma classe no lugar desse atributo, naturalmente tomando o cuidado de chamar new antes de usá-lo. Métodos static Métodos static também são chamados de métodos de classes. Estes métodos só podem operar sobre atributos que também sejam static. Assim: public static void metodoA() {... Exemplificando, vamos criar um método static para incrementar o contador: public static void incrementa(){ contador++; } Atributos de instância Para armazenar dados dentro de um objeto de modo que, a partir de então, possamos acessá-los por meio da referência desse objeto, devemos definir os dados em questão como variáveis de instância, fazendo deles atributos de instância. Assim, torna-se possível alterar ou consultar o valor desses dados por meio da referência do objeto. Um atributo de instância é visível a todos os métodos dessa instância. À instância de um objeto corresponde uma área de memória, criada para armazenar a estrutura desse objeto. Essa área pode conter variáveis de tipo primitivo e de tipo de referência. Analisemos o exemplo: public class Conta{ private int numero; private double saldo; private double juros; private java.util.Date vencimento; } No código anterior é possível notar que a classe possui quatro atributos de instância – três do tipo primitivo(numero, saldo e juros) e um do tipo referência Java.util.Date(vencimento). A partir da classe Conta, é possível criar vários objetos com conteúdos diferentes e que podem ser manipulados por meio de referências. Veja o código a seguir: public class TesteConta { public static void main(String[] args){ Conta conta1=new Conta(); System.out.println(“Ref. conta1: ” + conta1); Conta conta2=new Conta(); System.out.println(“Ref. conta2: ” + conta2); } No código anterior temos duas instâncias de objetos criadas a partir da classe Conta, chamadas conta1 e conta2. Veja que elas têm valores diferentes, ou seja, são objetos diferentes, podendo conter dados diferentes. Constantes Para declarar uma constante, use a palavra chave final antes da declaração da variável e inclua um valor inicial para esta variável. Exemplo: final float pi=4.141592; final boolean debug=false; final int maxsize = 40000; Desafios 1. Crie métodos set e get para todos os atributos private da classe Carro. 2. Crie um novo construtor que receba também o estado em que o motor deve ser iniciado e inicie a rotação de acordo com o estado passado como argumento: 0 (para motor desligado) ou 1000 (para motor ligado). 3. Para a classe Quadrado, criada no desafio da aula passada, crie também métodos get e set para o atributo lado. Não permita que o cliente da classe passe um valor negativo para o atributo lado. 4. Crie uma classe Livro, que possua atributos para nome, autor, edição, editora e ano de publicação. Os atributos devem ser privados e devem possuir métodos de acesso. Deve haver um método que imprima os atributos correntes do livro. Deve haver um construtor padrão que inicie os atributos com o valor ‗desconhecido‘ e a seguir imprima uma mensagem de saudação com os atributos correntes. Deve haver construtores alternativos para instanciar os objetos da classe Livro com os respectivos parâmetros correspondentes aos atributos. Adicionalmente, crie um programa principal que instancie diferentes livros chamando os diferentes construtores e os métodos get e set dos atributos que se fizerem necessários. 5. Crie uma classe ContaPoupanca que possui os atributos taxaJurosAnual (que guarda a taxa de juros dessa conta) e saldo (que guarda quanto o poupador tem em depósito). O construtor da classe deve receber e iniciar os valores dos atributos. Forneça o método calculaRemuneracaoMensal que calcula a remuneração mensal da conta multiplicando o saldo pela taxa de juros anual e dividindo o resultado por 12 e adiciona esse montante ao saldo da conta. Forneça também os métodos para modificar a taxa de juros anual e o saldo da conta com novos valores. Escreva um programa para testar a sua classe ContaPoupanca. Instancie dois objetos conta1 e conta2 com saldos $2.000 e $3.000 respectivamente. Configure inicialmente taxaJurosAnual como 4% e então calcule a remuneração mensal e imprima os novos saldos para os novos poupadores. Em seguida configure taxaJurosAnual para 5%, calcule a remuneração do próximo mês e imprima os novos saldos para os dois poupadores.
Compartilhar