여기 Human(사람) 클래스가 있다.
public class Human {
private String name;
private String gender;
private ArrayList<Human> subHumanList;
public Human(String name, String gender) {
this.name = name;
this.gender = gender;
subHumanList = new ArrayList<>();
}
public void addHumanList(Human human) {
subHumanList.add(human);
}
}
사람 한 명을 만들면, 그 사람은 '이름'과 '성별'을 가진다. 이름과 성별은 한 개의 값만 가지는 것이 자연스럽다.
그리고 그(또는 그녀)는 자식을 낳을 수 있다. 자식도 똑같은 사람이기 때문에 '사람(Human)' 객체로 만들 수 있다. 한 명뿐만 아니라 여러 명의 자식을 낳을 수 있어서 Human 인스턴스를 ArrayList로 저장한다.
이제 한 집안의 가계부를 그려보자.
import java.util.ArrayList;
public class HumanTest {
public static void main(String[] args) {
Human fH1 = new Human("외할머니", "여");
Human fH2 = new Human("할머니", "여");
Human sbH1 = new Human("큰이모", "여");
Human sbH2 = new Human("둘째이모", "여");
Human sbH3 = new Human("셋째이모", "여");
Human sbH4 = new Human("엄마", "여");
Human sbH5 = new Human("큰아빠", "남");
Human sbH6 = new Human("고모", "여");
Human sbsbH1 = new Human("철수", "남");
Human sbsbH2 = new Human("길동", "남");
Human sbsbH3 = new Human("서희", "여");
Human sbsbH4 = new Human("남해", "남");
Human sbsbH5 = new Human("우리", "여");
Human sbsbH6 = new Human("강산", "남");
Human sbsbsbH1 = new Human("훈이", "남");
Human sbsbsbH2 = new Human("순신", "여");
Human sbsbsbH3 = new Human("이도", "여");
ArrayList<Human> list = new ArrayList<>();
list.add(fH1);
list.add(fH2);
fH1.addHumanList(sbH1);
fH1.addHumanList(sbH2);
fH1.addHumanList(sbH3);
fH1.addHumanList(sbH4);
fH2.addHumanList(sbH5);
fH2.addHumanList(sbH6);
sbH1.addHumanList(sbsbH1);
sbH1.addHumanList(sbsbH2);
sbH2.addHumanList(sbsbH3);
sbH5.addHumanList(sbsbH4);
sbH5.addHumanList(sbsbH5);
sbH5.addHumanList(sbsbH6);
sbsbH2.addHumanList(sbsbsbH1);
sbsbH6.addHumanList(sbsbsbH2);
sbsbH6.addHumanList(sbsbsbH3);
String result = toString(list);
System.out.println(result);
}
private static String toString(ArrayList<Human> list) {
StringBuilder sb = new StringBuilder();
for (Human item : list) {
sb.append(item);
}
return sb.toString();
}
}
여러 명의 '사람(Human)' 객체를 만들었다. 그리고 객체들을 각각의 상위 객체의 ArrayList에 넣었다. 할머니가 자식들을 가지고, 그 자식들이 또 자식들을 가지는 구조로 되어있다.
만약 이 상태로 테스트를 하면, list 변수가 가리키는 fH1, fH2의 주소 값이 나올 것이다.

제대로 그 객체의 정보를 나타내고 싶으면, Human 객체가 어떤 정보를 문자열로 나타낼 것인가 toString()을 구현해(오버 라이딩) 따로 설정해야 한다.
Human 클래스에 toString()을 구현해보자.
각 객체의 성별과 이름을 나타내려고 한다.
public String toString() {
return this.gender + ", " + this.name + System.lineSeparator();
}
이렇게 하면 아래와 같은 결과가 도출된다.

어? 딱 두 개만 나온다. 나는 분명 여러 사람을 만들어서 각각의 객체에 넣었는데 왜 가장 위에 있는 사람들만 나오는 걸까??
그 이유는, HumanTest 클래스의 main()에서 list 변수는 위 두 객체만 가지고 있기 때문이다.
내가 원하는 대로 설정하기 위해선 해당 객체가 가진 ArrayList를 따로 탐색해야 한다.
해당 기능을 구현하기 위해선 '재귀 함수'를 사용한다.
가장 먼저 toString()을 구현한다.
public String toString() {
String blank = "";
StringBuilder sb = new StringBuilder();
toStringRecursive(blank, sb);
return sb.toString();
}
여기서 각 객체가 반복될 때마다 만들어질 문장들이 저장될 수 있도록 StringBuilder을 만들어 준다.
그리고 실질적으로 문장이 만들어져 변수 sb(StringBuilder)에 저장될 메서드, toStringRecursive()를 만든다.
toStringRecursive()에는 '공백'과 앞으로 사용할 StringBuilder, 즉 sb를 매개변수로 넣어준다.
public void toStringRecursive(String blank, StringBuilder sb) {
String line = String.format("%s(%s) %s%s", blank, gender, name, System.lineSeparator());
sb.append(line);
if (subHumanList.isEmpty()) {
return;
}
for (Human human : subHumanList) {
human.toStringRecursive(blank + " ", sb);
}
}
일단 String line 변수를 선언하고 초기화시킨다. 하나의 객체마다 한 줄을 차지하도록 포맷한다.
그리고 가지고 온 sb(StringBuilder)에 해당 line을 저장한다.
그리고 if문을 통해 해당 객체가 '자식'을 가지고 있는지 확인한다.
if (subHumanList.isEmpty()) {
return;
}
만약 해당 객체가 자식이 없다면(해당 객체의 subHumanList가 비어있으면), 해당 메서드를 종료한다.
그렇지 않고 subHumanList에 객체들이 있다면 for문으로 진입해서 자식 객체들을 하나씩 확인한다.
for (Human human : subHumanList) {
human.toStringRecursive(blank + " ", sb);
}
subHumanList에 객체를 하나씩 가지고 나와, 그 자식 객체를 기준으로 toStringRecursive()를 돌린다.
그렇다면 해당 객체의 내용이 line에 초기화되어 다시 처음에 만들어 놓았던 sb(StringBuilder)에 저장한다.
여기서 중요한 건, 재귀 함수의 특징이다.
재귀 함수가 실행되면, 뒤에 순서는 잠깐 대기하고, 해당 재귀 함수를 먼저 처리한 후 실행된다. 그 성질을 이용해서 어떤 객체의 하위 자식들을 모두 탐색했으면 그 형제 객체의 차례가 되어 똑같이 자식 객체가 있는지 확인한다.
위 코드의 결과는 다음과 같다.

'Java' 카테고리의 다른 글
| [Java] 배열과 리스트 사이의 변환 방법들 ( feat. Arrays.asList() ) (0) | 2022.09.01 |
|---|---|
| [Java] String 클래스의 .split() 메서드 사용하기 (0) | 2022.08.31 |
| [Java] System.out.printf() 메서드 사용법 (feat. String.format()) (0) | 2022.08.29 |
| [Java] 가변인자로 받은 원소들은 어디로 향할까? (feat. Arrays.asList()로 리스트화 하기) (0) | 2022.08.28 |
| [OOP] 객체지향 개발 5대 원칙, 'SOLID'란 무엇인가? (1) | 2022.04.13 |