자바 핵심 개념
선언부와 구현부
메서드는 다음과 같이 정의할 수 있다.
public static int multiply(int a, int b) {
}
제어자 반환타입 메서드이름(매개변수 목록) {
구현부
}
선언부
메서드에서 구현부를 제외한 나머지를 선언부라고 한다. 선언부에는 다음과 같은 종류가 있다.
제어자:
public
,static
과 같은 부분이다.접근 제어자에는 다음과 같은 종류가 있다.
private
: 같은 클래스에서만 접근 가능하다.default
: 같은 패키지에서만 접근 가능하다.protected
: 상속 관계에서만 접근 가능하다.public
: 어디서든 접근 가능하다.
반환 타입: 메서드가 실행되고 반환하는 데이터의 타입이다.
메서드 이름: 메서드를 호출하는 메서드의 이름이다.
매개 변수: 메서드 내에서만 사용할 수 있는 지역 변수이다.
매개 변수 (Parameter) vs. 인수(Argument)
메서드 정의와 호출
다음과 같이 곱셈 연산을 하는 메서드가 정의되어 있고 multiply
메서드를 호출했다고 하자.
public class ParamAndArg {
public static void main(String[] args) {
// 2. 메서드 호출
int multiplication = multiply(3, 8);
// 3. 결과 출력
System.out.println("결과 출력: "+ multiplication);
}
// 1. 메서드 정의
public static int multiply(int a, int b) {
System.out.printf("%d * %d 연산 수행%n", a, b);
return a * b;
}
}
인수 (Argument)
위 예시에서 3
, 8
과 같이 메서드를 호출할 때 넘기는 값을 인수, 인자, 또는 아규먼트라고 한다.
매개변수 (Parmeter)
위 예시에서 int a
, int b
과 같이 메서드 선언부에 있는 변수를 매개변수 또는 파라미터라고 한다.
참조에 의한 호출 (Call by Reference) vs. 값에 의한 호출 (Call by Value)
메서드를 호출할 때 값을 전달하는 방법에는 다음과 같은 두 가지 방식이 있다.
참조에 의한 호출
주소 값을 인자로 전달하는 호출 방식.
원본 값 변경 가능.
기본형과 관련.
예) 아파트 주소
값에 의한 호출
값 자체를 복사해 인자로 전달하는 호출 방식.
원본 값 변경 불가.
참조형과 관련.
예) 아파트 주소에 사는 가구원 수
멤버 변수와 지역 변수
클래스를 구성하는 변수에는 멤버 변수와 지역 변수가 있다. 멤버 변수에는 인스턴스 변수와 클래스 변수가 있다.
변수의 종류 | 선언 위치 | 사용 가능 시점 |
---|---|---|
인스턴스 변수 (iv ) |
클래스 영역 | 객체 생성 이후 |
클래스 변수 (static + iv ) |
클래스 영역 | 메모리에 로딩 될 때 = 객체 생성 이전 (항상) |
지역 변수 (lv ) |
메서드 영역 | 메서드 호출 시 |
자바의 메모리 영역
자바의 메모리 영역은 다음과 같이 크게 세 가지 영역으로 구분된다.
클래스 영역 (Class Area)
프로그램을 실행하는데 필요한 공통 데이터를 관리하는 영역이다.
클래스 정보: 클래스(
.class
)와 관련된 모든 정보를 보관한다.static
영역:static
변수들을 보관한다.상수 풀: 공통 리터럴 상수를 보관한다.
예)"java"
,123
스택 영역 (Stack Area)
메서드 실행을 위한 영역이다.
메서드의 지역 변수를 위한 공간이 생성되며 메서드 수행이 끝나면 메모리 공간을 반환한다.
힙 영역 (Heap Area)
new
연산자를 통해 생성된 객체(배열 포함)를 위한 영역이다.참조 되지 않는 객체는 가비지 컬렉터(GC)에 의해 제거되는 가비지 컬렉션이 일어나는 영역이기도 하다.
클래스와 인스턴스
class 붕어빵 {
String filling;
붕어빵(String filling, String topping) {
this.filling = filling;
}
}
public class Main {
public static void main(String[] args) {
붕어빵 붕어빵1 = new 붕어빵("참치");
붕어빵 붕어빵2 = new 붕어빵("초콜릿");
}
}
클래스
- 사용자 정의 타입을 만드는 설계도
- 예)
붕어빵
인스턴스 (객체)
- 클래스를 이용해 만든 실제 메모리(힙 영역)에 만든 실체
- 예)
붕어빵1
,붕어빵2
가 참조하고 있는 것
객체 지향 핵심 개념
abstract class 붕어빵 {
private filling;
abstract void 속_채우기();
protected void 굽기() {
System.out.println("굽기!");
}
}
class 피자_붕어빵 extends 붕어빵 {
private String filling;
@Override
void 속_채우기() {
this.filling = "피자";
}
void 속_채우기(boolean 재료_소진) {
if (재료_소진) {
this.filling = "팥";
}
}
void 굽기() {
System.out.println("굽기!");
}
}
위와 같은 예를 이용해 객체 지향 개념을 설명하겠다.
상속 (Inheritance)
재사용 & 확장과 구체화
기존 클래스의 필드와 메서드를 재사용하고 확장 가능하게 한다.
예)굽기()
추상 클래스의 구체화이다.
예)속_채우기()
상속 관계
- 두 클래스가
is-a
관계일 때 두 클래스는 상속 관계에 있다고 할 수 있다.
예) 피자 붕어빵은 붕어빵이다. 팥 붕어빵은 붕어빵이다.
오버로딩과 오버라이딩
오버로딩: 선언부가 같고 매개변수가 다른 메서드를 정의
예)속_채우기(boolean 재료_소진)
오버라이딩: 부모의 메서드를 재정의, 상속 관계
예)속_채우기()
캡슐화
- setter나 getter 등을 통해 클래스의 필드에 접근하게 하는 방식이다.
- 필드의 위변조를 방지한다.
다형성
핵심 기능
public class Polymorphism {
public static void main(String[] args) {
피자_붕어빵 피붕 = new 피자_붕어빵();
팥_붕어빵 팥붕 = new 팥_붕어빵();
붕어빵[] 붕어빵들 = {피붕, 팥붕};
for (붕어빵 붕어빵_하나 : 붕어빵들) {
System.out.println("붕어빵 속 채우기 시작");
붕어빵_하나.속_채우기();
System.out.println("붕어빵 속 채우기 종료");
}
}
}
다형적 참조: 부모 타입의 변수가 자식 인스턴스를 참조할 수 있다.
예)붕어빵[] 붕어빵들 = {피붕, 팥붕};
오버라이딩 우선: 자식 클래스에서 오버라이딩된 메서드가 우선 호출된다.
예)붕어빵_하나.속_채우기();
: 붕어빵 타입이피자_붕어빵
일 때붕어빵
의속_채우기()
가 아닌피자_붕어빵
의 오버라이드된 메서드가 호출된다.
[참고] 두 객체가 서로 상속 관계에 있는지 확인할 때는 instanceof
를 사용한다.
그렇다면 이와 같은 다형성을 사용하는 이유는 무엇일까?
사용 이유
앞서 본 예와 같이 추상화의 정도에 따라 추상 클래스, 인터페이스 순서로 추상화의 정도가 극대화된다. 인터페이스 사용해야 좋은 설계를 할 수 있다고 하는데 그 이유는 다음과 같다.
제약: 인터페이스의 모든 메서드는 추상 메서드이기 때문에 해당 인터페이스를 구현하는 자식 클래스는 모든 기능을 구현해야만 하는 강제성을 가진다.
다중 구현 가능: 상속과 달리
implements
를 사용하는 인터페이스는 여러 인터페이스를 구현할 수 있다.
하지만 가장 중요한 다형성의 사용 이유는 바로 역할과 구현의 분리이다.
class Driver {
private Model1 model1 = new Model1();
void drive() {
model1.시동_켜기();
model1.엑셀_밟기();
model1.시동_끄기();
}
}
class Model1 {
void 시동_켜기() {};
void 엑셀_밟기() {};
void 시동_끄기() {};
}
위와 같이 운전자
가 차를 운전하는 상황을 가정해보자. 해당 운전자
가 차를 Model2
로 바꾸는 경우 Driver
클래스의 대부분을 수정해야 한다. 결국 역할과 구현의 분리가 이루어지지 않아 운전자
자신을 고쳐야 하는 경우가 발생하는 것이다. 이를 방지하기 위해 Car
(역할)라는 인터페이스가 필요하며 이를 Driver
에 도입하게 되면 차를 Model2
(구현)로 바꾸는 상황에서 Driver
클래스를 바꿔야 하는 경우가 없어진다.
class Driver {
private Car car;
Driver(Car car) {
this.car = car;
}
void drive() {
car.시동_켜기();
car.엑셀_밟기();
car.시동_끄기();
}
}
interface Car {
void 시동_켜기();
void 엑셀_밟기();
void 시동_끄기();
}
class Model1 implements Car {
void 시동_켜기() {};
void 엑셀_밟기() {};
void 시동_끄기() {};
}
이와 같이 어떤 차
를 운전
해도 운전자
의 코드는 변하지 않는 설계를 확장에는 열려있고 수정에는 닫혀 있는 OCP (Open-Closed Principle) 원칙이라고 한다.
'공부 > JAVA' 카테고리의 다른 글
[java] 정석코딩 자바 문제 오답노트 (0) | 2024.04.04 |
---|---|
[java] 자바의 정석 요약정리 8 ~ 9장 (0) | 2024.04.04 |
[java] 자바의 정석 요약정리 6 ~ 7장 (0) | 2024.04.04 |
[java] 자바의 정석 요약정리 2 ~ 5장 (0) | 2024.04.04 |