오토박싱 & 언방식(autoboxing & unboxing)
JDK 1.5 전에는 기본형과 참조형 간의 연산이 불가능했기 때문에, 래퍼 클래스로 기본형을 객체로 만들어서 연산해야 했다.
int i = 5;
Integer iObj = new Integer(7);
int sum = i + iObj; //에러, 기본형과 참조형 간의 덧셈 불가(JDK 1.5 이전)
그러나 이제는 기본형과 참조형 간의 덧셈이 가능하다. 자바 언어의 규칙이 바뀐 것은 아니고 , 컴파일러가 자동으로 변환하는 코드를 넣어주기 때문이다. 아래의 경우, 컴파일러가 Integer객체를 int타입의 값으로 변환해주는 intValue()를 추가해준다.
--컴파일 전의 코드
int i = 5;
Integer iObj = new Integer(7);
int sum = i + iObj;
============== 컴파일의 후의 코드 ===============
int i = 5;
Integer iObj = new Integer(7);
int sum = i + iObj.intValue();
이 외에도 내부적으로 객체 배열을 가지고 있는 Vector클래스나 ArrayList 클래스에 기본형 값을 저쟁해야 할 때나 형변환이 필요할 때도 컴파일러가 자동적으로 코드를 추가해준다. 기본형 값을 래퍼 클래스의 객체로 자동 변환해주는 것을 오토박싱(autoboxing)이라 하고, 반대로 변환하는 것은 언박싱(unboxing)이라고 한다.
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(10); //오토박싱, 10 - > new Integer(10)
int value = list.get(0); //언박싱, new Integer(10) -> 10
위의 코드에서 알 수 있듯이 ArrayList에 숫자를 저장하거나 꺼낼 때, 기본형 값을 래퍼 클래스의 객체로 변환하지 않아도 되므로 편리하다.
public class WrapperEx3 {
public static void main(String[] args) {
int i = 10;
//기본형을 참조형으로 형변환(형변환 생략 가능)
Integer intg = (Integer) i; //Integer intg =Integer.valueOf(i);
Object obj = (Object) i; //Object obj =(Object) Integer.valueOf(i);
Long lng = 100L; // Long Ing = new Long(100L);
int i2 = intg + 10; //참조형과 기본형간의 연산 가능
long l = intg + lng ; //참조형 간의 덧셈도 가능
Integer intg2 = new Integer(20);
int i3 = (int)intg2; //참조형을 기본형으로 형변환도 가능(형변환 생략가능)
Integer intg3 = intg2 * i3;
System.out.println("i ="+i);
System.out.println("intg ="+intg);
System.out.println("obj ="+obj);
System.out.println("lng ="+lng);
System.out.println("intg + 10= "+i2);
System.out.println("intg + lng ="+l);
System.out.println("intg2 ="+intg2);
System.out.println("i3 ="+i3);
System.out.println("intg2 + i3 ="+intg3);
}
}
===================
i =10
intg =10
obj =10
lng =100
intg + 10= 20
intg + lng =110
intg2 =20
i3 =20
intg2 + i3 =400
이 예제는 오토박싱(autoboxing)을 이용해서 기본형과 참조형간의 형변환과 연산을 수행하는 예를 보여준다. 지금까지 배워온것과 달리 기본형과 참조형 간의 형변환도 가능할 뿐만 아니라, 심지허는 참조형 간의 연산도 가능하다는 것에 다소 놀랐을 것이다.
그러나 사실 이 기능은 컴파일러가 제공하는 편리한 기능일 뿐 자바의 원칙이 바뀐것은 아니다 .생성자가 없는 클래스에 컴파일러가 기본 생성자를 자동적으로 추가해 주듯이 개발자가 간략하게 쓴 구문을 컴파일러가 원래의 구문으로 변경해 주는 것 뿐이다.
컴파일 전의 코드
Integer intg = (Integer)i;
Object obj = (Object) i;
Long lng = 100L;
------------------------
컴파일 후의 코드
Integer intg = Integer.valueOf(i);
Object obj = (Object) Integer.valueOf(i);
Long lng = new Long(100L);
오토박싱이 편리한 기능이긴 하지만, 처음 자바를 배우는 입장에서는 지금까지 배워온 기본 개념이 흔들릴 수 있다. 적어도 자바의 기본 개념들이 익숙해질 때까지는 이러한 새로운 기능들을 사용하지 않아도 괜찮다.
유용한 클래스
java.util패키지에는 많은 수의 클래스가 있지만 실제로 자주 쓰이는 것들은 그렇게 많지 않기 때문에 모든 클래스들을 간단히 설명하기 보다는 자주 사용되는 중요한 클래스들만을 골라서 다양한 용도로 활용하는 방법을 보여주고자 한다.
java.util.Object클래스
Object클래스의 보조 클래스로 Math클래스처럼 모든 메서드가 'static'이다. 객체의 비교나 널 체크(null check)에 유용하다.
isNull()은 해당 객체가 널인지 확인해서 null이면 true를 반환하고 아니면 false를 반환한다. nonNull()은 isNull()과 정반대의 일을 한다. 즉 !Object.isNull(obj)와 같다.
static boolean isNull (Obejct obj)
static boolean nonNull(Object obj)
그리고 requireNonNull()은 해당 객체가 널이 아니어야 하는 경우에 사용한다. 만일 객체가 널이면, NullPointerException을 발생시킨다. 두 번째 매개변수로 지정하는 문자열은 예외의 메시지가 된다.
static <T> T requireNonNull(T, obj)
static <T> T requireNonNull(T, obj, String message)
static <T> T requireNonNull(T, obj, Supplier<String> messageSupplier)
예전 같으면, 매개변수의 유효성 검사를 다음과 같이 해야하는데, 이제는 requireNonNull()의 호출만으로 간단히 끝낼 수 있다.
void setName(String name){
if(name == null)
throw new NullPointerException("name must not be null");
this.name = name;
}
—>
void setName(String name){
this.name = Objects.requireNonNull(name, "name must not be null.");
}
Object클래스에는 두 객체의 등가비교를 위한 equals()만 있고, 대소비교를 위한 compare()가 없는 것이 좀 아쉬웠다. 그래서인지 Objects에는 compare()가 추가되었다.
compare()는 두 비교대상이 같으면 0, 크면 양수, 작으면 음수를 반환한다.
static int compare(Object a, Object, b, Comparator c)
이 메서드는 a와 b 두 객체를 비교하는데, 두 객체를 비교하는데 사용할 비교 기준이 필요하다. 그 역할을 하는 것이 Comparator이다.
static boolean equals(Obejct a, Object b)
static boolean deepEquals(Object a, Object b)
Object클래스에 정의된 equals()가 왜 Objects클래스에도 있는지 궁금할 텐데, 이 메서드의 장점은 null검사를 하지 않아도 된다는 점에 있다.
if(a != null && a.equlas(b)) { // a가 null인지 반드시 확인해야 한다.
...
}
==========================
if(Obejct.equals(a,b) { //매개변수의 값이 null인지 확인할 필요가 없다.
...
}
equals()의 내부에서 a와 b의 널 검사를 하기 때문에 따로 널 검사를 위한 조건식을 넣지 않아도 되는 것이다. 이 메서드의 실제 코드는 다음과 같다.
public static boolean equlas(Object a, Object b){
return (a == b) || (a != null && a.equlas(b));
}
a와 b가 모두 널인 경우에는 참을 반환한다는 점을 빼고 특별한 것이 없다. equals()보다는 deepEquals()의 존재가 더 반가운데, 이 메서드는 객체를 재귀적으로 비교하기 때문에 다차원배열의 비교도 가능하다.
String [][] str2D = new String[][]{{"aaa","bbb"}, {"AAA","BBB"}};
String [][] str2D2 = new String[][]{{"aaa","bbb"}, {"AAA","BBB"}};
System.out.println(Objects.equals(str2D, str2D2)); //false
System.out.println(Obejcts.deepEquals(str2D, str2D2)); //true
위와 같이 두 2차원 문자열 배열을 비교할 때, 단순히 equals()를 써서는 비교할 수 없다.
equals()와 반복문을 함께 써야하는데, deepEquals()를 쓰면 간단히 끝난다.
static String toString(Object o)
static String toStirng(Object o, String nullDefault)
toString()도 equlas9)처럼, 내부적으로 널 거맛를 한다는 것 빼고는 특별한 것이 없다.
두번째 메서드는 o가 널일때 , 대신 사용할 값을 지정할 수 있어서 유용하다.
마지막으로 hashCode()인데, 이것 역시 내부적으로 널 검사를 한 후에 Object클래스의 hashCode()를 호출할 뿐이다. 단 널일 때는 0을 반환한다.
static int hashCode(Object o)
static int hash(Object... values)
보통은 클래스에 선언된 인스턴스의 변수들의 hashCode()를 조합해서 반환하도록, hashCode()를 오버라이딩하는데, 그 대신 매개변수의 타입이 가변인자인 두 번째 메서드를 사용하면 편리하다.
import java.util.*;
import static java.util.Objects.*;
public class ObjectTest {
public static void main(String[] args) {
String[][] str2D = new String[][] {{"aaa","bbb"}, {"AAA", "BBB"}};
String[][] str2D2 = new String[][] {{"aaa","bbb"}, {"AAA", "BBB"}};
System.out.print("str2D = {");
for(String[] tmp : str2D)
System.out.print(Arrays.toString(tmp));
System.out.println("}");
System.out.print("str2D2 = {");
for(String[] tmp : str2D2)
System.out.print(Arrays.toString(tmp));
System.out.println("}");
System.out.println("equals(str2D, str2D2)="+Objects.equals(str2D, str2D2));
System.out.println("deepEquals(str2D, str2D2)="+Objects.deepEquals(str2D, str2D2));
System.out.println("inNull(null) ="+isNull(null));
System.out.println("nonNull(null)="+nonNull(null));
System.out.println("hashCode(null)="+Objects.hashCode(null));
System.out.println("toString(null)="+Objects.toString(null));
System.out.println("toString(null, \"\")="+Objects.toString(null, ""));
Comparator c = String.CASE_INSENSITIVE_ORDER; //대소문자 구분 안하는 비교
System.out.println("compare(\"aa\",\"bb\")="+compare("aa","bb",c));
System.out.println("compare(\"bb\",\"aa\")="+compare("bb","aa",c));
System.out.println("compare(\"ab\",\"AB\")="+compare("ab","AB",c));
}
}
======================
str2D = {[aaa, bbb][AAA, BBB]}
str2D2 = {[aaa, bbb][AAA, BBB]}
equals(str2D, str2D2)=false
deepEquals(str2D, str2D2)=true
inNull(null) =true
nonNull(null)=false
hashCode(null)=0
toString(null)=null
toString(null, "")=
compare("aa","bb")=-1
compare("bb","aa")=1
compare("ab","AB")=0
static import문을 사용했음에도 불구하고 Object클래스의 메서드와 이름이 같은 것들은 충돌이 난다. 컴파일러가 구별을 못한다. 그럴 때는 클래스의 이름을 붙여줄 수 밖에 없다.
그리고 String클래스에 상수로 정의되어 있는 Comparator가 있어서 그걸 사용해서 compare()을 호출 했다.
Comparator c = String.CASE_INSENSITIVE_ORDER; //대소문자 구분 안하는 비교
System.out.println("compare(\"aa\",\"bb\")="+compare("aa","bb",c));
System.out.println("compare(\"bb\",\"aa\")="+compare("bb","aa",c));
System.out.println("compare(\"ab\",\"AB\")="+compare("ab","AB",c));
이 Comparator는 문자열을 대소문자 구분하지 않고 비교할 때 사용하기 위한 것이다. 그래서 아래와 같이 "ab"와 "AB"를 비교한 결과가 0, 즉 두 문자열이 같다는 결과가 나온다.
'Back-end' 카테고리의 다른 글
java.lang패키지와 유용한 클래스(8) (0) | 2021.07.01 |
---|---|
java.lang패키지와 유용한 클래스(7) (0) | 2021.07.01 |
java.lang패키지와 유용한 클래스(5) (0) | 2021.06.27 |
java.lang패키지와 유용한 클래스(4) (0) | 2021.06.26 |
java.lang패키지와 유용한 클래스(3) (0) | 2021.06.25 |