You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 5 Next »

Typer i programmeringsspråk brukes for å bestemme hva det er lov å gjøre med en verdi eller objekt. Ved å deklarere typen til variabler, parametre, attributter/felt og returverdier til metoder, så kan kompilatoren og utviklingsverktøy sjekke om operasjoner og metoder er brukt riktig (i hvert fall ikke ulovlig).

 

Typer

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 utløst et unntak. Hvis en vet hva slags typer 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. Dette kalles statisk typing, siden typene til alle uttrykk kan sjekkes uten (dynamisk) kjøring av koden. Det krever litt mer innsats av programmereren, men gir mye tilbake i form av større trygghet for at koden virker, samt at utviklingsverktøyet kan være mer hjelpsomt (komplettering av navn, halvautomatisk retting av feil osv.).

Java har en rekke innebygde typer, både verdityper og objekttyper. I tillegg kan man definere nye typer, som man jo gjør når man skriver nye klasser (og grensesnitt). Her er noen eksempler:

  • boolean - sannhetsverdier, som støtter logiske operasjoner som og (& og &&) og eller (| 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 lage nye String-objekter
  • java.util.ArrayList - objektsekvensklasse som har en rekke metoder for å endre å sekvensen (endre, legge til og fjerne elementer)

Basert på dette kan en f.eks. avgjøre hvilke av følgende uttrykk som er lov og ikke:

  • 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 at det skulle gi en ny dato en viss tid fremover)

Typer og uttrykk

Et viktig poeng er at når en vet typen til verdiene/objektene som inngår i et uttrykk (såkalte deluttrykk), så vet en også noe om typen til resultatet. F.eks. vet en at 1 + 2 gir en int fordi int + int generelt en ny int. Dermed vet en også at (1 + 2) + 3 er lov og gir enda en int. Ved å jobbe seg innenfra og ut i et uttrykk, så en dermed finne typen til uttrykket som helhet. F.eks. vil (1 + 2) + "3" gi en String, fordi (1 + 2) gir en int og int + String gir en (ny) String. Dermed er også ((1 + 2) + "3").length() lov, fordi String.length() er lov.

Konvertering av tall

Når en blander talltyper, f.eks. int og double, så har Java regelen at da omgjøres tallene først til samme type, før operasjonen utføres. Typen som velges er slik at en ikke mister sifre eller desimaler. Ved beregning av 1 + 2.0, altså int + double, så blir int-verdien 1 først omgjort til double-verdien 1.0 før de legges sammen. Hvis en i stedet hadde gjort om double-verdien til int, så kunne en potensielt mistet desimaler og det hadde ikke vært bra.

Bruk av + og konvertering til String

+-operatoren brukt på String-objekter håndteres spesielt. Hvis typen på minst én side av + er en String, så blir begge sidene omgjort til String ved å kalle String.valueOf som igjen kaller toString(), før de spleises sammen. Blanding av + med String, tall og andre tall-operatorer kan være forvirrende og gi overraskende feil:

  • "en" + 2 + 3 + "fire" tolkes som (("en" + 2) + 3) + "fire", som gir ((String + int) + int) + String som igjen gir String som gir verdien "en23fire" som resultat.
  • "en" + 2 - 3 + "fire" tolkes som (("en" + 2) - 3) + "fire", som gir ((String + int) - int) + String som igjen gir typefeil, siden  String - int ikke er lov.
  • "en" + 2 * 3 + "fire" tolkes som ("en" + (2 * 3)) + "fire", som gir (String + (int * int)) + String som igjen gir String som gir verdien "en6fire" som resultat.

Deklarasjoner

Java krever at en oppgir typer i deklarasjoner av både variabler og metoder, og det er grunnlaget for å finne typen til alle uttrykk. Typen i deklarasjoner er både en hjelp for oss når vi leser koden og for kompilatoren og verktøyet til å sjekke om koden vår er lovlig. Typen til en variabel begrenser oss på to måter:

  1. hva slags verdier variabelen kan tilordnes, dvs. typen til høyresiden av en tilordning (evt. initialisering)
  2. hva man siden kan gjøre med variabelen, dvs. hvordan den kan inngå i uttrykk

Variabler og tilordninger

Hvis en variabel har deklarert til en type T, så kan den bare tilordnes verdien av uttrykk som har samme type T eller en subtype. Hvis en tenker (og det bør du gjøre) på typen til en variabel som en garanti om hva en kan gjøre med den, så er det jo greit at verdien er en subtype og dermed kan mer, men ikke greit om den er en supertype som kan mindre. Eksempler:

  • String v1 = "Java", lov siden høyresiden har samme type (String) som v1 er deklarert som.
  • Object v2 = "Java", lov siden høyresiden er en subtype (String) av typen som v2 er deklarert som.
  • String v3 = new Object(), ulovlig siden høyresiden er en supertype (Object) av v3 sin type (String).
  • Comparable<String> v4 = "Java", lovlig siden typen til høyresiden er en subtype av v4, ved at String implementerer Comparable<String>-grensesnittet.
  • String v5 = new Date(), ulovlig siden høyresiden (av typen Date) ikke har noe til felles med v5 sin type (String).
  • String v6 = null, lovlig siden null kan gis en hvilken som helst referansetype.

Det er mulig å konstruere eksempler som det kan virke som burde gå, men som ikke gjør det:

Object o = "Java";
String s = o;

Det er jo opplagt for oss at s i praksis kan ha verdien til o, men tilordningen er likevel ulovlig, siden typen til o ikke garanterer at det alltid er tilfelle. Sammenlign med eksemplet til høyre.

Object o = null;
if (Math.random() <= 0.99999) {
	o = "Java";
} else {
	o = new StringBuilder();
}
String s = o;

Her er det like opplagt at selv om s som oftest i praksis kan ha verdien til o, så vil det ikke alltid gå, og dermed må det anses som ulovlig.

Merk at det tillates litt fleksibilitet ved bruk av tall, siden de (som nevnt over) noen ganger kan konverteres uten fare for å miste siffer eller desimaler. Feks.

  • double d = 1, lov siden 1 sin type (int) alltid kan konverteres greit til double.
  • int i = 2.0, ulovlig siden 2.0 sin type (double) ikke alltid kan konverteres til int, og på tross av at 2.0 kan konverteres.

Variabler i uttrykk

Typen til en variabel brukt i et uttrykk er typen den er deklarert som (statisk type), ikke typen til verdien den faktisk har (dynamisk type)! For verdityper som boolean, int og double, så gjør ikke det noen forskjell, men for referansetyper, altså klasser og grensesnitt, så er det pga. arv helt vesentlig. Antar en har variabler v1-v5 deklarert som over:

  • 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 v2 faktisk 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.

 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.

Collection<String> col =  new ArrayList<String>();

Greit, siden ArrayList er en subtype av Collection og de er spesialisert til samme type.

Collection<Object> col = new ArrayList<String>();
col.add(new Object()); // må jo være lov for col sin del, men går jo ikke med ArrayList<String> som verdi

Ikke greit, fordi spesialiseringene er forskjellig. Den andre setningen illustrerer hvorfor.

 

 

 

  • No labels