Existem classes que são criadas apenas para abrigar métodos e campos estáticos. Esse tipo de classe ganhou má reputação ultimamente porque os programadores simplesmente abusam demais desse tipo de abordagem. No entanto, essas classes ainda podem ser bastante úteis quando precisamos agrupar métodos relacionados em valores primitivos ou matrizes, como ocorre na API no Java em java.lang.Math e java.util.Arrays. Abaixo podemos verificar alguns códigos encontrados dentro de java.lang.Math e java.util.Arrays:

public final class Math {

	private Math() {}

	public static final double E = 2.7182818284590452354;
	public static final double PI = 3.14159265358979323846;

	public static double sin(double a) {
		return StrictMath.sin(a);
	}

	public static double cos(double a) {
		return StrictMath.cos(a);
	}

	public static double tan(double a) {
		return StrictMath.tan(a);
	}

	public static double toRadians(double angdeg) {
		return angdeg / 180.0 * PI;
	}

	public static double toDegrees(double angrad) { 		
		return angrad * 180.0 / PI;
	}

	public static double log10(double a) {
		return StrictMath.log10(a);
	}

	public static double sqrt(double a) { 
		return StrictMath.sqrt(a);
	}
}
Listagem 1. Exemplo de implementação da classe Math em Java

Para o código fonte completo da classe Math, confira em [1] nas referências bibliográficas.


package java.util;

import java.lang.reflect;

public class Arrays {
	private Arrays() {}

	public static void sort(int[] a) {
		DualPivotQuicksort.sort(a);
	}

	public static void sort(int[] a, int fromIndex, int toIndex) {
		rangeCheck(a.length, fromIndex, toIndex);
		DualPivotQuicksort.sort(a, fromIndex, toIndex - 1);
	}

	public static void sort(long[] a) {
		DualPivotQuicksort.sort(a);
	}

	public static void sort(long[] a, int fromIndex, int toIndex) {
		rangeCheck(a.length, fromIndex, toIndex);
		DualPivotQuicksort.sort(a, fromIndex, toIndex - 1);
	}

	public static void sort(short[] a) {
		DualPivotQuicksort.sort(a);
	}

	public static void sort(char[] a) {
		DualPivotQuicksort.sort(a);
	}

	public static void fill(char[] a, char val) {
		for (int i = 0, len = a.length; i < len; i++)
			a[i] = val;
	}
	
	public static void fill(char[] a, int fromIndex, int toIndex, char val) {
		rangeCheck(a.length, fromIndex, toIndex);
		for (int i = fromIndex; i < toIndex; i++)
			a[i] = val;
	}

	public static void fill(byte[] a, byte val) {
		for (int i = 0, len = a.length; i < len; i++)
 			a[i] = val;
	}
}
Listagem 2. Exemplo de implementação da classe Arrays em Java

Para o código fonte completo da classe Arrays, confira em [2] nas referências bibliográficas.

Além disso, essas classes também podem ser utilizadas para agrupar métodos estáticos (inclui-se os métodos de fabricação) para objetos que implementem uma interface específica, como por exemplo, ocorre em java.util.Collections.

Uma situação interessante que pode ser observada nessas classes utilitárias é que elas não foram projetadas para serem instanciadas, afinal, nem faria sentido ter uma instância dessas classes. Se o programador não fornecer um construtor, o compilador fornecerá um construtor padrão público sem parâmetros, o que para o usuário é igual a qualquer outro construtor criado na classe pelo próprio usuário. Muitas APIs não fornecem nenhum construtor, o que acaba acarretando involuntariamente na criação de instâncias de classes.

Uma forma de impor a não instanciação de uma classe utilitária é tornando essa classe abstrata, o que não é considerada uma boa prática. Apesar de uma classe abstrata não poder ser instanciada, essa classe poderia gerar subclasses dela, que por sua vez pode ser instanciada. Além disso, classes abstratas induzem o usuário a pensar de forma errada que essa classe foi projetada para herança.

Utilizando Construtores Privados para garantir a não-instanciação

A melhor forma de garantir essa não-instanciação da classe utilitária é através da definição de construtores privados, como ilustra o exemplo abaixo:


public class ClasseUtilitaria {
	private ClasseUtilitaria() {
		throw new AssertionError();
	}
	//outros métodos da classe utilitaria
}
Listagem 3. Exemplo de implementação com construtores privados

A Listagem 1 anterior da classe Math também ilustra o mesmo conceito, onde a classe utilitária Math faz uso de um construtor privado para não permitir a sua instanciação externa.

Já que o construtor é explicitamente privado, essa classe não pode ser instanciada de fora dessa classe. No entanto, se ela for instanciada acidentalmente dentro da própria classe, uma exceção é disparada. Essa exceção não é necessária, porém garante que essa classe nunca seja instanciada em qualquer que seja o momento.

O construtor privado também impede que a classe tenha subclasses, isso acontece porque os construtores sempre devem chamar um construtor da superclasse, explicita ou implicitamente, e nesse caso ,como o construtor é privado, a subclasse não teria um construtor de superclasse acessível para chamar.

Conclusão

As classes utilitárias são bastante utilizadas em diversos frameworks e diversas aplicações corporativas. No entanto, muitas são mal elaboradas, expondo situações que não deveriam ser expostas. Outras, por sua vez, utilizam-se de más práticas como definindo-as como abstratas para inibir a instanciação da classe. Porém, neste artigo vimos que o simples uso de construtores privados pode inibir essas más práticas que muitas vezes confundem os usuários da API ou os levar a supor situações errôneas.