Métodos de objetos de Java: toString()

En este artículo, iniciaré una serie de artículos que describen los métodos a menudo olvidados de la clase de objeto base del lenguaje Java. Abajo están los...

Introducción

En este artículo, iniciaré una serie de artículos que describen los métodos a menudo olvidados de la clase de objeto base del lenguaje Java. A continuación se muestran los métodos del Objeto Java base, que están presentes en todos los objetos Java debido a la herencia implícita de Objeto. Los enlaces a cada artículo de esta serie se incluyen para cada método a medida que se publican los artículos.

En las secciones siguientes, describiré cuáles son estos métodos, sus implementaciones básicas y cómo anularlos cuando sea necesario. El enfoque de este primer artículo es el método toString() que se usa para dar una representación de cadena que identifica una instancia de objeto y transmite su contenido y/o significado en forma legible por humanos.

El método toString()

A primera vista, el método toString() puede parecer un método bastante inútil y, para ser honesto, su implementación predeterminada no es muy útil. De forma predeterminada, el método toString() devolverá una cadena que enumera el nombre de la clase seguido de un signo @ y luego una representación hexadecimal de la ubicación de memoria a la que se ha asignado el objeto instanciado.

Para ayudar en mi discusión de los omnipresentes métodos de Java Object, trabajaré con una clase Person simple, definida así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.adammcquistan.object;

import java.time.LocalDate;

public class Person {
    private String firstName;
    private String lastName;
    private LocalDate dob;
    
    public Person() {}
    
    public Person(String firstName, String lastName, LocalDate dob) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.dob = dob;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public LocalDate getDob() {
        return dob;
    }

    public void setDob(LocalDate dob) {
        this.dob = dob;
    }
}

Junto con esta clase, tengo una clase Main rudimentaria para ejecutar los ejemplos que se muestran a continuación para presentar las características de la implementación base de toString().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package com.adammcquistan.object;

import java.time.LocalDate;

public class Main {
    public static void main(String[] args) {
        Person me = new Person("Adam", "McQuistan", LocalDate.parse("1987-09-23"));
        Person me2 = new Person("Adam", "McQuistan", LocalDate.parse("1987-09-23"));
        Person you = new Person("Jane", "Doe", LocalDate.parse("2000-12-25"));
        System.out.println("1. " + me.toString());
        System.out.println("2. " + me);
        System.out.println("3. " + me + ", " + you);
        System.out.println("4. " + me + ", " + me2);
}

La salida se ve así:

1
2
3
4
1. [correo electrónico protegido]
2. [correo electrónico protegido]
3. [correo electrónico protegido], [correo electrónico protegido]
4. [correo electrónico protegido], [correo electrónico protegido]

Lo primero que hay que mencionar es que la salida de las líneas uno y dos es idéntica, lo que muestra que cuando pasa una instancia de objeto a métodos como print, println, printf, así como a registradores, toString( El método ) se llama implícitamente.
Además, esta llamada implícita a toString() también ocurre durante la concatenación como se muestra en la salida de la línea 3.

Ok, ahora es el momento de que interponga mi propia opinión personal en lo que respecta a las mejores prácticas de programación Java. ¿Qué le parece potencialmente preocupante sobre la línea 4 (en realidad, cualquiera de los resultados)?

Espero que esté respondiendo con una pregunta como esta, "bueno, Adam, es bueno que la salida me diga el nombre de la clase, pero ¿qué diablos voy a hacer con esa dirección de memoria tonta?".

Y yo respondía con "¡Nada!". Es 99.99% inútil para nosotros como programadores. Una idea mucho mejor sería anular esta implementación predeterminada y proporcionar algo que sea realmente significativo, como esto:

1
2
3
4
5
6
7
8
public class Person {
    // omitting everyting else remaining the same

    @Override
    public String toString() {
        return "<Person: firstName=" + firstName + ", lastName=" + lastName + ", dob=" + dob + ">";
    }
}

Ahora, si vuelvo a ejecutar la clase principal anterior, obtengo el siguiente resultado muy mejorado:

1
2
3
4
1. <Person: firstName=Adam, lastName=McQuistan, dob=1987-09-23>
2. <Person: firstName=Adam, lastName=McQuistan, dob=1987-09-23>
3. <Person: firstName=Adam, lastName=McQuistan, dob=1987-09-23>, <User: firstName=Jane, lastName=Doe, dob=2000-12-25>
4. <Person: firstName=Adam, lastName=McQuistan, dob=1987-09-23>, <User: firstName=Adam, lastName=McQuistan, dob=1987-09-23>

¡DIOS MÍO! ¡Algo que pueda leer! Con esta implementación, ahora tengo la oportunidad de luchar para poder comprender lo que está sucediendo en un archivo de registro. Esto es especialmente útil cuando el soporte técnico grita sobre el comportamiento errático relacionado con las instancias de Personas en el programa por el que estoy enganchado.

Advertencias para implementar y usar toString() {#Advertencias para implementar y usar tostring}

Como se muestra en la sección anterior, implementar un método toString() informativo en sus clases es una idea bastante buena, ya que proporciona una manera significativa de transmitir el contenido y la identidad de un objeto. Sin embargo, hay momentos en los que querrá adoptar un enfoque ligeramente diferente para implementarlos.

Por ejemplo, supongamos que tiene un objeto que simplemente contiene demasiado estado para incluirlo en la salida de un método toString() o cuando el objeto contiene principalmente una colección de métodos de utilidades. En estos casos, a menudo es recomendable generar una descripción simple de la clase y sus intenciones.

Considere la siguiente clase de utilidad sin sentido que encuentra y devuelve a la persona de mayor edad de una lista de objetos Personas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class OldestPersonFinder {
    public List<Person> family;
    
    public OldestPersonFinder(List<Person> family) {
        this.family = family;
    }

    public Person oldest() {
        if (family.isEmpty()) {
            return null;
        }
        Person currentOldest = null;
        for (Person p : family) {
            if (currentOldest == null || p.getDob().isAfter(currentOldest.getDob())) {
                currentOldest = p;
            }
        }
        return currentOldest;
    }

    @Override
    public String toString() {
        return "Class that finds the oldest Person in a List";
    }
}

En este caso, no sería muy útil recorrer toda la colección de objetos Person en el miembro de la instancia List de la familia y construir una cadena ridículamente grande para volver que represente a cada Person. En cambio, es mucho más significativo devolver una cadena que describa las intenciones de la clase, que en este caso es encontrar a la ‘Persona’ que es la mayor.

Otra cosa que me gustaría sugerir encarecidamente es asegurarse de proporcionar acceso a toda la información específica de los datos de su clase que incluye en la salida de su método toString().

Digamos, por ejemplo, que no he proporcionado un método getter para el miembro dob de mi clase Person en un vano intento de mantener en secreto la edad de la persona. Desafortunadamente, los usuarios de mi clase Person finalmente se darán cuenta de que simplemente pueden analizar la salida del método toString() y adquirir los datos que buscan de esa manera. Ahora, si alguna vez cambio la implementación de toString(), estoy casi seguro de romper su código. Por otro lado, permítanme decir que generalmente es una mala idea analizar la salida toString() de un objeto por esta misma razón.

Conclusión

Este artículo describe los usos y el valor del método toString(), a menudo olvidado, de la clase de objeto base de Java. He explicado el comportamiento predeterminado y he dado mi opinión sobre por qué creo que es una mejor práctica implementar su propio comportamiento específico de clase.

Como siempre, gracias por leer y no se avergüence de comentar o criticar a continuación.

Licensed under CC BY-NC-SA 4.0