Las clases anidadas inner, anónimas y locales en Java

Escrito por el .
java planeta-codigo programacion
Comentarios

Java

Por regla general en Java cada clase se define en su propio archivo de código fuente, pdor ejemplo, una clase de nombre Order se ha de definir en el archivo Order.java. Sin embargo, esta no es la única forma de definir clases, es posible definir clases inner y anónimas que evita tener que crear un nuevo archivo de código fuente.

Las clases inner se definen dentro de otra clase cuando esa clase inner tiene alta cohesión (su lógica está muy relacionada con la clase que la contiene), en algunos casos se emplean para ocultar los tipos que implementan la lógica. Dependiendo de si la clase inner debe acceder a los datos de la clase que la contiene o no la clase inner se define como no estática o como estática con la palabra reservada static. Las clases inner estáticas no necesitan una referencia a la clase que la contiene y por ello son algo más eficientes y el método preferido de definirlas, si la clase inner debe acceder a los miembros de la clase contenedora hay que definirla como no estática. Para desambiguar la referencia this y miembros con el mismo nombre de la clase inner con la de la clase contenedora se puede utilizar en el ejemplo Order.this.products quitando los static de las clases.

Las clases anónimas pueden definirse en la misma línea de código donde se declara su referencia, se denominan anónimas porque no se les asigna un nombre como en el ejemplo es el caso de la clase calculadora de precio para el descuento del más barato gratis.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package io.github.picodotdev.blogbitix.javainnerclasses;

import java.math.BigDecimal;

public class Product implements Comparable<Product> {

    private BigDecimal price;

    public Product(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getPrice() {
        return price;
    }

    @Override
    public int compareTo(Product o) {
        return price.compareTo(o.getPrice());
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package io.github.picodotdev.blogbitix.javainnerclasses;

import java.util.Collection;
import java.math.BigDecimal;
import java.util.stream.Collectors;

public class Order {

    Collection<Product> products;

    // Inner
    public enum Discount {
        NORMAL, DISCOUNT_10, CHEAPEST_FREE
    }

    public Order(Collection<Product> products) {
        this.products = products;
    }

    public BigDecimal calculatePrice(Discount discount) {
        return new PriceCalculatorFactory().getInstance(discount).calculate(products);
    }

    // Inner static
    private static class PriceCalculatorFactory {
        PriceCalculator getInstance(Discount discount) {
            switch (discount) {
                case DISCOUNT_10:
                    return new DiscountCalculator(new BigDecimal("0.90"));                    
                case CHEAPEST_FREE:
                    // Anonymous
                    return new NormalCalculator() {
                        @Override
                        BigDecimal calculate(Collection<Product> products) {
                            Collection<Product> paid = products.stream().sorted().skip(1).collect(Collectors.toList());
                            return super.calculate(paid);
                        }
                    };
                case NORMAL:
                default:
                  return new NormalCalculator();
            }
        }
    }

    // Inner static
    private static abstract class PriceCalculator {
        abstract BigDecimal calculate(Collection<Product> products);
    }

    // Inner static
    private static class NormalCalculator extends PriceCalculator {

        @Override
        BigDecimal calculate(Collection<Product> products) {
            return products.stream().map(i -> i.getPrice()).reduce(new BigDecimal("0.00"), (a, b) -> { return a.add(b); });
        }
    }

    // Inner static
    private static class DiscountCalculator extends PriceCalculator {

        private BigDecimal discount;

        public DiscountCalculator(BigDecimal discount) {
            this.discount = discount;
        }

        @Override
        BigDecimal calculate(Collection<Product> products) {
            PriceCalculator calculator = new NormalCalculator();
            return calculator.calculate(products).multiply(discount);
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package io.github.picodotdev.blogbitix.javainnerclasses;

import java.util.Collection;
import java.util.ArrayList;
import java.math.BigDecimal;
import java.text.DecimalFormat;

public class Main {

    public static void main(String[] args) {
        Collection<Product> products = new ArrayList<>();
        products.add(new Product(new BigDecimal("5.0")));
        products.add(new Product(new BigDecimal("10.0")));
        products.add(new Product(new BigDecimal("15.0")));
        Order order = new Order(products);

        DecimalFormat df = new DecimalFormat("#,###.00");

        System.out.printf("Price (normal): %s%n", df.format(order.calculatePrice(Order.Discount.NORMAL)));
        System.out.printf("Price (discount 10%%): %s%n", df.format(order.calculatePrice(Order.Discount.DISCOUNT_10)));
        System.out.printf("Price (chapest free): %s%n", df.format(order.calculatePrice(Order.Discount.CHEAPEST_FREE)));
    }
}
1
2
3
Price (normal): 30,00
Price (discount 10%): 27,00
Price (chapest free): 25,00

Para los programadores en Java seguramente esto de las clases inner y anónimas no es nada nuevo pero ¿conoces las clases locales? Dentro de un método también se puede definir una clase, llamada por ello local. Las clases locales no son habituales y para su uso su funcionalidad ha de estar altamente cohesionado con el método, un posible uso es para realizar validaciones o formateos que sean un poco complejos. El siguiente ejemplo de clase local PhoneNumber muestra su uso.

En la sección de clases anidadas o nested classes del tutorial sobre clases y objetos se explica más detalladamente estas capacidades del lenguaje Java.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew run.