본문 바로가기

개발 일지/TIL

[ #10 ] TIL

✏️ 0426      


[ 특강 ] 알고리즘 세션 2 (심화)

코딩 테스트 연습 ( 알고리즘, SQL )

Java 문법 종합반 강의_3주차


 

코딩 테스트 연습

 

오늘은 알고리즘 문제도 SQL 문제도 푸는데 꽤 힘들었다

코드 자체는 쉬운데 분명 지금 배우는 것보다 간단한 건데

개념이 아닌 문제 풀이라 그런가 응용이 약해서 그런지 시간도 꽤 걸렸다

 

다행인 건 사용할 수 있는 코드들이나 특정 상황에 쓰는 코드들 같은 걸

잊어버리지 않고 기억하고 사용해서 써먹었다는 걸 위안 삼았다

 

자세한 건 알고리즘, SQL 카테고리 쪽에서...

 

1시간이라는 제한이 있으니까

타임어택 하듯이 풀게 되서 지루하지 않고 재밌는 것 같다! 🫠

 

 

Java 문법 종합반_3주차

    3주차... 이녀석! 널 여기에 적는 것도 오늘이 마지막이다...!!

 

상속
부모 클래스의 필드와 메서드를 자식 클래스에게 물려주는 것

 

 

상속을 사용하면 코드의 중복 제거, 재사용성 크게 증가, 유지 보수성에 매우 유리하다

 

public class 자식클래스 extends 부모클래스 {  }

 

클래스 간의 상속에는 extends 키워드를 사용해서 정의할 수 있다

여기서 한 가지 중요한 건, 부모 클래스보다 자식 클래스가 더 크다

 

 

  • 부모 클래스에 새로운 필드와 메서드가 추가되면 자식 클래스는 상속 받아서 사용할 수 있다
  • 자식 클래스에 새로운 필드와 메서드가 추가되어도 부모 클래스는 영향 받지 않는다
  • 자식 클래스의 멤버 개수는 부모 클래스보다 항상 같거나 많다

 

Car.java : 부모 클래스

SportsCar.java : 자식 클래스

// 부모
public class Car { // 내용 생략 }
// 자식
public class SportsCar extends Car{
    String engine;
    public void booster() {
        System.out.println("엔진 " + engine + " 부앙\n");
    }
}

 

 

Main. java

// 부모 클래스 객체에서 자식 클래스 멤버 사용
Car car = new Car();
// car.engine = "Orion"; // 오류
// car.booster(); // 오류

// 자식 클래스 객체에서 부모 클래스 멤버 사용
sportsCar.company = "GENESIS";
sportsCar.setModel("GV80");
System.out.println("sportsCar.company = " + sportsCar.company);
System.out.println("sportsCar.getModel() = " + sportsCar.getModel());

 

오류가 일어나는 이유

자식 클래스 안에서 생성된 멤버는 부모 클래스 안에 없기 때문에 오류가 날 수 밖에 없다

 

하지만 그 반대인 부모 클래스 안에 생성된 멤버는 자식 클래스에 사용이 가능하다

부모 클래스에게 상속 받기 때문에 같이 이용 가능

 

헷갈린다면 위의 그림을 생각하면서 천천히 생각해보자

 

 

클래스 간의 관계

 

  • 상속관계 : is - a ( " ~ 은 ~ (이)다 " )
  •    ex ) 고래는 포유류다
  • 포함관계 : has - a ( " ~ 은 ~ 을(를) 가지고 있다 " )
  •    ex ) 자동차는 타이어를 가지고 있다

Car.java

Tire[] tire;

public void setTire(Tire ... tire) {
    this.tire = tire;
}

 

Tire.java

public class Tire {
    String company; // 타이어 회사
    double price; // 타이어 가격

    public Tire(String company, double price) {
        this.company = company;
        this.price = price;
    }
}

 

Main.java

// 자동차 객체 생성
Car car = new Car("GV80", "Black", 50000000);

Tire[] tires = new Tire[]{
        new Tire("KIA", 150000), new Tire("금호", 150000),
        new Tire("Samsung", 150000), new Tire("LG", 150000)
};

// 자동차 객체에 부품 등록
car.setTire(tires);

// 등록된 부품 확인하기
for (Tire tire : car.tire) {
    System.out.println("tire.company = " + tire.company);
}

 

간단하게 Tire 관련 부분만 모아봤다

 

Main 에서 Tire 를 4개 생성했고

car 객체의 setTire 메서드를 통해 Tire 배열에 추가

그리고 반복문을 통해 Trie 배열을 순서대로 출력한다

 

해당 부분은 포함 관계에 속한다

 

 

단일 상속 & 다중 상속

 

Java 는 다중 상속을 허용하지 않는다

클래스 간의 관계가 복잡해지는 문제가 발생하기 때문이다

 

자식 클래스에서 상속받는 서로 다른 부모 클래스들이 같은 이름의 멤버를 가지고 있다면

자식 클래스에서는 이 멤버를 구별할 수 있는 방법이 없다는 문제가 발생하게 된다

 

 

final 클래스 & final 메서드

 

public final class Car { }
...
public class SportsCar extends Car{} // 오류 발생

 

클래스에 final 키워드를 선언하면 최종 클래스가 되므로 더 이상 상속할 수 없는 클래스가 된다

부모의 클래스에 선언 되면 자식 또한 사용할 수 없게 된다

 

public class Car {
    public final void horn() {
        System.out.println("빵빵");
    }
}
...
public class SportsCar extends Car{
    public void horn() { // 오류 발생
        super.horn();
    }
}

 

메서드에 final 키워드를 선언하면 최정 메서드가 됨으로 더 이상 오버라이딩 할 수 없는 메서드가 된다

 

 

Object

 

Object
객체
보통 Object 클래스를 의미

 

 

Object 클래스는 Java 내 모든 클래스들의 최상위 부모 클래스이다

모든 클래스는 Object 의 메서드를 사용할 수 있고,

부모 클래스가 없는 자식 클래스는 컴파일러에 의해 자동으로 Object 클래스를 상속 받게 된다

 

  • Object 클래스의 메서드 
    • Object clone() : 해당 객체의 복사본을 생성 반환
    • boolean equals(Object object) : 해당 객체와 전달받은 객체가 같은지 여부 반환
    • Class getClass() : 해당 객체의 클래스 타입 반환
    • int hashCode() : 자바에서 객체를 식별하는 정수값인 해시 코드 반환
    • String toString() : 해당 객체의 정보를 문자열로 반환

 

오버라이딩

 

오버라이딩
부모 클래스로부터 상속받은 메서드의 내용을 재정의 하는 것

 

 

 

부모 클래스의 메서드를 그대로 사용 가능하지만

자식 클래스의 상황에 맞게 변경해야 하는 경우 오버라이딩을 사용

 

  • 조건
    • 선언부가 부모 클래스의 메서드와 일치
    • 접근 제어자를 부모 클래스의 메서드보다 좁은 범위로 변경 불가능
    • 예외는 부모 클래스의 메서드보다 많이 선언 불가능

 

Car.java : 부모 클래스

public void horn() {
    System.out.println("빵빵");
}

 

SportsCar.java : 자식 클래스

public class SportsCar extends Car{
    String engine;
    public void booster() {
        System.out.println("엔진 " + engine + " 부앙\n");
    }
    
   @Override
    public void horn() {
        booster();
    }
}

 

부모 클래스의 메서드를 가져와서 자식 클래스에서 재정의

 

Main.java

// 부모 클래스 자동차 객체 생성
Car car = new Car();
car.horn();  // 빵빵

// 자식 클래스 스포츠카 객체 생성
SportsCar sportsCar = new SportsCar("Orion");
sportsCar.horn();  // 엔진 Orion 부앙

 

 

super & super()

 

이미 우리는 비슷한 걸 본 적이 있다

기억 안 난다면 다시 복습!

 

super
부모 클래스의 멤버를 참조할 수 있는 키워드

 

 

객체 내부 생성자 및 메서드에서 부모 클래스의 멤버에 접근하기 위해서 사용

자식 클래스 내부에서 선언한 멤버와 부모 클래스에서 상속받은 멤버와 이름이 같을 경우 구분하기 위해 사용

 

 

SportsCar.java : 자식 클래스

// 자식 클래스의 초기값
String model = "Ferrari";
String color = "Red";
double price = 300000000;

public void setCarInfo(String model, String color, double price) {
    super.model = model; // 부모 필드의 model
    super.color = color; // 부모 필드의 color
    this.price = price; // 자식 필드의 price
}

 

Main.java

// 자식 클래스 스포츠카 객체 생성
SportsCar sportsCar = new SportsCar("Orion");

// setCarInfo 메서드 호출해서 부모 및 자식 필드 값 저장
sportsCar.setCarInfo("GV80", "Black", 50000000);

System.out.println("sportsCar.model = " + sportsCar.model); // Ferrari
System.out.println("sportsCar.color = " + sportsCar.color); // Red
System.out.println("sportsCar.price = " + sportsCar.price); // 5.0E7

 

model 과 color 부분은 부모의 필드에서 저장하고 가져옴으로 자식의 model, color 값은 변화가 없다

 

 

super()
부모 클래스의 생성자를 호출할 수 있는 키워드 

 

 

객체 내부 생성자 및 메서드에서 해당 객체의 부모 클래스의 생성자를 호출하기 위해 사용

자식 클래스의 객체가 생성될 때 부모 클래스들이 모두 합쳐져서 하나의 인스턴스가 생성

 

주의할 점, 부모 클래스의 멤버들의 초기화 작업이 먼저 수행되어야 한다

부모 클래스의 생성자는 가장 첫 줄에서 호출 되어야 한다

 

// 자식 클래스 SportsCar 생성자
public SportsCar(String model, String color, double price, String engine) {
     // this.engine = engine; // 오류 발생
    super(model, color, price);
    this.engine = engine;
}

 

자식 클래스 객체를 생성할 때 생성자 매개변수에 매개값을 받아와 super() 를 이용해

부모 생성자의 매개변수에 값을 전달하여 호출하면서 부모 클래스의 멤버를 먼저 초기화

 

오버로딩된 부모 클래스의 생성자가 없다고 하더라도 부모 클래스의 기본 생성자를 호출해야 한다

컴파일러가 super(); 자식 클래스 생성자 첫 줄에 자동으로 추가해준다

 

 

참조 변수의 타입 변환

 

  • 자동 타입 변환

부모 타입 변수 = 자식 타입 객체;

Mammal mammal = new Whale();

 

자식 객체는 부모 객체의 멤버를 상속 받기 때문에 부모와 동일하게 취급될 수 있다

주의할 점, 부모 타입 변수로 자식 객체의 멤버에 접근할 때는 부모 클래스에 전언된 상속받은 멤버만 접근 가능하다

 

Mammal.java : 부모 클래스

class Mammal {
    public void feeding() {
        System.out.println("부모 클래스");
    }
}

 

Whale.java : 자식 클래스

class Whale extends Mammal {
    public void swimming() {
        System.out.println("자식 / 수영하다.");
    }
    @Override
    public void feeding() {
        System.out.println("자식 클래스");
    }
}

 

Main.java

public static void main(String[] args) {
    // Whale 은 Mammal 을 상속받고 있기 때문에 타입 변환 가능
    Mammal mammal = new Whale();

    // 부모 클래스에 선언되지 않은(오버라이딩되지 않은)swimming 은 사용 불가
    // mammal.swimming(); // 오류 발생

    // 부모타입의 객체는 자식타입의 변수로 변환 불가
    // Whale whale = new Mammal(); // 오류 발생

    mammal.feeding();  // 자식 클래스
}

 

요약하면 이런 느낌이다

 

부모 = 자식

자식 = 부모  >> 오류 발생

 

 

  • 강제 타입 변환

자식 타입 변수 = (자식 타입) 부모 타입 객체;

Whale whale = (Whale) mammal;

 

부모 타입 객체는 자식 타입 변수로 자동으로 타입 변환이 되지 않는다

타입 변환 연산자를 사용해서 강제로 자식 타입으로 변환할 수 있다

 

// 자식 타입 객체가 자동 타입변환된 부모타입의 변수
Mammal mammal = new Whale();
mammal.feeding();

// 자식객체의 수영 기능을 사용하고 싶다면
// 다시 자식타입으로 강제 타입변환
Whale whale = (Whale) mammal;
whale.swimming();

 

무조건 강제 타입 변환을 할 수 있는 건 아니다

 

자식 타입 객체가 부모 타입으로 자동 타입 변환된 후

다시 자식 타입으로 변환될 때만 강제 타입 변환이 가능

 

부모 타입 변수로는 자식 타입 객체의 고유한 멤버를 사용할 수 없기 때문에

사용이 필요한 경우가 생겼을 때 강제 타입 변환을 사용한다

Mammal newMammal = new Mammal();
Whale newWhale = (Whale) newMammal; // ClassCastException 발생

 

자동 타입 변환된 부모 타입 변수가 아닌

부모 객체를 자식 타입의 변수로 강제 타입 변환하려고 하면 오류가 발생한다

 

강제 변환의 경우는

자동 형변환 완료된 것만 사용할 수 있어서 기능은 제한적이고 잘 사용하지 않을 듯 하다고 한

 

 

다형성

 

여러 가지 형태를 가질 수 있는 능력을 의미

 

자동 타입 변환을 선언해서 자동 타입 변환된 변수를 사용해서

각각의 객체에 재정된 메서드를 통해 다양한 값을 가진 객체를 생성할 수 있다

 

Car car1 = new Car(new KiaTire("KIA"));
Car car2 = new Car(new HankookTire("HANKOOK"));

 

이 코드를 풀어보면 이렇게 된다

Tire kiaSampleTire = new Tire("KIA");
Tire HanSampleTire = new Tire("HANKOOK");

Car car1 = new Car(kiaSampleTire);
Car car2 = new Car(HanSampleTire);

 

Car 생성자에서 매개변수의 타입이 부모 타이어이기 때문에 자식 타이어 객체들을 매개값으로 전달할 수 있다

 

 

또한, 반환 타입에도 다형성이 적용될 수 있다

반환 타입이 부모 타이어이기 때문에 자식 타이어 객체들을 반환값으로 지정 가능

자동 타입 변환이 된 반환값인 자식 타이어 객체를 강제 타입 변환할 수도 있다

 

Tire.java : 부모 클래스

public void rideComfort() {
    System.out.println(company + " 타이어 승차감은?");
}

 

HankookTire.java : 자식 클래스

@Override
public void rideComfort() {
    System.out.println(super.company + " 타이어 승차감은 " + 100);
}

 

KiaTire.java : 자식 클래스

@Override
public void rideComfort() {
    System.out.println(super.company + " 타이어 승차감은 " + 60);
}

 

Main.java

Car car1 = new Car(new KiaTire("KIA"));
Car car2 = new Car(new HankookTire("HANKOOK"));

car1.tire.rideComfort(); // KIA 타이어 승차감은 60
car2.tire.rideComfort(); // HANKOOK 타이어 승차감은 100

 

 

instanceof

 

다형성 기능으로 인해 해당 클래스 객체의 원래 클래스명을 체크하는 것이 필요한데

이때 사용할 수 있는 명령어가 instance of 

 

해당 객체가 내가 의도하는 클래스의 객체인지 확인할 수 있다

{ 대상 객체 } instance of { 클래스 이름 } : 응답값 boolean

public static void main(String[] args) {

    Parent pc = new Child();  // 다형성 허용 (자식 -> 부모)
    Parent p = new Parent();

    // p는 objsect의 인스턴스가 맞는지 물음
    System.out.println(p instanceof Object); // true 출력
    System.out.println(p instanceof Parent); // true 출력
    System.out.println(p instanceof Child);  // false 출력

    Parent c = new Child();
    System.out.println(c instanceof Object); // true 출력
    System.out.println(c instanceof Parent); // true 출력
    System.out.println(c instanceof Child);  // true 출력
}

 

 

추상 클래스

 

클래스가 설계도라면

추상 클래스는 미완성된 설계도

public abstract class 추상클래스명 { }
  • 추상 클래스
    • 추상 메서드를 포함할 수 있고 추상 메서드가 없어도 추상 클래스를 선언할 수 있다
    • 자식 클래스에 상속되어 자식 클래스에 의해서만 완성된다
    • 여러 개의 자식 클래스들에서 공통적인 필드나 메서드를 추출해서 만들 수 있다
    • abstract 키워드를 사용해서 추상 클래스 선언 가능

 

추상 메서드는 구현되지 않은 메서드

public abstract class 추상클래스명 {
		abstract 리턴타입 메서드이름(매개변수, ...);
}
  • 추상 메서드
    • 일반적인 메서드와 다르게 블록 { } 이 없다
    • 선만 할 뿐, 구현 부분은 가지고 있지 않는다
    • abstract 키워드를 사용해서 추상 메서드 선언 가능

 

추상 클래스의 상속

public class 클래스명 extends 추상클래스명 {
    @Override
    public 리턴타입 메서드이름(매개변수, ...) {
       // 실행문
    }
}

 

추상 메서드는 extends 키워드를 사용하여 클래스에 상속된다

상속받은 클래스에서 추상 클래스의 추상 메서드는 반드시 오버라이딩 되어야 한다

 

 

Car 라는 추상 클래스와 AudiCar, BenzCar, ZenesisCar 라는 클래스가 존재한다

클래스들의 코드 안에는 동일한 이름의 메서드지만 다른 값을 출력하는 메서드가 존재한다

// Car.java
public abstract class Car {
	public abstract void horn();
}

// BenzCar.java
public void horn() {
        System.out.println("Benz 빵빵");
}

// AudiCar.java
public void horn() {
        System.out.println("Audi 빵빵");
}

// GenesisCar.java
public void horn() {
        System.out.println("Zenesis 빵빵");
}

 

horn() 메서드를 추상 메서드로 선언해서 자식 클래스에서 재정의 될 수 있도록 해보자

 

public class AudiCar extends Car {
   @Override
    public void horn() {
        System.out.println("Audi 빵빵");
    }
}
public class BenzCar extends Car {
    @Override
    public void horn() {
        System.out.println("Benz 빵빵");
    }
}
public class ZenesisCar extends Car {
    @Override
    public void horn() {
        System.out.println("Zenesis 빵빵");
    }
}

 

오버라이딩을 해주고 Car 추상 클래스에 상속하는 내용을 추가해주었다

 

public static void main(String[] args) {
    Car car1 = new BenzCar();
    car1.horn(); // Benz 빵빵

    Car car2 = new AudiCar();
    car2.horn();  // Audi 빵빵

    Car car3 = new ZenesisCar();
    car3.horn();  // Zenesis 빵빵
}

 

결과 따란

 

 

인터페이스

 

두 객체를 연결해 주는 다리 역할

 

상속 관계가 없는 다른 클래스들이 서로 동일한 행위,

메스드를 구현해야 할 때 인터페이스는 구현 클래스들의 동일한 사용 방법과 행위를 보장해 줄 수 있다

  • 스팩이 정의된 메서드들의 집합
  • 구현 클래스들은 반드시 정의된 메서드들을 구현
  • 구현 클래스들의 동일한 사용 방법과 행위를 보장
  • 인터페이스에 다형성을 적용할 수 있게 된다

 

public interface 인터페이스명 { }

 

interface 키워드를 사용해서 선언할 수 있다

클래스와 마찬가지로 public, default 접근 제어자를 지정할 수 있다

 

모든 멤버 변수는  public static final 이어야 한다 (생략가능)

모든 메서드는 public abstract 이어야 한다 (생략가능 / static 와 default 메서드 예외)

생략되는 제어자는 컴파일러가 자동으로 추가

 

public class 클래스명 implements 인터페이스명 { 
        // 추상 메서드 오버라이딩
        @Override
	    public 리턴타입 메서드이름(매개변수, ...) {
           // 실행문
	    }
}

 

인터페이스는 추상 클래스와 마찬가지로 직접 인스턴스를 생성할 수 없기 때문에

클래스에 구현되어 생성된다

 

implements 키워드를 사용해서 인터페이스를 구현할 수 있다

인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩 되어야 한다

만약 인터페이스의 추상 메서드를 일부만 구현해야 한다면 해당 클래스를 추상 클래스로 변경해주면 된다

 

그리고 인터페이스 간의 상속이 가능하다

상속은 implements 가 아니라 extends 키워드를 사용한다

클래스와 다르게 다중 상속이 가능하다

 

interface A {
    void a();
}
class B implements A {
    @Override
    public void a() {
        System.out.println("B.a()");
    }
    public void b() {
        System.out.println("B.b()");
    }
}
class C extends B {
    public void c() {
        System.out.println("C.c()");
    }
}
// A 인터페이스에 구현체 B 대입
A a1 = new B();
a1.a();
// a1 은 인터페이스 A타입(자동 형변환)이기 때문에, a()메서드만 가지고 있다
// a1.b(); // 불가능 (오버라이딩 X)

// 강제 타입 변환
B b = (B) a1;
b.a();
b.b(); // 사용 가능

// A 인터페이스에 구현체 B를 상속받은 C 대입
// C는 다 가지고있는데 A로 형변환되서 a만 사용가능해졌다
A a2 = new C();
a2.a();
//a2.b(); // 불가능
//a2.c(); // 불가능

// 강제 타입 변환
C c = (C) a2;
c.a();
c.b(); // 사용 가능
c.c(); // 사용 가능

 

 

 

default 와 static 메서드

 

  • default 메서드
    • 추상 메서드의 기본적인 구현을 제공하는 메서드
    • 메서드 앞에 default 키워드를 붙이고 블럭 { } 이 존재해야 한다
    • 추상 메서드가 아니기 때문에 인터페이스의 구현체들에서 필수로 재정의할 필요는 없다
  • static 메서드
    • 인터페이스에서 static 메서드 선언 가능
    • static 의 특성 그대로 인터페이스의 static 메서드 또한 객체 없이 호출이 가능
    • 선언하는 방법과 호출하는 방법은 클래스의 static 메서드와 동일
    • 접근 제어자를 생략하면 컴파일러가 public 를 추가해준다
public class Main implements A {
    @Override
    public void a() {
        System.out.println("A");
    }
    public static void main(String[] args) {
        Main main = new Main();
        main.a();
        
        // default 메서드 재정의 없이 바로 사용가능
        main.aa();

        // static 메서드 aaa() 호출
        A.aaa();
    }
}
interface A {
    void a();
    default void aa() {
        System.out.println("AA");
    }
    static void aaa() {
        System.out.println("static method");
    }
}

 

 

인터페이스의 타입 변환

 

인터페이스 변수 = 구현객체;  => 자동 타입 변환

구현 객체 타입 변수 = (구현 객체 타입) 인터페이스 변수;  => 강제 타입 변환

 

public class Main {
    public static void main(String[] args) {
	// 자동 타입 변환
        // A 인터페이스에 구현체 B 대입
        A a1 = new B();
        a1.a();
        a1.b(); // 불가능
        
        // A 인터페이스에 구편체 B를 상속받은 C 대입
        A a2 = new C();
        a2.a();
        a2.b(); // 불가능
        a2.c(); // 불가능

	// 강제 타입 변환
        B b = (B) a1;
        b.a();
        b.b(); // 강제 타입변환 사용 가능

        C c = (C) a2;
        c.a();
        c.b(); // 강제 타입변환 사용 가능
        c.c(); // 강제 타입변환 사용 가능
    }
}
interface A {
    void a();
}
class B implements A {
    @Override
    public void a() {
        System.out.println("B.a()");
    }
    public void b() {
        System.out.println("B.b()");
    }
}
class C extends B {
    public void c() {
        System.out.println("C.c()");
    }
}

 

 

 

후기

 

슬슬 개인과제도 시작해야 할 것 같아서

남은 3주차를 다 들으려고 조금 무리했더니 좀 힘들었다

 

잘 할 수 있을지 조금 걱정이 되면서도

새로운 개인 과제를 할 생각에 조금 기대가 되기도 한다

 

배운 걸 다시 복습하면서 공부한다는 느낌으로 개인과제를 해보면 좋을 것 같다

'개발 일지 > TIL' 카테고리의 다른 글

[ #12 ] TIL (개인과제 Level 1)  (1) 2024.04.30
[ #11 ] TIL  (0) 2024.04.30
[ #9 ] TIL  (3) 2024.04.25
[ #8 ] TIL  (0) 2024.04.24
[ #7 ] TIL  (2) 2024.04.23