sirve - settitle java
¿Cómo funciona este fragmento de código Java?(Conjunto de cadenas y reflexión) (7)
¿Qué le pasó a Mario?
Lo cambiaste, básicamente. Sí, con la reflexión puede violar la inmutabilidad de las cadenas ... y debido al internamiento de cadenas, eso significa que cualquier uso de "Mario" (que no sea en una expresión constante de cadena más grande, que se habría resuelto en tiempo de compilación) terminará como "Luigi" en el resto del programa.
Este tipo de cosas es la razón por la cual la reflexión requiere permisos de seguridad ...
Tenga en cuenta que la expresión
str + " " + "Mario"
no
realiza ninguna concatenación en tiempo de compilación, debido a la asociatividad izquierda de
+
.
Es efectivamente
(str + " ") + "Mario"
, por lo que todavía ves a
Luigi Luigi
.
Si cambia el código a:
System.out.println(str + (" " + "Mario"));
... entonces verás a
Luigi Mario
ya que el compilador habrá internado a
" Mario"
en una cadena diferente a
"Mario"
.
Esta pregunta ya tiene una respuesta aquí:
- ¿Es una cadena Java realmente inmutable? 15 respuestas
El conjunto de cadenas de Java junto con la reflexión puede producir un resultado inimaginable en Java:
import java.lang.reflect.Field;
class MessingWithString {
public static void main (String[] args) {
String str = "Mario";
toLuigi(str);
System.out.println(str + " " + "Mario");
}
public static void toLuigi(String original) {
try {
Field stringValue = String.class.getDeclaredField("value");
stringValue.setAccessible(true);
stringValue.set(original, "Luigi".toCharArray());
} catch (Exception ex) {
// Ignore exceptions
}
}
}
El código anterior se imprimirá:
"Luigi Luigi"
¿Qué le pasó a Mario?
Acabas de cambiar el
grupo constante
de
String
of
String
Mario
a
Luigi
que fue referenciado por múltiples
String
s, por lo que cada referencia
literal de
Mario
ahora es
Luigi
.
Field stringValue = String.class.getDeclaredField("value");
Ha obtenido el campo de
value
nombre
char[]
de la clase
String
stringValue.setAccessible(true);
Hazlo accesible.
stringValue.set(original, "Luigi".toCharArray());
String
campo
original
String
a
Luigi
.
Pero el original es
Mario
the
String
literal
y literal pertenece al grupo de
String
y todos están
internados
.
Lo que significa que todos los literales que tienen el mismo contenido se refieren a la misma dirección de memoria.
String a = "Mario";//Created in String pool
String b = "Mario";//Refers to the same Mario of String pool
a == b//TRUE
//You changed ''a'' to Luigi and ''b'' don''t know that
//''a'' has been internally changed and
//''b'' still refers to the same address.
Básicamente, ha cambiado el grupo Mario of
String
que se
reflejó
en todos los campos de referencia.
Si crea un
Object
String
(es decir, una
new String("Mario")
) en lugar de literal, no enfrentará este comportamiento porque tendrá dos
Mario
s diferentes.
Estaba configurado para Luigi.
Las cadenas en Java son inmutables;
por lo tanto, el compilador puede interpretar todas las menciones de
"Mario"
como referencias al mismo elemento de agrupación constante de Cadena (aproximadamente, "ubicación de memoria").
Usaste reflejo para cambiar ese elemento;
así que todos los
"Mario"
en su código ahora son como si escribiera
"Luigi"
.
Las otras respuestas explican adecuadamente lo que está sucediendo. Solo quería agregar el punto de que esto solo funciona si no hay un administrador de seguridad instalado. Cuando se ejecuta código desde la línea de comandos de forma predeterminada, no existe, y puede hacer cosas como esta. Sin embargo, en un entorno donde el código de confianza se mezcla con código no confiable, como un servidor de aplicaciones en un entorno de producción o un sandbox de applet en un navegador, normalmente habrá un administrador de seguridad presente y no se le permitirán este tipo de travesuras, por lo que Esto es menos de un terrible agujero de seguridad como parece.
Los literales de cadena se almacenan en el grupo de cadenas y se utiliza su valor canónico.
Ambos literales
"Mario"
no son solo cadenas con el mismo valor, son
el mismo objeto
.
Manipular uno de ellos (usando la reflexión) modificará "ambos", ya que son solo dos referencias al mismo objeto.
Otro punto relacionado: puede utilizar el grupo constante para mejorar el rendimiento de las comparaciones de cadenas en algunas circunstancias, utilizando el método
String.intern()
.
Ese método devuelve la instancia de String con el mismo contenido que el String en el que se invoca desde el grupo de constantes de String, y lo agrega si aún no está presente.
En otras palabras, después de usar
intern()
, se garantiza que todas las cadenas con el mismo contenido serán la misma instancia de cadena entre sí y como cualquier constante de cadena con esos contenidos, lo que significa que puede usar el operador igual (
==
) en ellas .
Este es solo un ejemplo que no es muy útil por sí solo, pero ilustra el punto:
class Key {
Key(String keyComponent) {
this.keyComponent = keyComponent.intern();
}
public boolean equals(Object o) {
// String comparison using the equals operator allowed due to the
// intern() in the constructor, which guarantees that all values
// of keyComponent with the same content will refer to the same
// instance of String:
return (o instanceof Key) && (keyComponent == ((Key) o).keyComponent);
}
public int hashCode() {
return keyComponent.hashCode();
}
boolean isSpecialCase() {
// String comparison using equals operator valid due to use of
// intern() in constructor, which guarantees that any keyComponent
// with the same contents as the SPECIAL_CASE constant will
// refer to the same instance of String:
return keyComponent == SPECIAL_CASE;
}
private final String keyComponent;
private static final String SPECIAL_CASE = "SpecialCase";
}
Este pequeño truco no vale la pena diseñar su código, pero vale la pena tenerlo en cuenta para el día en que note que se puede obtener un poco más de velocidad de un poco de código sensible al rendimiento utilizando el operador
==
en una cadena con uso juicioso de
intern()
.