이것이 자바다 정리 #2 클래스

Jake Seo·2021년 4월 22일
0

이것이자바다

목록 보기
2/17

이것이 자바다 정리 #2 클래스

이것이 자바다 책을 참고하였습니다.

객체지향 프로그래밍이란?

부품에 해당하는 객체들을 먼저 만들고, 이것들을 하나씩 조립하여 완성된 프로그램을 만드는 기법을 객체지향 프로그래밍(OOP: Object Oriented Programming)이라고 한다.

객체란?

물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 있고 다른 것과 식별 가능한 것을 말한다.

객체 모델링이란?

현실 세계 객체의 속성과 동작을 추려내어 소프트웨어 객체의 필드와 메소드로 정의하는 과정이다.

객체의 관계란?

객체는 대부분 다른 객체와 관계를 맺고 있다. 관계의 종류는 집합 관계, 사용 관계, 상속 관계가 있다.

집합 관계

부품을 조합해서 완성품이 되는 관계이다.

머리, 팔, 몸통, 다리를 합치면 인체가 된다.

사용 관계

한 객체가 다른 객체와 상호작용하는 것을 말한다.

사람은 라이터를 사용한다.

라이터와 사람은 사용 관계이다.

사람은 필요할 때 라이터를 켠다 등의 메소드를 호출한다.

상속 관계

상위(부모) 객체를 기반으로 하위(자식) 객체를 생성하는 관계를 말한다. 일반적으로 상위 객체는 종류(인터페이스)이며, 하위 객체는 구체적인 사물(구현체)에 해당한다.

객체지향 프로그래밍(OOP)은 만들고자하는 완성품인 객체를 모델링하고, 집합 관계에 있는 부품 객체와 사용 관계에 있는 객체를 하나씩 설계한 후 조립하는 방식으로 프로그램을 개발하는 기법이다.

객체지향 프로그래밍의 특징

  • 캡슐화
  • 상속
  • 다형성

캡슐화

객체의 내부 구조를 알지 못하게하고, 객체가 제공하는 필드와 메소드만 이용할 수 있게 하는 것이다. 외부의 잘못된 사용을 예방한다.

사람의 장기가 밖으로 노출되어 있으면 잘못될 확률이 높다.

상속

상위 객체가 필드와 메소드를 하위 객체에게 물려주는 것을 말한다. 공통적인 부분을 추상화하여 상위 객체로 만들면, 하위 객체에서는 반복된 코드가 줄어든다. 또 코드를 변경 시에도 상위의 코드만 변경하면 하위에 자동으로 적용되어 용이하다.

다형성

같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말한다. 하나의 인터페이스로 다양한 구현체를 만드는 것이 이에 해당한다. 같은 인터페이스 타입이라면 어느 구현체로든 교체가 가능하다.

객체, 클래스, 인스턴스

클래스는 객체의 설계도이며, 생성자로 객체를 생성하면 인스턴스가 된다.

클래스 작성 규칙

  • 하나 이상의 문자로 이루어져야 한다.
  • 첫번째 글자는 숫자가 올 수 없다.
  • $, _ 외의 특수문자를 사용할 수 없다.
  • 자바 키워드는 사용할 수 없다.
    • ex) int, for 등...

한 소스파일(.java)에서 두개 이상의 클래스를 선언하면?

public class Car {

}

class Tire {

}

소스파일은 클래스 선언을 담고 있는 저장단위일 뿐, 클래스 자체가 아니다. 그래서 클래스를 선언한만큼 바이트 코드 파일(.class)이 생긴다. Car.classTire.class가 각각 생긴다.

주의할 점은 파일 이름과 동일한 이름의 클래스에만 public을 붙일 수 있으므로, 소스파일 하나당 동일한 이름의 클래스 하나를 선언하는 것이 좋다.

new 연산자

new 연산자는 힙 영역에 객체를 생성시킨 후 객체의 주소를 리턴한다.

라이브러리 클래스와 실행 클래스

  • 실행 클래스는 main 메소드가 위치한 클래스로 실제 IDE로 실행을 하는 클래스이다.
  • 라이브러리 클래스는 여러 클래스에서 끌어와서 쓰는 클래스를 말한다. API라고도 한다.

대부분의 객체지향 프로그램은 라이브러리와 실행 클래스가 분리되어 있다.

클래스의 구성멤버

  • 필드 (Field)
    • 객체의 데이터를 저장
  • 생성자 (Constructor)
    • 객체 생성 시 초기화 역할 담당
  • 메소드 (Method)
    • 객체의 동작에 해당하는 실행 블록

필드를 클레스 멤버 변수라고도 하는데, 필드라는 명칭을 그대로 쓰는 것이 좋다.

필드 타입별 초기값

  • byte: 0
  • char: \0000 (빈 공백)
  • short: 0
  • int: 0
  • long: 0L
  • float: 0.0F
  • double: 0.0
  • boolean: false
  • 배열,클래스, 인터페이스: null

기본 생성자

  • 클래스 내부에 생성자 선언을 생략하면, 자바는 아무런 내용이 없는 기본 생성자를 바이트 코드에 자동 추가시킨다.
  • 클래스가 public으로 선언되면 기본 생성자에도 public이 붙는다.
  • 클래스가 public이 아니라면, 기본 생성자도 public이 아니다.

정의된 생성자

  • 정의된 생성자가 있으면, 기본 생성자는 사라지기 때문에 정의된 생성자를 호출해야 한다.
    • 생성자가 정의되어 있으면 기본 생성자는 호출할 수 없다.

필드 초기화

  • 필드를 만들면서 고정된 값을 줄 수 있다.
  • 생성자에서도 가능하다.

생성자 오버로딩

  • 매개변수의 타입, 갯수 등이 다른 생성자를 여러개 만드는 것이다.
  • 매개변수의 타입, 갯수, 선언된 순서가 같은 경우 오버로딩으로 볼 수 없다.

생성자에서 다른 생성자 호출

  • 생성자에서 다른 생성자를 호출할 때는 this()를 이용하면 된다.
  • this()는 생성자 첫째 줄에서만 사용 가능하다.
Car(String model) {
  this.model = model;
}

Car(int mileage) {
  this("Hyundai");
  this.mileage = milage
}

메소드 시그니쳐

인터페이스에 작성하는 것과 같은 메소드 선언부를 메소드 시그너처라고도 한다.

메소드 이름

  • 숫자로 시작하면 안된다.
  • $_를 제외한 특수문자 사용 불가능하다.
  • 메소드명은 camelCase로 작성한다.

메소드 매개변수의 수를 모를 경우

  • ...을 쓸 수 있다.
  • 매개변수가 배열 형태로 들어온다.
    public int sum(int ... values) {
        int result = 0;

        for(int value : values) {
            result += value;
        }

        return result;
    }

    @Test
    public void sumTest() {
        System.out.println(sum(1, 2, 3, 4, 5, 6, 7, 8, 9 , 10)); // 55
    }

리턴 값이 없는 경우 (리턴이 void인 경우) return을 쓰면?

  • 메소드 실행을 강제 종료시킨다.

메소드 오버로딩

  • 이름이 같은 메소드인데, 매개변수 타입, 개수, 순서 중 하나라도 다른 것을 만들면 메소드 오버로딩이라 한다.

매개변수나 리턴 타입이 일치하지 않는 경우엔?

  • 자동 타입 변환이 가능한지 검사하고, 가능한 방향으로 동작한다.
  • 자동 타입 변환이 불가능하면 예외가 발생한다.

정적 멤버와 static

정적 멤버의 메모리 영역

정적 필드와 정적 메소드는 클래스에 고정된 멤버이므로, 클래스 로더가 클래스(바이트 코드)를 로딩해서 메소드 메모리 영역에 적재할 때 클래스 별로 관리되므로, 클래스의 로딩이 끝나면 바로 사용할 수 있다.

메소드 영역에 정적 필드정적 메소드가 저장된다.

정적으로 선언하는 가의 기준

  • 객체마다 가지고 있어야 한다면, 인스턴스
  • 모든 객체에서 공통이라면, 정적

수학에서 pi3.14인 것은 공통적인 요소이므로 정적으로 선언할 수 있다.

정적 요소의 접근 관례

  • 인스턴스로도 접근은 가능하지만, 클래스 이름으로 접근하는 것이 맞다.
double pi = Math.pi; // 맞음

Math math = new Math();
double pi = math.pi; // 틀림

정적 초기화 블록

static {
  // TODO: ...
}
  • 클래스 내부에 위와 같은 static 블록을 선언하여 정적 변수 등의 초기화 등이 가능하다.
  • 정적 블록은 클래스가 메모리로 로딩될 때 자동으로 실행된다.

정적 메소드와 블록 선언 시 주의점

  • 정적 메소드 내부나 정적 블록 내부에서는 인스턴스 필드가 사용 불가능하니 유의하자.
  • this 키워드도 사용 불가능하다.

객체가 없어도 실행된다는 특징 때문이다.

싱글톤

  • 싱글톤은 정적 필드를 이용한 패턴이다.
  • 클래스 내부에 private static으로 자신의 타입을 가진 필드를 하나 생성하고, 자신을 인스턴스화한다.
  • 외부에서 접근할 때는 정적 메소드인 .getInstance() 메소드를 이용한다.
  • 생성자를 이용해서 생성할 수 없게 private으로 막아둬야 한다.
  • 프로그램에서 오직 하나만 존재하는 객체임이 보장된다.
public class Singleton {
  private Singleton() {
  
  }

  private static Singleton singleton = new Singleton();
  
  public static Singleton getInstance() {
    return singleton;
  }
}

외부에서는 아래와 같은 패턴으로 이용한다.

public static void main(String args[]) {
  Singleton singleton = Singleton.getInstance();
}

final 필드와 상수

final 필드

  • final 필드는 초기 값이 저장되면 프로그램 실행 도중에 수정이 불가능하다.
  • 초기 값은 선언 시 혹은 생성자에서 줄 수 있다.
    • 생성자에서 까지도 값을 결정하지 않으면 컴파일 에러가 난다.

상수 (static final)

  • final 필드는 객체마다 저장되지만, 상수는 객체마다 저장될 필요성이 없는 공통적인 값이다. 그래서 staticfinal을 동시에 써서 정의한다.
  • static final 필드는 클래스에만 포함되며, 초기값이 저장되면 변경할 수 없다.
  • static final 필드의 초기화 과정이 복잡하다면 static 블록에서 초기화해준다.
  • 네이밍은 모두 대문자로 하며 단어마다 _로 구분한다.
static final double PI = 3.14159;

패키지

  • 패키지는 단순히 파일 시스템의 폴더 기능만 하는 것이 아니라 클래스의 일부다.
  • 패키지는 클래스를 유일하게 만들어주는 식별자 역할을 한다.
  • 클래스 이름이 동일하더라도 패키지가 다르면 다른 클래스로 인식한다.

패키지 네이밍 규칙

  • 숫자로 시작하면 안되고, _, $를 제외한 특수문자를 사용해선 안된다.
  • java로 시작하는 패키지는 자바 표준 API에서만 사용하므로 사용해선 안된다.
  • 모두 소문자로 작성한다.

회사 도메인 이름으로 만드는 패키지

  • 여러 회사가 함께 참여하는 프로젝트에서 회사의 소스코드가 섞일 수 있기 때문에 시작됐다.
    • 회사간 패키지를 구분하려고 도메인 이름을 기준으로 패키지를 만든다.
  • 도메인 이름을 역순으로 패키지명을 짓는데, 공통적인 이름이 상위 패키지가 되도록 하기 위해서이다.
  • 마지막에는 프로젝트 이름을 붙여서 마무리한다.
com.samsung.projectname
com.hyundai.projectname
com.lg.projectname
org.apache.projectname

접근 제한자

접근 제한자의 종류

  • public
    • 적용 대상
      • 클래스
      • 필드
      • 생성자
      • 메소드
    • 접근할 수 없는 클래스
      • 없음
  • protected
    • 적용 대상
      • 필드
      • 생성자
      • 메소드
    • 접근할 수 없는 클래스
      • 자식클래스가 아닌 다른 패키지에 소속된 클래스
  • default
    • 적용 대상
      • 클래스
      • 필드
      • 생성자
      • 메소드
    • 접근할 수 없는 클래스
      • 다른 패키지에 소속된 클래스
  • private
    • 적용 대상
      • 필드
      • 생성자
      • 메소드
    • 접근할 수 없는 클래스
      • 모든 외부 클래스

클래스는 public 이거나 default이거나 둘 중 하나이다. 그래서 나머지 접근 제어자가 사용 불가능하다.

Getter와 Setter 메소드

  • 캡슐화의 일원으로 객체 필드의 값을 직접 수정하거나 접근하지 못하게 private으로 막아놓고, GetterSetter만을 이용하도록 하는 것이다.
    • Setter에는 특정 범위를 지정하는 if문 등을 응용해 여러가지 응용이 가능하다.

네이밍 규칙

  • setFieldName, getFieldName과 같이 기본적으로 앞에 set혹은 get등을 붙이고 camelCase 네이밍 규칙을 쓴다.
  • 단, 필드의 타입이 booleanGetter의 경우에는 관례적으로 앞에 get대신 is를 붙인다.

애노테이션

  • 애노테이션은 메타데이터이다.
    • 애플리케이션이 처리하는 데이터가 아닌 컴파일 과정과 실행 과정에서 코드를 어떻게 컴파일하고 처리할 것인지를 알려주는 정보이다.

애노테이션의 용도

  • 컴파일러에게 코드 문법 에러를 체크하도록 정보 제공
  • 소프트웨어 개발 툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 정보 제공
  • 실행 시 특정 기능을 실행하도록 정보 제공

대표적 애노테이션으로 @Override가 있다. @Override는 메소드가 오버라이드 됐음을 알려주며, 컴파일러가 오버라이드 검사를 하고 정확히 오버라이드가 되지 않았다면 에러를 발생시킨다.

애노테이션은 빌드 시 자동으로 XML 설정 파일을 생성하거나, 배포를 위한 JAR 압축 파일을 생성하는데도 사용된다.

애노테이션은 클래스의 역할을 정의하기도 한다. (스프링에서 @Component, @SpringBootApplication과 같은 것들..)

애노테이션의 타입 정의와 적용

  • 애노테이션의 타입 정의는 인터페이스 정의와 유사하다.
public @interface AnnotationName {
  ...
}
  • 정의된 애노테이션은 @AnnotationName의 형태로 이용하면 된다.

애노테이션의 엘리먼트(element)

  • 엘리먼트는 애노테이션의 멤버이다.
  • 엘리먼트는 타입과 이름으로 구성되어 있으며, 기본 값을 가질 수 있다.
  • 타입은 어떤 기본 데이터타입이나 참조타입 등이 가능하다.
  • 엘리먼트의 이름 뒤에는 메소드처럼 ()를 붙여야 한다.
public @interface AnnotationName {
  타입 elementName() {default}; // 엘리먼트 선언
}
public @interface AnnotationName {
  String elementName1();
  int elementName2() default 5;
}
@AnnotationName(elementName1="안녕하세요", elementName2=10);

애노테이션 기본 엘리먼트 value

애노테이션의 엘리먼트 이름이 value인 경우에는 애노테이션 속성에 간단히 값만 입력해도 된다.

public @interface AnnotationName {
  String value();
  int elementName() default 5;
}
@AnnotationName("안녕하세요");

애노테이션 적용 대상

java.lang.annotation.ElementType에 열거 상수로 정의되어 있다.

각 열거 상수와 적용 대상은 아래와 같다.

  • TYPE: 클래스, 인터페이스, 열거 타입
  • ANNOTATION_TYPE: 애노테이션
  • FIELD: 필드
  • CONSTRUCTOR: 생성자
  • METHOD: 메소드
  • LOCAL_VARIABLE: 로컬 변수
  • PACKAGE: 패키지

@Target으로 애노테이션 적용 대상 설정하기

@Target 애노테이션의 기본 엘리먼트인 value()ElementType 타입을 배열로 가진다.

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface Annotation {

}

위의 예제의 경우 클래스, 인터페이스, 열거 타입, 필드, 메소드에 애노테이션 적용이 가능하다.

애노테이션 유지 정책

애노테이션을 용도에 따라 어디까지 유지할 것인지 정할 수 있다. 애노테이션에 @Retnetion 애노테이션을 붙임으로써 지정할 수 있다. @Retention의 기본 엘리먼트인 valueRetentionPolicy 타입이므로 아래 세가지 상수 중 하나를 지정하면 된다.

java.lang.annotation.RetentionPolicy에 유지정책이 열거 상수로 정의되어있다.

  • SOURCE: 소스상에만 유지한다. 바이트코드 파일에는 정보가 남지 않는다.
  • CLASS: 컴파일된 클래스(바이트코드 파일)까지 유지한다. 리플렉션을 이용해서 애노테이션 정보를 얻을 수는 없다.
  • RUNTIME: 컴파일된 클래스 이후 런타임시에도 유지한다. 리플렉션을 이용해서 런타임에 애노테이션 정보도 얻을 수 있다.

위에서 언급되는 용어인 '리플렉션'이란 클래스의 메타정보를 얻는 기능이다. 클래스의 필드, 생성자, 메소드, 적용된 애노테이션이 무엇인지를 알아내는 것이다. 리플렉션을 이용해 런타임에 애노테이션의 정보를 얻으려면 정책을 RUNTIME으로 설정해야 한다.

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationName{

}

런타임 시 애노테이션 정보 사용하기

런타임 시에 애노테이션이 적용되었는지 확인하고 엘리먼트의 값을 이용해서 특정 작업을 수행할 수 있다. 애노테이션 자체는 단순 메타데이터지만, 리플렉션을 이용하면 애노테이션의 적용 여부, 엘리먼트 값을 읽고 처리가 가능하다.

클래스에 적용된 애노테이션 정보를 얻으려면 java.lang.Class를 이용하면 되지만, 필드, 생성자, 메소드에 적용된 애노테이션 정보를 얻으려면 Class의 다음 메소드를 통해 java.lang.reflect 패키지의 Field, Constructor, Method 타입의 배열을 얻어야 한다.

  • getFields()
    • 리턴 타입: Field[]
    • 필드 정보를 Field 배열로 리턴
  • getConstructors()
    • 리턴 타입: Constructor[]
    • 생성자 정보를 Constructor 배열로 리턴
  • getDeclaredMethods()
    • 리턴 타입: Method[]
    • 메소드 정보를 Method 배열로 리턴

위의 메소드를 통해 Class, Field, Constructor, Method가 가지고 있는 다음 메소드를 호출해서 적용된 애노테이션 정보를 얻을 수 있다.

  • isAnnotationPresent(Class<? extends Annotation> annotationClass)
    • 리턴 타입: boolean
    • 지정한 애노테이션이 적용되었는지 여부이다. Class에서 호출했을 때, 상위 클래스에 적용된 경우에도 true를 리턴한다.
  • getAnnotation(Class<T> annotationClass)
    • 리턴 타입: Annotation
    • 지정한 애노테이션이 적용되어 있으면 애노테이션을 리턴하고, 그렇지 않다면 null을 리턴한다. Class에서 호출했을 때 상위 클래스에 적용된 경우에도 애노테이션을 리턴한다.
  • getAnnotations()
    • 리턴 타입: Annotation[]
    • 적용된 모든 애노테이션을 리턴한다. Class에서 호출했을 때 상위 클래스에서 적용된 애노테이션도 모두 포함한다. 적용된 애노테이션이 없을 경우 길이가 0인 배열을 리턴한다.
  • getDeclaredAnnotations()
    • 리턴 타입: Annotation[]
    • 직접 적용된 모든 애노테이션을 리턴한다. Class에서 호출했을 때, 상위 클래스에 적용된 애노테이션은 포함되지 않는다.

애노테이션 예제 실행해보기

@PrintAnnotation 애노테이션

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PrintAnnotation {
    String value() default "-";
    int number() default 15;
}

애노테이션은 단순히 메타데이터로 어떠한 정보들을 갖고 있다.

위의 애노테이션의 타겟은 ElementType.Method라 메소드에 적용되며,
애노테이션의 유지기한은 RetentionPolicy.RUNTIME이므로 런타임까지 유지된다.

기본 엘리멘트인 value()에는 "-"가 기본 값으로 들어있다.
number() 엘리먼트에는 15가 기본 값으로 들어있다.

Service 클래스

public class Service {
    @PrintAnnotation
    public void method1() {
        System.out.println("실행 내용1");
    }

    @PrintAnnotation("*")
    public void method2() {
        System.out.println("실행 내용2");
    }

    @PrintAnnotation(value = "#", number = 20)
    public void method3() {
        System.out.println("실행 내용3");
    }
}

모든 메소드에 @PrintAnnotation을 주었고, 엘리먼트에 대한 값들은 각각 조금씩 다르게 주었다.

PrintAnnotationExample 클래스

public class PrintAnnotationExample {
    public static void main(String[] args) {
        Method[] declaredMethods = Service.class.getDeclaredMethods();

        // 메소드를 하나씩 처리
        for (Method method : declaredMethods) {
            // PrintAnnotation이 적용되었는지 확인
            if(method.isAnnotationPresent(PrintAnnotation.class)) {
                // PrintAnnotation 객체 얻기
                PrintAnnotation printAnnotation = method.getAnnotation(PrintAnnotation.class);

                // 메소드 이름 출력
                System.out.println("method = " + method.getName());

                // 구분선 출력
                for (int i = 0; i < printAnnotation.number(); i++) {
                    System.out.print(printAnnotation.value());
                }

                System.out.println();

                try {
                    // 메소드 호출
                    method.invoke(new Service());
                } catch (Exception e) {

                }

                System.out.println();
            }
        }
    }
}
  • @PrintAnnotation의 메타데이터를 이용해 처리되는 메인 메소드를 가지고 있다.
  • .class에서 getDeclaredMethods() 메소드로 상속되지 않고 해당 클래스에 직접 적용된 애노테이션을 불러온다.
  • @PrintAnnotation이 적용되었는지 확인 후에 해당 애노테이션을 method.getAnnotation() 메소드를 통해 가져온다.
  • 애노테이션 적용을 확인 후에 애노테이션의 메타데이터를 이용한 출력을 한다.
  • 이후 method.invoke() 메소드를 이용해 해당 메소드를 호출한다.
profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글