TDT4100: Objektorientert Programmering
Tips
Gjør oppgaver fordelt i temaer på Intoit http://www.intoit.io
Primitive datatyper
Type | Beskrivelse | Størrelse |
int | Heltall | 4 byte |
byte | En enkelt byte. | 1 byte |
short | Heltall med mindre rekkevidde. | 2 byte |
long | Heltall med større rekkevidde. | 8 byte |
double | Dobbel-presisjons-flyttall. | 8 byte |
float | Enkel-presisjons-flyttall. | 4 byte |
char | Tegn | 2 byte |
boolean | True eller false. | 1 bit |
Objekter og klasser
Bilfabrikkanalogi
Klasser og objekter er essensielle begreper innenfor objektorientert programmering. La oss bruke bilfabrikkanalogien til å forklare forskjellen mellom en klasse og et objekt. Bilfabrikken holder på malen for hvordan en bil skal se ut og oppføre seg. I denne analogien vil bilfabrikken være klassen. Det blir produsert biler i bilfrabrikken som selges videre til andre personer. De individuelle bilene er objektene. Objektet er en instansiering av klassen, så objekter kalles også instanser av klassen. Vi sier at objekter har en tilstand og en oppførsel, for eksempel at bilen har en bestemt farge (eksempel på tilstand) og muligheten til å kjøre (oppførsel).
Klasser i Java
En enkel klasse i Java ser slik ut:
public class Car {
}
Denne klassen har ingen egendefinerte instansvariabler, konstruktører eller metoder (alt forklart nærmere nedenfor).
Instansvariabler (felt)
Tilstanden til denne klassen er de dataene den inneholder, og dette bestemmes av instansvariablene (feltene) den har. En kan tenke at et felt er en linje i en klasse som ikke er en metode eller en konstruktør. La oss utvide Car
-klassen.
public class Car {
private String color = "Black";
}
color
er en instansvariabel som er skjult for andre klasser. Den er av typen String
.
Konstruktører
For å instansiere klasser så må en bruke nøkkelordet new
.
Car myCar = new Car();
Alle klasser tilbyr en konstruktør uten argumenter. Du kan også skrive egne konstruktører som tar inn en eller flere argumenter. Vi legger til en konstruktør i Car
-klassen.
public class Car {
private String color = "Black";
public Car(String color) {
this.color = color;
}
}
Metoder
Metodene i en klasse er det klassen kan gjøre. Vi legger til en metode i Car
-klassen.
public class Car {
private String color = "Black";
public Car(String color) {
this.color = color;
}
public void paintCar(String color) {
this.color = color;
}
}
Metoden over er public
, så den er en del av grensesnittet til klassen; brukere av klassen kan se denne metoden. Den returnerer heller ingenting, ettersom det står void
. Denne metoden vil gi bilen en ny farge.
Bruken av this()
Hvis man for eksempel har en Person-klasse med følgende felt og konstruktør:
private final String firstName, lastName;
private final int age, height;
public Person(String firstName, String lastName, int age, double height){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.height = height;
}
Så kan man også legge til en konstruktør som ikke tar inn alle parameterene og bruke this() til å designere standard-verdier og referere til en annen konstruktør i den samme klassen:
public Person(){
this(“Ola”, “Nordmann”, 22, 194);
}
Husk at konstruktører ikke kan arves, men man kan kalle på konstruktøren til en superklasse med super(param)
;
Wrapper-klasser
String
String
-klassen er en wrapperklasse for serier av char
-primitiver til strenger som en kan manipulere. Noen av de mange metodene en kan bruke er:
charAt(int index)
contains(String str)
equalsIgnoreCase(String str)
isEmpty()
lastIndexOf(char ch)
length()
replace(char oldChar, char newChar)
toLowerCase()
toUpperCase()
Når en skal sammenlikne to String
-objekter så burde en alltid gjøre dette med string1.equals(string2)
eller bruke equalsIgnoreCase(String str)
-metoden fremfor å bruke to likhetestegn.
Integer
Integer
-klassen er en wrapperklasse for den primitive datatypen int
. Vi gjør dette blant annet for å kunne ha int
-primitiver inne i ArrayList
-lister. Vi kan sammenlikne to Integer
-objekter med compare(int x, int y)
-metoden.
Modifikatorer
Synlighetsmodifikatorer
Modifikator | Klasse | Pakke | Subklasse | Verden |
Public | Ja | Ja | Ja | Ja |
Protected | Ja | Ja | Ja | Nei |
Default/no modifier | Ja | Ja | Nei | Nei |
Private | Ja | Nei | Nei | Nei |
Abstract
En abstrakt klasse er en klasse som ikke kan instansieres, men som kan brukes av subklasser. Et eksempel på en abstrakt klasse er gitt under.
public abstract class klasseNavn {}
En abstrakt metode er en metode uten spesifisert implementasjon. Alle klasser som arver fra en abstrakt klasse med abstrakte metoder må implementere de abstrakte metodene for at subklassen skal kunne instansieres. For eksempel:
public abstract String makeString();
For at en klasse skal kunne inneholde abstrakte metoder så må klassen deklareres som en abstrakt klasse. Derimot må ikke en abstrakt klasse inneholde abstrakte metoder; det kan også brukes som et skjold mot instansiering. En abstrakt klasse skiller seg fra et grensesnitt på den måten at den kan implementere noen metoder om den ønsker.
Final
Modifikatoren final
uttrykker immuterbarhet; dersom en instansvariabel markeres som final
vil den ikke kunne forandres etter at den har blitt initialisert. Eksempel på dette er gitt nedenfor.
public final int someNumber = 10;
someNumber
kan ikke forandres senere i koden. Eventuelt kan en ha følgende tilfelle:
public final int someNumber;
Denne variabelen kan kun initialiseres i konstruktøren, og aldri igjen etter det. Immuterbare variabler brukes ofte sammen med static
, et nøkkelord som forklares i avsnittet under.
Static
Et felt merket med static
-modifikatoren regnes som felles for alle objekter av samme klasse. Det vil si at alle instansene av en klasse med en static
variabel deler den samme variabelen. For eksempel:
public static final double PI = 3.14;
Alle objektene av denne klassen vil dele variabelen PI, og den vil være uforandelig og satt til 3,14.
Standardteknikker
Observatør-observert
Dette er en viktig teknikk som handler om å kunne følge med på endringer som skjer i essensielle data som for eksempel kan forandre rekkefølgen på en sortering eller lignende. Man har en eller flere observatører som ønsker å følge med på den observerte. Den observerte må ha metoder som for å lage og sende endringshendelser, samt for å legge til og fjerne observatører til/fra en liste med alle som ønske rendringsmeldingene. Observatøren må implementere et grensesnitt av typen EventListener
, for eksempel PropertyChangeListener
, med metoden propertyChange()
.
Delegering
Har et interface man kan implementere i f.eks. en standardklasse for grensesnittet. Deretter kan en annen klasse, som ikke implements interface, lage et felt som instansierer et standardklasse-objekt, det objektet kan igjen brukes i metoder i den vanlige klassens metoder som ligner på metodene i interfacet / implementarsjonsklassen/standardklassen. På den måten kan standardklasse-objektet enkelt byttes ut, som gjør delegeringsteknikken mer fleksibel enn arv.
Casting
Når man itererer gjennom en mengde, kan det være hensiktsmessig å bruke casting. For eksempel kan det være en mengde objekter som arver fra samme objekt, men der ikke alle metodene er definert for alle typene klasser.
La oss si at vi har en klasse som heter togvogn, og to klasser passasjervogn og godsvogn som begge arver fra togvogn. Passasjervogn har en metode for å telle passasjerer - tellePassasjerer()
. Et tog kan beskrives som en liste av vogner. Hvis vi nå ønsker å summere opp alle passasjerene i toget, nytter det ikke å iterere over hele toget og kalle på tellePassasjerer-metoden, fordi toget består også av godsvogner, som vil gi oss en error i itereringen. Da kan vi bruke casting:
for (togvogn vogn : tog) {
if (vogn instanceof passasjervogn){
passasjervogn pvogn = (passasjervogn)vogn;
antallPassasjerer += pvogn.tellePassasjerer();
}
Husker fra Iterable<T>
-grensesnittet at denne forløkken gjelder for alle vogner av typen togvogn i en collection tog. instanceof sjekker om vognene er av typen passasjervogn, som legger til rette for selve castingen, (passasjervogn)vogn. Her tar vi en passasjervogn-instanse av vogna, ettersom vi først har sjekket instanceof
. Deretter kan vi hente tellepassasjerer-metoden.
Collection-rammeverket
Collection er et stort rammeverk som består av lister, set, maps, iterator/iterable-grensesnitt, HashMap, ArrayList m.m. Felles for Collection er en rekke metoder; her er noen av de viktigste:
add(T elm) | Legger til element av typen T til collection |
addAll(Collection<T> c) | Legger alle elementene i Collection c av type T til en annen collection |
clear() | Fjerner alle elementer fra collection |
contains(object obj) | Returnerer true hvis col. inneholder obj |
containsAll(Collection<T> c) | Returerer true hvis alle elementene i c ligger i collection |
isEmpty() | Returnerer true hvis collection er tom |
size() | Returnerer en int med antall elementer i collection |
toArray() | Returnerer en Object[] array med alle elementene i lista |
remove(Object obj) | Fjerner objektet fra collection |
HashMap<K,V>
HashMap i Java minner veldig om Dictionary fra Python. Man har en type keys K og en type verdi V som den husker på. Når man skal bruke remove-metoden med en HashMap, er det først og fremst nøkkelverdien som brukes:
remove(Object key),
men kan også spesifisere objekt:
remove(Object key, Object obj).
Set<E>
Et Set i Java er veldig likt et set i Python, det kan ikke inneholde duplikater. For eksempel om man har en ArrayList med mange duplikter og ønsker å finne antall unike objekter i lista kan man legge alle elementene fra ArrayList’n til et Set.
List<String> liste = new ArryList<>();
liste.add(Obj...obj);
Set<String> set1 = new HashSet<>();
set1.addAll(liste);
Og eventuelt putte det tilbake til ArrayList om man absolutt må:
liste.clear();
liste.addAll(set1);
Den mest vanlige typen Set er HashSet, som er en usortert Set med verdier, i motsetning til f.eks. TreeSet som er sortert.
Grensesnitt
Generelt kan en si at grensesnittet til en klasse er det som er synlig for andre brukere av klassen. Vi bruker dog begrepet grensesnitt om en type som bare består av public
konstanter og tomme metoder. Et slikt grensesnitt kan implementeres av andre klasser.
Interface Comparable<T>
Ved å implementere Comparable-grensesnittet, så kan objekter sorteres med bruk av Java sine innebygde sorteringsfunksjoner. Comparable har kun èn metode:
int compareTo(T objekt)
Sammenligner dette objektet med et annet objekt. Logikken fungerer slik: for å bli plassert før objektet du sammenligner deg med i sorteringsrekkefølgen, må metodekallet returnere en int < 0. Om det returnerer en int > 0, blir objektet du sammenligner med satt først i sorteringsrekkefølgen. Om compareTo returnerer 0, sorterer den inkonsekvent. Comparable sorterer alltid i stigende rekkefølge. Brukes av en klasse slik:
public class testKlasse implements Comparable<T>{
// som må inneholde metoden
public int compareTo(T objekt){......}}
Interface Comparator<T>
Er ment å implementeres av en annen klasse enn den som skal sorteres. Den kan da også ha friere regler på utfallet av sammenligninger. For å bruke en Comparator sendes den enkelt med som et andre argument til kallet til Collections.sort(). Comparator-grensesnittet krever at du har implementert metoden compare(o1,o2). Denne har samme logikk for returverdien som compareTo(), men tar en inn to argumenter istedet for at et argument sammenlignes med "this". I motsetning til Comparable, kan man bestemme sorteringsrekkefølge, utenom dette er logikken den samme. Eksempelkode
Comparator går hånd i hånd med lambda-uttrykk, som man kan lese mer om her
Det handler om at Comparator er et funksjonelt grensesnitt (altså kun èn metode), slik at man kan skrive om instansieringen av en Comparator i et collection.sort kall, f. eks:
persons.sort(new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
return a.getAge() - b.getAge();
});
blir til:
persons.sort((a, b) -> a.getAge() - b.getAge());
Men serr, les mer på wiki-sida.
Interface Iterable<T>
Ved å implementere Iterable-interface i en klasse, kan man iterere mye enklere gjennom en Collection med instanser av denne klassen, slik at man kan bruke klassen på høyreside av kolon i en slik for-løkke:
for (T obj : c){
action();
}
Denne koden kan leses slik: “For alle objekter av typen T i collection c, utfør følgende handling. Husk å lage:
@Override
public Iterator<T> iterator() { return c.iterator();}
Interface Iterator<E>
En instans av iterator-grensesnittet, altså en Iterator, kan brukes til å iterere gjennom elementer til et annet objekt. Det viktige å merke seg her er at en Iterator husker hvor langt man har kommet i itereringen, og at etter itereringen er gjennomført er iteratoren oppbrukt. Iterator-grensesnittet ligger i Collection-rammeverket og har følgende tre metoder:
boolean hasNext()
som returnerer true hvis det er flere elementer igjen å iterere gjennom.
<E> next()
returnerer neste elementer og tar et steg videre i iteratoren. Ikke bruk denne med mindre hasNext() returnerer true.
void remove()
som fjerner det siste elementet som ble returnert av next() fra collection-kilden om den støtter det.
Funksjonelle grensesnitt
Et funksjonelt grensesnitt har èn abstrakt metode (kalt den funksjonelle metoden), som legger til rette for lambda-uttrykk.
Exceptions
Her er det viktig å skille mellom checked exceptions og unchecked exceptions.
Checked exception
Enkelt forklart er dette feilene som blir oppdaget ved kompilering. Eksempler er IOException
, SQLException
, DataAccessException
, ClassNotFoundException
, InvocationTargetException
og MalformedURLException
.
Løsningen på checked exceptions er ofte å legge koden i en try/catch:
try{
// etellerannet
} catch(IOException e){}
Unchecked exception
Dette er exceptions som ikke blir verifisert iløpet av kompileringen, og skyldes dårlig programmering. Eksempler er NullPointerException
, ArrayIndexOutOfBound
, IllegalArgummentException
og IllegalStateException
. Her også kan man ofte løse problemet med å prøve en try/catch, og huske å bruke throw
.
Input og output
Her benytter vi oss stort sett av subklasser av InputStreamReader
og OutputStreamWriter
. Her snakker jeg om Reader
og Writer
, som igjen arver til FileReader
og FileWriter
. Personlig foretrekker jeg PrintWriter
, da den har en utvidet print-metode som dekker boolean
, String
-objekter, char
-primitiver, integerverdier og annet. Veldig kjekk metode er println (leses: print-line), som først printer og deretter går til ny linje.
JFileChooser
Når man skal velge fil fra datamaskin som det skal skrives til, kan man bruke JFileChooser
(ikke veldig eksamensrelevant).
JFileChooser chooser = new JFileChooser();
if(chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
File selectedFile = chooser.getSelectedFile();
// deretter kan man
try {
PrintWriter outprint = new PrintWriter(selectedFile);
outprint.println(“Hello World”);
out.close();
catch (FileNotFoundException e) {
e.printStackTrace();
}
Samme gjelder når man skal lese fra fil, men da bruker man FileReader og .read() isteden for PrintWriter og println().
Scanner
Brukes for å lese data fra fil, eks. InputStream
. Hvis man har en InputStream
input, lages scanneren basert på denne:
Scanner scanner = new Scanner(input);
Ved iterering brukes ofte while(scanner.hasNextLine())
, forså å lage en ny streng av neste linje og manipulere denne: String line = scanner.nextLine();
Må alltid huske å lukke scanneren etter bruk scanner.close();
Diverse
vararg
Når en metode skal ta inn et uspesifisert antall elementer, skriver man slik:
public Constructor(Obj...obj)
som lager en String array obj[]. Deretter kan man for eksempel lage en ArrayList slik:
this.someList = new ArrayList<Obj>(Arrays.asList(obj));
Ternary operator
? : kan brukes som en forkortelse på if-else-then for enkle variabel-designeringer.
if (a > b) {
max = a;
} else {
max = b;
}
kan forkortes til den enkle koden:
max = (a > b) ? a : b;
Leses: Er a større enn b? true gir a. ellers: b.
Break/continue
I en løkke: bruk break for å stoppe itereringen, og bruk continue for å hoppe til neste ledd i iterasjonen.
Diagrammer
Objektdiagram
Objektdiagrammer viser tilstanden til objekt(struktur)er, med verdiene til attributter og referanser som knytter objekter sammen.
Objekttilstandsdiagrammer
Objekttilstandsdiagrammer viser hvordan objekt(struktur)er endres over tid, når en kaller metoder.
Klassediagrammer
Klassediagrammer er en illustrasjon av innholdet i og sammenhengen mellom klasser, som et supplement til tekslig kode. Et klassediagram viser klasser som bokser, attributter og operasjoner som tekstlinjer inni boksene (i hver sine deler) og assosiasjoner og arv som streker med. I tillegg annoteres assosiasjonsstreker med informasjon om navn og såkalt multiplisitet (også kalt kardinalitet).
Sekvensdiagram
Et sekvensdiagram viser kall mellom objekter.
Testing med JUnit
Dette handler om å sjekke at koden fungerer slik den skal; hyppig bruk av assertEquals. For eksempel, hvis vi har en Number-klasse med en metode getNumber() som alltid skal returnere 1. Da kan man ha en testklasse som ser slik ut:
public class NumberTest extends junit.framework.TestCase {
public void TestNumber(){
Number number = new Number();
assertEquals(1, number.getNumber());
}
}
Andre test-metoder inkluderer assertFalse, assertTrue m.m.