среда, 1 августа 2012 г.

Object: toString

Факты
    - toString предназначен для преобразования объекта в строку, но он не обязан обеспечить обратимое преобразование. Т.е. гарантировать, что по полученной строке можно восстановить объект. Пример: может распечатывать не все поля
    - toString предназначен для людей, а не для машин. Он должен обеспечить удобное для человека строковое представление. Пример: "ColoredPoint[x=2,y=4,color=RED]". Пример: "Phone[(097)345-65-32]".
    - неосторожный вызов toString для полей может привести к прямой или косвенной беcконечной рекурсии и StackOverflowException.
    - "эталонная реализация toString" у java.awt.Point
String str = new Point(1,2).toString(); // str == "Point[x=1,y=2]"

Материалы
[Effective Java] стр 39: "Всегда переопределяйте метод toString"

Примеры
import java.util.Arrays;
import java.util.List;
public class Employee {
    public int age;
    public String name;
    public int[] childrenAges;
    public List<String> childrenNames;

    @Override
    public String toString() {
        return "Employee[" +
                "age=" + age +
                ", name=" + name +
                ", childrenAges=" + Arrays.toString(childrenAges) +
                ", childrenNames=" + childrenNames + ']';
    }
}

import java.util.ArrayList;
public class TestEmployeeToString {
    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.age = 35;
        emp.name = "Mike";
        emp.childrenAges = new int[]{2, 4, 7};

        emp.childrenNames = new ArrayList<String>();
        emp.childrenNames.add("Jimmy");
        emp.childrenNames.add("Stella");
        emp.childrenNames.add("Ann");

        System.out.println("emp = " + emp);
    }
}
>> emp = Employee[age=35, name=Mike, childrenAges=[2, 4, 7], childrenNames=[Jimmy, Stella, Ann]]

Замечания:
1) Строка "emp = " + emp вызвала String.valueOf(Object), который вызвал emp.toString(). Вопрос - а почему не вызвали напрямую emp.toString()?
2) У массивов toString() не переопределен, поэтому пользуемся утилитарным методом Arrays.toString(int[]). Вопрос: а что вывел бы ", childrenAges=" + childrenAges?
3) У List-ов toString() должен быть переопределен, поэтому вызываем напрямую ", childrenNames=" + childrenNames, что неявно вызывает String.valueOf(Object), который вызвал emp.toString(). 
4) Инициализацию childrenNames можно было бы сделать в одну строку
import static java.util.Arrays.asList;        
...
emp.childrenNames = asList("Jimmy", "Stella", "Ann");
Вопрос: List - это интерфейс, экземпляр какого именно класса вернул метод Arrays.asList(...)?

Лабораторные
oop.tostring.plain_address (обязательно)
Реализуйте метод toString у Address.
public class Address {
    private String country;
    private String city;
    private String street;
    private int houseNumber;
}
Замечание: getter/setter я пропустил - для краткости кода. Добавьте их сами.
Address address = ...
System.out.println(address);
>> Address[country=.., city=.., street=.., houseNumber=..]


oop.tostring.plain_phone (обязательно)
Реализуйте метод toString у Phone. Он должен работать следующим образом:
System.out.println(new Phone(777, 1234567));
>> Phone[(777) 123-45-67]
public class Phone {
    private final int code;
    private final int number;

    public Phone(int code, int number) {
        this.code = code;
        this.number = number;
    }
}
Замечание: getter-ы я пропустил - для краткости кода. Добавьте их сами.



oop.tostring.nested_employee (обязательно)
Реализуйте метод toString у NestedEmployee. Он должен работать следующим образом:
NestedEmployee emp = ...
System.out.println(emp);
>> NestedEmployee[age=.., name=.., address=Address[..], phone = Phone[...]]
public class NestedEmployee {
    private int age;
    private String name;
    private Address address;
    private Phone phone;
}
Замечание: getter/setter я пропустил - для краткости кода. Добавьте их сами.
Рекомендация: address и phone должны выводить себя сами.


oop.tostring.cyclic_ref_hand_cut
Реализуйте метод toString у CyclicEmployee, который "обрезает" вложенные поля, которые могут привести к рекурсии.
public class CyclicEmployee {
    private int age;
    private String name;    
    private CyclicEmployee boss;
    private List<CyclicEmployee> subordinate;
}
>> Employee[age=30,name='Mike',boss=Employee[age=45,name='Ann'], subordinate=[Employee[age=25,name='Jimmy']]]
Замечание: getter/setter я пропустил - для краткости кода. Добавьте их сами.


oop.tostring.cyclic_ref_hand_expand (очень сложно)
Реализуйте метод toString у CyclikEmployee (из предыдущей лабораторной), который "разворачивает" вложенные поля, пока не встретит цикл.
Пример: проанализируйте, как реализован Arrays.deepToString(...)
import java.util.Arrays;
class DeepToStringTest {
    public static void main(String[] args) {
        Object[] arr0 = new Object[] {new Object[0], 1, "Hi!", null};
        Object[] arr1 = new Object[] {new Object[0], 1, "Hi!", null};
        Object[] arr2 = new Object[] {new Object[0], 1, "Hi!", null};
        arr0[3] = arr1;
        arr1[3] = arr2;
        arr2[3] = arr0;
        System.out.println(Arrays.deepToString(arr0));
    }
}
>> [[], 1, Hi!, [[], 1, Hi!, [[], 1, Hi!, [...]]]]


oop.tostring.cyclic_ref_jakarta (сложно)
Сделайте задание oop.tostring.nested_employee с помощью библиотеки Jakarta Commons Lang 3.1. Используйте класс ToStringBuilder. Пример:
 public class Person {
   String name;
   int age;
   boolean smoker;
   ...
   public String toString() {
     return new ToStringBuilder(this).
       append("name", name).
       append("age", age).
       append("smoker", smoker).
       toString();
   }
 }

oop.tostring.cyclic_ref_jakarta_reflection (очень сложно)
Сделайте задание oop.tostring.cyclic_ref_hand_expand с помощью библиотеки Jakarta Commons Lang 3.1. Используйте класс ReflectionToStringBuilder