Categorías
Basics Diagram

Diagrama para mostrar la inmutabilidad de las cadenas de Java

Aquí hay un conjunto de diagramas para ilustrar las cadenas de Java inmutabilidad.

1. Declare una cadena

El siguiente código inicializa una cadena s.

String s = "abcd";

La variable s almacena la referencia de un objeto de cadena como se muestra a continuación. La flecha se puede interpretar como «almacenar referencia de».

2. Asignar una variable de cadena a otra variable de cadena

El siguiente código asigna s a s2.

String s2 = s;

s2 almacena el mismo valor de referencia ya que es el mismo objeto de cadena.

String-Immutability-2

3. Cadena Concat

Cuando concatenamos una cadena «ef» a s,

s = s.concat("ef");

s almacena la referencia del objeto de cadena recién creado como se muestra a continuación.

inmutabilidad de cuerdas

En resumen, una vez que se crea una cadena en la memoria (montón), no se puede cambiar. Los métodos de cadena no cambian la cadena en sí, sino que devuelven una nueva cadena.

Si necesitamos una cadena que se pueda modificar, necesitaremos StringBuffer o StringBuilder. De lo contrario, se perderá mucho tiempo para la recolección de basura, ya que cada vez que se crea una nueva cadena. Aquí hay un ejemplo de uso StringBuilder.

Categorías
Collections Diagram

La interfaz y el diagrama de jerarquía de clases de las colecciones de Java

1. Colección vs colecciones

En primer lugar, «Colección» y «Colecciones» son dos conceptos diferentes. Como verá en el diagrama de jerarquía a continuación, «Colección» es una interfaz raíz en la jerarquía de Colección, pero «Colecciones» es una clase que proporciona métodos estáticos para manipular algunos tipos de Colección.

2. Jerarquía de clases de la colección

El siguiente diagrama muestra la jerarquía de clases de Collection.

3. Jerarquía de clases de Map

Aquí está la jerarquía de clases de Map.

4. Resumen de clases

resumen de la colección

5. Ejemplo de código

El siguiente es un ejemplo simple para ilustrar algunos tipos de colección:

List<String> a1 = new ArrayList<String>();
a1.add("Program");
a1.add("Creek");
a1.add("Java");
a1.add("Java");
System.out.println("ArrayList Elements");
System.out.print("t" + a1 + "n");
 
List<String> l1 = new LinkedList<String>();
l1.add("Program");
l1.add("Creek");
l1.add("Java");
l1.add("Java");
System.out.println("LinkedList Elements");
System.out.print("t" + l1 + "n");
 
Set<String> s1 = new HashSet<String>(); // or new TreeSet() will order the elements;
s1.add("Program");
s1.add("Creek");
s1.add("Java");
s1.add("Java");
s1.add("tutorial");
System.out.println("Set Elements");
System.out.print("t" + s1 + "n");
 
Map<String, String> m1 = new HashMap<String, String>(); // or new TreeMap() will order based on keys
m1.put("Windows", "2000");
m1.put("Windows", "XP");
m1.put("Language", "Java");
m1.put("Website", "programcreek.com");
System.out.println("Map Elements");
System.out.print("t" + m1);

Producción:

ArrayList Elements
	[Program, Creek, Java, Java]
LinkedList Elements
	[Program, Creek, Java, Java]
Set Elements
	[tutorial, Creek, Program, Java]
Map Elements
	{Windows=XP, Website=programcreek.com, Language=Java}

Categorías
Diagram Exceptions

Diagrama de jerarquía de excepciones

En Java, la excepción se puede marcar o desmarcar. Ambos encajan en una jerarquía de clases. El siguiente diagrama muestra la jerarquía de clases de excepción de Java.

Son de color rojo comprobado excepciones. Cualquier excepción marcada que se pueda lanzar en un método debe ser capturada o declarada en la cláusula throws del método. Las excepciones marcadas deben detectarse en tiempo de compilación. Las excepciones marcadas se denominan así porque tanto el compilador de Java como la máquina virtual de Java comprueban que se obedezca esta regla. De color verde son desmarcar excepciones. Son excepciones que no se espera recuperar, como puntero nulo, dividir por 0, etc.

Consulte las 10 preguntas principales sobre las excepciones de Java.

Categorías
Common Methods Diagram

Contrato Java equals () y hashCode ()

La superclase de Java java.lang.Object define dos métodos importantes:

public boolean equals(Object obj)
public int hashCode()

En esta publicación, primero mostraré un ejemplo de un error común y luego explicaré cómo contrato igual () y hashCode () obras.

1. Un error común

El error común se muestra en el siguiente ejemplo.

import java.util.HashMap;
 
public class Apple {
	private String color;
 
	public Apple(String color) {
		this.color = color;
	}
 
	public boolean equals(Object obj) {
		if(obj==null) return false;
		if (!(obj instanceof Apple))
			return false;	
		if (obj == this)
			return true;
		return this.color.equals(((Apple) obj).color);
	}
 
	public static void main(String[] args) {
		Apple a1 = new Apple("green");
		Apple a2 = new Apple("red");
 
		//hashMap stores apple type and its quantity
		HashMap<Apple, Integer> m = new HashMap<Apple, Integer>();
		m.put(a1, 10);
		m.put(a2, 20);
		System.out.println(m.get(new Apple("green")));
	}
}

En el método principal, se crean dos manzanas («verde» y «roja») y se colocan en un HashMap. Sin embargo, cuando se le pide al mapa que proporcione la manzana verde, no se encuentra la manzana verde. El programa anterior imprime nulo. Estamos seguros de que la manzana verde se almacena en el hashMap al inspeccionar el HashMap en el depurador.

¿Qué causa el problema?

2. Problema causado por hashCode ()

El problema es causado por el método no reemplazado «hashCode ()». El contrato entre equals () y hashCode () es:
1) Si dos objetos son iguales, entonces deben tener el mismo código hash.
2) Si dos objetos tienen el mismo código hash, pueden ser iguales o no.

La idea detrás de un mapa es poder encontrar un objeto más rápido que una búsqueda lineal. El uso de claves hash para localizar objetos es un proceso de dos pasos. Internamente, HashMap se implementa como una matriz de objetos Entry. Cada entrada tiene un par y un puntero que apunta a la siguiente entrada. El código hash del objeto clave es el índice para direccionar la matriz. Esto ubica la lista vinculada de entradas en esa celda de la matriz. La lista vinculada en la celda luego se busca linealmente usando equals () para determinar si dos objetos son iguales. ,>

La implementación predeterminada de hashCode () en la clase Object devuelve enteros distintos para diferentes objetos. Por tanto, la segunda manzana tiene un código hash diferente.

El HashMap está organizado como una secuencia de cubos. Los objetos clave se colocan en diferentes cubos. O (1) necesita tiempo para llegar al depósito correcto porque es un acceso a la matriz con el índice. Por lo tanto, es una buena práctica distribuir uniformemente los objetos en esos depósitos, es decir, tener un método hashCode () que produzca un código hash distribuido uniformemente. (Aunque no es el punto principal aquí)

La solución es agregar el método hashCode a la clase Apple. Aquí solo uso la longitud de la cadena de color para la demostración.

public int hashCode(){
	return this.color.hashCode();	
}

Categorías
Concurrency Diagram

Monitores: la idea básica de la sincronización de Java

Si tomó un curso de sistema operativo en la universidad, es posible que recuerde que el monitor es un concepto importante de sincronización en los sistemas operativos. También se utiliza en la sincronización de Java. Esta publicación utiliza una analogía para explicar la idea básica de «monitor».

1. ¿Qué es un monitor?

Un monitor puede considerarse como un edificio que contiene una habitación especial. La sala especial puede ser ocupada por un solo cliente (hilo) a la vez. La sala suele contener algunos datos y códigos.

Si un cliente desea ocupar la habitación especial, primero debe ingresar al Pasillo (Conjunto de entrada) para esperar. El programador elegirá uno en función de algunos criterios (por ejemplo, FIFO). Si es suspendido por alguna razón, será enviado a la sala de espera y se programará que vuelva a ingresar a la sala especial más tarde. Como se muestra en el diagrama anterior, hay 3 habitaciones en este edificio.

En resumen, un monitor es una instalación que supervisa el acceso de los hilos a la sala especial. Asegura que solo un hilo pueda acceder a los datos o códigos protegidos.

2. ¿Cómo se implementa en Java?

En la máquina virtual Java, cada objeto y clase está asociado lógicamente con un monitor. Para implementar la capacidad de exclusión mutua de los monitores, se asocia un bloqueo (a veces llamado mutex) con cada objeto y clase. Esto se llama semáforo en los libros de sistemas operativos, mutex es un semáforo binario.

Si un hilo posee un bloqueo en algunos datos, entonces ningún otro puede obtener ese bloqueo hasta que el hilo que posee el bloqueo lo libere. No sería conveniente si tuviéramos que escribir un semáforo todo el tiempo cuando hacemos programación de subprocesos múltiples. Afortunadamente, no es necesario, ya que JVM lo hace por nosotros automáticamente.

Para reclamar una región de monitor, lo que significa que los datos no son accesibles por más de un hilo, Java proporciona declaraciones sincronizadas y métodos sincronizados. Una vez que el código está incrustado con la palabra clave sincronizada, es una región de monitorización. Los bloqueos son implementados en segundo plano automáticamente por JVM.

3. En el código de sincronización de Java, ¿qué parte es monitor?

Sabemos que cada objeto / clase está asociado con un Monitor. Creo que es bueno decir que cada objeto tiene un monitor, ya que cada objeto podría tener su propia sección crítica y ser capaz de monitorear la secuencia del hilo.

Para permitir la colaboración de diferentes subprocesos, Java proporciona wait () y notificar () para suspender un subproceso y para despertar otro subproceso que está esperando en el objeto, respectivamente. Además, existen otras 3 versiones:

wait(long timeout, int nanos)
wait(long timeout) notified by other threads or notified by timeout. 
notify(all)

Estos métodos solo se pueden invocar dentro de una instrucción sincronizada o un método sincronizado. La razón es que si un método no requiere exclusión mutua, no hay necesidad de monitorear o colaborar entre hilos, cada hilo puede acceder a ese método libremente.

A continuación, se muestran algunos ejemplos de códigos de sincronización.

Referencia:
1. Documento Java para objetos
2. Sincronización de subprocesos
3. Bloqueos y sincronización
4. notificar () vs notificar a todos ()

Categorías
Diagram Memory

¿Cómo maneja Java el aliasing?

¿Qué es el alias de Java?

La creación de alias significa que hay varios alias en una ubicación que se pueden actualizar, y estos alias tienen diferentes tipos.

En el siguiente ejemplo, a y b son dos nombres de variable que tienen dos tipos diferentes A y B. B se extiende A.

B[] b = new B[10];
A[] a = b;
 
a[0] =  new A();
b[0].methodParent();

En la memoria, ambos se refieren a la misma ubicación.

La ubicación de la memoria señalada está señalada por ay b. Durante el tiempo de ejecución, el objeto real almacenado determina a qué método llamar.

¿Cómo maneja Java el problema de los alias?

Si copia este código en su eclipse, no habrá errores de compilación.

class A {
	public void methodParent() {
		System.out.println("method in Parent");
	}
}
 
class B extends A {
	public void methodParent() {
		System.out.println("override method in Child");
	}
 
	public void methodChild() {
		System.out.println("method in Child");
	}
}
 
public class Main {
 
	public static void main(String[] args) {
 
		B[] b = new B[10];
		A[] a = b;
 
		a[0] =  new A();
		b[0].methodParent();
	}
}

Pero si ejecuta el código, el resultado sería:

Exception in thread "main" java.lang.ArrayStoreException: aliasingtest.A
	at aliasingtest.Main.main(Main.java:26)

La razón es que Java maneja la creación de alias durante el tiempo de ejecución. Durante el tiempo de ejecución, sabe que el primer elemento debe ser un objeto B, en lugar de A.

Por lo tanto, solo se ejecuta correctamente si se cambia a:

B[] b = new B[10];
A[] a = b;
 
a[0] =  new B();
b[0].methodParent();

y la salida es:

override method in Child

Categorías
Collections Diagram Versus

ArrayList frente a LinkedList frente a vector

1. Descripción general de la lista

List, como su nombre lo indica, es una secuencia ordenada de elementos. Cuando hablamos de List, es una buena idea compararlo con Set, que es un conjunto de elementos únicos y desordenados. El siguiente es el diagrama de jerarquía de clases de Collection. Desde el diagrama de jerarquía puede obtener una idea general de las colecciones de Java.

2. ArrayList vs. LinkedList vs. Vector

Desde el diagrama de jerarquía, todos implementan Lista interfaz. Son muy similares de usar. Su principal diferencia es su implementación, lo que provoca un rendimiento diferente para diferentes operaciones.

Lista de arreglo se implementa como una matriz de tamaño variable. A medida que se agregan más elementos a ArrayList, su tamaño aumenta dinámicamente. Se puede acceder a sus elementos directamente mediante los métodos get y set, ya que ArrayList es esencialmente una matriz.

Lista enlazada se implementa como una lista de doble enlace. Su rendimiento al agregar y quitar es mejor que Arraylist, pero peor en los métodos get y set.

Vector es similar con ArrayList, pero está sincronizado.

ArrayList es una mejor opción si su programa es seguro para subprocesos. Vector y ArrayList requieren más espacio a medida que se agregan más elementos. Vector cada vez duplica su tamaño de matriz, mientras que ArrayList crece un 50% de su tamaño cada vez. LinkedList, sin embargo, también implementa Cola interfaz que agrega más métodos que ArrayList y Vector, como offer (), peek (), poll (), etc.

Nota: La capacidad inicial predeterminada de una ArrayList es bastante pequeña. Es un buen hábito construir ArrayList con una capacidad inicial más alta. Esto puede evitar el costo de cambio de tamaño.

3. Ejemplo de ArrayList

ArrayList<Integer> al = new ArrayList<Integer>();
al.add(3);
al.add(2);		
al.add(1);
al.add(4);
al.add(5);
al.add(6);
al.add(6);
 
Iterator<Integer> iter1 = al.iterator();
while(iter1.hasNext()){
	System.out.println(iter1.next());
}

4. Ejemplo de LinkedList

LinkedList<Integer> ll = new LinkedList<Integer>();
ll.add(3);
ll.add(2);		
ll.add(1);
ll.add(4);
ll.add(5);
ll.add(6);
ll.add(6);
 
Iterator<Integer> iter2 = ll.iterator();
while(iter2.hasNext()){
	System.out.println(iter2.next());
}

Como se muestra en los ejemplos anteriores, su uso es similar. La verdadera diferencia es su implementación subyacente y su complejidad operativa.

5. Vector

Vector es casi idéntico a ArrayList, y la diferencia es que Vector está sincronizado. Debido a esto, tiene una sobrecarga que ArrayList. Normalmente, la mayoría de los programadores de Java utilizan ArrayList en lugar de Vector porque pueden sincronizar explícitamente por sí mismos.

6. Rendimiento de ArrayList frente a LinkedList

La comparación de la complejidad del tiempo es la siguiente:
Array-lista-vs-lista-enlazada-complejidad

* add () en la tabla se refiere a add (E e), y remove () se refiere a remove (int index)

  • ArrayList tiene una complejidad de tiempo O (n) para índices arbitrarios de agregar / quitar, pero O (1) para la operación al final de la lista.
  • LinkedList tiene O (n) complejidad de tiempo para índices arbitrarios de agregar / quitar, pero O (1) para operaciones al final / comienzo de la Lista.

Utilizo el siguiente código para probar su rendimiento:

ArrayList<Integer> arrayList = new ArrayList<Integer>();
LinkedList<Integer> linkedList = new LinkedList<Integer>();
 
// ArrayList add
long startTime = System.nanoTime();
 
for (int i = 0; i < 100000; i++) {
	arrayList.add(i);
}
long endTime = System.nanoTime();
long duration = endTime - startTime;
System.out.println("ArrayList add:  " + duration);
 
// LinkedList add
startTime = System.nanoTime();
 
for (int i = 0; i < 100000; i++) {
	linkedList.add(i);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println("LinkedList add: " + duration);
 
// ArrayList get
startTime = System.nanoTime();
 
for (int i = 0; i < 10000; i++) {
	arrayList.get(i);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println("ArrayList get:  " + duration);
 
// LinkedList get
startTime = System.nanoTime();
 
for (int i = 0; i < 10000; i++) {
	linkedList.get(i);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println("LinkedList get: " + duration);
 
 
 
// ArrayList remove
startTime = System.nanoTime();
 
for (int i = 9999; i >=0; i--) {
	arrayList.remove(i);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println("ArrayList remove:  " + duration);
 
 
 
// LinkedList remove
startTime = System.nanoTime();
 
for (int i = 9999; i >=0; i--) {
	linkedList.remove(i);
}
endTime = System.nanoTime();
duration = endTime - startTime;
System.out.println("LinkedList remove: " + duration);

Y la salida es:

ArrayList add:  13265642
LinkedList add: 9550057
ArrayList get:  1543352
LinkedList get: 85085551
ArrayList remove:  199961301
LinkedList remove: 85768810

arraylist-vs-linkedlist

La diferencia de su desempeño es obvia. LinkedList es más rápido en agregar y quitar, pero más lento en obtener. Según la tabla de complejidad y los resultados de las pruebas, podemos averiguar cuándo usar ArrayList o LinkedList. En resumen, debe preferirse LinkedList si:

  • no hay una gran cantidad de acceso aleatorio de elemento
  • hay una gran cantidad de operaciones de agregar / quitar

Categorías
Diagram Memory

¿Cómo se ve una matriz de Java en la memoria?

En Java, una matriz almacena valores primitivos (int, char, …) o referencias (también conocidas como punteros) a objetos.

Cuando se crea un objeto utilizando «nuevo», se asigna un espacio de memoria en el montón y se devuelve una referencia. Esto también es cierto para las matrices, ya que las matrices son objetos en Java.

1. Matriz de una dimensión

int arr[] = new int[3];

El INT[] arr es solo la referencia a la matriz de 3 enteros. Si crea una matriz con 10 enteros, es lo mismo: se asigna una matriz y se devuelve una referencia.

2. Matriz bidimensional

¿Qué tal una matriz bidimensional? En realidad, solo podemos tener matrices unidimensionales en Java. Una matriz de 2 dimensiones es solo una matriz de matrices de 1 dimensión.

int[ ][ ] arr = new int[3][ ];
arr[0] = new int[3];
arr[1] = new int[5];
arr[2] = new int[4];

Matriz en memoria Java

Las matrices multidimensionales son similares a las que puedes imaginar.

3. ¿Dónde se encuentran en la memoria?

Las matrices también son objetos en Java, por lo que la apariencia de un objeto en la memoria se aplica a una matriz.

Como sabemos, las áreas de datos en tiempo de ejecución de JVM incluyen montón, pila de JVM y otras. Para un ejemplo simple como sigue, veamos dónde se almacenan la matriz y su referencia.

class A {
	int x;
	int y;
}
 
...
 
public void m1() {
	int i = 0;
	m2();
}
 
public void m2() {
	A a = new A();
}
 
...

Con la declaración anterior, invoquemos m1 () y veamos qué sucede:

  1. Cuando se invoca m1, se inserta un nuevo marco (Marco-1) en la pila y la variable local i también se crea en el Marco-1.
  2. Luego se invoca m2 dentro de m1, se inserta otro marco nuevo (Marco-2) en la pila. En m2, se crea un objeto de clase A en el montón y la variable de referencia se coloca en el Cuadro-2. Ahora, en este punto, la pila y el montón tienen el siguiente aspecto:

Java-matriz-en-memoria

Las matrices se tratan de la misma manera que los objetos, por lo que la ubicación de las matrices en la memoria es sencilla.

Categorías
Diagram JVM/Compiler Memory

Áreas de datos en tiempo de ejecución de JVM

Las siguientes son mis notas sobre la lectura de las especificaciones de JVM.

1. Áreas de datos para cada hilo individual (no compartido)

Las áreas de datos para cada subproceso individual incluyen registro de contador de programa, pila de JVM y pila de método nativo. Todos se crean cuando se crea un nuevo hilo.

Program Counter Register se utiliza para controlar cada ejecución de cada hilo.
JVM Stack contiene marcos que se muestran en el diagrama a continuación.
Native Method Stack se utiliza para admitir métodos nativos, es decir, métodos que no son de lenguaje Java.

2. Áreas de datos compartidas por todos los subprocesos

Todos los subprocesos comparten el área de método y montón.

Montón es el área con la que nos ocupamos con más frecuencia. Almacena matrices y objetos, creados cuando se inicia JVM. Garbage Collection trabaja en esta área.

El área de métodos almacena el grupo de constantes en tiempo de ejecución, los datos de campos y métodos, y el código de los métodos y constructores.

Runtime Constant Pool es una representación en tiempo de ejecución por clase o por interfaz de la tabla constant_pool en un archivo de clase. Contiene varios tipos de constantes, que van desde literales numéricos conocidos en tiempo de compilación hasta referencias a métodos y campos que deben resolverse en tiempo de ejecución.

Pila de JVM

La pila contiene marcos y se envía un marco a la pila cuando se invoca un método. Un marco contiene una matriz de variables locales, Operand Stack, Reference to Constant Pool.

Para obtener más información, visite el sitio oficial de especificaciones de JVM.

Referencias:
1. Especificación de JVM: áreas de datos en tiempo de ejecución
2. Fundamentos de código de bytes de Java

Categorías
Basics Diagram Java

¿Por qué String es inmutable en Java?

String es inmutable en Java. Una clase inmutable es simplemente una clase cuyas instancias no se pueden modificar. Toda la información de una instancia se inicializa cuando se crea la instancia y la información no se puede modificar. Hay muchas ventajas de las clases inmutables. Este artículo resume por qué String está diseñado para ser inmutable. Este artículo ilustra el concepto de inmutabilidad en la perspectiva de la memoria, la sincronización y las estructuras de datos.

1. Requisito de la agrupación de cadenas

El grupo de cuerdas (grupo interno de cuerdas) es un área de almacenamiento especial en el área de métodos. Cuando se crea una cadena y si la cadena ya existe en el grupo, se devolverá la referencia de la cadena existente, en lugar de crear un nuevo objeto.

El siguiente código creará solo un objeto de cadena en el montón.

String string1 = "abcd";
String string2 = "abcd";

Así es como se ve:

Si una cadena es mutable, cambiar la cadena con una referencia conducirá a un valor incorrecto para las otras referencias.

2. Almacenamiento en caché de Hashcode

El código hash de una cadena se usa con frecuencia en Java. Por ejemplo, en un HashMap o HashSet. Ser inmutable garantiza que el hashcode siempre será el mismo para que se pueda cobrar sin preocuparse por los cambios, es decir, no es necesario calcular el hashcode cada vez que se utiliza. Esto es más eficiente.

En la clase String, tiene el siguiente código:

private int hash;//this is used to cache hash code.

3. Facilitar el uso de otros objetos

Para hacer esto concreto, considere el siguiente programa:

HashSet<String> set = new HashSet<String>();
set.add(new String("a"));
set.add(new String("b"));
set.add(new String("c"));
 
for(String a: set)
	a.value = "a";

En este ejemplo, si String es mutable, su valor se puede cambiar, lo que violaría el diseño del conjunto (el conjunto contiene elementos no duplicados). Por supuesto, el ejemplo anterior es solo para fines de demostración y no hay value campo en una clase de cadena real.

4. Seguridad

String se usa ampliamente como parámetro para muchas clases de Java, por ejemplo, conexión de red, apertura de archivos, etc. Si String no fuera inmutable, se cambiaría una conexión o un archivo y esto puede conducir a una seria amenaza a la seguridad. El método pensó que se estaba conectando a una máquina, pero no fue así. Las cadenas mutables también podrían causar un problema de seguridad en Reflection, ya que los parámetros son cadenas.

Aquí hay un ejemplo de código:

boolean connect(string s){
    if (!isSecure(s)) { 
throw new SecurityException(); 
}
    //here will cause problem, if s is changed before this by using other references.    
    causeProblem(s);
}

5. Los objetos inmutables son naturalmente seguros para subprocesos

Debido a que los objetos inmutables no se pueden cambiar, se pueden compartir libremente entre varios subprocesos. Esto elimina los requisitos de sincronización.

En resumen, String está diseñado para ser inmutable por razones de eficiencia y seguridad. Esta es también la razón por la que se prefieren las clases inmutables en muchos casos en general.