본문 바로가기
TIL/JAVA

23.05.23

by J1-H00N 2023. 5. 23.

2주차 숙제 진행 중 발생한 문제들

문제 발생

더보기
ArrayList<String> list = new ArrayList<String>(); // List 선택 시 ArrayList로 저장

            System.out.println("레시피 이름을 입력합니다.");
            String recipeName = scanner.nextLine();

            System.out.println("레시피를 입력합니다.");
            System.out.println("레시피 입력이 끝나면 '끝'을 입력해주세요");
            int i = 1; // 레시피가 몇 번까지 있는지 알기 위함.
            while (true) { // break가 없으면 무한 반복되도록
                String recipe = scanner.nextLine();
                if (recipe.equals("끝")) { // 끝을 입력하면 멈추고, 아니면 계속 입력
                    break;
                }
                list.add(i + ". " + recipe);
                i++; // 다음 레시피 번호 +1
            }

            System.out.println("[ " + collection + " 으로 저장된 " + recipeName + " 만들기 ]");
            for (int j = 0; j < i; j++) {
                System.out.println(list.get(j));
                }

이 상태에서 실행시 끝을 입력하면 IndexOutOfBoundsException 오류가 발생, 아마 반복문이 실행하는 크기가 list의 크기보다 커서 발생하는 문제 같음.

시도해본 것들

1. 일단 레시피가 제대로 작성됐는지 확인하기 위해 아래 코드를 추가하고 출력

System.out.println(list.toString());

결과는 제대로 나왔으므로 마지막 반복문의 문제로 생각하고 시도

2. 반복문이 없는 index를 출력하려고 해서 문제가 발생했다고 생각해 반복 범위를 j <= i로 줄여봤음.

왜 그랬나 모르겠는데 이건 오히려 늘리는 짓... 진짜 왜 그랬지

3. 반복문이 없는 index를 출력하려고 해서 문제가 발생했다고 생각해 반복 범위를 더 줄여봄 (j < i - 1)

=> 해결

 

해결 방법

i가 1부터 시작하기 때문에 레시피를 3줄 입력했다고 가정하면 리스트에 레시피를 추가하고 i에 1을 추가하기 때문에 i의 값은 4로 끝난다. 기존의 j < i로 실행하면 list.get(3)이 실행되는데 index는 3까지 밖에 없으므로 오류가 발생했던 것. 그래서 반복문의 범위를 j < i - 1로 수정했다.

 

알게 된 것

반복문이 출력하고자 하는 배열이나 리스트의 크기를 미리 확인하고 그에 맞춰 범위를 지정하는 것의 중요성

그냥 foreach문을 이용했어도 됐었다...

 

문제 발생

for (Integer key : map.keySet()) { // 목록에 있는 모든 값을 출력하기 위한 반복문
                for (String value : map.values()) {
                    System.out.println(key + ". " + value);
                }
            }

key와 value를 불러와서 붙이기만 하려고 위와 같이 만들었는데 실행하니

key1. value1

key1. value2

key1. value3

key2. value1

key2. value2

key2. value3

.

.

.

와 같이 value가 반복해서 출력되는 문제가 발생했다.

 

시도해본 것들

1. 어차피 키는 1부터 순서대로이고 그 순서 그대로 출력할 것이기 때문에 키에 따른 값을 출력하는 .get()과 기본 for반복문을 이용해

for (int j = 0; j < i - 1; j++) {
                System.out.println((j + 1) + ". " + map.get(j + 1));
            }

와 같이 수정 => 수정은 됐으나 지금처럼 key가 번호가 아닌 문자열 등의 형태라면 사용하지 못할 방법이므로 추후 다른 방안을 찾아볼 예정

해결 방안

임시 방안이긴 하나 기본 반복문과 .get()을 이용해 해결. 하지만 key가 다른 형태라면 제대로 해결이 안되므로 다른 방안 모색 예정

 

알게 된 것

for 중첩문을 이용할 때의 주의사항과 당장 해결이 됐다 하더라도 더 나은 방법을 찾아야 될 필요성

 

문제 발생

for (String value : set) { // 목록에 있는 모든 값을 출력하기 위한 반복문
                System.out.println(value);
            }

위 방법으로 반복문을 수행했을 때 Set에 추가한 순서나 번호에 상관없이 출력되는 문제가 발생함

 

시도해본 것들

1. List와 같이 기본 fori문을 이용해 반복하려 했으나 애초에 순서가 상관이 없어 n번째 자료를 가져오는 함수 자체를 지원하지 않아 실패

2. TreeSet을 써보기로함 => HashSet과의 차이를 모르겠으나 결과는 똑같이 무작위로 출력돼서 실패

 

해결 방법

Set 객체를 생성할 때 LinkedHashSet이라는 순서를 보장하는 Set을 쓰면 된다.

 

알게 된 점

HashSet과 TreeSet의 차이점(TreeSet이 범위 검색, 정렬에 장점을 가지지만 추가 삭제가 느리다)을 알게 되었고 순서를 보장하지 않는 Set 중에서 순서를 보장하는 LinkedHashSet을 알게 되었다.

 

위 과정을 통해 2주차 숙제 요리 레시피 메모장을 만들었다. 

↓ 완성 코드

더보기
package sparta_nbc.HW;
/*
요리 레시피 메모장 만들기

입력값
저장할 자료구조명을 입력합니다. (List / Set / Map)
내가 좋아하는 요리 제목을 먼저 입력합니다.
이어서 내가 좋아하는 요리 레시피를 한문장씩 입력합니다.
입력을 마쳤으면 마지막에 “끝” 문자를 입력합니다.

출력값
입력이 종료되면 저장한 자료구조 이름과 요리 제목을 괄호로 감싸서 먼저 출력 해줍니다.
이어서, 입력한 모든 문장앞에 번호를 붙여서 입력 순서에 맞게 모두 출력 해줍니다.

입력 예시
Set
백종원 돼지고기 김치찌개 만들기
돼지고기는 핏물을 빼주세요.
잘익은 김치 한포기를 꺼내서 잘라주세요.
냄비에 들기름 적당히 두르고 김치를 넣고 볶아주세요.
다진마늘 한스푼, 설탕 한스푼 넣어주세요.
종이컵으로 물 8컵 부어서 센불에 끓여주세요.
핏물 뺀 돼지고기를 넣어주세요.
된장 반스푼, 양파 반개, 청양고추 한개를 썰어서 넣어주세요.
간장 두스푼반, 새우젓 두스푼, 고춧가루 두스푼반 넣어주세요.
중불로 줄여서 오래 끓여주세요~!!
마지막에 파 쏭쏭 썰어서 마무리하면 돼요^^
끝

출력 예시
[ Set 으로 저장된 백종원 돼지고기 김치찌개 만들기 ]
1. 돼지고기는 핏물을 빼주세요.
2. 잘익은 김치 한포기를 꺼내서 잘라주세요.
3. 냄비에 들기름 적당히 두르고 김치를 넣고 볶아주세요.
4. 다진마늘 한스푼, 설탕 한스푼 넣어주세요.
5. 종이컵으로 물 8컵 부어서 센불에 끓여주세요.
6. 핏물 뺀 돼지고기를 넣어주세요.
7. 된장 반스푼, 양파 반개, 청양고추 한개를 썰어서 넣어주세요.
8. 간장 두스푼반, 새우젓 두스푼, 고춧가루 두스푼반 넣어주세요.
9. 중불로 줄여서 오래 끓여주세요~!!
10. 마지막에 파 쏭쏭 썰어서 마무리하면 돼요^^
*/

import java.util.*; // 어떤 형태의 자료구조든 사용하기 위해

public class HW_02 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in); // 입력값을 받기 위한 기본 상태
        System.out.println("저장할 자료구조 명을 입렵합니다 (List/Set/Map)");
        String collection = scanner.nextLine();

        if (collection.equals("List")) { // collection이 List일 때
            // 조건에 == 대신 .equals()를 사용한 이유는 참조형 변수는 실제 값이 아닌 주소를 반환하기 때문
            ArrayList<String> list = new ArrayList<>(); // List 선택 시 ArrayList로 저장

            System.out.println("레시피 이름을 입력합니다.");
            String recipeName = scanner.nextLine();

            System.out.println("레시피를 입력합니다.");
            System.out.println("레시피 입력이 끝나면 '끝'을 입력해주세요");
            int i = 1; // 레시피가 몇 번까지 있는지 알기 위함.
            while (true) { // break가 없으면 무한 반복되도록
                String recipe = scanner.nextLine();
                if (recipe.equals("끝")) { // 끝을 입력하면 멈추고, 아니면 계속 입력
                    break;
                }
                list.add(i + ". " + recipe);
                i++; // 다음 레시피 번호 +1
            }

            System.out.println("[ " + collection + " 으로 저장된 " + recipeName + " 만들기 ]");
            for (int j = 0; j < i - 1; j++) { // 현재 i의 값은 목록 수 + 1이므로 목록 수만큼만 반복하기 위해 1을 뺌
                System.out.println(list.get(j));
            }
        } else if (collection.equals("Set")) { // collection이 Set일 때
            Set<String> set = new LinkedHashSet<>(); // Set 선택 시 Set으로 저장

            System.out.println("레시피 이름을 입력합니다.");
            String recipeName = scanner.nextLine();

            System.out.println("레시피를 입력합니다.");
            System.out.println("레시피 입력이 끝나면 '끝'을 입력해주세요");
            int i = 1; // 레시피가 몇 번까지 있는지 알기 위함.
            while (true) { // break가 없으면 무한 반복되도록
                String recipe = scanner.nextLine();
                if (recipe.equals("끝")) { // 끝을 입력하면 멈추고, 아니면 계속 입력
                    break;
                }
                set.add(i + ". " + recipe);
                i++; // 다음 레시피 번호 +1
            }

            System.out.println("[ " + collection + " 으로 저장된 " + recipeName + " 만들기 ]");
            for (String value : set) { // 목록에 있는 모든 값을 출력하기 위한 반복문
                System.out.println(value);
            }
        } else if (collection.equals("Map")) { // collection이 Map일 때
            Map<Integer,String> map = new HashMap<>(); // Map 선택 시 Map으로 저장

            System.out.println("레시피 이름을 입력합니다.");
            String recipeName = scanner.nextLine();

            System.out.println("레시피를 입력합니다.");
            System.out.println("레시피 입력이 끝나면 '끝'을 입력해주세요");
            int i = 1; // 레시피가 몇 번까지 있는지 알기 위함.
            while (true) { // break가 없으면 무한 반복되도록
                String recipe = scanner.nextLine();
                if (recipe.equals("끝")) { // 끝을 입력하면 멈추고, 아니면 계속 입력
                    break;
                }
                map.put(i, recipe);
                i++; // 다음 레시피 번호 +1
            }

            System.out.println("[ " + collection + " 으로 저장된 " + recipeName + " 만들기 ]");
            for (int j = 0; j < i - 1; j++) {
                System.out.println((j + 1) + ". " + map.get(j + 1));
            }
        }
    }
}

 

반환타입 중 void는 반환값이 없음을 의미하므로 return문을 지정할 필요는 없다. 하지만 메서드가 return을 만나면 메서드가 종료되는데 이를 이용하여 void에서도 return;을 입력하여 원하는 지점에서 메서드를 종료시킬 수 있다.

 

기본 생성자를 호출하는 this()를 사용할 때 위에 다른 코드가 있으면 오류가 발생한다.

public Car(String model) {
    System.out.println("model = " + model);
    this(model, "Blue", 50000000);
}

 

상속

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

공통되는 코드를 관리하여 코드의 추가와 변경이 쉬워진다.

코드의 중복이 줄어들고 재사용성이 크게 증가하여 생산성과 유지보수성이 높아진다. <== 객체지향의 장점

 

상속 관계는 is - a(~~는 -- 이다)로 표현한다. (car - sports car)

포함 관계는 has - a(~~는 --을 가지고 있다)로 표현한다. (car - door, tire, handle, ... )

 

단일상속과 다중상속

java는 기본적으로 다중상속을 허용하지 않음

why? => 클래스간의 관계가 복잡해지기 때문, 자식 클래스에서 상속받는 부모 클래스들이 겹치는 멤버를 가지고 있다면 구분도 힘들어짐

 

클래스에 final을 선언하면 더 이상 상속 불가 (수정이 안되기 때문에 <= 이는 상속이 오버라이딩이라는 메서드를 재정의하는 기법이 디폴트인 방식인 것과 연관이 있다.)

 

부모 클래스가 없는 자식 클래스는 자동적으로 Object 클래스를 상속받게 된다.(최상위 부모 클래스)

 

오버라이딩(Overriding)

부모 클래스로부터 상속받은 메서드를 상황에 맞춰 재정의해 변경하는 경우에 사용

조건

  • 선언부가 부모클래스의 메서드와 일치해야 한다.
  • 접근 제어자를 부모 클래스의 메서드보다 좁은 범위로 바꿀 수 없다
  • 예외는 부모 클래스의 메서드보다 많이 선언할 수 없다

오버라이딩을 하면 메서드 위에 @Override라는 문구가 생기는데, 이처럼 @가 붙는 문구를

애노테이션(annotaion)이라고 한다.

 

super, super()

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

this와 비슷한데, this는 자신의 멤버를 지칭하기 위해 썼다면 super는 부모의 멤버를 지칭하기 위해 사용한다.

더보기
// 부모 클래스 Car
String model; // 자동차 모델
String color; // 자동차 색상
double price; // 자동차 가격
// 부모 클래스 Car 
public String getModel() { return model; }
public String getColor() { return color; }
public double getPrice() { return price; }
// 자식 클래스 SportsCar
String model = "Ferrari"; // 자동차 모델
String color = "Red"; // 자동차 색상
double price = 300000000; // 자동차 가격
public void setCarInfo(String model, String color, double price) {
    super.model = model; // model은 부모 필드에 set
    super.color = color; // color는 부모 필드에 set
    this.price = price; // price는 자식 필드에 set
}
sportsCar.setCarInfo("GV80", "Black", 50000000);

super에 의해 부모 필드의 model, color만 바뀌고 this에 의해 자식 필드의 price만 바뀐다

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
// .getModel() 등은 부모클래스로부터 상속받은 메서드이기 때문에 부모 클래스의 필드값을 가져온다
System.out.println("sportsCar.getModel() = " + sportsCar.getModel()); // GV80
System.out.println("sportsCar.getColor() = " + sportsCar.getColor()); // Black
System.out.println("sportsCar.getPrice() = " + sportsCar.getPrice()); // 0.0 // 초기값

 

super()은 this()와 비슷하게 생성자를 호출하는데 사용되는데, super와 this의 차이점처럼 super()도 부모의 생성자를 호출한다.

자식 클래스의 객체가 생성될 때 부모 클래스의 멤버들의 초기화 작업이 먼저 수행되어야 하므로 자식 클래스의 가장 첫 줄에 부모 클래스의 생성자가 자동으로 호출된다.(눈에 보이지는 않지만 자식 클래스의 가장 첫 줄에 super();가 자동으로 추가된다.)

 

다형성

다형성이 무엇인지 말하기 전에 자동 타입 변환과 강제 타입 변환을 짚고 넘어가야 한다.

 

자동 타입 변환

객체는 Mammal mammal = new mammal(); 와 같은 형태로 생성하는 것이 기본적인 형태이지만, 

Mammal whale = new Whale(); 와 같은 형태로 자식 클래스의 생성자로 생성 가능하다. 이 경우 Whale이 Mammal로 자동형변환된다. 단, 이와 같은 형태로 접근할 때는 상속받은 멤버만 접근 가능

더보기
class Mammal {
    // 포유류는 새끼를 낳고 모유수유를 한다.
    public void feeding() {
        System.out.println("모유수유를 합니다.");
    }
}
class Whale extends Mammal {
    // 고래는 포유류 이면서 바다에 살며 수영이 가능하다.
    public void swimming() {
        System.out.println("수영하다.");
    }

    @Override
    public void feeding() {
        System.out.println("고래는 모유수유를 합니다.");
    }
}
// 고래는 포유류이기 때문에 포유류 타입으로 변환될 수 있습니다.
// Mammal mammal = new Whale();
Mammal whale = new Whale();

// 생성자는 Whale이어도 Mammal로 객체를 생성했기에 상속받지 않은 메서드는 사용할 수 없다.
// whale.swimming(); // 오류 발생

// 부모타입의 객체는 자식타입의 변수로 변환될 수 없습니다.
// Whale whale = new Mammal(); // 오류 발생

whale.feeding();

 

강제 타입 변환

부모 타입 객체는 자식 타입 변수로 자동 형변환 되지 않는다. 따라서 변수의 강제 형변환과 같이 

Mammal mammal = new Whale();
Whale whale = (Whale) mammal;

와 같은 형태로 형변환 할 수 있다. 단, 위와 같이 자식 타입 객체로 생성했을 때만 강제 형변환이 가능하다.

Mammal mammal = new Mammal();
Whale whale = (Whale) mammal; // ClassCastException 발생

 

이제 다형성에 대해 말해보자

다형성이란 여러가지 형태를 가질 수 있는 능력을 말한다.

위에서 말한 타입 변환도 다형성을 보여준다.

그 외에도 매개변수, 반환타입에도 다형성을 나타낼 수 있다.

 

매개변수의 다형성

public class Car {
    Tire tire;

    public Car(Tire tire) {
        this.tire = tire;
    }

    Tire getHankookTire() {
        return new HankookTire("HANKOOK");
    }

    Tire getKiaTire() {
        return new KiaTire("KIA");
    }
}

여기서 Car()는 tire를 매개변수로 받지만, tire의 자식 클래스의 객체들을 매개변수로 받을 수도 있다.

// 자동 타입 변환
// Tire tire = new HankookTire("HANKOOK");
// Tire tire = new KiaTire("KIA");

// Car car = new Car(Tire tire);에서 위 자동 타입 변환과 같이 자식 클래스 객체로 생성
Car car1 = new Car(new KiaTire("KIA"));
Car car2 = new Car(new HankookTire("HANKOOK"));
더보기

위의 car1에서 매개변수는 KiaTire로 생성된 객체를 받지만 자동 형변환 되어 Tire 객체로서 작동한다. ??? 문제 발생

 

문제 발생

Tire kiaSampleTire = new KiaTire("KIA");

Car car1 = new Car(kiaSampleTire);

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

위에서 이해한 내용대로라면 kiaSampleTire도 KiaTire을 통해 생성했더라도 자동 형변환에 의해 Tire로 변환되었을 텐데 

KiaTire kiaSampleTire = new KiaTire("KIA")로 생성 했을 때와 같은 결과가 나온다. 제대로 이해하지 못했다는 증거.

 

1. kiaSampleTire가 KiaTire의 객체로 생성되었고, KiaTire가 rideComfort를 오버라이드 했기에 실행되는건가??

2. KiaTire와 Tire에 있는 super, this로 인해 Tire의 객체가 KiaTire로 연결 된건가?? <-- 이건 아니다 싶음

'TIL > JAVA' 카테고리의 다른 글

23.05.25  (0) 2023.05.25
23.05.24  (0) 2023.05.24
23.05.22  (0) 2023.05.22
23.05.04  (0) 2023.05.04
23.05.03  (0) 2023.05.03