Link Cerca Menu Expand Document

Píndoles Java

Noms a Java

CamelCase: pràctica d’escriure frases o paraules compostes eliminant espais i posant en majúscula la primera lletra de cada paraula.

Pot ser UpperCamelCase o lowerCamelCase.

Es recomanable evitar caràcters que no siguin lletres o números.

  • Classes (i Interfaces): noms en UpperCamelCase
  • Mètodes: verbs en lowerCamelCase
  • Variables: en lowerCamelCase. Han de ser mnemònics i evitar variables d’una lletra, excepte per temporals: i,j,k,m,n (sencers) i c,d,e (caràcters)
  • Constants: paraules en majúscules separades per subratllat (underscore)
  • Paquets (packages): paraules en minúscules separades per punts. Solen tenir format de domini invertit (com.domain.subdomain). S’ha d’evitar el default package (sense nom)

Valors inicials de les variables

  • Cada variable de classe, d’instància o component d’un array s’inicialitza amb un valor per defecte quan es crea:
    • Per a byte, el valor per defecte és zero, o sigui, el valor de (byte)0.
    • Per a short, el valor per defecte és zero, o sigui, el valor de (short)0.
    • Per a int, el valor per defecte és zero, o sigui, 0.
    • Per a long, el valor per defecte és zero, o sigui, 0L.
    • Per a float, el valor per defecte és positive zero, o sigui, 0.0f.
    • Per a double, el valor per defecte és positive zero, o sigui, 0.0d.
    • Per a char, el valor per defecte és el null character, o sigui, ‘\u0000’.
    • Per a boolean, el valor per defecte és false.
    • Per a tots els tipus referència, el valor per defecte és null.
  • Cada paràmetre d’un mètode o constructor s’inicialitza amb el valor de l’argument corresponent quan s’invoca el mètode o constructor.
  • El paràmetre d’una excepció s’inicialitza amb l’objecte que representa l’excepció.
  • Una variable local ha de ser inicialitzada explícitament abans de ser utilitzada, amb una inicialització o assignació.

Modificadors d’accés

Els modificadors d’accés indiquen el nivell d’accés per a variables, mètodes i constructors. En tenim quatre, de menys a més restrictius:

  • public: sense restriccions, totes les classes de tots els paquets poden accedir.
  • protected: només podem accedir des del mateix paquet o des de qualsevol subclasse, encara que no estigui al mateix paquet.
  • default (sense paraula clau): només podem accedir des del mateix paquet.
  • private: només es pot accedir des de la mateixa classe.

Expressions i conversió de tipus

Ordre de les expressions

A Java, les expressions s’avaluen d’esquerra a dreta.

Java no executarà parts de l’expressió quan no sigui necessari. En particular:

  • Si tenim expressions AND i el resultat d’una a l’esquerra és false, ja no es continua avaluant a la dreta.
  • Si tenim expressions OR i el resultat d’una a l’esquerra és true, ja no es continua avaluant a la dreta.

Tipus primitius

Quan diversos tipus intervenen en una expressió, tots es converteixen al mateix tipus mitjançant unes normes de promoció. Per començar, tots els char / byte / short es promouen a int. Si hi ha algun long, a long. Si hi ha algun float, a float. I si hi ha algun double, a double.

La conversió només es pot fer amb tipus numèrics (exclou el boolean). Hi ha de dos tipus:

  • Widening: cap a un tipus més ample, no cal utilitzar cap notació.
  • Narrowing: cap a un tipus més estret, cal utilitzar el cast: ( tipus ). La conversió pot perdre informació.

Objectes

Tenim dos tipus:

  • Upcasting: quan volem passar el tipus d’un objecte des de la subclasse a una superclasse.
  • Downcasting: quan volem passar el tipus d’un objecte des d’una superclasse a una subclasse.

L’operació de downcasting sol venir precedida de l’ús de l’operador instanceof. Aquest operador retorna true si la classe és del tipus que es pregunta, si és una subclasse o si implementa la interfície.

Autoboxing i unboxing

El boxing / unboxing permet convertir automàticament entre els tipus primitius i les classes embolcall (boolean/Boolean, byte/Byte, char/Character, float/Float, int/Integer, long/Long, short/Short i double/Double):

  • Boxing: conversió automàtica que es produeix des d’un tipus primitiu cap a un objecte.
  • Unboxing: conversió automàtica que es produeix des d’un objecte cap a un tipus primitiu.
Character ch = 'a'; // autoboxing
int val = new Integer(-8); // unboxing

Emmagatzematge de variables

Java passa els paràmetres d’un mètode per valor. Però el valor d’un objecte és una referència. Això també inclou qualsevol array (de primitius o objectes).

Per tant, mai podem modificar el valor d’una variable primitiva, ni el d’un objecte (la referència). El que es pot fer és, si l’objecte és mutable, modificar-lo.

Precedència d’operadors

level Operator Description Associativity
16 []
.
()
access array element
access object member
parentheses
left to right
15 ++
unary post-increment
unary post-decrement
not associative
14 ++

+
-
!
~
unary pre-increment
unary pre-decrement
unary plus
unary minus
unary logical NOT
unary bitwise NOT
right to left
13 ()
new
cast
object creation
right to left
12 * / % multiplicative left to right
11 + -
+
additive
string concatenation
left to right
10 << >>
>>>
shift left to right
9 < <=
> >=
instanceof
relational not associative
8 ==
!=
equality left to right
7 & bitwise AND left to right
6 ^ bitwise XOR left to right
5 | bitwise OR left to right
4 && logical AND left to right
3 || logical OR left to right
2 ?: ternary right to left
1 = += -=
*= /= %=
&= ^= |=
<<= >>= >>>=
assignment right to left

Local vs Instance vs Class variables

characteristic Local variable Instance variable Class variable
Where declared In a method, constructor, or block. In a class, but outside a method. Typically private. In a class, but outside a method. Must be declared static. Typically also final.
Use Local variables hold values used in computations in a method. Instance variables hold values that must be referenced by more than one method (for example, components that hold values like text fields, variables that control drawing, etc), or that are essential parts of an object’s state that must exist from one method invocation to another. Class variables are mostly used for constants, variables that never change from their initial value.
Lifetime Created when method or constructor is entered. Destroyed on exit. Created when instance of class is created with new. Destroyed when there are no more references to enclosing object (made available for garbage collection). Created when the program starts. Destroyed when the program stops.
Scope/Visibility Local variables (including formal parameters) are visible only in the method, constructor, or block in which they are declared. Access modifiers (private, public, …) can not be used with local variables. All local variables are effectively private to the block in which they are declared. No part of the program outside of the method / block can see them. A special case is that local variables declared in the initializer part of a for statement have a scope of the for statement. Instance (field) variables can been seen by all methods in the class. Which other classes can see them is determined by their declared access: private should be your default choice in declaring them. No other class can see private instance variables. This is regarded as the best choice. Define getter and setter methods if the value has to be gotten or set from outside so that data consistency can be enforced, and to preserve internal representation flexibility. Default (also called package visibility) allows a variable to be seen by any class in the same package. private is preferable. public. Can be seen from any class. Generally a bad idea. protected variables are only visible from any descendant classes. Uncommon, and probably a bad choice. Same as instance variable, but are often declared public to make constants available to users of the class.
Declaration Declare before use anywhere in a method or block. Declare anywhere at class level (before or after use). Declare anywhere at class level with static.
Initial value None. Must be assigned a value before the first use. Zero for numbers, false for booleans, or null for object references. May be assigned value at declaration or in constructor. Same as instance variable, and it addition can be assigned value in special static initializer block.
Access from outside Impossible. Local variable names are known only within the method. Instance variables should be declared private to promote information hiding, so should not be accessed from outside a class. However, in the few cases where there are accessed from outside the class, they must be qualified by an object (eg, myPoint.x). Class variables are qualified with the class name (eg, Color.BLUE). They can also be qualified with an object, but this is a deceptive style.
Name syntax Standard rules. Standard rules, but are often prefixed to clarify difference from local variables, eg with my, m, or m_ (for member) as in myLength, or this as in this.length. static public final variables (constants) are all uppercase, otherwise normal naming conventions. Alternatively prefix the variable with “c_” (for class) or something similar.

Checked versus unchecked exceptions

Unchecked exceptions:

  • represent defects in the program (bugs) - often invalid arguments passed to a non-private method. To quote from The Java Programming Language, by Gosling, Arnold, and Holmes: “Unchecked runtime exceptions represent conditions that, generally speaking, reflect errors in your program’s logic and cannot be reasonably recovered from at run time.”
  • are subclasses of RuntimeException, and are usually implemented using IllegalArgumentException, NullPointerException, or IllegalStateException
  • a method is not obliged to establish a policy for the unchecked exceptions thrown by its implementation (and they almost always do not do so)

Checked exceptions:

  • represent invalid conditions in areas outside the immediate control of the program (invalid user input, database problems, network outages, absent files)
  • are subclasses of Exception
  • a method is obliged to establish a policy for all checked exceptions thrown by its implementation (either pass the checked exception further up the stack, or handle it somehow)

Interpretació de les excepcions

Hem vist que podem tenir excepcions Checked (subclasses de Exception) i Unchecked (subclasses de RuntimeException).

Si mirem la jerarquia de classes, totes extenen Throwable, la classe pare de totes:

Els tres mètodes més importants de Throwable són:

  • public String getMessage(): conté el missatge d’excepció.
  • public StackTraceElement[] getStackTrace(): conté la traça de la pila de l’excepció.
  • public Throwable getCause(): opcionalment, conté una referència a l’excepció que ha causat aquesta. Pot ser una cadena de diverses excepcions.

Stacktrace

Un stacktrace no ha de ser necessàriament un error. Es pot obtenir el valor actual mitjançant aquest codi:

public class TestStackTrace {
    public static void one() {
        two();
    }

    public static void two() {
        three();
    }

    public static void three() {
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        for (StackTraceElement elem : trace) {
            System.out.println(elem);
        }
    }

    public static void main(String[] args) {
        one();
    }
}

Cause

Si pots, guarda la causa quan facis throw d’una excepció més específica.

    try {
      doSomething();
    } catch (NumberFormatException e) {
      throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
    } catch (IllegalArgumentException e) {
      throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
    }

Estructura típica

paquet.NomDeLaException: missatge que explica la excepció al getMessage()
at paquet.Classe.metode(Classe.java:XXX)
at paquet.Classe.metode(Classe.java:XXX)
...
Caused by: paquet.NomDeLaException: missatge que explica la excepció al getMessage()
at paquet.Classe.metode(Classe.java:XXX)
at paquet.Classe.metode(Classe.java:XXX)
...

Exemple

A continuació veiem una excepció amb les causes encadenades (Caused by). Els punts suspensius expressen la repetició de les línies respecte de l’excepció pare.

org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
 at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:275)
 at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:237)
 at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
 at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.injectServices(DefaultIdentifierGeneratorFactory.java:152)
 at org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:286)
 at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:243)
 at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
 at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:179)
 at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:119)
 at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:904)
 at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:935)
 at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:56)
 at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79)
 at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54)
 at cotxes.CotxesDAO.getEMF(CotxesDAO.java:205)
 at cotxes.CotxesDAO.access$400(CotxesDAO.java:15)
 at cotxes.CotxesDAO$JPATransaction.result(CotxesDAO.java:223)
 at cotxes.CotxesDAO.findMarques(CotxesDAO.java:81)
 at cotxes.ProvaCotxes.processCommand(ProvaCotxes.java:69)
 at cotxes.ProvaCotxes.prova(ProvaCotxes.java:49)
 at cotxes.ProvaCotxes.main(ProvaCotxes.java:18)

Caused by: org.hibernate.exception.JDBCConnectionException: Error calling DriverManager#getConnection
 at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:115)
 at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
 at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
 at org.hibernate.engine.jdbc.connections.internal.BasicConnectionCreator.convertSqlException(BasicConnectionCreator.java:118)
 at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionCreator.makeConnection(DriverManagerConnectionCreator.java:37)
 at org.hibernate.engine.jdbc.connections.internal.BasicConnectionCreator.createConnection(BasicConnectionCreator.java:58)
 at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections.addConnections(DriverManagerConnectionProviderImpl.java:363)
 at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections.<init>(DriverManagerConnectionProviderImpl.java:282)
 at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections.<init>(DriverManagerConnectionProviderImpl.java:260)
 at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections$Builder.build(DriverManagerConnectionProviderImpl.java:401)
 at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.buildPool(DriverManagerConnectionProviderImpl.java:112)
 at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.configure(DriverManagerConnectionProviderImpl.java:75)
 at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.configureService(StandardServiceRegistryImpl.java:100)
 at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:246)
 at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
 at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.buildJdbcConnectionAccess(JdbcEnvironmentInitiator.java:145)
 at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:66)
 at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35)
 at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:94)
 at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
 ... 20 more

Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
 at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
 at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990)
 at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:342)
 at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2197)
 at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2230)
 at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2025)
 at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:778)
 at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:47)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
 at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
 at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:386)
 at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:330)
 at java.sql.DriverManager.getConnection(DriverManager.java:664)
 at java.sql.DriverManager.getConnection(DriverManager.java:208)
 at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionCreator.makeConnection(DriverManagerConnectionCreator.java:34)
 ... 35 more

Caused by: java.net.ConnectException: Connection refused
 at java.net.PlainSocketImpl.socketConnect(Native Method)
 at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345)
 at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
 at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
 at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
 at java.net.Socket.connect(Socket.java:589)
 at com.mysql.jdbc.StandardSocketFactory.connect(StandardSocketFactory.java:211)
 at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:301)
 ... 50 more

Genèrics

Mètodes genèrics

  • Totes les declaracions genèriques del mètode tenen una secció de paràmetre tipus delimitada per claudàtors d’angle que precedeix el tipus de retorn del mètode (<E> en el següent exemple).
  • Cada secció de paràmetres de tipus conté un o més paràmetres de tipus separats per comes. Un paràmetre tipus, també conegut com a variable de tipus, és un identificador que especifica un nom de tipus genèric.
  • Els paràmetres de tipus han de ser una lletra majúscula. Convencions: ‘T’ per tipus, ‘E’ per a elements de col·leccions, ‘S’ per a serveis i K i V per a claus i valors de mapes.
  • Els paràmetres de tipus es poden utilitzar per declarar el tipus de devolució i actuar com a marcadors per als tipus d’arguments passats al mètode genèric, que es coneixen com a arguments de tipus reals.
  • El cos d’un mètode genèric es declara com el de qualsevol altre mètode. Tingueu en compte que els paràmetres de tipus només poden representar tipus de referència, no tipus primitius (com int, double i char).
public static <E> void printArray( E[] inputArray ) {
    // Display array elements
    for(E element : inputArray) {
        System.out.printf("%s ", element);
    }
    System.out.println();
}

Paràmetres de tipus delimitat

Hi pot haver moments en què voldreu restringir els tipus de tipus que es permeten passar a un tipus de paràmetre. Per exemple, un mètode que opera sobre números només pot voler acceptar instàncies de Number o de les seves subclasses. Per a què serveixen els paràmetres del tipus de límit.

Per declarar un paràmetre de tipus delimitat, enumereu el nom del paràmetre del tipus, seguit de la paraula clau extends, seguit tipus delimitant.

public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
    T max = x;   // assume x is initially the largest
    if(y.compareTo(max) > 0) {
        max = y;   // y is the largest so far
    }
    if(z.compareTo(max) > 0) {
        max = z;   // z is the largest now                 
    }
    return max;   // returns the largest object   
}

Classes genèriques

Una declaració de classe genèrica sembla una declaració de classe no genèrica, tret que el nom de classe sigui seguit per una secció de paràmetre tipus.

Com en els mètodes genèrics, la secció de paràmetres de tipus d’una classe genèrica pot tenir un o més paràmetres de tipus separats per comes. Aquestes classes es coneixen com a classes parametrizades o tipus parametrizats perquè accepten un o més paràmetres.

public class Box<T> {
    private T t;

    public void add(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}

Interfícies genèriques

Imaginem que tenim aquesta interfície:

interface Container<T> {
    T get();
}

La podríem implementar amb una classe genèrica:

public class GenericContainer<T> implements Container<T> {
    private T t;
    public GenericContainer(T t) {
        this.t = t;
    }
    @Override
    public T get() {
        return t;
    }
}

Aquesta implementació serveix per qualsevol tipus de paràmetre. Però també podem fer-ho concretant el tipus i esborrant el paràmetre:

public class StringContainer implements Container<String> {
    private String s;
    public StringContainer(String s) {
        this.s = s;
    }
    @Override
    public String get() {
        return s;
    }
}

Aquesta implementació serveix només per a Strings, però les dues implementen Container.

Optional

La classe Optional<T> és un contenidor que pot o no tenir un valor null. Si el valor està present, isPresent() retorna true i get() retorna el valor.

Ens permet evitar haver de comprovar object != null al codi contínuament, reduint el nombre de NullPointerException i construint APIs més expressives. Per exemple: no cal explicar si un mètode retorna o no null, si el que retorna és un Optional<T>.

S’ha d’utilitzar sempre com a valor de retorn d’un mètode. Evitar sempre que sigui un camp de l’objecte o un paràmetre d’un constructor o mètode.

public Optional<String> findName() { // API expressiva
    String name = findNameInDatabase();
    return Optional.ofNullable(name);
}

Optional<String> optional = findName();
if (optional.isPresent()) {
    System.out.println("trobat " + optional.get());
}
else {
    System.out.println("no trobat!);
}

Classes dins de classes

Java permet definir classes dins de classes. Això permet agrupar i encapsular, fent més llegible i gestionable el codi.

Classes estàtiques imbricades

És una classe relacionada amb la classe exterior, però que no pot fer referència a les variables d’instància d’aquesta. De fet, és com una classe normal que s’ha imbricat dins d’una altra, per qüestions de conveniència.

class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
}

Classes imbricades

Les classes imbricades (no estàtiques) estan associades amb una instància de la classe que les conté.

class OuterClass {
    ...
    class InnerClass {
        ...
    }
}

Si una declaració utilitza una variable o paràmetre amb el mateix nom que una altra a un àmbit que el conté, llavors la declaració encobreix aquesta, i podem accedir-hi amb la sintaxi OuterClass.this.variable.

Classes locals

Una classe es pot definir localment, en qualsevol bloc de codi. Aquestes classes només poden accedir variables locals que siguin finals o efectivament finals: el seu valor no canvia després d’una declaració amb inicialització.

public class LocalClassExample {
    ...
    public static void metode() {
        ...
        class LocalClass {
            ...
        }
    }
}

Classes anònimes

Les classes anònimes permeten declarar i inicialitzar una classe al mateix temps. Són com classes locals, però sense nom. S’utilitzen quan només cal utilitzar una classe un cop. Es consideren expressions.

Es componen de:

  • L’operador new.
  • El nom d’una interfície a implementar o una classe a estendre.
  • Els parèntesis amb els arguments d’un constructor.
  • Un bloc de codi de la classe.
...
Runnable tasca = new Runnable() {
    @Override
    public void run() {
        ...
    }
};    
...

Les classes anònimes no poden contenir declaracions de constructors, ni poden accedir a variables locals que no siguin finals o efectivament finals.

Expressions Lambda

Una expressió lambda és una forma d’instanciar una interfície funcional. Una interfície funcional és aquella que només té un mètode abstracte.

Per exemple, partim d’una interfície funcional i un mètode que la utilitza:

@FunctionalInterface
interface MyFunctionalInterface {		
    String myOnlyMethod(String input);
}

static String process(String input, MyFunctionalInterface mfi) {		
    return mfi.myOnlyMethod(input);
}

Una expressió lambda permet definir i instanciar una classe que implementa la interfície funcional només escrivint el codi que falta: el seu únic mètode.

Com podem cridar aquest mètode? Sense i amb expressió lambda:

String output = process("input", new MyFunctionalInterface(){
    @Override
    public String myOnlyMethod(String input) {
        return input.toUpperCase();
    }
});
// versió lambda
String output = process("input", (input) -> input.toUpperCase());

Les expressions lambda són molt habituals al disseny d’interfícies gràfiques. Exemple de gestió d’un esdeveniment d’un botó (control de tipus Button):

button.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        System.out.println("Botó clicat!");
    } 
});

Aquest codi utilitza classes anònimes.

També podem utilitzar expressions Lambda, ja que els gestors d’esdeveniments són interfícies funcionals (un sol mètode abstracte):

button.setOnAction(
    event -> System.out.println("Botó clicat!")
);

Veure https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

Referències a mètodes

De vegades, una expressió lambda només crida a un mètode. Llavors, és més fàcil i clar fer una referència al mètode. S’utilitza l’operador doble dos punts (double colon) amb el format Objecte::metode.

Tenim quatre tipus de referències. Veiem exemples a partir d’aquest codi:

class Persona {
    private String name;
    private int age;
    Persona(String name, int age) {
        this.name = name;
        this.age = age;
    }	
    String getName() {
        return name;
    }	
    Integer getAge() {
        return age;
    }	
    public int compareByName(Persona p) {
        return getName().compareTo(p.getName());
    }	
    public int compareByAge(Persona p) {
        return getAge().compareTo(p.getAge());
    }	
    @Override
    public String toString() {
        return name + ":" + age;
    }
}
@FunctionalInterface
interface CreadorPersones {
	Persona create(String name, int age);
}	
class ProveidorComparadors {	
    public int compareByName(Persona a, Persona b) {
        return a.getName().compareTo(b.getName());
    }        
    public int compareByAge(Persona a, Persona b) {
        return a.getAge().compareTo(b.getAge());
    }
}

Aquí es veuen els quatre tipus de referències:

// 4: Referència a constructor
CreadorPersones creator; // mètode amb els mateixos paràmetres que el constructor
creator = (name, age) -> new Persona(name, age); // lambda
creator = Persona::new; // ref.mètode

// 1: Referència a mètode estàtic
List<Persona> list = Arrays.asList(createArray(creator));
list.forEach((person) -> System.out.println(person)); // lambda
list.forEach(System.out::println); // ref.mètode

// 2: Referència a mètode d'instància d'un objecte particular
ProveidorComparadors provComparadors = new ProveidorComparadors();
Arrays.sort(createArray(creator), (a, b) -> provComparadors.compareByName(a, b)); // lambda
Arrays.sort(createArray(creator), provComparadors::compareByName); // ref.mètode

// 3: Referència a mètode d'instància d'objecte arbitrari d'un tipus particular
Arrays.sort(createArray(creator), (a, b) -> a.compareByName(b)); // lambda
Arrays.sort(createArray(creator), Persona::compareByName); // ref.mètode

Mètodes default i static a interfícies

Abans de Java 8, les interfícies només podien tenir mètodes abstractes. Java 8 introdueix el concepte de mètodes default que permeten a les interfícies tenir mètodes amb implementació sense afectar les classes que implementen la interfície.

Per definir-los, només cal utilitzar el keyword “default” davant de la signatura.

També s’afegeixen els mètodes static, que permeten agrupar mètodes d’ajuda d’una classe.

public interface Thermometer {
    double getCelsius();
    default double getFahrenheit() {
        return convertToFahrenheit(getCelsius());
    }
    static double convertToFahrenheit(double celsius) {
        return celsius * 1.8 + 32;
    }
}

Programació funcional

Una interfície funcional en Java és una que conté un sol mètode abstracte per a implementar. Pot contenir mètodes default i static implementats.

public interface MyFunctionalInterface {
    public void execute();
}

També podria contenir mètodes default i static:

public interface MyFunctionalInterface2{
    public void execute();

    public default void print(String text) {
        System.out.println(text);
    }

    public static void print(String text, PrintWriter writer) throws IOException {
        writer.write(text);
    }
}

Una interfície funcional es pot implementar amb una expressió lambda:

MyFunctionalInterface lambda = () -> {
    System.out.println("Executing...");
}

Java té una sèrie d’interfícies funcionals a la seva llibreria.

Function

public interface Function<T,R> {
    public <R> apply(T parameter);
}

Consumer

public interface Consumer<T> {
    void accept(T t);
}

Predicate

public interface Predicate {
    boolean test(T t);
}

Supplier

public interface Supplier<T> {
    T get();
}

Modules

Un mòdul de Java (9+) és un conjunt de paquets reutilitzables. Es defineix utilitzant un arxiu anomenat modules-info.java a l’arrel del codi. El format base és:

module modulename {
    // directives
}

El nom del mòdul pot utilitzar punts, com els paquets. Les directives permeten definir:

  • Les seves dependències: requires modulename. A més, si s’afegeix la paraula clau transitive al mig, indica que tercers mòduls que el requereixin també tindran aquestes dependències implícitament.
  • Quins paquets estaran disponibles a altres mòduls: exports packagename.
  • Quins serveis ofereix: provides interfacename with classname.
  • Quins serveix consumeix: uses interfacename.
  • Quins paquets estaran disponibles a altres mòduls mitjançant reflection: opens packagename [to modulename, modulename...].

Regla important a respectar: un paquet només ha d’aparèixer en un mòdul.

A continuació es mostren els module-info.java de tres mòduls que permeten definir una API (my.interface), implementar-la (my.provider) i utilitzar-la (my.consumer).

module my.provider {
    requires my.interface;
    provides app.api.MyService with app.impl.MyServiceImpl; // ofereix el servei
}
module my.consumer {
    requires my.interface;
    uses app.api.MyService; // consumeix el servei
}
module my.interface {
    exports app.api; // per al consumer i el provider
}

El consumer pot buscar totes les implementacions i instanciar-les. En aquest cas, només trobaria una, la del provider. Però podria haver més mòduls amb més implementacions del mateix servei.

ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
for (MyService service: loader) {
    // ...
}

Un servei només s’instancia un cop per cada ServiceLoader.

Línia de comanda

Imaginem que tenim una classe test.HelloModular amb un main que volem executar, i aquesta depèn d’un jar, stleary-json.jar, que no és modular.

Si volem comprovar si un jar és modular, tenim dues eines:

$ jar -tf nom-de-larxiu.jar # llista el contingut
$ jar --file=nom-de-larxiu.jar --describe-module # descriu la modularitat

Si no volem utilitzar mòduls en la nostra aplicació, podem compilar i executar així:

$ javac -cp lib/stleary-json.jar -d classes `find src -name *.java`
$ java -cp classes:lib/stleary-json.jar test.HelloModular

Si en canvi volem utilitzar mòduls, necessitem crear un module-info.java com aquest:

module hello_modular {
    requires org.json; // nom que hi ha a Automatic-Module-Name del jar
}

stlearly-json.jar és un mòdul automàtic. El seu nom sol derivar del nom del jar, excepte si hi ha un META-INF/MANIFEST.MF al jar i conté un nom alternatiu amb la clau Automatic-Module-Name. En aquest cas existeix, i el contingut és “org.json”.

i compilar i executar així:

$ javac -p lib/stleary-json.jar -d classes `find src -name *.java`
$ java -p classes:lib/stleary-json.jar -m hello_modular/test.HelloModular

També podem crear un jar enlloc d’utilitzar la carpeta classes:

$ jar --create --file lib/hello-modular.jar -C classes .
$ java -p lib/hello-modular.jar:lib/stleary-json.jar -m hello_modular/test.HelloModular

Referències