목차
- 캡슐화
- 추상화
- 생성자
1. 캡슐화(encapsulation)
- 클래스 작성할 때 특별한 경우가 아닌 이상 지켜야 하는 기본 원칙이다.
- 필드로의 직접 접근을 제한하고, public 메소드를 이용해 간접 접근하도록 만든 기술이다. 달리 말하면 데이터를 은닉하는 것이 된다.
- 캡슐화는 결합도를 낮춘다. 예를 들어 필드명을 바꾸거나 했을 때 이를 사용한 다른 클래스에서 영향을 받지 않게끔 만든다. 한 클래스의 변경이 다른 클래스에 미치는 영향이 극히 낮다는 의미이다.
1-1. 접근제한자 정의
- 접근제한자란, 참조연산자(.)를 가지고 접근할 때에 클래스나 클래스의 멤버(필드, 메소드)에 접근할 수 있는 범위를 제한하는 키워드이다.
구분 | 해당 클래스 내 | 같은 패키지 내 | 후손 클래스 내 | 전체 | |
+ | public | O | O | O | O |
# | protected | O | O | O | |
~ | (default) | O | O | ||
- | private | O |
가장 왼쪽의 기호는 클래스 다이어그램에서 사용되는 접근제한자별 기호이다.
-kinds : String
+setKinds(kinds: String): void
A. public
- 모든 패키지에 접근 허용한다. 어떤 위치에서든 참조하여 사용할 수 있다는 의미이다.
B. protected
- 같은 패키지에서만 접근 허용한다.
- 단, 상속관계에 있는 경우 후손 클래스에서도 접근 가능하다.
C. (default)
- 같은 패키지에서만 접근 허용한다.
- default는 명시하지 않고, 생략되는 경우를 일컫는다.
D. private
- 해당 클래스 내부에서만 접근 허용한다. 같은 패키지의 다른 클래스에서도 직접 접근이 불가함을 말한다.
- 필드, 메소드 등 클래스 멤버의 범위를 제한할 때는 public, protected, (default), private 위 네 가지 모두 활용할 수 있다.
- 클래스 선언 시에는 public과 (default)만 사용 가능하다.
public class Product {}
class Product {}
1-2. 접근제한자 사용
1. private로 필드를 제한한다. 예) Product.class
private String fruit;
private String origin;
private int price;
2. public 메소드 통해 간접 접근하도록 만든다. 예) Application.class
prod1.fruit= "바나나";컴파일 에러 메시지: The field Product.fruit is not visible
- 앞서 접근제한자를 private로 명시했기에 필드에 직접 접근을 시도하면 위와 같은 컴파일 오류가 발생한다.
- 접근을 허용하지 않은 필드이기 때문에 직접 접근할 수 없다는 의미이다.
prod1.setFruit("바나나");
- 이는 즉 메소드를 통한 간접 접근만이 가능한 상황으로, 간접 접근을 강제화 한 것이다.
✅ 접근제한자에 대해 이해할 수 있다.
✅ 캡슐화의 목적을 설명할 수 있다.
✅ 캡슐화를 적용하여 클래스를 작성할 수 있다.
2. 추상화(abstraction)
2-1. 추상화 정의와 목적
- 객체 지향 프로그래밍(OOP, Object Oriented Programming)은 현실세계는 사람·사물·개념 등 독립적으로 구분된 각각의 객체로 이루어져 있고, 여기서 발생하는 모든 사건들은 결국 객체간 상호작용이라고 정의한 세계관을 프로그래밍 언어에 도입한 것이다.
- 현실세계에서 객체간 상호작용의 예로는 다음과 같은 것을 들 수 있다.
사람이 문을 열거나 닫는다.
사람이 칠판에 쓰거나 지운다.
사람이 말하거나 듣는다.
.
.
- 그러나 이러한 현실세계 속 상호작용들을 프로그래밍에 그대로 반영하기에는 그 양이 너무 방대하고 복잡하다.
- 따라서 현실세계를 프로그램의 목적에 맞게 단순화하는 기법을 적용하는데, 이것이 바로 추상화이다.
- 추상화 작업은 개발 비용을 최소화할 수 있도록 한다는 코드 작성의 궁극적인 목적과도 맞닿아 있다.
A. 유연성을 확보한다.
- 객체간 공통된 부분은 선택하고, 그렇지 않은 부분은 제거한다.
B. 재사용성을 높인다.
- 유연성이란 곧 여러 곳에 적용될 수 있는 유연한 객체를 의미한다.
C. 유지보수성을 증가시킨다.
- 중복 작성된 코드 사용을 줄이고, 오류 발생 가능성 또한 감소시킴으로써 코드 관리 측면에서 효율성을 높인다.
2-2. 클래스와 인스턴스
- 클래스(class)란 객체를 추상화한 공간이다.
- 인스턴스를 생성할 목적으로 정의해놓은 소스코드 작성 단위를 말한다.
- 자바에서 객체를 구현하기 위한 메커니즘으로 사용되는 것이 바로 클래스이다.
추상화를 통해 개개인 또는 개별 객체가 가진 구체적인 것들은 제거하고, 프로그램에 필요한 공통점만을 추출한다.
자동차란 택시도 버스도 트럭도 될 수 있듯이 추상화는 유연성을 확보하기 위한 작업이다.
클래스 | 인스턴스==객체 |
자동차 | 택시 | 버스 | 트럭... |
회원 | 이름 | 나이 | 연락처 | 등록일... |
학생 | 이름 | 소속 반 | 연락처 | 공부를 한다 | 등교를 한다 | 하교를 한다... |
- 객체(object)란 클래스에 정의된 대로 new 연산자 통해 heap에 할당된 공간이다.
- 클래스 → 인스턴스화 → 인스턴스==객체: 클래스를 인스턴스화 시키면 인스턴스가 생긴다.
- 아래와 같은 객체 생성 구문이 선언되었을 때, stack과 heap 영역에 각각 자리잡는다.
Student kim = new Student();
stack | heap | static |
kim | name | age | address | setName | setAge | setAddress |
Radio.class
stack | heap |
radio | 주파수 | 볼륨 | 전원 |
- 라디오 속성으로 볼륨, 전원, 주파수, 제조사, 모델명 등을 꼽을 수 있다. 여기서 제조사, 모델명 같은 구체성은 제거한다.
- 라디오 기능은 주파수를 높인다 / 낮춘다, 볼륨을 높인다 / 낮춘다, 전원을 켠다 / 끈다를 떠올릴 수 있다.
- 이처럼 공통적으로 가지는 속성과 기능을 Radio.class라는 추상화된 클래스로 만들 수 있다.
2-3. 행위 추상화와 속성 추상화
A. 행위 추상화
- 카레이서가 자동차를 운전하는 프로그램을 예시로 들자.
- 이때 필요한 객체는 1. 카레이서, 2. 자동차가 된다.
- 카레이서(송신자)-자동차(수신자) 객체간에 상호작용은 메시지(message)를 통해 이루어진다.
- 즉, 카레이서가 자동차에게 '시동을 걸어라', '앞으로 나가라', '멈춰라'와 같은 메시지를 보내는 것이다.
- 메시지는 곧 메소드(method)를 호출한다는 의미이다.
package com.reminder.abstraction;
public class CarRacer {
private Car car = new Car();
public void startUp() {
car.startUp();
}
public void stepAccelator() {
car.go();
}
public void stepBreak() {
car.stop();
}
public void turnOff() {
car.turnOff();
}
}
CarRacer.class는 상호작용할 Car.class를 필드에 가지고 있어야 한다. 카레이서가 자신이 이용할 자동차가 무엇인지 알아야 쓸 수 있듯이 말이다.
package com.reminder.abstraction;
public class Car {
private boolean isOn;
private int speed;
public void startUp() {
if(isOn) {
System.out.println("이미 시동이 걸려 있습니다.");
} else {
System.out.println("시동을 걸었습니다. 출발할 준비가 완료되었습니다.");
}
}
public void go() {
if(isOn) {
System.out.println("차가 앞으로 움직입니다.");
this.speed += 10;
System.out.println("현재 시속은 " + this.speed + "km/h 입니다.");
} else {
System.out.println("시동이 걸려 있지 않습니다. 시동을 먼저 확인하세요.");
}
}
public void stop() {
if(isOn) {
if(speed > 0) {
speed = 0;
System.out.println("브레이크를 밟으셨습니다. 차를 멈춥니다.");
} else {
System.out.println("이미 멈춰 있는 상태입니다.");
}
} else {
System.out.println("시동이 걸려 있지 않습니다. 시동을 먼저 확인하세요.");
}
}
public void turnOff() {
if(isOn) {
if(speed > 0) {
System.out.println("달리는 상태에서 시동을 끌 수 없습니다. 우선 멈춰주세요.");
} else {
isOn = false;
System.out.println("시동을 끕니다. 다시 운행하기 위해서는 시동을 켜주세요.");
}
} else {
System.out.println("시동이 걸려 있지 않습니다. 시동을 먼저 확인하세요.");
}
}
}
package com.reminder.abstraction;
import java.util.Scanner;
public class Application {
public static void main(String[] args) {
CarRacer racer = new CarRacer();
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println("======= 카레이싱 프로그램 =======");
System.out.println("1. 시동 On");
System.out.println("2. 전진");
System.out.println("3. 정지");
System.out.println("4. 시동 Off");
System.out.println("0. 프로그램 종료");
System.out.print("메뉴 선택 > ");
int num = scanner.nextInt();
switch(num) {
case 1: racer.startUp(); break;
case 2: racer.stepAccelator(); break;
case 3: racer.stepBreak(); break;
case 4: racer.turnOff(); break;
case 0: System.out.println("프로그램을 종료합니다."); return;
default: System.out.println("잘못된 번호를 입력하셨습니다.");
}
}
}
}
시동을 켜거나 끄고, 앞으로 가거나 멈추고 하는 현상들은 현재 시동이 걸려 있는지를 우선 확인해야 한다.
startUp, go, stop, turnOff 각각의 메소드이지만 결국은 메소드간에 상태를 공유해야 하는 속성이 존재한다.
초기 상태는 접근제한자 private과 함께 시동과 속도를 다룰 수 있는 필드로 생성한다.
가속할 때마다 10km/h를 추가한다는 조건 또한 if문을 활용해 만들 수 있다.
B. DTO 추상화
앞선 예시에서처럼 행위 위주가 아니라 데이터를 하나로 뭉치기 위한 객체(DTO, Data Transfer Object)라면 일일이 추상화 작업을 거치지 않아도 가능한 경우들이 있다.
캡슐화 원칙을 준수하여 절차대로 ① 모든 필드를 private으로 설정해 직접 접근을 제한하고, ② 필드값을 변경 및 반환하기 위한 메소드를 준비한다.
이에 따라 ⓐ private 필드, ⓑ 필드값을 수정하는 설정자(setter), ⓒ 필드에 접근하는 접근자(getter)가 구성되는 것이다.
- 회원 정보 관리 프로그램을 예시로 들자.
- 여기서 회원 정보를 구상해 값 객체를 가지는 속성(==필드)으로 추출하는 과정 또한 추상화라 할 수 있다.
DTO 추상화 과정은 다음과 같다.
- 첫째, 접근제한자 private 사용해 필드를 생성한다. 이 과정을 수행하면서 필드로의 직접 접근을 제한한 셈이다.
- 둘째, 설정자(setter)-접근자(getter)를 이용해 public 메소드로 간접 접근하도록 한다.
- 설정자(setter)는 매개변수 통해 필드값을 변경(설정)하는 것이 목적이다.
- 때문에 반환형 또는 리턴값이 없어 void로 작성하지만, 매개변수를 반드시 가지고 있어야 한다.
- 여기서 매개변수는 반영하고자 하는 필드와 같은 자료형을 선언해야 한다.
- 호출 시에는 매개변수 값을 명시하여 전달한다.
❗ 설정자 작성 예시
public void set필드명(매개변수) {
필드 = 매개변수;
}
- 설정자 메소드명을 set필드명으로 작성하고, camel-case 표기법을 적용한다.
- 특히 필드명==매개변수명을 똑같이 쓰는 것이 통용되는 법칙이다.
❗ 필드명==매개변수명과 동일하게 작성해야 한다!
public void setNumber(int number) {
this.number = number;
}
- 이처럼 같은 이름을 쓰게 되기 때문에, 매개변수명-필드명간 구분을 위해 객체의 주소값을 나타내는 this.를 붙인다.
- this. 없이는 전역변수보다 지역변수가 위치적으로 가깝기 때문에 이를 지역변수로 읽어 오류로 이어지므로 주의해야 할 사항이다.
- 접근자(getter)는 필드값을 반환 받을 목적의 메소드 집합을 일컫는다.
- 각 접근자는 하나의 필드에만 접근한다는 특징이 있다.
- 필드에 접근해 기록된 값을 return; 이용하여 반환한다.
- 이때 반환형은 해당 값의 자료형과 일치시켜야 한다.
❗ 접근자 작성 예시
public 반환형 get필드명() {
return 반환값;
}
- 접근자 메소드명을 get필드명으로 작성하고, camel-case 표기법을 적용한다.
public String getName() {
return this.name;
}
- 여기서는 소괄호() 안에 같은 이름의 매개변수 값이 없으므로 this. 생략해도 괜찮다.
- 대신에 리턴값을 반드시 명시하여야 한다. return에서 반환시키는 것이 바로 필드값이다.
❗ boolean의 접근자는 get으로 시작하지 않고 is로 시작하는 것이 일반적인 관례이다.
public boolean isActivated() {
return isActivated;
}
- 캡슐화의 목적은 필드에 변경사항이 발생했을 때 유연하게 대처하는 데에 있다.
- 행위보다는 필드, 속성에 대한 추상화를 행한 DTO 작성 과정에서는 사실상 이름을 그대로 썼으므로 캡슐화한 의미가 크게 없다.
name → memberName;
setName → setMemberName;
getName → getMemberName;
- 전역변수명, 즉 필드명을 바꾼 경우를 예로 든다면 뒤따라온 설정자(setter), 접근자(getter)의 이름들도 일괄적으로 바뀌어야 하는 상황이 되는 것이다. 이 같은 이유에서 캡슐화한 의미가 크게 없다고 해석할 수 있다.
- 그러나 현재도 많이 사용되고 있는 관례이기 때문에, 이러한 DTO 작성법에 맞춰 작성해가야 한다.
- 당장은 DTO 작성법을 숙지하는 데에 집중하자.
✅ 추상화를 이해하고 설명할 수 있다.
✅ 객체를 추상화하여 클래스를 작성하고 이를 활용할 수 있다.
✅ DTO 작성법을 숙지하고 사용할 수 있다.
✅ 설정자(setter)와 접근자(getter) 메소드를 이해하고 사용할 수 있다.
3. 생성자(constructor)
- 생성자 호출 목적은 인스턴스 생성 시 필드 초기화에 있다. 지정된 매개변수 통해 값을 넘겨주며 초기화한다.
- 만들어져 있는 생성자 외에는 인스턴스 생성 방법이 없게 된다. 초기값 전달 강제화가 가능하다고 해석할 수 있다.
- 설정자(setter)-접근자(getter)가 하나의 값에 대해서 수행됐다면, 반면 생성자는 여러 개 값을 다루는 것이 가능하다.
- 아래와 같은 객체 생성 코드를 이제까지는 클래스명 변수명 = 연산자 클래스명(); 이라고 불러왔으나, new 연산자 뒤에 위치한 클래스명은 사실 생성자(constructor)라고 불리는 메소드 호출 구문이다.
MemberDTO member = new MemberDTO();
❗ 클래스명 레퍼런스변수명 = 연산자 생성자();
- 객체가 new 연산자를 통해 heap 메모리 영역에 할당될 때 1회성으로 호출된다.
- 리턴 타입이 없는 일종의 메소드를 말한다. 즉 void 등 반환형이 생략된 형태를 가진다.
- method(); 일반 메소드 정의하듯 괄호가 뒤에 붙는 것을 알 수 있다.
public Reunion() {}
❗ 접근제한자 클래스명() {}
- 생성자는 클래스 내부 어디에서 선언하든
컴파일 에러 대상은 아니다. - 다만 관례적으로 필드선언-생성자선언-메소드선언 순서에 따라 작성하고 있다.
private String name; 필드
public Reunion(String name...) {} 생성자
public void setName(String name) {} 설정자(setter)메소드
public String getName() {} 접근자(getter)메소드
❗ 필드선언-생성자선언-메소드선언 순서를 따른다!
3-1. 기본 생성자(default constructor)
Reunion reunion = new Reunion();
- 생성자 함수에 매개변수가 없는 생성자를 말한다.
- 기본 생성자는 컴파일러(compiler)에 의해 자동으로 추가되었기 때문에 지금까지 명시적으로 작성하지 않고 사용할 수 있었다.
- 기본 생성자를 통해 인스턴스를 생성하게 되면 자료형별 기본값으로 필드가 초기화된다.
public Reunion() {
}
3-2. 일부 매개변수가 있는 생성자
- public Reunion으로 같은 이름이어도 매개변수가 있는 생성자의 경우 각각 다르게 인식한다.
- 초기화 할 필드가 여러 개인 경우 초기화하고 싶은 필드의 갯수별로 생성자를 여러 개 만들 수 있다.
- 매개변수 있는 생성자의 주 목적은 인스턴스 생성 시점에 매개변수로 전달 받은 값을 이용해서 필드를 초기화하는 데에 있다.
Reunion.class
public Reunion(int classOf, String name, boolean isOnMembershipList) {
this.classOf = classOf;
this.name = name;
this.isOnMembershipList = isOnMembershipList;
}
- 객체 생성과 동시에 초기값 설정까지 실시한다.
- 인자를 받아 필드를 초기화한 것이다.
Application.class
Reunion reunionB = new Reunion(1999, "김자바", true);
reunionB.printInformation();
- 이처럼 생성자 호출 시 인자로 값을 전달하여 필드의 초기값을 사용자가 원하는 대로 설정할 수 있다.
3-3. 모든 매개변수가 있는 생성자
- 중복된 코드는 this() 구문으로 대신할 수 있다. this란 모든 객체의 메소드 안에 숨겨진 채 존재하는 참조변수를 말한다. 할당된 객체의 주소가 저장돼 있다.
- this()는 동일 클래스 내에 작성한 다른 생성자 메소드를 호출하는 구문이다.
- 소괄호 안에 기재된 매개변수의 타입, 갯수, 순서에 알맞는 생성자를 호출하고 복귀한다. 이는 즉 메소드의 기능과 동일하다.
Reunion.class
public Reunion(int classOf, String name, boolean isOnMembershipList, java.util.Date responseDate) {
this(classOf, name, isOnMembershipList);
this.responseDate = responseDate;
}
Application.class
Reunion reunionC = new Reunion(1999, "김코딩", true, new java.util.Date());
reunionC.printInformation();
❗ this()는 메소드 내부의 가장 첫 줄에 선언한다!
- this() 메소드가 첫 줄에 위치하지 않은 경우 컴파일 에러가 발생한다: 오류 메시지 Constructor call must be the first statement in a constructor
- this() 위에 ctrl + 마우스 클릭해 어떤 생성자를 가리키는지 확인할 수 있다.
- new java.util.Date()는 Scanner, Random 등과 같이 java.util에 속한 클래스로, 실행한 때의 시스템 시간이 반환된다.
- 매개변수 타입, 갯수, 순서가 똑같은 생성자들은 구분이 가지 않아 에러가 발생한다.
- 아래 경우들은 중복되지 않아 사용 가능하다.
String, int
int, String
int, String, int
3-4. 복사 생성자
- 이미 만들어져 있는 동일 타입의 인스턴스 필드값을 이용해 새로운 인스턴스를 생성하며 초기화 값으로 반영할 수 있다.
동일한 데이터를 가지지만, 새롭게 할당된 인스턴스이기 때문에 서로 다른 주소값을 가지게 된다. - 즉, 복사 생성자 사용은 깊은 복사의 일례이다.
Reunion.class
public Reunion(Reunion otherReunion) {
this(otherReunion.classOf, otherReunion.name, otherReunion.isOnMembershipList, otherReunion.responseDate);
}
new java.util.Date() 실행한 때의 시스템 시간이 반환된다.
Application.class
Reunion reunionD = new Reunion(reunionC);
reunionD.printInformation();
============================
졸업 연도 : 1999
이름 : 김코딩
멤버십 가입 여부 : true
회신일 : Wed Dec 29 22:28:12 KST 2021
- 위와 같이 여러 개의 생성자가 만들어진 상황에서 기본 생성자를 호출하면 더이상 자동 추가되지 않고 컴파일 에러가 발생한다: The constructor Reunion() is undefined
❗ 결론적으로 앞으로는 기본 생성자를 반드시 명시하여야 한다.
- 정의되지 않은 형식의 생성자는 호출 불가하다: The constructor Reunion(String, String) is undefined
- 생성자는 인스턴스 생성 방법을 매개변수로 정해둔 형식으로 제공하며, 제공되는 생성자 외에는 인스턴스를 생성하는 방법을 제한한다.
- 생성자를 사용함으로써 여러 개의 설정자(setter) 메소드를 호출한 것과 같은 효과를 가질 수 있다. 즉 한 번에 여러 개 필드 초기화가 가능해졌음이다.
- 단편적인 예로 설정자(setter) 메소드는 다뤄야 하는 대상의 수만큼 필드를 생성해야 하지만, 생성자는 보다 간추린 방식으로 한 번에 호출할 수가 있다.
✅ 생성자에 대해 이해할 수 있다.
✅ 생성자의 사용 목적에 대해 이해할 수 있다.
✅ 생성자의 작성 문법을 숙지하고 생성자를 작성할 수 있다.
✅ 기본생성자와 매개변수 있는 생성자에 대해 이해할 수 있다.
✅ this.과 this()를 이해할 수 있다.
✅ 복사생성자에 대해 이해할 수 있다.
'Java' 카테고리의 다른 글
[JAVA] 6-3. 오버로딩, 파라미터, static, final, 싱글톤 패턴 (0) | 2021.12.30 |
---|---|
[JAVA/수업 과제 practice] 클래스와 객체 Lv. 1~2 (0) | 2021.12.29 |
[JAVA] 6-1. 클래스, 사용자 정의 자료형 (0) | 2021.12.29 |
[자바/수업 Quiz] 배열 (0) | 2021.12.28 |
[JAVA] 5-2. 배열의 복사 및 정렬 (0) | 2021.12.28 |