Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

Typen til en verdi eller et objekt bestemmer hva en kan gjøre med verdien eller et objekt, dvs. hvilke operasjoner en kan utføre eller metoder en kan kalle. F.eks. kan en bruke + mellom tall og mellom String-objekter, men ikke mellom Date-objekter. For hver type kan en liste opp hva som er lov og ikke, og hvis en gjør noe ulovlig så vil det typisk blir bli utløst et unntak. Hvis en vet hva slags typer type verdier en har å gjøre med i et uttrykk eller setning, så kan en også sjekke at de blir brukt riktig. Og hvis dette kan sjekkes av kompilatoren eller utviklingsverktøyet, så kan en unngå at det blir feil ved kjøring, fordi en nektes å skrive og kjøre kode som ikke er korrekt iht. typene.

...

  • boolean - sannhetsverdier, som støtter logiske operasjoner som og AND (& og &&) og eller OR (| og ||) 
  • int - helttallstype som støtter aritmetiske operasjoner som +, -, *, / og % (rest)
  • double - desimaltallstype som støtter aritmetiske operasjoner som +, -, *, / og % (rest)
  • java.lang.String - tegnsekvensklasse som støtter + for sammensetting (spleising) og en rekke metoder for å hente ut tegn og /delsekvenser og for å lage nye String-objekter
  • java.util.ArrayList - objektsekvensklasse som har en rekke metoder for å endre å sekvensen (endre, legge til og fjerne elementer)

...

  • 1 + 2, lov fordi 1 og 2 begge er av typen int, som støtter + (resultatet blir 3)
  • "1" + "2", lov fordi "1" og "2" er av typen String, som støtter + (resultatet blir "12")
  • "1".length(), lov fordi "1" er av typen String og String har metoden length() (uten parametre)
  • "2".charAt(2.0), ikke lov fordi selv om "2" er en String og String har metoden charAt, så tar charAt en int som argument og 2.0 er en double-verdi.
  • ("1" + "2").metodeSomIkkeFinnes(), ikke lov fordi "1" + "2" gir en ny String, og String har ikke metoden metodeSomIkkeFinnes.
  • new Date() + 3, ikke lov fordi new Date() gir et objekt av typen Date, som ikke støtter + (selv om en kunne definert det teknisk sett er mulig å definere at det skulle gi en ny dato en viss tid fremover)

...

  • v1.compareTo("C++"), lov siden typen til v1 er String, som har metoden compareTo(String).
  • v2.compareTo("C++"), ulovlig siden typen til v2 er Object, som ikke har metoden compareTo(String), selv om verdien til v2faktisk er en (referanse til en) String, som har det.
  • v4.compareTo("C++"), lov siden typen til v4 er Comparable<String>, som (selvsagt) har metoden compareTo(String) (uten å vite noe om implementasjonen).
  • v6.compareTo("C++"), lov siden typen til v6 er String, som har metoden compareTo(String), selv om verdien til v6 i praksis er null og en får utløst unntaket NullPointerException ved kjøring.

Bruk av arv

Ved bruk av arv, og spesielt kombinasjoner av extends og implements, så blir det lett forvirrende. Her er et eksempel fra ordinær eksamen 2010

Anta følgende klasser, grensesnitt og metoder:
- Klasse A deklarerer metoden A methodA().
- Grensesnittet G deklarerer metoden G methodG(A) .
- Klasse B arver fra A, implementerer G og deklarerer metoden B methodB(A).
- Klasse C implementerer G og deklarerer metoden C methodC(G).

Lenke til koden: A.javaB.javaC.javaG.java og Main.java

PlantUML Macro
class A {
	A methodA()
}
interface G {
	G methodG(A)
}
class B {
	B methodB(A)
}
B --|> A
B ..|> G
class C {
	C methodC(G)
}
C ..|> G

 

 

Oppgave a)

 Hvilke av følgende deklarasjoner/initialiseringer vil gi feil i editoren/ved kompilering:
A a = new B();

B b = new A();

...

Casting

Casting er en mulighet en har i Java for å "endre" typen til et uttrykk. Notasjonen er (T) expr, hvor T er en type og expr er et uttrykk. En setter altså typen en ønsker i parentes foran et uttrykk og samlet sett får en et nytt uttrykk av angitt type. En kan selvsagt ikke velge T fritt, det må jo være en sammenheng mellom T og typen til expr. Effekten av utførelsen av casting-uttrykk og hva som er lov og ikke, kan deles i to tilfeller:

  1. Hvis T og typen til expr er tall-typer, så vil Java foreta en konvertering av tall-verdien fra den ene typen til den andre. Dette kan tvinge Java til å fjerne desimaler eller siffer, men en får lov for det er på eget ansvar. F.eks. vil (int) 2.5 gi int-verdien 2 som resultat siden desimaler fjernes, mens (byte) 256 vil gi byte-verdien 0 siden bare de laveste 8 binære sifrene beholdes.
  2. Hvis T og typen til expr er referansetyper (klasser og grensesnitt), så vil det sjekkes om den faktiske verdien til expr er en instans av T eller en subklasse. Hvis ikke utløses ClassCastException. Objektet selv vil uansett ikke bli endret. Dette er altså en dynamisk typesjekk tilsvarende den instanceof gjør. Men i tillegg aksepterer altså Java at typen til uttrykket som helhet er T, som er den statiske typen. Hvis det kan bevises at en casting umulig kan gå, så får en feil allerede ved kompilering.

Eksempler:

Code Block
languagejava
byte b = (byte) 265; // greit, b blir 109, siden ekstra binære siffer fjernes
int i = (int) '0'; // greit, i blir 48, som er koden til tegnet '0'
int j = (int) Math.random(10) + 1; // j blir et tilfeldig heltall fra og med 1 til og med 10
Object o = (Object) "Java"; // unødvendig casting, siden uttrykket "Java" er Object allerede
String s = (String) o; // nødvendig casting, siden o muligens, men ikke nødvendigvis må ikke være String
String s = (String) new Date(); // ulovlig, siden et objekt umulig kan være en instans av både Date og String på samme tid

Den vanligste bruken av casting er såkalt downcasting, dvs. casting til en subtype, f.eks. så en kan bruke subtype-metoder:

Code Block
languagejava
Object o = ...
if (o instanceof String) {
   System.out.println("Lengden er :" + ((String) o).length());
}

 Det kan faktisk også være aktuelt å caste til en supertype, f.eks. hvis en ønsker å "velge" en bestemt metode av flere med samme navn (overloading):

Code Block
languagejava
void metode(Object o) {
	...
}
void metode(String s) {
	...
	metode((Object) s);
}
 

method-metoden finnes i to varianter: den ene tar et Object-argument, og den andre tar et String-argument. Hvis den andre skal kunne kalle den første med et objekt som Java vet er en String, så kan man caste typen til Object. Da velges den første metoden og ikke den andre, og en unngår evig rekursjon.

 

En kan også caste null-verdien, for å velge mellom ulike metoder med samme navn: metode((Object) null) vil kalle den første og metode((String) null) vil kalle den andre. Uten casting her vil det være tvetydig, og en vil få kompileringsfeil.

Bruk av arv

Ved bruk av arv, og spesielt kombinasjoner av extends og implements, så blir det lett forvirrende. Her er et eksempel fra oppgave 2 på ordinær eksamen 2010

Anta følgende klasser, grensesnitt og metoder (diagram til høyre):

  • Klasse A deklarerer metoden A methodA().
  • Grensesnittet G deklarerer metoden G methodG(A)
  • Klasse B arver fra A, implementerer G og deklarerer metoden B methodB(A).
  • Klasse C implementerer G og deklarerer metoden C methodC(G).

Lenke til koden: A.javaB.javaC.javaG.java og Main.java

PlantUML Macro
class A {
	A methodA()
}
interface G {
	G methodG(A)
}
class B {
	B methodB(A)
}
B --|> A
B ..|> G
class C {
	C methodC(G)
}
C ..|> G

Spørsmålet i a) er hvilke av følgende deklarasjoner/initialiseringer vil gi feil i editoren/ved kompilering (altså handler det om statiske typer):

  • A a = new B(), går greit, siden uttrykket new B() gir en B som er en subklasse (arver fra) A
  • B b = new A(), går ikke greit, siden en A ikke er en B
  • G g1 = new A(), g2 = new B(), g3 = new C(), den første går ikke, siden en A ikke er en subklasse av (implementerer) G, mens de andre er greie, siden både B og C implementerer G
Spørsmålet i b) er hvilke av følgende uttrykk vil gi feil i editoren/ved kompilering (statiske typer) og hvorfor (anta at variablene ab og c er deklarert til å være av klassene AB og C):

  • a.methodA() == c.methodC(a), ikke greit fordi C sin methodC tar inn en G, og a (i uttrykket c.methodC(a)) er jo ikke det.
  • b.methodA().toString(), greit fordi b arver methodA fra A og alle objekter har jo toString()-metoden.
  • b.methodG(new B()), greit fordi b arver (må implementere) methodG fra G, denne tar inn en A og new B() har jo (i hvert fall) typen A

De tre siste uttrykkene i b)-oppgaven var om casting (kallet til methodG i originalen er utelatt her, fordi det er greit i alle tilfeller):

  • (G) a, greit fordi a (deklarert som A) kan vise seg å være av en subklasse som implementerer G
  • (B) a, greit fordi a jo i praksis kan være en B, f.eks. hvis a = b var blitt utført tidligere
  • (C) a, ikke greit, fordi noe som er en A ikke samtidig kan være en C (siden en potensiell subklasse ikke kan arve fra to vanlige klasser samtidig)

Casting er altså ulovlig hvis det kan (be)vises at det alltid vil gi ClassCastException ved kjøring. Men merk at beviset må ta høyde for at det finnes klasser en ikke vet om (derfor ville den første av casting-uttrykkene (G) a, over vært lov selv om en ikke visste om B, siden enhver "ukjent" subklasse kan implementere et hvilket som helst grensesnitt).

Generiske typer

Generiske typer som Collection og List gjør analyse av typer litt mer komplisert. Vi skal ikke dra hele historien her, men si at resonnementene over angående variabler og tilordninger holder så lenge typene er spesialisert til samme (element)type. Tilordningen under til venstre er lov, siden ArrayList er en subtype av Collection. Tilordningene i midten og til høyre er imidlertid ikke lov, siden spesialiseringen er forskjellig.

...