Java 중급 1

j0yy00n0·2025년 3월 4일

2025.03.04 ~ 03.05

클래스, 객체

클래스

서로 다른 타입의 데이터와 메소드를 정의하여 사용자 정의의 타입을 만든 것.
-> 사용자 정의의 자료형

클래스 정의

접근제어자 class 클래스명{
자료형 변수; // 이 부분은 필드라고 함 ( 필드 변수 )
자료형 변수;
}

public class Member{
자료형 변수;
}
Member member = new Member(); // member 는 레퍼런스변수명

필드에 접근하기 위해서는 레퍼런스변수명.필드명 으로 접근

캡슐화

클래스에 간접적으로 접근하여 사용할 수 있도록 클래스를 작성하는 기법

  • 유지보수성 증가 = 낮은 결합도
  • 필드의 직접 접근 제한
    무생물이나 개념 같은 존재들도 하나의 주체로 본인의 스스로를 제어하고 행동한다.
    의인화 기법 -> 캡슐화

필드에 직접 접근 시 발생하는 문제점

  • 필드에 올바르지 않는 값이 들어가도 통제가 불가능
  • 필드의 이름이나 자료형을 변경할 때 사용하는 쪽에도 수정 해야 한다.
    -> 캡슐화로 문제점 해결!

캡슐화로 문제점 해결!

  • 필드에 직접 접근하지 못하도록 private 접근 제어자 사용
  • public 메소드를 이용하여 간접적으로 접근하도록 사용
  • 메소드 내에 값을 검증하는 로직 추가

this는 인스턴스(객체)가 생성되었을 때 자신의 주소를 저장하는 레퍼런스 변수
-> 지역변수와 전역변수의 이름이 동일한 경우 지역변수를 우선적으로 접근하기 때문에 전역변수에 접근하기 위해서 this.을 명시해야 한다.

접근제한자

  • public : 모든 패키지에 접근 허용.
  • protected : 동일 패키지에 접근 허용. 단, 상속관계에 있는 경우 다른 패키지에서도 접근 가능
  • default : 동일 패키지에서만 접근 허용. (작성하지 않는 것이 default)
  • private : 해당 클래스 내부에서만 접근 허용.

위의 네 가지 접근제한자는 클래스의 멤버(필드, 메소드)에 모두 사용 가능하다.
단, 클래스 선언 시 사용하는 접근제한자는 public과 default만 사용 가능하다.

추상화

유연성을 확보하기 위해 공통적인 것을 추출하고 공통적이지 않은 것을 제거하는 것

  • 객체(Object)가 도출이 되며, 이러한 객체를 생성하기 위해 클래스를 설계하게 된다.

객체간의 상호작용

객체와 객체는 메세지(메소드 호출)을 통해 서로 상호작용을 한다.
-> 그 메세지에 해당하는 내용을 처리하는 방법을 스스로 결정한다.
메소드(method) : 스스로 결정한 방식대로 명령어를 순차적으로 기술한 것
-> 필드보다 메소드를 중점적으로 추상화 기법을 적용하여 객체를 설계하는 것이 중요

  • DTO class : 데이터를 중심으로 추상화 하여 객체 및 클래스를 설계한 경우

DTO(Data Transfer Object) class

행위 위주가 아닌 데이터를 하나로 뭉치기 위한 객체

  • 모든 필드를 private로 직접 접근을 막고, 각 필드값을 변경하거나 반환하는 메소드를 세트로 미리 작성해둔다.
  • 설정자와 접근자들로 구성
    - 설정자(setter) : private 필드 / 필드값을 수정
    - 접근자(getter) : 필드에 접근하는 접근자
  • 주로 계층간 데이터를 주고 받을 목적으로 사용
  • 필드명을 그대로 사용해서 캡슐화의 의미가 별로 없지만 데이터를 주로 다루는 객체의 경우 행위를 추상화하지 않고 미리 모든 필드에 접근 가능성을 염두해두고 작성해두는 관례로 인해 현재도 많이 사용되고 있다. (엄밀히 말하자면 EJB의 java bean 작성 규칙에 따르는 것이다.)
  • DTO 필드는 다 작성해야 되는데 Alt + Insert 누르고 필요한 필드를 설정하면 된다.

설정자(setter) 작성 규칙

필드값을 변경할 목적의 매개변수를 변경하려는 필드와 같은 자료형으로 선언하고 호출 당시 전달되는 매개변수의 값을 이용하여 필드의 값을 변경한다.

public void set필드명(매개변수) {
필드 = 매개변수;
}
public void setName(String name) {
this.name = name;
}

접근자(getter) 작성 규칙

각 접근자는 하나의 필드에만 접근하도록 한다.
필드에 접근해서 기록된 값을 return을 이용하여 반환하며, 이 때 반환타입은 반환하려는 값의 자료형과 일치시킨다.

public 반환형 get필드명() {
return 반환값;
}
public void getName() {
return this.name;
}
public boolean isActivated() {
return isActivated;
}

boolean의 접근자는 get으로 시작하지 않고 is로 시작하는 것이 일반적인 관례이다.

생성자(constructor)

인스턴스를 생성할 때 초기 수행할 명령이 있는 경우 미리 작성해두고, 인스턴스를 생성할 때 단 한 번 호출되는 함수

  • 인스턴스 생성 시 클래스명 레퍼런스변수 = new 클래스명(); 이렇게 사용
  • 이때 new 뒤에 클래스명()은 생성자라는 메소드를 호출하는 구문
  • 기본 생성자(default constructor) : 생성자 함수에 매개변수가 없는 생성자
    - 기본 생성자는 compiler에 의해 자동으로 추가되기 때문에 지금까지 명시적으로 작성하지 않고 사용할 수 있었다.
  • 매개변수있는 생성자 : 주로 인스턴스 생성 시점에 필드의 초기값을 사용자가 원하는대로 설정하고 싶은 경우 생성자의 호출 시 인자로 값을 전달하여 호출 -> 이러한 인자를 받아 필드를 초기화 할 목적의 생성자

생성자 작성 위치

  • 문법상으로는 클래스 내부에 작성
  • 통상적으로 필드 선언부와 메소드 선언부 사이에 작성하는 것이 관례

생성자의 사용 목적

  • 인스턴스(객체) 생성 시점에 수행할 명령이 있는 경우 사용한다.
  • 매개변수 있는 생성자의 경우 매개변수로 전달받은 값으로 필드를 초기화하며 인스턴스를 생성할 목적으로 주로 사용된다.
  • 작성한 생성자 외에는 인스턴스를 생성하는 방법을 제공하지 않는다는 의미를 가진다.
    ->따라서, 인스턴스를 생성하는 방법을 제한하기 위한 용도로 사용할 수 도 있다. (초기값 전달 강제화)

생성자 작성 시 주의할 점

  • 생성자 메소드는 반드시 클래스의 이름과 동일해야 한다. (대/소문자까지 같아야 함)
  • 생성자 메소드는 반환형을 작성하지 않는다. (작성하는 경우 생성자가 아닌 메소드로 인식한다.)
  • 동일한 이름의 생성자 / 메소드를 한 클래스 안에서 작성하는 것은 불가능하다.
  • 하지만, 매개변수 선언부에 작성한 매개변수의 타입, 갯수, 순서를 다르게 하면 동일한 생성자 / 메소드를 한 클래스 내에 여러개 작성할 수 있다.(오버로딩)

    접근제한자 클래스명(매개변수) {
    인스턴스 생성 시점에 수행할 명령 기술 (주로 필드를 초기화)
    this.필드명 = 매개변수; //설정자(setter) 여러 개의 기능을 한 번의 호출로 수행할 수 있다.
    }

this() 사용하기

동일 클래스 내에 작성한 다른 생성자 메소드를 호출하는 구문

  • 괄호 안에 매개변수의 타입, 갯수, 순서에 맞는 생성자를 호출하고 복귀한다. (메소드와 동일함)
  • 리턴되어 돌아오지만 리턴값은 존재하지 않는다.
  • this()는 가장 첫 줄에 선언해야 하며, 그렇지 않은 경우 Compile Error가 발생한다.

생성자, 설정자 사용 시 장단점

생성자를 이용한 초기화

장점

  • 단 한번의 호출로 인스턴스를 생성 및 초기화 할 수 있다.

단점

  • 필드를 초기화할 매개변수의 갯수를 경우의 수 별로 모두 만들어둬야 한다.
  • 생성자 호출 시 인자가 많아지는 경우 어떠한 값이 어떤 필드를 의미하는지 한 눈으로 보기 힘들다.(객체 생성과 생성자 호출은 동시에 이뤄진다)
  • 예) 아이디, 비밀번호, 이름이 모두 ohgiraffers인 경우
    new User("ohgiraffers", "ohgiraffers", "ohgiraffers"); 몇 번째 인자가 아이디인지 이름인지 알 수 없다.

설정자를 이용한 초기화

장점

  • 필드를 초기화하는 각각의 값들이 어떤 필드를 초기화하는지 명확하게 볼 수 있다.
  • 예) 아이디, 비밀번호, 이름이 모두 ohgiraffers인 경우
    User user = new User();
    user.setId("ohgiraffers");
    user.setPwd("ohgiraffers");
    user.setName("ohgiraffers");
  • 호출하는 코드만 봐도 어떤 값이 어떤 필드를 초기화하는 내용인지 쉽게 알 수 있다.

단점

  • 하나의 인스턴스를 생성할 때 한 번의 호출로 끝나지 않는다.(상황에 따라 interrupt 등 여러 가지 문제가 발생할 수 있음)

오버로딩(overloading)

동일한 클래스 내에는 동일한 이름의 생성자 / 메소드를 작성하지 못한다.
하지만 매개변수의 타입, 갯수, 순서를 다르게 작성하면 서로 다른 생성자 / 메소드로 인식하기 때문에 동일한 이름의 생성자 / 메소드를 여러개 작성할 수 있게 해줌.

오버로딩 사용 이유

  • 매개변수의 종류별로 메소드 내용을 다르게 작성해야 하는 경우들이 종종 있는데 동일한 이름으로 다양한 종류의 매개변수에 따라 처리해야 하는 여러 메소드를 동일한 이름으로 관리하기 위해

메소드의 시그니쳐

public void method(int num) {} 이라는 메소드의 메소드명과 파라미터 선언부 부분을 시그니쳐라고 부른다.
method(int num) 이 부분이 시그니처
-> 메소드명은 동일 해야 하고 매개변수 부분이 다르게 설정하면 오버로딩된다.

메소드의 파라미터로 사용 가능한 자료형

  • 기본자료형
  • 기본자료형 배열
  • 클래스자료형
  • 클래스자료형 배열
  • 가변인자

static 키워드

정적 메모리 영역에 프로그램이 start될 시 할당 하고자 할 때 사용하는 키워드

  • static 필드(변수들이 작성되는 곳) / 메소드는 인스턴스 생성 없이 클래스명.(필드나 메소드)을 통해 접근해서 사용 가능
  • 여러 인스턴스가 공유해서 사용할 목적인 속성 / 필드의 예약어 자리에 추가
  • 객체를 생성할 때 마다 heap 영역에 개별적으로 저장된다. static은 정적메모리 영역에 객체를 생성하지 않고 프로그램이 시작될 때 생성되어 class 단위로 공통적으로 사용할 수 있도록 값이 고정 되어 사용하기 때문에 인스턴스를 생성할 필요가 없음
  • 하지만 static 키워드의 남발은 유지보수와 추적이 힘든 코드를 작성하는 피해야할 방식 -> 명확한 목적이 존재하지 않는 이상 static 키워드 사용은 자제
  • 의도적으로 static 키워드를 사용하는 대표적인 예 : singleton 객체를 생성

singleton

애플리케이션이 시작될 때 어떤 클래스가 최초 한 번만 메모리를 할당하고 그 메모리에 인스턴스를 만들어서 하나의 인스턴스를 공유해서 사용하며 메모리 낭비를 방지할 수 있게 함 (매번 인스턴스 생성 하지 않음)
생성자를 이용하여 직접 인스턴스 생성을 하지 못하고 getInstance() 메소드를 호출해야만 인스턴스를 생성할 수 있다. *
장점

  • 첫 번째 이용 시에는 인스턴스를 생성해야 하므로 속도 차이가 나지 않지만 두 번째 이용 시에는 인스턴스를 재사용하므로 객체 생성 시간 없이 사용할 수 있다.
  • 인스턴스가 절대적으로 한 개만 존재하는 것을 보증할 수 있다. -> 애플리케이션 전역에서 동일한 데이터를 공유할 수 있다.

단점

  • 싱글톤 인스턴스가 너무 많은 일을 하거나 많은 데이터를 공유하면 결합도가 높아진다.(유지보수와 테스트에 문제점이 있음)
  • 동시성 문제 : 멀티스레드 환경에서 동기화 처리를 하지 않으면 인스턴스가 여러 개 생성될 가능성이 있음.
  • 싱글톤은 상태를 유지하므로, 단위 테스트 시 객체를 초기화하기 어렵다.

이른 초기화 (Eager Initialization)

클래스가 초기화 되는 시점에서 인스턴스를 생성한다.
첫 번째 getInstance() 호출 시 딜레이 없음.
static 필드로 미리 인스턴스를 생성해 둔다. -> 인스턴스를 사용하지 않아도 메모리 차지
클래스를 로드하는 속도가 느려지지만 처음 인스턴스 반환 요청에서 속도가 빠르다는 장점을 가진다.

게으른 초기화 (Lazy Initialization)

인스턴스를 필요할 때 생성하는 방식 (처음 getInstance() 호출 시 생성).
-> 클래스가 초기화 되는 시점에서는 정적 필드를 선언해두고 null로 초기화된다.
클래스를 로드하는 속도는 빠르지만 첫 번째 요청에 대한 속도가 두 번째 요청에 대한 속도보다 느리다는 특징을 가진다.

final 키워드

변경 불가의 의미를 담고 있는 키워드

  • 상속과 관련하여 클래스나 메소드의 예약어 자리에 쓰이면 더 이상 하위 클래스에서 가지지 못하는 마지막 클래스나 메소드를 의미하게 된다.

  • 클래스 필드의 final 변수는 선언과 동시에 초기화 하거나 생성자를 초기화를 해야한다.

  • 지역변수 : 초기화 이후 값 변경 불가

  • 매개변수 : 호출시 전달한 인자 변경 불가

  • 전역변수 : 인스턴스 생성 후 초기화 이후에 값 변경 불가

  • 클래스(static) 변수 : 프로그램 start 이후 값 변경 불가

  • non-static 메소드 : 메소드 재작성(overriding) 불가

  • static 메소드 : 메소드 재작성(overriding) 불가

  • 클래스 : 상속 불가

변수의 종류

  • 필드 : 클래스 영역에 작성하는 변수 (전역변수)
  • 클래스 변수(정적필드) : static 키워드를 가지고 필드에 선언하는 변수, 메모리의 static 영역 사용
  • 인스턴스 변수 : static 키워드 없이 클래스의 필드에 선언하는 변수, 메모리의 heap 영역 사용, 객체 생성 시점에 사용 가능한 변수 -> 레퍼런스 변수필요
  • 지역 변수 : 메소드, 생성자, 초기화 블록 내부에서 선언하는 변수, 다른 변수들보다 우선권을 가진다. 메소드 호출 시 stack을 할당 받아 stack에 생성. 선언 외에 다시 사용(호출)하기 위해서는 반드시 초기화가 되어야 한다.
  • 레퍼런스 변수 : 객체의 주소(참조값)를 저장하는 변수, 객체를 다룰 때는 항상 레퍼런스 변수를 통해 접근한다.

객체 배열

레퍼런스변수에 대한 배열

  • 동일한 타입의 여러 개 인스턴스를 각각 취급하지 않고 연속 처리할 수 있어서 유용
  • 배열이기 때문에 반복문을 사용할 수 있고 할당과 동시에 초기화 가능, 향상된 for문도 사용가능할당과 동시에 초기화 가능, 향상된 for문도 사용가능

    클래스명(사용자 지정 참조 자료형)[] 참조변수명 = new 클래스명[배열 크기]; //먼저 배열할당
    참조변수명[인덱스] = new 클래스명(매개변수);
    참조변수명[인덱스] = new 클래스명(매개변수);

초기화 블럭

해당 클래스의 인스턴스 생성 시 어떤 생성자를 활용해서 인스턴스를 생성하더라도 공통적으로 실행 될 코드를 작성할 수 있는 블럭
-> 복잡한 초기화를 수행할 수 있는 블럭

인스턴스 초기화 블럭

인스턴스가 생성되는 시점에 생성자 호출 이전에 먼저 실행이 된다.
인스턴스를 호출하는 시점마다 호출이 된다.
인스턴스변수를 초기화하며 정적필드에는 실행시점마다 값을 덮어쓴다.

{
초기화 내용 작성
}

정적 초기화 블럭

클래스가 로드될 때 한 번 동작한다.
정적 필드를 초기화하며, 인스턴스변수는 초기화하지 못한다.

static {
초기화 내용 작성
}


초기화 순서
인스턴스변수 : 기본값 -> 명시적초기값 -> 인스턴스초기화블럭 -> 생성자
클래스변수 : 기본값 -> 명시적초기값 -> 정적초기화블럭 -> 인스턴스초기화블럭 -> 생성자
오른쪽으로 갈 수록 더 강한 힘을 가져서 덮어쓴다.

  • 초기화 블럭으로는 static final 초기화 가능

참조

객체는 개념!, 인스턴스는 구체적인 예시!


<!-- 생성자 안 만든 경우 -->
class Person {
	String name;
}

Person p = new Person(); // 가능
p.name = "홍길동"; // 가능


<!-- 생성자 만든 경우 -->
class Person {
	String name;
    
    Person(String name) {
    	this.name = name;
    }
}

Person p = new Person(); // 불가능
Person p = new Person("홍길동") // 가능


<!-- 기본 생성자를 직접 추가 -->
class Person {
    String name;

    Person() {} // 기본 생성자 직접 추가

    Person(String name) {
        this.name = name;
    }
}

Person p1 = new Person();        // 가능
Person p2 = new Person("홍길동"); // 가능

<!-- setter 없이 public 변수 -->
class Person {
    String name;
}

Person p = new Person();
p.name = "홍길동"; // 가능
p.name = "김철수"; // 가능 (언제든 변경됨)


<!-- private + setter -->
class Person {
    private String name;

    void setName(String name) {
        this.name = name;
    }
}

Person p = new Person();
p.name = "홍길동"; // 불가능
p.setName("홍길동"); // 가능
p.setName("김철수"); // 가능 (값 계속 변경됨)


<!-- private만 사용 -->
class Person {
    private String name;
}

Person p = new Person();
p.name = "홍길동"; // 불가능


<!-- private + final + 생성자 -->
class Person {
    private final String name;

    Person(String name) {
        this.name = name;
    }
}

Person p = new Person("홍길동");
p.name = "김철수"; //불가능
// setter도 만들 수 없음 (final이라 에러)

<!-- 요즘 가장 많이 쓰는 방식 -->
class User {
    private final String name;
    private final int age;
    private String password;

    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    void changePassword(String newPassword) {
    	// 검증 조건 추가
        this.password = newPassword;
    }
    
    public String getName() {
    	return name;
    }
}
profile
잔디 속 새싹 하나

0개의 댓글