Classes definem estados como variáveis de instância e comportamentos como métodos de instância.
As variáveis de instância também são conhecidas como variáveis de membro.
A classe não consome nenhum espaço.
Para lhe dar uma idéia sobre classe e objetos, vamos criar uma classe Cat que represente estados e comportamentos do mundo real Cat.
public class Cat {
/*
Instance variables: states of Cat
*/
String name;
int age;
String color;
String breed;
/*
Instance methods: behaviors of Cat
*/
void sleep(){
System.out.println("Sleeping");
}
void play(){
System.out.println("Playing");
}
void feed(){
System.out.println("Eating");
}
}Agora, definimos com sucesso um modelo para o gato. Digamos que temos dois gatos chamados Thor e Rambo.
Como podemos defini-los em nosso programa?
Primeiro, precisamos criar dois objetos da classe Cat.
public class Main {
public static void main(String[] args) {
Cat thor = new Cat();
Cat rambo = new Cat();
}
}Em seguida, definiremos seus estados e comportamentos.
public class Main {
public static void main(String[] args) {
/*
Creating objects
*/
Cat thor = new Cat();
Cat rambo = new Cat();
/*
Defining Thor cat
*/
thor.name = "Thor";
thor.age = 3;
thor.breed = "Russian Blue";
thor.color = "Brown";
thor.sleep();
/*
Defining Rambo cat
*/
rambo.name = "Rambo";
rambo.age = 4;
rambo.breed = "Maine Coon";
rambo.color = "Brown";
rambo.play();
}
}Como nos exemplos de código acima, podemos definir nossa classe, instanciar (criar objetos) e especificar os estados e comportamentos desses objetos.
Agora, abordamos o básico da programação orientada a objetos. Vamos seguir para os princípios da programação orientada a objetos.
Princípios de programação orientada a objetos
Esses são os quatro princípios principais do paradigma de programação orientada a objetos. Compreendê-los é essencial para se tornar um programador de sucesso.
- Encapsulamento
- Herança
- Abstração
- Polimorfismo
Agora vamos ver cada um com mais detalhes.
Encapsulamento
Encapsulamento é um processo de agrupar código e dados em uma única unidade.
É como uma cápsula que contém uma mistura de vários medicamentos e é uma técnica que ajuda a manter as variáveis da instância protegidas.
Isso pode ser alcançado usando private modificadores de acesso. Eles não podem ser acessados por ninguém fora da classe. Para acessar com segurança os estados privados, precisamos fornecer métodos públicos de obtenção e instalação. (Em Java, esses métodos devem seguir os padrões de nomenclatura JavaBeans.)
Digamos que há uma loja de discos que vende álbuns de artistas diferentes e um detentor de ações que os gerencia.
Se você observar a figura 4, o StockKeeper classe pode acessar o Album estados da classe diretamente como Album os estados da classe estão definidos para public.
E se o detentor das ações criar um álbum e definir os estados com valores negativos? Isso pode ser feito intencionalmente ou não por um detentor de ações.
Para ilustrar, vamos ver um programa Java de amostra que explica o diagrama e a declaração acima.
Classe do álbum:
public class Album {
public String name;
public String artist;
public double price;
public int numberOfCopies;
public void sellCopies(){
if(numberOfCopies > 0){
numberOfCopies--;
System.out.println("One album has sold!");
}
else{
System.out.println("No more albums available!");
}
}
public void orderCopies(int num){
numberOfCopies += num;
}
}Classe StockKeeper:
public class StockKeeper {
public String name;
public StockKeeper(String name){
this.name = name;
}
public void manageAlbum(Album album, String name, String artist, double price, int numberOfCopies){
/*
Defining states and behaviors for album
*/
album.name = name;
album.artist = artist;
album.price = price;
album.numberOfCopies = numberOfCopies;
/*
Printing album details
*/
System.out.println("Album managed by :"+ this.name);
System.out.println("Album details::::::::::");
System.out.println("Album name : " + album.name);
System.out.println("Album artist : " + album.artist);
System.out.println("Album price : " + album.price);
System.out.println("Album number of copies : " + album.numberOfCopies);
}
}Classe principal:
public class Main {
public static void main(String[] args) {
StockKeeper johnDoe = new StockKeeper("John Doe");
/*
Stock keeper creates album and assigns negative values for price and number of copies available
*/
johnDoe.manageAlbum(new Album(), "Slippery When Wet", "Bon Jovi", -1000.00, -50);
}
}Resultado:
Album managed by :John Doe
Album details::::::::::
Album name : Slippery When Wet
Album artist : Bon Jovi
Album price : -1000.0
Album number of copies : -50O preço e o número de cópias do álbum não podem ter valores negativos. Como podemos evitar essa situação? É aqui que usamos o encapsulamento.
Nesse cenário, podemos impedir que o depositário atribua valores negativos. Se eles tentarem atribuir valores negativos para o preço e o número de cópias do álbum, atribuímos a eles 0,0 e 0.
Classe do álbum:
public class Album {
private String name;
private String artist;
private double price;
private int numberOfCopies;
public void sellCopies(){
if(numberOfCopies > 0){
numberOfCopies--;
System.out.println("One album has sold!");
}
else{
System.out.println("No more albums available!");
}
}
public void orderCopies(int num){
numberOfCopies += num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
if(price > 0) {
this.price = price;
}
else {
this.price = 0.0;
}
}
public int getNumberOfCopies() {
return numberOfCopies;
}
public void setNumberOfCopies(int numberOfCopies) {
if(numberOfCopies > 0) {
this.numberOfCopies = numberOfCopies;
}
else {
this.numberOfCopies = 0;
}
}
}Classe StockKeeper:
public class StockKeeper {
private String name;
StockKeeper(String name){
setName(name);
}
public void manageAlbum(Album album, String name, String artist, double price, int numberOfCopies){
/*
Defining states and behaviors for album
*/
album.setName(name);
album.setArtist(artist);
album.setPrice(price);
album.setNumberOfCopies(numberOfCopies);
/*
Printing album details
*/
System.out.println("Album managed by :"+ getName());
System.out.println("Album details::::::::::");
System.out.println("Album name : " + album.getName());
System.out.println("Album artist : " + album.getArtist());
System.out.println("Album price : " + album.getPrice());
System.out.println("Album number of copies : " + album.getNumberOfCopies());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}Classe principal:
public class Main {
public static void main(String[] args) {
StockKeeper johnDoe = new StockKeeper("John Doe");
/*
Stock keeper creates album and assigns negative values for price and number of copies available
*/
johnDoe.manageAlbum(new Album(), "Slippery When Wet", "Bon Jovi", -1000.00, -50);
}
}Resultado:
Album managed by :John Doe
Album details::::::::::
Album name : Slippery When Wet
Album artist : Bon Jovi
Album price : 0.0
Album number of copies : 0Com o encapsulamento, impedimos nosso depositário de atribuir valores negativos (temos controle sobre os dados).
Vantagens do encapsulamento em Java
- Nós podemos fazer uma aula somente leitura ou somente gravação: para uma classe somente leitura, devemos fornecer apenas um método getter. Para uma classe somente gravação, devemos fornecer apenas um método setter.
- Controle sobre os dados: podemos controlar os dados fornecendo lógica aos métodos de configuração. Assim como restringimos o detentor do estoque de atribuir valores negativos no exemplo acima.
- Ocultar dados: outras classes não podem acessar membros particulares de uma classe diretamente.
Herança
Digamos que a loja de discos que discutimos acima também vende filmes em Blu-ray.
Como você pode ver no diagrama acima, existem muitos estados e comportamentos comuns (código comum) entre Album e Movie.
Ao implementar esse diagrama de classes no código, você irá escrever (ou copiar e colar) todo o código para Movie? Se você faz, você está se repetindo. Como você pode evitar a duplicação de código?
É aqui que usamos a herança.
A herança é um mecanismo no qual um objeto adquire todos os estados e comportamentos de um objeto pai.
A herança usa um relacionamento pai-filho (relacionamento IS-A).
Então, o que exatamente é herdado?
Modificadores de visibilidade / acesso impactar o que é herdado de uma classe para outra.
Em Java, como regra de ouro fazemos variáveis de instância private e métodos de instância public .
Nesse caso, podemos dizer com segurança que o seguinte é herdado:
- métodos de instância pública.
- variáveis de instância privada (variáveis de instância privada podem ser acessadas apenas por meio de métodos públicos getter e setter).
Tipos de herança em Java
Existem cinco tipos de herança em Java. Eles são únicos, multiníveis, hierárquicos, múltiplos e híbridos.
A classe permite heranças únicas, multiníveis e hierárquicas. A interface permite heranças múltiplas e híbridas.
Uma classe pode estender apenas uma classe, no entanto, pode implementar qualquer número de interfaces. Uma interface pode estender mais de uma interface.
Relacionamentos
I. Relação IS-A
Um relacionamento IS-A refere-se a herança ou implementação.
uma. Generalização
A generalização usa um relacionamento IS-A de uma classe de especialização para uma classe de generalização.
II Relação HAS-A
Uma instância de uma classe HAS-A referência a uma instância de outra classe.
uma. Agregação
Nesse relacionamento, a existência das classes A e B não depende uma da outra.
Para esta parte de agregação, veremos um exemplo da classe Student e da classe ContactInfo.
class ContactInfo {
private String homeAddress;
private String emailAddress;
private int telephoneNumber; //12025550156
}
public class Student {
private String name;
private int age;
private int grade;
private ContactInfo contactInfo;//Student HAS-A ContactInfo
public void study() {
System.out.println("Study");
}
}Student TEM UM ContactInfo. ContactInfo pode ser usado em outros lugares – por exemplo, uma empresa Employee classe também pode usar isso ContactInfo classe. assim Student pode existir sem ContactInfo e ContactInfo pode existir sem Student . Esse tipo de relacionamento é conhecido como agregação.
b. Composição
Nesse relacionamento, a classe B não pode existir sem a classe A – mas a classe A lata existe sem classe B.
Para lhe dar uma idéia sobre composição, vamos ver um exemplo da classe Student e da classe StudentId.
class StudentId {
private String idNumber;//A-123456789
private String bloodGroup;
private String accountNumber;
}
public class Student {
private String name;
private int age;
private int grade;
private StudentId studentId;//Student HAS-A StudentId
public void study() {
System.out.println("Study");
}
}Student TEM UM StudentId. Student pode existir sem StudentId mas StudentId não pode existir sem Student. Esse tipo de relacionamento é conhecido como composição.
Agora, voltemos ao exemplo anterior da loja de discos que discutimos acima.
Podemos implementar esse diagrama em Java para evitar duplicação de código.
Vantagens da herança
- Reutilização de código: a classe filha herda todos os membros da instância da classe pai.
- Você tem mais flexibilidade para alterar o código: alterar o código no local é suficiente.
- Você pode usar polimorfismo: a substituição do método requer um relacionamento IS-A.
Abstração
Abstração é um processo de ocultar os detalhes da implementação e mostrar apenas a funcionalidade para o usuário.
Aqui está um exemplo de abstração: pressionar o acelerador aumentará a velocidade do carro. Mas o motorista não sabe como pressionar o acelerador aumenta a velocidade – porque eles não precisam saber disso.
Tecnicamente abstrato significa algo incompleto ou a ser concluído posteriormente.
Em Java, podemos obter a abstração de duas maneiras: classe abstrata (0 a 100%) e interface (100%).
A palavra-chave abstract pode ser aplicado a classes e métodos. abstract e final ou static nunca podem ficar juntos.
I. Classe abstrata
Uma classe abstrata é aquela que contém a palavra-chave abstract.
Classes abstratas não podem ser instanciadas (não é possível criar objetos de classes abstratas). Eles podem ter construtores, métodos estáticos e métodos finais.
II Métodos abstratos
Um método abstrato é aquele que contém a palavra-chave abstract.
Um método abstrato não tem implementação (nenhum corpo do método e termina com ponto e vírgula). Não deve ser marcado como private.
III Classe abstrata e métodos abstratos
- Se pelo menos um método abstrato existir dentro de uma classe, toda a classe deve ser abstrata.
- Podemos ter uma classe abstrata sem métodos abstratos.
- Podemos ter qualquer número de métodos abstratos e não abstratos dentro de uma classe abstrata ao mesmo tempo.
- A primeira subclasse concreta de uma classe abstrata deve fornecer implementação para todos os métodos abstratos.
- Se isso não acontecer, a subclasse também deve ser marcada como abstrata.
Em um cenário do mundo real, a implementação será fornecida por alguém desconhecido para os usuários finais. Os usuários não conhecem a classe de implementação e a implementação real.
Vamos considerar um exemplo de uso de conceito abstrato.
abstract class Shape {
public abstract void draw();
}
class Circle extends Shape{
public void draw() {
System.out.println("Circle!");
}
}
public class Test {
public static void main(String[] args) {
Shape circle = new Circle();
circle.draw();
}
}Quando queremos marcar uma classe como abstrata?
- Forçar subclasses a implementar métodos abstratos.
- Parar de ter objetos reais dessa classe.
- Para continuar tendo uma referência de classe.
- Manter o código de classe comum.
Interface
Uma interface é um plano de uma classe.
Uma interface é 100% abstrata. Nenhum construtor é permitido aqui. Representa um relacionamento IS-A.
NOTA: As interfaces definem apenas os métodos necessários. Não podemos manter o código comum.
Uma interface pode ter apenas métodos abstratos, não métodos concretos. Por padrão, os métodos de interface são public e abstract. Portanto, dentro da interface, não precisamos especificar public e abstract.
Portanto, quando uma classe implementa o método de uma interface sem especificar o nível de acesso desse método, o compilador emitirá um erro informando “Cannot reduce the visibility of the inherited method from interface”. Portanto, o nível de acesso do método implementado deve ser definido como public.
Por padrão, as variáveis de interface são public, static e final.
Por exemplo:
interface Runnable {
int a = 10; //similar to: public static final int a = 10;
void run(); //similar to: public abstract void run();
}
public class InterfaceChecker implements Runnable{
public static void main(String[] args) {
Runnable.a = 5;//The final field Runnable.a cannot be assigned.
}
}Vamos ver um exemplo que explica o conceito de interface:
interface Drawable {
void draw();
}
class Circle implements Drawable{
public void draw() {
System.out.println("Circle!");
}
}
public class InterfaceChecker {
public static void main(String[] args) {
Drawable circle = new Circle();
circle.draw();
}
}Métodos padrão e estáticos em interfaces
Normalmente, implementamos métodos de interface em uma classe separada. Digamos que somos obrigados a adicionar um novo método em uma interface. Então devemos implementar esse método nessa classe separada também.
Para superar esse problema, o Java 8 introduziu métodos estáticos e padrão que implementam métodos dentro de uma interface, diferentemente dos métodos abstratos.
public interface DefaultInterface {
void sleep();
default void run() {
System.out.println("I'm running!");
}
}
public class InterfaceCheckers implements DefaultInterface{
public void sleep() {
System.out.println("Sleeping...");
}
public static void main(String[] args) {
InterfaceCheckers checker = new InterfaceCheckers();
checker.run();
checker.sleep();
}
}
/*
Output:
I'm running!
Sleeping...
*/Semelhante aos métodos estáticos de classes, podemos chamá-los pelo nome da interface.
public interface DefaultInterface {
void sleep();
static void run() {
System.out.println("I'm running!");
}
}
public class InterfaceCheckers implements DefaultInterface{
public void sleep() {
System.out.println("Sleeping...");
}
public static void main(String[] args) {
InterfaceCheckers checker = new InterfaceCheckers();
DefaultInterface.run();
checker.sleep();
}
}
/*
Output:
I'm running!
Sleeping...
*/É uma interface vazia. Por exemplo, interfaces serializáveis, clonáveis e remotas.
public interface Serializable
{
//No fields or methods
}Vantagens das interfaces
- Eles nos ajudam a usar herança múltipla em Java.
- Eles fornecem abstração.
- Eles fornecem acoplamento flexível: os objetos são independentes um do outro.
Quando queremos mudar uma classe para uma interface?
- Forçar subclasses a implementar métodos abstratos.
- Parar de ter objetos reais dessa classe.
- Para continuar tendo uma referência de classe.
NOTA: Lembre-se de que não podemos manter um código comum dentro da interface.
Se você deseja definir métodos potencialmente necessários e código comum, use um classe abstrata.
Se você deseja apenas definir um método necessário, use um interface.
Polimorfismo
Polimorfismo é a capacidade de um objeto assumir várias formas.
O polimorfismo no POO ocorre quando uma superclasse faz referência a um objeto de subclasse.
Todos os objetos Java são considerados polimórficos, pois compartilham mais de um relacionamento IS-A (pelo menos todos os objetos passam no teste IS-A para seu próprio tipo e para a classe Object).
Podemos acessar um objeto através de uma variável de referência. Uma variável de referência pode ser de apenas um tipo. Uma vez declarado, o tipo de uma variável de referência não pode ser alterado.
Uma variável de referência pode ser declarada como uma classe ou tipo de interface.
Um único objeto pode ser referido por variáveis de referência de muitos tipos diferentes, desde que sejam o mesmo tipo ou um super tipo do objeto.
Sobrecarga de método
Se uma classe possui vários métodos com o mesmo nome, mas com parâmetros diferentes, isso é conhecido como sobrecarga de método.
Regras de sobrecarga de método:
- Deve ter uma lista de parâmetros diferente.
- Pode ter diferentes tipos de retorno.
- Pode ter diferentes modificadores de acesso.
- Pode lançar diferentes exceções.
class JavaProgrammer{
public void code() {
System.out.println("Coding in C++");
}
public void code(String language) {
System.out.println("Coding in "+language);
}
}
public class MethodOverloader {
public static void main(String[] args) {
JavaProgrammer gosling = new JavaProgrammer();
gosling.code();
gosling.code("Java");
}
}
/*
Output:
Coding in C++
Coding in Java
*/NOTA: Métodos estáticos também podem ser sobrecarregados.
class Addition {
public static int add(int a,int b) {
return a+b;
}
public static int add(int a,int b,int c) {
return a+b+c;
}
}
public class PolyTest {
public static void main(String[] args) {
System.out.println(Addition.add(5, 5));
System.out.println(Addition.add(2, 4, 6));
}
}NOTA: Podemos sobrecarregar o método main (), mas a Java Virtual Machine (JVM) chama o método main () que recebe matrizes String como argumentos.
public class PolyTest {
public static void main() {
System.out.println("main()");
}
public static void main(String args) {
System.out.println("String args");
}
public static void main(String[] args) {
System.out.println("String[] args");
}
}
//Output: String[] argsRegras a seguir para o polimorfismo
Regras de tempo de compilação
- O compilador conhece apenas o tipo de referência.
- Ele pode procurar apenas no tipo de referência por métodos.
- Produz uma assinatura de método.
Regras de tempo de execução
- No tempo de execução, a JVM segue exatamente tipo de tempo de execução (tipo de objeto) para encontrar o método.
- Deve corresponder a assinatura do método de tempo de compilação ao método na classe do objeto real.
Substituição de método
Se uma subclasse possui o mesmo método declarado na superclasse, isso é conhecido como substituição de método.
Regras de substituição de método:
- Deve ter a mesma lista de parâmetros.
- Deve ter o mesmo tipo de retorno: embora o retorno covariante nos permita alterar o tipo de retorno do método substituído.
- Não deve ter um modificador de acesso mais restritivo: pode ter um modificador de acesso menos restritivo.
- Não deve lançar exceções verificadas novas ou mais amplas: pode lançar exceções verificadas mais estreitas e pode lançar qualquer exceção não verificada.
- Somente métodos herdados podem ser substituídos (devem ter um relacionamento IS-A).
Exemplo para substituição de método:
public class Programmer {
public void code() {
System.out.println("Coding in C++");
}
}
public class JavaProgrammer extends Programmer{
public void code() {
System.out.println("Coding in Java");
}
}
public class MethodOverridder {
public static void main(String[] args) {
Programmer ben = new JavaProgrammer();
ben.code();
}
}
/*
Output:
Coding in Java
*/NOTA: Os métodos estáticos não podem ser substituídos porque os métodos são substituídos no tempo de execução. Métodos estáticos são associados a classes enquanto métodos de instância são associados a objetos. Então, em Java, o main() O método também não pode ser substituído.
NOTA: Os construtores podem ser sobrecarregados, mas não substituídos.
Tipos de objetos e tipos de referência
class Person{
void eat() {
System.out.println("Person is eating");
}
}
class Student extends Person{
void study() {
System.out.println("Student is studying");
}
}
public class InheritanceChecker {
public static void main(String[] args) {
Person alex = new Person();//New Person "is a" Person
alex.eat();
Student jane = new Student();//New Student "is a" Student
jane.eat();
jane.study();
Person mary = new Student();//New Student "is a" Person
mary.eat();
//Student chris = new Person(); //New Person isn't a Student.
}
}No Person mary = new Student(); , essa criação de objeto está perfeitamente correta.
mary é um Person variável de referência do tipo e new Student() criará um novo Student objeto.
mary não pode acessar study() em tempo de compilação porque o compilador conhece apenas o tipo de referência. Como não há study() na classe de tipo de referência, ele não pode acessá-lo. Mas em tempo de execução mary vai ser o Student tipo (tipo de tempo de execução / tipo de objeto).
Revise isso postar para obter mais informações sobre tipos de tempo de execução.
Nesse caso, podemos convencer o compilador dizendo “em tempo de execução, mary será Student digite, então permita-me chamá-lo ”. Como podemos convencer o compilador assim? É aqui que usamos o casting.
Podemos fazer mary uma Student digite em tempo de compilação e pode chamar study() lançando-o.
((Student)mary).study();A seguir, aprenderemos sobre a transmissão.
Fundição do tipo de objeto
A conversão do tipo Java é classificada em dois tipos:
- Transmissão de ampliação (implícita): conversão automática de tipo.
- Estreitamento de elenco (explícito): precisa de conversão explícita.
Nas primitivas, long é um tipo maior que int . Como nos objetos, a classe pai é um tipo maior que a classe filho.
A variável de referência refere-se apenas a um objeto. A conversão de uma variável de referência não altera o objeto na pilha, mas rotula o mesmo objeto de outra maneira por meio da acessibilidade dos membros da instância.
I. Fundição de alargamento
Superclass superRef = new Subclass();II Fundição reduzida
Subclass ref = (Subclass) superRef;Temos que ter cuidado ao estreitar. Ao restringir, convencemos o compilador a compilar sem nenhum erro. Se convencermos errado, obteremos um erro de tempo de execução (geralmente ClassCastException)
Para executar o estreitamento corretamente, usamos o instanceof operador. Ele verifica se há um relacionamento IS-A.
class A {
public void display(){
System.out.println("Class A");
}
}
class B extends A{
public void display(){
System.out.println("Class B");
}
}
public class Test {
public static void main(String[] args) {
A objA = new B();
if(objA instanceof B){
((B)objA).display();
}
}
}
/**
* Output: Class B
*/
Como já afirmei antes, devemos lembrar de uma coisa importante ao criar um objeto usando o new palavra-chave: o tipo de referência deve ser o mesmo tipo ou um super tipo do tipo de objeto.
Conclusão
Obrigado a todos pela leitura. Neste artigo, expliquei os Princípios de Programação Orientada a Objetos em Java.
Convido você a ler mais artigos relacionados sobre OOP. Espero que este artigo tenha ajudado você.
Confira minha série de artigos originais no Medium: Princípios de programação orientada a objetos em Java
Por favor, não hesite em me avisar se tiver alguma dúvida.
O sonho não é aquilo que você vê enquanto dorme, é algo que não o deixa dormir.
– AP J Abdul Kalam, Asas de Fogo: Uma Autobiografia
Obrigado.
Feliz codificação!
