자바의 정석 3판 (11) 연습문제 : 컬렉션 프레임웍

NtoZ·2023년 4월 4일
0

Java

목록 보기
21/23
post-thumbnail

자바의정석3판 - 컬렉션 프레임웍


💡1-1. 리스트의 교집합, 차집합, 합집합 구하기

  • 문제:
    [11-1] 다음은 정수집합 1,2,3,4와 3,4,5,6의 교집합, 차집합, 합집합을 구하는 코드이다.
    코드를 완성하여 실행결과와 같은 결과를 출력하시오.
    [Hint] ArrayList클래스의 addAll(), removeAll(), retainAll()을 사용하라.

  • 풀이 접근:

  1. retainAll()은 교집합을 구하는 데 사용될 것이다.
    addAll()은 합집합을 구하는 데 사용될 것이다.
    removeAll()은 차집합을 구하는 데 사용될 것이다.
    ➡️ 사실은 해당 문제가 List이기 때문에 Set과 달리 자료의 중복을 허용한다. 따라서 '집합'을 만들기 위해서 중복을 허용하지 않도록 해당 메서드들을 잘 활용해야 한다.
  2. 각각의 매개변수를 대입하기 위해 Collection으로 형변환 한다.
    ❌❌ ➡️ Collection은 이미 Set, List가 구현한 조상 인터페이스이므로 다형성이 적용되어 Set과 List의 하위 클래스들은 Collection 타입의 매개변수로 대입될 수 있다. (업캐스팅)


    ✔️ 💡자료형의 타입이 boolean이라고 해서 반드시 어딘가에 저장해야 하는 것은 아니다. removeAll, retainAll addAll메서드들은 불린타입을 자료형으로 반환할 뿐, 저장하지 않아도 이미 해당 동작을 완료한다.

    ✔️ 단, containsAll은 동작 완료가 아닌, 검색해서 해당 사실 여부를 알려준다.
  • 풀이1:
public class Sol_Exercise11_1 {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        ArrayList list2 = new ArrayList();
        ArrayList kyo = new ArrayList();    //교집합
        ArrayList cha = new ArrayList();    //차집합
        ArrayList hap = new ArrayList();    //합집합

        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);

        list2.add(3);
        list2.add(4);
        list2.add(5);
        list2.add(6);

        /*
            (1) 알맞은 코드를 넣어 완성하시오.
         */

        //0. ArrayList ➡️ Collection 변환하여 addAll(합집합), removeAll(차집합), retainAll(교집합) ~~containsAll(교집합) => boolean형 반환이라 불가능~~ 비교
        //🔥🔥 Collection으로 변환할 필요가 없다. 왜냐하면 Collection 인터페이스는 Set, List가 이미 구현한 조상 인터페이스이기 때문이다.
        Collection c1 = list1;
        Collection c2 = list2;

        //1. 반복자에 각 객체 담기
        /*Iterator it1 = list1.iterator();
        Iterator it2 = list2.iterator();*/

        //2. 교집합 (contains(Object obj)를 사용하는법)
        /*while(it1.hasNext()) {
            Object tmp = it1.next();
            if(list2.contains(tmp)) {
                kyo.add(tmp);
            }
        }*/
        // ⭐2. 교집합 retainAll(Collection c)를 사용하는 법
        //kyo = list2.retainAll(c1);
        kyo.addAll(c2);
        kyo.retainAll(c1);

        // ⭐3. 차집합 removeAll(Collection c)를 사용하는 법
        cha.addAll(c2);
        cha.removeAll(c1);  //c2에서 c1의 요소만 지움

        // ⭐4. 합집합 addAll(Collection c)를 사용하는 법
        hap.addAll(c1);
        hap.addAll(cha);    //c1에 c2의 독립 요소만 추가함

        System.out.println("list1="+list1);
        System.out.println("list2="+list2);
        System.out.println("kyo="+kyo);
        System.out.println("cha="+cha);
        System.out.println("hap="+hap);
    }
}
<결과>
list1=[1, 2, 3, 4]
list2=[3, 4, 5, 6]
kyo=[3, 4]
cha=[5, 6]
hap=[1, 2, 3, 4, 5, 6]
  • 모범 풀이:
<정답>
import java.util.*;

class Exercise11_1 {
    public static void main(String[] args) { ArrayList list1 = new ArrayList(); ArrayList list2 = new ArrayList(); ArrayList kyo = new ArrayList(); // 교집합 ArrayList cha = new ArrayList(); // 차집합 ArrayList hap = new ArrayList(); // 합집합

        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);

        list2.add(3);
        list2.add(4);
        list2.add(5);
        list2.add(6);

        kyo.addAll(list1);	// list1의 모든 요소를 kyo에 저장한다.
        kyo.retainAll(list2); // list2와 kyo의 공통요소만 남기고  삭제한다.

        cha.addAll(list1);
        cha.removeAll(list2); // cha에서  list2와  공통된 요소들을  모두  삭제한다.


        hap.addAll(list1); hap.removeAll(kyo); hap.addAll(list2);

// list1의 모든 요소를 hap에 저장한다.
// hap에서 kyo와 공통된 모든 요소를 삭제한다.
// list2의 모든 요소를 hap에 저장한다.


        System.out.println("list1="+list1); System.out.println("list2="+list2); System.out.println("kyo="+kyo); System.out.println("cha="+cha); System.out.println("hap="+hap);
    }
}
  • 내 풀이 아쉬운 점:
    Collection을 구현하는 Set, List의 하위 클래스는 마찬가지로 Collection을 구현하기 때문에 매개변수의 다형성을 적용받아
    굳이 Collection으로 형변환 할 필요가 없다.

✔️컬렉션에서 다른 컬렉션으로 형변환

  • ➕보너스) 컬렉션에서 다른 컬렉션으로 형변환하는 방법
    생성자를 이용한다.
    - Set set = new HashSet<>(컬렉션 객체)
    - List list = new ArrayList<>(컬렉션 객체)
    addAll()메서드를 이용한다.
    - hashSet.addAll(컬렉션 객체)
    - arrayList.addAll(컬렉션 객체)

💡11-5. Comparable 구현하기

  • 문제 :
    [11-5] 다음에 제시된 Student클래스가 Comparable인터페이스를 구현하도록 변경해서 이름(name)이 기본 정렬기준이 되도록 하시오.

  • 풀이 접근:
    Comparable과 Comparator는 서로 유사하지만 다른 방식의 정렬이다.
    Comparable해당 클래스에 대한 기본 정렬 방식을 구현한다.
    추상 메서드 public int compareTo(Object o)를 구현해 비교하며,
    음수가 나오면 기준이 더 적고, 0이 나오면 같은 크기, 양수가 나오면 기준이 더 큰 것을 의미한다.

    Comparator는 기본 정렬방식이 아닌 별도의 정렬방식를 구현하기 위해서 Comparator를 구현하는 별도의 비교자 클래스를 만든다.
    int compare(T o1, T o2); 추상메서드를 구현해야 한다.
    o1과 o2의 비교를 원하는 방식으로 구현하여 마찬가지로,
    음수면 기준이 더 적음. 0이면 같음, 양수면 기준이 더 큼의 방식으로 진행된다.

    Comparable을 통해서 정렬을 구현해야 하므로,
    int compare(Object o)를 구현한다.
    이름(name)을 기준으로 비교하는데, 문자열은 공백, 숫자, 대문자, 소문자 순으로 크다.
    문자열 String 클래스에서는 이미 Comparable을 구현하면서 int compareTo(String anotherString)을 구현했으므로,
    해당 메서드를 재사용해 리턴해주면 될 것이다.

  • 풀이

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

class Student implements Comparable {
    String name;
    int ban;
    int no;
    int kor, eng, math;

    Student(String name, int ban, int no, int kor, int eng, int math) {
        this.name = name;
        this.ban = ban;
        this.no = no;
        this.kor = kor;
        this.eng = eng;
        this.math = math;
    }

    int getTotal() {
        return kor + eng + math;
    }

    float getAverage() {
        return (int) ((getTotal() / 3f) * 10 + 0.5) / 10f;
    }

    public String toString() {
        return name + "," + ban + "," + no + "," + kor + "," + eng + "," + math
                + "," + getTotal() + "," + getAverage();
    }

    @Override
    public int compareTo(Object o) {
        //🔥⭐⭐Object o로 받는 것은 해당 Student 객체이어야 하므로 다운캐스팅한다.
        Student tmp = (Student) o;
        //🔥⭐String 클래스에서 구현된 문자열 비교자를 활용한다. (🔥⭐String.compareTo(String anotherString))
        return this.name.compareTo(tmp.name);
    }
}

class Exercise11_5 {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(new Student("홍길동", 1, 1, 100, 100, 100));
        list.add(new Student("남궁성", 1, 2, 90, 70, 80));
        list.add(new Student("김자바", 1, 3, 80, 80, 90));
        list.add(new Student("이자바", 1, 4, 70, 90, 70));
        list.add(new Student("안자바", 1, 5, 60, 100, 80));

        Collections.sort(list);
        Iterator it = list.iterator();

        while (it.hasNext()) System.out.println(it.next());

    }
}

<실행결과>
김자바,1,3,80,80,90,250,83.3
남궁성,1,2,90,70,80,240,80.0
안자바,1,5,60,100,80,240,80.0
이자바,1,4,70,90,70,230,76.7
홍길동,1,1,100,100,100,300,100.0
  • 모범 풀이
class Student implements Comparable {
    String name;
    int ban;
    int no;
    int kor, eng, math;

    Student(String name, int ban, int no, int kor, int eng, int math) {
        this.name = name;
        this.ban = ban;
        this.no = no;
        this.kor = kor;
        this.eng = eng;
        this.math = math;
    }

    int getTotal() {
        return kor + eng + math;
    }

    float getAverage() {
        return (int) ((getTotal() / 3f) * 10 + 0.5) / 10f;
    }

    public String toString() {
        return name + "," + ban + "," + no + "," + kor + "," + eng + "," + math
                + "," + getTotal() + "," + getAverage();
    }

    public int compareTo(Object o) {
        if (o instanceof Student) { //⭐⭐형변환 검사를 해야 형변환이 가능하다.
            Student tmp = (Student) o;

            return name.compareTo(tmp.name); // String클래스의 compareTo()를 호출
        } else {	
        //⭐⭐ Object로 받는 객체가 Student클래스가 아니면, 애초에 name을 비교할 수 없다.
            return -1;	
        }
    }
}

class Exercise11_5 {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(new Student("홍길동", 1, 1, 100, 100, 100));
        list.add(new Student("남궁성", 1, 2, 90, 70, 80));
        list.add(new Student("김자바", 1, 3, 80, 80, 90));
        list.add(new Student("이자바", 1, 4, 70, 90, 70));
        list.add(new Student("안자바", 1, 5, 60, 100, 80));

        Collections.sort(list); // list에 저장된 요소들을 정렬한다.(compareTo()호출) Iterator it = list.iterator();
        while (it.hasNext()) System.out.println(it.next());
    }
}
  • 내 풀이와 다른점
    int compareTo(Object o)에서
public int compareTo(Object o) {
        if (o instanceof Student) { //⭐⭐형변환 검사를 해야 형변환이 가능하다.
            Student tmp = (Student) o;

            return name.compareTo(tmp.name); // String클래스의 compareTo()를 호출
        } else {	
        //⭐⭐ Object로 받는 객체가 Student클래스가 아니면, 애초에 name을 비교할 수 없다.
            return -1;	
        }
    }

➡️💡 형변환 검사는 반드시 하고 형변환을 진행해야 한다. 이에 대한 내용이 빠졌다.

<해설>Collections.sort(List list)를 이용하면 ArrayList에 저장된 요소들을 쉽게 정렬 할 수 있다.
    Collections.sort(list); // list에 저장된 요소들을 정렬한다.(compareTo()호출)
⭐그러나 한 가지 조건이 있다. ArrayList에 저장된 요소들은 모두 Comparable인터페이스를 구현한 것이어야 한다는 것이다.
이 인터페이스에는 compareTo메서드가 정의되어 있는데, 이 메서드는 Collections.sort(List list)에 의해 호출되어 정렬기준을 제공하게 된다
    public interface Comparable {
        public int compareTo(Object o); // ⭐주어진 객체(o)와 자신의 멤버변수를 비교
    }

⭐compareTo메서드는 매개변수로 주어진 객체(o)를 인스턴스자신과 비교해서 자신이 작으면 음수를, 같으면 0, 크면 양수를 반환하도록 구현되어야 한다.
문제에서는 학생의 이름으로 정렬될 것을 요구했으므로, 인스턴스변수 name만 비교하도록 compareTo메서드를 구현하면 된다.
문자열 name을 어떻게 비교하도록 구현해야할까?
고민되겠지만,String클래스에는 이미 문자열 비교를 위한 compareTo메서드를 구현해 놓았고
우리는 단지 그것을 활용하기만 하면 된다.
    public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
    {
    ...

    class Student implements Comparable {
    ...
    public int compareTo(Object o) { if(o instanceof Student) {
    Student tmp = (Student)o;
    return name.compareTo(tmp.name); // String클래스의 compareTo()를 호출
    } else {
    return -1;
    }
    }

 */

💡💡11-6. Comparator와 TreeSet의 subSet 구현하기

  • 문제 :
    [11-6] 다음의 코드는 성적평균의 범위별로 학생 수를 세기 위한 것이다.
    TreeSet이 학생들의 평균을 기준으로 정렬하도록 compare(Object o1, Object o2)와 평균점수의 범위를 주면 해당 범위에 속한 학생의 수를 반환하는 getGroupCount()를 완성하라.
    [Hint] TreeSet의 subSet(Object from, Object to)를 사용하라.

  • 풀이 접근 :
    int compare(Object o1, Object o2)는 Studnet로 형변환 검사 후 다운캐스팅하여 각 객체의 getAverage 값에 따라 조건문을 구성하여 음수, 0, 양수를 적절히 리턴하도록 하면 된다.

                        //🔥 익명클래스로 Comparator 인스턴스 구현!
        TreeSet set = new TreeSet(new Comparator() {
            public int compare(Object o1, Object o2) {
            /*
            (2)	알맞은 코드를 넣어 완성하시오.
            */
                if(o1 instanceof Student6 && o2 instanceof Student6) {
                    Student6 s1 = (Student6) o1;
                    Student6 s2 = (Student6) o2;
                            //🔥 3항 연산자 사용하여 평균값을 기준으로 정렬하기
                    return s1.getAverage()>s2.getAverage() ? 1 : (s1.getAverage()==s2.getAverage() ? 0 : -1);
                } else return -1;
            }
        });
  • 그러나, static int getGroupCount(TreeSet tset, int from, int to) 의 구현이 감이 잡히지 않는다.
    1.범위 검색이므로 tset.subSet(int from, int to)를 활용한다.
    2.해당 subSet은 결국 집합이므로 size()를 반환하면 된다.
    ➡️💡 갑자기 아이디어가 떠올랐다.
    인자로 전달되는 tset에는 학생 객체들에 대한 정보가 있으므로, 반복자로 하나씩 꺼내와서 그 학생의 평균값을 또다른 set에 담고 1~2를 수행하면 되는 것이다.
static int getGroupCount(TreeSet tset, int from, int to) {
    /*
    (1)	알맞은 코드를 넣어 완성하시오.
    */
        //🔥 tset에 담긴 Student 객체 값들의 평균을 꺼내와 집합에 저장한다.
        //🔥 Set을 참조변수의 타입으로 선언하면 안된다. subSet()으로 범위 탐색할 것이기 때문.
        TreeSet avgSet = new TreeSet();
        Iterator it = tset.iterator();
        while(it.hasNext()) {
            if(it.next() instanceof Student6) {
                Student6 tmp = (Student6) it.next();
                avgSet.add(tmp.getAverage());
            }
        }

        //🔥 범위 검색이므로 tset.subSet(int from, int to)를 활용한다.
        //🔥 그것의 사이즈를 반환하면 되는 것이다.
        return avgSet.subSet(from, to).size();
    }
  • ❓❓❓
    Exception in thread "main" java.lang.ClassCastException: java.lang.Float cannot be cast to java.lang.Integer 예외가 발생한다. 이유가 뭘까? tset의 반복자 중 Float 타입이 존재하는 것일까?
    디버깅을 해봐도 첫 두 객체는 정상적으로 형변환 하더니 바로 다음 객체를 꺼내올 때 it.hasNext()가 False가 되며 return avgSet.subSet(from, to).size();로 이동한다. 그리고 형변환 예외가 동작한다. TreeSet이 이진탐색트리로 저장된 자료구조이기 때문에 그 절반만 꺼내올 수 있는 것일까?
    아니면 예외 문구 그대로 avgSet에 Integer만 저장되어야 하는데
    Float 타입이 저장되려고 해서 발생하는 예외일까?
    ✔️✔️✔️
    avgSet에 담기는 게 Float형인데 매개변수가 int네요.
    별개로, it.next()는 instanceof에서 쓰면 소모되어버립니다.
    ➡️💡💡 ❷이 매우 중요하다. it.next()를 형변환에 사용하면 next가 소모되어 버리고
    그 다음 Student6 tmp = (Student6) it.next()를 사용할 때는 그 다음 next가 사용되어 형변환 검사가 의미없어지며, 하나씩 요소를 건너뛰게 된다.
    따라서 다음과 같이 서순을 다르게 해줘야 한다.
//🔥 tset에 담긴 Student 객체 값들의 평균을 꺼내와 집합에 저장한다.
        //🔥 Set을 참조변수의 타입으로 선언하면 안된다. subSet()으로 범위 탐색할 것이기 때문.
        TreeSet avgSet = new TreeSet();
        Iterator it = tset.iterator();
        while(it.hasNext()) {
            Object tmp = it.next();	//⭐ it.next()를 tmp에 저장해서
            if(tmp instanceof Student6) {
                Student6 stud = (Student6) tmp; //⭐형변환 검사 후 형변환
                avgSet.add(stud.getAverage());
            } else return -1;   //형변환 자체가 안되면 -1 반환
        }

        //🔥 범위 검색이므로 tset.subSet(int from, int to)를 활용한다.
        //🔥 그것의 사이즈를 반환하면 되는 것이다.
        return avgSet.subSet(from, to).size();
  • 위의 코드를 통해서 avgSet에 평균이 절반만 들어가는 문제는 해결했다. ⭐it.next()사용에 주의하자!

  • 남은 문제는 Float이 Integer로 형변환 될 수 없다는 예외이다. 이런 의견이 있었다.
    avgSet은 TreeSet<Float> 인데 from, to는 Integer라 subSet 계산하면서 에러가 발생
    ❓나는 Float으로 설정한 적이 없는데 왜 지네릭스가 Float이 되는 걸까?
    어쨌든 이것은 static int getGroupCount(TreeSet tset, float from, float to) from과 to의 타입을 int -> float으로 바꾸면 해결되는 문제이다. avgSet에는 Float만 입력되어 있으므로 subSet(from, to)에서 비교 기준이되는 from, to 역시 Float 형이어야 하나보다.

  • 해결된 코드는 다음과 같다.

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

class Student6 implements Comparable {
    String name;
    int ban;
    int no;
    int kor;
    int eng;
    int math;

    Student6(String name, int ban, int no, int kor, int eng, int math) {
        this.name = name;
        this.ban = ban;
        this.no = no;
        this.kor = kor;
        this.eng = eng;
        this.math = math;
    }

    int getTotal() {
        return kor + eng + math;
    }

    float getAverage() {
        return (int) ((getTotal() / 3f) * 10 + 0.5) / 10f;
    }

    public String toString() {
        return name
                + "," + ban
                + "," + no
                + "," + kor
                + "," + eng
                + "," + math
                + "," + getTotal()
                + "," + getAverage()
                ;
    }

    public int compareTo(Object o) {
        if (o instanceof Student6) {
            Student6 tmp = (Student6) o;

            return name.compareTo(tmp.name);
        } else {
            return -1;
        }
    }
} // class Student

class Sol_Exercise11_6 {
    static int getGroupCount(TreeSet tset, float from, float to) {
    /*
    (1)	알맞은 코드를 넣어 완성하시오.
    */
        //🔥 tset에 담긴 Student 객체 값들의 평균을 꺼내와 집합에 저장한다.
        //🔥 Set을 참조변수의 타입으로 선언하면 안된다. subSet()으로 범위 탐색할 것이기 때문.
        TreeSet avgSet = new TreeSet();
        Iterator it = tset.iterator();
        while(it.hasNext()) {
            Object tmp = it.next();
            if(tmp instanceof Student6) {
                Student6 stud = (Student6) tmp;
                avgSet.add(stud.getAverage());
            } else return -1;   //형변환 자체가 안되면 -1 반환
        }

        //🔥 범위 검색이므로 tset.subSet(int from, int to)를 활용한다.
        //🔥 그것의 사이즈를 반환하면 되는 것이다.
        return avgSet.subSet(from, to).size();

        /*Student6 s1 = new Student6("",0,0,from,from,from);
        Student6 s2 = new Student6("",0,0,to,to,to);
        return tset.subSet(s1,s2).size();*/

    }

    public static void main(String[] args) {
        //🔥 익명클래스로 Comparator 인스턴스 구현!
        TreeSet set = new TreeSet(new Comparator() {
            public int compare(Object o1, Object o2) {
            /*
            (2)	알맞은 코드를 넣어 완성하시오.
            */
                if (o1 instanceof Student6 && o2 instanceof Student6) {
                    Student6 s1 = (Student6) o1;
                    Student6 s2 = (Student6) o2;
                    //🔥 3항 연산자 사용하여 평균값을 기준으로 정렬하기
                    return s1.getAverage() > s2.getAverage() ? 1 : (s1.getAverage() == s2.getAverage() ? 0 : -1);
                } else return -1;
            }
        });




        set.add(new Student6("홍길동", 1, 1, 100, 100, 100));
        set.add(new Student6("남궁성", 1, 2, 90, 70, 80));
        set.add(new Student6("김자바", 1, 3, 80, 80, 90));
        set.add(new Student6("이자바", 1, 4, 70, 90, 70));
        set.add(new Student6("안자바", 1, 5, 60, 100, 80));
        Iterator it = set.iterator();
        while (it.hasNext()) System.out.println(it.next());

        System.out.println("[60~69] :" + getGroupCount(set, 60, 70));
        System.out.println("[70~79] :" + getGroupCount(set, 70, 80));
        System.out.println("[80~89] :" + getGroupCount(set, 80, 90));
        System.out.println("[90~100] :" + getGroupCount(set, 90, 101));
    }
}

<실행결과>
이자바,1,4,70,90,70,230,76.7
남궁성,1,2,90,70,80,240,80.0
김자바,1,3,80,80,90,250,83.3
홍길동,1,1,100,100,100,300,100.0
[60~69] :0
[70~79] :1
[80~89] :2
[90~100] :1
  • ❓❓❓ 궁금한 점 또 생김
    getGroupCount의 매개변수 타입이 int더라도 float으로 자동 형변환 되어야 하는게 아닌가? return avgSet.subSet(from, to).size(); 이 부분에서 문제가 생기는 것 같은데, 오히려 예외는 Float이 Integer로 변경될 수 없다.로 출력되는게 의아하다.


  • 모범 답안:
//⭐ 기본정렬이 아닌 Comparator를 익명클래스로 구현하여 정렬
//⭐ 각 학생의 평균을 비교해서 정렬한다.
TreeSet set = new TreeSet(new Comparator() { // 익명클래스
            public int compare(Object o1, Object o2) {
                if(o1 instanceof Student && o2 instanceof Student) { Student s1 = (Student)o1;
                    Student s2 = (Student)o2;

                    return (int)(s1.getAverage() - s2.getAverage());
                }

                return -1;
            }
        });


static int getGroupCount(TreeSet tset, int from, int to) {
	 //⭐⭐ from으로 국영수 부분을 채운 s1과 
     //		to로 국영수 부분을 채운 s2를 생성하여
     //  tset에서 해당 s1, s2를 기준으로한 집합을 만들고 그 사이즈를 반환
	 Student6 s1 = new Student6("",0,0,from,from,from);
     Student6 s2 = new Student6("",0,0,to,to,to);
     return tset.subSet(s1,s2).size();
    }
  • 내 풀이와 다른점1:
    익명 클래스로 Comparator를 구성하는 것에서 나는 형변환 후 삼항 연산자를 통해서 getAverage의 비교에 따라 1, 0, -1의 결과를 산출하여 정렬하였지만,
    모범답안은 아예 (int)를 씌워서 (s1.getAverage() - s2.getAverage()를 반환한다.
    하지만 평균으로 정렬하는 것에는 차이 없이 잘 동작한다.

  • 내 풀이와 다른점2:
    아예 학생 인스턴스 둘을 만들어 반과 번호를 제외한 국, 영, 수 성적을 인자 from과 to로 채웠다. 위의 Compartor에 의해서 해당 학생들의 평균값의 비교로 정렬되었므로, (from, to)의 부분집합의 size()를 반환하면 평균점수가 from에서 to 사이인 학생들의 수가 반환 되는 것이다.

  • 💡💡개념:
    Comparable인터페이스를 구현하지 않은 클래스(정렬기준이 없는 클래스 또는 다른 정렬기준으로 정렬하고 싶은 클래스, 여기서는 후자)는 생성자를 이용해서 반드시 정렬기준을 제공해주어야 한다.


💡💡❓11-8. int compare내림차순 구현과 동시에 총점 기준 전교등수 계산 및 오름차순 정렬

  • 문제:
    [11-8] 문제11-7의 Student클래스에 총점(total)과 전교등수(schoolRank)를 저장하기 위한 인스턴스변수를 추가하였다. Student클래스의 기본정렬을 이름(name)이 아닌 총점 (total)을 기준으로 한 내림차순으로 변경한 다음, 총점을 기준으로 각 학생의 전교등수를 계산하고 전교등수를 기준으로 오름차순 정렬하여 출력하시오.
//[11-8] 문제11-7의 Student클래스에 총점(total)과 전교등수(schoolRank)를 저장하기
//        위한 인스턴스변수를 추가하였다. Student클래스의 기본정렬을 이름(name)이 아닌 총점
//        (total)을 기준으로 한 내림차순으로 변경한 다음, 총점을 기준으로 각 학생의 전교등수
//        를 계산하고 전교등수를 기준으로 오름차순 정렬하여 출력하시오.

import java.util.*;

class Student implements Comparable {
    String name;
    int ban;
    int no;
    int kor;
    int eng;
    int math;

    int total;    // 총점
    int schoolRank;    // 전교등수

    Student(String name, int ban, int no, int kor, int eng, int math) {
        this.name = name;
        this.ban = ban;
        this.no = no;
        this.kor = kor;
        this.eng = eng;
        this.math = math;

        total = kor + eng + math;
    }

    int getTotal() {
        return total;
    }

    float getAverage() {
        return (int) ((getTotal() / 3f) * 10 + 0.5) / 10f;
    }

    public int compareTo(Object o) {
    /*
    (1)	알맞은 코드를 넣어 완성하시오.
    */
    }

    public String toString() {
        return name
                + "," + ban
                + "," + no
                + "," + kor
                + "," + eng
                + "," + math
                + "," + getTotal()
                + "," + getAverage()
                + "," + schoolRank // 새로추가
                ;
    }
} // class Student

class Exercise11_8 {
    public static void calculateSchoolRank(List list) {
        Collections.sort(list); // 먼저 list를 총점기준 내림차순으로 정렬한다.

        int prevRank = -1;    // 이전 전교등수
        int prevTotal = -1;		// 이전 총점
        int length = list.size();

        /*
        (2)	아래의 로직에 맞게 코드를 작성하시오.
        1.	반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다.
        1.1	총점(total)이   이전총점(prevTotal)과   같으면 이전 등수(prevRank)를 등수(schoolRank)로 한다.
        1.2	총점이 서로 다르면,
        등수(schoolRank)의 값을 알맞게 계산해서 저장한다.
        이전에 동점자 였다면, 그 다음 등수는 동점자의 수를 고려해야 한다. (실행결과 참고)
        1.3	현재 총점과 등수를 이전총점(prevTotal)과 이전등수(prevRank)에 저장한다.
        */
    }

    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(new Student("이자바", 2, 1, 70, 90, 70));
        list.add(new Student("안자바", 2, 2, 60, 100, 80));
        list.add(new Student("홍길동", 1, 3, 100, 100, 100));
        list.add(new Student("남궁성", 1, 1, 90, 70, 80));
        list.add(new Student("김자바", 1, 2, 80, 80, 90));
        calculateSchoolRank(list);
        Iterator it = list.iterator();

        while (it.hasNext()) System.out.println(it.next());
    }
}

/*
<실행결과>
홍길동,1,3,100,100,100,300,100.0,1
김자바,1,2,80,80,90,250,83.3,2
안자바,2,2,60,100,80,240,80.0,3
남궁성,1,1,90,70,80,240,80.0,3
이자바,2,1,70,90,70,230,76.7,5
 */
  • 풀이 접근:
    1.⭐ Comparable을 내림차순으로 구현하는 것은 간단하다.
    오름차순을 구현한 다음 return 값에 -1을 곱하거나
    비교하는 값과 비교기준 값의 위치를 서로 바꾸면 된다.
    <궁금증> : 형변환에 실패하면 내림차순일 경우 -1을 반환함. 그렇다면 오름차순일 경우 +1을 반환해야 하는가?
    그리고 애당초 Student 객체로 형변환 할 수 없는데 값을 리턴하는 것이 무슨 의미인가? 더불어 compare 메서드 등에서 int를 반환하면 컬렉션 프레임웍에 담을 때 서순이 정렬되는 구조 자체도 알아볼 필요가 있다.
public int compareTo(Object o) {
    /*
    (1)	알맞은 코드를 넣어 완성하시오.
    */
        //🔥 인스턴스 형변환 검사를 진행한다.
        if(o instanceof Student8) {
            Student8 stu = (Student8) o;
            //🔥 이 객체의 total값에 비교하는 stu의 total값을 뺀 다음 -1을 곱해주어야 내림차순
            return (this.total-stu.total)*(-1);
        } else {
            System.out.println("학생 객체가 아닙니다.");
            //❓❓ 얘도 -1을 곱해줘야 하는건가? 어차피 Student가 아니라 어떤값을 줘도 상관없나?
            return -1*(-1);
        }
    }

2.`void calculateSchoolRank(List list)`의 구현이 어려웠다. _특히 동점자를 계산에 포함하는 것이 어려웠다._ while로 구현하려다 어려움이 있어 답을 확인했는데, for문으로 구현되어 있었다. ➡️Iterator와 for문 사용의 차이를 인식하자. int i의 변수를 인식하면 동점자를 건너뛰는 등수를 구현하기 쉬워질 수 있다. (왜냐하면 이미 total에 따라 내림차순으로 구현된 list가 전달되어 반복문이 진행될수록 등수는 더해질 수 밖에 없다. 이 때 total이 같은 학생은 같은 등수를 부여하며, 다른 학생은 `i+1`을 부여하면 된다. 컬렉션 프레임워크에 담겨 있다고 반드시 Iterator로 꺼내와야 하는 것은 아니다.
public static void calculateSchoolRank(List list) {
        Collections.sort(list); // 먼저 list를 총점기준 내림차순으로 정렬한다. (🔥 Comparable로 구현)

        int prevRank = -1;    // ⭐이전 전교등수
        int prevTotal = -1;		// ⭐이전 총점
        int length = list.size();   //⭐전교 학생 수

        /*
        (2)	아래의 로직에 맞게 코드를 작성하시오.
        1.	반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다.
        1.1	총점(total)이   이전총점(prevTotal)과   같으면 이전 등수(prevRank)를 등수(schoolRank)로 한다. (🔥 이해 안됨.)
        1.2	총점이 서로 다르면,
        등수(schoolRank)의 값을 알맞게 계산해서 저장한다.
        이전에 동점자 였다면, 그 다음 등수는 동점자의 수를 고려해야 한다. (실행결과 참고)
        1.3	현재 총점과 등수를 이전총점(prevTotal)과 이전등수(prevRank)에 저장한다.
        */
        Iterator it = list.iterator();
        for(int i=0; it.hasNext(); i++) {
            Object itSud = it.next();
            if(itSud instanceof  Student8) {
                Student8 s = (Student8) itSud;
                if(s.total==prevTotal) {
                    s.schoolRank = prevRank;
                } else {
                    //⭐⭐i는 계속해서 진행하고, 동점자가 아닐경우에만 학교등수를 매기므로 동점자는 자연스럽게 건너뛰게 된다.
                    s.schoolRank = i+1;
                }
                //⭐⭐ s.total로 s.schoolRank 연산이 완료되면 prevTotal, prevRank에 각각 저장하여 등수를 비교하는 기준으로 삼는다.
                prevTotal = s.total;
                prevRank = s.schoolRank;
            }
        }

💡 iterator.hasNext() 조건문을 for문으로도 사용할 수 있다.

  • 모범 풀이:
import java.util.*;

class Student implements Comparable {
    String name;
    int ban;
    int no;
    int kor;
    int eng;
    int math;

    int total;    // 총점
    int schoolRank; // 전교등수

    Student(String name, int ban, int no, int kor, int eng, int math) {
        this.name = name;
        this.ban = ban;
        this.no = no;
        this.kor = kor;
        this.eng = eng;
        this.math = math;

        total = kor + eng + math;
    }

    int getTotal() {
        return total;
    }

    float getAverage() {
        return (int) ((getTotal() / 3f) * 10 + 0.5) / 10f;
    }
	//⭐⭐
    public int compareTo(Object o) {
        if (o instanceof Student) {
            Student tmp = (Student) o;

            return tmp.total - this.total; // 총점 기준(내림차순)으로 정렬한다.
        } else {
            return -1;
        }
    }

    public String toString() {
        return name
                + "," + ban
                + "," + no
                + "," + kor
                + "," + eng
                + "," + math

                + "," + getTotal()
                + "," + getAverage()
                + "," + schoolRank // 새로추가
                ;
    }
} // class Student

class Exercise11_8 {
	//⭐⭐
    public static void calculateSchoolRank(List list) {
        Collections.sort(list); // 먼저 list를 총점기준 내림차순으로 정렬한다.
        int prevRank = -1;
        int prevTotal = -1;
        int length = list.size();
        //	1. 반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다.
        for (int i = 0; i < length; i++) {
            Student s = (Student) list.get(i);

//	1.1 총점(total)이 이전총점(prevTotal)과 같으면
//	이전 등수(prevRank)를 등수(schoolRank)로 한다.
            if (s.total == prevTotal) {
                s.schoolRank = prevRank;
            } else {
//	1.2 총점이 서로 다르면,
//	등수(schoolRank)의 값을 알맞게 계산해서 저장한다.
//	이전에 동점자였다면, 그 다음 등수는 동점자의 수를 고려해야한다.
                s.schoolRank = i + 1;
            }
//	1.3 현재 총점과 등수를 이전총점(prevTotal)과 이전등수(prevRank)에 저장한다.
            prevRank = s.schoolRank;
            prevTotal = s.total;
        } // for
    }

    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(new Student("이자바", 2, 1, 70, 90, 70));
        list.add(new Student("안자바", 2, 2, 60, 100, 80));
        list.add(new Student("홍길동", 1, 3, 100, 100, 100));
        list.add(new Student("남궁성", 1, 1, 90, 70, 80));
        list.add(new Student("김자바", 1, 2, 80, 80, 90));
        calculateSchoolRank(list);
        Iterator it = list.iterator();

        while (it.hasNext()) System.out.println(it.next());
    }

  • 내 답안과 차이 : 형변환 검사를 하지 않았다. 또한 아예 Iterator로 표준화하지 않고 for문을 사용해 각 List에 있는 Student 객체들을 꺼내왔다.

  • 💡💡compare메서드에서 형변환에 실패했을 때 -1을 반환하는 이유, 그리고 compare메서드가 int를 반환했을 때 추후 컬렉션 프레임워크에서 정렬이 되는 이유?

    compare 메서드는 두 개의 인자를 비교하여 정렬 순서를 결정하는 역할을 합니다. 일반적으로 compare 메서드에서는 두 개의 인자를 비교할 때 형변환을 해야 하는데, 만약 형변환이 실패하면 ClassCastException이 발생합니다.

    따라서 형변환이 실패하면 비교를 진행할 수 없기 때문에 해당 메서드에서는 -1을 반환하여 호출한 곳에서 형변환에 실패한 것을 알 수 있도록 합니다. 이렇게 되면 호출한 곳에서는 -1을 받아서 에러 처리를 할 수 있게 됩니다.

    즉, -1을 반환함으로써 예외가 발생했을 때 호출한 쪽에서 적절한 예외 처리를 할 수 있도록 하는 것입니다.

    일반적으로 compare() 메서드에서 -1은 첫 번째 값이 두 번째 값보다 작다는 의미입니다. 그러나 compare() 메서드에서 -1을 반환하는 경우는 두 객체를 비교하는 도중에 형변환이 실패하여 비교할 수 없는 객체가 있을 경우입니다. 이럴 경우에는 비교할 수 없으므로 -1을 반환하여 예외적인 상황임을 알리고 비교를 중단시키는 것이 좋습니다.

  • ✔️형변환이 되지 않을 때 -1을 반환하면 결국 해당 객체가 우측으로 밀려나게 되는 것이므로, 정렬 가능한 객체를 정렬한 다음 가장 마지막에 정렬 불가능한 객체가 모이게 되는 것 같다!


💡💡11-9. 두 가지 기준을 적용한 Comparator, 그리고 반 등수 계산

  • 문제 :
    [11-9] 문제11-8의 Student클래스에 반등수(classRank)를 저장하기 위한 인스턴스변수를 추가하였다.
    반등수를 계산하고 반과 반등수로 오름차순 정렬하여 결과를 출력하시오.
    (1)~(2)에 알맞은 코드를 넣어 완성하시오.
//[11-9] 문제11-8의 Student클래스에 반등수(classRank)를 저장하기 위한 인스턴스변수를 추가하였다.
//        반등수를 계산하고 반과 반등수로 오름차순 정렬하여 결과를 출력하시오.
//        (1)~(2)에 알맞은 코드를 넣어 완성하시오.

import java.util.*;

class Student implements Comparable {
    String name;
    int ban;
    int no;
    int kor;
    int eng;
    int math;


    int total;
    int schoolRank; // 전교등수

    int classRank; //⭐ 반등수

    Student(String name, int ban, int no, int kor, int eng, int math) {
        this.name = name;
        this.ban = ban;
        this.no = no;
        this.kor = kor;
        this.eng = eng;
        this.math = math;

        total = kor + eng + math;
    }

    int getTotal() {
        return total;
    }

    float getAverage() {
        return (int) ((getTotal() / 3f) * 10 + 0.5) / 10f;
    }

    public int compareTo(Object o) {
        if (o instanceof Student) {
            Student tmp = (Student) o;

            return tmp.total - this.total;
        } else {
            return -1;
        }
    }

    public String toString() {
        return name
                + "," + ban
                + "," + no
                + "," + kor
                + "," + eng
                + "," + math
                + "," + getTotal()
                + "," + getAverage()
                + "," + schoolRank

                + "," + classRank
                ;
    }

// 새로추가

} // class Student

class ClassTotalComparator implements Comparator {
    public int compare(Object o1, Object o2) {
    /*
    (1)	알맞은 코드를 넣어 완성하시오.
    */
    }
}

class Exercise11_9 {
    public static void calculateClassRank(List list) {
    // 먼저 반별 총점기준 내림차순으로 정렬한다.
    Collections.sort(list, new ClassTotalComparator());

        int prevBan = -1;
        int prevRank = -1;
        int prevTotal = -1;
        int length = list.size();

        /*
        (2)	아래의 로직에 맞게 코드를 작성하시오.
        1.	반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다.
        1.1	반이 달라지면,(ban과 prevBan이 다르면)
        이전 등수(prevRank)와 이전 총점(prevTotal)을 초기화한다.
        1.2	총점(total)이 이전총점(prevTotal)과 같으면
        이전 등수(prevRank)를 등수(classRank)로 한다.
        1.3	총점이 서로 다르면,
        등수(classRank)의 값을 알맞게 계산해서 저장한다.
        이전에 동점자였다면, 그 다음 등수는 동점자의 수를 고려해야 한다. (실행결과 참고)
        1.4	현재 반과 총점과 등수를 이전 반(prevBan),
        이전 총점(prevTotal), 이전 등수(prevRank)에 저장한다.
        */
    } //public static void calculateClassRank(List list) {
    public static void calculateSchoolRank(List list) {
        /* 내용 생략 */
    }

    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(new Student("이자바", 2, 1, 70, 90, 70));
        list.add(new Student("안자바", 2, 2, 60, 100, 80));
        list.add(new Student("홍길동", 1, 3, 100, 100, 100));
        list.add(new Student("남궁성", 1, 1, 90, 70, 80));
        list.add(new Student("김자바", 1, 2, 80, 80, 90));
        calculateSchoolRank(list);
        calculateClassRank(list);
        Iterator it = list.iterator();
        while (it.hasNext())
            System.out.println(it.next());
    }
}

/*
<실행결과>
홍길동,1,3,100,100,100,300,100.0,1,1
김자바,1,2,80,80,90,250,83.3,2,2
남궁성,1,1,90,70,80,240,80.0,3,3
안자바,2,2,60,100,80,240,80.0,3,1
이자바,2,1,70,90,70,230,76.7,5,2
 */
  • 내 풀이

    ⭐main단의 흐름에 따라 구현된 풀이 과정을 한 번 더 자세히 살펴보자!

import java.util.*;

class Student9 implements Comparable {
    String name;
    int ban;
    int no;
    int kor;
    int eng;
    int math;


    int total;
    int schoolRank; // 전교등수

    int classRank; //⭐ 반등수

    Student9(String name, int ban, int no, int kor, int eng, int math) {
        this.name = name;
        this.ban = ban;
        this.no = no;
        this.kor = kor;
        this.eng = eng;
        this.math = math;

        total = kor + eng + math;
    }

    int getTotal() {
        return total;
    }

    float getAverage() {
        return (int) ((getTotal() / 3f) * 10 + 0.5) / 10f;
    }

    //⭐ 기본정렬 방식은 '총점수'의 '내림차순'이다.
    public int compareTo(Object o) {
        if (o instanceof Student9) {
            Student9 tmp = (Student9) o;

            return tmp.total - this.total;
        } else {
            return -1;
        }
    }

    public String toString() {
        return name
                + "," + ban
                + "," + no
                + "," + kor
                + "," + eng
                + "," + math
                + "," + getTotal()
                + "," + getAverage()
                + "," + schoolRank

                + "," + classRank       //⭐ 마지막에 출력되는 것이 반 등수이다.
                ;
    }

// 새로추가

} // class Student

class ClassTotalComparator implements Comparator {
    //🔥🔥 실행 결과를 보니 반이 같으면 학급 석차로 오름차순, 반이 다르면 반으로 오름차순을 구성해야하는 문제이다.
    public int compare(Object o1, Object o2) {
    /*
    (1)	알맞은 코드를 넣어 완성하시오.
    */
        if(o1 instanceof Student9 && o2 instanceof Student9) {
            Student9 s1 = (Student9) o1;
            Student9 s2 = (Student9) o2;

            //🔥반이 다르면 반으로 오름차순
          if(s1.ban!=s2.ban)
                return s1.ban-s2.ban;
            //🔥반이 같으면 학급 석차로 오름차순
            else {
                return s1.classRank-s2.classRank;
            }
        } else
            return -1; //🔥 형변환이 실패했으면 Student9 객체가 아니라는 의미이므로, 우측으로 밀어버리자.
    }
}

class Sol_Exercise11_9 {
    public static void calculateClassRank(List list) {
        //⭐ 먼저 반별 총점기준 내림차순으로 정렬한다.
        //🔥 반 번호로 오름차순 이후 반이 같으면 반 석차로 오름차순
        Collections.sort(list, new ClassTotalComparator()); //🔥비교자도 객체를 사용해야함.

        int prevBan = -1;
        int prevRank = -1;
        int prevTotal = -1;
        int length = list.size();

        /*
        (2)	아래의 로직에 맞게 코드를 작성하시오.
        1.	반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다.
        1.1	반이 달라지면,(ban과 prevBan이 다르면)
        이전 등수(prevRank)와 이전 총점(prevTotal)을 초기화한다.
        1.2	총점(total)이 이전총점(prevTotal)과 같으면
        이전 등수(prevRank)를 등수(classRank)로 한다.
        1.3	총점이 서로 다르면,
        등수(classRank)의 값을 알맞게 계산해서 저장한다.
        이전에 동점자였다면, 그 다음 등수는 동점자의 수를 고려해야 한다. (실행결과 참고)
        1.4	현재 반과 총점과 등수를 이전 반(prevBan),
        이전 총점(prevTotal), 이전 등수(prevRank)에 저장한다.
        */

        /*
        ⭐ 0. main단의 로직상 calculateSchoolRank(list)을 수행하는데,
        이 calculateSchoolRank(list)에는 Collections.sort(list)로 기본정렬을 수행한다.
        Student9의 기본정렬은 위의 int compareTo(Object o)에서 구현되어 있듯이, 총점수 내림차순이다.
        ⭐따라서 총점수 내림차순 이후 전교 석차를 매기는 로직이 이미 실행되고 난 이후라는 사실을 생각하자!
        ⭐게다가, Collections.sort(list, new ClassTotalComparator());를 수행했으니 반오름차순 다음 반 석차 오름차순이 수행된 상태이다.
         */

        int banGrade = 1; //🔥 int banGrade는 반 등수를 계산할 변수이다.
        //🔥 1. 반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다. (⭐총점수 내림차순된 순으로 정렬되어 있을 것)
        for(int i=0; i<length; i++) {
            Student9 s = (Student9) list.get(i);
        //🔥 1.1 반이 달라지면 이전 등수와 이전 총점을 초기화한다.
            if(s.ban!=prevBan) {
                prevTotal = -1; // prevBan을 초기화하는게 아니라 이전 총점prevTotal을 초기화해야함
                prevRank = -1; //🔥 반이 달라지면 반 석차를 1로 초기화되어야 함.
                banGrade = 1; //🔥 반이 달라지면 반 등수도 초기화해야 함.
            }
        //🔥 1.2 총점이 이전총점과 같으면 이전 등수를 반 등수로 한다.
            if(s.total==prevTotal) s.classRank = prevRank;
        //🔥 1.3 총점이 서로 다르면 등수의 값을 알맞게 계산해서 저장한다.
            if(s.total!=prevTotal) {
                s.classRank = banGrade;   //🔥banGrade는 반이 달라지면 0이되고, 반이 같으면 계속 1씩 증가한다.
            }
        //🔥 1.4 현재 반, 총점과 등수를 이전 반, 이전 총점, 이전 등수에 저장한다.
            prevBan =s.ban ;
            prevTotal = s.total;
            prevRank = s.classRank;
            banGrade++; //🔥 반 등수는 반이 달라질 때 0으로 초기화되며, 반이 같을 때 ++되어야 한다.
        }

    } //public static void calculateClassRank(List list) {
    public static void calculateSchoolRank(List list) {
        /* 내용 생략 */
        Collections.sort(list); // 먼저 list를 총점기준 내림차순으로 정렬한다. (🔥 Comparable로 구현)

        int prevRank = -1;    // ⭐이전 전교등수
        int prevTotal = -1;		// ⭐이전 총점
        int length = list.size();   //⭐전교 학생 수

        /*
        (2)	아래의 로직에 맞게 코드를 작성하시오.
        1.	반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다.
        1.1	총점(total)이   이전총점(prevTotal)과   같으면 이전 등수(prevRank)를 등수(schoolRank)로 한다. (🔥 이해 안됨.)
        1.2	총점이 서로 다르면,
        등수(schoolRank)의 값을 알맞게 계산해서 저장한다.
        이전에 동점자 였다면, 그 다음 등수는 동점자의 수를 고려해야 한다. (실행결과 참고)
        1.3	현재 총점과 등수를 이전총점(prevTotal)과 이전등수(prevRank)에 저장한다.
        */
        Iterator it = list.iterator();
        for(int i=0; it.hasNext(); i++) {
            Object itSud = it.next();
            if(itSud instanceof  Student9) {
                Student9 s = (Student9) itSud;
                if(s.total==prevTotal) {
                    s.schoolRank = prevRank;
                } else {
                    //⭐⭐i는 계속해서 진행하고, 동점자가 아닐경우에만 학교등수를 매기므로 동점자는 자연스럽게 건너뛰게 된다.
                    s.schoolRank = i+1;
                }
                //⭐⭐ s.total로 s.schoolRank 연산이 완료되면 prevTotal, prevRank에 각각 저장하여 등수를 비교하는 기준으로 삼는다.
                prevTotal = s.total;
                prevRank = s.schoolRank;
            }
        }
    }

    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(new Student9("이자바", 2, 1, 70, 90, 70));
        list.add(new Student9("안자바", 2, 2, 60, 100, 80));
        list.add(new Student9("홍길동", 1, 3, 100, 100, 100));
        list.add(new Student9("남궁성", 1, 1, 90, 70, 80));
        list.add(new Student9("김자바", 1, 2, 80, 80, 90));
        calculateSchoolRank(list);
        calculateClassRank(list);
        Iterator it = list.iterator();
        while (it.hasNext())
            System.out.println(it.next());
    }
}

/*
<실행결과>
홍길동,1,3,100,100,100,300,100.0,1,1
김자바,1,2,80,80,90,250,83.3,2,2
남궁성,1,1,90,70,80,240,80.0,3,3
안자바,2,2,60,100,80,240,80.0,3,1
이자바,2,1,70,90,70,230,76.7,5,2

<내 실행결과>
홍길동,1,3,100,100,100,300,100.0,1,1
김자바,1,2,80,80,90,250,83.3,2,2
남궁성,1,1,90,70,80,240,80.0,3,3
안자바,2,2,60,100,80,240,80.0,3,1
이자바,2,1,70,90,70,230,76.7,5,2
 */
  • 💡💡개념:
    Comparable기본 정렬이 아닌, 비교자 Comparator를 사용한 정렬에서 Collections.sort(컬렉션객체, 비교자객체)로 Comparator를 사용함에 주의하자!
    난, Comparator의 사용 방법이 헷갈렸다.
public static void calculateClassRank(List list) {
        //⭐ 먼저 반별 총점기준 내림차순으로 정렬한다.
        //🔥 반 번호로 오름차순 이후 반이 같으면 반 석차로 오름차순
        Collections.sort(list, new ClassTotalComparator()); //🔥비교자도 객체를 사용해야함.
  • 다시 보기 (Comparator 클래스 구현)
class ClassTotalComparator implements Comparator {
    //🔥🔥 실행 결과를 보니 반이 같으면 학급 석차로 오름차순, 반이 다르면 반으로 오름차순을 구성해야하는 문제이다.
    public int compare(Object o1, Object o2) {
    /*
    (1)	알맞은 코드를 넣어 완성하시오.
    */
        if(o1 instanceof Student9 && o2 instanceof Student9) {
            Student9 s1 = (Student9) o1;
            Student9 s2 = (Student9) o2;

            //🔥반이 다르면 반으로 오름차순
          if(s1.ban!=s2.ban)
                return s1.ban-s2.ban;
            //🔥반이 같으면 학급 석차로 오름차순
            else {
                return s1.classRank-s2.classRank;
            }
        } else
            return -1; //🔥 형변환이 실패했으면 Student9 객체가 아니라는 의미이므로, 우측으로 밀어버리자.
    }
}
  • 다시 보기 (반 석차 구성하기)
public static void calculateClassRank(List list) {
        //⭐ 먼저 반별 총점기준 내림차순으로 정렬한다.
        //🔥 반 번호로 오름차순 이후 반이 같으면 반 석차로 오름차순
        Collections.sort(list, new ClassTotalComparator()); //🔥비교자도 객체를 사용해야함.

        int prevBan = -1;
        int prevRank = -1;
        int prevTotal = -1;
        int length = list.size();

        /*
        (2)	아래의 로직에 맞게 코드를 작성하시오.
        1.	반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다.
        1.1	반이 달라지면,(ban과 prevBan이 다르면)
        이전 등수(prevRank)와 이전 총점(prevTotal)을 초기화한다.
        1.2	총점(total)이 이전총점(prevTotal)과 같으면
        이전 등수(prevRank)를 등수(classRank)로 한다.
        1.3	총점이 서로 다르면,
        등수(classRank)의 값을 알맞게 계산해서 저장한다.
        이전에 동점자였다면, 그 다음 등수는 동점자의 수를 고려해야 한다. (실행결과 참고)
        1.4	현재 반과 총점과 등수를 이전 반(prevBan),
        이전 총점(prevTotal), 이전 등수(prevRank)에 저장한다.
        */

        /*
        ⭐ 0. main단의 로직상 calculateSchoolRank(list)을 수행하는데,
        이 calculateSchoolRank(list)에는 Collections.sort(list)로 기본정렬을 수행한다.
        Student9의 기본정렬은 위의 int compareTo(Object o)에서 구현되어 있듯이, 총점수 내림차순이다.
        ⭐따라서 총점수 내림차순 이후 전교 석차를 매기는 로직이 이미 실행되고 난 이후라는 사실을 생각하자!
        ⭐게다가, Collections.sort(list, new ClassTotalComparator());를 수행했으니 반오름차순 다음 반 석차 오름차순이 수행된 상태이다.
         */

        int banGrade = 1; //🔥 int banGrade는 반 등수를 계산할 변수이다.
        //🔥 1. 반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다. (⭐총점수 내림차순된 순으로 정렬되어 있을 것)
        for(int i=0; i<length; i++) {
            Student9 s = (Student9) list.get(i);
            //🔥 1.1 반이 달라지면 이전 등수와 이전 총점을 초기화한다.
            if(s.ban!=prevBan) {
                prevTotal = -1; // prevBan을 초기화하는게 아니라 이전 총점prevTotal을 초기화해야함
                prevRank = -1; //🔥 반이 달라지면 반 석차를 1로 초기화되어야 함.
                banGrade = 1; //🔥 반이 달라지면 반 등수도 초기화해야 함.
            }
            //🔥 1.2 총점이 이전총점과 같으면 이전 등수를 반 등수로 한다.
            if(s.total==prevTotal) s.classRank = prevRank;
            //🔥 1.3 총점이 서로 다르면 등수의 값을 알맞게 계산해서 저장한다.
            if(s.total!=prevTotal) {
                s.classRank = banGrade;   //🔥banGrade는 반이 달라지면 0이되고, 반이 같으면 계속 1씩 증가한다.
            }
            //🔥 1.4 현재 반, 총점과 등수를 이전 반, 이전 총점, 이전 등수에 저장한다.
            prevBan =s.ban ;
            prevTotal = s.total;
            prevRank = s.classRank;
            banGrade++; //🔥 반 등수는 반이 달라질 때 0으로 초기화되며, 반이 같을 때 ++되어야 한다.
        }

    } //public static void calculateClassRank(List list)
  • 모범 풀이
class ClassTotalComparator implements Comparator {
        public int compare(Object o1, Object o2) {
        Student s1 = (Student) o1;
        Student s2 = (Student) o2;

        int result = s1.ban - s2.ban; // 반(ban) 기준 정렬(오름차순)

        if (result == 0)
            result = s2.total - s1.total;    // 총점(total) 기준 정렬(내림차순)
                                            //✔️ 나와 달리 반이 같으면 총점 기준으로 정렬했다. 생각해보니 반 석차는 아직 정해지기 전이잖아..

        return result;
    }
}

class Exercise11_9 {
    public static void calculateClassRank(List list) {
// 먼저 반별 총점기준 내림차순으로 정렬한다.
        Collections.sort(list, new ClassTotalComparator());

        int prevBan = -1;
        int prevRank = -1;
        int prevTotal = -1;
        int length = list.size();

        //💡💡✔️✔️ 반 석차를 위한 n이라는 등수를 for문에 추가했다. 마찬가지로 증감식에 n++를 추가했다!
        for (int i = 0, n = 0; i < length; i++, n++) {
        //⭐	1. 반복문을 이용해서 list에 저장된 Student객체를 하나씩 읽는다.
            Student s = (Student) list.get(i);

        //⭐	1.1 반이 달라지면,(ban와 prevBan이 다르면)
        //	이전 등수(prevRank)와 이전 총점(prevTotal)을 초기화 한다.
            if (s.ban != prevBan) {
                prevRank = -1;  //❗❗ 나와 달리 prevRank를 1로 초기화하지 않았다.
                                //❓그런데 1반의 마지막 석차 학생의 총점과 2반의 1등 학생 총점이 같으면 1.2에서 반 등수로 -1을 반환하지 않나?
                                //✔️ 아니지, prevTotal을 초기화하는 데 상관없지!
                prevTotal = -1;
                n = 0;  //✔️ 마찬가지로 반이 바뀌면 n 초기화
            }

        //	1.2 총점(total)이 이전총점(prevTotal)과 같으면
        //	이전 등수(prevRank)를 등수(classRank)로 한다.
            if (s.total == prevTotal) {
                s.classRank = prevRank;
            } else {
        //	1.3 총점이  서로  다르면,
        //	등수(classRank)의 값을 알맞게 계산해서 저장한다.
        //	이전에 동점자였다면, 그 다음 등수는 동점자의 수를 고려해야한다.
                s.classRank = n + 1;
            }

        //	1.4 현재 반과 총점과 등수를 이전 반(prevBan),
        //	이전 총점(prevTotal), 이전 등수(prevRank)에 저장한다.
            prevBan = s.ban;
            prevRank = s.classRank;
            prevTotal = s.total;
        }
    } // public static void calculateClassRank(List list)
  • 나와 다른 점 : Comparator를 구현할 때 total의 내림차순 비교를 사용했다. 생각해보니 아직 반 석차가 정해지기 이전에 Collections.sort하는 것이라 당연히 총점기준 비교를 해야한다.

  • 💡💡 아이디어 :
    초기화문과 증감식에 각각 반 석차를 의미하는 n을 삽입하면 훨씬 깔끔한 코드가 작성된다.

//💡💡✔️✔️ 반 석차를 위한 n이라는 등수를 for문에 추가했다. 마찬가지로 증감식에 n++를 추가했다!
        for (int i = 0, n = 0; i < length; i++, n++) {


✔️11-10. Set과 List의 장점 함께 이용하기

  • 문제:
    [11-10] 다음 예제의 빙고판은 1~30사이의 숫자들로 만든 것인데,
    숫자들의 위치가 잘 섞이지 않는다는 문제가 있다.
    이러한 문제가 발생하는 이유와 이 문제를 개선하기 위한 방법을 설명하고,
    이를 개선한 새로운 코드를 작성하시오.
import java.util.*;

class Sol_Exercise11_10 {
    public static void main(String[] args) {
        Set set = new HashSet();
        int[][] board = new int[5][5];

        //⭐ 1~30 사이의 임의의 수를 String 타입으로 25개 만들어서 HashSet 객체에 저장
        for (int i = 0; set.size() < 25; i++) {
            set.add((int) (Math.random() * 30) + 1 + "");
        }

        //⭐HashSet의 반복자 객체를 it에 저장
        Iterator it = set.iterator();
        //⭐5x5 이차원 배열인 board.length의 행(i)과 열(j)를 순회
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[i].length; j++) {
                //⭐ set에 저장했던 String타입 요소들이 board[i][j]에 int타입으로 담김
                board[i][j] = Integer.parseInt((String) it.next());
                //⭐ 한자리 숫자는 공백 둘을 주고, 두자리 숫자는 공백 하나 띄고 출력
                System.out.print((board[i][j] < 10 ? "  " : " ")
                        + board[i][j]);
            }
            System.out.println();
        }

        System.out.println();

    } // main
}
  • 풀이 접근:
    빙고판의 숫자의 위치가 잘 섞이지 않는 이유는
    자료구조로 Set을 구현한 HashSet을 사용했기 때문이다.
    Set은 저장순서를 유지하지않으며, 중복을 허용하지 않는다.
    이 중, 저장순서를 유지하지 않는다는 점이 순서를 섞어도 저장되지 않는 문제를 발생시킨다.
    정확히는 Set이 독자적인 규격으로 String 요소를 저장하고 있기 때문에
    결과적으로 1~30의 난수를 발생시켰음에도 불구하고 비슷한 위치에 해당 숫자가 들어가게 된것이다.

    따라서 랜덤 생성된 그대로 저장순서를 유지하기 위해서는 Set을 List 타입으로 변경할 수 있다.
    방법으로는 ArrayList 객체를 구현해서 arrayList.addAll(Collection c)를 사용하던지,
    List list = new ArrayList(Collection c) 생성자로 변환하면 저장 순서를 유지할 수 있을 것이다.

    🔥🔥🔥 단, set이 이미 난수를 add하기 전에 List로 바뀌어 있어야 한다.
    🔥🔥🔥 단, 빙고는 숫자의 중복을 허용하지 않아야 한다.
    위의 두 가지 문제를 해결해야 하기 때문에 set으로 바꾸기 전 List로 바꾼다는 생각은 하지 않는다.
    결국 set의 중복을 허용하지 않는다는 장점과 list의 저장순서를 유지한다는 장점을 결합시켜야 한다.
    set을 List에 그대로 담고 랜덤 Comparator로 섞어주면 어떨까?
    랜덤 Comparator는 1과 -1만을 5:5 비율로 리턴하는 int compare(Object o1, Object o2)를 가지고 있는거지...
    ➡️ 성공적이었다.

  • 문제 풀이1

import java.util.*;

class Sol_Exercise11_10 {
    public static void main(String[] args) {
        Set set = new HashSet();
        int[][] board = new int[5][5];

        //⭐ 1~30 사이의 임의의 수를 String 타입으로 25개 만들어서 HashSet 객체에 저장
        for (int i = 0; set.size() < 25; i++) {
            set.add((int) (Math.random() * 30) + 1 + "");
        }
        System.out.println(set);
        //⭐HashSet의 반복자 객체를 it에 저장
        Iterator it = set.iterator();
        //⭐5x5 이차원 배열인 board.length의 행(i)과 열(j)를 순회
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[i].length; j++) {
                //⭐ set에 저장했던 String타입 요소들이 board[i][j]에 int타입으로 담김
                board[i][j] = Integer.parseInt((String) it.next());
                //⭐ 한자리 숫자는 공백 둘을 주고, 두자리 숫자는 공백 하나 띄고 출력
                System.out.print((board[i][j] < 10 ? "  " : " ")
                        + board[i][j]);
            }
            System.out.println();
        }

        System.out.println();

        //🔥 set 요소의 내용 담기
        List list = new ArrayList(set);
        System.out.println(list);
        //🔥 Collections.sort(list, RandomComparator)로 순서 섞어주기
        Collections.sort(list, new RandomComparator());
        System.out.println(list);
        Iterator it2 = list.iterator();
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[i].length; j++) {
                board[i][j] = Integer.valueOf((String) it2.next());
                System.out.print((board[i][j] < 10 ? "  " : " ") + board[i][j]);
            }
            System.out.println();


        }

    }// main
}

<실행결과>
//🔥set에 저장된 것
[23, 24, 25, 26, 27, 28, 30, 10, 12, 14, 15, 16, 17, 18, 19, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20] 
 23 24 25 26 27
 28 30 10 12 14
 15 16 17 18 19
  1  2  3  4  5
  6  7  8  9 20

//🔥 set -> ArrayList로 옮긴 것
[23, 24, 25, 26, 27, 28, 30, 10, 12, 14, 15, 16, 17, 18, 19, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20]
//🔥Collections.sort(ArrayList, new RandomComaparator())로 섞은 것
[3, 23, 9, 12, 10, 2, 1, 27, 16, 28, 6, 18, 20, 30, 24, 14, 8, 4, 25, 17, 26, 19, 5, 7, 15]
  3 23  9 12 10
  2  1 27 16 28
  6 18 20 30 24
 14  8  4 25 17
 26 19  5  7 15
  • 모범 풀이
import java.util.*;

class Exercise11_10_2 {
    public static void main(String[] args) {
        Set set = new HashSet();
        int[][] board = new int[5][5];

        for (int i = 0; set.size() < 25; i++) {
            set.add((int) (Math.random() * 30) + 1 + "");
        }

        //⭐ HashSet에 저장된 요소들을 ArrayList에 옮긴다.
        ArrayList list = new ArrayList(set);
        //⭐⭐ Collections.shuffle을 이용해 섞어준다.
        Collections.shuffle(list);
        
        //⭐반복자를 이용해 출력한다.
        Iterator it = list.iterator();

        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[i].length; j++) {
                board[i][j] = Integer.parseInt((String) it.next());
                System.out.print((board[i][j] < 10 ? " " : " ") + board[i][j]);
            }
            System.out.println();
        }
    } // main
}

<실행결과>
매번 5x5의 완전히 다른 행렬이 나온다.

<해설>
[해설] 중복된 값을 허용하지 않는 다는 특성을 이용해서 HashSet에 서로 다른 임의의 값 을 저장하는 것까지는 좋았는데,
⭐해싱알고리즘의 특성상 같은 값은 같은 자리에 저장되기 때문에 빙고판의 숫자들이 잘 섞이지 않는다는 문제가 발생하였다.
그래서 저장순서를 유지하는 ArrayListHashSet의 데이터를 옮겨 담은 다음,
Collections.shuffle()을 이용해서 저장된 데이터들의 순서를 뒤섞었다.

이처럼 대부분의 컬렉션 클래스들은 다른 컬렉션으로 데이터를 쉽게 옮길 수 있게 설계되어 있다.
  • Collections.shuffle을 이용하면 쉽게 풀 수 있는 문제였다.

  • ✔️✔️HashSet은 해싱 알고리즘이 적용되어 특수한 저장 알고리즘이 작용되었던게 가장 큰 문제였다!

  • 💡 개념

💡매개변수의 타입이 Collection인터페이스이므로 Collection 인터페이스의 자손인 List인터페이스와
Set인터페이스를 구현한 모든 클래스의 인스턴스가 매개변수로 가능하다.
ArrayList(Collection<? extends E> c)
LinkedList(Collection<? extends E> c)
Vector(Collection<? extends E> c)
HashSet(Collection<? extends E> c)
TreeSet(Collection<? extends E> c)
LinkedHashSet(Collection<? extends E> c)

11-11. HashMap 해싱 구현하기

  • 문제:
    [11-11] 다음은 SutdaCard클래스를
    HashSet에 저장하고 출력하는 예제이다.
    HashSet에 중복된 카드가 저장되지 않도록 SutdaCard의 hashCode()를 알맞게 오버라이딩하시오.
    [Hint] String클래스의 hashCode()를 사용하라.
<import java.util.*;

class SutdaCard {
    int num;
    boolean isKwang;

    SutdaCard() {
        this(1, true);
    }

    SutdaCard(int num, boolean isKwang) {
        this.num = num;
        this.isKwang = isKwang;
    }

    public boolean equals(Object obj) {
        if (obj instanceof SutdaCard) {
            SutdaCard c = (SutdaCard) obj;
            return num == c.num && isKwang == c.isKwang;
        } else {
            return false;
        }
    }

    public String toString() {
        return num + (isKwang ? "K" : "");
    }
}

class Exercise11_11 {
    public static void main(String[] args) {
        SutdaCard c1 = new SutdaCard(3, true);
        SutdaCard c2 = new SutdaCard(3, true);
        SutdaCard c3 = new SutdaCard(1, true);

        HashSet set = new HashSet();
        set.add(c1);
        set.add(c2);
        set.add(c3);

        System.out.println(set);
    }
}

/*
<실행결과>
[3K, 1K]
 */
  • 풀이 접근
<🔥풀이접근>
HashSet의 특성에 대해 이해하고 있어야 하는 문제이다.
HashSetSet을 해싱 방식으로 구현한 자료구조이며,
해싱이란 배열 + 링크드 리스트의 조합을 의미한다. 키값에 따라 다른 해시코드를 반환한다. (해싱 과정에서 손실이 발생하지 않는다면)
배열 인덱스는 서랍의 위치, 링크드 리스트 객체는 서랍이며, 각 노드들은 서류가 될 것이다.
기본적으로 Set을 구현하기 때문에 HashSet 역시 중복을 허용하지 않는다.
이 중복X 특성을 구현하기 위하여 HashSet은 기존 객체와 새 객체의 equals와 hashCode의 값이 같은지 여부를 살핀다.
🔥equals메서드가 true가 나온다면 반드시 두 객체의 hashCode 값이 같아야 한다.

🔥현 상황에서 Sutda 클래스에서 equals메서드는 iv값을 비교하는 것으로 재정의 되어 있지만,
int hashCode() 메서드는 재정의 되어 있지 않기 때문에 HashSet을 사용했음에도 불구하고
3K로 동일한 iv를 가지고 있는 객체를 서로 다른 객체로 인식한다.
따라서 [1K, 3K, 3K]를 출력하게 되는 것이다.

🔥결론적으로, Sutda 클래스에 동일한 iv값을 가지고 있으면 동일한 hashCode()를 반환하도록 int hashCode()를 오버라이딩하면 된다.
앞의 조건은 equals()로 구현하였으므로 equals를 통한 조건식을 사용하던지, Object.hash(iv, iv)를 사용한다.Object.hash() 자동완성이 안되는데? Objects였다...
또는 String에 구현되어있는 hashCode()를 이용해서 this.toString.hashCode()를 사용하면 될 것이다.

<참고 자료>
Java Objects.hash() 대 Objects.hashCode()

  • 풀이
import java.util.*;

class SutdaCard {
    int num;
    boolean isKwang;

    SutdaCard() {
        this(1, true);
    }

    SutdaCard(int num, boolean isKwang) {
        this.num = num;
        this.isKwang = isKwang;
    }

    public boolean equals(Object obj) {
        if (obj instanceof SutdaCard) {
            SutdaCard c = (SutdaCard) obj;
            return num == c.num && isKwang == c.isKwang;
        } else {
            return false;
        }
    }

    //🔥 hashCode() 오버라이딩
    @Override
    public int hashCode() {
        //🔥1. String 클래스의 hashCode()를 그대로 이용하는 방법
        //return this.toString().hashCode();

        //🔥2. Objects.hash() 사용하기. 자바.유틸의 Object's'임을 확인!
        return Objects.hash(this.num, this.isKwang);

        //🔥3. SutdaCard에 구현되어있는 eqauls()이용해서 hashCode 만들어 반환하기
        // 인자값이 없는 hashCode()메서드라 구현이 불가능. 다른 곳에서 직접 만들어줘야 함.
    }

    public String toString() {
        return num + (isKwang ? "K" : "");
    }
}

class Sol_Exercise11_11 {
    public static void main(String[] args) {
        SutdaCard c1 = new SutdaCard(3, true);
        SutdaCard c2 = new SutdaCard(3, true);
        SutdaCard c3 = new SutdaCard(1, true);

        HashSet set = new HashSet();
        set.add(c1);
        set.add(c2);
        set.add(c3);

        System.out.println(set);
    }
}

<실행결과>
[3K, 1K]
  • 해설
[해설]
hashCode()의 기본 구현은 클래스이름과 메모리주소와 관련된 정수값으로 이루어져 있기 때문에,
⭐두 객체의 hashCode()값은 절대로 같을 수가 없다.(서로 다른 두 객체가 같은 메모리 번지에 존재할 수 없기 때문에)
⭐대부분의 경우 서로 다른 객체라도 클래스의 인스턴스변수 값이 같으면,
예를 들어 SutdaCar의 경우 num과 isKwang의 값이 같으면 같은 객체로 인식해야한다.
⭐즉, equals()의 결과가 true이어야하고, 두 객체의 해시코드(hashCode()를 호출한 결과)가 같아야 한다.
그래서 equals()hashCode()를 적절히 오버라이딩 해줘야 한다.

때로는 equals()만 오버라이딩해줘도 되지만,
⭐해시알고리즘을 사용하는 HashSet에 담을 때는 반드시 hashCode()도 오버라이딩해줘야 한다.
이 문제의 실행결과를 보면 중복을 허용하지 않는 HashSet을 사용하고도 [1K, 3K, 3K]와 같은 결과를 얻는다.
그 이유는 hashCode()를 오버라이딩 하지 않았기 때문이다.
그러나 hashCode()를 오버라이딩한 후에는 [3K, 1K]와 같이 중복이 제거된 결과를 얻을수 있다.

hashCode()를 오버라이딩하라고 하면 어떻게 해야 할지 막막할 것이다.
그러나 걱정하지 말자. 이미 다 구현되어 있으니 그냥 가져다 쓰기만 하면 된다.
⭐⭐String클래스의 hashCode()는 객체의 주소가 아닌 문자열의 내용을 기반으로 해시코드를 생성하므로
문자열의 내용이 같으면 항상 같은 값의 해시코드를 반환한다.

SutdaCardtoString()이 num과 isKwang의 값으로 문자열을 만들어 반환하기 때문에,
toString()을 호출한 결과에 hashCode()를 호출함으로써
SutdaCardhashCode()를 간단히 구현할 수 있었다.

    public String toString() {
        return num + ( isKwang ? "K":"");
    }

    public int hashCode() {
        return toString().hashCode(); // String클래스의 hashCode()를 호출한다.

    }
  • ⭐해시알고리즘을 사용하는 HashSet에 담을 때는 반드시 hashCode()도 오버라이딩해줘야 한다.
  • ⭐대부분의 경우 서로 다른 객체라도 클래스의 인스턴스변수 값이 같으면,
    예를 들어 SutdaCar의 경우 num과 isKwang의 값이 같으면 같은 객체로 인식해야한다.
  • ⭐⭐String클래스의 hashCode()는 객체의 주소가 아닌 문자열의 내용을 기반으로 해시코드를 생성하므로
    문자열의 내용이 같으면 항상 같은 값의 해시코드를 반환한다.

11-12. HashMap에 값 등록하고 읽어오기

  • 문제:[11-12] 다음은 섯다게임에서 카드의 순위를 결정하는 등급목록(족보)이다.
    HashMap에 등급과 점수를 저장하는 registerJokbo()와
    게임참가자의 점수를 계산해서 반환하는 getPoint()를 완성하시오.
    [참고] 섯다게임은 두 장의 카드의 숫자를 더한 값을 10으로 나눈 나머지가 높은 쪽이 이기는 게임이다.
    그 외에도 특정 숫자로 구성된 카드로 이루어진 등급(족보)이 있어서 높은 등급의 카드가 이긴다.
import java.util.*;

class Exercise11_12 {
    public static void main(String args[]) throws Exception {
        SutdaDeck deck = new SutdaDeck();

        deck.shuffle();
        Player p1 = new Player("타짜", deck.pick(), deck.pick());
        Player p2 = new Player("고수", deck.pick(), deck.pick());

        System.out.println(p1 + " " + deck.getPoint(p1));
        System.out.println(p2 + " " + deck.getPoint(p2));
    }
}

class SutdaDeck {
    final int CARD_NUM = 20;
    SutdaCard[] cards = new SutdaCard[CARD_NUM];

    int pos = 0; // 다음에 가져올 카드의 위치
    HashMap jokbo = new HashMap(); // 족보를 저장할 HashMap

    SutdaDeck() {
        for (int i = 0; i < cards.length; i++) {
            int num = i % 10 + 1;
            boolean isKwang = i < 10 && (num == 1 || num == 3 || num == 8);

            cards[i] = new SutdaCard(num, isKwang);
        }
        registerJokbo(); // 족보를 등록한다.
    }

    void registerJokbo() {
/*
(1)	아래의 로직에 맞게 코드를 작성하시오.
1. jokbo(HashMap)에 족보를 저장한다.
두 카드의 값을 문자열로 붙여서 key로, 점수를 value로 저장한다.
*/
    }

    int getPoint(Player p) {
        if (p == null) return 0;

        SutdaCard c1 = p.c1;
        SutdaCard c2 = p.c2;

        Integer result = 0;

/*
(2)	아래의 로직에 맞게 코드를 작성하시오.
1.	카드 두 장이 모두 광이면, jokbo에서 키를 "KK"로 해서 점수를 조회한다.
2.	두 카드의 숫자(num)로 jokbo에서 등급을 조회한다.
3.	해당하는 등급이 없으면, 아래의 공식으로 점수를 계산한다. (c1.num + c2.num) % 10 + 1000
4.	Player의 점수(point)에 계산한 값을 저장한다.
*/

        return result.intValue();
    }

    SutdaCard pick() throws Exception {
        SutdaCard c = null;

        if (0 <= pos && pos < CARD_NUM) {
            c = cards[pos];
            cards[pos++] = null;
        } else {
            throw new Exception("남아있는 카드가 없습니다.");
        }

        return c;
    }

    void shuffle() {
        for (int x = 0; x < CARD_NUM * 2; x++) {
            int i = (int) (Math.random() * CARD_NUM);
            int j = (int) (Math.random() * CARD_NUM);

            SutdaCard tmp = cards[i];
            cards[i] = cards[j];
            cards[j] = tmp;
        }
    }
} // SutdaDeck

class Player {
    String name;
    SutdaCard c1;
    SutdaCard c2;

    int point; // 카드의 등급에 따른 점수 - 새로 추가

    Player(String name, SutdaCard c1, SutdaCard c2) {
        this.name = name;
        this.c1 = c1;
        this.c2 = c2;
    }

    public String toString() {
        return "[" + name + "]" + c1.toString() + "," + c2.toString();
    }
} // class Player

class SutdaCard {
    int num;
    boolean isKwang;

    SutdaCard() {
        this(1, true);
    }

    SutdaCard(int num, boolean isKwang) {
        this.num = num;
        this.isKwang = isKwang;
    }

    public String toString() {
        return num + (isKwang ? "K" : "");
    }
}

/*
<실행결과>
[타짜]5,9 1004
[고수]1,1K 3010
 */
  • 문제 풀이:
import java.util.HashMap;

class Exercise11_12 {
    public static void main(String args[]) throws Exception {
        SutdaDeck deck = new SutdaDeck();

        //⭐deck을 섞는다.
        deck.shuffle();
        //⭐p1과 p2가 섞인 덱에 있는 카드를 2개씩 뽑는다.
        Player p1 = new Player("타짜", deck.pick(), deck.pick());
        Player p2 = new Player("고수", deck.pick(), deck.pick());

        //⭐계산한 점수를 알려준다.
        System.out.println(p1 + " " + deck.getPoint(p1));
        System.out.println(p2 + " " + deck.getPoint(p2));
    }
}

class SutdaDeck {
    final int CARD_NUM = 20;
    SutdaCard12[] cards = new SutdaCard12[CARD_NUM];

    int pos = 0; // 다음에 가져올 카드의 위치
    HashMap jokbo = new HashMap(); // 족보를 저장할 HashMap

    SutdaDeck() {
        for (int i = 0; i < cards.length; i++) {
            int num = i % 10 + 1;
            boolean isKwang = i < 10 && (num == 1 || num == 3 || num == 8);

            cards[i] = new SutdaCard12(num, isKwang);
        }
        registerJokbo(); // 족보를 등록한다.
    }

    void registerJokbo() {
    /*
    (1)	🔥🔥아래의 로직에 맞게 코드를 작성하시오.
    1. jokbo(HashMap)에 족보를 저장한다.
    두 카드의 값을 문자열로 붙여서 key로, 점수를 value로 저장한다.
    */
    jokbo.put("KK",4000);
    for(int i=10, j=10, n=0; i>0; i--, j--, n+=10) {
       jokbo.put(i+""+j, 3100-n);
    }
    jokbo.put("12",2060);
    jokbo.put("21",2060);
    jokbo.put("14",2050);
    jokbo.put("41",2050);
    jokbo.put("19",2040);
    jokbo.put("91",2040);
    jokbo.put("110",2030);
    jokbo.put("101",2030);
    jokbo.put("410",2020);
    jokbo.put("104",2020);
    jokbo.put("46",2010);
    jokbo.put("64",2010);

    }

    int getPoint(Player p) {
        if (p == null) return 0;

        SutdaCard12 c1 = p.c1;
        SutdaCard12 c2 = p.c2;

        Integer result = 0;

    /*
    (2)	🔥🔥아래의 로직에 맞게 코드를 작성하시오.
    1.	카드 두 장이 모두 광이면, jokbo에서 키를 "KK"로 해서 점수를 조회한다.
    2.	두 카드의 숫자(num)로 jokbo에서 등급을 조회한다.
    3.	해당하는 등급이 없으면, 아래의 공식으로 점수를 계산한다. (c1.num + c2.num) % 10 + 1000
    4.	Player의 점수(point)에 계산한 값을 저장한다.
    */
        if(c1.isKwang&&c2.isKwang) result = (Integer) jokbo.get("KK");
        result = (Integer) jokbo.get(c1.num+""+c2.num); //🔥처음엔 jokbo.get(c1.num+c2.num+"")로 했는데 잘못된 값이 나옴. 덧셈후 문자열로 바뀌기 때문
        if(result==null) result=(c1.num+c2.num)%10 +1000;

        return result.intValue();
    }

    //⭐ card를 0번부터 뽑는 메서드 뽑은 카드 인덱스는 null, pos는+1함.
    SutdaCard12 pick() throws Exception {
        SutdaCard12 c = null;

        if (0 <= pos && pos < CARD_NUM) {
            c = cards[pos];
            cards[pos++] = null;
        } else {
            throw new Exception("남아있는 카드가 없습니다.");
        }

        return c;
    }

    void shuffle() {
        for (int x = 0; x < CARD_NUM * 2; x++) {
            int i = (int) (Math.random() * CARD_NUM);
            int j = (int) (Math.random() * CARD_NUM);

            SutdaCard12 tmp = cards[i];
            cards[i] = cards[j];
            cards[j] = tmp;
        }
    }
} // SutdaDeck

class Player {
    String name;
    SutdaCard12 c1;
    SutdaCard12 c2;

    int point; // 카드의 등급에 따른 점수 - 새로 추가

    Player(String name, SutdaCard12 c1, SutdaCard12 c2) {
        this.name = name;
        this.c1 = c1;
        this.c2 = c2;
    }

    public String toString() {
        return "[" + name + "]" + c1.toString() + "," + c2.toString();
    }
} // class Player

class SutdaCard12 {
    int num;
    boolean isKwang;

    SutdaCard12() {
        this(1, true);
    }

    SutdaCard12(int num, boolean isKwang) {
        this.num = num;
        this.isKwang = isKwang;
    }

    public String toString() {
        return num + (isKwang ? "K" : "");
    }
}

<실행결과>
[타짜]2,9 1001
[고수]10,4 2020
  • 모범 풀이 (getPoint(Player p)):
int getPoint(Player p) { 
    
    if(p==null) return 0;

        SutdaCard c1 = p.c1; SutdaCard c2 = p.c2;

        Integer result = 0; // Integer result = new Integer(0);

//	1. 카드 두 장이 모두 광이면, jokbo에서 키를 "KK"로 해서 점수를 조회한다.
        if(c1.isKwang && c2.isKwang) {
        result = (Integer)jokbo.get("KK");
        } else {
//	2. 두 카드의 숫자(num)로 jokbo에서 등급을 조회한다.
        result = (Integer)jokbo.get(""+c1.num+c2.num);

//	3. 해당하는 등급이 없으면, 아래의 공식으로 점수를 계산한다.
//	(c1.num + c2.num) % 10 + 1000
        if(result==null) {
        result = new Integer((c1.num + c2.num) % 10 + 1000);
        }
        }

//	4. Player의 점수(point)에 계산한 값을 저장한다.
        p.point = result.intValue();

        return result.intValue();
        }

❓11-14. ArrayList에 클래스 객체 저장하기

  • 문제:
    [11-14] 다음은 성적처리 프로그램의 일부이다.
    Scanner클래스를 이용해서 화면으로부터 데이터를 입력하고 보여주는 기능을 완성하시오.
import java.util.*;

class Exercise11_14 {
    static ArrayList record = new ArrayList(); // 성적데이터를 저장할 공간
    static Scanner s = new Scanner(System.in);

    public static void main(String args[]) {
        while (true) {
            switch (displayMenu()) {
                case 1:
                    inputRecord();
                    break;
                case 2:
                    displayRecord();
                    break;
                case 3:
                    System.out.println("프로그램을 종료합니다.");
                    System.exit(0);
            }
        } // while(true)
    }

    // menu를 보여주는  메서드
    static int displayMenu() {
        System.out.println("**************************************************");

        System.out.println("*             성적 관리 프로그램                  *");

        System.out.println("**************************************************");
        System.out.println();
        System.out.println(" 1. 학생성적 입력하기 ");
        System.out.println();
        System.out.println(" 2. 학생성적 보기");
        System.out.println();
        System.out.println(" 3. 프로그램 종료 ");
        System.out.println();
        System.out.print("원하는 메뉴를 선택하세요.(1~3) : ");
        int menu = 0;
/*
(1)	아래의 로직에 맞게 코드를 작성하시오.
1.	화면으로부터 메뉴를 입력받는다. 메뉴의 값은 1~3사이의 값이어야 한다.
2.	1~3사이의 값을 입력받지 않으면, 메뉴의 선택이 잘못되었음을 알려주고 다시 입력받는다.(유효한 값을 입력받을 때까지 반복해서 입력받는다.)
*/

        return menu;
    } // public static int displayMenu(){

    // 데이터를 입력받는 메서드
    static void inputRecord() {
        System.out.println("1. 학생성적 입력하기");
        System.out.println("이름,반,번호,국어성적,영어성적,수학성적'의 순서로 공백없이 입력하세요.");
        System.out.println("입력을 마치려면 q를 입력하세요. 메인화면으로 돌아갑니다.");

        while (true) {
            System.out.print(">>");

/*
(2)	아래의 로직에 맞게 코드를 작성하시오.
1.	Scanner를 이용해서 화면으로 부터 데이터를 입력받는다.(','를 구분자로)
2.	입력받은 값이 q 또는 Q이면 메서드를 종료하고,
그렇지 않으면 입력받은 값으로 Student인스턴스를 생성하고 record에 추가한다.
3.	입력받은 데이터에서 예외가 발생하면, "입력오류입니다."를 보여주고 다시 입력받는다.
4.	q 또는 Q가 입력될 때까지 2~3의 작업을 반복한다.
*/
        } // end of while
    } // public static void inputRecord() {

    // 데이터 목록을 보여주는 메서드
    static void displayRecord() {
        int koreanTotal = 0;
        int englishTotal = 0;
        int mathTotal = 0;
        int total = 0;
        int length = record.size();
        if (length > 0) {
            System.out.println();
            System.out.println("이름 반 번호 국어 영어 수학 총점 평균 전교등수 반등수");
            System.out.println("====================================================");
            for (int i = 0; i < length; i++) {
                Student9 student = (Student9) record.get(i);
                System.out.println(student);
                koreanTotal += student.kor;
                mathTotal += student.math;
                englishTotal += student.eng;
                total += student.total;
            }

            System.out.println("====================================================");
            System.out.println("총점: " + koreanTotal + " " + englishTotal
                    + " " + mathTotal + " " + total);
            System.out.println();
        } else {
            System.out.println("====================================================");
            System.out.println(" 데이터가 없습니다.");
            System.out.println("====================================================");
        }
    } // static void displayRecord() {
}

class Student implements Comparable {
    String name;
    int ban;
    int no;
    int kor;
    int eng;
    int math;

    int total;
    int schoolRank;
    int classRank; // 반등수

    Student(String name, int ban, int no, int kor, int eng, int math) {
        this.name = name;
        this.ban = ban;
        this.no = no;
        this.kor = kor;
        this.eng = eng;
        this.math = math;

        total = kor + eng + math;
    }

    int getTotal() {
        return total;
    }

    float getAverage() {
        return (int) ((getTotal() / 3f) * 10 + 0.5) / 10f;
    }

    public int compareTo(Object o) {
        if (o instanceof Student9) {
            Student9 tmp = (Student9) o;

            return tmp.total - this.total;
        } else {
            return -1;
        }
    }

    public String toString() {
        return name
                + "," + ban
                + "," + no
                + "," + kor
                + "," + eng
                + "," + math
                + "," + getTotal()
                + "," + getAverage()
                + "," + schoolRank
                + "," + classRank
                ;
    }
} // class Student


/*
<실행결과>
**************************************************
*	성적 관리 프로그램	*
**************************************************

1.	학생성적 입력하기

2.	학생성적 보기

3.	프로그램 종료

원하는 메뉴를 선택하세요.(1~3) : 5
메뉴를 잘못 선택하셨습니다. 다시 입력해주세요. 원하는 메뉴를 선택하세요.(1~3) : 2
====================================================
데이터가 없습니다.
====================================================
**************************************************
*	성적 관리 프로그램	*
**************************************************

1.	학생성적 입력하기

2.	학생성적 보기

3.	프로그램 종료

원하는 메뉴를 선택하세요.(1~3) : 1
1. 학생성적 입력하기
이름,반,번호,국어성적,영어성적,수학성적'의 순서로 공백없이 입력하세요. 입력을 마치려면 q를 입력하세요. 메인화면으로 돌아갑니다.
>>
입력오류입니다. 이름, 반, 번호, 국어성적, 영어성적, 수학성적'의 순서로 입력하세 요.
>>자바짱,1,1,100,100,100
잘입력되었습니다. 입력을 마치려면 q를 입력하세요.
>>김자바,1,2,80,80,80
잘입력되었습니다. 입력을 마치려면 q를 입력하세요.
>>q
**************************************************
*	성적 관리 프로그램	*
**************************************************

1.	학생성적 입력하기

2.	학생성적 보기

3.	프로그램 종료

원하는 메뉴를 선택하세요.(1~3) : 2

이름 반 번호 국어 영어 수학 총점 평균 전교등수 반등수
====================================================
자바짱,1,1,100,100,100,300,100.0,0,0
김자바,1,2,80,80,80,240,80.0,0,0
====================================================
총점: 180 180 180 540

**************************************************
*	성적 관리 프로그램	*
**************************************************

1.	학생성적 입력하기

2.	학생성적 보기

3.	프로그램 종료

원하는 메뉴를 선택하세요.(1~3) : 3
프로그램을 종료합니다.
 */
  • 문제 풀이 static int displayMenu()
//🔥🔥 menu를 보여주는  메서드
    static int displayMenu() {
        System.out.println("**************************************************");

        System.out.println("*             성적 관리 프로그램                  *");

        System.out.println("**************************************************");
        System.out.println();
        System.out.println(" 1. 학생성적 입력하기 ");
        System.out.println();
        System.out.println(" 2. 학생성적 보기");
        System.out.println();
        System.out.println(" 3. 프로그램 종료 ");
        System.out.println();
        System.out.print("원하는 메뉴를 선택하세요.(1~3) : ");
        int menu = 0;
    /*
    🔥🔥(1)	아래의 로직에 맞게 코드를 작성하시오.
    1.	화면으로부터 메뉴를 입력받는다. 메뉴의 값은 1~3사이의 값이어야 한다.
    2.	1~3사이의 값을 입력받지 않으면, 메뉴의 선택이 잘못되었음을 알려주고 다시 입력받는다.(유효한 값을 입력받을 때까지 반복해서 입력받는다.)
    */
        menu = s.nextInt();
        if(!(menu>=1&&menu<=3)) {
            System.out.println("유효한 값이 아닙니다. 1부터 3 사이의 숫자를 입력해주세요.");
            return 0;
        }

        return menu;
    } // public static int displayMenu(){
    
<실행결과>
**************************************************
*             성적 관리 프로그램                  *
**************************************************

 1. 학생성적 입력하기 

 2. 학생성적 보기

 3. 프로그램 종료 

원하는 메뉴를 선택하세요.(1~3) : 5
유효한 값이 아닙니다. 1부터 3 사이의 숫자를 입력해주세요.
**************************************************
*             성적 관리 프로그램                  *
**************************************************

 1. 학생성적 입력하기 

 2. 학생성적 보기

 3. 프로그램 종료 

원하는 메뉴를 선택하세요.(1~3) : 
  • 문제풀이 static void inputRecord()
//🔥🔥 데이터를 입력받는 메서드
    static void inputRecord() {
        System.out.println("1. 학생성적 입력하기");
        System.out.println("이름,반,번호,국어성적,영어성적,수학성적'의 순서로 공백없이 입력하세요.");
        System.out.println("입력을 마치려면 q를 입력하세요. 메인화면으로 돌아갑니다.");

        while (true) {
            System.out.print(">>");

        /*
        🔥🔥(2)	아래의 로직에 맞게 코드를 작성하시오.
        1.	Scanner를 이용해서 화면으로 부터 데이터를 입력받는다.(','를 구분자로)
        2.	입력받은 값이 q 또는 Q이면 메서드를 종료하고,
        그렇지 않으면 입력받은 값으로 Student인스턴스를 생성하고 record에 추가한다.
        3.	입력받은 데이터에서 예외가 발생하면, "입력오류입니다."를 보여주고 다시 입력받는다.
        4.	q 또는 Q가 입력될 때까지 2~3의 작업을 반복한다.
        */
        Student14 student = null;
        String[] inputs = new String[6]; //🔥이름, 반, 번호, 국, 영, 수를 나눠 저장할 String 배열 생성
        inputs = s.nextLine().replaceAll(" ", "").split(","); //어차피 trim으로는 양 옆 공백만 제거되니 공백을 받지 않아야 한다. 혹은 replaceAll(" ", "")로 교체하던지
        if(inputs[0].equalsIgnoreCase("q")) return;
        else { // q를 입력한게 아니라면
            try {
                String name = inputs[0];
                int ban = Integer.valueOf(inputs[1]);
                int num = Integer.valueOf(inputs[2]);
                int kor = Integer.valueOf(inputs[3]);
                int eng = Integer.valueOf(inputs[4]);
                int math = Integer.valueOf(inputs[5]);
                student = new Student14(name, ban, num, kor, eng, math);
                record.add(student);
            } catch(Exception e) {
                System.out.println("올바른 값을 입력하지 않았습니다. 다시 입력해주세요.");
                //🔥 다음 블록을 실행하지 않기 때문에 while문으로 다시 돌아간다.
            }
        }

        } // end of while
    } // public static void inputRecord() {
    
<실행결과>
**************************************************
*             성적 관리 프로그램                  *
**************************************************

 1. 학생성적 입력하기 

 2. 학생성적 보기

 3. 프로그램 종료 

원하는 메뉴를 선택하세요.(1~3) : 1
1. 학생성적 입력하기
이름,,번호,국어성적,영어성적,수학성적'의 순서로 공백없이 입력하세요.
입력을 마치려면 q를 입력하세요. 메인화면으로 돌아갑니다.
>>올바른 값을 입력하지 않았습니다. 다시 입력해주세요. //❌ 문제1 아직 값을 입력하지 않았는데 처음부터 해당 문구가 출력
>>김자바, 1, 1, 100, 50, 60
>>경자바, 1, 3, 50, 70, 90
>>박자바, 1, 2, 20, 60, 100
>>현자바, 2, 1, 60, 68, 100, 60, 50 //❌ 문제2 성적을 여러 개 입력해도 체크하지 못함.
>>q
**************************************************
*             성적 관리 프로그램                  *
**************************************************

 1. 학생성적 입력하기 

 2. 학생성적 보기

 3. 프로그램 종료 

원하는 메뉴를 선택하세요.(1~3) : 2

이름 반 번호 국어 영어 수학 총점 평균 전교등수 반등수
====================================================
김자바,1,1,100,50,60,210,70.0,0,0
경자바,1,3,50,70,90,210,70.0,0,0
박자바,1,2,20,60,100,180,60.0,0,0
현자바,2,1,60,68,100,228,76.0,0,0
====================================================
총점: 230 248 350 828

**************************************************
*             성적 관리 프로그램                  *
**************************************************

 1. 학생성적 입력하기 

 2. 학생성적 보기

 3. 프로그램 종료 

원하는 메뉴를 선택하세요.(1~3) : 
  • 내 풀이에서 가장 큰 문제는 다음과 같다.
<내 풀이>
static void inputRecord() {
        System.out.println("1. 학생성적 입력하기");
        System.out.println("이름,반,번호,국어성적,영어성적,수학성적'의 순서로 공백없이 입력하세요.");
        System.out.println("입력을 마치려면 q를 입력하세요. 메인화면으로 돌아갑니다.");

        while (true) {
            System.out.print(">>");

        /*
        🔥🔥(2)	아래의 로직에 맞게 코드를 작성하시오.
        1.	Scanner를 이용해서 화면으로 부터 데이터를 입력받는다.(','를 구분자로)
        2.	입력받은 값이 q 또는 Q이면 메서드를 종료하고,
        그렇지 않으면 입력받은 값으로 Student인스턴스를 생성하고 record에 추가한다.
        3.	입력받은 데이터에서 예외가 발생하면, "입력오류입니다."를 보여주고 다시 입력받는다.
        4.	q 또는 Q가 입력될 때까지 2~3의 작업을 반복한다.
        */
        Student14 student = null;
        String[] inputs = new String[6]; //🔥이름, 반, 번호, 국, 영, 수를 나눠 저장할 String 배열 생성
        inputs = s.nextLine().replaceAll(" ", "").split(","); //어차피 trim으로는 양 옆 공백만 제거되니 공백을 받지 않아야 한다. 혹은 replaceAll(" ", "")로 교체하던지
        if(inputs[0].equalsIgnoreCase("q")) return;
        else { // q를 입력한게 아니라면
            try {
                String name = inputs[0];
                int ban = Integer.valueOf(inputs[1]);
                int num = Integer.valueOf(inputs[2]);
                int kor = Integer.valueOf(inputs[3]);
                int eng = Integer.valueOf(inputs[4]);
                int math = Integer.valueOf(inputs[5]);
                student = new Student14(name, ban, num, kor, eng, math);
                record.add(student);
            } catch(Exception e) {
                System.out.println("올바른 값을 입력하지 않았습니다. 다시 입력해주세요.");
                //🔥 다음 블록을 실행하지 않기 때문에 while문으로 다시 돌아간다.
            }
        }

        } // end of while
    } // public static void inputRecord() {

<콘솔 창>
원하는 메뉴를 선택하세요.(1~3) : 1
1. 학생성적 입력하기
이름,,번호,국어성적,영어성적,수학성적'의 순서로 공백없이 입력하세요.
입력을 마치려면 q를 입력하세요. 메인화면으로 돌아갑니다.
//❌ 문제1 아직 값을 입력하지 않았는데 처음부터 해당 문구가 출력
>>올바른 값을 입력하지 않았습니다. 다시 입력해주세요. 
>>김자바, 1, 1, 100, 50, 60
>>경자바, 1, 3, 50, 70, 90
>>박자바, 1, 2, 20, 60, 100
//❌ 문제2 성적을 그 이상 여러 개 입력해도 체크하지 못함.
>>현자바, 2, 1, 60, 68, 100, 60, 50 

❓ 의문점

  • 문제 1 관련 내 생각: inputs = s.nextLine().replaceAll(" ", "").split(","); 여기서 걸려야 하는거 아닌가? 왜 입력값을 받지 않았는데 그 이후로 진행이 되지? 혹시 replaceAll, split 때문에 값을 받지 않아도 받은 것처럼 되어 이후로 넘어가는건가? 그럼 값을 받은 input이랑 inputs로 나눠볼까?
 Student14 student = null;
        String[] inputs = new String[6]; //🔥이름, 반, 번호, 국, 영, 수를 나눠 저장할 String 배열 생성
        String input = s.nextLine();
        inputs = input.replaceAll(" ", "").split(","); //🔥공백 제거한 String을 split(",")로 나눠 inputs에 저장
        if(inputs[0].equalsIgnoreCase("q")) return;
        else { // q를 입력한게 아니라면

❌ 이렇게 해보아도 똑같음.

String input = s.nextLine();
String[] inputs = new String[6]; //🔥이름, 반, 번호, 국, 영, 수를 나눠 저장할 String 배열 생성
❌ 이렇게 순서를 바꿔보아도 똑같음.
  • ➡️문제를 깨달았다. sinputRecord()로 진입하기 위한 1값이 들어있었기 때문에 입력값을 다시 받지 않고 조건문 안으로 진입한 것이었다. 새로운 Scanner 객체를 생성하여 값을 다시 받아주면 된다.
    ✔️✔️✔️쓰레기값이 있는지 항상 체크해야한다!
Student14 student = null;
        Scanner s2 = new Scanner(System.in);    //🔥🔥🔥 쓰레기값을 갖고 있는 s가 아닌, s2를 추가!
        String input = s2.nextLine();
        String[] inputs = new String[6]; //🔥이름, 반, 번호, 국, 영, 수를 나눠 저장할 String 배열 생성
        inputs = input.replaceAll(" ", "").split(",", 6); //🔥공백 제거한 String을 split(",")로 나눠 inputs에 저장
        
<실행결과>
원하는 메뉴를 선택하세요.(1~3) : 1
1. 학생성적 입력하기
이름,,번호,국어성적,영어성적,수학성적'의 순서로 공백없이 입력하세요.
입력을 마치려면 q를 입력하세요. 메인화면으로 돌아갑니다.
  • 문제 2 관련 내 생각:
    String[] split(string regex, int limit)메서드를 사용하면 6개 이상의 과목을 입력했을 때에도 inputs의 요소가 6개로 한정되기 때문에 inputs[5]에 int로 형변환 할 수 없는 String 값이 쌓이게 된다. 이렇게 된다면 String 타입을 int로 파싱하지 못하기 때문에 NumberFormatException가 발생하게 된다. 이 예외처리를 별도로 추가해주면 될 것이다. ➡️ 성공적으로 해결!
	...
 String input = s.nextLine();
        String[] inputs = new String[6]; //🔥이름, 반, 번호, 국, 영, 수를 나눠 저장할 String 배열 생성
        //⭐ limit가 추가되어 6개만 쌓이게 됨.
        inputs = input.replaceAll(" ", "").split(",", 6); //🔥공백 제거한 String을 split(",")로 나눠 inputs에 저장
        ...
        
       } catch (NumberFormatException ne) {
                System.out.println("이름, 반, 번호, 국, 영, 수 6개의 과목을 입력했는지 확인해주세요.");
            } 
<실행결과>
**************************************************
*             성적 관리 프로그램                  *
**************************************************

 1. 학생성적 입력하기 

 2. 학생성적 보기

 3. 프로그램 종료 

원하는 메뉴를 선택하세요.(1~3) : 1
1. 학생성적 입력하기
이름,,번호,국어성적,영어성적,수학성적'의 순서로 공백없이 입력하세요.
입력을 마치려면 q를 입력하세요. 메인화면으로 돌아갑니다.
>>올바른 값을 입력하지 않았습니다. 다시 입력해주세요.
>>김자바, 1, 3, 12, 100, 50, 60
이름,, 번호,,,6개의 과목을 입력했는지 확인해주세요.
>>김자바, 1, 3, 100, 50, 60
>>이자바, 1, 4, 100, 60, 70
>>박자바, 1, 5, 50, 70, 90
>>ㅂ
올바른 값을 입력하지 않았습니다. 다시 입력해주세요.
>>q
**************************************************
*             성적 관리 프로그램                  *
**************************************************

 1. 학생성적 입력하기 

 2. 학생성적 보기

 3. 프로그램 종료 

원하는 메뉴를 선택하세요.(1~3) : 2

이름 반 번호 국어 영어 수학 총점 평균 전교등수 반등수
====================================================
김자바,1,3,100,50,60,210,70.0,0,0
이자바,1,4,100,60,70,230,76.7,0,0
박자바,1,5,50,70,90,210,70.0,0,0
====================================================
총점: 250 180 220 650

모범 답안

  • static int displayMenu()
static int displayMenu(){
    System.out.println("**************************************************");
        System.out.println("*

        성적 관리 프로그램

        *");

        System.out.println("**************************************************");
        System.out.println();
        System.out.println(" 1. 학생성적 입력하기 ");
        System.out.println();
        System.out.println(" 2. 학생성적 보기");
        System.out.println();
        System.out.println(" 3. 프로그램 종료 ");
        System.out.println();
        System.out.print("원하는 메뉴를 선택하세요.(1~3) : ");
    int menu=0;
        
    //⭐do~while문을 사용해 받은 int값 유효성 검사하기
    do{
        try{
        menu=Integer.parseInt(s.nextLine().trim());

		//⭐menu값이 정상적인 값이면 무한 반복문 빠져나감.
        if(1<=menu&&menu<=3){
            break;
        }else{
            throw new Exception();
        }
        }catch(Exception e){
            System.out.println("메뉴를 잘못 선택하셨습니다. 다시 입력해주세요 .");
            System.out.print("원하는 메뉴를 선택하세요.(1~3) : ");
        }
    }while(true);

    return menu;
} // public static int displayMenu(){
  • do~while문을 사용하여 int 유효성체크를 하였다.

  • static void inputRecord

// 데이터를 입력받는 메서드
    static void inputRecord() {
        System.out.println("1. 학생성적 입력하기");
        System.out.println("이름,반,번호,국어성적,영어성적,수학성적'의 순서로 공백없이 입력하세요.");
        System.out.println("입력을 마치려면 q를 입력하세요. 메인화면으로 돌아갑니다.");

        while (true) {
            System.out.print(">>");

            try {
                String input = s.nextLine().trim();

                if (!input.equalsIgnoreCase("q")) {
                //⭐ Scanner를 이용해서 화면으로 부터 데이터를 입력받는다.(','를 구분자로)
                //⭐ 새로운 스캐너 객체 s2를 생성해서 
                    Scanner s2 = new Scanner(input).useDelimiter(",");
                // 입력받은 값으로 Student인스턴스를 생성하고 record에 추가한다.
                //⭐ next()와 nextInt()라는 메서드를 이용하여 추가
                    record.add(new Student(s2.next(), s2.nextInt(), s2.nextInt(), s2.nextInt(), s2.nextInt(), s2.nextInt()));
                    System.out.println("잘입력되었습니다. 입력을 마치려면 q를 입력하세요 .");
                } else {
                // 입력받은 값이 q 또는 Q이면 메서드를 종료한다.
                    return;
                }
            } catch (Exception e) {
            // 입력받은 데이터에서 예외가 발생하면, "입력오류입니다."를 보여주고 다시 입력받는다.
                System.out.println("입력오류입니다. 이름, 반, 번호, 국어성적 , 영어성적 , 수학성적 '의 순서로 입력하세요.");
            }
        } // end of while
    } // public static void inputRecord()
  • 의문점 문제1이 해결된 것같다. 여기서는
    Scanner s2 = new Scanner(input).useDelimiter(",");를 사용했는데, 스캐너 객체를 아예 새로 만들어줬다. 내 문제1에서는 static Scanner 객체s를 그대로 이용했기 때문에 해당 메서드에 진입하자마자 쓰레기값이 걸린 것이다.

Scanner 클래스는 기본적으로 whitespace (공백, 탭, 개행문자 등)를 구분자로 사용하여 입력을 처리합니다. 그러나 useDelimiter() 메서드를 사용하여 Scanner 클래스가 사용할 구분자를 사용자 지정할 수 있습니다.

예를 들어, "name,age,gender"과 같이 콤마로 구분된 문자열이 있다면, useDelimiter(",")를 사용하여 콤마를 구분자로 설정할 수 있습니다. 그 후에 next() 메서드를 호출하면 "name"을 반환하고, 다시 next() 메서드를 호출하면 "age"를 반환하고, 이어서 호출하면 "gender"를 반환합니다.

따라서, "Scanner s2 = new Scanner(input).useDelimiter(",");" 코드는 input 문자열을 Scanner 클래스로 만들고, 구분자로 콤마를 사용하도록 설정한 Scanner 클래스의 인스턴스 s2를 생성하는 코드입니다. 이렇게 설정하면 s2의 next() 메서드를 호출할 때마다 콤마로 구분된 문자열을 하나씩 반환합니다.

profile
9에서 0으로, 백엔드 개발블로그

0개의 댓글