객체지향 프로그래밍 : OOP
public / private / protectedprivate 속성을 공유할 때는 getter / setter 메소드 구현 extends 사용 implements 사용 클래스와 객체의 구조와 관계
클래스 : 멤버(필드+메소드) 설계도
객체 : 클래스 모양대로 찍어낸 실체
객체의 상호 작용
주요 관계 종류
UML : Unified Markup Language
생성자(Constructor) 기본과 호출 순서
생성자 : 객체가 생성될 때 초기화를 위해 실행되는 메소드
생성자의 특징
강아지 a = new 강아지(); // 생성자 호출 강아지 b = new 강아지("댕댕이"); // 이름을 "댕댕이"로 설정할 수 있는 생성자 오버로딩public class Circle {
int radius;
String name;
public Circle() { // 매개변수 없는 생성자, 기본 생성자
radius = 1;
name = "";
}
public Circle(int r, String n) { // 매개변수를 가진 생성자
radius = r;
name = n;
}
public double getArea() {
return 3.14*radius*radius;
}
public static void main(String[] args) {
Circle pizza = new Circle(10, "자바피자"); // Circle 객체 생성, 반지름 10
double area = pizza.getArea();
System.out.println(pizza.name + "의 면적은 " + area);
Circle donut = new Circle(); // Circle 객체 생성, 반지름 1(초기값)
donut.name = "도넛피자";
area = donut.getArea();
System.out.println(donut.name + "의 면적은 " + area);
}
}

public class Book {
String title;
String author;
public Book() { // 기본 생성자
}
public Book(String t) { // 제목으로 초기화하는 생성자
this.title = t;
this.author = "작가미상";
}
public Book(String t, String a) { // 제목과 저자 이름으로 초기화하는 생성자
this.title = t;
author = a;
}
public static void main(String[] args) {
Book book = new Book();
Book littlePrince = new Book("어린왕자", "생텍쥐페리");
Book loveStory = new Book("춘향전");
System.out.println(littlePrince.title + " " + littlePrince.author);
System.out.println(loveStory.title + " " + loveStory.author);
}
}

Q. 왜 인자가 하나인 생성자에서는 this.author을 사용하고 인자 두 개인 생성자에서는 author을 사용한 거지?
this. ⇒ 현재 객체에 속한 것이라는 걸 알려주는데, 여기서는 멤버 변수랑 매개변수가 같으니까 굳이 사용을 안 해도 됨
super() 사용 super()가 생김 super(), super(parameter)
메소드 오버로딩(Overloading)과 오버라이딩(Overriding)
메소드 : 접근 지정자 + 리턴 타입 + 메소드 이름 + 메소드 인자들 + 메소드 코드
자바의 모든 메소드는 반드시 클래스 안에 있어야 함 ⇒ 캡슐화 원칙
같은 클래스나 상속 관계에서 동일한 이름의 메소드를 중복으로 작성하는 것
서브 클래스에서 슈퍼 클래스에 있는 메소드와 동일한 이름의 메소드 재작성
슈퍼 클래스에 구현된 메소드를 무시하고 서브 클래스에서 새로운 기능의 메소드를 재정의, 다형성 실현
메소드 이름, 매개변수 타입과 개수, 리턴 타입 모두 동일
서브 클래스에 오버라이딩된 메소드가 실행되는 동적 바인딩 발생
접근 제한을 더 강하게 오버라이딩 하는 것은 불가
public을 default / private으로 수정 불가default을 public으로는 수정 가능 접근 지정자
클래스나 멤버의 공개 범위 설정
→ 클래스나 일부 멤버를 공개/비공개하여 다른 클래스에서 접근하는 것을 제한할 수 있음
private : 같은 클래스 내에서만 접근 가능, 외부로부터 완벽 차단 default : 명시하지 않으면 동일 패키지 내에서만 접근 가능 protected : 동일 패키지와 자식 클래스에서 접근 가능public : 모든 클래스에서 접근 가능 
앞으로 주요 변수는 private으로 설정하고 auto-generation으로 getter, setter을 만들어두자!
getter : 값을 가져오는 메소드리턴 타입 getFieldName() {} setter : 값을 세팅하는 메소드void setFieldName(타입 변수) {} static / final / 싱글톤
static 멤버
static 공간에 할당됨this 사용 불가non-static 멤버
static의 활용
① 전역 변수와 전역 함수를 만들 때
② 공유 멤버를 작성할 때
하나의 어플리케이션 내에서 단 하나만 생성되도록 만든 객체
private 접근 제한자를 생성자 앞에 붙임 private 접근 제한자를 붙여 외부에서 필드 값 변경할 수 없도록 getInstance()를 선언하여 접근하도록 함public class 하늘 {
private static 하늘 instance = null;
private 하늘() {
System.out.println("하늘 객체를 만듭니다!");
}
public static 하늘 getInstance() {
if (instance == null) instance = new 하늘();
return instance;
}
}
public class Main {
public static void main(String[] args) {
//하늘 sky1 = new 하늘();
하늘 sky1 = 하늘.getInstance();
하늘 sky2 = 하늘.getInstance();
하늘 sky3 = 하늘.getInstance();
System.out.println(sky1);
System.out.println(sky2);
System.out.println(sky3);
}
}

final 클래스 : 클래스 상속 불가
final 메소드 : 오버라이딩 불가
final 상수 선언 - 선언 시 초기값 지정해야 하며, 실행 중 값 변경 불가
추상 클래스와 인터페이스
실체 클래스 생성 목적이 아닌, 상속용/참조용 클래스
실체 클래스들의 공통되는 필드와 메소드만 정의해둔 것
→ 구체적인 행위는 상속받은 클래스마다 다를 수 있으므로 추상적인 개체를 표현해야 함
비슷한 점
다른 점
인터페이스 Review
interface A{
public void a();
}
interface B{
public void b();
}
interface C extends A,B { // 인터페이스 다중 상속 가능
public default void c() {
System.out.println("인터페이스에서 바디가 있는 디폴트 메소드를 가질 수 있다");
}
}
abstract class E {
public abstract void e();
}
class D extends E implements C {
// 위에서 상속받은 모든 것들이 구현되어 있어야 함
public void a() {}
public void b() {}
public void c() {}
public void e() {}
}
public class Main {
public static void main(String[] args) {
A a = new D();
D d = new D();
}
}
→ 인터페이스는 클래스에게 어떤 기능을 반드시 구현해야 한다는 책임을 부여함
→ 인터페이스는 인터페이스를 상속(extends)한다고 하지만, 클래스는 인터페이스를 구현(inplements)한다고 함
→ 일반 클래스는 추상 메소드를 가질 수 없기 때문에 모두 구현해야 함
→ main에서는 두 경우 모두 D 객체를 생성하는 것은 맞지만, 앞에 있는 참조 타입(A, D)에 따라 접근할 수 있는 멤버가 달라짐

interface RemoteRole {
// 인터페이스는 상수와 static만 가질 수 있음
public static final int MAX_VOL = 10;
public static final int MIN_VOL = 0;
public void turnOn();
public void turnOff();
public void set_vol(int vol);
//어떤 객체든 같은 동작을 한다면 default로 바디가 있는 메소드를 정의
public default int get_vol() {
return 0;
}
public default void set_MUTE() {
System.out.println("무음처리합니다.");
this.set_vol(0);
}
}
public class Audio implements RemoteRole{
private int volume;
@Override
public void turnOn() {
// TODO Auto-generated method stub
System.out.println("AUDIO를 켭니다.");
}
@Override
public void turnOff() {
// TODO Auto-generated method stub
System.out.println("AUDIO를 끕니다.");
}
@Override
public void set_vol(int vol) {
// TODO Auto-generated method stub
this.volume = vol;
System.out.println("AUDIO의 volume을 " + vol + "로 setting합니다.");
}
@Override
public int get_vol() {
// TODO Auto-generated method stub
return this.volume;
}
}
→ 다른 클래스에서 private인 volume 자체에 접근할 수는 없지만, public인 메소드를 통해 접근 가능
public class Car implements RemoteRole{
private int volume;
@Override
public void turnOn() {
// TODO Auto-generated method stub
this.시동걸기();
}
@Override
public void turnOff() {
// TODO Auto-generated method stub
this.시동끄기();
}
@Override
public void set_vol(int vol) {
// TODO Auto-generated method stub
this.volume = vol;
}
public void 시동걸기() {
System.out.println("시동을 켭니다.");
}
public void 시동끄기() {
System.out.println("시동을 끕니다.");
}
public void move() {
System.out.println("부릉부릉~");
}
}
→ RemoteRole의 turnOn 요청은 Car에서 시동걸기 동작으로 수행되고, turnOff 요청은 Car에서 시동끄기 동작으로 수행됨
public class TV implements RemoteRole{
private int volume;
@Override
public int get_vol() {
// TODO Auto-generated method stub
return this.volume;
}
@Override
public void set_MUTE() {
// TODO Auto-generated method stub
System.out.println("TV를 무음처리합니다.");
set_vol(0);
}
@Override
public void turnOn() {
// TODO Auto-generated method stub
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
// TODO Auto-generated method stub
System.out.println("TV를 끕니다.");
}
@Override
public void set_vol(int vol) {
// TODO Auto-generated method stub
this.volume = vol;
System.out.println("TV의 volume을 " + vol + "로 setting합니다.");
}
}
public class MyRemoteControl implements RemoteRole{
RemoteRole rc = null;
public MyRemoteControl(TV tv) {
this.rc = tv;
}
public MyRemoteControl(Audio au) {
this.rc = au;
}
public MyRemoteControl(RemoteRole rc) {
this.rc = rc;
}
// 장치 변경 메소드 방법 1
// 하나의 메소드에서 여러 객체를 구분하는 동작
public void changeMode(Object o) {
System.out.println("인자를 Object로 받아 변경합니다.");
if (o instanceof Audio) {
System.out.println("Audio Mode로 바꿉니다!");
this.rc = (Audio) o;
} else if(o instanceof TV) {
System.out.println("TV Mode로 바꿉니다!");
this.rc = (TV) o;
} else System.out.println("Car Mode로 바꿉니다!")
}
// 장치 변경 메소드 방법 2
// 메소드 인자로 구분된 메소드마다 각각의 동작
public void changeMode(Audio au) {
System.out.println("Audio Mode로 바꿉니다!");
this.rc = au;
}
public void changeMode(TV tv) {
System.out.println("TV Mode로 바꿉니다!");
this.rc = tv;
}
public void changeMode(Car car) {
System.out.println("Car Mode로 바꿉니다!");
this.rc = car;
}
@Override
public void turnOn() {
// TODO Auto-generated method stub
this.rc.turnOn();
}
@Override
public void turnOff() {
// TODO Auto-generated method stub
this.rc.turnOff();
}
@Override
public void set_vol(int vol) {
// TODO Auto-generated method stub
this.rc.set_vol(vol);
}
@Override
public int get_vol() {
// TODO Auto-generated method stub
return this.rc.get_vol();
}
}
→ 방법2가 모듈화+OOP에 더 적합함, 새로운 장치가 추가되면 메소드만 추가하면 됨
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
// test1: 리모컨 인터페이스로 연결한 각 객체들의 동작
RemoteRole rm; // 인터페이스 rm 선언
TV tv = new TV();
Audio au = new Audio();
Car car = new Car();
rm = tv; // rm을 이용해 TV 동작
rm.turnOn();
rm.set_MUTE();
rm.set_vol(3);
System.out.println("-------------------------------");
rm = au; // rm을 이용해 Audio 동작
rm.turnOn();
rm.set_vol(5);
rm.set_MUTE();
System.out.println("-------------------------------");
// test2: 리모컨 객체를 통해 연결된 객체들의 동작
MyRemoteControl my_rc = new MyRemoteControl(au); // 리모컨 객체 my_rc 생
System.out.println("audio sound : " + my_rc.get_vol());
my_rc.set_vol(9);
System.out.println("audio sound : " + my_rc.get_vol());
my_rc.changeMode(car);
my_rc.turnOn();
System.out.println("-------------------------------");
my_rc.changeMode((Object)tv);
my_rc.turnOn();
}
}

다형성 동작 매커니즘 : 업캐스팅과 오버라이딩
자식 클래스의 객체를 부모 클래스 타입으로 바라보는 것

→ 오버라이딩은 덮어쓴 거라서 원래 내용을 쓰려면 super 사용해야 함
→ super을 사용하면 바로 상위 클래스로만 갈 수 있어서 할머니처럼요리하기()를 해도 바로 할머니한테 가는 게 아니라 엄마한테 감, 엄마의 할머니처럼요리하기() 안의 super을 사용해서 할머니의 요리하기()가 실행됨

→ 요리하기()는 이름도 같고 매개변수도 같은 걸 자식이 또 쓰니까 오버라이딩
→ 핸드폰뺏기(짱구), 핸드폰뺏기(엄마)는 이름은 같지만 매개변수가 다르니까 오버로딩
Q. 짱구는 엄마를 상속받았는데 핸드폰뺏기(짱구)는 어떻게 되는 거지?
내가 코드에 꼭 적지는 않았어도, 짱구는 핸드폰뺏기(짱구)도 엄마로부터 상속받음 그래서 지금 짱구 코드에 이 메소드를 적지는 않았지만 테스트코드에서 실행하려고 하면 할 수는 있음
람다식(λ-Expression)
이름 없는 함수를 간단히 표현하는 문법
표현 방법
① 인자가 없을 때 : () -> {...}
② 인자가 하나일 때 : (매개변수) -> {...}
③ 인자가 두 개 이상일 때 : (매개변수1, 매개변수2) -> {...}
람다식을 이용한 함수의 구현과 호출
1. 인터페이스에서 메소드 '틀'만 정의 → 추상 메소드
2. 람다식으로 메소드 구현
3. 메소드 호출
interface Hello {
void sayHello(); // 메소드 정의
default void sayBye() {
System.out.println("이제 집에 가요~");
}
}
interface Square {
int calc(int x); // 메소드 정의
}
interface Calculator {
int operate(int a, int b); // 메소드 정의
}
public class LambdaTest {
public static void main(String[] args) {
Hello h = () -> {
System.out.println("안녕하세요!");
};
h.sayHello();
h.sayBye();
Square s = (x) -> x*x; // 메소드 구현
System.out.println(s.calc(10)); // 메소드 호출
Calculator add = (x,y) -> x+y;
System.out.println(add.operate(3, 2));
Calculator sub = (x,y) -> x-y;
System.out.println(sub.operate(3, 2));
}
}
→ 인터페이스 안에서 인자가 없는 것을 두 개 만드려면 위에서처럼 default를 사용해야 함, 그렇지 않으면 읽지 못함
→ 핵심 동작만 표현함으로써 객체 생성 없이 유연하게 함수를 정의할 수 있게 됨