JAVA Basic) 기본 클래스
Programming/Java 기초

JAVA Basic) 기본 클래스

728x90

 

목차

     


     

    <java.lang 패키지>

    지금까지 String, Integer와 같은 클래스를 자료형으로 불러와 사용했다.
    이러한 클래스들은 'java.lang 패키지'에 속해있다. String 클래스의 전체 이름은 java.lang.String이고, Integer 클래스는 java.lang.Integer이다. 이처럼 java.lang 패키지에는 기본적으로 많이 사용하는 클래스들이 포함되어 있다.

    자바가 설치된 경로에 있는 src.zip(자바 소스 코드 압축 파일)의 압축을 풀면 src폴더가 나타나는데, 이 폴더 하위의
    [ java.base\java\lang ]에 들어가면 java.lang 패키지에서 제공하는 여러 소스 코드를 확인할 수 있다.

     

     

    자바 프로그래밍에서 외부 패키지에 선언한 클래스를 사용할 때는 기본적으로 import문으로 클래스가 어느 패키지에 속해있는지 선언해야한다.

    그런데 우리는 지금까지 String 클래스를 쓰면서 import 선언을 한 적이 없다.
    'java.lang 패키지'컴파일할 때 import java.lnag.*;문장이 자동으로 추가되어 java.lang 패키지 하위 클래스를 모두 사용할 수 있어서 프로그래머가 직접 쓸 필요가 없다.

    프로그래머가 import문을 쓰지 않아도 java.lang 패키지의 모든 하위 클래스를 참조가 가능하다.

    java.lang 패키지에 속해있는 클래스는 모두 프로그램에서 가장 많이 사용하는 '기본 클래스'다.

    그럼 자바 클래스의 최상위 클래스인 java.lang.Object부터 알아보자.

    <Objact 클래스>

    클래스의 최상위 클래스

    'Object클래스'는 모든 자바 클래스의 최상위 클래스이다. 다시 말하면 모든 클래스는 'Object 클래스로부터 상속'을 받는다. 
    컴파일 과정에서 Object 클래스를 상속받는다는 뜻의 extends Object가 클래스 선언부에 자동으로 작성된다.

    우리가 직접 만드는 클래스 뿐 아니라 기존 JDK에서 제공하는 클래스 모두 Object클래스에서 상속을 받는다. 

    String 클래스를 JavaDoc으로 확이하면 String클래스 역시 Object클래스를 상속받았음을 알 수 있다.
    모든 클래스가 Object클래스를 상속받았스므로 Object의 메서드를 사용할 수 있고, 재정의할 수 있고, 형 변환도 역시 가능하다.
    자바 프로그래밍을 하다 보면 클래스가 Object형으로 변환되는 경우도 있고, 다운 캐스팅 되는 경우도 있다.

    이클립스 편집창에 Object라 쓰고 F1키를 누르면 JavaDoc 내용이 보인다. 그러면 Object 클래스에 정의된 메서드를 확인할 수 있다.

    주로 사용되는 Object 메서드는 다음과 같다.

    Object 메서드 중 재정의 할 수 있는 메서드도 있고, 아닌 메서드도 있다.
    (final 선언된 메서드는 재정의 X)

    여기에서는 자주 재정의하여 사용하는 메서드를 설명할 것이다.

     

    toString()

    Object 클래스에서 기본으로 제공하는 'toString() 메서드'는 이름처럼 객체 정보를 문자열(String)으로 바꿔준다.

    Object 클래스를 상속받은 모든 클래스는 toString()을 재정의할 수 있다. String이나 Integer등 여러 JDK 클래스에는 toString() 메서드가 이미 재정의되어있다.

    Object 클래스의 toString() 메서드

    toString() 메서드는 인스턴스 정보를 문자열로 반환하는 메서드다. toStirng() 메서드의 원형은 생성된 인스턴스의 클래스 이름과 주소 값을 보여준다. (패키지.클래스@주소 값)

    다음 예제를 보자.
    책 번호와 제목을 담고있는 Book클래스의 인스턴스를 생성하여 그 참조 변수를 출력한다.

    package object;
    
    class Book {
    	int bookNumber;
    	String bookTitle;
    	
    	Book(int bookNumber, String bookTitle){
    		this.bookNumber = bookNumber;
    		this.bookTitle = bookTitle;
    	}// 책번호와 제목을 매개변수로 입력받는 생성자
    }
    
    public class ToStringEX {
    	public static void main(String[] args) {
    		Book book1 = new Book(100, "일기장");
    		
    		System.out.println(book1);	//인스턴스정보 출력
    		System.out.println(book1.toString());	//인스턴스 정보 출력
    	}
    }

    두 출력문의 결과가 같음을 볼 수 있다.

     

    처음 System.out.println()출력문에 참조 변수를 넣으면 인스턴스 정보가 출력되는데, 이때 Object의 toString() 메서드가 자동으로 호출된다.

    Object 클래스의 toString()의 원형은

    getClass().getName() + '@' + Integer.toHexString(hashCode())

    위 정의 내용을 보면 '클래스이름@해시코드값'임을 알 수 있다.

    String과 Integer 클래스의 toString() 메서드

    toStirng() 메서드가 호출된 경우라도 출력 결과가 '클래스이름@해시코드값'이 아닌 경우가 있다.
    다음 코드를 보자

    String str = new String("test");
    System.out.println(str); //test 출력
    Integer i1 = new Integer(100);
    System.out.println(i1);  //100 출력

    String과 Integer클래스로 인스턴스로 생성하여 출력문에 참조변수를 넣으면 '클래스이름@해시코드값'이 나오지 않는다.
    그 이유는 각 클래스에서 이미 재정의되었기 때문이다.

    Book클래스에서 toString()메서드 재정의하기

    이번에는 앞서 만든 Book클래스에서 toString()메서드를 직접 재정의해 보려고 한다.

    Book클래스의 참조변수를 사용해 '책 이름, 책 번호'를 출력해보자. [마우스 오른쪽 - Source - Override~...] 기능을 이용해 자동으로 메서드 재정의 코드를 넣어도 된다.
    다음처럼 toString()메서드를 재정의하여 코드를 작성해보자.

    package object;
    
    class Book {
    	int bookNumber;
    	String bookTitle;
    	
    	Book(int bookNumber, String bookTitle){
    		this.bookNumber = bookNumber;
    		this.bookTitle = bookTitle;
    	}
    
    	@Override
    	public String toString() {
    		return bookTitle + ", " + bookNumber;
    	}//toString()메서드 재정의
    }
    
    public class ToStringEX {
    	public static void main(String[] args) {
    		Book book1 = new Book(100, "일기장");
    		
    		System.out.println(book1);
    		System.out.println(book1.toString());
    	}
    }

    toString()메서드를 직접 재정의하면 객체의 참조 변수를 이용해 원하는 문자열을 표현할 수 있다.

    equals()

    'equals() 메서드'는 원래 기능은 두 인스턴스의 주소 값을 비교하여 boolean값(true/false)을 반환해 주는 것이다.
    주소 값이 같다면 당연히 같은 인스턴스이다. 그런데 서로 다른 주소 값을 가질 때도 같은 인스턴스라고 정의할 수 있는 경우가 있다.
    따라서 '물리적 동일성(인스턴스의 메모리 주소가 같음)'뿐 아니라 '논리적 동일성(논리적으로 두 인스턴스가 같음)'을 구현할 때도 equals() 메서드를 재정의하여 사용한다.

    Object 클래스의 equals() 메서드

    생성된 두 인스턴스가 '같다'는 것은 무엇을 의미하는 걸까?

    인스턴스를 가리키는 참조 변수가 두 개 있을 때,
    이 두 인스턴스가 '물리적으로 같다'는 것은, '두 인스턴스의 주소 값이 같은 경우'를 말한다. 
    다시 말해 두 변수가 같은 메모리 구조를 가리키고 있다는 것.
    예를 들어 학생 객체를 구현한 Student 클래스가 있다. 다음 코드처럼 Student 클래스를 생성하고, 생성된 인스턴스를 가리키는 참조 변수(studentLee)를 다른 변수(studentLee2)에 복사.

    Student studentLee = new Student(100, "이상원");
    Student studentLee2 = studentLee; //주소 복사

    그러면 두 변수는 다음 그림처럼 동일한 인스턴스를 가리킨다.
    이때 equals()메서드를 이용해 두 변수를 비교하면 true라는 결과가 나온다.

    같은 인스턴스를 가리키는 두 변수 (=주소 값이 같다)(=물리적으로 같다)


    다음 코드는 이름과 학번이 동일한 학생을 한명 더 생성하고, 다른 변수(studentSang)가 가리키도록 만들었다.

    Student studentLee = new Student(100, "이상원");
    Student studentLee2 = studentLee; //주소 복사
    Studnet studnetSang = new Student(100, "이상원");

    위 코드를 그림으로 보면

    studentSang변수는 두 인스턴스와 다르다.

    studentLee, studentLee2가 가리키는 인스턴스와 studentSang이 가리키는 인스턴스는 서로 다른 주소를 가지고 있지만, 저장된 학생 정보는 같다. 이런 경우 논리적으로는 studentLee, studentLee2와 studentSang을 같은 학생으로 처리하는 것은 맞을 것(논리적으로 같다)이다.

    이 상황을 코드로 만들어 보자.

    package object;
    
    class Student {
    	int studentID;
    	String studentName;
    	
    	public Student(int studentID, String studentName){
    		this.studentID = studentID;
    		this.studentName = studentName;
    	}
    	
    	@Override
    	public String toString() { //toString 재정의
    		return studentID + ", " + studentName;
    	}
    }
    	
    	public class EqualsTest {
    	public static void main(String[] args) {
    		Student studentLee = new Student(100, "이상원");
    		Student studentLee2 = studentLee; //주소 복사
    		Student studentSang = new Student(100, "이상원");
    		
    //-----------------------동일한 주소의 두 인스턴스 비교
    		if (studentLee == studentLee2)	//==기호로 비교
    			System.out.println("studentLee와 studentLee2의 주소는 같다");
    		else
    			System.out.println("studentLee와 studentLee2의 주소는 다르다");
    		
    		if (studentLee.equals(studentLee2)) //equals() 메서드로 비교
    			System.out.println("studentLee와 studentLee2는 동일하다");
    		else
    			System.out.println("studentLee와 studentLee2의 동일하지 않다");
    		System.out.println("==============");
    //------------------------동일인(논리적으로 같음)이지만 인스턴스 주소가 다른 경우
    		if (studentLee == studentSang)	//==기호로 비교
    			System.out.println("studentLee와 studentSang의 주소는 같다");
    		else
    			System.out.println("studentLee와 studentSang의 주소는 다르다");
    		
    		if (studentLee.equals(studentSang)) //equals() 메서드로 비교
    			System.out.println("studentLee와 studentSang는 동일하다");
    		else
    			System.out.println("studentLee와 studentSang의 동일하지 않다");
    	}
    }

    • Object의 equals()메서드의 원래 기능은 두 인스턴스의 주소를 비교하는 것. 따라서 같은 주소인 경우만 결과가 true가 된다.
    • studentLee 참조 변수와 studentLee2 참조 변수는 동일한 주소를 가리켜 true.
    • studentSang의 경우다른 주소를 가리키므로 false.

     

    그러나 논리적으로는 같은 사람이다. 따라서 인스턴스 주소 값이 달라도 동일한 객체임을 확인할 수 있어야 한다.
    Object의 equals()'메서드를 재정의'하여 논리적으로 같은 인스턴스인지 확인하도록 구현할 수 있다.

    String과 Integer클래스의 equals()메서드

    JDK에서 제공하는 'String클래스와 Integer클래스'에는 equals()메서드가 이미 재정의되어 있다.

    재정의된 equals()메서드를 사용하는 예제를 따라해보자.

    package object;
    
    public class StringEquals {
    	public static void main(String[] args) {
    		String str1 = new String("abc");
    		String str2 = new String("abc");
    		Integer i1 = new Integer(100);
    		Integer i2 = new Integer(100);
    		
    		System.out.println("======String.equals()=====");
    		System.out.println(str1 == str2); //주소값 비교
    		System.out.println(str1.equals(str2)); //두 인스턴스의 문자열이 같은지 비교
    		
    		System.out.println("======Integer.equals()=====");
    		System.out.println(i1 == i2); //주소값 비교
    		System.out.println(i1.equals(i2)); //두 인스턴스의 정수값이 같은지 비교
    	}
    }
    • 코드의 내용을 보면 str1str2는 서로 다른 인스턴스를 가리키기 때문에 주소값이 달라 str1==str2는 false다.
    • 하지만 String클래스의 equals()는 같은 문자열의 경우 true를 반환하기 때문에 str1.equals(str2)는 true다.
    • Integer의 equals()메서드도 마찬가지.

    Student클래스에서 equals() 메서드 직접 재정의하기

    package object;
    
    class Student {
    	int studentID;
    	String studentName;
        
    	...
    
    	@Override
    	public boolean equals(Object obj) {
    		if(obj instanceof Student) {
    			Student std = (Student)obj;
    			if(this.studentID == std.studentID)
    				return true; //재정의한 equals()는 학생의 학번이 같으면 true를 반환
    			else
    				return false;
    		}
    		return false;
    	}
    }
    	
    	public class EqualsTest {
    	public static void main(String[] args) {
        ...

    studentLee.equals(studentSang)의 결과가 동일로 바뀜

    • equals()메서드를 재정의했다. equals()의 매개변수는 Object형이다. 비교할 객채가 Object형 매개변수로 전달되면 instanceof를 사용해 매개변수의 원래 자료형이 Student인지 확인. obj변수가 Student형으로 다운 캐스팅 되고.
    • this의 학번과 매개변수로 전달된 객체의 학번이 같으면 true반환.

    출력 결과를 보면 재정의한 equals()는 true를 반환한다.

     

    heshCode()

    '해시(hash)'정보를 저장하거나 검색할 때 사용하는 자료 구조이다.
    정보를 어디에 저장할 것인지, 어디서 가져올 것인지 해시 함수를 사용하여 구현한다.

    해시 함수는 객체의 특정 정보(키 값)를 매개변수 값으로 넣으면 그 객체가 저장되어야 할 위치나 저장된 해시 테이블 주소(위치)를 반환한다.
    따라서 객체 정보를 알면 해당 객체의 위치를 빠르게 검색할 수 있다.

    해시 함수는 개발하는 프로그램의 특성에 따라 다르게 구현된다.

    자바에서는 인스턴스를 힙 메모리에 생성하여 관리할 때 해시 알고리즘을 사용한다.

    hashCode = hash(key);	//객체의 해시 코드 값(메모리 위치 값)을 반환

    Object 클래스의 toString()메서드 원형을 다시 살펴보면
    getClass().getName()+'@'+Integer.toHexString(hashCode()) 이다.

    Object 클래스의  toString()메서드 원형

    즉 우리가 참조 변수를 출력할 때 본 16진수 숫자 값이 '해시 코드 값'이고, 이 값은 자바 가상 머신(JVM)이 힙 메모리에 저장된 '인스턴스 주소 값'인 것이다.

    즉 자바에서 두 인스턴스가 같다면 hashCode()메서드에서 반환하는 해시 코드 값이 같아야 한다. 따라서 논리적으로 같은 두 객체도 같은 해시 코드 값을 반환하도록 hashCode()메서드를 재정의해야 한다.

    다시 말해, equals() 메서드를 재정의했다면 hashCode()메서드도 재정의해야 한다.

    String과 Integer클래스의 hashCode() 메서드

    String 클래스와 Integer 클래스의 equals() 메서드는 재정의되어 있다.
    그렇다면 hashCode() 메서드도 재정의 되어있을 것이다.

    package object;
    
    public class HashCodeTest {
    	public static void main(String[] args) {
    		String str1 = new String("abc");
    		String str2 = new String("abc");
    		
    		Integer i1 = new Integer(100);
    		Integer i2 = new Integer(100);
    		
    		System.out.println(str1.hashCode());
    		System.out.println(str2.hashCode());
    		//abc문자열의 해시 코드 값 출력
    		
    		System.out.println(i1.hashCode());
    		System.out.println(i2.hashCode());
    		//Integer(100)의 해시 코드 값 출력
    	}
    }
    • String 클래스같은 문자열을 가진 경우, 즉 equals() 메서드 결과값이 true인 경우 hashCode() 메서드는 동일한 해시코드 값을 반환한다.
    • Integer 클래스hashCode()메서드는 정수 값을 반환하도록 재정의되어있다.

    Student 클래스에서 hashCode() 메서드 재정의

    논리적으로 동일한 두 학생은 같은 해시 코드 값을 반환하도록 hashCode()메서드를 재정의하자.

    Student 클래스에서 hashCode()를 재정의할 때 어떤 값을 반환하도록 하는게 가장 합리적일까?
    논리적으로 같은 학생인지 비교하는 equals()를 재정의할 때 학번이 같으면 true를 반환하도록 했다.
    일반적으로 hashCode()메서드를 재정의할 때equals() 메서드에서 논리적으로 같다는 것을 구현할 때 사용한 멤버 변수를 활용하는 것이 가장 좋다.
    따라서 Student 클래스에서는 hashCode() 메서드가 학번을 반환하는 것이 가장 합리적.

    package object;
    
    class Student {
    	int studentID;
    	String studentName;
    	
    	...
    
    	@Override
    	public int hashCode() {
    		return studentID;
    	}//해시 코드 값으로 학번을 반환하도록 메서드 재정의
    }
    	public class EqualsTest {
    	public static void main(String[] args) {
    
    		...
            
    		System.out.println("==============");
    		System.out.println("studentLee의 hashCode : " + studentLee.hashCode());
    		System.out.println("studentLee2의 hashCode : " + studentLee2.hashCode());
    		System.out.println("studentSang의 hashCode : " + studentSang.hashCode());
    		
    		System.out.println("studentLee의 실제 주소값 : " + System.identityHashCode(studentLee));
    		System.out.println("studentLee의 실제 주소값 : " + System.identityHashCode(studentLee2));
    		System.out.println("studentLee의 실제 주소값 : " + System.identityHashCode(studentSang));
    	}
    }

    • studentLee와 studentSang은 학번이 같기 때문에 논리적으로 같은지 확인하는 equals() 메서드 출력 값이 true이다.
    • 또한 같은 해시 코드 값을 반환한다.
    • hashCode() 메서드를 재정의했을 때 실제 인스턴스의 주소 값
      System.indentityHashCode() 메서드를 사용하면 알 수 있다.

    즉 studentLee와 studentSang은 '논리적으로는 같지만, 실제로는 다른 인스턴스'인 것이다.

     

    clone()

    객체 원본을 유지해 놓고 복사본을 사용한다거나, 기본 틀(prototype)의 복사본을 사용해 동일한 인스턴스를 만들어 복잡한 생성 과정을 간단히 하려는 경우에 clone() 메서드를 사용할 수 있다.
    clone()메서드는 Object에 아래와 같이 선언되어 있으며, 객체를 복제해 또 다른 객체를 반환해주는 메서드.

    protected Object cloen();

    예제를 통해 자세히 알아보자.
    하나의 원점과 반지름을 멤버 변수로 가지는 Circle 클래스의 인스턴스를 생성, 이를 clone()메서드를 사용해 복제하는 프로그램이다. 원점은 Point클래스를 사용.

    package object;
    
    class Point{
    	int x;
    	int y;
    	
    	Point(int x, int y){
    		this.x = x;
    		this.y = y;
    	}
    
    	public String toString() {
    		return "x = " + x + ", y = " + y;
    	}
    }
    
    class Circle implements Cloneable {
    	//객체를 복제해도 된다는 의미의 Cloneable 인터페이스를 함께 선언
    	Point point;
    	int radius;
    	
    	Circle(int x, int y, int radius){
    		this.radius = radius;
    		point = new Point(x, y);
    	}
    	
    	public String toString() {
    		return "원점은 " + point + "이고, " + "반지름은 " + radius + "입니다.";
    	}
    
    	@Override
    	protected Object clone() throws CloneNotSupportedException {
    		//clone()메서드를 사용할 때 발생할 수 있는 오류를 예외 처리함
    		return super.clone();
    	}
    }
    
    public class ObjectCloneTest {
    	public static void main(String[] args) throws CloneNotSupportedException {
    		Circle circle = new Circle(10, 20, 30);
    		Circle copyCircle = (Circle)circle.clone();
    		//clone()메서드를 사용해 circle인스턴스를 copyCircle에 복제.
    		
    		System.out.println(circle);
    		System.out.println(copyCircle);
    		System.out.println(System.identityHashCode(circle));
    		System.out.println(System.identityHashCode(copyCircle));
    	}
    }

    • 'clone() 메서드'를 사용하려면 객체를 복제해도 된다는 의미로 클래스에 Cloneable 인터페이스를 구현해야 한다.
      만약 Cloneable 인터페이스를 명시하지 않으면 clone()메서드를 호출할 때 CloneNotSupportedException이 발생한다.
    • 이 예제에서는 Object의 clone()메서드를 그대로 사용하는데, 'Object의 clone()'클래스의 인스턴스를 새로 복제하여 생성한다. 멤버 변수가 동일한 인스턴스가 다른 메모리에 새로 생성되는 것.
    • 출력 결과를 보면 인스턴스의 멤버 변수 값은 같고 주소 값은 다른 copyCircle이 생성되었음을 알 수 있다.

    ※예외 처리란 프로그램이 실행 중에 멈추지 않도록 오류가 생길 수 있는 부분에 특정 코드를 구현하는 것.

    ※Cloneable 인터페이스를 선언해도 별도로 구현해야 하는 메서드는 없다. 이렇게 구현할 메서드가 없는 인터페이스를 마커 인터페이스(marker interface)라고 한다.

     

    <String 클래스>

    String 선언 방법

    자바는 문자열을 사용할 수 있도록 'String 클래스를 제공'한다. 문자열은 프로그램을 구현할 때 많이 활용하게 되는데, String을 사용할 때 문자열을 생성자의 매개변수로 하여 생성하는 방식과, 이미 생성된 문자열 상수를 가리키는 방식이 있다.

    String str1 = new String("abc");
    // 생성자의 매개변수로 문자열 생성
    String str2 = "test";
    // 문자열 상수를 가리키는 방식

    언뜻 비슷해 보이지만, 내부적으로는 두 가지 방식은 큰 차이가 있다.
    new예약어를 사용하여 객체를 생성하는 경우 : 
    "abc"문자열을 위한 메모리가 할당되고 새로운 객체가 생성된다.

    str2 = "test"와 같이 생성자를 이용하지 않고 바로 문자열 상수를 가리키는 경우 :
    str2가 기존에 만들어져 있던 "test"라는 문자열 상수의 메모리 주소를 가리키게 된다.
    따라서 String str3 = "test"라고 코드를 추가하면 str2와 str3는 주소 값이 같게 되는 것.

    test나 10, 20 등과 같이 프로그램에서 사용하는 상수 값을 저장하는 공간'상수 풀(constant pool)'이라고 한다.

     

     

     

    package string;
    
    public class StringTest1 {
    	public static void main(String[] args) {
    		String str1 = new String("abc");
    		String str2 = new String("abc");
    		
    		System.out.println(str1 == str2);
    		//인스턴스가 새로 생성했으므로 둘의 주소 값이 다름.
    		System.out.println(str1.equals(str2));
    		//문자열 값이 같으므로 true
    		
    		
    		String str3 = "abc";
    		String str4 = "abc";
    	
    		System.out.println(str3 == str4);
    		//문자열 abc는 상수 풀에 저장되어 있으므로 둘은 가리키는 주소 값이 같음.
    		System.out.println(str3.equals(str4));
    		//문자열 값이 같으므로 true 반환
    	}
    }

     

    final char[] 변수

    다른 프로그래밍 언어는 문자열을 구현할 때 일반적으로 char[]배열을 사용한다. 자바는 String 클래스를 제공해 char[]배열을 직접 구현하지 않고도 편리하게 문자열을 사용할 수 있다.
    String.java 파일을 보면 다음처럼 선언되어 있다.
    String 클래스의 구현 내용에 'private final char value[]'라고 선언된 char형 배열이 있다.

    프로그램에서 String s = new String("abc")라고 쓰면 abc는 String클래스의 value변수에 저장된다.
    그런데 이 변수는 final로 선언되어 있다. final은 문자열을 변경할 수 없다는 뜻.
    따라서 한 번 생성된 문자열은 변경되지 않는다.

    이런 문자열의 특징을 '문자열은 불변(immutable)한다'라고 한다.

    따라서 프로그램에서 두 개의 문자열을 연결하는 경우에는 둘 중 하나의 문자열이 변경되는 것이 아닌(문자열은 불변이기 때문), 두 문자열이 연결된 새로운 문자열이 생겨난다. 

    String으로 두 개의 문자열을 생성하고 concat()메서드로 두 문자열을 연결, 그리고 주소 값을 비교해보자.

    package object;
    
    public class StringTest2 {
    	public static void main(String[] args) {
    		String javaStr = new String("java");
    		String androidStr = new String("android");
    		System.out.println(javaStr);
    		System.out.println("처음 문자열 주소 값 : " + System.identityHashCode(javaStr));
    		
    		javaStr = javaStr.concat(androidStr);
    		//문자열 javaStr과 문자열 androidStr을 연결해 javaStr에 대입
    		
    		System.out.println(javaStr);
    		System.out.println("연결된 문자열 주소 값 : " + System.identityHashCode(javaStr));
    	}
    }

    • 두 개의 문자열 "java", "android"를 생성. 그리고 두 문자열을 연결하는 concat()메서드를 호출.
    • javaStr변수 출력 결과를 보면 "javaandroid"로 이상 없이 출력되었다.
    • javaStr변수 값 자체가 변하는 것이 아닌 새로운 문자열이 생성된 것

     

    즉 "javaandroid" 문자열이 새로 생성되고 javaStr은 그 문자열을 가리키게 된다.
    실제로 처음의 javaStr 주소 값과, concat()메서드 실행 후 javaStr의 주소 값이 달라진 것을 알 수 있다.

    StringBuffer와 StringBuilder 클래스

    프로그램을 만들다 보면 문자열을 변경하거나 연결해야 할 때가 많다.
    그런데 String 클래스는 한번 생성되면 그 내부의 문자열이 변경되지 않기 때문에 String클래스를 사용하여 문자열을 계속 연결하거나 변경하는 프로그램을 작성하면 메모리 낭비가 심해지게 된다.

    이 문제를 해결하는 것이 바로 'StringBuffer''StringBuilder 클래스'다.

    StringBuffer와 StringBuilder는 내부에 변경 가능한(final X) char[]를 변수로 가지고 있다. 이 두 클래스를 사용하여 문자열을 연결하면 기존에 사용하던 char[]배열이 확장되므로 추가 메모리를 사용하지 않는다.
    따라서 문자열을 연결하거나 변경할 경우 두 클래스 중 하나를 사용하면 된다.

    두 클래스의 차이는 여러 작업(스레드)이 동시에 문자열을 변경하려 할 때 문자열이 안전하게 변경되도록 보장해주는지의 차이다.

    StringBuffer 클래스는 문자열이 안전하게 변경되도록 보장.
    StringBuilder 클래스는 보장되지 않는다.
    프로그램에서 따로 스레드를 생성하는 멀티스레드 프로그램이 아니라면 StringBuilder가 실행속도가 좀 더 빠르다.

    package object;
    
    public class StringBuilderTest {
    	public static void main(String[] args) {
    		String javaStr = new String("Java");
    		System.out.println("javaStr의 문자열 주소 : " + System.identityHashCode(javaStr));
    	
    		StringBuilder buffer = new StringBuilder(javaStr); //String으로부터 StringBuilder생성
    		System.out.println("연산 전 buffer의 메모리 주소 : " + System.identityHashCode(buffer));
    		
    		buffer.append(" and");
    		buffer.append(" android");
    		buffer.append(" programming is fun!");
    		//문자열 추가
    		System.out.println("연산 후 buffer의 메로리 주소 : " + System.identityHashCode(buffer));
    		
    		javaStr = buffer.toString(); //String클래스로 반환
    		System.out.println(javaStr);
    		System.out.println("연결된 javaStr 문자열 주소 : " + System.identityHashCode(javaStr));
    	}
    }

    • StringBuilder 클래스를 생성 후 여기에 문자열을 append한다.
    • 문자열 변경을 완료하면 buffer에 toString()메서드를 호출하면 다시 문자열로 반환이 가능하다.
    • append를 할 때마다 하나의 메모리에 문자열이 계속 연결됨을 알 수 있다.

     

    <Wrapper 클래스>

    기본 자료형을 위한 클래스

    지금까지 정수를 사용할 때 기본형인 int를 사용했다. 그런데 정수를 객체형으로 사용해야 하는 경우가 있다.
    예를 들어, 매개변수가 객체거나 반환 값이 객채형인 경우다.

    public void setValue(Integer i){...}
    //객체를 매개변수로 받는 경우
    
    public Integer returnValue(){...}
    //반환 값이 객체형인 경우

    이를 위해 자바에서는 기본 자료형처럼 사용할 수 있는 클래스를 제공한다.
    이러한 클래스를 기본 자료형을 감쌌다는 의미'Wrapper 클래스'라고 한다.

    Wrapper 클래스의 종류는 다음과 같다.

    Wrapper 클래스

    그럼 가장 대표적인 Integer클래스를 통해 Wrapper 클래스의 사용법을 알아보자.
    다른 클래스도 사용 방법이 크게 다르지 않으므로 Integer클래스를 익혀두면 충분히 응용 가능할 것이다.

     

    Integer 클래스

    'Integer 클래스'의 JavaDoc을 살펴보면 'int 자료형을 감싼 클래스'라고 설명되어 있다. 

    'Integer 클래스의 생성자'는 다음과 같이 특정 정수를 매개변수로 받는 경우문자열을 받는 경우 두 가지가 있다.

    Integer(int value){...} 
    //특정 정수를 매개변수로 받는 경우
    Integer(String s){...}
    //특정 문자열을 매개변수로 받는 경우

    그러면 자바 소스 파일인 Integer.java를 살펴보자

    Integer.java

    Integer 클래스는 int자료형의 특성이 그대로 구현되어 있다. 사용 가능한 최댓값과 최솟값이 static변수로 정의되어 있다.
    대부분의 Wrapper 클래스가 Integer클래스 정의와 크게 다르지 않다. 또한 Integer 클래스는 멤버 변수로 기본 자료형 int를 가지고 있고, int 값을 객체로 활용할 수 있는 여러 메서드를 제공한다.

    int value는 final 변수이며 한번 생성되면 변경될 수 없다.

     

     

    Integer 클래스의 메서드

    Integer 클래스의 여러 메서드 중 자주 사용하는 메서드 몇 가지를 살펴보면서 Wrapper 클래스 메서드 사용법을 알아보자.


    Integer 클래스 내부의 int 자료형 값을 가져오기 위해서 'intValue() 메서드'를 사용한다.

    Integer iValue = new Integer(100);
    int myValue = iValue.intValue();
    //int값 가져오기. myValue값을 출력하면 100이 출력됨.

     

    valueOf() 정적 메서드를 활용하면 문자열이 어떤 숫자를 나타낼 때, 이를테면 학번이나 개수 등이 문자열로 전달될 경우에 문자열에서 int 값을 바로 가져와 반환할 수도 있다.

    int num = Integer.parseInt("100");

    다른 Wrapper 클래스의 사용법도 크게 다르지 않다.

     

    오토박싱 / 언박싱

    어떤 정수 값을 사용할 때 int로 선언하는 경우와 Integer로 선언하는 경우는 전혀 다르다.
    int는 기본 자료형 4바이트지만, Integer의 경우 클래스이기 때문에 인스턴스로 생성하려면 생성자를 호출하고 정수 값을 인수로 넣어야 한다.

    이처럼 기본 자료형과 Wrapper 클래스는 같은 값을 나타내지만, 그 쓰임과 특성이 전혀 다르다.

    그래서 자바5 이전에는 기본 자료형과 Wrapper 클래스형을 함께 연산하기 위해 둘 중 하나의 형태로 일치시켜야 했다.
    예를 들어 Integer와 int형 변수 두 값을 더한다면 Integer에서 intValue()메서드를 사용해 정수 값을 꺼내거나 int형 변수를 Integer로 만들어 연산해야 했다.
    하지만 자바5 부터 다음과 같이 변환 없이 사용할 수 있다.

    Integer num1 = new Integer(100);
    int num2 = 200;
    
    int sum = num + num2;
    //num.intValue()로 변환(num을 int형으로 변환(언박싱))
    integer num3 = num2;
    //Integer.valueOf(num2)로 변환(num2를 Integer형으로 변환(오토박싱))

    기본형을 객체형으로 바꾸는 것을 '오토박싱(autoboxing)', 객체형을 기본형으로 꺼내는 것을 '언박싱(unboxing)'이라고 한다.
    이는 자바의 연산방식이 바뀐것이 아니라 컴파일러가 변경하는 것이다.
    따라서 객체의 형 변환에 신경쓰지 않고 편리하게 프로그래밍이 가능해졌다.

    지금까지 Integer 클래스를 살펴보면서 Wrappe 클래스의 역할과 특징에 대해 알아봤다.
    다른 Wrapper 클래스의 쓰임 또한 크게 다르지 않으니 JavaDoc을 참고하며 활용해보자.


    [Do it! 자바 프로그래밍 입문] 도서로 공부하며 정리한 글입니다.

    300x250