Shape
tidigare:
public class Shape { public Shape( int ix, int iy, int isize, boolean ifill) { x = ix; y = iy; fill = ifill; } protected int x; protected int y; protected boolean fill; public void moveRel( int dx, int dy ) { x += dx; y += dy; } public void print() { System.out.println("pos " + x "," + y); } } public class Circle extends Shape { public Circle( int ix, int iy, int r, boolean ifill) { super(ix, iy, r, ifill); radius = r; } protected int radius; public void print() { System.out.println("Cirkel, pos " + x "," + y); } }Klassen
Shape
definierar en position samt huruvida figurens
innanmäte skall fyllas eller ej, dess arvinge Circle
innehåller
dessutom cirkelns storlek. Man kan säga att klassen Circle
utökar (extends) Shape
och nyckelordet
extends
används också för att deklerara en arvinge. Attributen i
Shape
deklareras protected
vilket innebär att dess
arvingar men inte andra kan komma åt dem.
Vid arv kedjas konstruktorerna, d.v.s. Shape
s
konstruktor anropas vid konstruktion av Circle
-objekt. Om man
vill anropa en annan konstruktor än en utan argument måste detta göras
explicit: referensen super
betyder en referens till den del av
klassen som utgörs av superklassen (jämför this
). Precis som
this
kan super
användas för att kvalificera
attribut och metoder: detta kan ibland vara nödvändigt, t.ex. om ett attribut
i superklassen har samma namn som ett i subklassen.
I exemplet ersätts metoden print()
i subklassen. Detta innbär
att Circle
-referenser och Shape
-referenser beter
sig olika vid anrop av denna metod. Eftersom Shape
s
print()
ju gör en del av det som Circle
s gör
skulle man kanske vilja använda den. Men om vi skriver print()
i Circle
s print()
får vi ett rekursivt anrop.
Lösningen heter super
:
public void print() // i klassen Circle { System.out.print("Cirkel,"); super.print(); }För att riktigt inse styrkan i arv skall vi nu utvidga exemplet med en klass och en metod för att rita figuren. Dessutom införs begreppet abstrakt klass: inga instanser av klassen
Shape
kommer
någonsin att skapas, en figur är en cirkel eller en rektangel, aldrig
bara en figur. Nyckelordet abstract
i en klassdeklaration
innebär att inga instanser av klassen kan skapas. Även metoder kan
deklareras abstract
:
abstract public class Shape { public Shape( int ix, int iy, int isize, boolean ifill) { x = ix; y = iy; fill = ifill; } protected int x; protected int y; protected boolean fill; public void moveRel( int dx, int dy ) { x += dx; y += dy; } abstract public void draw(); // Notera ; i stället för { .. } } public class Circle extends Shape { public Circle( int ix, int iy, int r, boolean ifill) { super(ix, iy, ifill); radius = r; } public void draw() { /* do the drawing */ } public void setSize( int r ) { radius = r; } protected int radius; } public class Rectangle extends Shape { public Rectangle( int ix, int iy, int w, int h, boolean ifill) { super(ix, iy, ifill); width = w; height = h; } public void draw() { /* do the drawing */ } public void setSize( int w, int h ) { width = w; height = h; } protected int width; protected int height; }Själva
draw()
-metoderna visas inte här men finns t.ex. i det
tidigare exemplet. Antag nu att vi har en massa figurer vi vill rita. Ett
sätt att göra detta vore att ha en array med cirklar och en med rektanglar
(och en med trianglar, en med generella polygoner, etc). Men med hjälp av
vår arvshierarki kan vi skapa instanser av subklasser och lagra i referenser
till superklassen! Den regel som styr hur man får lagra referenser säger just
detta: en variabel får referera till instanser av sin "deklarerade" klass och
till instanser av alla subklasser. Och vid anrop av en metod väljs
inte superklassens metod utan subklassens ersättning om en sådan
finns. I fallet abstrakta metoder är ju för övrigt detta beteende nödvändigt.
Ett kodavsnitt som utnyttjar detta - som kallas polymorfi kan
se ut så här:
Shape[] shape = new Shape[10]; shape[0] = new Circle( 100, 100, 50, false ); shape[1] = new Rectangle( 200, 100, 50, 100, true ); . . . for (int i=0;i < shape.length; i++) shape[i].draw();Men hur ska man kunna använda metoden
setSize()
som ju har
olika argumentuppsättning i de två fallen? Så långt möjligt bör man undvika
att hamna i lägen där man måste veta exakt vilken subklass en referens
avser men ibland kan det bli nödvändigt:
// Antag att alla cirklar ska ges storleken 12 for (int i=0;i < shape.length; i++) { if ( shape[i] instanceof Circle) { Circle c = (Circle) shape[i]; // cast shape[i] to Circle. c.setSize( 12 ); } }Här ser vi två nya konstruktioner, dels operatorn
instanceof
som returnerar sant om dess första operand är en
instans av den andra operanden, dels också en s.k. cast,
d.v.s. man konverterar en variabel från en typ till en annan. Java
kontrollerar att typkonverteringen kan göras: om man använder uttrycket
Circle c = (Circle) shape[i];
när shape[i]
är
en Rectangle
kastas ett undantag. En varning: använd
instanceof
och casts så litet som möjligt: det är fullt möjligt
att helt ta bort fördelarna med OOP genom flitigt uttnyttjade av dessa
"finesser"!
Precis som attribut kan också metoder och klasser deklareras
final
: en final
-deklarerad klass kan inte ärvas
och en final
-deklarerad metod kan inte ersättas i subklasser.
Metoder som är deklarerade private
eller static
blir också final
.
Metoden finalize()
används ju för
att städa upp efter sig och
kan ses som motsatsen till konstruktorn. Till skillnad från den "kedjas" inte
finalize()
, d.v.s. superklassens finalize
anropas
inte automatiskt.
Som tidigare påpekats är klassen Object
superklass til samtliga
Java-klasser - detta utan att man behöver ange det med extends
.
I superklassen finns några metoder som kan vara bra att ersätta i de klasser
man skriver själv:
public boolean equals( Object obj); protected Object clone(); public String toString();
toString()
har tidigare diskuterats. Metoden equals()
avgör om två objekt är lika. Vad detta betyder kan diskuteras men
Object
s equals()
returnerar sant endast om samma
objekt refereras av de två variablerna. Man bör ersätta detta med en metod
som kontrollerar om alla relevanta attribut är lika.
class Circle { public Circle( int ix, int iy, int isize ) { x = ix; y = iy; size = isize; } public boolean equals( Object obj ) { if (obj instanceof Circle) { Circle c = (Circle) obj; return (c.x==x) && (c.y==y) && (c.size==size); } else return false; } }Observera att
equals()
för att fungera med variabler av
superklassens typ (som kan vara en referens till en subklass) måste skrivas
med ett argument av klassen Object
.
interface
. Ett (en?) interface
är en klass som
innehåller endast abstrakta metoder. En klass implementerar ett
interface
. Ett alternativt sätt att implementera uppritning av
de olika figurerna i det tidigare exemplet
vore att ta bort den abstrakta metoden draw
i Shape
och använda interface
:
interface Drawable { public void draw( Graphics g); } class Circle extends Shape implements Drawable { public Circle( int ix, int iy, int isize, boolean ifill) { super(ix, iy, isize, ifill); } public void draw( Graphics g ) { if (fill) g.fillOval(x-size,y-size,size*2,size*2); else g.drawOval(x-size,y-size,size*2,size*2); } }Ett annat exempel på användning av
interface
är metoden
clone
i Object
. För att en klass skall kunna
"klonas" krävs att den implementerar (det helt tomma!) gränssnittet
Cloneable
:
class Circle implements Cloneable { public Circle( int ix, int iy, int isize ) { x = ix; y = iy; size = isize; } public Object clone() { return new Circle( ix, iy, isize ); } }
java.net
för nätverksprogrammering. Man kan skapa egna paket genom att ange nyckelordet
package
i klassdeklarationen. Alla klasser utan detta buntas ihop
i ett default-paket. Detta har viss betydelse för åtkomst av attribut och
metoder som vi ska se. Exempel på ett egendefinierat paket:
package MyShapes; public class Shape { ... }Genom att placera
package MyShapes;
i alla filer som
beskriver figurer skapar man ett eget paket.
Alla attribut och metoder har en (oftast explicit angiven) skyddsnivå som
talar om vem som kommer åt den. Om man inte anger någon nivå får variabeln
eller metoden s.k. default
-nivå, vilket innebär åtkomst för alla
metoder i samma paket men ingen annan, t.ex. inte subklasser i andra paket.
Om man anger private
kommer endast klassens egna metoder
åt fältet, public
ger fullständig åtkomst för vem som helst.
Däremellan finns protected
som ger alla i samma paket plus
alla subklasser åtkomst och private protected
som ger endast
subklasser (oavsett paket) åtkomst.
Vilken nivå man skall använda avgörs från fall till fall och beror på hur fältet (attributen eller metoden) skall användas.
import
för att göra klasser och/eller paket
tillgängliga för vår kod. Ett par exempel:
import java.awt.*; import java.awt.Button;Den första raden ger oss tillgång till alla klasser i
java.awt
och
ser till så att vi kan referera till dem med bara deras namn, t.ex.
Label
, den andra raden gör samma sak men bara med klassen
Button
. Alternativt, om vi inte använder import
kan vi ge hela namnet, t.ex. java.awt.Label
direkt i koden.Viktiga paket:
java.applet
klasser för applets
java.awt
klasser för grafik och användargränssnitt.
java.awt.event
klasser för händelsehantering
java.awt.image
klasser för bilder
java.io
klasser för läsning/skrivning
java.lang
klasser för själva Java
java.math
klasser för matematiska funktioner
java.net
klasser för nätverksprogrammering
java.util
klasser med en del vanliga datatyper
java.lang
inte behöver importeras explicit,
paketet finns alltid tillgängligt.
Undantag som ej fångas skickas vidare till anropande metod.
Ett undantag måste fångas av någon metod, annars hamnar det till slut i
main()
-metoden varvid Java skriver ett felmeddelande och
avslutar programmet.
Klassen Throwable
är superklass till Error
och
Exception
som alla undantag ärver från. Man kan enklast säga
att undantag av typen Error
är fatala fel som man knappast skall
försöka rädda. De mer intressanta undantagen finns alltså i
Exception
. Eftersom undantag är Object
kan de
definiera data och metoder som fångaren kan använda. När man genererar
undantag kan man använda sig av de fördefinierade eller man kan definiera
egna.
Undantagshanteringen använder de tre nyckelorden throw, catch
och try
:
// Först ett egendefinerat undantag: class SquareRootOfNegativeValue extends Exception { public SquareRootOfNegativeValue() { super(); } public SquareRootOfNegativeValue(String s) { super(s); } } class TestSqrt { private double value; public TestSqrt( double a ) { value = a; } public double getSqrt() throws SquareRootOfNegativeValue { if (value >= 0) return Math.sqrt( value ); else throw new SquareRootOfNegativeValue(); } public static void main( String[] args ) { try // koden inom detta block kontrolleras { Double a = Double.valueOf( args[0] ); TestSqrt ts = new TestSqrt( a.doubleValue() ); ts.getSqrt(); } catch (SquareRootOfNegativeValue e) { System.out.println("Negative value");} catch (NumberFormatException e) // kan kastas av Double.valueOf { System.out.println("Couldn't parse the number"); } catch (ArrayIndexOutOfBoundsException e ) // kan kastas av args[0] { System.out.println("No argument"); } } }Java kräver att en metod som kan generera ett undantag måste endera fånga det eller tala om att undantaget kastas vidare med
throws
:
int getElement( int[] arr, int index ) throws IndexOutOfBoundsException { return arr[index]; }Undantag som är subklasser till
Error
eller
RuntimeException
behöver ej deklareras med throws
(i stort sett all kod kan generera dessa undantag). Observera att om en
metod kan kasta flera undantag som alla är subklasser till samma superklass
räcker det att tala om att metoden kastar superklassen.
Man kan vilja vara säker på att en metod exekverar en del kod oavsett om
try-blocket exekveras färdigt eller ej. Sådan kod kan placeras i ett
finally
-block som alltid exekveras:
try { } catch (Exception e) { } finally { // exekveras alltid även om undantag kastats. }