자바-17(java.lang 패키지)

dragonappear·2021년 5월 12일
0

Java

목록 보기
17/22

학습할내용

java.lang 패키지

Object 클래스
Class 클래스
래퍼(wrapper) 클래스

java.util 패키지

java.util.Objects 클래스
java.util.Scanner 클래스
java.util.StringTokenizer 클래스
java.math.BigInteger 클래스
java.math.BigDecimal 클래스


Java.lang 패키지:

  • java.lang 패키지는 자바 프로그래밍에 가장 기본이 되는 클래스들을 포함하고 있다.
  • 그렇기 때문에 java.lang 패키지의 클래스들은 import문 없이도 사용할 수 있게 되어있다.
  • String 클래스나 System 클래스들을 import문 없이 사용할 수 있었던 이유가 바로 java.lang 패키지에 속한 클래스들이기 때문이었던 것이이다.

Object 클래스:


  • Object 클래스는 모든 클래스의 최고 조상으로 모든 클래스는 Object 클래스를 상속받는다.
  • Object 클래스의 멤버들은 모든 클래스에서 바로 사용가능하고 일부 메서드는 오버라이딩해서 사용할수있다.
  • Object 클래스는 멤버변수는 없고 오직 11개의 메서드만 가지고 있다.

equals(Object obj)

[참고링크]https://atoz-develop.tistory.com/entry/%EC%9E%90%EB%B0%94-Object-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%A0%95%EB%A6%AC-toString-equals-hashCode-clone

  • 기본동작: 연산 결과 반환
  • override 목적: 물리적으로 다른 메모리에 위치하는 객체여도 논리적으로 동일함을 구현하기 위해

  • Object 클래스로부터 상속받은 equals 메서드는 두 개의 참조변수가 같은 객체를 참조하는지(두 참조변수에 저장된 주소값이 같은지) 판단 후 그 결과를 boolean 타입으로 반환한다.
  • equals 메서드로 Value 인스턴스가 가지고 있는 value 값을 비교하려면, Value 클래스에서 equals 메서드를 오버라이딩하여 주소가 아닌 객체에 저장된 내용을 비교하도록 오버라이딩해야한다.
  • 즉, String 클래스는 Object 클래스의 equals 메서드를 그대로 사용하는 것이 아니라 오버라이딩을 통해서 String 인스턴스가 갖는 문자열 값을 비
  • equals()를 사용하여 두 객체의 동일함을 논리적으로 override 할수있다.
    교하도록 되어있다.
  • 물리적 동일함은 객체가 메모리에서 같은 주소를 갖는것을, 논리적 동일함은 물리적으로 다른 위치에 있지만 같은 id의 회원 객체 혹은 같은 id의 주문 객체와 같이 도메인을 구분할 수있는 고유한 값 등에 의해 동일한 것을 의미한다.
package me.whiteship.livestudy.javalangpackage;

public class User {
    int id;
    String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public static void main(String[] args) {
        User user1 = new User(1001,"홍길동");
        User user2 = new User(1001,"홍길동");
        System.out.println(user1.equals(user2)); 
    }
}

output

false
package me.whiteship.livestudy.javalangpackage;

public class User {
    int id;
    String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if( obj instanceof User){
            return this.getId() == ((User) obj).getId();
        }else{
            return false;
        }
    }

    public static void main(String[] args) {
        User user1 = new User(1001,"홍길동");
        User user2 = new User(1001,"홍길동");
        System.out.println(user1.equals(user2)); 
    }
}

output

true
  • equals()의 반환값이 true로 두 객체가 같다는 것은 같은 해시코드 값을 갖는다는 것을 의미한다.

hashCode()

 public native int hashCode();
  • 기본동작: JVM이 부여한 코드값. 인스턴스가 저장된 가상머신의 주소를 10진수로 반환

  • 목적: 두 개의 서로 다른 메모리에 위치한 객체가 동일성을 갖기 위해

  • 해시코드란 JVM이 인스턴스를 생성할 때 메모리 주소를 변환해서 부여하는 코드이다.

  • 해시함수는 찾는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드(hash code)를 반환한다.

  • 일반적으로 해시코드가 같은 두 객체가 존재하는 것이 가능하지만, Object 클래스에 정의된 hashCode 메서드는 객체의 주소값을 이용해서 해시코드를 만들어 반환하기 때문에 서로 다른 두 객체는 같은 해시코드를 가질 수 없다.

  • 실제 메모리 주소값과는 별개의 값이며 실제 메모리 주소는 System 클래스의 identityHashCode()로 확인할 수 있다.

  • 자바에서의 동일성은 equals()의 반환값이 true, hashCode() 반환값이 동일함을 의미한다.

  • 그래서 일반적으로 equals() 와 hashCode()는 함께 override한다.

  • String 클래스는 문자열의 내용이 같으면 같은 해시코드를 반환하도록 hashCode 메서드가 오버라이딩 되어있기 때문에 문자열의 내용이 같은 str1과 str2에 대해 hashCode()를 호출하면 항상 동일한 해시코드 값을 얻는다.

package me.whiteship.livestudy.javalangpackage;

public class User {
    int id;
    String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if( obj instanceof User){
            return this.getId() == ((User) obj).getId();
        }else{
            return false;
        }
    }

    public static void main(String[] args) {
        User user1 = new User(1001,"홍길동");
        User user2 = new User(1001,"홍길동");

        System.out.println(user1.equals(user2));
        System.out.println("user1.hashCode() = "+ user1.hashCode());
        System.out.println("user2.hashCode() = "+ user2.hashCode());


    }
}

output

true
user1.hashCode() = 1554874502
user2.hashCode() = 1846274136
  • 지금은 User 객체 user1 과 user2가 서로 다른 해시코드를 반환한다.
  • 자바에서의 동일성을 구현하기 위해서는 hashCode()도 override 해야하며, equals()를 오버라이딩할때 사용한 멤버를 활용하면 된다.
package me.whiteship.livestudy.javalangpackage;

public class User {
    int id;
    String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if( obj instanceof User){
            return this.getId() == ((User) obj).getId();
        }else{
            return false;
        }
    }

    @Override
    public int hashCode() {
        return getId();
    }

    public static void main(String[] args) {
        User user1 = new User(1001,"홍길동");
        User user2 = new User(1001,"홍길동");

        System.out.println(user1.equals(user2));
        System.out.println("user1.hashCode() = "+ user1.hashCode());
        System.out.println("user2.hashCode() = "+ user2.hashCode());
        System.out.println("System.identityHashCode(user1) = " + System.identityHashCode(user1));
        System.out.println("System.identityHashCode(user2) = " + System.identityHashCode(user2));

    }
}

output

true
user1.hashCode() = 1001
user2.hashCode() = 1001
System.identityHashCode(user1) = 1554874502
System.identityHashCode(user2) = 1846274136
  • 자바의 Integer 클래스도 Object 클래스의 hashCode()를 override하고 있다.
        Integer integer1 = 100;
        Integer integer2 = 100;

        System.out.println("integer1.hashCode() = " + integer1.hashCode());
        System.out.println("integer2.hashCode() = " + integer2.hashCode());

output

integer1.hashCode() = 100
integer2.hashCode() = 100

toString()

  • Object 클래스에 정의된 toString()
public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
  • 목적: 객체의 hashCode() 출력
  • override 목적: 객체의 정보를 문자열 형태로 표현하고자 할때 사용
  • 클래스를 작성할 때 toString()을 오버라이딩 하지 않고, toString()을 호출하면 클래스이름+16진수의 해시코드를 반환한다.
  • String 클래스의 toStirng()은 String 인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩 되어있다.
  • Data 클래스의 경우. Data 인스턴스가 갖고 있는 날짜와 시간을 문자열로 반환하도록 오버라이딩 되어있다.
package me.whiteship.livestudy.javalangpackage;

public class Book {
    private String title;
    private String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public static void main(String[] args) {
        Book book = new Book("하하하하", "최고");
        System.out.println(book.toString());
    }
}

output

me.whiteship.livestudy.javalangpackage.Book@5cad8086
  • 이렇게 toString()을 override 하지않고 인스턴스를 출력하면 FQCN@해시코드 가 출력된다.
        String s = "하이하이";
        System.out.println(s.toString());

output

하이하이
  • String 객체를 출력하면 String 객체가 저장하고 있는 문자열이 출력된다.
  • 이 이유는 JDK의 String 클래슨느 toString()을 오버라이딩 하고 있기 때문이다.
  • System.out.println(s) 는 System.out.println(s.toString()) 과 같다.
  • 즉, toString()은 어떤 객체의 정보를 문자열형태로 표현해야 할 때 호출하는 메서드이다.

clone()

[참고링크] https://javacan.tistory.com/entry/31

protected native Object clone() throws CloneNotSupportedException;
  • 자신을 복제하여 새로운 인스턴스를 생성한다.
  • 복사된 객체와 원래 객체는 다른 객체이다. x.clone()!=x
  • 복사된 객체와 원본 객체의 클래스는 동일하다 x.clone().getClass()==x.getClass()
  • Object 클래스에 정의된 clone()은 단순히 인스턴스 변수의 값만 복사하기 때문에 참조타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다.
  • clone()은 반드시 예외처리를 해줘야한다.
  • 클로닝을 통한 객체의 복사는 새로운 객체를 생성하고 내부의 데이터 구조들을 복사하지만 생성자 메소드는 실행하지 않는다.
  • Object.clone() 메서드는 다음과 같은 작업을 수행한다.
  • 제일 먼저 Cloneable 인터페이스를 구현(implement, 실제 의미는 상속)했는지 확인하고, 구현하지 않았다면 CloneNotSupportedException 예외 객체를 던진다. Cloneable 인터페이스가 구현되어있으면 원본과 같은 객체를 새로 생성하고, 모든 필드 멤버들을 원본의 필드와 완전히 같은 값으로 초기화한다.
  • 이 때 필드 멤버의 내용은 새로 생성되지 않고 단지 대입에 의해 복사된다.
  • 즉 원본 객체를 deep cloning 하지 않고 shallow cloning 한다.
  • 참고로 모든 배열은 기본적으로 Cloneable 인터페이스르 구현한 것으로 가정된다.
  • Object 클래스는 그자체로 Cloneable 인터페이스를 구현하지 않았으므로, Object에서 직접 clone() 메서드를 호출하는 것은 CloneNotSupportedException 예외를 발생시킨다.
  • CloneNotSupportedException 런타임예외로 컴파일시에는 발견되지 않는다.
  • Object.clone() 메서드는 Object 클래스에서 구현되어있으므로 상속받은 클래스가 손쉽게 Clonealbe 인터페이스를 구현함으로써 clone() 메서드를 호출할수있게 되며, 대부분의 경우 clone() 메서드를 오버라이딩하여 사용하게 된다.
  • 이경우 오버라이딩한 메서드는 Object.clone() 메서드의 기능을 호출하기 위하여 super.clone() 메서드를 호출하면 된다.
  • clone() 메서드가 발생할 수 있는 예외는 두종류이다.
  • 하나는 CloneNotSupportedException이며 Clonealbe 인터페이스를 구현하지 않았거나 상속된 클래스가 클로닝될 수 없음을 나타내기 위하여 일부러 발생시키는 경우이다.
  • 다른 하나는 OutOfMemoryError 이며 객체를 생성할 수 있는 메모리가 부족할 때 발생하는 에러이다.

예제코드#1

배열을 포함하는 객체의 클로닝

package me.whiteship.livestudy.javalangpackage;

public class MyNumbers implements Cloneable{
    private int[] num = null;

    public MyNumbers() {
        num  = new int[10];
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) {
        
    }
}
  • 배열을 포함하는 클래스의 클로닝은 배열의 복사를 따로 제어해주어야 한다.

  • Object.clone() 메서드는 배열의 레퍼런스만을 복사하므로 배열자체는 복사되지 않는다.

  • 배열의 내용이 변경되지 않는다면 문제가 없지만 배열의 내용이 바뀔 경우 원본과 복사본이 같이 영향을 받는다.

  • 따라서 좀더 깊게 deep cloning을 시도해야 한다.

  • 위 코드는 이 객체의 clone() 메서드는 MyNumbers 객체의 복사본을 만들지만 배열에 대해서는 새로운 배열을 생성하지 않고 원본에서 생성된 배열을 공유하게 된다.

  • 원본의 배열을 가리키는 num 레퍼런스의 레퍼런스 값만 복사하기 때문이다.

  • 아래 코드는 shallow cloning을 deep cloning으로 바꾼것이다.

    @Override
    protected MyNumbers clone() throws CloneNotSupportedException {
        MyNumbers myObj = (MyNumbers) super.clone();
        myObj.num = (int[])num.clone();
        return myObj;
    }
  • 새로운 clone() 메서드는 먼저 super.clone() 메서드로 Object.clone(_ 메서드를 호출하여 완전한 객체 구조를 복사하고, deep cloning을 위하여 배열을 클로닝하여 새로 생성된 클론 객체에 대입해준다. 이로서 완전한 deep cloning을 구현할 수 있다.
  • 앞에서 설명했듯이 기본적으로 Cloneable 인터페이스를 구현했으므로 클로닝이 가능하다.
  • 따라서 이 경우에는 CloneNotSupportedException 예외가 발생할 원인이 없다.

예제코드#2

객체 레퍼런스를 포함하는 객체의 클로닝

  • 객체 레퍼런스를 포함하는 경우에는 배열보다 조금 더 주의하여야 한다.
  • 기본적으로 객체의 레퍼런스만 복사되므로, deep cloning을 위하여 배열의 경우와 같은 방법을 사용하면 된다.
package me.whiteship.livestudy.javalangpackage;

public class MyData implements Cloneable{
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
package me.whiteship.livestudy.javalangpackage;

public class MyClass implements Cloneable{
    private MyData myData = null;

    public MyClass() {
        myData = new MyData();
    }

    @Override
    protected MyClass clone() throws CloneNotSupportedException {
        MyClass obj = (MyClass)super.clone();
        obj.myData =  (MyData) myData.clone();
        return obj;
    }
}
  • 배열의 경우와 마찬가지로 완전한 클로닝을 제공하는 것 같지만 한가지 문제가 있따.
  • 배열은 기본적으로 클로닝을 제공하지만 MyData 클래스는 클로닝을 제공한다는 보장이 없다.
  • 따라서 MyData 클래스가 클로닝을 보장하지 않는다면, 즉 Clonealbe 인터페이스를 구현하지 않았다면 MyClass의 clone() 메서드는 항상 CloneNotSupportedException 예외를 발생시킬것이다
  • 따라서 deep cloning을 위해선 MyData 클래스의 클로닝을 보장할 필요가 있다.

예제코드#3

Clollection 계열의 객체를 포함하는 객체의 클로닝

  • 컬럭션 계열의 객체를 포함하는 클래스는 클로닝에 조금 더 세심한 주의를 필요로 한다.
  • 왜냐하면 콜력센 내에 어떠한 타입의 객체도 저장될 수 있으므로 저장된 객체 중 하나라도 Clonealbe 인터페이스를 구현하지 않는 객체가 있다면 클로닝에서 CloneNotSupportedException예외를 발생시킬것이다. 이러한 이유로 컬렉션 계열의 클래스는 기본적으로 Cloneable 인터페이스를 구현하지만 반드시 클로닝에 성공하는 것은 아니다.
  • 컬렉션 계열의 Vector 클래스를 예로 들면 다음과 같다.
package me.whiteship.livestudy.javalangpackage;

import java.util.Vector;

public class MyClass implements Cloneable{

    private Vector vector = null;
    public MyClass() {
        vector = new Vector();
    }

    @Override
    protected MyClass clone() throws CloneNotSupportedException {
        MyClass obj = (MyClass)super.clone();
        obj.vector = (Vector)vector.clone();
        return obj;
    }
}
  • 객체의 레퍼런스의 클로닝과 마찬가지로 컬렉션 계열의 Vector 클래스도 클로닝을 지원하므로 clone() 메서드를 사용하여 depp 클론을 구현할수가 있다. 그러나 Vector 객체에 저장된 객체들이 모두 클로닝을 지원하지 않고 하나라도 CloneNotSupportedException예외를 발생하면 deep cloning에 실패하게 된다.

shallow cloning과 deep cloning 중 어느것을 선택할것인가?

  • 어떤 클래스를 완전히 deep cloning 하기 위해선느 그 클래스로부터 참조되는 모든 클래스를 추적해서 복사하여야 한다.
  • 위의 예에서는 한단계 깊이의 클로닝이지만 필드 멤버가 가리키는 객체의 내부에 다시 객체 레퍼런스인 필드 멤버가 있고 다시 그 객체에 객체 레퍼런스가 있는 식의 구조를 지닌 경우 어느 깊이까지 클로닝을 해야 하는가는 개발자가 선택해야 한다.
  • shallow cloning을 채택하여 내부에 클로닝의 범위가 미치지 않는다면 클로닝에실패하고 CloneNotSupportedException예외 발생을 허용할 것인지 아니면 내부 객체나 배열을 공유할 것인지 미리 결정하여야 한다.
  • 공유의 경우에는 값의 변경이 치명적인 영향을 미칠 수 있다는 것을 고려하여야 한다.
  • 참고로 객체 직렬화는 deep cloning으로 객체의 참조 그래프를 모두 복제하여 스트림으로 전송하게 된다.

공변 반환타입(covariant return type)

  • JDK 1.5부터 오버라이딩할 때 부모 메서드의 반환타입을 자식 클래스의 타입으로 변경 가능하다.
public Point clone()  { // 반환타입을 Object에서 Point로 변경
    Object obj = null;
    try {
    obj = super.clone();
    } catch(CloneNotSupportedException e) {}
    return (Point)obj; //Point 타입으로 형변환
  • 공변 반환타입을 사용하면 조상의 타입이 아닌, 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어 번거로운 형변환이 줄었다.
package me.whiteship.livestudy.javalangpackage;

public class Point {

    @Override
    protected Point clone() throws CloneNotSupportedException {
        Object obj = null;
        obj = super.clone();
        return (Point) obj;
    }


    public static void main(String[] args) throws CloneNotSupportedException {
        Point org = new Point();
        Point copy =  org.clone();
    }
}
Point copy = (Point)original.clone(); -> Point copy = original.clone();

Class 클래스

[참고]https://joont.tistory.com/165

리플렉션:

  • 리플렉션은 객체를 통해 클래스의 정보를 분석해내는 프로그램 기법이다.
  • 클래스 파일의 위치나 이름만 있으면 해당 클래스의 정보를 얻어내고, 객체를 생성하는 것 또한 가능하게 해주는 유연한 프로그래밍을 위한 기법이다.
  • 또한 동적으로 객체를 생성하는 것 또한 가능해진다.
  • 현재 많이 사용되는 스프링 프레임워크에서도 리플렉션을 아주 많이 사용하고 있따.

Class 클래스:

public final class Class implements ... {	// Class 클래스
	...
}
  • Class 클래스는 리플렉션의 기초가 되는 클래스이다.
  • Class 클래스는 자바에서 사용되는 클래스들에 대한 구조를 가지고 있는 Class라고 보면된다.
package me.whiteship.livestudy.javalangpackage;

public class User { 
    public String id;
    public String pwd;
    public String name;
    public Integer birthDate;

    public User() { }

    public User(String id, String pwd, String name, Integer birthDate) {
        this.id = id;
        this.pwd = pwd;
        this.name = name;
        this.birthDate = birthDate;
    }

    @Override
    public String toString() {
        return super.toString();
    }
    
    // getter,setter 생략
}
  • User 클래스는 4개의 필드, 2개의 생성자. 9개의 메서드(getter/setter 포함)라는 속성을 가지고 있는 클래스로 정의된다.
  • 여기서 한단계 더 높은 게념으로 생각해보면, 클래스라는 것 자체도 필드,생성자,메서드 등의 속성을 가지고 있다.
  • 즉, Class 클래스는 이러한 클래스의 구조 자체를 하나의 클래스로 표현해놓은 클래스이다.
  • Class 클래스 내부의 메서드를 보면

  • 반환형인 Field,Method,Package,Constructor 등은 모두 reflect 패키지에서 제공해주는 클래스들이다.
  • 이렇듯 리플렉션을 통해 클래스의 정보를 알아내기 위한 기점에는 Class 클래스가 있는것이다.

getClass()

  • 이제 위에서 작성한 User 클래스에 리플렉션을 통해 정보를 뽑아내보자.
  • 먼저 User 클래스를 Class 객체로 뽑아내야 한다.
Class.forName("전체경로") or 클래스이름.class
  • 자신이 속한 클래스의 Class 객체를 반환하는 메서드인데. Class 객체는 이름이 'Class' 인 클래스의 객체이다.

  • Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스당 1개만 존재한다.

  • 그리고 클래스 파일이 클래스 로더에 의해서 메모리에 올라갈때, 자동으로 생성된다.

  • 클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.

  • 먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환하고 없으면 클래스패스에 지정된 경로를 따라서 클래스 파일을 찾는다.

  • 못찾으면 ClassNotFoundException이 발생하고, 찾으면 해당 클래스 파일을 읽어서 Class 객체로 변환한다.

  • 파일 형태로 저장되어있는 클래스르 읽어서 CLASS 클래스에 정의된 형식으로 변환하는 것이다.

  • 즉, 클래스 파일을 읽어서 사용하기 편한 형태로 저장해 높은것이 클래스 객체이다.(클래스 파일을 메모리에 로드하고 변환하는 일은 클래스 로더가 한다.)

Class 객체를 얻는 방법

Class c1 = new Book().getClass(); // 생성된 객체로부터 얻는 방법
        Class c2 = Book.getClass(); // 클래스 리터럴(*.class)로 부터 얻는 방법
        Class c3 = Class.forName("Book"); // 클래스이름으로 부터 얻는 방법
        
  • 특히 forName()은 특정 클래스파일, 예를 들어데이터베이스 드라이버를 메모리에 올릴때 주로 사용한다.
  • Class 객체를 이용하면 클래스에 정의된 멤버의 이름이나 개수 등 클래스에 대한 모든 정보를 얻을 수 있기 때문에 Class 객체를 통해서 객체를 생성하고 메서드를 호출하는 등 보다 동적인 코드를 작성할 수 있다.
  • 위의 형태로 호출하게 되면 해당 클래스의 정보를 담은 Class 클래스가 반환된다.
package me.whiteship.livestudy.javalangpackage;

import java.lang.reflect.*;

public class User {
    public String id;
    public String pwd;
    public String name;
    public Integer birthDate;

    public User() { }

    public User(String id, String pwd, String name, Integer birthDate) {
        this.id = id;
        this.pwd = pwd;
        this.name = name;
        this.birthDate = birthDate;
    }

    @Override
    public String toString() {
        return super.toString();
    }

    public String getId() {
        return id;
    }

    public String getPwd() {
        return pwd;
    }

    public String getName() {
        return name;
    }

    public Integer getBirthDate() {
        return birthDate;
    }

    public static void main(String[] args) throws ClassNotFoundException{
        Class user = Class.forName("me.whiteship.livestudy.javalangpackage.User");

        System.out.println("Field List");

        for (Field field : user.getFields()) {
            System.out.println(field.getName());
        }

        System.out.println("\nConstructor List");
        for (Constructor constructor : user.getConstructors()) {
            System.out.println("개수 " + constructor.getParameterCount() + "=");
            for (Class parameter : constructor.getParameterTypes()) {
                System.out.println(parameter.getName());
            }
        }

        System.out.println("\nMethod List");
        for (Method method : user.getMethods()) {
            System.out.println(method.getName());
        }

    }
}

output

Field List
id
pwd
name
birthDate

Constructor List
개수 0=
개수 4=
java.lang.String
java.lang.String
java.lang.String
java.lang.Integer

Method List
getPwd
getBirthDate
main
toString
getName
getId
wait
wait
wait
equals
hashCode
getClass
notify
notifyAll

Process finished with exit code 0
  • 필드, 생성자, 메서드 외에도 어노테이션,패키지 등등 다양한 정보를 추출해 낼 수 있다.

getDeclaredXXX()

  • Class 클래스의 메서드를 보면 getDeclaredXXX() 라는 메서드도 있다.
  • 이는 public 외의 접근 제어자를 가진 속성에 접근하기 위한 메서드이다
  • 현재 User 클래스의 모든 필드, 생성자, 메서드가 public 이므로 각 속성을 잘 뽑아왔지만, 실상 위의 메서드들은 public 인 속성만 뽑아오는 메서드이다.
  • getDeclaredXXX()은 접근 제어자에 상관없이 클래스의 모든 속성을 가져올 수 있다.

래퍼(wrapper) 클래스:

  • 자바의 자료형은 크게 기본타입,참조타입으로 나누어진다.
  • 대표적으로 기본타입은 char,int,double,boolean 등이 있고, 참조 타입은 class,interface 등이 있는데 프로그래밍을 하다보면 기본 타입의 데이터를 객체로 표현해야 하는 경우가 종종있다.
  • 이럴때에 기본자료타입을 객체로 다루기 위해서 사용하는 클래스들을 래퍼 클래스라고 한다.
  • 자바는 모든 기본타입의 값을 갖는 객체를 생성할수있다
  • 이런 객체를 포장 객체라고도 하는데 그 이유는 기본 타입의 값을 내부에 두고 포장하기 때문이다.
  • 래퍼 클래스로 감싸고 있는 기본 타입 값은 외부에서 변경할수없다.
  • 만약 값을 변경하고 싶다면 새로운 포장 객체를 만들어야 한다.
  • 래퍼클래스들은 객체생성시에 생성자의 인자로 주어진 각 자료형에 알맞은 값을 내부적으로 저장하고 있으며, 이에 관련된 여러 메서드가 정의되어있다.
  • 래퍼클래스들은 모두 equals() 메서드가 오버라이딩되어 있어서 주소값이 아닌 객체가 가지고 있는 값을 비교한다.
  • 오토박싱이 된다고 해도 Integer 객체에 비교연산(==)을 사용할수없다.
  • 또한 toString() 도 오버라이딩 되어있어서 객체가 가지고 있는 값을 문자열로 변환하여 반환한다.
기본 타입래퍼 클래스
byteByte
charCharactre
intInteger
floatFloat
doubleDouble
booleanBoolean
longLong
shortShort

  • 위의 계층 구조에서 볼수있듯 모든 래퍼 클래스의 부모는 Object이고 내부적으로 숫자를 다루는 래퍼클래스의 부모 클래스는 Number 클래스이다.

박싱과 언박싱:

  • 기본 타입의 값을 포장 객체로 만드는 과정을 박싱이라고 하고, 반대로 포장객체에서 기본타입의 값을 얻어내는 과정을 언박싱이라고 한다.
        Integer num = new Integer(7);// 박싱
        int n = num.intValue(); // 언박싱
        System.out.println(n);

자동박싱과 자동언박싱:

  • 기본타입 값을 직접 박싱, 언박싱하지 않아도 자동적으로 박싱과 언박싱이 일어나느 경우가 있다.
  • 자동박싱은 포장 클래스 타입에 기본값이 대입될 경우에 발생한다.
  • 예를 들어 int타입의 값을 Integer 클래스 변수에 대입하면 자동박싱이 일어나 힙 영역에 Integer 객체가 생성된다.
Integer num = 7;// 자동박싱
int n = num; // 자동언박싱
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(10);			 // 오토박싱

int value = list.get(0); // 언박싱

간단한 사용예제

  • 문자열을 기본타입값으로 변환
        String str1 = "10";
        String str2 = "10.5";
        String str3 = "true";

        byte b = Byte.parseByte(str1);
        int i = Integer.parseInt(str1);
        short s = Short.parseShort(str1);
        long l = Long.parseLong(str1);

        float f = Float.parseFloat(str2);
        double d = Double.parseDouble(str2);
        boolean bool = Boolean.parseBoolean(str3);
  • 래퍼 클래스의 주요 용도는 기본 타입의 값을 박싱 해서 포장 객체로 만드는것이지만, 문자열을 기본타입값으로 변환할때에도 사용된다. 대부분의 래퍼 클래스에는 paser+기본타입명으로 되어있는 정적메서드가 있다.

  • 이 메서드는 문자열을 매개 값으로 받아 기본 타입 값으로 변환한다.

  • 값 비교

        Integer integer = new Integer(10);
        Integer integer1 = new Integer(10);
        int i = 10;

        System.out.println("래퍼클래스 == 기본타입 : "+(integer == i)); //true
        System.out.println("래퍼클래스.equals(기본타입) : "+integer.equals(i)); //true
        System.out.println("래퍼클래스 == 래퍼클래스 : "+(integer == integer1)); //false
        System.out.println("래퍼클래스.equals(래퍼클래스) : "+integer.equals(integer1)); //true
  • 래퍼객체는 내부의 값을 비교하기 위해 == 연산자를 사용할수없다.
  • 이 연산자는 내부의 값을 비교하는 것이 아니라 래퍼 객체의 참조 변수가 저장한 주소를 비교하는 것이다.
  • 객체끼리의 비교를 하려면 내부의 값만 얻어 비교해야 하기 때문에 equals 메서드를 사용하면 된다.
  • 래퍼클래스와 기본자료형과의 비교는 ==연산과 equals연산 모두 가능하다.
  • 그 이유는 컴파일러가 자동으로 오토박싱과 언박싱을 해주기 때문이다.

java.util 패키지

java.util.Objects 클래스

  • Object 클래스의 보조 클래스로 Math 클래스 처럼 모든 메서드가 static이다.
  • 객체의 비교나 null check에 유용하다
  • isNull()은 해당 객체가 널인지 확인해서 null이면 true를 반환하고 아니면 false를 반환
  • nonNull()은 isNull()과 정확히 반대의 일을한다.
	static boolean isNull(Object obj)
    static boolean nonNull(Object obj)
  • 그리고 requireNonNull() 은 해당 객체가 널이 아니어야 하는 경우에 사용한다.
  • 만약 객체가 널이면, NullPointerException을 발생시킨다.
  • 두번째 매개변수로 지정하는 문자열은 예외 메시지가 된다
static <T> requireNonNull(T obj)
static <T> requireNonNull(T obj, String message)
  • Object 클래스에는 두 객체를 비교하는 메서드가 등가비교를 위한 equals()만 있고, 대소비교를 위한 compare()가 없는 것이 좀 아쉬웠다.
  • 그래서인지 Objects에는 compare()이 추가되었다. compare()은 두 비교대상이 같으면 0, 크면 양수, 작으면 음수를 반환한다.
static int compare(Object a, Object b, Comparator c)
  • 이 메서드는 객체 a와 b를 비교하는데, 두 객체를 비교하는데 사용할 비교기준이 필요하다.

  • 그 역할을 하는 것이 Comparator이다.

  • Objects 클래스의 equals 메서드는 Object 클래스와는 달리, null 검사를 하지 않아도 된다.

  • equals() 메서드 내부에서 a와 b의 null 검사를 하기 때문에 따로 null 검사를 위한 조건식을 넣이 않아도 된다.

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}
  • a와 b가 모두 null인 경우 참을 반환한다는 점을 빼고는 특별한 것이 없다.
  • deepEquals() 메서드는 객체를 재귀적으로 비교하기 때문에 다차원 배열의 비교도 가능하다.

java.util.Scanner 클래스

  • Scanner는 화면,파일,문자열과 같은 입력소스로 부터 문자데이터를 읽어오는데 도움을 줄 목적으로 JDK 1.5부터 도입되었다
  • Scanner에는 다음과 같은 생성자를 지원하기 때문에 다양한 입력소스로부터 데이터를 읽을수있다.
Scanner(String source)
Scanner(File source)
Scanner(InputStream source)
Scanner(Readable source)
Scanner(ReadableByteChannel source)
Scanner(Path source)		.. JDK 1.7부터 추가

java.util.StringTokenizer 클래스

  • StringTokenizer는 긴 문자열을 지정한 구분자(delimeter)를 기준으로 토큰(token)이라는 여러개의 문자열로 잘라내는데 사용된다.
  • StringTokenizer을 이용하는 방법이외에도 String의 split이나 Scanner의 useDelimiter를 사용할수도 있다.
  • 위 두가지 방법은 정규식 표현(Regular expression)을 사용해야 하므로 정규식 표현에 익숙하지 않은 경우 StringTokenizer을 사용하는 것이 간단하면서 명확한 결과를 얻을 수 있다.
  • 그러나 StringTokenizer는 구분자로 단 하나의 문자열 밖에 사용하지 못하기 때문에 복잡한 형태로 문자열을 나누어야 하는 경우네느 정규식을 사용하는 메서드를 사용해야한다.
  • split()은 빈 문자열도 토큰으로 인식하는 반면 StringTokenizer는 빈 문자열을 토큰으로 인식하지 않기 때문에 인식하는 토큰의 개수가 서로 다른 것을 알 수 있다.
  • 이 외에도 성능의 차이가 있는데 split()은 데이터를 토큰으로 잘라낸 결과를 배열에 담아서 반환하기 때문에 데이터를 토큰으로 바로바로 잘라서 반환하는 StringTokenizer보다 성능이 떨어질수밖에 없다.
  • 그런 데이터의 양이 많은 경우가 아니라면 별 문제가 되지 않으므로 크게 신경 쓸 부분은 아니다.
        String str="this-=string-includes=delims";
        StringTokenizer stk=new StringTokenizer(str,"-=");
        System.out.println(str);
        System.out.println();

        System.out.println("total tokens:"+stk.countTokens());
        System.out.println("================tokens==================");
        while(stk.hasMoreTokens()){
            System.out.println(stk.nextToken());
        }
        System.out.println("total tokens:"+stk.countTokens());

output

total tokens:4
================tokens==================
this
string
includes
delims
total tokens:0

java.math.BigInteger 클래스

  • 정수형으로 표현할 수 있는 값의 한계가 있다.

  • 가장 큰 정수형 타입인 long으로 표현할 수 있는 값은 10진수로 19자리 정도이다.

  • 이 값도 상당히 크지만 과학적 계산에서는 더 큰 값을 다뤄야할 때가 있다.

  • 그럴때 사용하면 좋은것이 BigInteger이다.

  • BigInteger는 내부적으로 int배열을 사용해서 값을 다룬다.

  • 그래서 long타입보다 훨씬 큰 값을 다룰수 있따.

  • 대신 성능은 long보다 떨어질수밖에 없다

final int signum; // 부호. 1(양수), 0, -1(음수) 셋 중의 하나
final int[] mag;  // 값
  • BigInteger은 String처럼 불변이다.
  • 그리고 모든 정수형이 그렇듯이 BigInteger 역시 2의 보수의 형태로 표현한다.
  • 위 코드에서 알 수 있듯이 부호를 따로 저장하고 배열에는 값 자체만 저장한다.
  • 그래서 signum의 값이 -1, 즉 음수인 경우, 2의 보수법에 맞게 mag의 값을 변환해서 처리한다.
  • 그래서 부호만 다른 두 값의 mag는 같고 signum은 다르다.

비트연산메서드

  • 워낙 큰 숫자를 다루기 위한 클래스이므로, 성능을 향상시키기 위해 비트단위로 연산을 수행하는 메서드들을 많이 가지고 있다.
  • 따라서 가능하면 산술연산 대신 비트연산으로 처리하면 성능이 빨라질것이다.

java.math.BigDecimal 클래스

  • double 타입으로 표현할 수 있는 값은 상당히 범위가 넓지만 정밀도가 최대 13자리 밖에 되지 않고 실수형의 특성상오차를 피할수없다.
  • BigDecimal은 실수형과 달리 정수를 이용해서 실수를 표현한다.
  • 실수의 오차는 10진 실수를 2진 실수로 정확히 변환할 수 없는 경우가 있기 때문에 발생하는 것이므로, 오차가 없는 2진 정수로 변환하여 다루는 것이다.

참고

[참고링크]https://ryan-han.com/post/java/java-lang/
[클래스참고]https://jongmin92.github.io/2018/04/07/Java/java-lang-package-and-useful-class/
[wrapper참고]https://coding-factory.tistory.com/547

0개의 댓글