[Java] 8. StringBuffer & StringBuilder & Wrapper Class & Generic

Kyunghwan Ko·2022년 9월 30일
0

Java

목록 보기
10/14

01. String vs StringBuffer & StringBuilder

String 클래스

String과 StringBuffer & StringBuilder 클래스의 가장 큰 차이점은 String은 불변성(immutable)을 갖는다는 점입니다.

1. String str = "hello"; // String str = new String("hello");
2. str = str + " world";
(GC: Garbage Collection, Memory: 정확하게는 Heap영역의 String pool)

위 사진과 같이 String은 불변성을 가지기 때문에 변하지 않는 문자열을 자주 읽어들이는 경우 String클래스를 사용해주면 좋은 성능을 기대할 수 있다. 그러나 문자열의 추가, 수정, 삭제 등의 연산(이하, 문자열 연산)이 빈번하게 발생하는 로직에서 String클래스를 사용하면 Heap 메모리영역에 많은 임시 Gargabe가 생성되어 Heap 메모리가 부족하게 되어 애플리케이션 성능에 큰 영향을 끼치게 된다.

이를 해결하기 위해 Java에서는 가변성(mutable)을 가지는 StringBuffer & StringBuilder 클래스를 도입했습니다. String과는 반대로 두 클래스는 가변성을 가지기 때문에 append(), delete() 등의 API를 이용하여 동일 객체내에서 문자열을 변경하는 것이 가능합니다. 따라서 문자열 연산가 빈번하게 발생할 경우라면 String 클래스가 아니라 StringBuffer 또는 StringBuilder 클래스를 사용하는 것이 좋다.

StringBuffer sb = new StringBuffer("hello");
sb.append(" world");

StringBuffer vs StringBuilder 클래스

동일한 API를 가지고 있는 StringBuffer와 StringBuilder의 차이점은 무엇일까?
가장 큰 차이점은 동기화의 유무로써 StringBuffer는 동기화를 지원하여 멀티쓰레드 환경에서 안정하다는 점(thread-safe)이다. 참고로 String도 불변성을 가지기 때문에 마찬가지로 thread-safe하다는 특징을 가진다.

정리

  • String: 문자열 연산이 적고 멀티쓰레드 환경일 경우 적합
  • StringBuffer: 문자열 연산이 많고 멀티쓰레드 환경일 경우 적합
  • StringBuilder: 문자열 연산이 많고 단일쓰레드이거나 동기화를 고려하지 않아도 되는 경우 적합
비교조건StringStringBufferStringBuilder
저장공간String pool(in Heap영역)Heap 영역Heap 영역
변경가능 가능한가XOO
thread-safe한가
(동기화 지원하는가)
OOX
처리 속도빠름느림빠름

~ wrpper 클래스 쓰자 ~
https://coding-factory.tistory.com/547

02. Wrapper 클래스

Integer

Integer n1 = Integer.valueOf(5);
Integer n2 = Integer.valueOf("1234");
int num = n1; // 언박싱(Unboxing)

Ingeger n3 = 5; // 박싱(Boxing)

2진, 8진, 16진수 표현

Integer.toBinaryString(20);
Integer.toOctalString(20);
Integer.toHexString(20);

Long.MAX_VALUE; 9천경
Long.MIN_VALUE; -9천경

// 9천경이 넘어갈 경우 대안
BigInteger b1 = new BigInteger("10000000000000000000000");

[0930_13시 녹화듣기]

03. Generic

자바 15버전 이상 되면서 아래의 내용이 새롭게 등장했습니다.

  • @Override
  • enum
  • Generic

Generic 이전 코드

print(객체) -> default로 객체의 toString()함수를 호출한다.

class Apple{
	@Override
	public Strng toString() {return "im a apple";}
}
class Orange{
	@Override
	public String toString() { return "im a orange"; }
}
// Apple, Orange 다 담을 수 있는 Box
class Box{
	private Object ob;
    public void set(Object o) {this.ob = o;}
    public Object get() {return this.ob;}
}

public class GenericPractice1{
	Box aBox = new Box();
    Box oBox = new Box();
    aBox.set(new Apple());
    oBox.set(new Box());
    
    Apple ap = (Apple) aBox.get();
    Orange og = (Orange) oBox.get();
    
    Sysout(ap); // im a apple
    sysout(og); // im a orange
}

어쩔 수 없이 형변환(type casting)의 과정이 수반된다
그리고 이는 컴파일러의 오류발견가능성을 낮춘다.

문제점1

컴파일에러가 발생하지 않고 실행중간에 에러가 발생한다. 즉, 실시간 에러가 발생한다.

문제점2

프로그래머의 실수가 실행과정에서 조차 발견되지 않을 수 있다.

Object ob = "Apple"; 이 가능했기 때문에 위와 같은 에러가 발생한다.

"Apple".toString(); 되면서 의도치 않는 결과가 나와서

제네릭으로 문제해결

SW적으로 위와 같은 문제를 해결한 것이 아니라
제네릭 문법을 별도로 만들어서 우리가 지키게 만들었다.
(제네릭 문법으로 인해 위와 같은 상황이 발생하면 바로 컴파일에러를 띄움)

제네릭 기반의 클래스 정의하기

인스턴스 생성시 결정이 되는 자료형의 정보를 T로 대체한다.
(모든 객체의 최상위조상인 Object로 받는 것 x)

T를 타입매개변수라고 한다.


set, get()함수 안에 인자로 있는 타입 T가 매개변수화 타입이다.

class Apple{
}
class Orange{
}
class Box<T>{
	private T ob ~ 위 사진처럼 코드 쓰자
}

public class GenericPractice1{
	public static void main(String[] args){
		Box<Apple> aBox = new Box<Apple>();
   	 	Box<Orange> oBox = new Box<Orange>();
    	aBox.set(new Apple());
    	oBox.set(new Orange());
    	Apple ap = aBox.get();
    	Orange og = oBox.get();
    	System.out.println(ap); // im a apple 
    	System.out.println(og); // im a orange
   }
}

개선점:

  1. 형변환 사라짐 // Apple ap = (Apple) aBox.get(); 하지 않아도됨
  2. 프로그래머의 실수로 인한 컴파일 에러가 명확해짐 // aBox.set("Apple"); 하면 컴파일 에러남 Box<Apple> aBox = new Box<Apple>(); 했기 때문임
    (매개변수화 타입에서 Apple은 생략해도됨 컴파일러가 알아서 앞에있는 타입인자보고 생략된 매개변수화 타입 채워넣음)
[발생하는 컴파일 에러]
Unsolved compilation problem
not applicable for the arguments

제네릭 적용 o/x

public class GenericPractice1 {
	static void printArray_noGeneric(Object[] obArray) {
		for (Object ob : obArray) {
			if(ob instanceof String) {
				System.out.print((String) ob);
			} else if(ob instanceof Integer) {
				System.out.print((Integer) ob);
			}
		}
	}
	
	static <T> void printArray_Generic(T[] tArray) {
		for (T t : tArray) {
			System.out.print(t);
		}
	}
	
	public static void main(String[] args) {
		Integer[] intArray = {1, 2, 3};
		String[] strArray = {"Hellow", "World"};
		printArray_noGeneric(intArray);
		printArray_noGeneric(strArray);
		System.out.println();
		printArray_Generic(intArray);
		printArray_Generic(strArray);
 
	}

}

제네릭 클래스의 타입 인자 제한하기

class Box<T extends Number>{
	private T t;
	public void set(T t) {
		this.t = t;
	}
	public T get() {
		return this.t;
	}
}
// Integer와 Dobule Wrapper클래스는 Number클래스를 상속받았기 때문에 사용가능
// String, Apple 등은 사용 불가능!

왜, 타입 인자를 제한할까? - 타입 인자 제한효과

Box선언할 때 할당했던 객체의 참조타입인 T 클래스안에 있는 intValue()함수를 사용하고 싶은대
만약에 T클래스에 intValue()함수가 없다면 에러가 발생할 것이다.
따라서 T클래스 참조변수 ob에서 intValeu()함수를 사용하기 위해선 Number 클래스를 이미 상속한 클래스여야 하기 때문에 위와 같이 타입인자를 제한할 수 있다.

제네릭 메서드 정의


클래스 전부가 아닌 메서드 하나에 대해 제네릭으로 정의할 경우 <T>를 꼭 써줘야함

class Box<T>{ }
class BoxFactory{
	// <T>타입을 사용할 것이고 return타입은 Box<T>이고 ㅌ타입 매개변수로 T타입을 받겠다.
	public static <T> Box<T> makeBox(T o){
    	Box<T> box = new Box<T>();
        box.set(o);
        return box; //
    }
}

제네릭 메서드의 T는 메서드 호출 시점에 결정한다.

Box<String> sBox = BoxFactory.<String>makeBox("Sweet"); // T가 String타입이 된다.
Box<Double> dBox = BoxFactory.<Double>makeBox(7.59); // 7.59에 대해 오토 박싱(Boxing) 진행됨

아래와 같이 타입 인자 생략할 수 있다.

Box<String> sBox = BoxFactory.makeBox("Sweet"); // "Sweet"이 String이기 때문에 생략해도 컴파일러가 알게됨
Box<Double> dBox = BoxFactory.makeBox(7.59); // 7.59에 대해 오토 박싱

04. Calendar 클래스

날짜 정보를 얻어낼 수 있다.

Calendar now = new Calendar.getInstance(); // Calendar 객체 생성
int hour = now.get(Calendar.HOUR_OF_DAY);
int minute = now.get(Calendar.MINITE);

05. 참고(Reference)

[Java]String, StringBuffer, StringBuilder 차이 및 장단점

profile
부족한 부분을 인지하는 것부터가 배움의 시작이다.

0개의 댓글