Matriz inmutable en Java

158

¿Existe una alternativa inmutable a las matrices primitivas en Java? Hacer una matriz primitiva en finalrealidad no impide que uno haga algo como

final int[] array = new int[] {0, 1, 2, 3};
array[0] = 42;

Quiero que los elementos de la matriz sean inmutables.


fuente
43
Hazte un favor y deja de usar matrices en Java para cualquier cosa que no sea 1) io 2) gran cantidad de números 3) si necesitas implementar tu propia Lista / Colección (lo cual es raro ). Son extremadamente inflexibles y anticuados ... como acaba de descubrir con esta pregunta.
whaley
39
@Whaley, y actuaciones, y código donde no necesita arreglos "dinámicos". Las matrices siguen siendo útiles en muchos lugares, no es tan raro.
Colin Hebert
10
@Colin: sí, pero son muy restrictivos; es mejor acostumbrarse a pensar "¿realmente necesito una matriz aquí o puedo usar una lista en su lugar?"
Jason S
77
@Colin: solo optimiza cuando lo necesites. Cuando pasas medio minuto agregando algo que, por ejemplo, amplía una matriz o mantienes algún índice fuera del alcance de un ciclo for, ya has perdido parte del tiempo de tu jefe. Primero construya, optimice cuándo y dónde sea necesario, y en la mayoría de las aplicaciones, eso no es reemplazar Listas con matrices.
fwielstra
9
Bueno, ¿por qué nadie mencionó int[]y new int[]es MUCHO más fácil de escribir que List<Integer>y new ArrayList<Integer>? XD
lcn

Respuestas:

164

No con matrices primitivas. Deberá usar una Lista o alguna otra estructura de datos:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));
Jason S
fuente
18
De alguna manera nunca supe sobre Arrays.asList (T ...). Supongo que ahora puedo deshacerme de mi ListUtils.list (T ...).
MattRS
3
Por cierto, Arrays.asList da una lista no modificable
mauhiz
3
@tony que ArrayList no es java.util's
mauhiz
11
@mauhiz noArrays.asList es inmodificable. docs.oracle.com/javase/7/docs/api/java/util/… "Devuelve una lista de tamaño fijo respaldada por la matriz especificada. (Cambios en la lista devuelta" escribir "en la matriz)"
Jason S
77
@JasonS bueno, de Arrays.asList()hecho parece inmodificable en el sentido de que no puedes addo los removeartículos devueltos java.util.Arrays.ArrayList(que no deben confundirse con java.util.ArrayList): estas operaciones simplemente no se implementan. ¿Quizás @mauhiz intenta decir esto? Pero, por supuesto, puede modificar elementos existentes con List<> aslist = Arrays.asList(...); aslist.set(index, element), por lo que java.util.Arrays.ArrayListciertamente no es inmutable , QED agregó el comentario solo para enfatizar la diferencia entre el resultado de asListy el normalArrayList
NIA
73

Mi recomendación es no utilizar una matriz o una unmodifiableListsino utilizar guayaba 's ImmutableList , que existe para este fin.

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);
ColinD
fuente
3
+1 ImmutableList de Guava es incluso mejor que Collections.unmodifiableList, porque es un tipo separado.
sleske
29
ImmutableList suele ser mejor (depende del caso de uso) porque es inmutable. Collections.unmodifiableList no es inmutable. Más bien, es una vista que los receptores no pueden cambiar, pero la fuente original PUEDE cambiar.
Charlie Collins
2
@CharlieCollins, si no hay forma de acceder a la fuente original, ¿es Collections.unmodifiableListsuficiente para hacer que una Lista sea inmutable?
savanibharat
1
@savanibharat Sí.
Solo un estudiante
20

Como otros han señalado, no puede tener matrices inmutables en Java.

Si necesita absolutamente un método que devuelva una matriz que no influya en la matriz original, deberá clonar la matriz cada vez:

public int[] getFooArray() {
  return fooArray == null ? null : fooArray.clone();
}

Obviamente, esto es bastante costoso (ya que creará una copia completa cada vez que llame al getter), pero si no puede cambiar la interfaz (para usar un Listpor ejemplo) y no puede arriesgarse a que el cliente cambie sus componentes internos, entonces Puede ser necesario.

Esta técnica se llama hacer una copia defensiva.

Joachim Sauer
fuente
3
¿Dónde mencionó que necesitaba un captador?
Erick Robertson
66
@Erik: no lo hizo, pero ese es un caso de uso muy común para estructuras de datos inmutables (modifiqué la respuesta para referirme a los métodos en general, ya que la solución se aplica en todas partes, incluso si es más común en getters).
Joachim Sauer
77
¿Es mejor usar clone()o Arrays.copy()aquí?
kevinarpe
Por lo que recuerdo, según "Java efectivo" de Joshua Bloch, clone () es definitivamente la forma preferida.
Vincent
1
Última oración del ítem 13, "Anular juiciosamente al clon": "Como regla, la funcionalidad de copia es mejor proporcionada por constructores o fábricas. Una excepción notable a esta regla son los arreglos, que se copian mejor con el método de clon".
Vincent
14

Hay una forma de hacer una matriz inmutable en Java:

final String[] IMMUTABLE = new String[0];

Las matrices con 0 elementos (obviamente) no se pueden mutar.

En realidad, esto puede ser útil si está utilizando el List.toArraymétodo para convertir Lista en una matriz. Dado que incluso una matriz vacía ocupa algo de memoria, puede guardar esa asignación de memoria creando una matriz vacía constante y siempre pasándola al toArraymétodo. Ese método asignará una nueva matriz si la matriz que pasa no tiene suficiente espacio, pero si la tiene (la lista está vacía), devolverá la matriz que pasó, permitiéndole reutilizar esa matriz cada vez que llametoArray a un vaciar List.

final static String[] EMPTY_STRING_ARRAY = new String[0];

List<String> emptyList = new ArrayList<String>();
return emptyList.toArray(EMPTY_STRING_ARRAY); // returns EMPTY_STRING_ARRAY
Brigham
fuente
3
Pero esto no ayuda al OP a lograr una matriz inmutable con datos.
Sridhar Sarnobat
1
@ Sridhar-Sarnobat Ese es el punto de la respuesta. No hay forma en Java de hacer una matriz inmutable con datos en ella.
Brigham
1
Me gusta más esta sintaxis más simple: Private static final String [] EMPTY_STRING_ARRAY = {}; (Obviamente no he descubierto cómo hacer "mini-Markdown". Un botón de vista previa sería bueno.)
Wes
6

Otra respuesta

static class ImmutableArray<T> {
    private final T[] array;

    private ImmutableArray(T[] a){
        array = Arrays.copyOf(a, a.length);
    }

    public static <T> ImmutableArray<T> from(T[] a){
        return new ImmutableArray<T>(a);
    }

    public T get(int index){
        return array[index];
    }
}

{
    final ImmutableArray<String> sample = ImmutableArray.from(new String[]{"a", "b", "c"});
}
aeracode
fuente
¿De qué sirve copiar esa matriz en el constructor, ya que no proporcionamos ningún método de establecimiento?
vijaya kumar
Un contenido de la matriz de entrada todavía se puede modificar más adelante. Queremos preservar su estado original, así que lo copiamos.
aeracode
4

A partir de Java 9 puede usar List.of(...), JavaDoc .

Este método devuelve un inmutable Listy es muy eficiente.

rmuller
fuente
3

Si necesita (por razones de rendimiento o para ahorrar memoria) 'int' nativo en lugar de 'java.lang.Integer', entonces probablemente deba escribir su propia clase de contenedor. Hay varias implementaciones de IntArray en la red, pero ninguna (encontré) era inmutable: Koders IntArray , Lucene IntArray . Probablemente hay otros.

Thomas Mueller
fuente
3

Desde Guava 22, desde el paquete com.google.common.primitivespuede usar tres nuevas clases, que tienen una huella de memoria más baja en comparación con ImmutableList.

También tienen un constructor. Ejemplo:

int size = 2;
ImmutableLongArray longArray = ImmutableLongArray.builder(size)
  .add(1L)
  .add(2L)
  .build();

o, si el tamaño se conoce en tiempo de compilación:

ImmutableLongArray longArray = ImmutableLongArray.of(1L, 2L);

Esta es otra forma de obtener una vista inmutable de una matriz para primitivas Java.

JeanValjean
fuente
1

No, esto no es posible. Sin embargo, uno podría hacer algo como esto:

List<Integer> temp = new ArrayList<Integer>();
temp.add(Integer.valueOf(0));
temp.add(Integer.valueOf(2));
temp.add(Integer.valueOf(3));
temp.add(Integer.valueOf(4));
List<Integer> immutable = Collections.unmodifiableList(temp);

Esto requiere el uso de contenedores, y es una Lista, no una matriz, pero es lo más cercano que obtendrá.


fuente
44
No es necesario escribir todos esos valueOf()mensajes, el autoboxing se encargará de eso. También Arrays.asList(0, 2, 3, 4)sería mucho más conciso.
Joachim Sauer
@Joachim: El punto de uso valueOf()es utilizar el caché interno de objetos Integer para reducir el consumo de memoria / reciclaje.
Esko
44
@Esko: lea la especificación de autoboxing. Hace exactamente lo mismo, así que no hay diferencia aquí.
Joachim Sauer
1
@John Nunca obtendrás un NPE convirtiendo un inta un Integersin embargo; solo hay que tener cuidado al revés.
ColinD
1
@ John: tienes razón, puede ser peligroso. Pero en lugar de evitarlo por completo, probablemente sea mejor comprender el peligro y evitarlo.
Joachim Sauer
1

En algunas situaciones, será más ligero usar este método estático de la biblioteca Google Guava: List<Integer> Ints.asList(int... backingArray)

Ejemplos:

  • List<Integer> x1 = Ints.asList(0, 1, 2, 3)
  • List<Integer> x1 = Ints.asList(new int[] { 0, 1, 2, 3})
kevinarpe
fuente
1

Si desea evitar la mutabilidad y el boxeo, no hay forma de salir de la caja. Pero puede crear una clase que contenga una matriz primitiva dentro y proporcione acceso de solo lectura a los elementos a través de métodos.

Nombre para mostrar
fuente
1

El método of (E ... elements) en Java9 se puede usar para crear una lista inmutable usando solo una línea:

List<Integer> items = List.of(1,2,3,4,5);

El método anterior devuelve una lista inmutable que contiene un número arbitrario de elementos. Y agregar cualquier número entero a esta lista resultaría en una java.lang.UnsupportedOperationExceptionexcepción. Este método también acepta una sola matriz como argumento.

String[] array = ... ;
List<String[]> list = List.<String[]>of(array);
akhil_mittal
fuente
0

Si bien es cierto que Collections.unmodifiableList()funciona, a veces puede tener una gran biblioteca con métodos ya definidos para devolver matrices (por ejemplo String[]). Para evitar romperlos, puede definir matrices auxiliares que almacenarán los valores:

public class Test {
    private final String[] original;
    private final String[] auxiliary;
    /** constructor */
    public Test(String[] _values) {
        original = new String[_values.length];
        // Pre-allocated array.
        auxiliary = new String[_values.length];
        System.arraycopy(_values, 0, original, 0, _values.length);
    }
    /** Get array values. */
    public String[] getValues() {
        // No need to call clone() - we pre-allocated auxiliary.
        System.arraycopy(original, 0, auxiliary, 0, original.length);
        return auxiliary;
    }
}

Probar:

    Test test = new Test(new String[]{"a", "b", "C"});
    System.out.println(Arrays.asList(test.getValues()));
    String[] values = test.getValues();
    values[0] = "foobar";
    // At this point, "foobar" exist in "auxiliary" but since we are 
    // copying "original" to "auxiliary" for each call, the next line
    // will print the original values "a", "b", "c".
    System.out.println(Arrays.asList(test.getValues()));

No es perfecto, pero al menos tiene "matrices pseudoinmutables" (desde la perspectiva de la clase) y esto no romperá el código relacionado.

miguelt
fuente
-3

Bueno ... las matrices son útiles para pasar como constantes (si lo fueran) como parámetros de variantes.

Martín
fuente
¿Qué es un "parámetro de variantes"? Nunca lo oí.
sleske
2
El uso de matrices como constantes es exactamente donde muchas personas fallan. La referencia es constante, pero el contenido de la matriz es mutable. Una persona que llama / cliente podría cambiar el contenido de su "constante". Esto probablemente no sea algo que quieras permitir. Use una lista inmutable.
Charlie Collins
@CharlieCollins incluso esto puede ser pirateado a través de la reflexión. Java es un lenguaje inseguro en términos de mutabilidad ... y esto no va a cambiar.
Nombre para mostrar el
2
@SargeBorsch Eso es cierto, pero cualquiera que haga piratería basada en la reflexión debería saber que está jugando con fuego modificando cosas a las que el editor de API podría no haber querido que accedan. Mientras que, si una API devuelve un int[], la persona que llama podría suponer que puede hacer lo que quiera con esa matriz sin afectar las partes internas de la API.
daiscog