Buscar..


Introducción

Esta página de documentación sirve para mostrar detalles con ejemplos sobre constructores de clases Java y sobre Métodos de clases de objetos que se heredan automáticamente de la Superclase Object de cualquier clase recién creada.

Sintaxis

  • Clase nativa final pública <?> getClass ()
  • pública final vacío nativo notificar ()
  • vacío nativo final público notificar a todos ()
  • la espera de final nativa pública pública (tiempo de espera largo) lanza InterruptedException
  • pública final void wait () lanza InterruptedException
  • la espera de final público (tiempo de espera largo, int nanos) lanza la excepción interrumpida
  • código nativo int local ()
  • booleanos públicos iguales (objeto obj)
  • Cadena pública a la cadena ()
  • El objeto nativo protegido clone () lanza la excepción CloneNotSupportedException
  • vacío vacío finalizado () lanza Throwable

método toString ()

El método toString() se usa para crear una representación de String de un objeto utilizando el contenido del objeto. Este método debe ser anulado al escribir su clase. toString() se llama implícitamente cuando un objeto se concatena a una cadena como en "hello " + anObject .

Considera lo siguiente:

public class User {
    private String firstName;
    private String lastName;
    
    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    @Override
    public String toString() {
        return firstName + " " + lastName;
    }
    
    public static void main(String[] args) {
        User user = new User("John", "Doe");
        System.out.println(user.toString()); // Prints "John Doe"
    }   
}

Aquí toString() de la clase Object se reemplaza en la clase User para proporcionar datos significativos sobre el objeto al imprimirlo.

Cuando se utiliza println() , el método toString() del objeto se llama implícitamente. Por lo tanto, estas declaraciones hacen lo mismo:

System.out.println(user); // toString() is implicitly called on `user`
System.out.println(user.toString());

Si el toString() no se reemplaza en la clase de User mencionada anteriormente, System.out.println(user) puede devolver User@659e0bfd o una cadena similar casi sin información útil, excepto el nombre de la clase. Esto se debe a que la llamada usará la implementación toString() de la clase de Object Java base que no sabe nada sobre la estructura de la clase de User o las reglas de negocios. Si desea cambiar esta funcionalidad en su clase, simplemente anule el método.

método igual ()

TL; DR

== pruebas de igualdad de referencia (si son el mismo objeto )

.equals() comprueba la igualdad de valores (si son lógicamente "iguales" )


equals() es un método usado para comparar dos objetos para la igualdad. La implementación predeterminada del método equals() en la clase Object devuelve true si y solo si ambas referencias apuntan a la misma instancia. Por lo tanto, se comporta igual que la comparación por == .

public class Foo {
    int field1, field2;
    String field3;

    public Foo(int i, int j, String k) {
        field1 = i;
        field2 = j;
        field3 = k;
    }

    public static void main(String[] args) {
        Foo foo1 = new Foo(0, 0, "bar");
        Foo foo2 = new Foo(0, 0, "bar");

        System.out.println(foo1.equals(foo2)); // prints false
    }
}

Aunque foo1 y foo2 se crean con los mismos campos, apuntan a dos objetos diferentes en la memoria. La implementación por defecto de equals() por lo tanto, se evalúa como false .

Para comparar el contenido de un objeto para la igualdad, se debe anular equals() .

public class Foo {
    int field1, field2;
    String field3;

    public Foo(int i, int j, String k) {
        field1 = i;
        field2 = j;
        field3 = k;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        Foo f = (Foo) obj;
        return field1 == f.field1 &&
               field2 == f.field2 &&
               (field3 == null ? f.field3 == null : field3.equals(f.field3));
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = 31 * hash + this.field1;
        hash = 31 * hash + this.field2;
        hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
        return hash;
    }

    public static void main(String[] args) {
        Foo foo1 = new Foo(0, 0, "bar");
        Foo foo2 = new Foo(0, 0, "bar");

        System.out.println(foo1.equals(foo2)); // prints true
    }
}

Aquí el método equals() anulado decide que los objetos son iguales si sus campos son iguales.

Observe que el método hashCode() también se sobrescribió. El contrato para ese método establece que cuando dos objetos son iguales, sus valores hash también deben ser iguales. Es por eso que uno casi siempre debe reemplazar hashCode() y equals() juntos.

Preste especial atención al tipo de argumento del método equals . Es el Object obj , no Foo obj . Si coloca este último en su método, eso no es una anulación del método equals .

Al escribir su propia clase, tendrá que escribir una lógica similar cuando se reemplaza a equals() y hashCode() . La mayoría de los IDE pueden generar esto automáticamente por ti.

Se puede encontrar un ejemplo de una implementación equals() en la clase String , que forma parte de la API de Java central. En lugar de comparar punteros, la clase String compara el contenido de String .

Java SE 7

Java 1.7 introdujo la clase java.util.Objects que proporciona un método conveniente, equals , que compara dos referencias potencialmente null , por lo que puede usarse para simplificar implementaciones del método equals .

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null || getClass() != obj.getClass()) {
        return false;
    }

    Foo f = (Foo) obj;
    return field1 == f.field1 && field2 == f.field2 && Objects.equals(field3, f.field3);
}

Comparación de clases

Dado que el método equals puede ejecutarse contra cualquier objeto, una de las primeras cosas que el método hace a menudo (después de verificar si es null ) es verificar si la clase del objeto que se está comparando coincide con la clase actual.

@Override
public boolean equals(Object obj) {
    //...check for null
    if (getClass() != obj.getClass()) {
        return false;
    }
    //...compare fields
}

Esto se hace normalmente como se muestra arriba comparando los objetos de clase. Sin embargo, eso puede fallar en algunos casos especiales que pueden no ser obvios. Por ejemplo, algunos marcos generan proxies dinámicos de clases y estos proxies dinámicos son en realidad una clase diferente. Aquí hay un ejemplo usando JPA.

Foo detachedInstance = ...
Foo mergedInstance = entityManager.merge(detachedInstance);
if (mergedInstance.equals(detachedInstance)) {
    //Can never get here if equality is tested with getClass()
    //as mergedInstance is a proxy (subclass) of Foo
}

Un mecanismo para evitar esa limitación es comparar clases usando instanceof

@Override
public final boolean equals(Object obj) {
    if (!(obj instanceof Foo)) {
        return false;
    }
    //...compare fields
}

Sin embargo, hay algunos escollos que se deben evitar al usar instanceof . Como Foo podría tener otras subclases y esas subclases podrían reemplazar a equals() , podría entrar en un caso en el que un Foo es igual a una FooSubclass pero la FooSubclass Foo no es igual a Foo .

Foo foo = new Foo(7);
FooSubclass fooSubclass = new FooSubclass(7, false);
foo.equals(fooSubclass) //true
fooSubclass.equals(foo) //false

Esto viola las propiedades de simetría y transitividad y, por lo tanto, es una implementación no válida del método equals() . Como resultado, cuando se utiliza instanceof , una buena práctica es hacer que el método equals() final (como en el ejemplo anterior). Esto asegurará que ninguna anulación de subclase sea equals() y viole las suposiciones clave.

método hashCode ()

Cuando una clase de Java anula el método equals , también debería anular el método hashCode . Como se define en el contrato del método :

  • Cada vez que se invoca en el mismo objeto más de una vez durante una ejecución de una aplicación Java, el método hashCode debe devolver constantemente el mismo número entero, siempre que no se modifique la información utilizada en comparaciones iguales en el objeto. No es necesario que este número entero permanezca consistente de una ejecución de una aplicación a otra ejecución de la misma aplicación.
  • Si dos objetos son iguales según el método equals(Object) , entonces llamar al método hashCode en cada uno de los dos objetos debe producir el mismo resultado entero.
  • No es necesario que si dos objetos son desiguales según el método equals(Object) , llamar al método hashCode en cada uno de los dos objetos debe producir resultados enteros distintos. Sin embargo, el programador debe tener en cuenta que producir resultados enteros distintos para objetos desiguales puede mejorar el rendimiento de las tablas hash.

Los códigos hash se utilizan en implementaciones de hash como HashMap , HashTable y HashSet . El resultado de la función hashCode determina el grupo en el que se colocará un objeto. Estas implementaciones de hash son más eficientes si la implementación de hashCode proporcionada es buena. Una propiedad importante de una buena implementación de hashCode es que la distribución de los valores de hashCode es uniforme. En otras palabras, existe una pequeña probabilidad de que numerosas instancias se almacenen en el mismo cubo.

Un algoritmo para calcular un valor de código hash puede ser similar al siguiente:

public class Foo {
    private int field1, field2;
    private String field3;

    public Foo(int field1, int field2, String field3) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        Foo f = (Foo) obj;
        return field1 == f.field1 &&
               field2 == f.field2 &&
               (field3 == null ? f.field3 == null : field3.equals(f.field3);
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = 31 * hash + field1;
        hash = 31 * hash + field2;
        hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
        return hash;
    }
}

Usando Arrays.hashCode () como un atajo

Java SE 1.2

En Java 1.2 y superior, en lugar de desarrollar un algoritmo para calcular un código hash, se puede generar uno utilizando java.util.Arrays#hashCode proporcionando un objeto o una matriz de primitivas que contiene los valores de campo:

@Override
public int hashCode() {
    return Arrays.hashCode(new Object[] {field1, field2, field3});
}
Java SE 7

Java 1.7 introdujo la clase java.util.Objects que proporciona un método conveniente, hash(Object... objects) , que calcula un código hash basado en los valores de los objetos que se le suministran. Este método funciona igual que java.util.Arrays#hashCode .

@Override
public int hashCode() {
    return Objects.hash(field1, field2, field3);
}

Nota: este enfoque es ineficiente y produce objetos de basura cada vez que se llama a su método hashCode() personalizado:

  • Se crea un Object[] temporal Object[] . (En la versión Objects.hash() , la matriz se crea mediante el mecanismo "varargs").
  • Si alguno de los campos son tipos primitivos, deben estar recuadrados y eso puede crear más objetos temporales.
  • La matriz debe estar poblada.
  • La matriz debe Arrays.hashCode Objects.hash método Arrays.hashCode o Objects.hash .
  • Las llamadas a Object.hashCode() que Arrays.hashCode o Objects.hash tiene que hacer (probablemente) no se pueden insertar.

Caché interno de códigos hash

Dado que el cálculo del código hash de un objeto puede ser costoso, puede ser atractivo almacenar el valor del código hash en el objeto la primera vez que se calcula. Por ejemplo

public final class ImmutableArray {
    private int[] array;
    private volatile int hash = 0;

    public ImmutableArray(int[] initial) {
        array = initial.clone();
    }

    // Other methods

    @Override
    public boolean equals(Object obj) {
         // ...
    }

    @Override
    public int hashCode() {
        int h = hash;
        if (h == 0) {
            h = Arrays.hashCode(array);
            hash = h;
        }
        return h;
    }
}

Este enfoque intercambia el costo de (repetidamente) calcular el código hash contra la sobrecarga de un campo adicional para almacenar en caché el código hash. Si esto se amortiza como una optimización del rendimiento dependerá de la frecuencia con la que se halle (busque) un objeto determinado y otros factores.

También notará que si el verdadero código hash de un ImmutableArray es cero (una posibilidad en 2 32 ), el caché no es efectivo.

Finalmente, este enfoque es mucho más difícil de implementar correctamente si el objeto que estamos haciendo hash es mutable. Sin embargo, hay mayores preocupaciones si los códigos hash cambian; Consulte el contrato anterior.

Métodos de esperar () y notificar ()

wait() y notify() trabajan en tándem: cuando un subproceso llama a wait() en un objeto, ese subproceso se bloqueará hasta que otro subproceso llame a notify() o notifyAll() en ese mismo objeto.

(Ver también: esperar () / notificar () )

package com.example.examples.object;

import java.util.concurrent.atomic.AtomicBoolean;

public class WaitAndNotify {

    public static void main(String[] args) throws InterruptedException {
        final Object obj = new Object();
        AtomicBoolean aHasFinishedWaiting = new AtomicBoolean(false);
    
        Thread threadA = new Thread("Thread A") {
            public void run() {
                System.out.println("A1: Could print before or after B1");
                System.out.println("A2: Thread A is about to start waiting...");
                try {
                    synchronized (obj) { // wait() must be in a synchronized block
                        // execution of thread A stops until obj.notify() is called
                        obj.wait();
                    }
                    System.out.println("A3: Thread A has finished waiting. "
                            + "Guaranteed to happen after B3");
                } catch (InterruptedException e) {
                    System.out.println("Thread A was interrupted while waiting");
                } finally {
                    aHasFinishedWaiting.set(true);
                }
            }
        };
    
        Thread threadB = new Thread("Thread B") {
            public void run() {
                System.out.println("B1: Could print before or after A1");

                System.out.println("B2: Thread B is about to wait for 10 seconds");
                for (int i = 0; i < 10; i++) {
                    try {                        
                        Thread.sleep(1000); // sleep for 1 second 
                    } catch (InterruptedException e) {
                        System.err.println("Thread B was interrupted from waiting");
                    }
                }
            
                System.out.println("B3: Will ALWAYS print before A3 since "
                        + "A3 can only happen after obj.notify() is called.");
            
                while (!aHasFinishedWaiting.get()) {
                    synchronized (obj) {
                        // notify ONE thread which has called obj.wait()
                        obj.notify();
                    }
                }
            }
        };
    
        threadA.start();
        threadB.start();
    
        threadA.join();
        threadB.join();
    
        System.out.println("Finished!");
    }
}

Algunos ejemplos de salida:

A1: Could print before or after B1
B1: Could print before or after A1
A2: Thread A is about to start waiting...
B2: Thread B is about to wait for 10 seconds
B3: Will ALWAYS print before A3 since A3 can only happen after obj.notify() is called.
A3: Thread A has finished waiting. Guaranteed to happen after B3
Finished!

B1: Could print before or after A1
B2: Thread B is about to wait for 10 seconds
A1: Could print before or after B1
A2: Thread A is about to start waiting...
B3: Will ALWAYS print before A3 since A3 can only happen after obj.notify() is called.
A3: Thread A has finished waiting. Guaranteed to happen after B3
Finished!

A1: Could print before or after B1
A2: Thread A is about to start waiting...
B1: Could print before or after A1
B2: Thread B is about to wait for 10 seconds
B3: Will ALWAYS print before A3 since A3 can only happen after obj.notify() is called.
A3: Thread A has finished waiting. Guaranteed to happen after B3
Finished!

método getClass ()

El método getClass() se puede usar para encontrar el tipo de clase de tiempo de ejecución de un objeto. Vea el ejemplo a continuación:

public class User {
   
    private long userID;
    private String name;

    public User(long userID, String name) {
        this.userID = userID;
        this.name = name;
    }
}

public class SpecificUser extends User {
    private String specificUserID;

    public SpecificUser(String specificUserID, long userID, String name) {
        super(userID, name);
        this.specificUserID = specificUserID;
    }
}

public static void main(String[] args){
    User user = new User(879745, "John");
    SpecificUser specificUser = new SpecificUser("1AAAA", 877777, "Jim");
    User anotherSpecificUser = new SpecificUser("1BBBB", 812345, "Jenny");

    System.out.println(user.getClass()); //Prints "class User"
    System.out.println(specificUser.getClass()); //Prints "class SpecificUser"
    System.out.println(anotherSpecificUser.getClass()); //Prints "class SpecificUser"
}

El método getClass() devolverá el tipo de clase más específico, por lo que cuando se llama a getClass() en anotherSpecificUser , el valor devuelto es la class SpecificUser porque es más bajo en el árbol de herencia que el User .


Es de destacar que, mientras que el método getClass se declara como:

public final native Class<?> getClass();

El tipo estático real devuelto por una llamada a getClass es Class<? extends T> donde T es el tipo estático del objeto en el que se llama a getClass .

Es decir, lo siguiente compilará:

Class<? extends String> cls = "".getClass();

método clone ()

El método clone() se utiliza para crear y devolver una copia de un objeto. Este método discutible debe evitarse ya que es problemático y se debe utilizar un constructor de copia o algún otro método para copiar en favor de clone() .

Para que el método se use, todas las clases que llaman al método deben implementar la interfaz Cloneable .

La interfaz Cloneable en sí misma es solo una interfaz de etiqueta utilizada para cambiar el comportamiento del método native clone() que verifica si la clase de objetos llamantes implementa Cloneable . Si la persona que llama no implementa esta interfaz, se CloneNotSupportedException una CloneNotSupportedException .

La clase Object sí no implementa esta interfaz, por lo que se CloneNotSupportedException una CloneNotSupportedException si el objeto que llama es de la clase Object .

Para que un clon sea correcto, debe ser independiente del objeto desde el cual se está clonando, por lo tanto, puede ser necesario modificar el objeto antes de que se devuelva. Esto significa crear esencialmente una "copia profunda" copiando también cualquiera de los objetos mutables que conforman la estructura interna del objeto que se está clonando. Si esto no se implementa correctamente, el objeto clonado no será independiente y tendrá las mismas referencias a los objetos mutables que el objeto desde el que se clonó. Esto resultaría en un comportamiento inconsistente ya que cualquier cambio en aquellos en uno afectaría al otro.

class Foo implements Cloneable {
    int w;
    String x;
    float[] y;
    Date z;
    
    public Foo clone() {
        try {
            Foo result = new Foo();
            // copy primitives by value
            result.w = this.w;
            // immutable objects like String can be copied by reference
            result.x = this.x;
            
            // The fields y and z refer to a mutable objects; clone them recursively.
            if (this.y != null) {
              result.y = this.y.clone();
            }
            if (this.z != null) {
              result.z = this.z.clone();
            }
            
            // Done, return the new object
            return result;
            
        } catch (CloneNotSupportedException e) {
            // in case any of the cloned mutable fields do not implement Cloneable
            throw new AssertionError(e);
        }
    }
}

finalizar () método

Este es un método protegido y no estático de la clase Object . Este método se utiliza para realizar algunas operaciones finales o limpiar operaciones en un objeto antes de que se elimine de la memoria.

Según el documento, este método es llamado por el recolector de basura en un objeto cuando la recolección de basura determina que no hay más referencias al objeto.

Pero no hay garantías de que se llame al método finalize() si el objeto aún es accesible o si no se ejecuta ningún recolector de basura cuando el objeto es elegible. Es por eso que es mejor no confiar en este método.

En las bibliotecas principales de Java, se pueden encontrar algunos ejemplos de uso, por ejemplo, en FileInputStream.java :

protected void finalize() throws IOException {
    if ((fd != null) &&  (fd != FileDescriptor.in)) {
        /* if fd is shared, the references in FileDescriptor
         * will ensure that finalizer is only called when
         * safe to do so. All references using the fd have
         * become unreachable. We can call close()
         */
        close();
    }
}

En este caso, es la última oportunidad de cerrar el recurso si ese recurso no se ha cerrado antes.

En general, se considera una mala práctica utilizar el método finalize() en aplicaciones de cualquier tipo y debe evitarse.

Los finalizadores no están destinados a liberar recursos (por ejemplo, cerrar archivos). Se llama al recolector de basura cuando (si!) El sistema se queda con poco espacio de almacenamiento. No se puede confiar en que se llame cuando el sistema se esté quedando sin los manejadores de archivos o, por cualquier otro motivo.

El caso de uso previsto para los finalizadores es para un objeto que está a punto de ser reclamado para notificar a otro objeto sobre su inminente destino. Ahora existe un mejor mecanismo para ese propósito: la clase java.lang.ref.WeakReference<T> . Si crees que necesitas escribir un método finalize() , entonces deberías investigar si puedes resolver el mismo problema usando WeakReference . Si eso no resuelve su problema, es posible que deba volver a pensar su diseño en un nivel más profundo.

Para leer más aquí, hay un artículo sobre el método finalize() del libro "Effective Java" de Joshua Bloch.

Constructor de objetos

Todos los constructores en Java deben hacer una llamada al constructor de Object . Esto se hace con la llamada super() . Esta tiene que ser la primera línea en un constructor. La razón de esto es para que el objeto se pueda crear realmente en el montón antes de que se realice una inicialización adicional.

Si no especifica la llamada a super() en un constructor, el compilador la colocará por usted.

Así que los tres de estos ejemplos son funcionalmente idénticos

con llamada explícita a super() constructor

public class MyClass {

    public MyClass() {
        super();
    }
}

con llamada implícita a super() constructor

public class MyClass {

    public MyClass() {
        // empty
    }
}

con constructor implícito

public class MyClass {

}

¿Qué pasa con el encadenamiento de constructores?

Es posible llamar a otros constructores como la primera instrucción de un constructor. Como tanto la llamada explícita a un súper constructor como la llamada a otro constructor tienen que ser las dos primeras instrucciones, se excluyen mutuamente.

public class MyClass {

    public MyClass(int size) {

        doSomethingWith(size);

    }

    public MyClass(Collection<?> initialValues) {

        this(initialValues.size());
        addInitialValues(initialValues);
    }
}

Llamar a la nueva MyClass(Arrays.asList("a", "b", "c")) llamará al segundo constructor con el argumento Lista, que a su vez delegará al primer constructor (que delegará implícitamente a super() ) y luego llame a addInitialValues(int size) con el segundo tamaño de la lista. Esto se usa para reducir la duplicación de código donde varios constructores necesitan hacer el mismo trabajo.

¿Cómo llamo a un constructor específico?

Dado el ejemplo anterior, uno puede llamar a new MyClass("argument") o new MyClass("argument", 0) . En otras palabras, al igual que la sobrecarga de métodos , simplemente llame al constructor con los parámetros que son necesarios para su constructor elegido.

¿Qué pasará en el constructor de la clase Object?

Nada más de lo que sucedería en una subclase que tiene un constructor vacío predeterminado (menos la llamada a super() ).

El constructor vacío predeterminado se puede definir explícitamente, pero si no, el compilador lo incluirá siempre que no haya otros constructores definidos.

¿Cómo se crea un Objeto a partir del constructor en Objeto?

La creación real de objetos se reduce a la JVM. Cada constructor en Java aparece como un método especial llamado <init> que es responsable de la inicialización de la instancia. El compilador suministra este método <init> y debido a que <init> no es un identificador válido en Java, no se puede usar directamente en el lenguaje.

¿Cómo invoca la JVM este método <init> ?

La JVM invocará el método <init> utilizando la instrucción especial invokespecial y solo puede invocarse en instancias de clase sin inicializar.

Para obtener más información, consulte la especificación JVM y la especificación del lenguaje Java:



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow