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
| packageio.github.picodotdev.blogbitix.javainnerclasses;importjava.math.BigDecimal;publicclassProductimplementsComparable<Product>{privateBigDecimalprice;publicProduct(BigDecimalprice){this.price=price;}publicBigDecimalgetPrice(){returnprice;}@OverridepublicintcompareTo(Producto){returnprice.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
| packageio.github.picodotdev.blogbitix.javainnerclasses;importjava.util.Collection;importjava.math.BigDecimal;importjava.util.stream.Collectors;publicclassOrder{Collection<Product>products;// Inner
publicenumDiscount{NORMAL,DISCOUNT_10,CHEAPEST_FREE}publicOrder(Collection<Product>products){this.products=products;}publicBigDecimalcalculatePrice(Discountdiscount){returnnewPriceCalculatorFactory().getInstance(discount).calculate(products);}// Inner static
privatestaticclassPriceCalculatorFactory{PriceCalculatorgetInstance(Discountdiscount){switch(discount){caseDISCOUNT_10:returnnewDiscountCalculator(newBigDecimal("0.90"));caseCHEAPEST_FREE:// Anonymous
returnnewNormalCalculator(){@OverrideBigDecimalcalculate(Collection<Product>products){Collection<Product>paid=products.stream().sorted().skip(1).collect(Collectors.toList());returnsuper.calculate(paid);}};caseNORMAL:
default:returnnewNormalCalculator();}}}// Inner static
privatestaticabstractclassPriceCalculator{abstractBigDecimalcalculate(Collection<Product>products);}// Inner static
privatestaticclassNormalCalculatorextendsPriceCalculator{@OverrideBigDecimalcalculate(Collection<Product>products){returnproducts.stream().map(i->i.getPrice()).reduce(newBigDecimal("0.00"),(a,b)->{returna.add(b);});}}// Inner static
privatestaticclassDiscountCalculatorextendsPriceCalculator{privateBigDecimaldiscount;publicDiscountCalculator(BigDecimaldiscount){this.discount=discount;}@OverrideBigDecimalcalculate(Collection<Product>products){PriceCalculatorcalculator=newNormalCalculator();returncalculator.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
| packageio.github.picodotdev.blogbitix.javainnerclasses;importjava.util.Collection;importjava.util.ArrayList;importjava.math.BigDecimal;importjava.text.DecimalFormat;publicclassMain{publicstaticvoidmain(String[]args){Collection<Product>products=newArrayList<>();products.add(newProduct(newBigDecimal("5.0")));products.add(newProduct(newBigDecimal("10.0")));products.add(newProduct(newBigDecimal("15.0")));Orderorder=newOrder(products);DecimalFormatdf=newDecimalFormat("#,###.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 localPhoneNumber 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
.