Object 클래스

최상위 클래스 Object

모든 자바 클래스는 Object 클래스로부터 상속을 받습니다.

package inheritance;

public class InheritanceObject {
    public static void main(String[] args) {
    }
}

extends라는 것이 없어서 확장한 부모클래스가 없다고 생각할 수 있지만 기본적으로 아무런 상속을 받지 않으면 java.lang.Object 클래스를 확장합니다. Object 클래스를 상속받는 이유는 모든 클래스가 기본적으로 가져야 할 것들을 정의하고 싶은 것입니다.

Object 클래스 메소드들

메소드 설명
proctected Object clone() 객체의 복사본을 만들어 리턴한다.
public boolean equals(Object obj) 현재 객체와 매개 변수로 넘겨 받은 객체가 같은지 확인하다. 같으면 true를 다르면 false를 리턴한다.
protected void finalize() 현재 객체가 더 이상 쓸모가 없어졌을 때 가비지 컬렉터에 의해서 이 메소드가 호출된다.
public Class<?> getClass() 현재 객체의 Class 클래스의 객체를 리턴한다.
public int hashCode() 객체에 대한 해시코드값을 리턴한다. 해시코드란 16진수 객체 메모리의 주소를 말한다.
public String toString() 객체를 문자열로 표현하는 값을 리턴한다.

Object 클래스 메소드들 중 스레드 처리를 위한 메소드에는 다음과 같은 것들이 있습니다.

메소드 설명
public void notify() 이 객체의 모니터에 대기하고 있는 단일 스레드를 깨운다.
public void notifyAll() 이 객체의 모니터에 대기하고 있는 모든 스레드를 깨운다.
public void wait() 다른 스레드가 현재 객체에 대한 notify() 메소드나 notifyAll() 메소드를 호출할 때까지 현재 스레드가 대가하고 있도록 한다.
public void wait(long timeout) wait() 메소드와 동일한 기능을 제공하며, 매개 변수에 지정한 시간만큼만 대기한다. 즉, 매개변수 시간을 넘어 섰을 때에는 현재 스레드는 다시 깨어난다. 시간은 밀리초 단위다.
public void wait(long timeout, int nanos) wait()메소드와 동일한 기능을 제공한다. 이 메소드는 밀리초 + 나노초(1/1,000,000,000)만큼만 대기한다.

Object 클래스에서 가장 많이 쓰이는 toString() 메소드

package c.inheritance;

public class InheritanceObject {
    //중간생략
    public void toStringMethod(InheritanceObject obj) {
        System.out.println(obj);
        System.out.println(obj.toString());
        System.out.println("plus "+obj);
    }
}
package c.inheritance;

public class InheritanceObject {
    public static void main(String[] args) {
        InhertanceObject obj = new InhertanceObject();
        obj.toStringMethod();
    }
    //이하생략
}

toString()으로 출력하는 문장은

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

InheritanceObject 클래스의 toString() 메소드를 다음과 같이 Overriding하자.

package c.inheritance;

public class InheritanceObject {
    //중간생략
    public String toString() {
        return "InheritanceObject class";
    }
}

앞에서 예제로 사용한 MemberDTO 클래스의 인스턴스 변수 선언부를 보자.

package c.inheritance;

public class MemberDTO {
    public String name;
    public String phone;
    public String email;
    //이하 생략
}

MemberDTO에 선언되어 있는 name, phone, email을 확인하려면

MemberDTO dto = new MemberDTO("Sangmin", "010xxxxxxx", "java@gmail.com");
System.out.println("Name="+dto.name+" Phone="+dto.phone+" Email="+dto.email);

이라고 해야 할 것이다. 하지만 toString() 메소드를 overriding하면 쉽게 정보를 출력할 수 있다.

package c.inheritance;

public class MemberDTO {
    //중간 생략
    public String toString() {
        return "Name="+name+" Phone="+phone+" Email="+email;
    }
}

그러면 다음과 같이 간단히 출력할 수 있다.

MemberDTO dto = new MemberDTO("Sangmin", "010xxxxxxx", "java@gmail.com");
System.out.println(dto);

equals() 메소드

== 연산자는 기본 자료형에서 두 값이 같은지를 판단하는데 사용할 수 있지만 참조 자료형에서 사용하면 두 객체의 주소값이 같은지를 판단하는데 사용됩니다. 즉, 두 객체의 내용이 같은지를 판단하는 것은 부적절합니다.

package inheritance;

public class InheritanceObject {
    //중간 생력
    public void equalMethod() {
        MemberDTO obj1 = new MemberDTO("Sangmin");
        MemberDTO obj2 = new MemberDTO("Sangmin");
        if (obj1 == obj2) {
            System.out.println("obj1 and obj2 is same");
        } else {
            System.out.println("obj1 and obj2 is different");
        }
    }
}

를 실행하면 obj1과 obj2는 다른 객체이기 때문에 다르다고 출력한다. 하지만 메모리 주소는 다르지만 안에 있는 내용은 똑같으므로 두 객체가 같다고 해야하는 상황이 발생할 수 있다. 이럴때 사용하는 메소드가 equals()이다. 다음과 같이 해보자.

package c.inheritance;

public class InheritanceObject {
    //중간 생력
    public void equalMethod() {
        MemberDTO obj1 = new MemberDTO("Sangmin");
        MemberDTO obj2 = new MemberDTO("Sangmin");
        if (obj1.equals(obj2)) {
            System.out.println("obj1 and obj2 is same");
        } else {
            System.out.println("obj1 and obj2 is different");
        }
    }
}

이것을 실행해도 마찬가지로 두 객체가 다르다고 나온다. 이유는 기본적으로 equals()메소드는 객체의 주소값에 해당하는 hashCode()값을 리턴하기 때문이다. 이제 MemberDTO 클래스의 equals() 메소드를 overriding하자.

package c.inheritance;

public class MemberDTO {
    //중간 생력
    public boolean equals(Object obj) {
        if (this == obj) return true; //주소가 같으므로
        if (obj == null) return false; //obj가 null이므로
        if (getClass() != obj.getClass()) return false; //클래스의 이름이 다르므로

        MemberDTO other = (MemberDTO) obj;

        if (name == null) {
            if (other.name != null) return false;
        } else if (!name.equals(other.name)) return false;
        if (email == null) {
            if (other.email != null) return false;
        } else if (!email.equals(other.email)) return false;

        if (phone == null) {
            if (other.phone != null) return false;
        } else if (!phone.equals(other.phone)) return false;
        return true;
    }
}

equals() 메소드를 오버라이딩할 때 다음과 같은 조건을 만족해야한다.

  • 재귀(reflexive): null이 아닌 x라는 객체의 x.equals(x) 결과는 항상 true여야 한다.
  • 대칭(symmetric): null이 아닌 x와 y의 객체가 있을 때 y.equals(x)가 true이면 x.equals(y)도 true여야 한다.
  • 타동적(transitive): null이 아닌 x, y, z가 있을 때 x.equals(y)가 true이고 y.equals(z)가 true이면 x.equals(z)도 반드시 true여야 한다.
  • 일관(consistent): null이 아닌 x와 y가 있을 때 객체가 변경되지 않은 상황에서는 몇 번을 호출하더라도 x.equals(y)의 결과는 항상 true이거나 항상 false여야 한다.
  • null과 비교: null이 아닌 x라는 객체의 x.equals(null) 결과는 항상 false여야 한다.

한가지 유의해야 할 것이 있다. equals() 메소드를 오버라이딩할 때 hashCode() 메소드도 같이 오버라이딩해야 한다. equals() 메소드가 true여도 두 객체의 주소값이 달라서 hashCode() 메소드의 값은 다르게 된다.

package c.inheritance;

public class MemberDTO {
    //중간 생력
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((email == null) ? 0 : email.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((phone == null) ? 0 : phone.hashCode());
        return result;
    }

hashCode() 메소드

hashCode() 메소드는 기본적으로 객체의 메모리 주소를 16진수로 리턴한다. 만약 두 개의 객체가 같으면 hashCode() 값은 무조건 동일해야 한다. 따라서 equals() 메소드를 오버라이딩하면 hashCode() 메소드로 오버라이딩해야 한다. 자바 API 문서에 hashCode() overriding 조건은 다음과 같다.

  • 자바 애플리케이션이 수행되는 동안에 어떤 객체에 대해서 이 메소드가 호출될 때에는 항상 동일한 int값을 리턴해야 한다. 하지만 자바를 실행할 때마다 같은 값이어야 할 필요는 없다.
  • 어떤 두 개의 객체에 대하여 equals() 메소드를 사용하여 비교한 결과가 true일 경우에, 두 객체의 hashCode() 메소드를 호출하면 동일한 int값을 리턴해야 한다.
  • 두 객체를 equals() 메소드를 사용하여 비교한 결과 false를 리턴했다고 해서, hashCode() 메소드를 호출한 int 값이 무조건 달라여 할 필요는 없다. 하지만, 이 경우에 서로 다른 int값을 제공하면 hashtable의 성능을 향상시키는데 도움이 된다.

직접해 봅시다

  1. name, address, phone, email 변수가 선언되어 있는 것을 확인하자.

  2. Student 클래스의 내용, 즉 name, address, phone, email 값이 같으면 같은 클래스로 인식이 되도록 equals() 메소드를 직접 작성해보자.

  3. ManageStudent 클래스에 매개 변수 및 리턴값이 없는 checkEquals()라는 메소드를 만들자.

  4. checkEquals() 메소드에 다음과 같이 동일한 두 개의 Student 객체를 생성하자.

    Student a = new Student("Min", "Seoul", "010xxxxxxx", "ask@gmail.com");
    Student b = new Student("Min", "Seoul", "010xxxxxxx", "ask@gmail.com");
    
  5. a와 b 객체가 같으면 “Equal”을 다르면 “Not Equal”을 출력하는 문장을 checkEquals() 메소드에 추가하자.

  6. ManageStudent 클래스의 main() 메소드에서 나머지 메소드 호출은 주석 처리하고, checkEquals() 메소드를 호출하도록 하자.

  7. ManageStudent 클래스를 컴파일하고 실행결과가 “Equal”로 출력되는지 확인해보자.

Class 클래스

모든 자바 클래스는 getClass() 메소드를 자바의 Object 클래스로부터 상속을 받습니다. getClass()는 객체의 Class [1] 객체를 반환합니다. Class 클래스는 객체의 클래스의 필드, 메소드, 어노테이션 등의 정보들을 이용할 수 있습니다.

다음은 inheritance 패키지에 있는 Recipe.java 클래스입니다.

// Recipe.java
package inheritance;

public class Recipe {
  private String title;
  private String blogger;
  private String recipe;

  public Recipe() {

  }

  public Recipe(String title, String blogger, String recipe) {
    this.title = title;
    this.blogger = blogger;
    this.recipe = recipe;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getBlogger() {
    return blogger;
  }

  public void setBlogger(String blogger) {
    this.blogger = blogger;
  }

  public String getRecipe() {
    return recipe;
  }

  public void setRecipe(String recipe) {
    this.recipe = recipe;
  }
}

클래스 이름

Class 객체의 getName() 메소드는 객체의 패키지 이름이 붙은 온전한 클래스 이름을 반환합니다.

Recipe recipe = new Recipe();
Class<? extends Recipe> c = recipe.getClass();

System.out.println(c.getName());
inheritance.Recipe

getSimpleName() 메소드는 간단히 클래스 이름만 반환합니다.

Recipe

필드 이용

클래스의 필드 이름들을 반환받으려면 getDeclaredFields() 또는 getMethods()를 사용하면 됩니다. 이 메소드들은 java.lang.reflect 패키지의 Field 객체를 반환합니다. Field 객체를 이용해 객체의 필드에 동적으로 접근할 수 있습니다.

import java.lang.reflect.Field;
import java.util.Arrays;

Recipe recipe = new Recipe();
Class<? extends Recipe> c = recipe.getClass();

Field[] fields = c.getDeclaredFields();
System.out.println(Arrays.toString(fields));
[private java.lang.String inheritance.Recipe.title, private java.lang.String inheritance.Recipe.blogger, private java.lang.String inheritance.Recipe.recipe]

메소드 이용

클래스의 메소드 정보를 이용하기 위해서는 Class 클래스의 getDeclaredMethods() 또는 getMethods()를 사용하면 됩니다.

Recipe recipe = new Recipe();
Class<? extends Recipe> c = recipe.getClass();

Method[] methods = c.getDeclaredMethods();
System.out.println(Arrays.toString(methods));
[public java.lang.String inheritance.Recipe.getTitle(), public java.lang.String inheritance.Recipe.getBlogger(), public void inheritance.Recipe.setBlogger(java.lang.String), public void inheritance.Recipe.setTitle(java.lang.String), public java.lang.String inheritance.Recipe.getRecipe(), public void inheritance.Recipe.setRecipe(java.lang.String)]

두 메소드의 차이는 getDeclaredMethods는 정의된 클래스에 있는 모든 메소드들을 부르는 반면 getMethods는 public으로 정의된 메소드들을 반환합니다. 또한 getMethods는 상속받은 모든 public 메소드들도 반환합니다. 즉 Object 클래스에서 상속받은 public 메소드들 wait, equals, toString, getClass등도 반환합니다.

Recipe recipe = new Recipe();
Class<? extends Recipe> c = recipe.getClass();

Method[] methods = c.getMethods();
  for (Method method : methods) {
    System.out.println(method.getName());
  }
getTitle
getBlogger
setBlogger
setTitle
getRecipe
setRecipe
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

메소드 실행

invoke()메소드를 이용해 객체의 메소드를 실행할 수 있습니다.

실행할 메소드 이름 getTitle을 getMethod의 인자로 넘겨서 Method 객체를 얻어 옵니다.

Recipe recipe = new Recipe("비빔밥", "쉐프", "볶아요!");
Class<? extends Recipe> c = recipe.getClass();

// getTitle이란 메소드를 반환합니다.
Method method = c.getMethod("getTitle");

Method 객체의 invoke 메소드의 인자로 Recipe 객체를 넘겨줌으로 Recipe 객체의 getTitle() 메소드를 호출합니다.

String value = (String) method.invoke(recipe);
System.out.println(value);

그럼 다음과 같은 결과가 출력됩니다.

비빔밥

다음은 위의 코드들을 합치고 예외처리를 한 것입니다.

Recipe recipe = new Recipe("비빔밥", "쉐프", "볶아요!");
Class<? extends Recipe> c = recipe.getClass();

try {
  String title = "getTitle";

  // getTitle이란 메소드를 반환합니다.
  Method method = c.getMethod(title);

  // recipe 객체의 getTitle() 메소드를 호출합니다.
  String value = (String) method.invoke(recipe);
  System.out.println(value);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

결과는 다음과 같습니다.

비빔밥

인자를 필요로 하는 메소드를 실행합니다. recipe.setTitle() 메소드는 인자로 String 객체가 필요합니다.

method = c.getMethod("setTitle", String.class);
method.invoke(recipe, "전주비빔밥");
System.out.println(recipe.getTitle());

출력은 다음과 같습니다.

전주비빔밥

Insert

for (int i=0; i<fieldNames.length; i++) {
  String methodName = "get" + Character.toUpperCase(fieldNames[i].charAt(0)) + fieldNames[i].substring(1);
  Method method = c.getDeclaredMethod(methodName);
  pstmt.setString(i+1, (String) method.invoke(recipe));
}

Create Sql

private void createTableSql(String[] fieldNames) {
  createTableSqlStr = "CREATE TABLE IF NOT EXISTS " + tableName + " (";
  String fields = String.join(" TEXT, ", fieldNames) + " TEXT";
  createTableSqlStr += fields + ")";
  System.out.println(createTableSqlStr);
}
// RecipeTest.java
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class RecipeTest {

  public static void main(String[] args) {
    Recipe recipe = new Recipe("비빔밥", "쉐프", "나물");
    Class<? extends Recipe> c = recipe.getClass();
    System.out.println(c.getName().toLowerCase());
    tableName = c.getSimpleName().toLowerCase();
    Field[] fieldList = c.getDeclaredFields();
    fieldNames = new String[fieldList.length];
    for (int i=0; i<fieldList.length; i++) {
      System.out.println("Field Name: "+ fieldList[i].getName());
      fieldNames[i] = fieldList[i].getName();
      System.out.println("Field Type: "+ fieldList[i].getType());
      System.out.println(fieldNames[i]);
    }
  }
}

참조 사이트

[1]https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html