14. JAVA – obiektowo


Programowanie obiektowe – OOP (object-oriented programming) – pozwala na przedstawienie rzeczywistości i relacji w niej zachodzących za pomocą obiektów.

 

Klasa i obiekt

Kiedy mówimy o klasie musimy wyobrazić ją sobie jak ogólny zarys/opis jakiegoś obiektu, zbiór cech wspólnych dla np. człowieka. Gdybyśmy mieli klasę człowiek moglibyśmy określić ogólne cechy jakie definiują ludzi, czyli np. to że śpi, że ma głos, oczy, włosy, ręce, nogi itd.

Obiekt jest instancją danej klasy, czyli konkretnym człowiekiem np. Janem Kowalskim, który ma brązowe włosy, jest typem „skowronka” i ma niebieskie oczy. Te cechy nie są bezpośrednio związane z klasą, a z obiektem (instancją/wystąpieniem klasy).
Można powiedzieć, że Jan Kowalski jest zmienną typu klasy, czyli obiektem typu człowiek.

W Javie istnieje wiele „gotowych” klas, z których możemy korzystać. Zawierają one konkretne własności oraz metody. Poznaliście już klasę String, Integer i inne.

Już wiecie mniej więcej co to jest obiekt i klasa. Rozszerzając te zagadnienia możemy powiedzieć, że klasa jest schematem na bazie którego będziemy tworzyli obiekty np. mając projekt domu, możemy na jego podstawie stworzyć wiele podobnych do siebie domów.
Klasy możecie Tworzyć samodzielnie, mogą one zawierać zdefiniowane wartości i metody lub wykorzystywać wartości przekazane przez użytkownika.
Podsumowując – za pomocą jednej klasy możemy utworzyć wiele obiektów, czyli instancji naszej klasy, które będą niezależne od siebie.

Należy zapamiętać, że dobrą praktyką jest tworzenie nowej klasy w oddzielnym pliku.

Przykład:

Stwórzmy nowy plik/klasę o nazwie Person ustawimy w niej, że nasze obiekty będą miały imię i wiek:

plik Person.java

public class Person1 {
String name;
int age;
}

plik ObjectsAndClasses .java

public class ObjectsAndClasses {
public static void main(String[] args) {

//tworzymy nową osobę
Person osoba1 = new Person();
osoba1.name = "Jan"; //ustawiamy imię
osoba1.age = 18; //ustawiamy wiek

//tworzymy jeszcze jedną osobę
Person osoba2 = new Person();
osoba2.name = "Anna"; //ustawiamy imię
osoba2.age = 22; //ustawiamy wiek

System.out.println(osoba1.name + " ma " + osoba1.age + " lat.");
System.out.println(osoba2.name + " ma " + osoba2.age + " lat.");
}
}

 

Przykład:

Stworzymy nowy obiekt wykorzystując klasę JFrame (należy zaimportować javax.swing.* na samej górze skryptu), dzięki której wywołamy okienko systemowe, czyli utworzymy instancję klasy.

public class UsingObjects {
public static void main(String[] args) {

JFrame window = new JFrame();
//podajemy nazwę typu/klasy + wymyślona nazwa = słówko "new" i nazwa klasy + ()

//teraz nadamy rozmiar okienka
//po wpisaniu nazwy zmiennej i postawieniu "." pokaże nam się lista dostępnych opcji
window.setSize(600,600);

//ustawimy tytuł
window.setTitle("Moje pierwsze okienko");

window.setVisible(true); // ustawiamy widoczność okienka na true
//po uruchomieniu programu pokaże się małe okienko systemowe

 

Metody

Klasa może definiować metody, czyli nic innego jak funkcje z podejścia proceduralnego. Kiedy tworzymy obiekt nadajemy mu dostęp do metod danej klasy.

Możemy rozszerzyć powyższy przykład z osobą lub utworzyć nową klasę:

plik Person2.java:

import java.util.*; //import do metody określającej rok urodzenia

public class Person2 {
String name;
int age;

//w Java istnieją tzw. gettery i settery, czyli metody pobierające lub ustawiające wartość, co do ich stosowania zdania są podzielone;
//możemy zaimplementować je w Intellij automatycznie lub napisać samodzielnie
//automatycznie: PPM -> Generate... (u mnie skrót Alt+G) -> Getter and Setter...

public String getName() {
return name;
}

public void setName(String name) {
this.name = name; //słówko this pozwala dostać się do pola w obiekcie, nie musimy go używać jeżeli nazwy argumentów i zmiennych się różnią
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}



//METODY
//ustawimy metodę, która wygeneruje powitanie
public void sayHello(){
System.out.println("Hello new Person! " + name);
}

//stworzymy metodę określającą rok urodzenia
public void birthYear(){
Calendar data = Calendar.getInstance();
int by = data.get(Calendar.YEAR) - age;
System.out.println("Rok urodzenia to:" + by);
/*
musimy zwrócić uwagę na to, że tak skonstruowana metoda nic nam nie zwraca.
To oznacza, że nie otrzymamy fizycznie wartości, którą możemy wykorzystać gdzieś indziej, otrzymamy jedynie wyświetlony napis. Jeżeli chcemy uzyskać wartość musimy ją zwrócić używając słówka return.
*/
}

public int yearsToRetirement(){
return 65 - age;
}

/*
Pamiętamy z tematu JAVA – instrukcje sterujące, pętle i funkcje(metody), że metody mogą przyjmować argumenty. Czyli możemy przekazać bezpośrednio wartość przy wywołaniu metody
*/
public void speak(String text){
System.out.println(text);
}

public void move(String direction, double distance){
System.out.println("Hurra, poszedłem " + direction + ", " + distance + " metrów");
}

//przeładujemy metodę
public void speak(String text, int words){
System.out.println(text + words + " wyrazy");
}
}

plik Methods.java

package tutaj.nazwa.waszego.pakietu;

public class Methods {
public static void main(String[] args) {
/*
Klasa może definiować metody, czyli nic innego jak funkcje z podejścia proceduralnego. Kiedy tworzymy obiekt nadajemy mu dostęp do metod danej klasy.
*/
Person2 osoba2_1 = new Person2();

/* tak ustawiamy dane bez getterów i setterów*/
osoba2_1.name = "Ania"; //ustawiamy imię
osoba2_1.age = 10; //ustawiamy wiek
osoba2_1.sayHello(); //wyświetli: Hello new Person! Ania
System.out.println(osoba2_1.name + " ma " + osoba2_1.age + " lat."); //wyświetli: Ania ma 10 lat.

//wykorzystanie getterów i setterów do ustawiania wartości danych
Person2 osoba2_2 = new Person2();

osoba2_2.setName("Jan");
osoba2_2.setAge(18);
osoba2_2.sayHello(); //wyświetli: Hello new Person! Jan
System.out.println(osoba2_2.getName() + " ma " + osoba2_2.getAge() + " lat."); //wyświetli: Jan ma 18 lat.


//wywołujemy metodę sprawdzającą rok urodzenia
//ona tylko wyświetla napis
if (osoba2_1.age > 0) osoba2_1.birthYear();

//wywołujemy metodę zwracającą lata do emerytury
int yl = osoba2_1.yearsToRetirement();
System.out.println("Do emerytury pozostało: " + yl + " lat");

//wywołujemy metodę z 1 argumentem
osoba2_1.speak("Cześć, nauczyłem się mówić :)");

//wywołujemy metodę z 2 argumentami
osoba2_1.move("prosto", 5);

//wywołujemy przeładowaną metodę
osoba2_1.speak("Wypowiedziałam dzisiaj ", 2);

//argumenty do metody mogą być przekazywane przez zmienne
String hello = "Hello everybody";
osoba2_1.speak(hello);

}
}

 

Konstruktory

Konstruktory, to metody wywoływane automatycznie w momencie tworzenia obiektu i nie wymagają dodatkowych akcji. Metody te występują tylko w kontekście klas, a ich nazwy są takie same jak nazwa klasy głównej.

Dzięki konstruktorowi możemy przesłać określone wartości do obiektu w momencie jego tworzenia.

 

plik Person3.java

public class Person3 {
String name;
int age;

//tworzymy konstruktor bezargumentowy
public Person3() {
System.out.println("Obiekt klasy Person3 storzony - to ja, konstruktor bezargumentowy");
}

//konstruktor przyjmujący 1 argumant: imię lub wiek
public Person3(String name) {
this.name = name; //aby zmodyfikować konkretną zmienną w obiekcie możemy użyć słówka "this", ale tylko w sytuacji kiedy argument i zmienna mają takie same nazwy
}

//konstruktor przyjmujący 2 argumenty
public Person3(String name, int age) {
this.name = name;
this.age = age;
}
/*
możemy wewnątrz konstruktora wywołać inny konstruktor
nie używamy wywołania np: Person("Ania",13), tylko zamiast nazwy konstruktora używamy "this"
np: this("Ania",13)
*/
}

plik Constructors.java

public class Constructors {
public static void main(String[] args) {

//tworzymy obiekt klasy Person3
Person3 osoba3_1 = new Person3();
/*
wywoła się konstruktor bezargumentowy, bo w nawiasie nie podaliśmy danych
po uruchomieniu skryptu otrzymamy napis:
Obiekt klasy Person3 stworzony - to ja, konstruktor bezargumentowy
został wywołany konstruktor w momencie tworzenia obiektu
*/

/*
możemy tworzyć wiele konstruktorów, rozróżniając je ilością przekazywanych argumentów
*/

//tworzymy obiekt przekazując 1 argument - uruchomi się konstruktor z 1 argumentem
Person3 osoba3_2 = new Person3("Kama");
System.out.println("Przypisano imię " + osoba3_2.name);

//tworzymy obiekt przekazując 2 argumenty - uruchomi się konstruktor z 2 argumentami
Person3 osoba3_3 = new Person3("Anna", 22);
System.out.println("Przypisano imię " + osoba3_3.name + " oraz wiek = " + osoba3_3.age);
}
}

 

Modyfikatory dostępu

Do tej pory wykonywaliśmy przykłady w oparciu o publiczną dostępność danego elementu, czyli public.
Taka metoda lub właściwość jest publiczna, więc widoczna wszędzie wewnątrz obiektu i poza nim.
Dostęp do pola jest nieograniczony i można je odczytywać i zmieniać dowolnie.

Właściwość lub metoda może być prywatna, czyli private, wówczas może zostać użyta tylko wewnątrz obiektu.

Właściwość lub metoda protected może być użyta tylko wewnątrz obiektu, package, ale może być również dziedziczona.

Metoda domyślna – może być dostępna tylko wewnątrz tego package

Zaleca się w programowaniu i w życiu, nie udostępniać zbyt wiele informacji na zewnątrz, więc tego się trzymajmy – korzystamy z modyfikatorów private.

Kolejność restrykcji:
– private
– default
– protected
– public

np.
public void doSomethingPublic(){}
private void doSomethingPrivate(){}
protected void doSomethingProtected(){}
void doSomething(){}

I tutaj pojawia się pojęcie enkapsulacji, czyli hermetyzacji, która polega na zgrupowaniu danych i metod w obrębie jednej jednostki np. klasy.
Enkapsulacja pozwala również na ograniczenie dostepu do danych.

plik AccessModifiers.java

public class AccessModifiers {
public static void main(String[] args) {
//ustawiamy prywatne zmienne za pomocą setterów
Frog frog1 = new Frog();
frog1.setName("Bella");
frog1.setAge(2);
System.out.println("Żabcia " + frog1.getName() + " ma lat " + frog1.getAge());
}
}

plik Frog.java

public class Frog {
private String name;
private int age;

//jeżeli modyfikatory zmiennych będą private, nie będziemy mogli ich zmienić poza klasą,
//jedyne co możemy zrobić to skorzystać z setterów
public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}

 

Static i Final

Czasami istnieje potrzeba stworzenia zmiennej lub metody statycznej, czyli takiej, która nie jest przypisana do obiektu, a jedynie do klasy i może być wykorzystywana poza klasą.

Metody i właściwości statyczne to takie, które nie wymagają utworzenia obiektu, aby móc się do nich odwołać. Nadawane są dla całej klasy. Tworzymy je przy pomocy modyfikatora static.

plik Static_Final.java

public class Static_Final {
public static void main(String[] args) {

Thing something = new Thing();
something.name = "Itit";
something.showName();

//teraz wykorzystamy static
Thing.saluton = "Jestem statycznym powitaniem niezależnym od obiektów";
Thing.showInfo();
/*
zwróćmy uwagę, że nie tworzymy nowego obiektu, ani nie odwołujemy się do metody, czy zmiennej przez obiekt.
Podajemy nazwę klasy i po krropce wywołujemy wartości statyczne.
*/

something.showNameSaluton();

//STAŁE
System.out.println(Thing.LUCK_NR); //ta wartość jest niezmienna

//działanie statycznego licznika przedstawiam tutaj na dole, jednak należy pamiętać, że już 1 obiekt został stworzony wcześniej
System.out.println("Stan licznika przed utworzeniem dodatkowych obiektów " + Thing.count);

Thing something1 = new Thing();
Thing something2 = new Thing();
Thing something3 = new Thing();
System.out.println("Stan licznika po utworzeniu dodatkowych obiektów " + Thing.count);


}
}

plik Thing.java

public class Thing {
public String name; //publiczna zmienna
public static String saluton; //statyczna zmienna

public void showName(){
System.out.println(name);
}

public void showNameSaluton(){
System.out.println(saluton + " - " + name);
}

public static void showInfo() {
System.out.println(saluton);
//System.out.println(name); takie odwołanie wygenerujee błąd, pamiętajmy, że to jest stałe w klasie, a obiekty przyjmują inne wartości
}
/*
we wcześniejszych lekcjach korzystaliśmy ze stałych wbudowanych, np. liczby Pi z biblioteki Math
stałą możemy stworzyć sami, wystarczy użyć słówka "final"
Stała to właściwość klasy tylko do odczytu. Jak wiemy, stałe deklarujemy drukowanymi literami, są dostępne public i nie można ich modyfikować w kodzie.
*/
public final static int LUCK_NR = 7;


/*
o ile idea istnienia stałych nikogo nie dziwi o tyle właściwości i metody statyczne już tak. Bo po co? A na przykład dla licznika utworzonych obiektów
*/
//tworzę konstruktor, który za każdym razem, gdy zostanie utworzony nowy obiekt tej klasy doda 1 do licznika
public static int count = 0;
//teraz konstruktor,
public Thing(){
/*
możemy stan licznika przy każdym obiekcie dodać do zmiennej i będziemy mieli np. id konkretnego obiektu
*/
count++;
}
}

 

Dziedziczenie

Inheritance, czyli dziedziczenie. Tak jak w życiu – jako potomkowie dziedziczymy po naszych przodkach pewne cechy (tak naprawdę to 50% od matki i 50% od ojca), tak samo klasy dziedziczą pola i metody, ale mogą zostać uzupełnione o inne elementy, których przodek nie posiada.

Jeżeli klasa przodka posiada pola i metody, które nie są prywatne lub są chronione, to klasa potomna również będzie je posiadać. Aby klasa dziedziczyła po innej należy użyć słówka extends i podać nazwę klasy przodka:

public class Car extends Machine{}


Klasa może dziedziczyć tylko po jednej klasie (rozszerzać tylko jedną klasę bazową ).

plik Machine.java

public class Machine {
protected String type;

Machine(){
System.out.println("Wywołanie konstruktora z klasy nadrzędnej");
}

public void start(){
System.out.println("Maschine started.");
}
public void stop(){
System.out.println("Maschine stopped.");
}
}

plik Car.java

/*
chcemy aby nasza klasa dziedziczyła wszystko po klasie Maschine
należy użyc słówka "extends"
*/
public class Car extends Machine{
public void turn(){
System.out.println("Car turned.");
}
//możemy nadpisać klasę dziedziczoną w klasie potomnej
public void stop(){
System.out.println("Car stopped.");
}
/*
metody możemy nadpisywać automatycznie klikając (u mnie) Ctrl+o lub ALT+g lub wybierając z menu Code i "override methods"

@Override
public void start() {
super.start(); //tą część możemy wykasować i podmienić na swoją wersję
}
*/

//możemy wywołać konstruktor z klasy nadrzędnej
Car(){
super();
}
}

plik Inheritance.java

public class Inheritance {
public static void main(String[] args) {

Machine machine1 = new Machine();
machine1.start();
machine1.stop();

Car car1 = new Car(); //tutaj zostanie wywołany konstruktor z klasy nadrzędnej
car1.start(); //dostęp do odziedziczonej metody
car1.turn(); //nowa metoda, tylko dla klasy Car, Maschine nie ma tej funkcjonalności
car1.stop();//dostęp do odziedziczonej metody

/*
jeżeli nie chcemy, aby stworzona klasa była dziedziczona na początku wpisujemy "final", np. final class Example
*/

//jeżeli zajdzie potrzeba sprawdzenia jakiej klasy instancją jest dany obiekt - oto rozwiązanie
System.out.println(car1 instanceof Car); //true
System.out.println(machine1 instanceof Car); //false
}
}

 

Przykład:

plik App.java

public class App {
public static void main(String[] args) {
Car auto = new Car(270.0f);
auto.Info();
}
}

plik Car.java

public class Car extends Vehicle{
public Car(float topSpeed) {
//wywołujemy konstruktor klasy nadrzędnej
super("Auto", topSpeed);
}
}

plik Vehicle.java

public class Vehicle {
protected String type;
protected float topSpeed;

public Vehicle(){
type = "basic";
}

public Vehicle(String type, float topSpeed) {
this.type = type;
this.topSpeed = topSpeed;
}

protected void Info(){
System.out.println("Typ pojazdu " + this.type + ", max szybkość " + this.topSpeed);
}
}

Przykład:

plik App.java

public class App {
public static void main(String[] args) {
School t = new School(3, 316, "Technikum");
}
}

plik Building.java

public class Building {
public int numFloors;
public int numClass;

public Building(){
numFloors = 3;
}

public Building(int numFloors, int numClass) {
this.numFloors = numFloors;
this.numClass = numClass;
System.out.println("Budynek ma " + numFloors + " pięter oraz " + numClass + " sal lekcyjnych.");
}
}

plik School.java

public class School extends Building{
public String type = "Technikum";

public School(int numFloors, int numClass, String type) {
super(numFloors, numClass);
System.out.println("Szkoła ma " + numFloors + " pięter " + numClass + " klas i jest to " + type);
}
}

 

Polimorfizm

Polimorfizm to możliwość przybierania różnych form w zależności od wyboru podklasy.
Każdy obiekt klasy dziedziczącej jest tez obiektem klasy nadrzędnej.

plik Polymorphism.java

public class Polymorphism {
public static void main(String[] args) {
/*
Polimorfizm to możliwość przybierania różnych form w zależności od wyboru podklasy
Każdy obiekt klasy dziedziczącej jest tez obiektem klasy nadrzędnej.
*/

Vehicle newCar = new Car("Auto");//nie ma dostępu do maxSpeed
System.out.println(newCar.name);

Vehicle newBicycle = new Bicycle("Rower"); //ma dostęp tylko do name
System.out.println(newBicycle.name);

//poniższy zapis jest nieprawidłowy
//Car test = new Vehicle("test");

//tłumacząc prościej - każde auto jest pojazdem, ale nie każdy pojaz jest autem
}
}

plik Vehicle.java

public class Vehicle {
public String name;

public Vehicle(String name) {
this.name = name;
}
}

plik Car.java

public class Car extends Vehicle{
public int maxSpeed;

public Car(String name) {
super(name);
}
}

plik Bicycle.java

public class Bicycle extends Car{
int numWheels;
public Bicycle(String name){
super(name);
}
}

 

Przykład:

plik Company.java

public class Company {
public static void main(String[] args) {
Employee employee = new Employee("Kama", "Programmer", 10000);
Manager manager = new Manager("Jan", "Team Leader", 11000, "R&D");
Ceo ceo = new Ceo("Rafał", "Prezes", 20000, "Board", 100);

Employee worker = ceo; //każdy ceo jest employee i ma dostęp do metod i własności tej klasy
worker.doWork();
Manager person = ceo;
//wpisując nazwę obiektu po kropce możemy zobaczyć dostęp do metod i właściwości
}
}

plik Employee.java

public class Employee {
private String name;
private String jobTitle;
private int salary;

public Employee(){
name = "unknown";
jobTitle = "unknown";
salary = 2500;
}

public Employee(String name, String jobTitle, int salary) {
this.name = name;
this.jobTitle = jobTitle;
this.salary = salary;
}

public void doWork(){
System.out.println("Working!");
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getJobTitle() {
return jobTitle;
}

public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}

public int getSalary() {
return salary;
}

public void setSalary(int salary) {
this.salary = salary;
}
}

plik Manager.java

public class Manager extends Employee{
private String departmentName;

public Manager() {
super();
departmentName = "unknown";
}

public Manager(String name, String jobTitle, int salary, String departmentName) {
super(name, jobTitle, salary);
this.departmentName = departmentName;
}

public void hireEmployee(){
System.out.println("Employee hired!");
}
public void giveRise(Employee employee){
System.out.println("Employee got rise!");
}

public String getDepartmentName() {
return departmentName;
}

public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
}

plik Ceo.java

public class Ceo extends Manager{
private int sharesNumber;

public Ceo() {
sharesNumber = 100;
}

public Ceo(String name, String jobTitle, int salary, String departmentName, int sharesNumber) {
super(name, jobTitle, salary, departmentName);
this.sharesNumber = sharesNumber;
}

public void signContract(){
System.out.println("Contract signed!");
}

public int getSharesNumber() {
return sharesNumber;
}

public void setSharesNumber(int sharesNumber) {
this.sharesNumber = sharesNumber;
}
}

 

Przykład:

plik PolymorphismCasting.java

public class PolymorphismCasting {
public static void main(String[] args) {
Employee employee[] = new Employee[10];
employee[0] = new Manager("Dyrektor", 10000); //typ Manager
employee[0].printName();
employee[1] = new Employee("Programista", 12000);
employee[1].printName();

/*
w takiej sytuacji jeżeli chcemy, aby dyrektor mógł dać podwyżkę to musimy
rzutowac jego typ na Managera, bo typ employee nie posiada takiej metody
*/

Manager manager = (Manager) employee[0];
manager.giveRise(employee[1], 0.15f);
employee[1].printName();
}
}

plik Employee.java

public class Employee {
protected int salary;
protected String name;
public Employee(String name, int salary){
this.name = name;
this.salary = salary;
}
public void printName(){
System.out.println("Employee: " + name + ", salary: " + salary);
}
}

plik Manager.java

public class Manager extends Employee{
public Manager(String name, int salary) {
super(name, salary);
}
public void printName(){
System.out.println("Manager: " + name + ", salary: " + salary);
}
public void giveRise(Employee e, float percent){
e.salary+=(int)(e.salary*percent);
}
}

 

Klasy i metody abstrakcyjne oraz interfejsy

Klasa abstrakcyjna definiuje pewien model i zachowanie obiektu.
Taka klasa nie posiada definicji tych metod, jedynie ich deklaracje, że gdzieś dalej w kodzie muszą się pojawić. Czyli do czasu rozszerzenia ich o funkcjonalności w klasach dziedziczących, te metody nie istnieją (są abstrakcją).
Metody abstrakcyjne definiujemy w klasie bazowej klauzulą abstract. Wszystkie tego typu zadeklarowane metody będą musiały pojawić się w klasach dziedziczących.
Nie można utworzyć obiektu klasy abstrakcyjnej. Klasa ta jest jedynie „bazą” dla klas potomnych.
Można przypisać podklasę do zmiennej typu abstrakcyjnego.
Pamiętamy, że klasę dziedziczymy i możemy zrobić to tylko raz.

 

Interfejs jest zbiorem metod jakie wymagamy by posiadała klasa, która go implementuje.
Możemy implementować wiele interfejsów dla jednej klasy.
Wszystkie metody określane w interfejsie są abstrakcyjne.

Przykład:

plik AbstractClassesAndInterfaces.java

public class AbstractClassesAndInterfaces {
public static void main(String[] args) {

//CelestialBody earth = new CelestialBody();
//powyższe wywołanie generuje błąd, bo nie ma czegoś fizycznego co się nazywa ciało niebieskie. To tylko pojęcie określające różne elementy
Planet earth = new Planet();
earth.setName("Ziemia");
System.out.println(earth.getName());
earth.round();

Star sun = new Star();
sun.round();

earth.showInfo();
}
}

plik CelestialBody.java

public abstract class CelestialBody {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

//tworzymy nazwę metody, którą musi mieć każda klasa dziedzicząca metodę abstrakcyjną
public abstract void round();


}

plik Info.java

public interface Info {
public void showInfo();
}

plik Planet.java

public class Planet extends CelestialBody implements Info {
@Override
public void round() {
System.out.println("jestem planetą - kręce się");
}

@Override
public void showInfo() {
System.out.println("Implementuję metodę wspólną dla obu klas");
}
}

plik Star.java

public class Star extends CelestialBody implements Info {

@Override
public void round() {
System.out.println("Jestem gwiazdą - nie kręcę się");
}

@Override
public void showInfo() {
System.out.println("Implementuję metodę wspólną dla obu klas");
}
}

 

Różnice między interfejsem, a klasą abstrakcyjną

  • klasa może implementować wiele interfejsów, ale rozszerzać tylko jednego rodzica
  • metody w interfejsie są publiczne, w klasie publiczne lub chronione
  • interfejs może zawierać tylko deklaracje metod, a klasa też metody zdefiniowane
  • klasy abstrakcyjne mogą zawierać atrybuty(pola), interfejsy nie
  • w interfejsach wszystkie metody są abstrakcyjne, natomiast w klasie abstrakcyjnej można stworzyć metody posiadające ciało, jak i abstrakcyjne
  • klasa abstrakcyjna zazwyczaj jest ściśle związana z klasami dziedziczącymi w sensie logicznym, czyli np. tworzymy klasę abstrakcyjną Planeta po której dziedziczą konkretne klasy planet (np. Ziemia, Mars).
  • Interfejs natomiast nie musi być już tak mocno związany z daną klasą, on określa jej cechy, np możesz stworzyć interfejs Zniszczalny, który mówi że dany obiekt może zostać zniszczony. Taki interfejs możesz nadać zarówno klasą Planeta, Gwiazda, Budynek itp.
  • interfejs może dziedziczyć jedynie po innych interfejsach, a klasa abstrakcyjna może dziedziczyć po klasach abstrakcyjnych, interfejsach a nawet zwykłych klasach
  • interfejs nie może mieć konstruktora, w klasie abstrakcyjnej możemy dostarczyć implementację konstruktora domyślnego

 

Przykład:

plik Interface.java

public class Interface {
public static void main(String[] args) {
Car car = new Car();
car.move();

// możemy zapimplementować obiekt tak:
// Vehicle vehicle = new Car();
//lub tak:
Vehicle vehicle = car;
System.out.println(vehicle.speed());

Vehicle car2 = new Car();
car2.turn();

Car car3 = (Car) car2; //rzutowanie
car3.stop();

Train train = new Train();
Vehicle trainVehicle = train;
System.out.println(trainVehicle.speed());

Plain plain = new Plain();
plain.increaseHeight();

Vehicle vehicles[] = new Vehicle[4];
vehicles[0] = car;
vehicles[1] = car2;
vehicles[2] = train;
vehicles[3] = plain;

vehicles[2].move();

if (vehicles[2] instanceof Train){
Train someTrain = (Train) vehicles[2];
someTrain.turn();
}

if (vehicles[3] instanceof Plain){
Plain somePlain = (Plain) vehicles[3];
somePlain.decreaseHeight();

Flying flyingVehicle = somePlain;
flyingVehicle.increaseHeight();
}
}
}

plik Flying.java

public interface Flying {
void increaseHeight();
void decreaseHeight();
}

plik Vehicle.java

public interface Vehicle {
void move(); // nie musimu podawać modyfikatora public
void stop();
void turn();
float speed();
}

plik Car.java

public class Car implements Vehicle{
@Override
public void move() {
System.out.println("Car is moving!");
}

@Override
public void stop() {
System.out.println("Car stopped!");
}

@Override
public void turn() {
System.out.println("Car is turning!");
}

@Override
public float speed() {
return 220;
}
}

plik Plain.java

//wiele interfejsów
public class Plain implements Vehicle, Flying{
@Override
public void move() {
System.out.println("Plain is moving!");
}

@Override
public void stop() {
System.out.println("Plain stopped!");
}

@Override
public void turn() {
System.out.println("Plain turn!");
}

@Override
public float speed() {
return 900;
}

//implementujemy metody z obu interfejsów

@Override
public void increaseHeight() {
System.out.println("Plain up!");
}

@Override
public void decreaseHeight() {
System.out.println("Plain down!");
}
}

plik Train.java

public class Train implements Vehicle{
@Override
public void move() {
System.out.println("Train is moving!");
}

@Override
public void stop() {
System.out.println("Train stopped!");
}

@Override
public void turn() {
System.out.println("Train turn!");
}

@Override
public float speed() {
return 180;
}
}

 

Przykład:

plik interface_extends_default_static_final.java

public class Train implements Vehicle{
@Override
public void move() {
System.out.println("Train is moving!");
}

@Override
public void stop() {
System.out.println("Train stopped!");
}

@Override
public void turn() {
System.out.println("Train turn!");
}

@Override
public float speed() {
return 180;
}
}

plik Animal.java

public interface Animal {
int getNumLegs();
String getName();
}

plik Bird.java

//rozszerzamy wiele interfejsów
public interface Bird extends Animal,Eating,Flying{

}

plik Eating.java

public interface Eating {
void eat();
default void searchForFood(){
System.out.println("Searching for food.");
}
}

plik Flying.java

public interface Flying {
//możemy dodać zmienną statyczną
static final float DEFAULT_WINGSPAN = 0.7f;
void fly();

default void flyHigher(){
System.out.println("Flying higher.");
}

default float getWingspan(){
return Flying.DEFAULT_WINGSPAN;
}

static int getDefaultNumWings(){
return 2;
}
}

plik Parrot.java

public class Parrot implements Bird{
@Override
public int getNumLegs() {
return 2;
}

@Override
public String getName() {
return "Parrot";
}

@Override
public void eat() {
System.out.println("Eating.");
}

@Override
public void fly() {
System.out.println("Parrot is flying.");
}

//możemy nadpisac metodę domyslną, jeżeli np nam się nie podoba

@Override
public void searchForFood() {
System.out.println("Parrot is searching for food.");
}
}

 

Przykład:

plik MouseListenerExample.java

import javax.swing.*;

public class MouseListenerExample {
public static void main(String[] args) {
Frame frame = new Frame();
frame.setSize(300,300); //wielkość okienka
frame.setTitle("Przykład"); //tytuł okienka
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//co ma zrobić aplikacja jeżeli użytkownik będzie chciał ją zamknąć
frame.setLayout(null);//wymuszamy położenie na taką pozycję jak my ustawiliśmy
frame.setVisible(true);//pokazujemy okienko
frame.addMouseListener(frame);
}
}

plik Frame.java

//korzystamy z wbudowanej klasy JFrame oraz wbudowanego interfejsu MouseListener
import javax.swing.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class Frame extends JFrame implements MouseListener {

//jeżeli nie chcemy uzyskiwać wyników kliknięć w konsoli, tylko w okienku musimy dodać JLable
private JLabel label;
public Frame(){
label = new JLabel("---");
label.setBounds(10,10,250,50);//określamy wielkość pola
this.add(label);
}

//alt+g i implementujemy wszystkie metody
@Override
public void mouseClicked(MouseEvent e) {
//pobieramy współrzędne kliknięcia w okienku
int x = e.getX();
int y = e.getY();

String str = "Kliknięto x: "+x+" y: "+y;
label.setText(str);

System.out.println(str);
}

@Override
public void mousePressed(MouseEvent e) {

}

@Override
public void mouseReleased(MouseEvent e) {

}

@Override
public void mouseEntered(MouseEvent e) {

}

@Override
public void mouseExited(MouseEvent e) {

}
}

 

Klasy wewnetrzne

W java istnieje opcja zadeklarowania klasy wewnętrznej, czyli klasy w klasie. Istnieje kilka opcji:
– klasa prywatna wewnątrz klasy może być używana tylko w niej
– klasa publiczna może zostać zainicjowana po podaniu nazwy klasy głównej, a następnie po kropce klasy wewnętrznej

 

Przykład

plik ClassPrvPub.java

public class ClassPrvPub {
//wewnętrzna prywatna klasa
private class PrivCls{
public void printInfo(){
System.out.println("Jestem z prywatnej wewnętrznej klasy.");
}
}

//wewnętrzna prywatna klasa
public class PubCls{
public void printInfo(){
System.out.println("Jestem z publicznej wewnętrznej klasy.");
}
}

//publiczna metoda wykorzystująca wewnętzną prywatną klasę
public void run(){
PrivCls prvCls = new PrivCls();
prvCls.printInfo();

//możemy zadeklarować klasę wewnątrz metody
class ClassInMethod{
public void printInfo(){
System.out.println("Klasa wewnątrz metody");
}
}
ClassInMethod innerClass = new ClassInMethod();
innerClass.printInfo();
}
}

plik InnerClass.java

public class InnerClass {
public static void main(String[] args) {
//tworzymy obiekt klasy ClassPrvPub
ClassPrvPub privExample = new ClassPrvPub();
//wywołujemy metodę
privExample.run();

//tworzymy obiekt publicznej klasy
ClassPrvPub.PubCls pubClass = privExample.new PubCls();
pubClass.printInfo();
}
}

 

Klasa Object

Klasa Object jest bazową klasą Javy, każdy nowo tworzony obiekt rozszerza klasę Object.
Klasa ta posiada wiele przydatnych metod, część z nich juz poznaliście np. equals, to String itp.

Przykład

plik CarObject.java

public class CarObject {
private String manufacturer;
private String model;
private String color;

//dodajemy konstruktor Alt+g lub Alt+Insert lub...
public CarObject(String manufacturer, String model, String color) {
this.manufacturer = manufacturer;
this.model = model;
this.color = color;
}

//przesłonimy teraz metodę toString, żeby wyświetlała więcej czytelnych informacji
//Alt+g i wybieramy toString
/*
@Override
public String toString() {
return "CarObject{" +
"manufacturer='" + manufacturer + '\'' +
", model='" + model + '\'' +
", color='" + color + '\'' +
'}';
}*/
}

plik ObjectExample.java

//jak przesłonić metodę toString
public class ObjectExample {
public static void main(String[] args) {
CarObject car = new CarObject("Ford", "Focus","blue");
CarObject car2 = new CarObject("Peugeot", "307SW", "blue");
System.out.println(car); //zostanie wykorzystana automatycznie metoda toString i wyświetli: all.ObjectClass.CarObject@378bf509, po przesłonięciu metody otrzymamy bardziej czytelne informacje
System.out.println(car.hashCode());//unikalna liczba na bazie CarObject

//sprawdzimy czy obiekty wskazują na to samo miejsce w pamięci
if(car.equals(car2)){
System.out.println("Obiekty wskazują na tą samą referencję, czyli miejsce w pamięci.");
} else{
System.out.println("Obiekty nie wskazują na tą samą referencję");
}
}
}

 

Zadanie1:

Napisz program sortujący tablicę metodą przez wybieranie zgodnie z poniższymi wytycznymi:

Sortowanie przez wybieranie – jedna z prostszych metod sortowania o złożoności O(n2). Polega na wyszukaniu elementu mającego się znaleźć na żądanej pozycji i zamianie miejscami z tym, który jest tam obecnie. Operacja jest wykonywana dla wszystkich indeksów sortowanej tablicy.

Algorytm przedstawia się następująco:

  1. wyszukaj minimalną wartość z tablicy spośród elementów od i do końca tablicy
  2. zamień wartość minimalną, z elementem na pozycji i

Gdy zamiast wartości minimalnej wybierana będzie maksymalna, wówczas tablica będzie posortowana od największego do najmniejszego elementu.

Założenia do programu:

  • program wykonywany w konsoli
  • sortowanie odbywa się malejąco, nie wykorzystuje gotowych funkcji do sortowania oraz do szukania maksimum
  • sortowana jest tablica 10 liczb całkowitych. Tablica jest polem klasy.
  • tablica jest wczytywana z klawiatury po uprzednim wypisaniu odpowiedniego komunikatu
  • wszystkie elementy posortowanej tablicy są wyświetlane na ekranie
  • klasa zawiera co najmniej dwie metody: sortującą i szukającą wartość najwyższą. Widzialność metody szukającej ogranicza się jedynie do klasy.
  • metoda szukająca zwraca wartość, w zależności od przyjętej taktyki może być to wartość maksymalna lub index wartości maksymalnej.
  • program powinien być zapisany czytelnie, z zasadami czystego formatowania kodu, należy stosować znaczące nazwy zmiennych i funkcji.

 

Zadanie2:

Napisz program, który szyfruje podany przez użytkownika tekst szyfrem podstawieniowym zwanym GADERYPOLUKI. Klucz ten zawiera pary zamienników: GADERYPOLUKI. Pierwsza litera w parze jest zastępowana na drugą, druga na pierwszą. Dla przykładu litera G zastępowana jest literą A, litera A literą G. Litery, których nie ma w kluczu (inne niż GADERYPOLUKI) pozostają bez zmian. Np. słowo PROGRAM po zaszyfrowaniu brzmi OYPAYGM, bo:
P
R O G R A M
O
Y P A Y G M
Założenia programu:

  • Program wykonywany w konsoli
  • Jeżeli język programowania tego wymaga, można założyć dla uproszczenia, że tekst do zaszyfrowania ma
    maksymalnie 20 liter i zapisany jest małymi literami oraz nie ma w nim innych znaków, cyfr, spacji
  • Program zawiera funkcję szyfrującą, która przyjmuje jako argument wprowadzony tekst
  • Funkcja zwraca zaszyfrowany tekst
  • W programie głównym występuje wczytanie tekstu z klawiatury po uprzednim wyświetleniu stosownego komunikatu dla użytkownika, a po zaszyfrowaniu wyświetlenie zaszyfrowanej jego wersji 
  • Program powinien być zapisany czytelnie, z zasadami czystego formatowania kodu, należy stosować znaczące nazwy zmiennych i funkcji