5 errores que comete el 99% de los desarrolladores de Java
Fuente:
Medio En esta publicación, conocerá los errores más comunes que cometen muchos desarrolladores de Java. Como programador de Java, sé lo malo que es dedicar mucho tiempo a corregir errores en el código. A veces esto lleva varias horas. Sin embargo, muchos errores aparecen debido al hecho de que el desarrollador ignora las reglas básicas, es decir, se trata de errores de muy bajo nivel. Hoy veremos algunos errores de codificación comunes y luego explicaremos cómo solucionarlos. Espero que esto te ayude a evitar problemas en tu trabajo diario.
Comparar objetos usando Objects.equals
Supongo que estás familiarizado con este método. Muchos desarrolladores lo utilizan con frecuencia. Esta técnica, introducida en JDK 7, le ayuda a comparar objetos rápidamente y evitar eficazmente la molesta comprobación de puntero nulo. Pero este método a veces se utiliza incorrectamente. Esto es lo que quiero decir:
Long longValue = 123L;
System.out.println(longValue==123);
System.out.println(Objects.equals(longValue,123));
¿Por qué reemplazar
== con
Objects.equals() produciría un resultado incorrecto? Esto se debe a que el compilador
== obtendrá el tipo de datos subyacente correspondiente al tipo de empaquetado
longValue y luego lo comparará con ese tipo de datos subyacente. Esto equivale a que el compilador convierta automáticamente constantes al tipo de datos de comparación subyacente. Después de usar el método
Objects.equals() , el tipo de datos base predeterminado de la constante del compilador es
int . A continuación se muestra el código fuente
de Objects.equals() donde
a.equals(b) usa
Long.equals() y determina el tipo de objeto. Esto sucede porque el compilador asumió que la constante era de tipo
int , por lo que el resultado de la comparación debe ser falso.
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
Conociendo el motivo, corregir el error es muy sencillo. Simplemente declare el tipo de datos de las constantes, como
Objects.equals(longValue,123L) . Los problemas anteriores no surgirán si la lógica es estricta. Lo que debemos hacer es seguir reglas de programación claras.
Formato de fecha incorrecto
En el desarrollo diario, a menudo es necesario cambiar la fecha, pero muchas personas usan el formato incorrecto, lo que genera cosas inesperadas. He aquí un ejemplo:
Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant));
Esto utiliza el formato
AAAA-MM-dd para cambiar la fecha de 2021 a 2022. No deberías hacer eso. ¿Por qué? Esto se debe a que
el patrón Java DateTimeFormatter “AAAA” se basa en el estándar ISO-8601, que define el año como el jueves de cada semana. Pero el 31 de diciembre de 2021 cayó en viernes, por lo que el programa indica incorrectamente 2022. Para evitar esto, debes utilizar el formato
aaaa-MM-dd para formatear la fecha . Este error ocurre con poca frecuencia, sólo con la llegada del nuevo año. Pero en mi empresa provocó un fallo de producción.
Usando ThreadLocal en ThreadPool
Si crea
una variable ThreadLocal , un hilo que acceda a esa variable creará una variable local de hilo. De esta manera puede evitar problemas de seguridad de subprocesos. Sin embargo, si utiliza
ThreadLocal en
un grupo de subprocesos , debe tener cuidado. Su código puede producir resultados inesperados. Para un ejemplo simple, digamos que tenemos una plataforma de comercio electrónico y los usuarios deben enviar un correo electrónico para confirmar la compra completa de productos.
private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);
private ExecutorService executorService = Executors.newFixedThreadPool(4);
public void executor() {
executorService.submit(()->{
User user = currentUser.get();
Integer userId = user.getId();
sendEmail(userId);
});
}
Si utilizamos
ThreadLocal para guardar información del usuario, aparecerá un error oculto. Debido a que se utiliza un grupo de subprocesos y los subprocesos se pueden reutilizar, cuando se utiliza
ThreadLocal para obtener información del usuario, es posible que muestre erróneamente la información de otra persona. Para resolver este problema, debes usar sesiones.
Utilice HashSet para eliminar datos duplicados
Al codificar, a menudo tenemos la necesidad de deduplicar. Cuando piensas en deduplicación, lo primero que mucha gente piensa es en usar
un HashSet . Sin embargo, el uso descuidado
de HashSet puede provocar que falle la deduplicación.
User user1 = new User();
user1.setUsername("test");
User user2 = new User();
user2.setUsername("test");
List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size());
Algunos lectores atentos deberían poder adivinar el motivo del fallo.
HashSet usa un código hash para acceder a la tabla hash y usa el método igual para determinar si los objetos son iguales. Si el objeto definido por el usuario no anula el método de código hash y el método
igual , entonces el método de código hash y el método
igual del objeto principal se utilizarán de forma predeterminada. Esto hará que
HashSet asuma que son dos objetos diferentes, lo que provocará que falle la deduplicación.
Eliminando un hilo de piscina "comido"
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(()->{
double result = 10/0;
});
El código anterior simula un escenario en el que se lanza una excepción en el grupo de subprocesos. El código comercial debe asumir varias situaciones, por lo que es muy probable que arroje
una RuntimeException por algún motivo . Pero si no hay un manejo especial aquí, el grupo de subprocesos "devorará" esta excepción. Y ni siquiera tendrás forma de comprobar la causa de la excepción. Por lo tanto, es mejor detectar excepciones en el grupo de procesos.
Cadenas en Java - vista interior
Fuente:
Medio El autor de este artículo decidió analizar detalladamente la creación, funcionalidad y características de las cadenas en Java.
Creación
Una cadena en Java se puede crear de dos maneras diferentes: implícitamente, como una cadena literal, y explícitamente, usando la
nueva palabra clave . Los literales de cadena son caracteres encerrados entre comillas dobles.
String literal = "Michael Jordan";
String object = new String("Michael Jordan");
Aunque ambas declaraciones crean un objeto de cadena, existe una diferencia en cómo se ubican ambos objetos en la memoria del montón.
Representación interna
Anteriormente, las cadenas se almacenaban en el formato
char[] , lo que significaba que cada carácter era un elemento separado en la matriz de caracteres. Dado que estaban representados en el formato de codificación de caracteres
UTF-16 , esto significaba que cada carácter ocupaba dos bytes de memoria. Esto no es muy correcto, ya que las estadísticas de uso muestran que la mayoría de los objetos de cadena constan únicamente de caracteres
Latin-1 . Los caracteres Latin-1 se pueden representar usando un solo byte de memoria, lo que puede reducir significativamente el uso de la memoria, hasta en un 50%. Se implementó una nueva función de cadena interna como parte de la versión JDK 9 basada en
JEP 254 llamada Compact Strings. En esta versión,
char[] se cambió a
byte[] y se agregó un campo de indicador de codificador para representar la codificación utilizada (Latin-1 o UTF-16). Después de esto, se produce la codificación según el contenido de la cadena. Si el valor contiene solo caracteres Latin-1, entonces se usa la codificación Latin-1 (la clase
StringLatin1 ) o la codificación UTF-16 (la clase
StringUTF16 ).
Asignación de memoria
Como se indicó anteriormente, existe una diferencia en la forma en que se asigna la memoria para estos objetos en el montón. Usar la nueva palabra clave explícita es bastante sencillo ya que la JVM crea y asigna memoria para la variable en el montón. Por lo tanto, el uso de una cadena literal sigue un proceso llamado pasantía. La pasantía de cuerdas es el proceso de poner cuerdas en un grupo. Utiliza un método para almacenar solo una copia de cada valor de cadena individual, que debe ser inmutable. Los valores individuales se almacenan en el grupo String Intern. Este grupo es un almacén
Hashtable que almacena una referencia a cada objeto de cadena creado usando literales y su hash. Aunque el valor de la cadena está en el montón, su referencia se puede encontrar en el grupo interno. Esto se puede verificar fácilmente mediante el siguiente experimento. Aquí tenemos dos variables con el mismo valor:
String firstName1 = "Michael";
String firstName2 = "Michael";
System.out.println(firstName1 == firstName2);
Durante la ejecución del código, cuando la JVM encuentra
firstName1 , busca el valor de la cadena en el grupo de cadenas interno
Michael . Si no puede encontrarlo, se crea una nueva entrada para el objeto en el grupo interno. Cuando la ejecución llega a
firstName2 , el proceso se repite nuevamente y esta vez el valor se puede encontrar en el grupo en función de la variable
firstName1 . De esta manera, en lugar de duplicar y crear una nueva entrada, se devuelve el mismo enlace. Por tanto, se cumple la condición de igualdad. Por otro lado, si se crea una variable con el valor
Michael usando la nueva palabra clave, no se produce ninguna internación y no se cumple la condición de igualdad.
String firstName3 = new String("Michael");
System.out.println(firstName3 == firstName2);
La pasantía se puede utilizar con
el método
intern() firstName3 , aunque normalmente no se prefiere.
firstName3 = firstName3.intern();
System.out.println(firstName3 == firstName2);
La internación también puede ocurrir al concatenar dos cadenas literales usando el operador
+ .
String fullName = "Michael Jordan";
System.out.println(fullName == "Michael " + "Jordan");
Aquí vemos que en tiempo de compilación, el compilador agrega ambos literales y elimina el operador
+ de la expresión para formar una sola cadena como se muestra a continuación. En tiempo de ejecución, tanto
el nombre completo como el "literal agregado" se internan y se cumple la condición de igualdad.
System.out.println(fullName == "Michael Jordan");
Igualdad
En los experimentos anteriores, puede ver que de forma predeterminada solo se internan cadenas literales. Sin embargo, una aplicación Java ciertamente no tendrá sólo cadenas literales, ya que puede recibir cadenas de diferentes fuentes. Por lo tanto, no se recomienda utilizar el operador de igualdad y puede producir resultados no deseados. Las pruebas de igualdad sólo deben realizarse mediante el método
de iguales . Realiza igualdad basándose en el valor de la cadena en lugar de en la dirección de memoria donde está almacenada.
System.out.println(firstName1.equals(firstName2));
System.out.println(firstName3.equals(firstName2));
También hay una versión ligeramente modificada del método igual llamada
equalsIgnoreCase . Puede resultar útil para fines que no distinguen entre mayúsculas y minúsculas.
String firstName4 = "miCHAEL";
System.out.println(firstName4.equalsIgnoreCase(firstName1));
Inmutabilidad
Las cadenas son inmutables, lo que significa que su estado interno no se puede cambiar una vez creadas. Puede cambiar el valor de una variable, pero no el valor de la cadena en sí. Cada método de la clase
String que se ocupa de manipular un objeto (por ejemplo,
concat ,
substring ) devuelve una nueva copia del valor en lugar de actualizar el valor existente.
String firstName = "Michael";
String lastName = "Jordan";
firstName.concat(lastName);
System.out.println(firstName);
System.out.println(lastName);
Como puede ver, no se producen cambios en ninguna de las variables: ni
nombre ni
apellido . Los métodos de clase
String no cambian el estado interno, crean una nueva copia del resultado y devuelven el resultado como se muestra a continuación.
firstName = firstName.concat(lastName);
System.out.println(firstName);
GO TO FULL VERSION