Dipa's document :: Dipa's document

Class클레스란?

자바의 클레스와 인터페이스의 메타데이터(클레스의 이름, 생성자의 정보, 필드 정보, 메소드 정보)등등을 관리 하는 클레스이다.

즉, 클레스 안의 멤버 변수와 메소드 등등의 리턴 타입이라던지...파라메터값 등등을 알 수 있다는 뜻이다.

그 중에서 오늘은 getClass(), forName()를 살펴보고자 한다. 


저 두개의 메소드의 공통점은 무엇일까? 일단 힌트는 둘다 Class의 정보를 받아볼수 있는 메소드이다.

코드를 통해 살펴보자..

 
  
public final native Class<?> getClass();
 
public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

일단 둘다 공통점은 Class를 return한다는 것이다. 

차이점은? 

getClass() 같은경우는 Object클레스에 있는 메소드로써 객체를 생성을 한뒤 호출 할 수 있는 메소드이다.

forName() 같은경우는 객체를 생성하기 전에 직접 객체를 얻을 수 있다.

package api_class._class;
 
public class Car {
	private String model;
	private String owner;
	
	public Car(){
		
	}
	
	public Car(String model){
		this.model = model;
	}
	public String getModel() {
		return model;
	}
	public void setModel(String model) {
		this.model = model;
	}
	public String getOwner() {
		return owner;
	}
	private void setOwner(String owner) {
		this.owner = owner;
	}
}
package api_class._class;
 
public class ClassExample {

	public static void main(String[] args) {
		Car car = new Car();
		Class clazz1 = car.getClass();
		System.out.println(clazz1.getName());
		System.out.println(clazz1.getSimpleName());
		System.out.println(clazz1.getPackage().getName());
		
		System.out.println();
		
		try {
			Class clazz2 = Class.forName("api_class._class.Car");
			System.out.println(clazz2.getName());
			System.out.println(clazz2.getSimpleName());
			System.out.println(clazz2.getPackage().getName());
		} catch (ClassNotFoundException e) {
			System.out.println("존재하지 않은 클레스");
		}
		
	}

}

위의 소스는 단지 클레스의 이름에 대한 정보만 나와있다.. 하지만 생성자, 필드 메소드의 정보도 얻을수 있다고 하였는데?

당연 정보를 얻어 올수 있다!! 이것이 바로 Reflection(리플렉션)이다.

각각의 정보를 얻어 올수 있는 메소드 (생성자 - getDeclaredConstructors(), 필드 - getDeclareFields(), 메소드 - getDeclaredMethods() ) 이다. 

이들의 리턴값은 각각의 타입에 대한 배열이다. (왜냐하면,, 여러개 있을수 있으니까?)

하지만 저들의 메소드는 그 클레스에 선언된 것만 가져온다. 만약 상속된 멤버 까지 가져오고 싶다면.. 중간에 Declare를 뺴면된다..? 쉽죠? ㅋㅋ

이제 각각의 정보를 가져오는 소스를 보도록 하자

package api_class._class;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionExample {

	public static void main(String[] args) throws ClassNotFoundException {
		try {
			Class clazz = Class.forName("_class.Car");
			System.out.println("[생성자 정보]");
			Constructor[] constructors = clazz.getDeclaredConstructors();
			for(Constructor constructor : constructors){
				System.out.print(constructor.getName() + "(");
				Class[] parameters = constructor.getParameterTypes();
				printParameters(parameters);
				System.out.println(")");
			}
			
			System.out.println();
			
			System.out.println("[필드 정보]");
			Field[] fields = clazz.getDeclaredFields();
			
			for(Field field : fields){
				System.out.println(field.getType().getSimpleName() + " " + field.getName());
			}
			System.out.println();

			System.out.println("[메소드 정보]");
			Method[] methods = clazz.getDeclaredMethods();
			for(Method method : methods){
				System.out.print(method.getReturnType().getSimpleName() + " " + method.getName() + "(");
				Class[] parameters = method.getParameterTypes();
				printParameters(parameters);
				System.out.println(")");
			}
			
		} catch (ClassNotFoundException e) {
			System.out.println("클레스가 없음");
		}
		
	}
	
	static void printParameters(Class[] parameters){
		for(int i=0;i<parameters.length;i++){
			System.out.print(parameters[i].getName());
			if(i<(parameters.length-1)){
				System.out.print(",");
			}
		}
	}

}

이제 마지막으로 그렇다면.. 이들을 정보만 알지말고 객체를 new연산자를 이용해서 생성하지 않고, 메소드를 호출하여 사용할 수 있을까?

그것이 바로 newInstance()를 이용하여 사용할 수있다. newInstance()는 new연산자를 이용하여 객체를 생성을 하지않고, 동적으로 객체를 생성하여, runtime시에 발생을 하게 된다.

package api_class._class;

public interface Action {
	public void execute();
}


package api_class._class;

public class ReceiveAction implements Action{

	@Override
	public void execute() {
		System.out.println("데이터를 받습니다.");
	}
	
}


package api_class._class;

public class SendAction implements Action{

	@Override
	public void execute() {
		System.out.println("데이터를 보냅니다.");
	}
	
}


package api_class._class;

public class NewInstanceExample {

	public static void main(String[] args) {
		try {
			Class clazz = Class.forName("_class.ReceiveAction");
			Action action = (Action) clazz.newInstance();
			action.execute();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}

}


'SH.Language > Java' 카테고리의 다른 글

[JAVA] Wrapper 클레스  (0) 2018.01.04
[JAVA] String, StringBuffer, StringBuilder 클레스  (0) 2018.01.02
[JAVA] Object클레스 - clone()  (0) 2017.12.14
[JAVA] Object클레스 - toString()  (0) 2017.12.11
[JAVA] Object클레스 - hashCode()  (0) 2017.12.06
Posted by SH후니
,

Object클레스의 clone()메소드는 복제를 의미한다. 따로 설명을 하기가 어려운데..그냥 정말 복제다.

복제를 하는 이유는 무엇일까? 원본 객체를 유지를 하기위한 방패막이라고나 할까?ㅋㅋㅋ

하지만 복재는 두가지 분류로 나뉜다. 얕은복제, 깊은복제

1) 얕은 복제 : 객체의 기본 타입만 복제(참조 타입의경우 객체의 번지가 복사 -> 참조를 바꾸면? 같이 바뀐다..)

2) 깊은 복제 : 객체의 기본 타입, 참조 타입 모두가 바뀐다.

그럼, 이제 코드를 한번 확인을 해보자.

package object;

public class Member implements Cloneable{
	public String id;
	public String name;
	public String password;
	public int age;
	public boolean adult;
	
	public Member(String id, String name, String password, int age, boolean adult){
		this.id = id;
		this.name = name;
		this.password = password;
		this.age = age;
		this.adult = adult;
	}
	
	public Member getMember(){
		Member cloned = null;
		try {
			cloned = (Member)this.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return cloned;
	}
}

Clone()메소드를 작성시에 유의사항은 복사할 객체를 Cloneable 인터페이스를 구현을 해야하고, Clone()메소드에 대한 예외 처리를 해야 한다는 것이다.

getMember()드에서 object클레스의 clone()을 override를 해야하지만 default로는 얕은복제를 하기떄문에 얕은 복제를 먼저 구현을 하고 실험을 해보자.


package object;

public class MemberExample {
	public static void main(String[] args){
		Member original = new Member("blue","홍길동","12345",25,true);
		
		Member cloned = original.getMember();
		original.age = 30;
		
		System.out.println("[원본 객체의 필드값]");
		System.out.println("id: " + original.id);
		System.out.println("name: " + original.name);
		System.out.println("password: " + original.password);
		System.out.println("age: " + original.age);
		System.out.println("adult: " + original.adult);
		
		System.out.println();
		
		System.out.println("[복제 객체의 필드값]");
		System.out.println("id: " + cloned.id);
		System.out.println("name: " + cloned.name);
		System.out.println("password: " + cloned.password);
		System.out.println("age: " + cloned.age);
		System.out.println("adult: " + cloned.adult);
	}
}

결과에서 나왔듯이 원본객체를 바꿔도 복제한 객체에는 바뀌지 않는것을 볼수가 있다.

그런데 여기서 의문을 가지는 개발자가 있을것이다. 처음에는 얕은 복제는 기본타입만 복제가 된다고 했는데 String을 바꿨는데 원본과 복제된 객체의 값이 다르다? 그러면 참조타입도 복제가 되었다는 말아닌가???? ㅡㅡ 여기서.. 여기서 나의대답은!! String은 참조 타입이다. 하지만 String같은경우는 값이 바뀌는 것이 아니라 오히려 그 데이터의 객체를 하나 생성을 한다...

말이 어려울수 있으니..이부분에 대해서는 String 클레스에서 다시한번 설명하도록 하겠다.


package object;

import java.util.Arrays;

public class Member2 implements Cloneable{
	public String name;
	public int age;
	public int[] scores;
	public Car car;
	
	public Member2(String name, int age, int[] scores, Car car) {
		this.name = name;
		this.age = age;
		this.scores = scores;
		this.car = car;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Member2 cloned = (Member2)super.clone();
		cloned.scores = Arrays.copyOf(this.scores, this.scores.length);
		cloned.car = new Car(this.car.model);
		
		return cloned;
	}
	
	public Member2 getMember(){
		Member2 cloned = null;
		try {
			cloned = (Member2)this.clone();
		} catch (CloneNotSupportedException e) {}
		
		return cloned;
	}
}

깊은 복제를 하기위해서는 override를 해야하는데, 일단 기본타입을 위해서 먼저 object클레스의 clone()을 한번 호출을한뒤, 남은 클레스타입과 배열타입을 복사를 한다.

package object;

public class Member2Example {
	public static void main(String[] args){
		Member2 original = new Member2("홍길동",25,new int[]{90,90},new Car("소나타"));
		
		Member2 cloned = original.getMember();
		cloned.scores[0] = 50;
		cloned.scores[1] = 50;
		
		System.out.println("[원본 객체의 필드값]");
		System.out.println("name : " + original.name );
		System.out.println("age : " + original.age );
		for(int i =0;i<original.scores.length;i++){
			System.out.print(original.scores[i] + " ");
		}
		
		System.out.println();
		
		System.out.println("[복제 객체의 필드값]");
		System.out.println("name : " + cloned.name );
		System.out.println("age : " + cloned.age );
		for(int i =0;i<cloned.scores.length;i++){
			System.out.print(cloned.scores[i] + " ");
		}
	}
}


Posted by SH후니
,

Object클레스의 toString()메소드느 객체의 문자 정보를 리턴을 한다. 그리하여 Object 클레스의 toString()메소드를 Override를 하여 사용자의 입맛에 맡게 사용하면된다.


먼저, Object클레스의 toString()메소드이다.

public class ObjectToStringMethod {

	public static void main(String[] args) {
		Object tmp = new Object();
		String output = tmp.toString();
		System.out.println(output);

	}
}

Object클레스의 toString()"클레스명@16진수헤쉬코드" 를 반환을 한다.


다음은 toString()를 Override를 한 클레스와 실행부분이다.



class SmartPhone{
	private String company;
	private String os;
	
	public SmartPhone(String company, String os){
		this.company = company;
		this.os = os;
	}
	
	@Override
	public String toString() {
		return company + " - " + os;
	}
}
public class OverrideToStringMethod {
	public static void main(String[] args) {
		SmartPhone smartPhone = new SmartPhone("구글","안드로이드");
		String output = smartPhone.toString();
		System.out.println(output);
		System.out.println(smartPhone);
	}
}

여기서 System.out.println()메소드의 경우에 객체를 집어 넣으면 어떻게 될까? 매개값으로 객체를 넣을경우,

Override를 통해 구현한 toString()를 호출해서 리턴값을 받아 출력하도록 되어 있다.

Posted by SH후니
,