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

JAVA Basic) Class 클래스

728x90

 

목차

     

     

     


     

    자바의 모든 클래스와 인터페이스는 컴파일되고 나면 class파일로 생성된다.
    예를 들어 a.java 파일이 컴파일 되면 a.class파일이 생성되고. 이 class파일에는 클래스나 인터페이스에 대한 변수, 메서드, 생성자 등 정보가 들어있다.

    'Class 클래스'컴파일 된 class 파일에 저장된 클래스나 인터페이스 정보를 가져오는데 사용된다.

    <Class 클래스란?>

    지금까지 변수를 선언할 때 자료형을 미리 파악하고 그 자료형에 따라 변수를 선언했다. 그리고 클래스를 사용할 때도 이미 그 클래스 정보(메서드, 변수 등)를 알고 있는 상황에서 프로그램을 만들었다.
    그런데 어떤 경우에는 여러 클래스 중에 상황에 따라 다른 클래스를 사용해야 할 때도 있고, 반환받는 클래스가 정확히 어떤 자료형인지 모를 때도 있다.

    이렇게 모르는 클래스의 정보를 사용할 경우 우리가 클래스 정보를 직접 찾아야한다.
    이때 'Class 클래스'를 활용한다.
    Class 클래스를 선언하고 클래스 정보를 가져오는 방법은 세 가지가 있다.

    클래스 정보를 가져오는 세가지 방법

    1. Object 클래스의 getClass() 메서드 사용

    String s = new String();
    Class c = s.getClass(); //getClass() 메서드의 반환형은 Class

    Object에 선언한 getClass() 메서드는 모든 클래스가 사용할 수 있는 메서드. 이 메서드를 사용하려면 생성된 인스턴스가 있어야 한다.

     

    2. 클래스 파일 이름을 Class변수에 직접 대입

    Class c = String.Class;

    컴파일된 클래스 파일이 있다면 클래스 이름만으로 Class 클래스를 반환받는다.
     

    3. Class.forName("클래스 이름")메서드 사용

    Class c = Class.forName("Java.lang.String");

    컴파일된 클래스 파일이 있다면 클래스 이름만으로 Class 클래스를 반환받는다.

    Class 클래스 활용법

    예제를 보고 Class 클래스를 반환받고 활용해보자.

    Person클래스 생성.

    package classEx;
    
    public class Person {
    	private String name;
    	private int age;
    	
    	public Person() {} //디폴트 생성자
    	
    	public Person(String name) { //이름만 입력받는 생성자
    		this.name = name;
    	}
    	public Person(String name, int age) { // 이름, 나이를 입력받는 생성자
    		this.name = name;
    		this.age = age;
    	}
    	
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    }
    • Person 클래스는 생성자가 3개이고, 각 멤버 변수에 get() 메서드와 set() 메서드를 제공한다.
    • 이를 컴파일 하여 Person.class 파일을 생성.

    이제 Person의 Class 클래스를 가져와보자.

    Person의 Class 클래스 가져오기. (ClassTest클래스)

    package classEx;
    
    public class ClassTest {
    	public static void main(String[] args) throws ClassNotFoundException {
    		/* forName() 멤서드에서 발생하는 예외를 처리. 
    		 * 이름과 일치하는 클래스가 없을 경우 ClassNotFoundException 발생 */
    		
    		Person person = new Person();
    		Class pClass1 = person.getClass(); //Object의 getClass()메서드 사용하기
    		System.out.println(pClass1.getName());
    		
    		Class pClass2 = Person.class; //직접 class파일 대입하기
    		System.out.println(pClass2.getName());
    		
    		Class pClass3 = Class.forName("classEx.Person"); //클래스 이름으로 가져오기
    		System.out.println(pClass3.getName());
    	}
    }
    • 'forName() 메서드'를 보면, 클래스 이름(패키지 이름 포함)으로 가져오는 경우에는 매개변수로 쓰이는 값이 문자열이다. 이때 매개변수로 받은 문자열에 해당하는 클래스가 존재하지 않으면 클래스를 가져오는 데 실패한다.
      이때 ClassNotFoundException 오류가 발생한다.
    • Class를 가져온 후 getName() 메서드를 호출하면 클래스 이름인 "classEx.Person"이 잘 출력되는 것을 볼 수 있다.

    'Class 클래스'를 통하여 클래스 정보를 알 수 있다.

    Class 클래스를 활용해 클래스 정보 확인하기

    프로그래밍을 하다 보면 내가 사용할 클래스의 자료형을 모르는 경우도 있다. 예를 들어 내 컴퓨터에 저장되어 있지 않은 객체를 메모리에 로드하고 생성하는 경우 그 객체의 정보를 알 수 없을 것이다. 이때 Class 클래스를 가져올 수 있다면 해당 클래스 정보, 즉 생성자·메서드·멤버 변수 정보를 찾을 수 있다.
    이렇게 사용하려는 클래스 자료형을 모르는 상태에서 Class 클래스를 활용하여 그 클래스 정보를 가져오고, 이 정보를 활용하여 인스턴스를 생성하거나 메서드를 호출하는 방식'리플렉션(reflection)'이라고 한다.

    사실 우리가 자바로 프로그램을 구현할 때 리플렉션 프로그래밍을 해야 하는 경우는 많지 않기 때문에, 이런 방법이 있다는 것 정도만 알아 두면 된다.

    Class 클래스의 몇 가지 메서드를 활용하는 예제를 통해 정보를 어떻게 찾는지 알아보자.
    Class 클래스와 java.lang.reflect 패키지의 클래스를 사용하면 리플렉션 프로그래밍을 할 수 있다.

    String 클래스 정보를 가져오는 방법을 살펴보자.

    package classEx;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    public class StringClassTest {
    
    	public static void main(String[] args) throws ClassNotFoundException {
    		Class strClass = Class.forName("java.lang.String"); //클래스 이름으로 가져오기
    		
    		Constructor[] cons = strClass.getConstructors(); //모든 생성자 가져오기
    		for(Constructor c : cons) {
    			System.out.println(c);
    		}
    		
    		System.out.println();
    		Field[] fields = strClass.getFields(); //모든 멤버 변수(필드) 가져오기
    		for(Field f : fields) {
    			System.out.println(f);
    		}
            
    		System.out.println();
    		Method[] methods = strClass.getMethods(); //모든 메서드 가져오기
    		for(Method m : methods) {
    			System.out.println(m);
    		}
    	}
    }

    String 클래스의 정보(생성자, 필드, 메서드)가 출력된 모습

    • Class 클래스를 가져오기 위해 forName() 메서드를 사용. 이 메서드는 정적 메서드이므로 클래스를 생성하지 않아도 사용이 가능하다. java.lang.String을 가져옴.
    • String 클래스의 모든 생성자를 가져오기 위해 Class 클래스의 getConstructor() 메서드를 호출하고 향상된 for문을 사용해 모든 생성자 정보를 출력.

    이렇듯 Class 클래스와 java.lang.reflect 패키지에 있는 클래스를 활용하면 클래스 이름만 알아도 클래스의 생성자, 필드, 메서드 등의 정보를 알 수 있다. 또한 생성자나 메서드를 직접 호출도 가능하다. (여기서는 다루지 않음)

    newInstrance()를 사용해 클래스 생성하기

    지금까지는 Class 클래스를 사용해 클래스 정보만 확인했다.
    이 정보들을 바탕으로 인스턴스도 생성이 가능한데, Class 클래스의 'newInstance() 메서드'를 사용하면 된다.

    newInstance() 메서드는 항상 Object를 반환하므로 생성된 객체형으로 형 변환을 해야한다. 앞에서 만든 Person 클래스의 인스턴스 Class 클래스와 newInstance() 메서드를 사용해 생성해보자.

    package classEx;
    
    public class NewInstanceTest {
    	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    		
    		Person person1 = new Person(); //생성자로 생성
    		System.out.println(person1);
    		
    		Class pClass = Class.forName("classEx.Person");
    		Person person2 = (Person)pClass.newInstance();
    		//Class 클래스의 newInstance()메서드로 생성.
    		System.out.println(person2);
    	}
    }

    person1과 person2의 인스턴스 주소 값

    코드를 보면 두 가지 방식으로 인스턴스를 생성하고 있다.

    1. 기존에 우리가 하던 방식으로 Person클래스의 변수를 선언하고 생성자를 사용해 생성함.
      (항상 하던 방식)
    2. Class 클래스의 newInstance()를 사용해 인스턴스를 생성함.

    Person 클래스 이름을 사용해 Class 클래스를 반환했다. 그리고 Class 클래스의 newInstance()메서드를 호출하면 Person클래스의 디폴트 생성자가 호출되어 인스턴스가 생성된다.
    newInstance()의 반환 값이 Object이므로 Person 클래스로 다운 캐스팅한 것을 알 수 있다.

    ※ throws InstantiationException, IllegalAccessException는 Class.forName()과 newInstance() 메서드를 사용하는 동안 발생하는 예외 처리에 관한 것이다.

    Class 클래스를 사용하는 방법은 클래스의 자료형을 직접 사용하여 프로그래밍 하는 것보다 더 복잡하고, 예외 처리도 해야한다.
    이미 우리가 자료형을 알고 있는 클래스인 경우 또는 컴파일할 때 직접 참조할 수 있는 클래스는 Class 클래스를 활용할 필요가 없다. 클래스 정보를 알고 있는 상황에서 리플렉션 프로그래밍을 하면 오히려 코드가 복잡해지고 속도도 느려진다.
    따라서 리플렉션 프로그래밍은 컴파일 시점에 알 수 없는 클래스, 즉 프로그램 실행 중에 클래스를 메모리에 로딩하거나 객체가 다른 곳에 위치해서 원격으로 로딩하고 생성할 때 사용한다.

    Class.forName()을 사용해 동적 로딩하기

    대부분의 클래스 정보는 프로그램이 로딩될 때 이미 메모리에 있다.
    그런데 한 가지 경우를 생각해보자. 어떤 회사에서 개발한 시스템이 있는데, 그 시스템은 여러 종류의 DB를 지원한다.
    오라클, MySQL, MS-SQL 등 여러 데이터베이스를 연동할 수 있다. 그렇다고 이 시스템을 컴파일할 때 모든 DB의 라이브러리(드라이버)를 같이 컴파일할 필요는 없다. 시스템을 구동할 때 어떤 DB와 연결하지만 결정된다면 해당 드라이버만 로딩하면 된다. 회사가 사용하는 DB정보는 환경 파일에 읽어 올 수 있고 다른 변수 값으로 받을 수도 있다.

    즉 프로그램 실행 이후 클래스의 로딩이 필요한 경우 클래스의 '동적 로딩(dynamic loading)' 방식을 사용한다.
    자바는 'Class.forName() 메서드'를 동적 로딩으로 제공한다.

    Class pClass = Class.forName("ClassEx.Person");

    forName() 메서드를 살펴보면 매개변수로 문자열을 입력받는다. 이때 입력받는 문자열을 변수로 선언하여 변수 값만 바꾸면 다른 클래스를 로딩할 수 있다.

    앞에서 설명했듯 여러 DB 드라이버 중 필요한 드라이버의 값을 설정 파일에서 읽어 문자열 변수로 저장한다면, 설정 파일을 변경함으로써 필요한 드라이버를 간단하게 로딩한다.

    String className = "classEx.Person"
    Class pClass = Class.forName(ClassName);

    위와 같이 작성하고 className 변수에 다른 문자열을 대입하면 필요에 따라 로딩되는 클래스를 동적으로 변경할 수 있다.

    'forName() 메서드'를 사용할 때 유의할 점

    forName() 메서드를 사용하여 Class 클래스를 가져올 때 가장 유의해야 할 점은 해당 forName("클래스 이름")의 클래스 이름이 문자열 값이므로, 문자열에 오류가 있어도(소·대문자의 구분 등) 컴파일할 때에는 그 오류를 알 수 없다는 것.
    결국 프로그램이 실행되고 메서드가 호출될 때 클래스 이름에 해당하는 클래스가 없다면 ClassNot FoundException이 발생한다. 따라서 동적 로딩 방식은 컴파일할 때 오류를 알 수 없다.

    하지만 앞에서 설명한 것처럼 여러 클래스 중 하나를 선택한다거나, 시스템 연동 중 매개변수로 넘어온 값에 해당하는 클래스가 로딩되고 실행되는 경우에는 동적 로딩 방식을 유연하게 사용할 수 있다.
    동적 로딩을 통해 Class 클래스를 가져올 수 있다면 리플렉션 프로그래밍으로 객체를 생성하고 활용이 가능하다.

     

    300x250