-
[JAVA] 자바 mid1 1 - 3 강 요약자바 2024. 11. 21. 08:58
1. Object 클래스
java.lang 패키지
자바가 기본으로 제공하는 라이브러리(클래스 모음) 중에 가장 기본이 되는 것이 java.lang 패키지이다
쉽게 이야기해서 자바 언어를 이루는 가장 기본이 되는 클래스들을 보관하는 패키지를 뜻한다
java.lang 패키지의 대표적인 클래스들
- Object : 모든 자바 객체의 부모 클래스
- String : 문자열
- Integer, Long, Double : 래퍼 타입, 기본형 데이터 타입을 객체로 만든 것
- Class : 클래스 메타 정보
- System : 시스템과 관련된 기본 기능들을 제공
Object 클래스
자바에서 모든 클래스의 최상위 부모 클래스는 항상 Object 클래스이다
// 부모가 없으면 묵시적으로 Object 클래스를 상속받는다. public class Parent { public void parentMethod() { System.out.println("Parent.parentMethod"); } } // extends Object 추가 public class Parent extends Object { public void parentMethod() { System.out.println("Parent.parentMethod"); } }
클래스에 상속받을 부모 클래스가 없으면 묵시적으로 Object 클래스를 상속받는다
extends Object는 생략하는 것을 권장한다
Parent는 Object를 묵시적으로 상속 받았기 때문에 메모리에도 함께 생성된다
자바에서 Object 클래스가 최상위 부모 클래스인 이유
1. 공통 기능 제공
Object는 모든 객체에 필요한 공통 기능을 제공한다
Object는 최상위 부모 클래스이기 때문에 모든 객체는 공통 기능을 편리하게 제공(상속) 받을 수 있다
Object가 제공하는 기능
- 객체의 정보를 제공하는 toString()
- 객체가 같음을 비교하는 equals()
- 객체의 클래스 정보를 제공하는 getClass()
- 기타 등등
2. 다형성 기본 구현
Object 클래스는 다형성을 지원하는 기본적인 매커니즘을 제공한다
모든 자바 객체는 Object 타입으로 처리될 수 있으며 이는 다양한 타입의 객체를 통합적으로 처리할 수 있게 해 준다
Object 다형성
class Car { public void move() { System.out.println("자동차 이동"); } } class Dog { public void sound() { System.out.println("멍멍"); } }
public class ObjectPolyExample1 { public static void main(String[] args) { Dog dog = new Dog(); Car car = new Car(); action(dog); action(car); } private static void action(Object obj) { //obj.sound(); //컴파일 오류, Object는 sound()가 없다. //obj.move(); //컴파일 오류, Object는 move()가 없다. //객체에 맞는 다운캐스팅 필요 if (obj instanceof Dog dog) { dog.sound(); } else if (obj instanceof Car car) { car.move(); } } }
Object를 활용한 다형성의 한계
- Object는 모든 객체를 대상으로 다형적 참조를 할 수 있다
- Object를 통해 전달받은 객체를 호출하려면 각 객체에 맞는 다운캐스팅 과정이 필요하다
Object 배열
public class ObjectPolyExample2 { public static void main(String[] args) { Dog dog = new Dog(); Car car = new Car(); // Object 인스턴스도 만들 수 있다. Object object = new Object(); Object[] objects = {dog, car, object}; size(objects); } private static void size(Object[] objects) { System.out.println("전달된 객체의 수는: " + objects.length); } }
toString()
Object.toString() 메서드는 객체의 정보를 문자열 형태로 제공한다
그래서 디버깅과 로깅에 유용하게 사용된다
// Object.toString() public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); // ex. java.lang.Object@a09ee92 }
Object가 제공하는 toString() 메서드는 기본적으로 패키지 이름을 포함한 객체의 이름과 객체의 참조값(해시코드)을 16진수로 제공한다
toString() 오버라이딩
@Override public String toString() { return "Dog{" + "dogName='" + dogName + '\'' + ", age=" + age + '}'; }
Object와 OCP
public class Car { private String carName; public Car(String carName) { this.carName = carName; } }
public class Dog { private String dogName; private int age; public Dog(String dogName, int age) { this.dogName = dogName; this.age = age; } @Override public String toString() { return "Dog{" + "dogName='" + dogName + '\'' + ", age=" + age + '}'; } }
public class ObjectPrinter { public static void print(Object obj) { String string = "객체 정보 출력: " + obj.toString(); System.out.println(string); } }
public class ToStringMain2 { public static void main(String[] args) { Car car = new Car("ModelY"); Dog dog1 = new Dog("멍멍이1", 2); Dog dog2 = new Dog("멍멍이2", 5); System.out.println("1. 단순 toString 호출"); System.out.println(car.toString()); // lang.object.tostring.Car@452b3a41 System.out.println(dog1.toString()); // Dog{dogName='멍멍이1', age=2} System.out.println(dog2.toString()); // Dog{dogName='멍멍이2', age=5} System.out.println("2. println 내부에서 toString 호출"); System.out.println(car); // lang.object.tostring.Car@452b3a41 System.out.println(dog1); // Dog{dogName='멍멍이1', age=2} System.out.println(dog2); // Dog{dogName='멍멍이2', age=5} System.out.println("3. Object 다형성 활용"); ObjectPrinter.print(car); // 객체 정보 출력: lang.object.tostring.Car@452b3a41 ObjectPrinter.print(dog1); // 객체 정보 출력: Dog{dogName='멍멍이1', age=2} ObjectPrinter.print(dog2); // 객체 정보 출력: Dog{dogName='멍멍이2', age=5} } }
추상적인 것에 의존
ObjectPrinter 클래스는 Car, Dog 같은 구체적인 클래스를 사용하는 것이 아니라 추상적인 Object 클래스를 사용한다
ObjectPrinter와 Object를 사용하는 구조는 다형성을 매우 잘 활용하고 있다
다형성을 잘 활용한다는 것은 다형적 참조와 메서드 오버라이딩을 적절하게 사용한다는 뜻이다
- 다형적 참조 : print(Object obj), Object 타입을 매개변수로 사용해서 다형적 참조를 사용한다 (모든 객체 인스턴스를 인수로 받을 수 있다)
- 메서드 오버라이딩 : Object는 모든 클래스의 부모이다 따라서 Dog와 같은 구체적인 클래스는 Object가 가지고 있는 toString() 메서드를 오버라이딩 할 수 있다 > print(Object obj) 메서드는 Dog, Car와 같은 구체적인 타입에 의존하지 않고 추상적인 Object 타입에 의존하면서서 런타임에 각 인스턴스의 toString()을 호출할 수 있다
OCP 원칙
- Open : 새로운 클래스를 추가하고 toString()을 오버라이딩해서 기능을 확장할 수 있다
- Closed : 새로운 클래스를 추가해도 Object와 toString()을 사용하는 클라이언트 코드인 ObjectPrinter는 변경하지 않아도 된다
System.out.println()
System.out.println() 메서드도 Object 매개변수를 사용하고 내부에서 toString()을 호출한다
자바 언어는 객체지향 언어답게 언어 스스로도 객체지향의 특징을 매우 잘 활용한다
참고 - 정적 의존관계 vs 동적 의존관계
- 정적 의존관계는 컴파일 시간에 결정되며, 주로 클래스 간의 관계를 의미한다 앞서 보여준 클래스 의존 관계 그림 이 바로 정적 의존관계이다 쉽게 이야기해서 프로그램을 실행하지 않고, 클래스 내에서 사용하는 타입들만 보면 쉽게 의존관계를 파악할 수 있다
- 동적 의존관계는 프로그램을 실행하는 런타임에 확인할 수 있는 의존관계이다 앞서 ObjectPrinter.print(Object obj)에 인자로 어떤 객체가 전달될지는 프로그램을 실행해 봐야 알 수 있다 이렇게 런타임에 어떤 인스턴스를 사용하는지를 나타내는 것이 동적 의존관계이다
- 참고로 단순히 의존관계 또는 어디에 의존한다고 하면 주로 정적 의존관계를 뜻한다
equals() - 1. 동일성과 동등성
- 동일성(Identity) : == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인 (자바 머신 기준 물리적으로 같은 메모리에 있는 객체 인스턴스인지 참조값을 확인하는 것)
- 동등성(Equality) : equals() 메서드를 사용해서 두 객체가 논리적으로 동등한 지 확인 (보통 사람이 생각 기준 논리적으로 같은지 확인)
실무에서는 대부분 IDE가 만들어주는 equals()를 사용
참고로 동등성 비교가 항상 필요한 것은 아니다 동등성 비교가 필요한 경우에만 equals()를 재정의하면 된다
equals()와 hashCode()는 보통 함께 사용된다
2. 불변 객체
기본형과 참조형
- 기본형(Primitive Type) : 하나의 값을 여러 변수에서 절대로 공유하지 않는다
- 참조형(Reference Type) : 하나의 객체를 참조값을 통해 여러 변수에서 공유할 수 있다
공유 참조와 사이드 이펙트
사이드 이펙트(Side Effect)는 프로그래밍에서 어떤 계산이 주된 작업 외에 추가적인 부수 효과를 일으키는 것을 말한다
// 단순히 주소를 보관하는 객체 public class Address { private String value; public Address(String value) { this.value = value; } public void setValue(String value) { this.value = value; } public String getValue() { return value; } @Override public String toString() { return "Address{" + "value='" + value + '\'' + '}'; } }
public class RefMain1_1 { public static void main(String[] args) { //참조형 변수는 하나의 인스턴스를 공유할 수 있다. Address a = new Address("서울"); Address b = a; System.out.println("a = " + a); System.out.println("b = " + b); b.setValue("부산"); // b의 값을 부산으로 변경해야함 System.out.println("부산 -> b"); System.out.println("a = " + a); // 사이드 이펙트 발생 System.out.println("b = " + b); } }
개발자는 b의 주소값을 서울에서 부산으로 변경할 의도로 값 변경을 시도했다
하지만 a, b는 같은 인스턴스를 참조한다 따라서 a의 값도 함께 부산으로 변경되어 버린다
이렇게 주된 작업 외에 추가적인 부수 효과를 일으키는 것을 사이드 이펙트라 한다
프로그래밍에서 사이드 이펙트는 보 통 부정적인 의미로 사용되는데, 사이드 이펙트는 프로그램의 특정 부분에서 발생한 변경이 의도치 않게 다른 부분에 영 향을 미치는 경우에 발생한다
이로 인해 디버깅이 어려워지고 코드의 안정성이 저하될 수 있다
사이드 이펙트 해결 방안 : a, b가 처음부터 서로 다른 인스턴스를 참조
Address a = new Address("서울"); Address b = new Address("서울");
Address a = new Address("서울"); Address b = a;
참조값의 공유를 막을 수 있는 방법이 없다
불변 객체
불변 객체(Immutable Object) : 객체의 상태(객체 내부의 값, 필드, 멤버 변수)가 변하지 않는 객체
public class ImmutableAddress { private final String value; public ImmutableAddress(String value) { this.value = value; } public String getValue() { return value; } @Override public String toString() { return "Address{" + "value='" + value + '\'' + '}'; } }
- value의 필드를 final로 선언
- 값을 변경할 수 있는 setValue() 제거
- 이 클래스는 생성자를 통해서만 값을 설정할 수 있고 이후에는 값을 변경하는 것이 불가능하다
public class RefMain2 { public static void main(String[] args) { ImmutableAddress a = new ImmutableAddress("서울"); ImmutableAddress b = a; //참조값 대입을 막을 수 있는 방법이 없다. System.out.println("a = " + a); System.out.println("b = " + b); //b.setValue("부산"); //컴파일 오류 발생 b = new ImmutableAddress("부산"); System.out.println("부산 -> b"); System.out.println("a = " + a); System.out.println("b = " + b); } }
- b.setValue 메서드가 제거되었기 때문에 ImmutableAddress 인스턴스의 값을 변경할 수 있는 방법이 없다
- 값을 변경하고 싶다면 ImmutableAddress("부산") 인스턴스를 새로 생성해서 b에 대입한다
- 결과적으로 a, b는 서로 다른 인스턴스를 참조하고 a가 창조한던 ImmutableAddress는 그대로 유지된다
불변이라는 단순한 제약을 사용해서 사이드 이펙트라는 큰 문제를 막을 수 있다
- 객체의 공유 참조는 막을 수 없다. 그래서 객체의 값을 변경하면 다른 곳에서 참조하는 변수의 값도 함께 변경되는 사이드 이펙트가 발생한다 불변 객체는 값을 변경할 수 없기 때문에 사이드 이펙트가 원천 차단된다
- 불변 객체는 값을 변경할 수 없다 따라서 불변 객체의 값을 변경하고 싶다면 변경하고 싶은 값으로 새로운 불변 객체를 생성해야 한다
불변 객체 - 값 변경
public class ImmutableObj { private final int value; public ImmutableObj(int value) { this.value = value; } public ImmutableObj add(int addValue) { int result = value + addValue; return new ImmutableObj(result); } public int getValue() { return value; } }
public class ImmutableMain1 { public static void main(String[] args) { ImmutableObj obj1 = new ImmutableObj(10); ImmutableObj obj2 = obj1.add(20); // 계산 이후에도 기존값과 신규값 모두 확인 가능 System.out.println("obj1 = " + obj1.getValue()); System.out.println("obj2 = " + obj2.getValue()); } }
add() 메서드 : 기존 값에 새로운 값을 더해야 한다 > 불변 객체는 기존 값은 변경하지 않고 대신에 계산 결과를 바탕으로 새로운 객체를 만들어서 반환한다
정리
자바에서 가장 많이 사용되는 String 클래스가 바로 불변 객체이다
뿐만 아니라 자바가 기본으로 제공하는 Integer, LocalDate 등 수많은 클래스가 불변으로 설계되어 있다
따라서 불변 객체가 필요한 이유와 원리를 제대로 이해해야, 이런 기본 클래스들도 제대로 이해할 수 있다
클래스를 불변으로 설계하는 이유
- 캐시 안정성
- 멀티 쓰레드 안정성
- 엔티티의 값 타입
3. String 클래스
String 클래스와 참조형
String은 클래스이다 따라서 기본형이 아니라 참조형이다
String 클래스 - 비교
String클래스 비교할 때는 == 비교가 아니라 항상 equals() 비교를 해야 한다
참고로 String 클래스는 내부 문자열 값을 비교하도록 equals() 메서드를 재정의 해두었다
- 문자열 리터럴을 사용하는 경우 자바는 메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용한다
- 자바가 실행되는 시점에 클래스에 문자열 리터럴이 있으면 문자열 풀에 String 인스턴스를 미리 만들어둔다 이때 같은 문자열이 있으면 만들지 않는다
- String str3 = "hello"와 같이 문자열 리터럴을 사용하면 문자열 풀에서 "hello"라는 문자를 가진 String 인스턴스를 찾는다 그리고 찾은 인스턴스의 참조(x003)를 반환한다
- String str4 = "hello"의 경우 "hello" 문자열 리터럴을 사용하므로 문자열 풀에서 str3과 같은 x003 참조를 사용한다
- 문자열 풀 덕분에 같은 문자를 사용하는 경우 메모리 사용을 줄이고 문자를 만드는 시간도 줄어들기 때문에 성능도 최적화할 수 있다
String 클래스 - 불변 객체
String은 불변 객체이다 따라서 생성 이후에 절대로 내부의 문자열 값을 변경할 수 없다
public class StringImmutable1 { public static void main(String[] args) { String str = "hello"; str.concat(" java"); System.out.println("str = " + str); // str = hello } }
public class StringImmutable1 { public static void main(String[] args) { String str = "hello"; String str2 = str1.concat(" java"); System.out.println("str = " + str); // str = hello System.out.println("str2 = " + str2); // str2 = hello java } }
String.concat()은 내부에서 새로운 String객체를 만들어서 반환한다 따라서 불변과 기존 객체의 값을 유지한다
String이 불변으로 설계된 이유
문자열 풀에 있는 String 인스턴스의 값이 중간에 변경되면 같은 문자열을 참고하는 다른 변수의 값도 함께 변경된다
String은 자바 내부에서 문자열 풀을 통해 최적화를 한다
만약 String 내부의 값을 변경할 수 있다면 기존에 문자열 풀에서 같은 문자를 참조하는 변수의 모든 문자가 함께 변경되어 버리는 문제가 발생한다
str3이 참조하는 문자를 변경하면 str4의 문자도 함께 변경되는 사이드 이펙트 문제가 발생한다
String 클래스는 불변으로 설계되어 이런 사이드 이펙트 문제가 발생하지 않는다
String 클래스 - 주요 메서드
문자열 정보 조회
- length() : 문자열 길이 반환
- isEmpty() : 문자열이 비어 있는지 확인 (길이가 0)
- isBlank() : 문자열이 비어 있는지 확인 (길이가 0이거나 공백만 있는 경우)
- charAt(int index) : 지정된 인덱스에 있는 문자를 반환
문자열 비교
- equals(Object anObject) : 두 문자열이 동일한지 비교
- equalsIgnoreCase(String anotherString) : 두 문자열을 대소문자 구분 없이 비교
- compareTo(String anotherString) : 두 문자열을 사전 순으로 비교
- compareToIgnoreCase(String str) : 두 문자열을 대소문자 구분 없이 사전적으로 비교
- startsWith(String prefix) : 문자열이 특정 접두사로 시작하는지 확인
- endsWith(String suffix) : 문자열이 특정 접미사로 끝나는지 확인
문자열 검색
- contains(CharSequence s) : 문자열이 특정 문자열을 포함하고 있는지 확인
- indexOf(String ch) / indexOf(String ch, int fromIndex) : 문자열이 처음 등장하는 위치 반환
- lastIndexOf(String ch) : 문자열이 마지막으로 등장하는 위치 반환
문자열 조작 및 변환
- substring(int beginIndex) / substring(int beginIndex, int endIndex) : 문자열의 부분 문자열 반환
- concat(String str) : 문자열의 끝에 다른 문자열을 붙인다
- replace(CharSequence target, CharSequence replacement) : 특정 문자열을 새 문자열로 대체
- replaceFirst(String regex, String replacement) : 문자열에서 정규 표현식과 일치하는 첫 번째 부분을 새 문자열로 대체
- replaceAll(String regex, String replacement) : 문자열에서 정규 표현식과 일치하는 부분을 새 문자열로 대체
- toLowerCase() / toUpperCase() : 문자열을 소문자나 대문자로 변환
- trim() : 문자열 양쪽 끝의 공백 제거 (whitespace만 제거 가능)
- strip() : whitespace와 유니코드 공백을 포함해 제거
문자열 분할 및 조합
- split(String regex) : 문자열을 정규 표현식을 기준으로 분할
- join(CharSequence delimiter, CharSequence... elements) : 주어진 구분자로 여러 문자열을 결합
기본 유틸리티
- valueOf(Object obj) : 다양한 타입을 문자열로 변환
- toCharArray() : 문자열을 문자 배열로 변환
- format(String format, Object... args) : 형식 문자열과 인자를 사용하여 새로운 문자열을 생성
- matches(String regex) : 문자열이 주어진 정규 표현식과 일치하는지 확인
StringBuilder - 가변 String
불변인 불변인 String 클래스의 단점 : 문자를 더하거나 변경할 때마다 계속해서 새로운 객체를 생성해야 한다
> 불변인 String의 내부 값은 변경할 수 없기 때문에 변경된 값을 기반으로 새로운 String 객체를 생성해야 한다
String str = "A" + "B" + "C" + "D"; String str = String("A") + String("B") + String("C") + String("D"); String str = new String("AB") + String("C") + String("D"); String str = new String("ABC") + String("D"); String str = new String("ABCD");
- 이 경우 총 3개의 String 클래스가 추가로 생성된다
- 중간에 만들어진 new String("AB"), new String("ABC")는 사용되지 않으며 최종적으로 생성된 new String("ABCD")만 사용된다
- 결과적으로 중간에 만들어진 new String("AB"), new String("ABC")는 제대로 사용되지 않고 GC의 대상이 된다
해결 : 불변이 아닌 가변 String
가변은 내부의 값을 바로 변경하면 되기 때문에 새로운 객체를 생성할 필요가 없다
따라서 성능과 메모리 사용면에서 불변보다 더 효율적이다
public final class StringBuilder { char[] value;// 자바 9 이전 byte[] value;// 자바 9 이후 //여러 메서드 public StringBuilder append(String str) {...} public int length() {...} ... }
StringBuilder는 내부에 final이 아닌 변경할 수 있는 byte[]을 가지고 있디
public class StringBuilderMain1_1 { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); sb.append("A"); sb.append("B"); sb.append("C"); sb.append("D"); System.out.println("sb = " + sb); // sb = ABCD sb.insert(4, "Java"); System.out.println("insert = " + sb); // insert = ABCDJava sb.delete(4, 8); System.out.println("delete = " + sb); // delete = ABCD sb.reverse(); System.out.println("reverse = " + sb); // reverse = DCBA // StringBuilder -> String String string = sb.toString(); System.out.println("string = " + string); // string = DCBA } }
StringBuilder는 보통 문자열을 변경하는 동안만 사용하다가 문자열 변경이 끝나면 안전한(불변) String으로 변환하는 것이 좋다
가변(Mutable) vs 불변(Immutable)
- String은 불변하다 즉, 한 번 생성되면 그 내용을 변경할 수 없다 따라서 문자열에 변화를 주려고 할 때마다 새로운 String 객체가 생성되고, 기존 객체는 버려진다 이 과정에서 메모리와 처리 시간을 더 많이 소모한다
- StringBuilder는 가변적이다 하나의 StringBuilder 객체 안에서 문자열을 추가, 삭제, 수정할 수 있으며, 이때마다 새로운 객체를 생성하지 않는다 이로 인해 메모리 사용을 줄이고 성능을 향상할 수 있다 단 사이드 이펙트를 주의해야 한다
String 최적화
(자바의 String 최적화) 자바 컴파일러는 다음과 같이 문자열 리터럴을 더하는 부분을 자동으로 합쳐준다
문자열 리터럴 최적화
// 컴파일 전 String helloWorld = "Hello, " + "World!"; // 컴파일 후 String helloWorld = "Hello, World!";
런타임에 별도의 문자열 결합 연산을 수행하지 않기 때문에 성능이 향상된다
String 변수 최적화
String result = str1 + str2; String result = new StringBuilder().append(str1).append(str2).toString();
문자열 변수의 경우 그 안에 어떤 값이 들어있는지 컴파일 시점에는 알 수 없기 때문에 단순하게 합칠 수 없다
자바가 최적화를 처리해 주기 때문에 지금처럼 간단한 경우에는 StringBuilder를 사용하지 않아도 된다
String 최적화가 어려운 경우
문자열을 루프 안에서 문자열을 더하는 경우에는 최적화가 이루어지지 않는다
StringBuilder 사용하여 최적화할 수 있다
public class LoopStringMain { public static void main(String[] args) { long startTime = System.currentTimeMillis(); String result = ""; for (int i = 0; i < 100000; i++) { result += "Hello Java "; } long endTime = System.currentTimeMillis(); System.out.println("result = " + result); System.out.println("time = " + (endTime - startTime) + "ms"); } }
public class LoopStringBuilderMain { public static void main(String[] args) { long startTime = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 100000; i++) { sb.append("Hello Java "); } String result = sb.toString(); long endTime = System.currentTimeMillis(); System.out.println("result = " + result); System.out.println("time = " + (endTime - startTime) + "ms"); } }
StringBuilder를 직접 사용하는 것이 더 좋은 경우
- 반복문에서 반복해서 문자를 연결할 때
- 조건문을 통해 동적으로 문자열을 조합할 때
- 복잡한 문자열의 특정 부분을 변경해야 할 때
- 매우 긴 대용량 문자열을 다룰 때
참고: StringBuilder vs StringBuffer
StringBuilder와 똑같은 기능을 수행하는 StringBuffer 클래스도 있다
StringBuffer는 내부에 동기화가 되어 있어서, 멀티 스레드 상황에 안전하지만 동기화 오버헤드로 인해 성능이 느리다
StringBuilder는 멀티 쓰레드에 상황에 안전하지 않지만 동기화 오버헤드가 없으므로 속도가 빠르다
메서드 체인닝 - Method Chaining
public class ValueAdder { private int value; public ValueAdder add(int addValue) { value += addValue; return this; // add() 메서드는 자기 자신(this)의 참조값 반환 } public int getValue() { return value; } }
public class MethodChainingMain1 { public static void main(String[] args) { // 1. add() 메서드의 반환값은 사용하지 않음 ValueAdder adder = new ValueAdder(); adder.add(1); adder.add(2); adder.add(3); int result = adder.getValue(); System.out.println("result = " + result); // 2. add() 메서드의 반환값 사용 ValueAdder adder = new ValueAdder(); ValueAdder adder1 = adder.add(1); ValueAdder adder2 = adder1.add(2); ValueAdder adder3 = adder2.add(3); int result = adder3.getValue(); System.out.println("result = " + result); // 3. add() 메서드의 반환값을 바로 메서드 호출에 사용 ValueAdder adder = new ValueAdder(); int result = adder.add(1).add(2).add(3).getValue(); System.out.println("result = " + result); } }
메서드 호출의 결과로 자기 자신의 참조값을 반환하면, 반환된 참조값을 사용해서 메서드 호출을 계속 이어갈 수 있다
코드를 보면 .을 찍고 메서드를 계속 연결해서 사용한다
마치 메서드가 체인으로 연결된 것처럼 보이며 이러한 기법을 메서드 체이닝이라 한다
메서드 체이닝 기법은 코드를 간결하고 읽기 쉽게 만들어준다
StringBuilder와 메서드 체인(Chain)
public StringBuilder append(String str) { super.append(str); return this; }
public class StringBuilderMain1_2 { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); String string = sb.append("A").append("B").append("C").append("D") .insert(4, "Java") .delete(4, 8) .reverse() .toString(); System.out.println("string = " + string); } }
StringBuilder의 append() 메서드를 보면 자기 자신의 참조값을 반환한다
StringBuilder에서 문자열을 변경하는 대부분의 메서드도 메서드 체이닝 기법을 제공하기 위해 자기 자신을 반환한다
김영한의 실전자바 -중급 1편 ( 1 강 - 3 강)
728x90'자바' 카테고리의 다른 글
[JAVA / SPRING] @SuppressWarnings 어노테이션 (0) 2024.11.28 [JAVA] 자바 mid1 4 - 6 강 요약 (0) 2024.11.23 [JAVA] 자바 basic 9 - 12 강 요약 (3) 2024.11.20 [JAVA] 자바 basic 5 - 8 강 요약 (1) 2024.11.19 [JAVA] 자바 basic 1 - 4 강 요약 (0) 2024.11.19