Certificação SCJP – Orientação a Objetos
Dando continuidade ao post anterior (Certificação SCJP – Declarações e Controle de Acesso) depois de muito tempo, irei abordar aqui a segunda parte do estudo sobre a certificação – Orientação a Objetos. Todos os códigos apresentados aqui fazem parte deste post e são totalmente comentados – É importante testá-los.
Objetivos abordados:
1.2 (Já Abordado) Desenvolver código que declare interfaces. Que implemente ou estenda uma ou mais interfaces. Que declare uma classe abstrata e desenvolver código que estenda classes abstratas;
1.3 (Já Abordado) Desenvolver código que declare, inicialize, e use primitivos, arrays, enuns, e objetos como static, instancia e variáveis locais. Também, usar identificadores válidos para nomes de variáveis;
1.5 Dado um exemplo de código, determinar se um método está substituindo ou sobrecarregando outro método corretamente, e identificar valores de retorno legais (incluindo retorno covariante), para o método;
1.6 Dado uma lista de classes e superclasses, desenvolver construtores para uma ou mais classes. Dado uma declaração de classe, determinar se um construtor default será criado, e se sim, determinar o comportamento do mesmo. Dado uma lista de classes aninhadas e não aninhadas, escrever código para instanciar a classe;
5.1 Desenvolver código que implemente encapsulamento rígido, acoplamento, e alta coesão em classes, e descrever os benefícios;
5.2 Dado um cenário, desenvolver código que demonstre o uso de polimorfismo. Além disso, determinar quando “casting” será necessário e reconhecer erros de compilação vs. em tempo de execução relacionados com uso de “casting” para objetos de referência;
5.3 Explicar o efeito de modificadores em herança com relação a construtores, variáveis estáticas ou de instância, e métodos estáticos ou de instancia;
5.4 Dado um cenário, desenvolver código que declare e/ou invoque métodos sobrescritos ou sobrecarregados e código que declare e/ou invoque superclasses, substituídos ou sobrecarregados construtores;
5.5 Desenvolver código que implemente relacionamentos “é um” e/ou “tem um”.
Encapsulamento (Objetivo 5.1)
Utilizando esta técnica, você pode tornar seu código totalmente flexível, de fácil manutenção e extensível. Como fazer isso?
- Mantenha suas variáveis de instância protegidas utilizando o modificador de acesso private;
- Faça métodos de acesso public para forçar o uso destes métodos em vez de acessos diretos às variáveis;
- Use o padrão de desenvolvimento JavaBeans: set<Propriedade> e get<Propriedade>.
Herança (Objetivo 5.5)
A primeira coisa que você precisa saber sobre a herança é que toda classe Java herdará a class Object, logo toda classe sempre terá os métodos: equals, clone, notify, wait e outros. Para o exame você precisa saber que para criar relacionamentos de herança você terá que estender uma classe. Objetivos de usar herança?
- Promover reuso de código;
- Usar polimorfismo.
O objetivo mais comum em projetos é criar classes genéricas para permitir redução de código e a partir desta, e criar classes mais especializadas.
Criando classes genéricas evita perda de tempo ao fazer mudanças em seu código. Imagine que você tivesse 20 classes que implementam um mesmo método ou similar e depois você descobrisse que precisaria mudar alguma coisa. Você iria ter que mudar em suas 20 classes se não criasse uma classe genérica.
Outra coisa importante sobre herança é o conceito de polimorfismo. Imagine uma classe Evento que implementa o método palestras e minicursos, e agora imagine outras duas subclasses dessa classe Evento chamadas: SISB e SISOL com suas propriedades específicas. Essas duas subclasses podem ser tratadas como uma classe Evento, olhe este exemplo e você irá entender:
No exemplo anterior você pode perceber o Relacionamento é-um: Este conceito é baseado em herança de classes ou implementação de interfaces.
e um Relacionamento Tem-um: Este relacionamento quer dizer que uma classe possui em seu código uma referencia para outra classe. Por exemplo, uma classe SISB herda (É-um) uma classe Evento, porém SISB possui uma instância (Tem-um) de PublicoAlvo como mostra o exemplo 2 novamente.
Neste exemplo você pode perceber também que:
Evento é superclasse de SISB / SISOL e SISB / SISOL é subclasse de Evento
primeiroSISB é uma variável de referência de tipo SISB.
primeiroSISB como é um tipo SISB ele tem acesso às propriedades específicas de SISB como as propriedades da superclasse de SISB, Evento.
Se eu disser primeiroSISB instanceof Evento isto retornará true como ocorre no exemplo. Se você tentar colocar primeiroSISB instanceof SISOL irá ocorrer um erro de compilação.
Polimorfismo (Objetivo 5.2)
Todos objetos em Java são considerados polimórficos exceto objetos do tipo Object, no sentido de que esses objetos passam no teste é-um em relação ao seu tipo e ao tipo Object (Teste instanceof). Para certificação é importante saber que a única forma de acessar um objeto é através de uma variável de referência (Leia com atenção as regras abaixo):
- Uma variável de referencia pode ser apenas de um tipo e depois de declarada nunca poderá ser mudado o seu tipo;
- Uma referencia é uma variável podendo ser reatribuída uma vez que não esteja marcada como final. Após ser marcada como final apenas sua referencia pode ser modificada;
- O tipo de uma variável de referencia é que determina quais métodos podem ser invocados (Aqui surge o conceito de sobrescrição);
- Uma variável de referencia pode referenciar qualquer objeto do mesmo tipo que a referência declarada ou qualquer outro subtipo da referência.
Por exemplo: temos uma classe chamada Evento e outras duas classes estendendo Evento (SISB e SISOL). Podemos criar um Evento da seguinte forma – Evento primeiroSISB = new SISB(); ou então diretamente SISB primeiroSISB = new SISB(); ou também Object primeiroSISB = new SISB();
Para a certificação é importante saber que uma subclasse não pode possuir mais de uma classe “pai”. Caso haja a necessidade de que uma classe herde mais de uma classe o correto a se fazer é implementar interfaces.
No exemplo 3 podemos criar algumas declarações válidas:
SISB primeiroSISB = new SISB();
Object o1 = primeiroSISB;
Evento e1 = primeiroSISB;
Maratona m1 = primeiroSISB;
Como podemos perceber existe apenas um objeto aqui, que é a instância do tipo SISB – primeiroSISB – e podemos encontrar 4 tipos de variáveis de referência referenciando apenas um objeto em memória.
Aí surge uma pergunta interessante: Quais métodos primeiroSISB, o1, e1 e m1 podem invocar? primeiroSISB – todos os métodos acessíveis da classe SISB, Object, Evento e Maratona; o1 – apenas os métodos da classe Object; e1 – apenas os métodos da classe Evento e Object; m1 – apenas os métodos da referente a classe Maratona e a Object;
É importante saber que apesar de você acessar os métodos de outra classe esses métodos irão retornar baseando-se no objeto instanciado. Se você sobrescrever o método propriedadeEvento() no primeiroSISB quando você chamar este método (e1.propriedadeEvento()) o mesmo irá retornar o evento sobrescrito;
Sobreposição/Sobrecarga (Objetivos 1.5 e 5.4)
Sobrepor – Override
significa definir um comportamento especifico a uma determinada subclasse, considerando que a superclasse tenha um comportamento pré-definido.
Quando a classe é abstrata, automaticamente você será obrigado a sobrepor os métodos abstratos em uma classe concreta (não abstrata).
Quando uma classe é estendida e seus métodos são marcados como public você está habilitado em modificar o acesso a seus métodos na subclasse. No exame é importante ficar atento às regras de polimorfismo. Abaixo segue algumas regras para sobrescrever um método:
- Os argumentos devem ser os mesmos do método sobrescrito, caso contrário você estará sobrecarregando-o;
- O retorno deve ser o mesmo ou de um subtipo do tipo de retorno declarado no método original;
- O nível de acesso não deve ser mais restritivo do que o do método sobrescrito, pode ser menos restritivo;
- Métodos de instância podem ser sobrescritos desde que a classe que o contenha seja herdada, não podendo sobrescrever métodos com modificadores private ou final;
- Classes em outro pacote só pode sobrescrever métodos que são marcados com public ou protected e não-estático. O compilador irá permitir, porém avisará que o método não está sendo sobrescrito;
- O método sobrescrito pode lançar qualquer exceção não-verificada, mesmo que o método original não tenha declarado a exceção;
- O método não deve lançar exceções verificadas novas ou mais abrangentes do que as já declaradas pelo método sobrescrito;
- Não é possível sobrescrever método marcado como final e/ou static;
- Se um método não for herdado você não poderá sobrescreve-lo;
Você pode invocar o método original da superclasse na subclasse utilizando a palavra-chave super como no exemplo.
OBS.: Se você tiver uma classe A que possui um método getA() que lança a exceção Exception e uma classe B que estenda a classe A e sobrescreva esse método removendo o lançamento da Exceção, um problema pode ocorrer quando utiliza-se polimorfismo. Veja o exemplo:
Sobrecarregar – Overload
significa reusar o mesmo nome de um método declarado na classe, porém com diferentes argumentos (Obrigatório) e com retorno diferente (Opcional). Para sobrecarregar métodos:
- você DEVE mudar os argumentos;
- você PODE mudar o tipo de retorno;
- você PODE mudar o modificador de acesso;
- você PODE declarar novas ou mais exceções verificadas (O contrário de sobreposição);
- você PODE sobrecarrega-lo na mesma classe ou em uma subclasse.
A escolha de qual método sobrecarregado usar não é feita em tempo de execução (tempo de compilação) causando alguns efeitos quando falamos de polimorfismo. Por exemplo: imagine que tenhamos duas classes (Evento e SISB) sendo que SISB extends Evento e uma classe C com dois métodos sobrecarregados getVersion(Evento evento) e getVersion(SISB sisb). Seguindo o exemplo abaixo você irá perceber que o método escolhido se baseará no tipo de Referência e não no tipo de Objeto. O tipo de Objeto você define quando coloca new … e o tipo de referência quando você coloca Evento e. Observe o exemplo:
É importante saber que se o método for sobreposto a escolha de qual método usar será feita diferentemente de métodos sobrecarregados, será feita em tempo de execução, ou seja, se baseará no tipo do Objeto e não no tipo de Referência.
“Casting” a variáveis de referência (Objetivo 5.2)
Casting significa converter um objeto a um tipo definido. Nada melhor do que um exemplo para entendermos como funciona. Vamos supor que eu tenha uma classe Evento e uma classe SISB que estenda a classe Evento (SISB é-um evento). Evento tem o método getEvento() e a classe SISB tem os métodos getSISB e getEvento (Sobreposto) – Veja o Exemplo 8. Se você criar:
Evento e1 = new Evento(); // TERÁ APENAS ACESSO AOS MÉTODOS DE EVENTO
SISB primeiroSISB = new SISB(); // TERÁ ACESSO AOS MÉTODOS DE EVENTO E SISB
Evento eventoSISB = new SISB(); // TERÁ APENAS ACESSO AOS MÉTODOS DE EVENTO
Agora suponha que eu queira acessar o método getSISB do Objeto eventoSISB, como eu faria isso? Utilizando casting da seguinte forma:
SISB sisb = (SISB)eventoSISB;
Após isso nós teremos acesso ao método getSISB!
Nós tempos dois tipos de CAST: explicito e implicito
SISB primeiroSISB = new SISB();
Explícito: Evento e1 = (Evento)primeiroSISB;
Implícito: Evento e2 = primeiroSISB;
Implementando uma Interface (Objetivo 1.2)
Como já foi no post anterior, interfaces é um tipo de classe totalmente abstrata. Logo, se você implementa uma interface isto quer dizer que você está aceitando um contrato, ou seja, você será obrigado a implementar todos os métodos que nela existir. Segue algumas regras válidas para implementações:
- Implementar todos os métodos declarados na interface;
- Seguir todas as regras para sobreposições (overrides) validas
- Declarar exceções não verificadas na implementação dos métodos diferentes das declarada pelo método na interface;
- Manter o nome do método declarado na interface como também o tipo de retorno (podendo ser um subtipo – Polimorfismo), não precisando declarar a exceção já declarada na interface pelo método.
É importante salientar que a implementação desses métodos só podem ser feitas em classes concretas ou não abstratas. Por exemplo: Temas uma interface Maratona (com os métodos m1 e m2) e uma classe abstrata Evento. Evento implementa Maratona, isso significa que Evento não será obrigada a implementar os métodos na interface Maratona.
Uma classe pode implementar mais de uma interface (classes não podem estender mais de uma classe). E referente a interface, não pode implementar nada e somente estender interfaces (podendo estender mais de uma interface).
Tipos de retornos válidos (Objetivo 1.5)
Para a certificação dois aspectos são necessários observar: o que podemos declarar como um tipo de retorno e o que podemos retornar como um valor. Para o primeiro aspecto é simples, porém o que complica mesmo é quando falamos herança - se você está sobrepondo um método herdado ou criando um novo (inclui-se aqui métodos sobrecarregados).
Tipo de retorno para Métodos Sobrecarregados:
Quando falamos de sobrecarregamento (já explicado antes), queremos dizer que mesmo que este método seja herdado você está apenas aproveitando o nome dele podendo assim ser considerado praticamente como um novo método. Com isso podemos escolher qualquer tipo de retorno que quisermos mudando apenas a lista de argumentos.
Tipo de retorno para Métodos Sobrepostos:
Quando falamos de sobreposição (também já explicado antes), queremos dizer que queremos mudar a implementação de um método herdado (override), porém nesse caso o método modificado deve ser declarada exatamente igual ao método da superclasse. Como mudar então o tipo de retorno? provavelmente você deve lembrar de polimorfismo! O Java 5, permitiu que você pudesse mudar o tipo de retorno de um método sobreposto para um subtipo deste tipo.
Se você tentar compilar este código no Java 1.4 ocorrerá o seguinte erro: attempting to use incompatible return type.
Retornando um valor
- Você pode retornar null em um método com um tipo de retorno de referência a objeto
- Arrays são tipos de retornos legais
- Método com tipo de retorno primitivo pode retornar qualquer valor que pode ser implicitamente convertido ao tipo de retorno declarado
- Método com tipo de retorno primitivo pode retornar qualquer valor que pode ser explicitamente convertido ao tipo de retorno declarado
- Você não pode retornar nada em um método com tipo de retorno void
- Um método com tipo de retorno uma referência a um objeto você pode retornar qualquer tipo de objeto que pode ser implicitamente convertido (SUBTIPO)
public Button getButton(){
return null;
}
public String[] getNomes(){
return new String[] {"Flávio", "Lara", "Amanda"};
}
public int getChars(){
char c = 'c';
return c; // Compatível com int
}
public int getValue(){
float c = 50.5f;
return (int)c; // Casting
}
public void values(){
return "Teste"; // Inválido
}
public Animal getAnimal(){
return new Gato();
}
Construtores e instanciações (Objetivos 1.6, 5.3 e 5.4)
Quando você cria um objeto isto é feito por intermédio do construtor. Os construtores são iniciados quando você utiliza em seu código a palavra-chave new. Todas as classes, incluindo as abstratas, devem ter um construtor, porém isso não significa que você precisa declara-lo. Construtores não possuem tipo de retorno e obrigatoriamente devem ter o nome exatamente igual o da classe.
public class Evento{
// Construtor
Evento(){
}
}
Geralmente os construtores são utilizados para inicializar variáveis de instância, como abaixo:
public class SISB{
Date data;
String tema;
Evento(Date data, String tema){
this.data = data;
this.tema = tema;
}
}
A palavra-chave this é utilizada para referenciar o objeto implícito na chamada a um método de instância, ou seja, utilizada para referenciar (representar) um objeto em execução.
Vamos supor que exista uma classe SISB e uma classe Evento, e SISB estende Evento. O que acontece quando eu declaro isto em meu código?
SISB primeiroSISB = new SISB();
- O Construtor da classe SISB é invocado. Todo construtor invoca o construtor da sua superclasse através de uma chamada implícita do método super() exceto um construtor invocando um construtor sobrecarregado da mesma classe.
- O Construtor da classe Evento é invocado.
- O Construtor da classe Object é invocado.
- Todas as variáveis de instancia da classe Object são inicializadas, ou seja, são dados os seus respectivos valores. Como por exemplo, se você declarar int x = 25; assim que o construtor é chamado essa variável é inicializada com o valor 25, caso o construtor não seja chamado esta variável será nula.
- Construtor da classe Object está completo!
- Todas as variáveis da classe Evento são inicializadas (se tiver).
- Construtor da classe Evento está completo!
- Todas as viriáveis da classe SISB são inicializadas.
- Construtor da classe SISB está completo!
Regras para construtores:
- Construtores podem usar qualquer modificador de acesso, incluindo private. Construtores com modificador de acesso privado significa que está classe não poderá ser instanciada diretamente. Você pode criar um método que retorne esta classe instanciada;
- O nome do construtor deve ser exatamente igual ao nome da classe;
- Construtores não possuem tipos de retorno;
- Você pode possuir um método com o mesmo nome da classe porém esteja claro que isto não será um construtor;
- Se você não declarar um construtor para sua classe, automaticamente o compilador gerará um construtor default;
- O construtor default não possui argumentos;
- Fique atento ao item 5, pois se você declarar um construtor com uma lista de argumentos e não declarar o construtor default o compilador não criará o construtor default, pois ele irá supor que você não precisará deste;
- Todo construtor terá uma chamada para um construtor sobrecarregado (this()) ou uma chamada para o construtor de sua superclasse (super());
- Se você digitar o construtor (caso não confia no compilador), e não digitar uma chamada para super() ou para this(), o compilador irá automaticamente inserir uma chamada sem argumentos para super();
- Uma chamada para super() pode ser feita através de uma chamada sem argumentos como também como argumentos;
- Um construtor sem argumentos não será necessariamente um construtor default, pois você pode modifica-lo;
- Você não pode acessar um método ou uma variável de instância sem que o construtor tenha iniciado;
- Somente métodos e variáveis estáticas podem ser acessados a parte da chamada do super() ou do this();
- Classes abstratas tem construtores, e o construtor dela sempre é chamado quando uma subclasse concreta é instanciada;
- Interfaces não possuem construtores;
- Um construtor só pode ser invocando dentro de outro construtor. Você não pode fazer como no exemplo:
public class SISB{
void getTipo(){
SISB(); // ESTÁ CHAMADA É ILEGAL
}
}
Quando você declarar um construtor é importante colocar antes de qualquer linha de código uma chamada para super() ou para this().
Statics (Objetivo 1.3)
Para entendermos como usar o modificador Static para métodos ou variáveis precisamos entender porque usar! Quando falamos de métodos ou variáveis estáticas queremos dizer que possuem um comportamento que nunca se modificará. Podemos citar como exemplo o caso da classe Math quem possui um método estático que gera numeros randomicos: Math.radom(); Este comportamento nunca se modificará e para utilizar esta funcionalidade não precisamos criar uma instância dessa classe basta apenas chama-la. Outro exemplo clássico é da classe JOptionPane que possui o método JOptionPane.showMessageDialog(…); como muitos outros métodos. Para chamar estes métodos e variáveis não precisamos instanciar estas classes. Olhe o Exemplo:
JOptionPane.showMessageDialog( frame, // nome do component "Tem certeza?", // mensagem "Confirmação", // titulo JOptionPane.YES_NO_OPTION, // VARIÁVEL STATIC DA CLASSE JOPTIONPANE JOptionPane.QUESTION_MESSAGE, // VARIÁVEL STATIC DA CLASSE JOPTIONPANE null, null, null );
Observe o exemplo abaixo
Como nesse exemplo, você pode perceber que a variável contador é criada antes que se crie uma instância da classe Exemplo11.
Se você remover a declaração static da variável irá ocorrer um erro de compilação no método main(). Isto ocorre porque o método é estático e não pode acessar uma variável não estática fora do seu contexto. Para acessar a variável você precisaria criar uma instancia de Exemplo11 (Exemplo11 exemplo = new Exemplo11()) e pegar o valor dessa variável (exemplo.contador).
A regra para métodos estáticos é que so podem acessar métodos e variáveis estáticas. E variáveis estáticas pode ser acessadas da seguinte forma: Exemplo11.contador (não precisa criar uma instância – new Exemplo11()).





Muito bom! Muitas informações =)
Obrigado! =D
[...] de muito tempo sem postar nada irei hoje dar continuidade ao post anterior (Certificação SCJP – Orientação a Objetos), irei abordar aqui a terceira parte do estudo sobre a certificação – Atribuições – [...]