this –
pozwala odwołać się wewnątrz metody do
‘adresata’ tej metody.
Dziedziczenie Klas
Dziedziczenie (inheritance)
klas pozwala dostosować istniejącą już klasę do własnych potrzeb.
Własność ta jest jedną z największych zalet języków obiektowych.
Załóżmy, że istnieje już pewna klasa, która posiada
prawie komplet wymaganych przez nas własności. Zamiast tworzyć nową
klasę od początku, używamy dziedziczenia aby dodać do istniejącej klasy
brakujące cechy.
syntaktyka
dziedziczenia: derived-class extends base-class
Przykład 1
Załóżmy, że chcemy napisać program przechowujący
informację o studentach: nazwisko, wiek, PESEL, kierunek studiów
i średnia ocen. Chcemy wykorzystać istniejącą już klasę Person reprezentującą ludzi,
która zawiera informacje takie jak: nazwisko, wiek i PESEL.
public class
Person{
// klasa bazowa
private String
nazwisko;
private int wiek;
private String
pesel;
public
Person (String n, int w, String p){
nazwisko=n;
wiek=w;
pesel=p;
}
public void printPerson(){
System.out.println("Nazwisko: "+nazwisko);
System.out.println("Wiek: "+wiek);
System.out.println("PESEL: "+pesel);
}
}
public class Student extends
Person{ // klasa
pochodna (dziedzicząca)
private String kierunek;
private double srednia;
public Student(String n, int w, String
p, String k, double av){
super(n,w,p); // konstruktor klasy bazowej (Person)
kierunek=k;
srednia=av;
}
public void studentInfo(){
printPerson(); // metoda z klasy bazowej;
System.out.println("Kierunek: "+kierunek);
System.out.println("Średnia: "+srednia);
}
}
Przykład 2
Chcemy rozszerzyć klasę Time
, aby uwzględniała sekundy.
Klasa bazowa
class Time{
private
int hour,minute; // pola obiektu są dostępne tylko wewnątrz klasy
Time
public Time (int
h, int m){hour=h;minute=m;}
// Konstruktor obiektu
public void advanceMinutes(int m)
{ // modyfikacja - metoda typu void
int
totalMinutes=(60*hour+minute+m)%(24*60);
if(totalMinutes<0)
totalMinutes=totalMinutes+24*60;
hour=totalMinutes/60;
minute=totalMinutes%60);
}
public void printTime(){
if ((hour == 0)
&& (minute == 0))
System.out.print("midnight");
else if ((hour
== 12) && (minute == 0))
System.out.print("noon");
else {
if (hour==0) System.out.print(12);
else
if (hour>12) System.out.print(hour-12);
else
System.out.print(hour);
if(minute<10) System.out.print(":0"+minute);
else
System.out.print(":"+minute);
if(hour<12) System.out.print("AM");
else
System.out.print("PM");
}
}
}
Klasa pochodna
class PreciseTime extends Time{
private int
second;
public PreciseTime(int h, int m, int s){
super(h,m); // wywołanie konstruktora klasy Time
second = s;
}
public void advanceSeconds(int s){
int advMinutes = s/60;
second+=s%60;
if(second<0){
advMinutes--;
second+=60;
}
else if (second>=60){
advMinutes++;
second-=60;
}
advanceMinutes(advMinutes);
}
}
Wykorzystanie
public class Tme1Mutate{
public static void main(String [] args){
PreciseTime lunchtime
= new PreciseTime(13,10,0);
lunchtime.advanceSeconds(60);
lunchtime.printTime();System.out.println();
lunchtime.advanceSeconds(-61);
lunchtime.printTime();System.out.println();
}
}
Wydruk:
1:11PM
1:09PM
printTime na obiektach klasy
PreciseTime działa dokładnie
tak
samo jak obiektach klasy Time:
wypisuje godziny i minuty.
Załóżmy, że w klasie PreciseTime
chcemy zdefiniować nową metodę
printTime:
public void
printTime(){
if ((hour == 0)
&& (minute == 0))
System.out.print("midnight");
else if ((hour
== 12) && (minute == 0))
System.out.print("noon");
else {
if (hour==0) System.out.print(12);
else
if (hour>12) System.out.print(hour-12);
else
System.out.print(hour);
if(minute<10) System.out.print(":0"+minute);
else
System.out.print(":"+minute);
if(second<10) System.out.print(":0"+second);
else
System.out.print(":"+second);
if(hour<12) System.out.print("AM");
else
System.out.print("PM");
}
}
Metoda ta nie zadziała. Z klasy
PreciseTime nie mamy dostępu do prywatnych pól klasy Time. Należy w klasie Time zmienić specyfikator private na protected:
class Time{
protected
int hour,minute;
....
Specyfikator protected udostępnia
dane pole klasom pochodnym ale nie klientom.
Dynamiczne
wiązanie (Dynamic binding)
Zarówno
w klasie Time jak i w klasie
PreciseTime mamy
zdefiniowane metody PrintTime.
Rozważmy konstrukcję:
Time dawn;
dawn = new PreciseTime(3,45,30);
dawn.PrintTime();
Pytanie: Z
której klasy zostanie wywołana metoda PrintTime ?
Obiekt dawn jest klasy Time, ale zawiera referencję do
klasy PreciseTime.
Odpowiedź: z klasy PreciseTime. Wywołana metoda
zależy od klasy słuchacza ( receiver)
metody.
Zwykle, to która metoda będzie wywołana można wyznaczyć bez
uruchamiania programu (w fazie kompilacji). Jest to tzw. wiązanie
statyczne. Jednakże, gdy metoda jest przedefiniowana w podklasie, to
reguła ta nie jest spełniona i w Javie stosuje się tzw. wiązanie
dynamiczne.
Załóżmy, że istnieją dwie podklasy: PreciseTime i PreciseTime1 ;
Time dawn;
if(arg[0].equals("A")) dawn= new PreciseTime(3,45,30); // arg[0] -
pierwszy parametr
else dawn= new
PreciseTime1(4,25,50);
dawn.PrintTime();
Jest oczywiste, że w tym przypadku wyznaczenie klasy słuchacza może się
odbyć wyłącznie w fazie uruchamiania programu.
Interfejsy
Wielodziedziczenie może niekiedy prowadzić do pewnych
niejednoznaczności np. dotyczących przynależności pól.
class A {
int a;
........
}
class B extends A { // ma pole a
........
}
class C extends A { // ma pole a
........
}
class D extends B i C { //
hipotetyczne wielodziedziczenie
........
}
Obiekt d z klasy D ma
pole a. Jedno czy dwa? Jeżeli tak to które? Co oznacza odwołanie
d.a?
W Javie zrezygnowano z koncepcji wielodziedziczenia. Zamiast tego
wprowadzono pojęcie interfejsu jako klasy nie zawierającej pól,
a tylko metody (publiczne i abstrakcyjne) i ewentualnie stałe statyczne.
Interfejs daje
więc możliwość definiowania zachowania
się obiektów bez określania jak to zachowanie będzie
implementowane.
Przykład Interfejs Shape
Interfejs ten zawiera dwie metody typowe dla obiektów
posiadających kształt: area i
circumference:
public interface Shape{
public double area();
public double circumference();
public static final double PI = 3.14159;
}
Dwa przykładowe kształty, które mogą implementować ten
interfejs to Circle i Rectangle. Aby klasa mogła
implementować intefejs, musi ona specyfikować jego metody.
public class Circle implements Shape{
private double radius;
public
Circle (double r) {radius = r;}
public
double area(){
return PI*radius*radius;
}
public double circumference(){
return 2*PI*radius;
}
}
public class Rectangle implements Shape{
private double height;
private double width;
public
Rectangle (double h, double w) {height=h; width=w}
public
double area(){
return height*width;
}
public double circumference(){
return 2*(height+width);
}
}
Wykorzystanie
public class TestShapes{
public static void display(Shape figure){
System.out.println("The area is "+figure.area());
System.out.println("The circumference is "+figure.circumference());
}
public static void main(String args[]){
Shape figOne =
new Circle(3.5);
display(figOne);
Shape
figOne = new Rectangle(3.2,5.1);
display(figOne);
}
}
- W tym przykładzie figOne
jest referencją polimorficzną.
- Metody interfejsu domyślnie są traktowane jako typ abstract (tzn. są niezdefiniowane).
- W klasach abstrakcyjnych część metod może być zdefiniowana a część
nie.
- Implementacja interfejsów odgrywa ważną rolę w Javie: obsługa
zdarzeń, tworzenie wątków,...
Obsługa
zdarzeń i komponenty AWT
Słuchacz to obiekt,
który może obsługiwać zdarzenia (wciśnięcie guzika, przesunięcie
myszki lub suwaka, wpisanie tekstu, itp.). Aby móc generować
obiekty-Słuchaczy, klasa musi implementować interfejs nasłuchu. Tzn.,
metody interfejsu nasłuchu muszą zyskać konkretne definicje.
Zdarzenia przytrafiają się
obiektom. Obiekt, któremu przytrafiło sie zdarzenie nazywany
jest źródłem (source).
Zdarzenie do obsługi przekazywane
jest ze źródła do słuchacza tylko wtedy, gdy do danego
źródła
przyłączony jest słuchacz reagujący na ten typ zdarzenia. Przyłączenia
dokonujemy
za pomocą odwołania:
z.addxxxListener(l);
gdzie z - źródło , xxx - rodzaj
nasłuchiwanych zdarzeń ( Action,
Item,Adjustement, MouseMotion), l - słuchacz.
ActionEvent
to klasa zdarzeń oznaczających wykonanie akcji. Słuchacz akcji (obiekt
klasy, która ma ją obsługiwać) musi implementować interfejs ActionListener i zdefiniować
metodę actionPerformed
tego interfejsu.
AWT - Abstract Windowing Toolkit
Jest to zbiór klas zapewniających tworzenie prostych
elementów graficznego interfejsu użytkownika ( applet, Label, Button, TextField, ...).
Znajdują się one w pakiecie java.awt
(wymagany import).
Konwersja
temperatury - Aplet
import java.awt.*;
import
java.applet.*;
import java.awt.event.*;
public class TempApplet1 extends Applet implements ActionListener {
TextField tFahr;
Label lCent;
public void init(){
tFahr=new TextField(10);
lCent=new Label ("I'll tell you
what that is in degrees C");
add (new Label("Please type the
temperature (deg F)"));
add(tFahr);
add(lCent);
tFahr.addActionListener(this);
}
public void actionPerformed(ActionEvent e){
int fahr=0,
cent=0;
fahr=Integer.parseInt(tFahr.getText());
cent=(int)(5.0*(fahr-32)/9.0);
lCent.setText(fahr+" deg F is "+cent+" deg C");
}
}
Dwustronna
konwersja temperatury - Aplet
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class TempApplet2 extends Applet implements ActionListener {
TextField tFahr=new TextField(9),
tCent=new TextField(9);
public void init(){
add (new Label("Fahrenheit"));
add(tFahr);
add(new
Label("Centigrade"));
add(tCent);
tFahr.addActionListener(this);
tCent.addActionListener(this);
}
public void actionPerformed(ActionEvent e){
int fahr,cent;
if(e.getSource()==tFahr){
// rozróżnienie źródła akcji
fahr=Integer.parseInt(tFahr.getText());
cent=(int)(5.0*(fahr-32)/9.0);
tCent.setText(cent+"");
}
else{
cent=Integer.parseInt(tCent.getText());
fahr=(int)(9.0*cent/5.0+32.0);
tFahr.setText(fahr+"");
}
}
}
Podobna obsługa zdarzeń jest możliwa w klasie
Button
StartStop-PrawoLewo - Aplet
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class startstop extends Applet
implements ActionListener {
Button b1 = new
Button("Start");
Button b2 =new
Button("Prawo");
Label la;
public void
init(){
la=new Label("Nacisnij przycisk");
add(la);
add(b1);
add(b2);
b1.addActionListener(this);
b2.addActionListener(this);
}
public void
actionPerformed(ActionEvent e){
String txt;
if(e.getSource()==b1){
if(b1.getLabel()=="Stop"){
b1.setLabel("Start");
}
else{
b1.setLabel("Stop");
}
la.setText("nacisnales 1");
}
else{
if(b2.getLabel()=="Prawo"){
b2.setLabel("Lewo");
}
else{
b2.setLabel("Prawo");
}
la.setText("nacisnales 2");
}
}
}
Button i TextField to przykłady tzw.
komponentów. Inne komponenty to np. MenuItem, Choice, Checkbox, Scrollbar. Komponenty
te można związać ze słuchaczami i metodami je obsługującymi.
Button
MenuItem
TextField
|
ActionListener
|
actionPerformed(ActionEvent e)
|
Choice
Checkbox
CheckboxMenuItem
|
ItemListener
|
itemStateChanged(ItemEvent e)
|
Scrollbar
|
AdjustementListener
|
adjustementValueChanged(AdjustementEvent
e)
|
Metody obsługi zdarzeń zwracają wartość void.
Metoda getSource() służy do
rozróżnienia źródła zdarzenia:
public void actionPerformed(ActionEvent
e){
....
if (e.getSource() ==
TextField name) {...}
....
if (e.getSource() ==
Button name) {...}
....
}
Analogicznie obsługujemy zdarzenia np. dla Checkbox. Interface ItemListener wymaga zdefiniowania
metody itemStateChanged:
public void
itemStateChanged (ItemEvent e){
....
if(e.getSource() == Checkbox name)
{...}
....
}
Klasa może implementować kilku słuchaczy:
public class My_applet extends Applet implements ItemListener,
ActionListener {
.....
}
Komponenty
Komponenty wywodzą się z abstrakcyjnej klasy Component, która definiuje
metody m.in. ustalające właściwości komponentów.Istnieją dwa
rodzaje komponentów: kontenery (mogące zawierać inne komponenty)
i komponenty terminalne.
Ogólne właściwości
komponentów:
Rozmiar i położenie - zwykle
ustalane przez zarządcę rozkładu (Layout
Manager )
Pismo - obiekt klasy Font. Konstruktor Font (nazwa_pisma, styl, rozmiar)
nazwa_pisma = "Dialog",
"DialogInput","Serif", "SansSerif",...
styl = Font.BOLD, Font.ITALIC,Font.PLAIN
rozmiar = liczba całkowita (wielkość w punktach)
Button b = new Button ("Napis");
b.setFont(new Font("Serif", Font.BOLD, 16));
Kolor - obiekt klasy Color.
b.setBackground(Color.red);
b.setForeground(new Color(0,15,245));
// (0,0,0) - czarny, (255,255,255) - biały, w ogólności:(red,
green, blue)
Zablokowanie/odblokowanie
b.setEnabled(false); // zablokowanie (nie reaguje na naciśnięcie
guzika)
b.setEnabled(true); // odblokowanie
Uwidacznianie - komponenty są domyślnie
widoczne. W trakcie działania programu
można je jednak uczynić niewidocznymi.
b.setVisible(false); // stanie się
niewidocznym
if (!b.isVisible()) b.setVisible(true); // jeżeli niewidoczny to
uwidaczniamy
Kontenery
Kontenery to komponenty, ktore moga zawierać inne komponenty
(niekoniecznie terminalne).
Przykład
Button b1 = new Button
("Napis1");
Button b2 = new
Button ("Napis2");
Panel p = new Panel();
p.add(b1);
p.add(b2);
Specjalizowany
słuchacz akcji
Można zdefiniować słuchacza akcji jako obiekt odrębnej klasy.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
class SpecSluch implements ActionListener{
Button bc;
int i=0;
SpecSluch(Button b){bc=b;}
public void actionPerformed(ActionEvent e){
i++;
bc.setLabel(""+i);
}
}
public class test_SpecSluch extends Applet {
Button b1 = new Button("guzik1");
Button b2 =new Button("guzik2");
public void
init(){
add(b1);
add(b2);
b1.addActionListener(new SpecSluch(b1));
b2.addActionListener(new SpecSluch(b2));
}
}
Dzięki takiej separacji
upraszcza się metoda actionPerformed.
Działanie
Zarządzanie wyglądem
ekranu
Rozmieszczeniem
komponentów wewnątrz apletu lub kontenera zarządza Layout Manager.
Domyślnie używany jest FlowLayout manager, tzn.
komponenty dodawane są w kolejności pojawiania się (add(...)), od strony lewej do
prawej, od góry do dołu.
Inne możliwości to:
GridLayout(m,n) - dzieli ekran (aplet lub kontener) na m
wierszy i n kolumn.
BorderLayout() - ekran jest podzielony na 5 części: "North", "South",
"East", "West" i "Center".
Dodawanie komponentu w tym przypadku odbywa się za pomocą
dwuargumentowej metody, np.
add("North", new Label("The
Title"));
Zmianę sposobu rozmieszczenia osiągamy za pomocą komendy setLayout(...). Na przykład:
setLayout(new BorderLayout());
Aplet StartStop-PrawyLewy z użyciem BorderLayout może wyglądać tak .
Można rownież zarządać brak menadżera rozkładu: kontener.setLayout(null). W takim
przypadku rozmiary i umiejscowienie komponentów musimy ustalić
samodzielnie. Np . za pomocą
metody setBounds(x,y,w,h), x,y -
współrzędne położenia , w - szerokość, h - wysokość .