Senhoras
e Senhores, nesse material entraremos no mundo misterioso do
polimorfismo! Claro, falaremos também de outros assuntos tão quanto
importantes: String e Constructor. Vamos lá.

A classe String
Já
avançamos bastante nos conceitos básicos da orientação a objetos no
contexto do Java e da UML. Eu tentei ao longo desse tempo esbarrar pouco
em detalhes de sintaxe ou comandos da API da linguagem Java justamente
para tentarmos fixar na quebra do paradigma da programação estruturada
(para os que a conhecem) em relação à programação orientada a objetos.
Todavia, faltam poucos passos para terminarmos esse material
introdutório e já podemos nos dar o luxo de aproximar mais das
ferramentas e componentes úteis no dia a dia de um programador. Veremos
agora um pouco do que a classe String do Java tem para nos oferecer.
Antes de iniciarmos a apresentação de alguns detalhes dessa classe, você precisa ter em mente que a API
(sigla de Application Programming Interface ou Interface de Programação
de Aplicativos) do Java é imensa. Portanto lembre-se sempre dos três
ensinamentos básicos do programador Java:
1- Não decore a API! Ela
está disponível na Internet. Tudo bem, tudo bem!! Essa regra tem
exceção se você for fazer concurso ou tirar uma certificação.
2- Não reinvente a roda! Se
você precisar resolver algum algoritmo corriqueiro, busque-o na rede! A
API provavelmente já oferece esse recurso para você.
3- Não tenha medo da API! Ela é simples de acessar, usar e entender (apesar de estar em inglês).
Hoje, na versão Java SE 7, o link de acesso da API é:
Veja abaixo um print screen com uma breve explicação.

A
seta verde aponta para o frame que contém todos os pacotes do Java. Sei
que ainda não falamos sobre pacotes, mas de forma breve imagine que são
pastas onde as classes ficam guardadas (é isso mesmo, na prática). Caso
você clique em um desses pacotes, os frames da seta vermelha e azul
passarão a apresentar componentes presentes nesse pacote.
A
seta vermelha mostra todas as classes acordando com o filtro da seta
verde. Na apresentação default, todas as classes são exibidas. Caso você
clique em uma das classes, o frame da seta azul passará a mostrar todos
os detalhes dessa classe específica. Veja na imagem que foi clicada na
classe String e, portanto, o frame da seta azul está exibindo os detalhes dela.
Por
fim o frame da seta azul é capaz de exibir detalhes do pacote ou classe
selecionados nos outros dois frames à esquerda da página. Caso esteja
sendo exibida uma classe, mais abaixo desse frame (seta azul) você
poderá visualizar todos os atributos, construtores e métodos (públicos)
dela, com uma explicação abreviada e outra completa. Em alguns casos
existem até exemplos.
Nota: na
geração da API, o default é que apenas os elementos públicos de uma
classes sejam exibidos na página. Outro detalhe muito interessante é que
nós, meros mortais, também podemos gerar uma API das classes Java que
programamos simplesmente acrescentando comentários de API no código.
Ainda não vimos como fazer isso, mas já adianto que é extremamente
simples e útil gerar a API no Java.
Bom, todo esse texto foi apenas para prepará-lo para a seguinte ideia:
A partir de agora, sempre que
você precisar pesquisar quais métodos, atributos ou construtores, ou
até mesmo quais classes estão disponíveis no Java, você recorrerá a API.
Nunca se esqueça disso e torne essa prática um hábito (saudável,
alias).

Ao que interessa...
A classe String no Java é utilizada para armazenar palavras. Qualquer palavra ou frase no Java poderá ser armazenada numa String. Note que String não é um tipo primitivo e sim uma classe!
Mas quantos caracteres cabem numa String? Não
perdi muito tempo em entender que talvez seja mais fácil chegarmos no
limite de memória do micro (ou da JVM) do que preencher toda a
capacidade suportada pela classe String no Java. Se pensarmos de forma lógica, quando perguntamos o tamanho de uma String, o Java nos retorna um primitivo int. Como o int pode
armazenar (na fração positiva do número) um valor máximo de
2.147.483.647, pode-se entender que esse seria o limite de caracteres
que o String suporta. Ou seja,
você poderá escrever muitos muitos muitos caracteres que ainda estará
longe de alcançar esse limite. Abaixo postei um link de uma discussão
sobre o assunto. Só recomendo a leitura para aqueles que querem dominar o
mundo! Para os demais, bola pra frente!
Outro detalhe importante é que, diferente do char, uma String é delimitada por aspas duplas. Veja um trecho de código abaixo.
public class Principal {
public static void main(String args[]) {
String s1 = "minha casa é na lua...";
String s2 = new String( "outra forma de criar uma String" );
// Essa linha vai dar erro de compilação
String erroDeCompilacao = 'outra String';
}
}
Veja que podemos instanciar uma String apenas
informando o seu valor diretamente entre as aspas duplas ou ainda
através de uma inicialização corriqueira de objetos no Java (através da
sintaxe new). Esse código não poderá ser compilado justamente por estarmos tentando criar uma String com aspas simples.
Vamos agora visualizar alguns métodos da classe String.
charAt(int index): Esse método retorna uma char (tipo primitivo) cujo
índice é passado pelo parâmetro. O índice numa String funciona
semelhante a um Array, onde o primeiro é zero e o último é igual ao
tamanho da String menos 1.
equals(Object anObject): Esse método, assim como aquele que existe na classe Object, permite que comparemos os objetos. Há uma particularidade na comparação de objetos do tipo String: o equals() da String compara
os valores dos caracteres presentes, de forma case sensitive, e retorna
TRUE, caso estes sejam iguais. Vou mostrar um exemplo adiante.
equalsIgnoreCase(String anotherString):
Para os casos onde devamos desprezar a diferença de maiúsculas e
minúsculas para comparação, podemos utilizar esse método. Com ele, as
frases “eu sou eu” e “Eu SOU eu” são iguais - ou seja, ele retornará
TRUE.
getBytes(): Esse método retorna um Array de bytes (tipo primitivo) da String criada.
Na prática ele não tem muita importância, todavia, caso você teve a
curiosidade de estudar os elementos utilizados na classe Util.java que
tem servido de apoio para nossos exercícios, verá que utilizei esse
método. Com exceção das devidas apresentações da classe System, você já tem condições de tentar montar essa classe Util sozinho (talvez quase, mas o getBytes() você já conhece).
Nota: Na aula surgiu uma dúvida interessante sobre esse método: um byte é composto por 8 bits. Na String, todavia, os caracteres são guardados em unicode (16 bits). Como essa classe faz a conversão?
Ai ai ai!!! E agora, como vamos descobrir isso!?!
Simples: olhando na API! Veja abaixo um copy-paste do texto original.
Encodes this String into a sequence of bytes using the platform's default charset, storing the result into a new byte array.
The
behavior of this method when this string cannot be encoded in the
default charset is unspecified. The CharsetEncoder class should be used
when more control over the encoding process is required.
Na
prática significa que sempre seremos remetidos ao charset do sistema
onde o programa está rodando. Caso não seja possível representar algum
dos caracteres em tipo primitivo char, o comportamento desse método é desconhecido.
length(): Esse com certeza é um dos métodos mais importantes que a classe String nos oferece. Ele retorna um int com o tamanho da String.
Em sistemas reais ele é muito utilizado na definição de regras do tipo
“o nome do cliente deve ter no máximo 40 caracteres”. Veremos em breve
um exemplo.
substring(int beginIndex, int endIndex): Esse outro método também é muito importante. Através dele podemos retornar uma substring de dentro de nossa String.
O valor do primeiro parâmetro é o índice inicial da substring,
inclusive. O segundo parâmetro define o índice final, exclusive. Isso
significa dizer que o índice apontado pelo segundo parâmetro não
participa da substring.
Veja por exemplo o código abaixo:
public class Principal {
public static void main(String args[]) {
// 01234567
String str = "maracanã";
/*
Note que maracanã possui 8 letras, sendo que o último
índice é igual a 7. Se tentarmos obter uma substring
começando do índice 2 e terminando no índice 5, deveremos
informar o segundo parâmetro como 6, pois a substring
NÃO vai considerar o índice do segundo parâmetro (ou seja,
é igual ao índice do segundo parâmetro menos um).
*/
System.out.println( str.substring( 2 , 6 ) );
// Esse comando vai exibir: raca
}
}
Então, podemos por exemplo obter uma substring que contém exatamente os mesmos caracteres da String original através do comando:
str.substring( 0 , str.length() );
Veremos agora um exemplo de código que utiliza todos esses métodos.
public class Principal {
public static void main(String args[]) {
String str1 = "Eu sou uma String!";
String str2 = "EU SOU UMA String!";
// Esse comando vai mostrar o char E
System.out.println( str1.charAt( 0 ) );
/*
As duas Strings apresentam as mesma frase,
todavia, como o equals() é sensitivo ao
tamanho do caracter (ou seja, a mesma letra
maiúscula e minúscula são diferentes), as
Strings são diferentes.
*/
if ( str1.equals( str2 ) ) {
System.out.println( "Sao iguais!" );
} else {
System.out.println( "Sao diferentes!" );
}
/*
Nesse caso, elas são iguais!
*/
if ( str1.equalsIgnoreCase( str2 ) ) {
System.out.println( "Sao iguais!" );
} else {
System.out.println( "Sao diferentes!" );
}
/*
Aqui serão exibidos os números (bytes) associados
às letras da str1. Note que usei o comando print ao
invés do println. A diferença é que o comando print
não pula linha após a impressão no prompt.
*/
byte[] meusBytes = str1.getBytes();
for (int i = 0; i < meusBytes.length; i++)
System.out.print( meusBytes[ i ] + " " );
// Apenas para pular linha
System.out.println();
// Tamanho da str2
System.out.println( "A str2 possui " + str2.length() + " caracteres!" );
// Obtendo uma substring: sou
System.out.println( str1.substring( 3 , 6 ) );
/*
Outros exemplos úteis.
Veja que é possível concatenar duas Strings apenas com o sinal de +
Veja que uma simples palavra dentro de aspas duplas já é considerada
um objeto String para o Java, portanto, pode receber chamadas de métodos
*/
String str3 = "Epa";
System.out.println( str3 + " " + str3 );
int tamanho = "Guilherme".length();
System.out.println( tamanho );
}
}
Pronto. Exercício! Encontre a definição desses métodos na API do Java. O link segue abaixo.

O que é um Constructor?
Ao longo desse tópico trataremos de algumas regras definitivas. Vamos à primeira:
Regra 1:
Um constructor (ou construtor, em português) é um método (por assim dizer) pelo qual um objeto que está sendo criado sempre passará. Ou seja, todo o objeto no Java que está sendo instanciado vai executar o código definido no seu construtor.
Regra 2:
A sintaxe do constructor é similar a dos métodos. Veja abaixo as regras para se criar um constructor.
1. O constructor deve ter exatamente o mesmo nome da classe;
2. Ele não deve conter nenhuma sintaxe para identificar o tipo de retorno - inclusive o void, também não é permitido.
3. Ele pode ser criado com qualquer visibilidade, todavia, caso opte em colocar algo diferente de public, você estará limitando a criação de objetos dessa classe. Inclusive caso você opte em colocar como private, ninguém, exceto a própria classe, poderá criar um objeto desse.
Veja abaixo um exemplo de código.
public class Recipiente {
int capMax;
public Recipiente() {
// Esse é o constructor
}
}
Lembra da aula 5, onde fizemos um exercício cujo foi necessário a classe Recipiente (http://linubr.blogspot.com.br/2012/08/aula-5-lapidando-o-aprendizado-da.html)?
Poisé, estou utilizando ela de propósito para uma explicação posterior.
Veja que esse constructor foi definido com a visibilidade public. Tenha em mente que sempre que um objeto da classe Recipiente for criado, o código que colocarmos dentro do constructor será executado. Veja o próximo exemplo.
public class Recipiente {
int capMax;
public Recipiente() {
System.out.println( "Ebaaa!!! Eu sou um novo recipiente!" );
}
}
class Executora {
public static void main(String args[]) {
Recipiente r = new Recipiente();
}
}
Note que coloquei uma impressão no prompt dentro dos comandos do constructor da classe Recipiente. A classe Executora simplesmente está criando um novo objeto Recipiente no método main(). Olhe o que ocorre na execução da classe Executora.

Exatamente! A mensagem foi exibida no prompt.
Mas espera ai!? Nós criamos vários objetos nos outros exercícios e em nenhum deles eu defini um constructor!
Regra 3:
Caso você não defina um constructor manualmente no código, o Java sempre definirá
um para você. Isso significa dizer que em todas as classes que criamos
até aqui o Java teve que criar um constructor padrão para nós.
Mas o que é um constructor padrão?
É um constructor público, sem parâmetros e sem código nenhum definido. Algo parecido com o exemplo abaixo.
public Recipiente() {
}
Uma questão importante é que caso você crie seu próprio constructor, o Java não criará um para você! Lembre-se sempre disso.
Você deve estar se perguntando sobre a utilidade de um constructor. Por que criar um constructor se o Java é capaz de criá-lo para mim?
Regra 4:
Pense na classe Recipiente. Existe algum recipiente, seja em qualquer sistema, que não possua a capacidade máxima definida? Não!
Sempre
que um objeto precisar da definição de algum atributo ou execução de
algum comportamento para que sua existência não fique inconsistente,
devemos fazer isso no constructor. Veja o caso abaixo.
public class Recipiente {
private int capMax;
public void definirCapMax(int capMax) {
if ( capMax < 1 ) {
System.out.println( "Capacidade Maxima invalida!" );
} else {
this.capMax = capMax;
}
}
}
class Executora {
public static void main(String args[]) {
// Aqui estou criando o objeto
Recipiente r = new Recipiente();
// ATENÇÃO - vou comentar sobre esse ponto!!
// Aqui estou definindo sua capacidade
r.definirCapMax( 50 );
}
}
Veja que no main() da classe Executora o objeto Recipiente foi criado em momento separado da definição da sua capacidade máxima. Note que após o chamada do definirCapMax() o objeto passou a existir num estado consistente, mas antes disso (no trecho comentado com a palavra ATENÇÃO), esse sistema coexistia com um Recipiente misterioso, sem definição de capacidade máxima. Volto a perguntar: existe agum recipiente sem capacidade máxima?
Nesse
caso, a melhor (e única solução) seria permitir que a capacidade máxima
do recipiente fosse definida tão logo o objeto fosse instanciado. Qual é o método que é chamado na criação do objeto? Isso mesmo!! O constructor vai nos ajudar agora.
public class Recipiente {
private int capMax;
public Recipiente(int capMax) {
this.definirCapMax( capMax );
}
public void definirCapMax(int capMax) {
if ( capMax < 1 ) {
System.out.println( "Capacidade Maxima invalida!" );
} else {
this.capMax = capMax;
}
}
}
class Executora {
public static void main(String args[]) {
// Aqui estou criando o objeto
Recipiente r = new Recipiente( 50 );
// Legal!! O recipiente já está consistente!
}
}
Incrível né?! Da forma como o código foi construído, nunca será
possível criar um recipiente sem que seja informada a capacidade
máxima. Note que dentro do constructor eu optei em chamar o próprio
método da definição da capacidade máxima definirCapMax(). Essa é uma boa prática, pois evita replicação de código de controle em lugares diferentes da classe, melhorando a coesão.
Regra 5:
Por
fim, só crie seu constructor se realmente for necessário. Estou dizendo
isso para que vocês não saiam criando constructor em todas as classes a
partir de agora. Só criem quando realmente for necessário. Lembre-se
que classes bem definidas não são aquelas em que não temos como incluir mais nada e sim aquelas em que não podemos retirar mais nada, caso contrário o código ficará errado. Simplicidade é tudo!

Então, resumindo as regras do constructor:
Regra 1: Quando um objeto está sendo instanciado o constructor sempre será executado.
Regra 2: Um constructor deve ter o mesmo nome da classe e não deve possuir valor de retorno.
Regra 3: Se
você não definir um constructor, o Java criará um para você. Todavia,
caso você crie um, o seu passará a ser utilizado na criação do objeto
(ou seja, o Java não vai criar um outro).
Regra 4: Sempre
que a consistência de um objeto depender da execução de algum código
(seja métodos ou definição de atributos), utilize um constructor.
Regra 5: Só crie um constructor se este for imprescindível.
Senhores, a partir de agora, devemos modelar nossas classes contemplando os construtores, sempre que necessário.
Objetos Polimórficos
É chegada a hora! Entraremos agora no último pilar da Orientação a Objetos.

Desde
a primeira aula vínhamos conversando que a Orientação a Objetos e a
programação da linguagem Java é sustentada por quatro pilares:
Abstração, Encapsulamento, Herança e Polimorfismo. A partir de agora
introduziremos o último, mas não menos importante, dos pilares: o
Polimorfismo.
A essência desse conceito é
simples, mas confesso que seu entendimento na prática é, digamos assim,
de difícil digestão. Entretanto, não tema, vamos caminhando aos poucos.
Afinal, o que são objetos polimórficos?
O
real significado é mais complexo do que o tema que trataremos nessa
aula. Ao longo das próximas aulas iremos aperfeiçoando esse conceito.
Por enquanto, podemos resumir em apenas uma assertiva:
Através do polimorfismo é possível instanciar um subtipo em uma classe mais genérica.
Certamente
ninguém espera entender o que é polimorfismo através dessa frase! Vamos
contextualizá-la. Lembra-se do exercício da Aula 12 (http://linubr.blogspot.com.br/2012/08/aula-12-lapidando-o-aprendizado-da.html)? Aquele da hierarquia de mamíferos e aves? Vou replicar o diagrama de classes aqui.

Pense
agora na seguinte situação: imagine um sistema de simulação de uma
fazenda, onde um usuário (o fazendeiro) poderia se aproximar de
determinado animal e alimentá-lo. Neste contexto, sempre que o
fazendeiro for alimentar, o animal emitirá um som. Isso deve ser
verdadeiro para todos os animais! Vamos então modelar esse cenário. Atenção: para simplificar o exemplo eu retirei as classes abstratas Mamifero e Ave. Ahh sim, coloquei também dois constructors nas subclasses e incluí um método voar() para a classe Galinha.

Note que o método alimentar() de Fazendeiro recebe como parâmetro um Animal.
A grande sacada é justamente essa!
Veja que o Fazendeiro deve ser capaz de alimentar qualquer objeto cujo supertipo é um Animal. Nunca acontecerá dele alimentar uma instância de Animal (pois essa classe é Abstrata - veja que ela está em itálico). Nós somos capazes de programar a classe Animal e Fazendeiro independentemente das classes Cachorro e Galinha. Vamos fazer um teste!!! Veja as classes abaixo.
Arquivo Animal.java
public abstract class Animal {
private String nome;
public void setNome(String nome) {
this.nome = nome;
}
public String getNome() { return this.nome; }
public abstract void falar();
}
|
Arquivo Fazendeiro.java
public class Fazendeiro {
public void alimentar(Animal animal) {
// ...
// código para alimentar o animal
// ...
// Depois de alimentado, o animal fala!
animal.falar();
}
}
|
Veja que nós criamos o método alimentar() ainda antes de conhecer quais animais efetivamente serão alimentados! Mas se o método recebe como parâmetro um Animal, como passaremos para ele um Cachorro (ou uma Galinha)?
Essa é a pergunta primordial! Você acha que o código acima compila? Veja.

Não
há erro nenhum de sintaxe que poderia causar problemas na compilação,
mas por enquanto não temos como executar nosso sistema. Isso se deve ao
fato de não existir nenhuma subclasse concreta de Animal e, é claro, não termos criado uma classe Executora.
Vamos continuar com o código.
Arquivo Cachorro.java
public class Cachorro extends Animal {
public Cachorro(String nome) {
super.setNome( nome );
}
public void falar() {
System.out.println( "O cachorro " + super.getNome() + " falou: Au Au!" );
}
}
|
Arquivo Galinha.java
public class Galinha extends Animal {
public Galinha(String nome) {
super.setNome( nome );
}
public void falar() {
System.out.println( "A galinha " + super.getNome() + " falou: Co co ri co!" );
}
public void voar() {
System.out.println( "A galinha " + super.getNome() + " voou!" );
}
}
|
Por enquanto não há novidade alguma. Vamos agora ao gran finale!
Arquivo Executora.java
public class Executora {
public static void main(String args[]) {
Fazendeiro joaoDoCaminhao = new Fazendeiro();
Animal a1 = new Cachorro( "Bidu" );
Animal a2 = new Cachorro( "Nemu" );
Animal a3 = new Galinha( "Chica" );
joaoDoCaminhao.alimentar( a1 );
joaoDoCaminhao.alimentar( a2 );
joaoDoCaminhao.alimentar( a3 );
}
}
|
Observe,
com muita atenção, que ambos os cachorros e a galinha foram
instanciados e guardados numa variável de instância do supertipo Animal. E essa é justamente a classe aguardada pelo método alimentar() do objeto joaoDoCaminhao (que é do tipo Fazendeiro). Você deve estar se perguntando: isso compila?

Não só compila, como roda! Veja que conforme conversamos, o método alimentar() da classe Fazendeiro foi capaz de executar os métodos falar() de cada um dos animais instanciados sem sequer
saber qual animal estava instanciado no momento. Isso só foi possível
porque o polimorfismo nos permite instanciar um subtipo e guardá-lo numa
classe do supertipo. Foi exatamente isso que fizemos quando criamos uma
Galinha e colocamos em um Animal. Veja o código abaixo.
Animal a3 = new Galinha( "Chica" );
Como o supertipo Animal possui o método falar(), o comportamento alimentar() de Fazendeiro pôde chamá-lo. Agora pense no seguinte: Eu também poderia chamar o método voar() da Galinha que está guardada no Animal?
Veja uma modificação na classe Executora onde tentamos fazer isso.
Arquivo Executora.java
public class Executora {
public static void main(String args[]) {
Fazendeiro joaoDoCaminhao = new Fazendeiro();
Animal a = new Galinha( "Chica" );
joaoDoCaminhao.alimentar( a );
// Aqui vai dar erro!!
a.voar();
}
}
|
Porém quando tentamos compilar:

Veja que o Java não encontrou o método voar() em Animal. Mas isso ocorre justamente porque nossa classe de trabalho Animal (a variável de instância) não contém esse método. Apesar da instância guardada em a ser do tipo Galinha, nós não poderemos acessar seus métodos ou atributos particulares pelo fato de estarmos usando um supertipo mais genérico.
Agora que fizemos esse exemplo, vou repetir a frase que explica o polimorfismo:

Vejam que foi exatamente isso que fizemos: criamos um Cachorro e uma Galinha (subtipos) dentro da classe mais genérica, ou supertipo, Animal.
Sei
que vocês devem estar com aquela sensação de que a ficha não caiu! Não
se preocupem!! Isso é absolutamente normal quando falamos em
polimorfismo. Faremos a partir de agora um intensivão de casos onde essa
característica estará presente.
Exercício das Datas (difícil)
Esse é mais um exercício que não é by Guilherme. O copiei de uma boa listagem de exercícios sobre Java que pode ser encontrada no link abaixo:
Nosso foco estará no exercício cujo texto repeti abaixo (com algumas adaptações):
Implemente as classes especificadas abaixo:
Data - Classe abstracta
Atributos: ano, dia, mes.
Métodos:
boolean bissexto() //verifica se a data pertence a um ano bissexto
boolean valida() // valida se a data está correta
void mostra() // método abstrato
void defineData(String data) // método abstrato
DataEuropeia - Sub-Classe de Data
Métodos:
void mostra() // mostra a data no formato ano/mes/dia
void defineData(String data) // lê os dados na forma ano/mes/dia
DataAmericana - Sub-Classe de Data
Métodos:
void mostra() // mostra a data no formato mes/dia/ano
void defineData(String data) // lê os dados na forma mes/dia/ano
DataBrasileira - Sub-Classe de Data
Métodos:
void mostra() // mostra a data no formato dia/mes/ano
void defineData(String data) // lê os dados na forma dia/mes/ano
O sistema basicamente deverá permitir que o usuário trabalhe com uma data. Podemos definir os passos da classe Executora da seguite forma:
1. O usuário deverá escolher o tipo da data trabalhado;
2. O usuário deve entrar com uma String no formato adequado com a data escolhida (utilize o método lerTexto() da classe Util);
3. O sistema deve validar a data;
4. Se a data for válida, então:
a. Verificar se a data pertence a um ano bissexto e
b. Exibir a data no formato padrão.
|
Observações: note que o usuário deverá informar uma String que seja compatível com a data escolhida. Por exemplo, caso ele opte em trabalhar com a DataAmericana, ele
deverá digitar algo parecido com 04/13/2011 (ou seja, mês/dia/ano).
Caso ele não digite nesse formato o sistema deve considerar que a data
está inválida.

Pessoal, boa sorte!!!! Tente resolver esse exercício. Vamos resolvê-lo juntos na próxima aula.
Ahh sim... isso foi só o início do polimorfismo! Ainda existem outros conceitos sobre ele...
Abs, Guilherme Pontes
Nenhum comentário:
Postar um comentário