객체 지향 프로그래밍
-
객체 : 세상에 존재하는 물체, 식별이 가능한 것 (물리적 혹은 개념적인 것)
속성 (필드) + 행위 (메서드) 로 이루어져 있다.
ex) 자동차 = 속성 (회사, 모델, 색상, 가격, 속도 등) + 행위 (가속, 브레이크, 기어변속, 조명, 경적등)
-
객체 간의 관계
- 사용 관계

사람이 자동차를 사용
- 포함 관계

타이어 객체, 차문 객체, 핸들 객체는 자동차 객체에 포함
- 상속 관계

자동차와 기차 객체는 하나의 공통된 기계시스템 객체를 토대로 만들어진다고 가정
자동차 객체와 기차 객체는 기계시스템 객체를 상속 받는 상속 관계
-
객체 지향 프로그래밍의 특징
- 캡슐화
- 속성 (필드)와 행위 (메서드)를 하나로 묶어 객체로 만든 후 실제 내부 구현 내용은 외부에서 알 수 없게 감추는 것
- 외부 객체에서는 캡슐화된 객체의 내부 구조를 알 수 없기 때문에 노출시켜 준 필드 혹은 메서드를 통해서만 접근 가능
- 필드와 메서드를 캡슐화 하여 숨기는 이유는 외부 객체에서 해당 필드와 메서드를 잘못 사용하여 객체가 변화하지 않게 하는데 있다. -> 보안 (남들이 함부로 값을 변경하지 못하도록)
- Java에서는 캡슐화된 객체의 필드와 메서드를 노출 시킬지 감출 지 결정하기 위해 접근 제어자를 사용
- 상속
- 부모 객체가 자신이 가지고 있는 필드와 메서드를 자식 객체에 물려주어 자식 객체가 이를 사용할 수 있도록 만드는 것
- 상속의 장점
- 각각의 객체들을 상속 관계로 묶음으로써 객체 간의 구조를 파악하기 쉬워집니다.
- 필드와 메서드를 변경하는 경우 부모 객체에 있는 것만 수정하게 되면 자식 객체 전부 반영이 되기 때문에 일관성을 유지하기 좋습니다.
- 자식 객체가 부모 객체의 필드와 메서드를 물려받아 사용할 수 있기 때문에 코드의 중복이 줄어들며 코드의 재사용성이 증가됩니다.
- 다형성
- 객체가 연산을 수행할 때 하나의 행위에 대해 각 객체가 가지고 있는 고유한 특성에 따라 다른 여러가지 형태로 재구성되는 것 (오버라이딩 같음...)
- 추상화
- 객체에서 공통된 부분들을 모아 상위 개념으로 새롭게 선언하는 것
- 예를 들면 자동차의 속성과 행위를 가진 가상의 자동차 모델을 만들어서 그것을 토대로 각 자동차의 설계도를 완성해 나가는 것
📌 객체는 현실에 있는 자동차, 클래스는 그 자동차를 만들기 위한 설계도, 그 설계도대로 만들어진 객체를 인스턴스라고 한다.
- 클래스 설계
- 만들려고 하는 설계도를 선언(클래스 선언)
- 객체가 가지고 있어야할 속성(필드)을 정의
- 객체를 생성하는 방식을 정의(생성자)
- 객체가 가지고 있어야할 행위(메서드)를 정의
- 객체 생성
new Car(); // new 라는 키워드를 통해서 클래스로부터 객체를 생성 -> 기본 생성자
Car car1 = new Car(); // Car클래스의 객체인 car1 인스턴스 생성
Car[] cars = new Car[5]; // 객체도 배열로 생성 가능
-
필드
-
객체의 데이터를 저장하는 역할

- 고유 데이터 : 객체가 가지고 있는 고유한 데이터
- 상태 데이터 : 객체의 상태를 의미하는 데이터
- 객체 데이터 : 객체로 이루어진 데이터
-
필드의 초기값과 초기화
- 기본적으로 초기값을 제공하지 않을 경우 객체가 생성될 때 자동으로 기본값으로 초기화

-
필드 사용
Car car = new Car();
car.color = "blue"; // 외부 접근
double brakePedal() { // 내부 접근
speed = 0;
return speed;
}
외부에서 필드에 접근해서 값을 바꾸는가, 클래스 내부의 메서드가 필드 값을 변경하는 가의 차이 인 거 같음
-
메서드
-
객체의 행위, 객체간의 협력을 위해 사용
리턴타입 메서드명(매개변수, ...) {
실행할 코드 작성
} // 선언
car.gasPedal(100, 'D'); // 호출 (외부 접근)
double gasPedal(double kmh, char type) { // 호출 (내부 접근)
changeGear(type);
speed = kmh;
return speed;
}
-
오버로딩 (println도 오버로딩임)
- 하나의 메서드 이름으로 여러 기능을 구현하도록 하는 기능
- 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도, 매개변수의 개수 또는 타입, 순서가 다르면 동일한 이름을 사용해서 메서드를 정의할 수 있다.
- 조건
- 메서드의 이름이 같고, 매개변수의 개수 or 타입 or 순서가 달라야한다.
- '응답 값 (return value)만' 다른 것은 오버로딩을 할 수 없다.
- 접근 제어자만 다른 것도 오버로딩을 할 수 없다.
- 오직 매개변수의 차이로만 구현
- 장점
- 메서드 이름 하나로 상황에 따른 동작을 개별로 정의할 수 있다.
- 메서드의 이름을 절약할 수 있습니다.
-
기본형 매개변수와 참조형 매개변수의 차이
- 기본형 매개변수는 지정된 값이 매개변수로 복사되어서 넘겨지기 때문에 변수의 원본 값이 변경되지는 않는다.
- 참조형 매개변수는 지정된 값의 주소값을 넘겨주기 때문에 변수의 원본 값을 변경 할 수 있다.
-
멤버 = 필드 + 메서드
- 인스턴스 멤버 = 인스턴스 필드 + 인스턴스 메서드
- 클래스 멤버 = 클래스 필드 + 클래스 메서드
-
인스턴스 멤버
- 인스턴스 멤버는 객체를 생성해야 사용할 수 있다
- 객체의 인스턴스 필드는 각각의 인스턴스 마다 고유하게 값을 가질 수 있다.
- 객체가 인스턴스화 할 때마다 객체의 메서드들은 인스턴스에 포함되어 매번 생성이 될까?
- 매번 저장한다면 중복 저장으로 인해 메모리 효율이 매우 떨어지기 때문에 메서드는 메서드 영역에 두고서 모든 인스턴스들이 공유해서 사용
- 대신 무조건 객체를 생성 즉, 인스턴스를 통해서만 메서드가 사용될 수 있도록 제한을 걸어둔 것
-
클래스 멤버
- 클래스는 Java의 클래스 로더에 의해 메서드 영역에 저장되고 사용
- 클래스 멤버란 메서드 영역의 클래스와 같은 위치에 고정적으로 위치하고 있는 멤버
- 객체의 생성 필요없이 바로 사용이 가능
-> 메서드로 사용시, 객체.method()로 사용하지 말고 Class.method()로 사용하는 편이 낫다.
- 필드와 메서드를 클래스 멤버로 만들기 위해서는 static 키워드를 사용
- 일반적으로 인스턴스마다 모두 가지고 있을 필요없는 공용적인 데이터를 저장하는 필드는 클래스 멤버로 선언하는 것이 좋다.
- 인스턴스 필드를 사용하지 않고 실행되는 메서드가 존재한다면 static 키워드를 사용하여 클래스 메서드로 선언하는 것이 좋다.
- 주의할 점
- 클래스 멤버로 선언된 메서드는 인스턴스 멤버를 사용할 수 없다.
- 반대로 인스턴스 멤버로 선언된 메서드는 클래스 멤버를 사용할 수 있다.
- 클래스 멤버는 객체 생성없이 바로 사용 가능하기 때문에 객체가 생성되어야 존재할 수 있는 인스턴스 멤버를 사용할 수 없다.
-> 클래스 멤버는 .java를 .class로 바꾸어 줄 때 만들어지고, 인스턴스 멤버는 runtime시에 만들어지기 때문
-
지역변수
- 메서드 내부에 선언한 변수
- 메서드가 실행될때마다 독립적인 값을 저장하고 관리
- 지역 변수는 메서드 내부에서 정의될때 생성되어 메서드가 종료될 때까지만 유지
-> 메서드가 종료되거나 메서드의 범위 {} 를 넘어갈 경우 존재하지 않는다.
-
final 필드
- 초기값이 저장되면 해당값을 프로그램이 실행하는 도중에는 절대로 수정할 수 없다.
- 반드시 초기값을 지정해야한다.
- C언어에서 const라고 생각하면 될 거 같다. (상수)
-
생성자
- 객체가 생성될 때 호출되며 객체를 초기화하는 역할
- 반환 타입이 없고 이름은 클래스의 이름과 동일
- new 할때 생성된다
- 기본 생성자
- 괄호( ) 안에 아무것도 넣지않는 생성자
- 생성자를 만들지 않았다면 컴파일러가 자동으로 만들어줌
-> 하나라도 생성자를 만들었다면 만들어주지 않음
- 컴파일러에 의해 생성되면 해당 클래스의 접근 제어자를 따른다.
- 오버로딩 가능
-
this
- 인스턴스 자신을 표현하는 키워드
- 객체 내부 생성자 및 메서드에서 객체 내부 멤버에 접근하기 위해 사용
- 객체의 메서드에서 리턴타입이 인스턴스 자신의 클래스 타입이라면 this를 사용하여 인스턴스 자신의 주소를 반환
-
this()
- 인스턴스 자신의 생성자를 호출하는 키워드
- 객체 내부 생성자 및 메서드에서 해당 객체의 생성자를 호출하기 위해 사용
- 생성자를 통해 객체의 필드를 초기화할 때 중복되는 코드를 줄여준다.
- this() 키워드를 사용해서 다른 생성자를 호출할 때는 반드시 해당 생성자의 첫 줄에 작성
-
접근제어자
-
클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여
-
클래스 내부에 선언된 데이터를 보호하기 위해서 사용 (함부로 데이터에 접근하여 수정하지 못하게 하기 위해서)
-
종류
- 접근 제어자 :
public, protected, default, private
public : 접근 제한이 전혀 없다.
protected : 같은 패키지 내에서, 다른 패키지의 자손클래스에서 접근이 가능
default : 같은 패키지 내에서만 접근이 가능
- 클래스, 멤버변수, 메서드, 생성자에 사용되고, 지정되어 있지 않다면 default
private : 같은 클래스 내에서만 접근이 가능
- 그 외 제어자 :
static, final, abstract
- 사용 가능한 부분
- 클래스 :
public, default, final, abstract
- 메서드 :
public, protected, default, private, final, abstract, static
- 멤버변수 :
public, protected, default, private, final, static
- 지역변수 :
final
- 접근제어자는 하나만 사용가능, 그 외 제어자는 여러개 사용 가능
- 주의점
- 메서드에
static과 abstract를 함께 사용할 수 없다.
- 클래스에
abstract와 final을 동시에 사용할 수 없다.
abstract메서드의 접근 제어자가 private일 수 없다.
- 메서드에
private와 final을 같이 사용할 필요는 없다.
-
이제부터 클래스에 접근시에는 은닉성을 위해서 getter랑 setter를 만들어서 접근한다.
- getter
- private으로 지정된 필드 값을 리턴해준다.
- setter
- private으로 지정된 필드 값을 변경하기 위해서 사용
-
package
- 클래스의 일부분이면서 클래스를 식별해 주는 용도로 사용
- package 상위패키지.하위패키지;
- 디렉토리로 이루어진 경로라고 보면 될 듯
-
import
- 다른 패키지에 있는 클래스를 사용하기 위해 명시하는 키워드
- 클래스 이름을 생략하고 * 를 사용하여 표현하면 아래에 있는 모든 클래스를 사용할 수 있다.
-
상속
- 부모 클래스의 필드와 메서드를 자식 클래스에게 물려주는 행위
- 적은 양의 코드로 새로운 클래스를 작성할 수도 있고, 공통적인 코드를 관리하여 코드의 추가와 변경이 쉬워진다.
- 코드의 중복이 제거되고 재사용성이 크게 증가하여 생산성과 유지보수성에 매우 유리
- 상속의 키워드는 extends
-> 확장의 개념으로 이해해야한다.
- 부모 클래스에 새로운 필드와 메서드가 추가되면 자식 클래스는 이를 상속받아 사용할 수 있다.
- 자식 클래스에 새로운 필드와 메서드가 추가되어도 부모 클래스는 어떠한 영향도 받지 않는다.
- 따라서 자식 클래스의 멤버 개수는 부모 클래스보다 항상 같거나 많다.
- 클래스 간의 관계로 이해
- 상속관계 : is - a (”~은 ~(이)다”)
- 포함관계 : has - a (”~은 ~을(를) 가지고 있다”)
- 다중 상속은 허용하지 않는다. 자식 클래스에서 상속받는 부모 클래스의 멤버들 간의 구분이 불가능하기 때문
- 클래스나 method에 final 키워드를 추가하면 상속을 받을 수 없다.
- ex) Object
-> Java 내 모든 클래스들의 최상위 부모 클래스
-> 모든 클래스는 Object의 메서드를 사용 가능
-> 부모 클래스가 없는 자식 클래스는 컴파일러에 의해 자동으로 Object 클래스를 상속
-> Object clone() : 해당 객체의 복제본을 생성하여 반환함.
-> boolean equals(Object object) : 해당 객체와 전달받은 객체가 같은지 여부를 반환함.
-> Class getClass() : 해당 객체의 클래스 타입을 반환함.
-> int hashCode() : 자바에서 객체를 식별하는 정수값인 해시 코드를 반환함.
-> String toString() : 해당 객체의 정보를 문자열로 반환함. & Object 클래스에서는 클래스이름 @해쉬코드값 리턴함.
-
오버라이딩
- 부모 클래스로부터 상속받은 메서드의 내용을 재정의 하는 것
- 부모 클래스의 메서드를 그대로 사용 가능하지만 자식 클래스의 상황에 맞게 변경을 해야하는 경우 사용
- 조건
1. 선언부가 부모 클래스의 메서드와 일치해야 합니다.
2. 접근 제어자를 부모 클래스의 메서드 보다 좁은 범위로 변경할 수 없습니다.
3. 예외는 부모 클래스의 메서드 보다 많이 선언할 수 없습니다.
- super
- 부모 클래스의 멤버를 참조할 수 있는 키워드 (this가 본인 클래스면 super는 부모 클래스라고 이해하면 될듯)
- 객체 내부 생성자 및 메서드에서 부모 클래스의 멤버에 접근하기 위해 사용
- 자식 클래스 내부에서 선언한 멤버와 부모 클래스에서 상속받은 멤버와 이름이 같을 경우 이를 구분하기 위해 사용
- super()
- 부모 클래스의 생성자를 호출할 수 있는 키워드 (this()랑 비교해서 외워둘 것)
- 객체 내부 생성자 및 메서드에서 해당 객체의 부모 클래스의 생성자를 호출하기 위해 사용
- 자식 클래스의 객체가 생성될 때 부모 클래스들이 모두 합쳐져서 하나의 인스턴스가 생성
* 이 때 부모 클래스의 멤버들의 초기화 작업이 먼저 수행이 되어야함
- 자식 클래스의 생성자에서는 부모 클래스의 생성자가 호출
- 부모 클래스의 생성자는 가장 첫 줄에서 호출 되어야함
-
다형성 (참조변수의 타입 변환)
-
여러 가지 형태를 가질 수 있는 능력
-
참조변수 타입변환을 활용해서 다형성을 구현
-
매개변수에도 다형성이 적용
-
반환타입에도 다형성이 적용
-
instance of
- 다형성 기능으로 인해 해당 클래스 객체의 원래 클래스명을 체크할 때 필요
- {대상 객체} instance of {클래스 이름}
- 내가 의도한 클래스로 만든 객체인지 확인할 때 사용
-
추상 클래스
- 미완성된 설계도
- abstract 키워드를 사용하여 선언
- 자식 클래스에 상속되어 자식 클래스에 의해서만 완성
- 여러개의 자식 클래스들에서 공통적인 필드나 메서드를 추출
- 예를 들면 현대 자동차, 테슬라 자동차, 포드 자동차 등의 설계도인 클래스가 각각 따로 존재하는데, 각 클래스가 가지고 있는 공통된 기능 (시동걸기, 출발, 브레이크 등) 을 하나로 모아둬서 이런 기능이 있다라고 적어둔 설계도 설명서 정도로 이해하면 될 듯 싶다.
- 추상 메서드
- abstract 리턴타입 메서드이름(매개변수, ...);
-> 블록 {} 이 없음. 그냥 이런게 있다지 각각의 기능들은 각각의 클래스에서 구현할 것이라서
- 상속받은 클래스에서 추상 클래스의 추상 메서드는 반드시 오버라이딩 되어야한다.
- 인터페이스
- 두 객체를 연결해주는 다리 역할
- 상속 관계가 없는 다른 클래스들이 서로 동일한 행위 즉, 메서드를 구현해야할 때 인터페이스는 구현 클래스들의 동일한 사용 방법과 행위를 보장
- 스팩이 정의된 메서드들의 집합
- 인터페이스의 구현 클래스들은 반드시 정의된 메서드들을 구현해야함
- 구현 클래스들의 동일한 사용 방법과 행위를 보장
-> 인터페이스에 다형성을 적용
- interface 키워드를 사용하여 선언
- 멤버
- 모든 멤버변수는 public static final
- 모든 메서드는 public abstract
- 생략 가능 (static 메서드와 default 메서드 예외)
- 생략되는 제어자는 컴파일러가 자동으로 추가
- implements 키워드를 사용하여 구현
- 인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩 되어야함.
- 인터페이스의 추상 메서드를 일부만 구현해야 한다면 해당 클래스를 추상 클래스로 변경
- 상속은 extends 키워드를 사용
=> 아니 근데 이러면 인터페이스랑 추상클래스랑의 차이가 뭐냐?
- 추상클래스는 미완성의 설계도, 인터페이스는 기본 설계도
- 추상 클래스는 상속을 위한 클래스라서 객체를 따로 생성할 수 없음
- 인터페이스는 다중 상속 (구현) 이 가능
- 해당 클래스의 구분을 추상클래스 상속을 통해 해결, 할 수 있는 기능들을 인터페이스로 구현
- 추상클래스 사용 시기 : 상속 관계를 쭉 타고 올라갔을때 같은 조상클래스를 상속하는데 기능까지 완벽히 똑같은 기능이 필요한 경우
- 인터페이스 사용 시기 : 상속 관계를 쭉 타고 올라갔을때 다른 조상클래스를 상속하는데 같은 기능이 필요할 경우 인터페이스 사용
=> 즉, 현대자동차, 테슬라자동차, 포드자동차처럼 자동차라는 같은 부모 클래스를 가지고 있고, 그 부모의 기능 (시동걸기, 브레이크, 액셀 등) 이 꼭 필요하면 추상클래스
=> 자동차, 비행기, 기차처럼 부모 클래스가 다르지만 같은 기능 (문 열기, 에어컨, 불 켜기 등) 을 구현하고 싶으면 인터페이스
=> 차이점 잘 알아둘 것
- 자동 타입 변환
- 강제 타입 변환
- 구현객체타입 변수 = (구현객체타입) 인터페이스변수;
HW3
Main.java
package hw.week3;
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator(new AddOperation());
System.out.println(calculator.calculate(8, 2));
calculator = new Calculator(new MultiplyOperation());
System.out.println(calculator.calculate(13,21));
calculator = new Calculator(new SubstractOperation());
System.out.println(calculator.calculate(20, 9));
calculator = new Calculator(new DivideOperation());
System.out.println(calculator.calculate(20150, 50));
calculator = new Calculator(new RemainOperation());
System.out.println(calculator.calculate(179, 50));
}
}
AbstractOperation.java
package hw.week3;
public abstract class AbstractOperation {
public abstract double operate(int firstNumber, int secondNumber);
}
Calculator.java
package hw.week3;
import java.util.Objects;
public class Calculator {
private AbstractOperation operation;
public Calculator(AbstractOperation operation) {
this.operation = operation;
}
// public void setOperation(AbstractOperation operation) {
// this.operation = operation;
// }
public double calculate(int firstNumber, int secondNumber) {
double answer = 0;
answer = operation.operate(firstNumber, secondNumber);
return answer;
}
}
AddOperation.java
package hw.week3;
public class AddOperation extends AbstractOperation {
@Override
public double operate(int firstNumber, int secondNumber) {
return firstNumber + secondNumber;
}
}
SubstractOperation.java
package hw.week3;
public class SubstractOperation extends AbstractOperation {
@Override
public double operate(int firstNumber, int secondNumber) {
return firstNumber - secondNumber;
}
}
MultiplyOperation.java
package hw.week3;
public class MultiplyOperation extends AbstractOperation {
@Override
public double operate(int firstNumber, int secondNumber) {
return firstNumber * secondNumber;
}
}
DivideOperation.java
package hw.week3;
public class DivideOperation extends AbstractOperation {
@Override
public double operate(int firstNumber, int secondNumber) {
return (double)firstNumber / secondNumber;
}
}
RemainOperation.java
package hw.week3;
public class RemainOperation extends AbstractOperation {
@Override
public double operate(int firstNumber, int secondNumber) {
return firstNumber % secondNumber;
}
}