[x^2 for x in lst]

Nyheter i Java 8 - del 1

2014-07-18

Det här är första delen i en liten miniserie om nyheterna i Java 8. Den kommer att handla om det som är nytt i själva språket Java och lite om några av core-paketen i JDK:n. Den gör inga anspråk på att vara komplett utan anvhandlar bara det som jag själv har tyckt varit intressant att lära mig om Java 8. Första delen handlar om interface, som för första gången sedan Java 1.0 har genomgått en förändring. Senare delar är tänkta att handla om Streams, ändringar på collections mm.

Interface

Interface har i Java 8 blivit mer lika abstrakta klasser. De kan nu mera innehålla defaultmetoder och statiska metoder.

Defaultmetoder

Vad är en defaultmetod?

En defaultmetod är precis som det låter en metod som det skapas en defaultimplementation för direkt i interfacet. Det som anger att en metod är en defaultmetod är nyckelordet default används samt att metoden har en kropp. Ex:


public interface Drivable {
  default void honk() {
    System.out.println("Tuut from Drivable");
  }        
}

Interfacet ovan innehåller endast en metod. Eftersom den är en defaultmetod kan en implementation av interfacet vara helt tomt:


public class Car implements Drivable {
}
Det går bra att skapa en instans av implementationsklassen och anropa honkHorn()-metoden:

public class TestClass {
  public static void main(String[] args) {
    Car car = new Car();
    car.honk();    
  } 
}
Kör man ovanstående klass fås följande resultat:

lars@larsLilla:~/text/blogg/140716$ java TestClass
Tuut from Drivable
lars@larsLilla:~/text/blogg/140716$ 

Vad är de bra för?

Det mest uppenbara användningsområdet för defaultmetoder, tycker jag, är om man har ett befintligt interface i produktion som man behöver lägga till en ny metod till. Tidigare läts inte detta göras med mindre än att man uppdaterade alla klasser som implementerade interfacet, vilket ofta var helt omöjligt. I alla fall om man själv inte förfogade över hela kodbasen som använde interfacet, vilket man ju ofta inte gör om interfacet används i till exempel ett publikt API och/eller ett open source-projekt. Eftersom det finns nya funktioner i Java 8 som har krävt ändringar på befintliga interface i JDK:n (t.ex. i Iterable) så gissar jag på man infört defaultmetoder för att kunna lösa detta. Vilket ju är trevligt även för oss vanliga dödliga som skapar i alla fall halvpublika interface som tidigare varit svåra att utöka.

Ett annat användningområde för defaultmetoder är om ett nytt interface skapas och det innehåller en metod som har en mycket uppenbar implementation. Ex:


public interface AnInterface {
  void doStuff();
  default void logToConsole(String msg) {
    System.out.println(msg); 
 }
}

Kan det bli problem?

I och med att en klass kan implementera flera interface kan man ju undra om inte en variant på det vanliga "diamantproblemet" kan uppkomma. Svaret är att det kan det, men att javac då vägrar att kompilera klassen och istället ger ifrån sig ett felmeddelande. Vi provar. Först skapar vi ett interface Honkable med en defaultmetod med samma namn som defaultmetoden ovan:

public interface Honkable {
  default void honk() {
    System.out.println("Tööööööt");
  }        
}
Sedan skapar vi en klass som implementerar båda interfacen...

public class SportsCar implements Drivable, Honkable
{
}
...och försöker kompilera den:

lars@larsLilla:~/text/blogg/140716$ javac SportsCar.java
SportCar.java:1: error: class SportsCar inherits unrelated defaults for honk() from types Drivable and Honkable
public class SportsCar implements Drivable, Honkable
       ^
1 error
Hur fixar man detta då? Det man får göra är att man skapar en implementation av den problematiska metoden i sin implementationsklass. Metoden kan på vanligt sätt innehålla vilken syntaktiskt korrekt Java-kod som helst utan någon som helst koppling till defaultimplementationerna i de implementerade interfacen.

public class SportsCar implements Drivable, Honkable {
  public void honk() {
    System.out.println("Tuuuut from SportsCar!"); 
  }        
}
Klassen kompilerar nu fint och ger det förväntade resultatet om man testkör den:

lars@larsLilla:~/text/blogg/140716$ javac SportsCar.java TestClassSportsCar.java 
lars@larsLilla:~/text/blogg/140716$ java TestClassSportsCar
Tuuuut from SportsCar!        
Om man vill referera kan man referera till en av de implementaterade interfacens default implementationer genom att använda nyckelordet super i kombination med namnet på interfacet vars defaultimplementation man vill anropa. Ex:

public class SportsCar implements Drivable, Honkable {
  public void honk() {
    Drivable.super.honk();
  }        
}
Kör man testklassen nu ser man att defaultmetoden i Drivable anropas:

lars@larsLilla:~/text/blogg/140716$ javac SportsCar.java TestClassSportsCar.java 
lars@larsLilla:~/text/blogg/140716$ java TestClassSportsCar
Tuut from Drivable

Skillnader mot abstrakta klasser

Vad är då skillnaden mellan en defaultmetod i ett interface och en (överridningsbar) metod i en abstrakt klass? En skillnad är att defaultmetoden inte har åtkomst till något tillstånd. Den kan endast använda sina inparametrar och sitt eventuella returvärde för att kommunicera med omvärlden. En metod i en abstrakt klass har ju precis som vilken annan klass som helst tillgång till de fält som finns definierade i klassen och i eventuella superklasser.

Att en defaultmetod inte har tillgång till något tillstånd förutom via sina inparametrar innebär ju även att de enda metoder en defaultmetod kan anropa är andra metoder på interfacet den är definierad i samt metoder på inparametrarna.

Statiska metoder

Vad är en statisk metod i ett interface?

En statisk metod i ett interface är en metod som kan anropas utan att ha en instans av en implementation av interfacet. Detta är helt analogt med statiska metoder i klasser. Syntaxen som används är Interfacenamn.metodnamn även det helt analogt med klassfallet.

För att definera en statisk metod i ett interface används nyckelordet static. Metoden ges även en kropp. Ex:

public interface InterfaceContainingStaticMethods {
  static void performSomeStuff() {
    System.out.println("Inside the static method"); 
  }
}

Exempel på användning:

public class TestClassStaticMehod {
  public static void main(String[] args) {
    InterfaceContainingStaticMethods.performSomeStuff();
  } 
}

Kompilering och testkörning:

lars@larsLilla:~/text/blogg/140716$ javac InterfaceContainingStaticMethods.java TestClassStaticMehod.java 
lars@larsLilla:~/text/blogg/140716$ java TestClassStaticMehod
Inside the static method
lars@larsLilla:~/text/blogg/140716$

Vad är statiska metoder i interface bra för?

Ett användingsområde för statiska metoder i interface är när man vill gruppera ihop ett antal metoder som opererar på en viss interface-klass. Tidigare fick man skapa en separat klass för detta och i den lägga statiska metoder, men nu kan man istället implementera de statiska metoderna direkt i interfacet. Ett exempel från JDK:n på kombinationen "interface - klass med statiska metoder som operarar på interfacet" är Collection - Collections. Med Java 8 hade de statiska metoderna i Collections kunnat ligga direkt i Collection-interfacet.

Avslutning del 1

Det var det om interface. Tycker att tilläggen med defaultmetoder och statiska metoder i Java 8 är väldigt trevliga. Speciellt möjligheten att implementera defaultmeoder i redan produktionssatta interface verkar mycket användbar. Med den kan man rätta till i alla fall vissa av de misstag man ofta gör när man skapar API:er.

Nästa del i serien om nyheter i Java 8 kommer att handla om Stream:s som är grundläggande för att kunna programmera funktionellt i Java.

Referenser

Subramaniam V, Functional Programming in Java, första utgåvan, The Pragmatic Programmers, 2014.
Muhammad Khojaye, Interface Default Methods in Java 8
The Java Tutorial - Default methods


Leave a reply

Your name as it will be displayed when the comment is posted on the page. Your email address will not be published.