Java - java.lang패키지, String, StringBuffer, Math 클래스
studyjava.lang 패키지는 자바 개발 시 가장 기본이 되는 클래스들을 포함하고 있다. import문 없이도 사용이 가능하다는 특징을 가지고 있다. 아래에서 java.lang 패키지의 대표적인 클래스들을 살펴보자.
1. Object 클래스
모든 클래스의 최고 조상인 Object클래스는 11개의 methods 를 가지고 있다. equals(), toString(), hashCode() 등 Object 클래스의 글은 아래의 글을 참고하자 [https://ezerocho.tistory.com/14]
2. Wrapper클래스
기본타입: int, byte, short, long, char… (기본형) Wrapper클래스: Integer, Byte, Short, Long, Charactor… (참조형)
가 존재한다.
기본타입을 Wrapper클래스로 바꾸는 것을 박싱, 반대의 경우를 언박싱이라고 한다.
박싱과 언박싱의 정의
박싱: 값 형식, 기본형을(value types)을 참조형식(reference type)으로 바꿔주는것 언박싱: 참조형식을 기본형으로 바꾸는것
기본형은 Stack메모리에 저장된다. 참조형은 힙메모리에 저장된다. 그리고 참조형의 Stack에는 힙메모리의 주소가 존재한다.
기본형은 null이 존재하지 않는다. 참조형은 null값이 존재한다. 즉, null값이 요구되는 데이터 접근을 위해 참조형 사용이 필요하다.(Ex, DB)
Integer num2 = new Integer(332); // 박싱 기본형 -> 참조형
Integer num1 = 332; //오토박싱
int a1 = num2.intValue(); //언박싱 참조형 -> 기본형
int a2 = num2; //오토언박싱
System.out.println(a1);
System.out.println(a2);
System.out.println(num1);
System.out.println(num2);
Wrapper클래스를 왜 사용할까?
Wrapper 클래스는 기본 자료타입을 객체로 다루기 위해서 사용한다. 객체 타입을 요구하는 곳이 많다! (메모리와 관련된 메서드, 큐와 배열의 정의 등등..)
그럼 오토박싱 오토언박싱은 무엇일까?
오토 박싱과 오토 언박싱은 JDK1.5버전부터 변경된 내용이다. 바로, 박싱과 언박싱 단계에서 기본타입을 래퍼클래스로 변환할 필요가 없어졌다!
컴파일러에 의해 자동으로 Wrapper클래스(참조형)로 변환되기 때문이다.
하지만 오토박싱 언박싱은 역시 추가적인 연산을 요구하기 때문에, 가능하면 박싱을 해주는 것이 좋다.(그렇다고 너무 큰 차이는 없다)
주요 사용처는 문자열 변환이다. (참조형 String을 변환하기 위한)
int strint = Integer.parseInt(str1);
short strshort = Short.parseShort(str2);
boolean strbool = Boolean.parseBoolean(str3);
double strdouble = Double.parseDouble(str4);
System.out.println(strint);
System.out.println(strshort);
System.out.println(strbool);
System.out.println(strdouble);
두번째론 ArrayList나 LinkedList에서의 사용이다. 이들도 역시 참조형이다.
ArrayList<Integer> arl = new ArrayList<>();
arl.add(30); //오토박싱
arl.add(Integer.valueOf(30)); //박싱
int arr0 = arl.get(0); //오토언박싱
int arr1 = arl.get(0).intValue(); //언박싱
Vector, Queue등을 정의할때도 사용된다.
Vector<Integer> v1 = new Vector<>();
Queue<Integer> queue = new LinkedList<>();
2-1. 참조(Reference)
- 참조타입의 비교
- 값비교: equals()를 사용한다.
- 주소비교: ‘==’ 연산자를 사용한다.
- 참조타입의 속도
- 접근시간이 더 오래걸린다(기본형들과 비교했을 때)
- 메모리를 더 많이 소모한다.(Heap영역과 stack영역으로 나뉘어 지기 때문이다.)
- 참조형 변수의 선언
String a = new String("abc");
위에서 참조형은 Heap 메모리에 그 값이 저장된다고 했다. 해당 선언문을 풀어서 각 부분의 역할을 아래와 같은 표로 나타낼 수 있다. ||||| |–|–|–|–|–| |Stack메모리|4바이트의 크기|주소|수행명령: String a| |Heap 메모리|요청한 값|실제 값|수행명령: new String(“abc”)|
참조를 이해하기 위한 코드
public class Test {
public int phoneNumber;
Test(int phoneNumber) {
this.phoneNumber = phoneNumber;
}
public static void main(String args[]){
Test a = new Test(4);
Test b = a;
b.phoneNumber = 5;
System.out.println(a.phoneNumber);
}
}
프로그램이 출력하는 결과는 무엇일까? 4일까? 5일까? 코드 해석 그림
3. String과 StringBuffer
우리는 Java 프로그래밍에서 문자열을 정의할 때 String을 사용한다. 그리고 문자열의 연산이 길어지거나, 문자열을 추가하는 상황에서는 StringBuffer, StringBuilder를 사용 해 왔다. 둘의 차이점을 무엇일까?
String 은 불변객체이다. String 생성시 할당 된 메모리는 변하지 않는다.
StringBuilder와 StringBuffer는 가변객체이다. 그래서 String 과는 다르게 객체의 공간이 부족해지면 Buffer의 크기를 늘려준다.
String의 불변성
그런데 우리는 String으로 수많은 연산을 한다. 대표적으론 문자열 더하기가 있다. 문자열을더할때 String 내부에서 어떤 변화가 나타나는지 살펴보자
아래 코드를 보면…
String str1 = "hello";
String str2 = " world";
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
str1 = str1 + str2;
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
String의 덧셈이다. str1과 str2를 더한 후 각각 메모리 값 정확히는 아님(hashCode)을 리턴했다. 결과는 다음과 같다.
99162322
1029451634
1794106052
1029451634
str1은 str1 + str2 연산을 통해 str1에 str2가 단순히 추가되었다고 생각할 수 있다.
하지만 String의 불변성 때문에, 내부적으로 str1은 새로운 메모리에 새로운 공간을 할당하여 새롭게 정의된 것이다.
우리는 이것으로 한번생성된 String 객체는 그 길이가 가변하지 않는다는것을 알 수 있다.
String 클래스의 문제점
String str3 = str1 + str2;
System.out.println(str3);
위의 코드에서 str1 과 str2를 가져와 새로운 문자열 str3를 만들었다.
우선 String은 기본타입이 아니다.(int, char, double, float…) boolean 을 제외한 나머지 기본형 들은 연산자를 사용할 수 있다.
그런데 어떻게 String이 연산자를 사용할 수 있는걸까?
사실 String의 연산에는 숨겨진 비밀이 있다.
String str3 = str1 + str2; //1번식
String str3 = new StringBuffer().append(str1).append(str2).toString(); //2번식
두 식은 정확하게 같다! String은 참조형이라 직접적인 연산이 불가능하다. 그래서….
String의 덧셈 연산시 내부적으로는 StringBuffer를 불러와 append()로 추가, toString()메서드를 통해 반환하여 str3을 초기화 시키는 방식이다.
결국 String str3 = str1 + str2; 는 보기에만 저런것이다. int c = a + b; 같은 연산이랑은 메커니즘 자체가 다른것이다.
그렇다면 StringBuffer에 대해서 조금 더 자세히 알아보자.
StringBuffer의 가변성
Buffer, 가변성을 보인다. Buffer의 뜻은 데이터를 A 에서 B로 옮기는 동안 일시적으로 값을 보관하는 메모리 역할이다.
그럼 StringBuffer는 String을 일시적으로 저장해주는 메모리, 정도로 해석할 수 있다. String과 다르게 StrinfBuffer는 메모리를 새로 할당하지 않는다! 위에서 String의 메모리주소를 조사한 것 처럼 StringBuffer도 똑같이 해보았다.
StringBuilder sb = new StringBuilder().append("abc");
System.out.println(sb.hashCode());
sb.append("cde");
System.out.println(sb.hashCode());
796533847
796533847
메모리 값이 변하지 않는다. 즉 StirngBuffer는 값이 변해도 주소는 그대로 라는 가변성의 특징을 가진다.
StringBuffer의 여러가지 메서드
우리가 흔히 쓰는 StringBuffer의 메서드는 append(), toString() 등이 있다.
StringBuffer sb = new StringBuffer();
로 선언할 수 있다. 주요 메서드들을 살펴보자
우선 String에서 사용하던 주요 메서드들을 사용할 수 있다.
sb.charAt() sb.indexOf() sb.toString() sb.length()
등등… 그리고 StringBuffer와 StringBuilder만 사용가능한 메서드는
sb.append() - 문자열 추가 sb.capacity() - 현재 가진 사이즈 반환 (length() 와는 다르다) sb.delete(index1, index2)- index1 ~. index2까지 제거 sb.insert(index, String) - index위치에 문자열 삽입
StringBuffer의 메서드체이닝
StringBuffer 가 갖고있는 메서드들은 대부분 자기 자신을 반환한다. 이것을 메서드 체이닝 이라고 한다.
StringBuffer sb2 = new StringBuffer().append("b").append(true).append(1).append('a').append(0.5);
이런식으로 사용할 수 있다. 이 이유는 append()의 반환타입이 StringBuffer이기 때문이다. 여러 줄로 적을 필요가 없어졌다!
문자열을 정의하기 위해서는
String str4 = new StringBuffer().append("hello").append(" ").append("world").toString();
이렇게 끝만 toString()으로 반환형을 String으로 해 주면 가능하다.
정리
이렇게 String과 StringBuffer를 비교 해 보았다.
String과 StringBuffer는 둘다 char형 배열 (char[])로 이루어져 있다. 하지만, 메모리의 크기가 불변, 가변에 따라서 나뉜다.
String은 주로 고정되고 정적인 문자열을 정의할 때 사용하면 좋다. StringBuffer는 문자열의 삽입과, 삭제가 잦은 경우에 사용하면 좋다.
String의 경우 생성에 필요한 메모리도 상대적으로 적고, 생성 시간도 오래 걸리지 않는다. StringBuffer의 경우 생성에 필요한 메모리도 상대적으로 많고, 생성 시간도 걸리는 편이다.
하지만 String의 경우 문자열의 삽입, 삭제가 필요할 때 StringBuffer를 불러와야하는 단점이 있다.
그래서..
상황에 맞는 타입을 사용 할 필요가 있다.
4. Math클래스
마지막으로 java.lang 패키지에 존재하는 Math 클래스이다. 아래 java공식 문서에서 Math클래스의 사용법과 여러 메서드들을 볼 수 있다. [https://docs.oracle.com/javase/8/docs/api/] Java Platform SE 8
Math Class는 java.lang패키지 내부에 존재하는 클래스이다.
Private라서 new로 객체를 생성할 수는 없다. 하지만 코드를 짤때 복잡한 연산을 획기적으로 줄여주는 많은 메서드들을 보유하고 있다.
Math클래스의 메서드
입력 형식은 double형에서 대부분 동작하며 int, float 역시 지원하는 메서드가 존재한다.
일반
Math.max() - 두 값중 큰 값을 반환한다.
Math.min() - 두 값중 작은값을 반환한다.
Math.abs() - 절댓값을 반환한다.
Math.PI() - 원주율을 출력한다.
제곱과 제곱근
Math.pow() - a의 b제곱을 반환한다.
Math.exp() - 무리수e의 n제곱을 반환한다.
Math.sqrt() - 제곱근을 반환한다.
Math.cbrt() - 세제곱근을 반환한다.
Math.hypot() - x^2^+y^2^의 제곱근을 반환한다.
올림, 내림, 반올림
Math.ceil() - 크거나 같은 정수값을 반환한다.(올림)
Math.floor() - 작거나 같은 정수값을 반환한다.(내림)
Math.round() - 반올림한다.
랜덤
Math.random() - 0.xxx ~ 0.9xxx중 무작위 값을 반환한다.