Programación en Java/Vectores (Arreglos)

Un arreglo en Java es una colección de valores del mismo tipo, en la cual se pueden almacenar valores de tipo primitivo o referencias a objetos.

Java trata los arreglos como objetos, por lo que su sintaxis de declaración y creación no difiere mucho de lo habitual.

//Declaración de un arreglo

int[] numeros = null;

Como cualquier variable de referencia es recomendable inicializar a null.

Para crear un arreglo debemos utilizar el operador de instanciacion new en la sentencia, indicando el tamaño del arreglo entre corchetes con un número entero positivo o a través de la evaluación de una expresión.

//Creación de un arreglo tamaño 5

numeros = new int[5];

//Creación de arreglo mediante el

//resultado de una expresión

int y=5,x=2;

numeros = new int[y*x];

Tras ejecutarse estas sentencias el compilador reserva espacio de memoria para un objeto de arreglo de enteros, el cual es referenciado por la variable numeros, todos sus índices son inicializados a 0 que es el valor por defecto para los enteros. Lo habitual es muchas veces realizar la declaración y la creación de un arreglo en una sola sentencia.

int[] numeros = new int[5];

Para inicializar un arreglo con determinados valores, se deben indicar entre llaves separados por comas

int[] numeros = {7,20,34,2,43};

El compilador calcula el tamaño del arreglo conforme al número de valores que indiquemos entre llaves. La sentencia de inicialización de un arreglo también puede incluir expresiones de cálculo o de conversión de tipos permitidos por el compilador

//inicialización con expresión de cálculo

int[] numeros ={7,10+10,12*2};

//inicialización con expresiones de conversión

int[] numeros = {7,((int)32,9),(Integer.valueOf("34"))};

La iteración de arreglos es realizada mediante construcciones del lenguaje, donde las más comúnmente usadas son for y foreach.

//Iteración mediante sentencia for

for(int i = 0;i < numeros.length;i++)

{

System.out.println(numeros[i]);

}

//Iteración mediante sentencia foreach

for(int i : numeros)

System.out.println(i);

El acceso a los valores de un arreglo se realiza colocando entre corchetes el subíndice indicado.

Todo objeto de tipo arreglo tiene una propiedad llamada length, que es una variable final que almacena el tamaño del arreglo al momento de crearse o inicializarse, al ser final hace que el arreglo no pueda modificar su tamaño una vez creado.

Clonación de arreglos editar

Para realizar una copia exacta de los valores de un arreglo en otro existe una manera sencilla de hacerlo. Al igual que en las demás clases, el tipo arreglo hereda de la clase Object que contiene el método clone. Un tipo arreglo sobrescribe el método clone que hereda de Object al implementar la interfaz Cloneable teniendo su propia versión del mismo.

int[] numeros = {23,7,40,21,5};

//Clonación del arreglo numeros

int[] copiaNumeros = numeros.clone();

El método clone crea un nuevo objeto en memoria con un espacio reservado distinto al original; es decir la variable copiaNumeros hace referencia a un objeto distinto que el que hace referencia la variable numeros pero con los mismos valores en sus subíndices y el mismo tamaño por consiguiente. Para comprobarlo veamos las siguientes sentencias.

//Comprobación que el código hash es distinto

System.out.println(numeros == copiaNumeros);

//Las modificaciones en un arreglo no afectan al otro

numeros[0] = 103;

for(int i : numeros)

System.out.println(i);

for(int i = copiaNumeros)

System.out.println(i);

Si las variables de referencia apuntarán al mismo objeto arreglo en memoria, el código hash sería el mismo en ambos y los cambios realizados en un arreglo afectarían al otro.

Existe una manera más flexible de realizar una copia de un arreglo y es por medio del método estático arraycopy de la clase System. Este método copia los valores de un arreglo origen a otro destino, en los subíndices indicados en sus argumentos.

La cabecera de este método tiene el siguiente detalle:

public static void arraycopy(Object arregloOrigen, int posOrigen,Object arregloDestino, int posDestino, int tamaño)

Ejemplo:

int[] numeros = {3,72,20,10,2};

int[] copiaNumeros = new int[3];

//Copia los 3 últimos valores del arreglo numeros

System.arraycopy(numeros,2,copiaNumeros,0,copiaNumeros.length);

for(int i: copiaNumeros)

System.out.println(i);


La salida es:

20

10

2

El método arraycopy lanzará las siguientes excepciones según las siguientes circunstancias:

NullPointerException - cuando alguna de las variables de referencia arregloOrigen ó arregloDestino reciban un valor null.

IndexPointException - cuando las variables posOrigen, posDestino ó tamaño tengan valor negativo. Si la variable posOrigen + tamaño es mayor que el tamaño del arreglo origen. Si la variable posDestino + tamaño es mayor que el tamaño del arreglo destino.

ArrayStoreException - Si el argumento arregloOrigen o arregloDestino no son objetos de tipo arreglo. Si los arreglos origen y destino contienen elementos de tipo primitivo distintos. Cuando cualquiera de los dos arreglos contenga elementos de tipo primitivo y el otro contenga elementos que sean referencias a objetos.

Arreglos de referencias a objetos editar

Los arreglos pueden almacenar en sus valores referencias a objetos, el uso de este tipo de arreglos tiene una cierta diferencia del tipo primitivo en sus elementos que veremos a continuación. Ejemplo:

public class Ejemplo{

public static void main(String args[]) {

//Declaración y creación de un arreglo de referencias de tipo Empleado

Empleado[] empleados = new Empleado[4];

//LLamada a método para instanciar las referencias a la clase Empleado

instanciarArreglo(empleados);

empleados[0].setNombre("Luis");

empleados[1].setNombre("Alexis");

empleados[2].setNombre("Ana");

empleados[3].setNombre("Carlos");


for(Empleado emp: empleados){

System.out.println(emp);

}


}

//Método estático que instancia cada referencia a la clase Empleado contenida en el arreglo empleados

private static void instanciarArreglo(Empleado[] emp){

for(int i = 0; i < emp.length;i++)

{

emp[i] = new Empleado();

}

//Muestra que las referencias que apuntan a objetos se pasan por valor a los métodos

emp = new Empleado[7];

}

public class Empleado{


private String nombre = null;


public void setNombre(String nombre)

{

this.nombre = nombre;

}


public String getNombre()

{

return this.nombre;

}

}

La salida del código anterior es:

Luis

Alexis

Ana

Carlos

La variable empleados hace referencia a un objeto arreglo de tipo Empleado de 4 indices, cada elemento en el arreglo es una referencia a un posible objeto Empleado en memoria; digo posible porque aún NO apuntan a ningún objeto Empleado, cada referencia (empleados[n]) contiene su valor por defecto que es null. En el método instanciarArreglo se enlaza cada referencia contenida en el arreglo empleados con un objeto de tipo Empleado (al utilizar el operador de instanciacion new y llamar al constructor predeterminado de la clase Empleado). En ese momento se crean 4 objetos de tipo Empleado con sus respectivas referencias contenidas en los indices del arreglo empleados.

Nota: Cabe destacar que el paso de parámetros que sean referencias a objetos como argumentos a métodos se hace por valor; es decir, la variable de referencia no puede apuntar a un objeto que sea distinto, esta sentencia emp = new Empleado[7]; no tiene ningún efecto al terminar la ejecución del método, la variable emp seguirá apuntando a un objeto arreglo de tipo Empleado de 4 indices. Pero los cambios que se realicen en los atributos del objeto a la que apunte la referencia si son reconocidos al terminar la ejecución del método.

Ejemplos editar

//Encontrar en número mayor en un arreglo de tamaño n

       int[] numeros = {12,19,5,278,3,28,33,25};

       //Se parte de la base que el mayor se encuentra en el último indice del arreglo

       //para realizar las iteraciones

       int mayor = numeros[numeros.length-1];

       for(int i = 0; i < numeros.length;i++){

               if(numeros[i] > mayor)

               {

                   mayor = numeros[i];

               }

       }

       System.out.println("El número mayor es : " + mayor);

La salida es: 278

//Encontrar todos los números primos en un arreglo de tamaño n y mostrarlos por pantalla

int[] numeros = {12,19,5,28,3,33,25,379,256,997};

int num,cont=1;

for(int i =0; i < numeros.length;i++)

{

num = numeros[i];

for(int j = 2; j <= num;j++)

{

if(num%j == 0){

cont++;

}

}

if(cont == 2){

System.out,println(num + " es numero primo");

}

cont = 1;

}

La salida es:

19 es numero primo

5 es numero primo

379 es numero primo

997 es numero primo


Arreglos Multidimensionales editar

Son arreglos que tienen mas de una dimensión, los mas comunes son las matrices o arreglos bidimensionales, en donde sus indices representan las filas y columnas respectivamente. Para crear una matriz se utiliza una sintaxis similar a los arreglos unidimensionales, en donde se colocan dos par de corchetes.

//Declaración e instanciación de un arreglo de dos dimensiones

String[][] paises = new String[4][7];

La sentencia anterior crear una matriz de 4 filas con 7 columnas con sus elementos inicializados a null, el valor por defecto para objetos de tipo String.

Java trata las matrices como arreglos de tamaño n referenciados entre si, asi la matriz anterior es tratada como un arreglo de tamaño 4 al cual se enlazan arreglos de tamaño siete, cada uno con su respectivo atributo length.

//Muestra el tamaño del arreglo que representa las filas

System.out.println("La matriz tiene " + paises.length + " filas");

//Muestra el tamaño del primer arreglo que representa las columnas

System.out.println("La matriz tiene " + paises[0].length + " columnas ");

Hay una forma para inicializar una matriz con valores determinados en donde se pueden declarar columnas de distinto tamaño, dado por la cantidad de valores que indiquemos en los paréntesis de llave. Para declarar e instanciar la matriz anterior, inicializando sus indices a valores dados se coloca la siguiente sintaxis:

String[][] paises2 = {{"China","Japón","Corea","Bélgica","Lituania","Finlandia","Rusia"},

{"Kenia","Ghana","Mozambique","Birmania","Omán","Tailandia","Bangladesh","Irán"},

{"Brasil","Perú","Bolivia","Canadá","México","Estados Unidos","Costa Rica"},

{"Italia","Francia","Inglaterra","Polonia","Dinamarca","España","Noruega"}};

En esta matriz se declararon 4 filas con arreglos enlazados de tamaño 7 a excepción del arreglo paises2[1] de tamaño 8.

El arreglo anterior se podría declarar de la siguiente manera, en donde queda más claro que una matriz es un arreglo de arreglos.

//Declaración e instanciación de matriz solo asignando el tamaño del arreglo que representa las filas (4)

String[][] paises3 = new String[4][];

//arreglo en la posición 0 se enlaza a arreglo de tamaño 5

paises3 = new String[5];

//arreglo en la posición 1 se enlaza a arreglo de tamaño 4

paises3 = new String[4];

//Y así sucesivamente...

Recorrido de matrices editar

La iteración sobre matrices se realiza por lo general a través de estructuras for anidadas, así la exterior recorre las filas y la interior recorre las columnas. Primero se ejemplificará como rellenar una matriz con valores ingresados por el usuario.

//Se crea objeto de tipo Scanner para recibir valores por teclado

Scanner sc = new Scanner(System.in);

//Se crea matriz con arreglo que representa las filas de tamaño 4

//Con arreglos enlazados de tamaño variable

String[][] paises3 = new String[4][];

paises3[0] = new String[5];

paises3[1] = new String[4];

paises3[2] = new String[3];

paises3[3] = new String[2];

//Ingreso de valores por teclado

//Estructura for externa recorre las filas (4)

for(int i = 0; i < paises3.length; i++)

{

//Estructura for interna recorre las columnas (5,4,3,2 consecutivamente)

for(int j = 0; j < paises3[i].length; j++)

{

paises3[i][j] = sc.next();

}

}


//Se muestran todos los valores en la matriz

for(int i = 0; i < paises3.length; i++)

{

for(int j = 0; j < paises3[i].length;j++)

{

System.out.println("Fila ["+ i +"] Columna ["+ j +"] : "+ paises3[i][j]);

}

System.out.println("=================================");

}

Para clonar una matriz completa se utiliza el método clone en la misma forma como se utiliza con arreglos unidimensionales

int[][] matriz = {{10,20,30},

{40,50,60},

{70,80,90}};

int[][] matrizClonada = matriz.clone();

Para clonar una matriz completa o parte de sus valores se utiliza en método arraycopy como en los arreglos unidimensionales, tomando los indices de las filas en sus argumentos.

int[][] matrizCopia = new int[3][3];

//Copiará las últimas 2 filas de la matriz original

System.arraycopy(matriz,1,matrizCopia,1,matrizCopia.length-1);

for(int i = 0; i < matrizCopia.length;i++){

System.out.println(i + 1 + " iteración");

for(int j = 0; j < matrizCopia[i].length;j++){

System.out.println(matrizCopia[i][j]);

}

}

El resultado es:

1 iteración

0

0

0

2 iteración

40

50

60

3 iteración

70

80

90

Ejemplos editar

   //Suma de matrices cuadradas   
int[][] matriz1 = {{10,20,30},
                           {40,50,60},
                           {70,80,90}};
       int[][] matriz2 = {{5,15,20},
                          {30,35,40},
                          {65,70,75}};
       
       int[][] matrizResultado = new int[matriz1.length][matriz1[0].length];
       
       for(int i = 0; i < matriz1.length;i++)
       {
           for(int j = 0;j < matriz1[i].length;j++)
           {
               matrizResultado[i][j] = matriz1[i][j] + matriz2[i][j];
               System.out.println(matrizResultado[i][j]);
           }
       }

//Matriz traspuesta

       int[][] matriz = {{10,20,-30,22},
                          {-2,0,40,33}};
       int[][] matrizTraspuesta = new int[matriz[0].length][matriz.length];
      
       for(int i = 0; i < matrizTraspuesta.length;i++)//4 iteraciones
       {
           for(int j = 0;j < matriz.length;j++)//2 iteraciones
           {
              matrizTraspuesta[i][j] = matriz[j][i];
              
           }
           
       }
       
       for(int i = 0; i < matrizTraspuesta.length;i++)
       {
           for(int j = 0;j < matrizTraspuesta[i].length;j++)
           {
               System.out.println("Fila["+i+"] Columna ["+j+"] : "+matrizTraspuesta[i][j]);
           }
       }

La salida es :

Fila[0] Columna [0] : 10

Fila[0] Columna [1] : -2

Fila[1] Columna [0] : 20

Fila[1] Columna [1] : 0

Fila[2] Columna [0] : -30

Fila[2] Columna [1] : 40

Fila[3] Columna [0] : 22

Fila[3] Columna [1] : 33

Arreglos Genéricos editar

Los genéricos son una potente característica introducida en la versión 5 de Java, que aporta mejoras significativas al lenguaje. La idea de los genéricos es mejorar la comprobación de tipos en tiempo de compilación y así evitar errores derivados de esto, al reducir los casting entre tipos. Por lo general estas características son bien conocidas al utilizar colecciones del framework de colecciones de Java, pero en esta ocasión la idea es implementarlos en los arreglos.

Para lograr aprovechar las ventajas de los genéricos en los objetos de tipo arreglo, se hace necesario implementar una clase genérica que reúna todas las operaciones comunes a los objetos de tipo arreglo, ademas de las especificas a la naturaleza del problema que se este tratando si se requiriera.

Una implementación básica a modo de ejemplo podría ser la siguiente:

//Clase genérica que manipula un arreglo tipo T

public class TipoGenerico<T> {

   //Declaración variable de referencia

   private T[] arreglo = null;

   

   //Constructor de clase genérica

   public TipoGenerico(T[] obj)

   {

       arreglo = obj;

   }

   //Método que agrega valores al arreglo en el indice n

   public void setArreglo(T valor,int pos){

       arreglo[pos] = valor;

   }

   

   //Retorna la referencia al objeto de tipo arreglo

   public T[] getArreglo()

   {

       return arreglo;

     }      

  //Muestra todos los valores contenidos en el arreglo

   public void mostrarArreglo(){

       for(T tipo: arreglo)

       {

           System.out.println(tipo);

       }

   }

}

La clase TipoGenerico recibe un parámetro de tipo T entre operadores diamante, este tipo el compilador lo reemplazará por el tipo que le pasemos al invocar la clase.  En esta clase el único atributo declarado es una variable  que hará referencia a un objeto de tipo arreglo, que a su vez tomará lo que le pasemos en el parámetro de tipo a la clase. El constructor en su parámetro espera una instancia de un objeto arreglo para asignarlo a la variable arreglo, es en ese momento cuando ya tenemos nuestro arreglo genérico listo para ser usado.

A continuación se muestra como usar la clase genérica para realizar operaciones básicas sobre un arreglo de tipo T.

public class ArreglosGenericos {

   public static void main(String[] args) {

        //Invocación de tipo genérico

       TipoGenerico<Integer> refGen = null;

       refGen = new TipoGenerico<>(new Integer[3]);

       refGen.setArreglo(24,0);

       refGen.setArreglo(30,1);

       refGen.setArreglo(47,2);

       

       refGen.mostrarArreglo();

       

       TipoGenerico<String> refGen2 = null;

       refGen2 = new TipoGenerico<>(new String[4]);

       refGen2.setArreglo("Manuel",0);

       refGen2.setArreglo("María",1);

       refGen2.setArreglo("Jorge",2);

       refGen2.setArreglo("Alejandro",3);

       refGen2.mostrarArreglo();


        System.out.println(refGen.getArreglo()[2]);

       System.out.println(refGen2.getArreglo()[2]);

       

   }

   

   

}

Al invocarse el tipo genérico con el valor concreto Integer como argumento de tipo, se llama al constructor el cual recibe una instancia de un objeto arreglo de tamaño tres. El objeto hace uso del método setArreglo para asignar valores en los indices del arreglo y el método mostrarArreglo para mostrar los valores de todos los indices del arreglo, aún cuando el tipo concreto sea distinto. La seguridad de tipos en Java al utilizar genéricos se hace mas fuerte en tiempo de compilación, no permitiendo que usemos el mismo objeto de la clase genérica para valores concretos distintos en sus argumentos de tipo (lo que sería en este caso Integer y String), por eso se hizo necesario crear dos referencias a objetos distintos para manipular arreglos, pero haciendo uso de los mismos métodos indistintamente de su tipo de dato. Por último se hace uso del método getArreglo para acceder a cada uno de los indices del arreglo.

La salida es la siguiente:

24

30

47

Manuel

María

Jorge

Alejandro

47

Jorge

Manipulación de arreglos editar

Muchas veces será necesario realizar ciertas operaciones sobre arreglos de dato, las cuales por lo general son repetitivas y tediosas de implementar, por esta razón Java dispone de una clase que implementa una seria métodos que facilitan las diversas tareas sobre arreglos que se pueden realizar. La clase Arrays es la contiene los métodos necesarios para manipular arreglos de forma sencilla, esta clase pertenece al Marco de Trabajo de Colecciones de Java . Todos los métodos en esta clase son estáticos.

Relleno de arreglos editar

Existen métodos para rellenar un arreglo con valores determinados, la desventaja de esto es que el arreglo almacenará en sus indices el mismo valor que le pasemos como argumento al método fill. Este método esta sobrecargado en sus dos variantes (como se verá luego), para tipos primitivos y referencias a objetos.

//LLenado de arreglo valores primitivos

       int[] nros = new int[5];

       Arrays.fill(nros, 10);

Un uso más interesante para este método es al usar referencias a objetos.

//Llenado de arreglo de referencias a objetos

       Empleado[] empleados = new Empleado[5];

       Arrays.fill(empleados,new Empleado());

La otra implementación del método fill acepta argumentos que permiten rellenar el arreglo en ciertos rangos con el mismio valor, la cabecera es la siguiente:

public static void fill(Tipo[] arreglo,int desdeIndice,int hastaIndice, int valor)

El argumento desdeIndice se incluye, mientras que hastaIndice no se incluye.

//Llenado parcial de arreglos

       int[] nros2 = new int[5];

       //Desde indice 2 hasta nros.length-1

       Arrays.fill(nros, 2, nros2.length,20);

     //Solo se rellena el arreglo con 3 valores

Copia de arreglos editar

Para copiar arreglos la clase Arrays ofrece métodos sobrecargados, ambos para tipos primitivos y referencias a objetos. La cabecera del primer método es la sguiente:

public static tipo[] copyOf(tipo[] arregloOriginal,int tamaño)

en donde el tamaño indica cuantos elementos se copiarán en un nuevo objeto de tipo arreglo, distinto al original pero con los mismos o parte de los valores si lo desearamos. Cuando el argumento tamaño se pasa con un valor superior al tamaño del arreglo original, el espacio sobrante se rellena con el valor predeterminado para el tipo de dato indicado.

//Copia de arreglos

       int[] nros3 = {10,20,30,40,50};

       int[] copia = null;

       copia = Arrays.copyOf(nros3, 8);

       for(int n:copia)

       {

           System.out.println(n);

       }

La salida es:

10

20

30

40

50

0

0

0
Para copiar un rango especifico de valores, se utiliza el método copyOfRange , siendo su cabecera la siguiente:

public static tipo[] copyOfRange(tipo[] arregloOriginal,int desde,int hasta)

El valor del argumento desde deberá estar entre 0 y el tamaño del arreglo original inclusive, el valor del argumento hasta deberá ser igual o mayor que el tamaño del arreglo original, si llegará a ser mayor los lugares restantes serán llenados con el valor por defecto para el tipo de dato utilizado.

//Copia de arreglos por rangos

       String[] nombres ={"Ana","Gabriel","Marta"};

       String[] copiaNombres = null;

       //Solo copia 2 nombres

       copiaNombres = Arrays.copyOfRange(nombres,1,5);

       for(String nom:copiaNombres)

       {

           System.out.println(nom);

       }

La salida es :

Gabriel

Marta

null

null