오늘 한 일

  • java 공부
    11일 java 공부에 이어서…
    • 기본 클래스
      • String 클래스
        • String을 선언하는 두 가지 방법
          String str1 = new String("abc");  // 생성자의 매개변수로 문자열 생성
          String str2 = "test";  // 문자열 상수를 가리키는 방식
          String str3 = "test";
          

          string_init

          상수 풀의 문자열을 참조하면 모든 문자열이 같은 주소를 가리킨다.

        • String 클래스로 문자열 연결
          String 클래스는 내부에서 문자열을 final byte 배열로 받기 때문에, 한번 생성된 String 문자열은 불변(immutable)하다. 따라서 두 개의 문자열을 연결하는 메서드는 새로운 인스턴스를 생성한다.
          문자열 연결을 계속하면 메모리에 garbage가 많이 생길 수 있으므로 주의가 필요하다.
          String javaStr = new String("java");
          String androidStr = new String("android");
          
          javaStr = javaStr.concat(androidStr);
          

          string_concat

        • StringBuilder, StringBuffer 사용하기
          내부적으로 가변적인 char 배열을 가지고 있는 클래스이다. 따라서 문자열을 여러 번 연결하거나 변경할 때 사용하면 유용하다. 매번 새로 생성하지 않고 기존 배열을 변경하므로 garbage가 생기지 않는다.

          단일 쓰레드 프로그래밍에서는 StringBuilder를 사용하기를 권장한다. StringBuilder와는 달리 StringBuffer는 멀티 쓰레드 프로그래밍에서 동기화(Synchronization)를 보장한다. 동기화를 보장한다는 것은 쓰레드 간의 순서를 보장한다는 뜻이다.

          StringBuilder나 StringBuffer는 String 클래스가 아니므로, String 클래스로 사용하려면 toString() 메서드를 사용해 string을 반환받으면 된다.

      • Wrapper 클래스
        • 기본 자료형(primitive data type)에 대한 클래스

          기본형 Wrapper 클래스
          boolean Boolean
          byte Byte
          char Character
          short Short
          int Integer
          long Long
          float Float
          double Double
        • 오토 박싱(auto-boxing)과 언박싱(unboxing)
          Integer는 객체이고 int는 4 byte 기본 자료형이므로 두 자료형의 데이터를 같이 연산할 때 자동으로 변환이 이뤄진다.

          Integer num1 = new Integer(100);
          int num2 = 200;
          int sum = num1 + num2;  // num1.intValue()로 변환 (unboxing)
          Integer num3 = num2;  // Integer.valueOf(num2)로 변환 (auto-boxing)
          
      • Class 클래스
        • 자바의 모든 클래스와 인터페이스는 컴파일 후 class 파일로 생성된다. class 파일에는 객체의 정보(멤버변수, 메서드, 생성자 등)가 포함되어 있는데, Class 클래스는 컴파일된 class 파일에서 객체의 정보를 가져올 수 있다.

          프로그래밍을 하다 보면 내가 사용할 클래스의 자료형을 모르는 경우가 있을 수 있다. 예를 들어 내 컴퓨터에 저장되어 있지 않은 객체를 메모리에 로드하고 생성하는 경우 그 객체의 정보를 알 수 없는데, 이 때 Class 클래스를 가져올 수 있으면 해당 클래스의 정보를 찾을 수 있다. 이렇게 사용하려는 클래스의 자료형을 모르는 상태에서 Class 클래스를 활용해 클래스 정보를 가져오고, 이를 통해 인스턴스를 생성하거나 메서드를 호출하는 방식을 리플렉션(reflection)이라 한다. Class 클래스와 java.lang.reflect 패키지 내의 Constructor, Method, Field 등을 활용해 프로그래밍한다.

        • Clas 클래스 가져오기
          1. Object 클래스의 getClass() 메서드 사용하기
              String s = new String();
              Class c = s.getClass();  // getClass() 메서드의 반환형은 Class
            
          2. 클래스 파일 이름을 Class 변수에 직접 대입하기
              Class c = String.Class;
            
          3. Class.forName(“클래스_이름”) 메서드 사용하기
              Class c = Class.forName("java.lang.String");  // 동적 로딩
            

          1번과 2번의 방법은 이미 컴파일되어 있어야 쓸 수 있는 방법이다(정적 로딩, Static Loading). 반면 3번은 런타임에 문자열에 해당하는 클래스가 존재하면 메모리에 적재한다(동적 로딩, Dynamic Loading).

        • Class.forName() 메서드로 동적 로딩하기
          동적 로딩은 컴파일 시에 테이터 타입이 모두 binding되어 자료형이 로딩되는 것(static loading)이 아니라, 실행 중에 데이터 타입을 알고 binding되는 방식이다.

          Class.forName() 메서드는 Class에서 제공하는 static 메서드이다. 이를 이용해 프로그래밍 할 때 어떤 클래스를 사용할지 모를 경우, 당장은 변수로 처리하고 후에 실행될 때(runtime) 해당 변수에 대입된 값의 클래스가 실행되도록 할 수 있다. 실행 시에 로딩되므로 경우에 따라 다른 클래스가 사용될 수 있어 유용하다. 다만, 컴파일 타임에 체크할 수 없으므로 해당 문자열에 대한 클래스가 없는 경우에 예외(ClassNotFoundException)이 발생할 수 있다.

          String className = "classex.Person";
          Class pClass = Class.forName(className);
          
    • 제네릭 프로그래밍
      • 제네릭 프로그래밍은 변수의 선언이나 메서드의 매개변수를 하나의 참조 자료형이 아니라 여러 자료형으로 변환될 수 있도록 프로그래밍 하는 방식이다. 따라서 해당 클래스를 사용할 때 어떤 참조 자료형을 쓸지 정해서 사용할 수 있다. 실제 사용되는 참조 자료형으로의 변환은 컴파일러가 검증하므로 안정적이다. 선언시에 자료형이 정해지는 것은 아니고, 처음에는 Object 형으로 선언된 뒤 후에 컴파일러가 형변환 시켜준다.

        제네릭 프로그래밍 컬렉션 프레임워크에서 많이 사용되고 있다.

      • 제네릭 클래스 정의하기
        여러 참조 자료형으로 대체될 수 있는 부분을 하나의 문자로 표현한다. 이 문자를 자료형 매개변수라고 한다.
        public class GenericPrinter<T> {
          private T material;
        
          public void setMaterial(T material) {
            this.material = material;
          }
        
          public T getMaterial() {
            return material;
          }
        
          public static void main(String[] args) {
            GenericPrinter<Powder> printer = new GenericPrinter<Powder>();
          }
        }
        

        만약 T에 들어올 수 있는 자료형에 제한을 두고 싶다면 어떻게 할까? 예를 들어 위 예제의 프린터에서 재료는 Plastic과 Powder만 사용 가능하다고 하자. 이 때는 새로운 클래스를 선언한 뒤 이 클래스를 상속받는 클래스만 오게 하면 된다.

        public class GenericPrinter<T extends Material> {
          // 생략
        }
        

        이 때 상속하는 조상 [추상] 클래스에 [추상] 메서드를 작성하면 제네릭 클래스의 변수에서 해당 메서드를 사용할 수 있게된다. (원래는 Object의 메서드만 사용가능함)

      • 자료형 매개변수 T
        type의 의미로 T를 많이 사용한다. <T>에서 <>는 다이아몬드 연산자라고 한다. static 키워드는 T에 사용할 수 없다.

        다이아몬드 연산자 내부에서 자료형은 생략 가능하다.

        ArrayList<String> list = new ArrayList<>();
        

        자바 10부터는 제네릭에서 자료형 추론을 사용할 수 있다.

        ArrayList<String> list = new ArrayList<String>();
        => var list = new ArrayList<String>();
        
      • 제네릭 클래스 사용하기
        T로 정의한 부분에 사용할 참조 자료형을 넣어서 클래스를 생성한다. getMaterial() 메서드가 호출될 때 강제 형변환을 하지 않아도 된다.
        GenericPrinter<Powder> powderPrinter = new GenericPrinter<Powder>();
        powderPrinter.setMaterial(new Powder());
        Powder powder = powderPrinter.getMaterial();  // 명시적인 형변환을 하지 않음
        
        용어 설명
        GenericPrinter<Powder> 제네릭 자료형(Generic type), 매개변수화된 자료형(parameterized type)
        Powder 대입된 자료형
      • 제네릭에서 대입된 자료형을 명시하지 않는 경우
        GenericPrinter<Powder>와 같이 사용할 자료형(Powder)을 명시해야 하는데, 이를 명시하지 않고 사용하는 경우가 있다. 이 때는 경고 표시가 나타나고, 필요시 강제 형변환 코드를 작성해주어야한다(Object 클래스이므로).

      • 제네릭 메서드 활용하기
        메서드의 매개변수를 자료형 매개변수로 사용하는 제네릭 메서드를 생성할 수 있다.

        제네릭 메서드의 일반 형식
        public <자료형_매개변수> 반환형 메서드_이름(자료형_매개변수, …) {}

        public class GenericMethod {
          public static <T, V> double makeRectangle(Point<T, V> p1, Point<T, V> p2) {
        
          }
        }
        

참조