본문 바로가기

Java의 정석 : 3rd Edition

[Java의 정석 - 연습문제] Chapter09. java.lang패키지와 유용한 클래스

Java의 정석 : 3rd Edition, 2016을 개인 학습용으로 정리한 내용입니다.
"ppt 파일 자료, 연습문제"를 학습 용도로 배포할 수 있음을 고지하셨습니다.
저자님께 감사드립니다.

 

 

 

 

[9-1] 다음과 같은 실행결과를 얻도록 SutdaCard클래스의 equals()를 멤버변수인 num, isKwang의 값을 비교하도록 오버라이딩하고 테스트 하시오.

class Exercise9_1 {
    public static void main(String[] args) {
        SutdaCard c1 = new SutdaCard(3, true);
        SutdaCard c2 = new SutdaCard(3, true);
        
        System.out.println("c1 = " + c1);
        System.out.println("c2 = " + c2);
        System.out.println("c1.equals(c2) : " + c1.equals(c2));
    }
}

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) {
        /*
            (1) 매개변수로 넘겨진 객체의 num, isKwang과
                멤버변수 num, isKwang을 비교하도록 오버라이딩 하시오.
        */
    }

    public String toString() {
        return num + (isKwang ? "K" : "");
    }
}
// c1 = 3K
// c2 = 3K
// c1.equals(c2) : true

 

나의 답 :

A객체와 B객체의 멤버변수가 같은 값을 갖는지 비교하려면 Object.equals()를 오버라이딩해야 한다.

Object(클래스)에는 멤버변수 num, isKwang가 정의되어 있지 않으므로 SutdaCard로의 형변환이 필요하다.

c2의 멤버변수(card.num, card.isKwang)와 c1의 멤버변수(num, isKwang)를 등가 비교 연산자(=)로 비교한다.

*Object.equals() : A객체와 B객체가 같은 객체인지를 주소값으로 판단한다.

// (1) 매개변수로 넘겨진 객체의 num, isKwang과 멤버변수 num, isKwang을 비교하도록 오버라이딩 하시오.
public boolean equals(Object obj) {
    if (obj instanceof SutdaCard) {
        SutdaCard card = (SutdaCard) obj;
        return (num == card.num && isKwang == card.isKwang);
    }
    return false;
}

 

 

 

 

[9-2] 다음과 같은 실행결과를 얻도록 Point3D클래스의 equals()를 멤버변수인 x, y, z의 값을 비교하도록 오버라이딩하고, toString()은 실행결과를 참고해서 적절히 오버라이딩하시오.

class Exercise9_2 {
    public static void main(String[] args) {
        Point3D p1 = new Point3D(1, 2, 3);
        Point3D p2 = new Point3D(1, 2, 3);

        System.out.println(p1);
        System.out.println(p2);
        System.out.println("p1 == p2 : " + (p1 == p2));
        System.out.println("p1.equals(p2) : " + (p1.equals(p2)));
    }
}

class Point3D {
    int x, y, z;

    Point3D(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    Point3D() {
        this(0, 0, 0);
    }

    public boolean equals(Object obj) {
        /*
            (1) 인스턴스변수 x, y, z를 비교하도록 오버라이딩하시오.
        */
    }
    
    public String toString() {
        /*
            (2) 인스턴스변수 x, y, z의 내용을 출력하도록 오버라이딩하시오.
        */
    }
}
// [1,2,3]
// [1,2,3]
// p1 == p2 : false
// p1.equals(p2) : true

 

나의 답 :

(1) 문제9-1와 동일. A객체와 B객체의 멤버변수가 같은 값을 갖는지 비교하려면 Object.equals()를 오버라이딩해야 한다.

(2) Object.toString()는 객체의 정보를 출력하므로 인스턴스 변수의 내용을 출력하려면 오버라이딩해야 한다.

     실행결과를 바탕으로 인스턴스 변수의 내용을 출력해야 하므로 x, y, z에 "[", ",", "]"를 결합한 문자열을 반환하도록 한다.

*Object.toString() : 객체의 정보를 '클래스명@hashCode(16진수)' 형태의 문자열로 반환한다.

// (1) 인스턴스 변수 x, y, z를 비교하도록 오버라이딩하시오.
public boolean equals(Object obj) {
    if (obj instanceof Point3D) {
        Point3D p3 = (Point3D) obj;
        return (x == p3.x && y == p3.y && z == p3.z);
    }
    return false;
}

// (2) 인스턴스 변수 x, y, z의 내용을 출력하도록 오버라이딩하시오.
public String toString() {
    return "[" + x + "," + y + "," + z + "]";
}

 

 

 

 

[9-3] 다음과 같은 실행결과가 나오도록 코드를 완성하시오.

class Exercise9_3 {
    public static void main(String[] args) {
        String fullPath = "c:\\jdk1.8\\work\\PathSeparateTest.java";
        String path = "";
        String fileName = "";

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

        System.out.println("fullPath:" + fullPath);
        System.out.println("path:" + path);
        System.out.println("fileName:" + fileName);
    }
}
// fullPath:c:\jdk1.8\work\PathSeparateTest.java
// path:c:\jdk1.8\work
// fileName:PathSeparateTest.java

 

나의 답 :

1) 전체경로(fullPath) 중 마지막 경로부분인 "\\PathSeparateTest.java" 부분만 활용하면 되므로,

    처음(왼→오)부터 검색하는 indexOf() 보다는 끝(오→왼)에서부터 검색하는 lastIndexOf()를 사용하는 게 효율적이다.

 1-1) path : 마지막 경로부분을 제외해야 한다.

 1-2) fileName : 마지막 경로부분 이후의 문자만 뽑아내야 한다.

2) lastIndexOf()는 indexOf()와 마찬가지로 일치하는 문자열을 찾지 못하면 -1를, 찾으면 해당 위치를 반환한다.

 2-1) lastIndexOf()의 결과가 -1이 아니라면(찾았다면)

 2-2) 처음부터 해당 위치까지를 substring()으로 잘라 path에 저장한다. (끝(\\)은 포함되지 않으므로 ~work까지 저장된다)

 2-3) \\의 부분을 찾았으므로 +1을 더해 P부터 문자열의 끝까지를 fileName에 저장한다. (PathSeparateTest.java)

※ IndexOf(), lastIndexOf의 범위 : from <= x < to(to로 지정한 값은 포함되지 않음)

※ pos의 값이 음수(-1)일 때, substring()을 호출하면 예외가 발생하므로 주의해야 한다.

int pos = fullPath.lastIndexOf("\\");  // 1

if (pos != -1) {                       // 2
    path = fullPath.substring(0, pos);
    fileName = fullPath.substring(pos + 1);
}

 

 

 

 

[9-4] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

메서드명 : printGraph

기       능 : 주어진 배열에 담긴 값만큼 주어진 문자를 가로로 출력한 후, 값을 출력한다.

반환타입 : 없음

매개변수 : int[] dataArr - 출력할 그래프의 데이터

                 char ch - 그래프로 출력할 문자.

class Exercise9_4 {
    static void printGraph(int[] dataArr, char ch) {
        /*
            (1) printGraph메서드를 작성하시오.
        */
    }

    public static void main(String[] args) {
        printGraph(new int[]{3, 7, 1, 4}, '*');
    }
}
// ***3
// *******7
// *1
// ****4

 

나의 답 :

count 변수를 바깥 반복문에 선언한 후 내부 반복문으로 dataArr[i]만큼 '*'을 출력한다.

내부 반복문이 도는 횟수를 count에 저장한 후 반복을 종료하면 count에 저장된 값을 출력 후 다음 반복을 진행한다.

해설을 보고 나니 변수를 따로 선언할 필요 없이 dataArr[i]에 저장된 값을 출력하면 되는 거였다.

// (1) printGraph메서드를 작성하시오.
static void printGraph(int[] dataArr, char ch) {
    for (int i = 0; i < dataArr.length; i++) {
        int count = 0;

        for (int j = 0; j < dataArr[i]; j++) {
            System.out.print(ch);
            count++;
        }
        System.out.printf("%d%n", count);
    }
}

 

해설 : 반복문으로 배열의 저장된 숫자만큼 별을 출력하면 된다.

// (1) printGraph메서드를 작성하시오.
static void printGraph(int[] dataArr, char ch) {
    for (int i = 0; i < dataArr.length; i++) {
        for (int j = 0; j < dataArr[i]; j++) {
            System.out.print(ch);
        }
        System.out.println(dataArr[i]);
    }
}

 

 

 

 

[9-5] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

[Hint] String클래스의 indexOf(String str, int fromIndex)를 사용할 것

메서드명 : count

기       능 : 주어진 문자열(src)에 찾으려는 문자열(target)이 몇 번 나오는지 세어서 반환한다.

반환타입 : int

매개변수 : String src, String target

class Exercise9_5 {
    public static int count(String src, String target) {
        int count = 0;  // 찾은 횟수
        int pos = 0;    // 찾기 시작할 위치

        /*
           (1) 반복문을 사용해서 아래의 과정을 반복한다.
            1. src에서 target을 pos의 위치부터 찾는다.
            2. 찾으면 count의 값을 1 증가 시키고, pos의 값을 target.length만큼 증가시킨다.
            3. indexOf의 결과가 -1이면 반복문을 빠져나가서 count를 반환한다.
        */
    }
    
    public static void main(String[] args) {
        System.out.println(count("12345AB12AB345AB", "AB"));
        System.out.println(count("12345", "AB"));
    }
}
// 3
// 0

 

나의 답 :

[9-5] 풀이과정
[9-5] 풀이과정

1) indexOf(대상, 찾기 시작할 위치)

2) pos += target.length(); : pos(찾기 시작할 위치)에 target의 길이를 더해 찾은 문자열의 위치 이후로 변경한다.

    변경하지 않으면 계속 처음부터 찾기 시작하므로 무한 반복문에 빠지게 된다.(프로그램 종료되지 않음)

    찾은 위치를 더하면 되지 않을까? 싶어 코드를 pos += pos로 변경해봤다.

    - 처음 찾을 때 5가 되므로 다음 반복때는 5를 더한 10번째 위치인 B부터 검색되어 두 번째 AB를 찾지 못했다.

    - 찾은 문자열 이후로만 pos를 변경하면 되겠다!라는 착각때문에 누적으로 인해 검색을 건너뛰게 되는 문제가 발생됐다.

    - 찾은 문자열의 길이만큼만 더해야 그 이후에 위치한 다음 문자열을 찾을 수 있다.

 2-1) target.length() 변하지 않는 값인데 반복문에서 사용하면 매 반복마다 메서드가 호출되므로 비효율적이다.

    상수(TARGET_LGTH)로 정의하면 상수에 담을 때 한 번만 호출되므로 불필요한 메서드 호출을 줄일 수 있다.

    *LGTH는 length의 축약어이다.

3) 찾지 못했으면(-1) 반복문을 빠져나가야 하므로 break문을 사용한다.

/* XXX 잘못 생각한 오답 XXX
if (pos != -1) {
    count++;
    System.out.println("pos : " + pos);
    pos += pos;
}
*/
// pos : 5
// pos : 14

 

/*
    (1) 반복문을 사용해서 아래의 과정을 반복한다.
     1. src에서 target을 pos의 위치부터 찾는다.
     2. 찾으면 count의 값을 1 증가 시키고, pos의 값을 target.length만큼 증가시킨다.
     3. indexOf의 결과가 -1이면 반복문을 빠져나가서 count를 반환한다.
*/
final int TARGET_LGTH = target.length();

while(true) {
    pos = src.indexOf(target, pos);  // 1

    if (pos != -1) {
        count++;
        pos += TARGET_LGTH;          // 2
    } else {                         // 3
        break;
    }
}
return count;
// 반복문을 간결하게 작성한 답안
final int TARGET_LGTH = target.length();

while((pos = src.indexOf(target, pos)) != -1) {
    count++;
    pos += TARGET_LGTH;
}

 

 

 

 

[9-6] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

메서드명 : fillZero

기       능 : 주어진 문자열(숫자)로 주어진 길이의 문자열로 만들고, 왼쪽 빈 공간은 '0'으로 채운다.

                 만일 주어진 문자열이 null이거나 문자열의 길이가 length의 값과 같으면 그대로 반환한다.

                 만일 주어진 length의 값이 0보다 같거나 작은 값이면, 빈 문자열("")을 반환한다.

반환타입 : String

매개변수 : String src - 변환할 문자열

                  int length - 변환한 문자열의 길이

class Exercise9_6 {
    public static String fillZero(String src, int length) {
        /* (1) fillZero메서드를 작성하시오.
            1. src가 널이거나 src.length()가 length와 같으면 src를 그대로 반환한다.
            2. length의 값이 0보다 같거나 작으면 빈 문자열("")을 반환한다.
            3. src의 길이가 length의 값보다 크면 src를 length만큼 잘라서 반환한다.
            4. 길이가 length인 char배열을 생성한다.
            5. 4에서 생성한 char배열을 '0'으로 채운다.
            6. src에서 문자배열을 뽑아내서 4에서 생성한 배열에 복사한다.
            7. 4에서 생성한 배열로 String을 생성해서 반환한다.
        */
    }

    public static void main(String[] args) {
        String src = "12345";
        System.out.println(fillZero(src, 10));
        System.out.println(fillZero(src, -1));
        System.out.println(fillZero(src, 3));
    }
}
// 0000012345
//
// 123

 

나의 답 :

src.length() : 사용할 때마다 호출되므로 불필요한 메서드 호출을 막기 위해 상수(SRC_LGTH)로 정의하여 사용한다.

3) SRC_LGTH > length : SRC_LGTH(5), length(3)이라면 substring()로 처음부터(0) length의 길이(3)만큼 자른다.

5) Arrays.fill(result, '0'); : 배열을 같은 내용으로 초기화할 때 유용하다.

 5-1) 매번 배열을 반복문으로 초기화 했는데, 같은 데이터만 넣으면 되므로 간단한 방법을 찾아 사용했다.

6) System.arraycopy(src.toCharArray(), 0, result, length - SRC_LGTH, SRC_LGTH);

 6-1) System.arraycopy()를 이용하여 str의 값을 result[]에 복사해야 하므로 str를 char[]로 변환해야 한다.

 6-2) String을 char[]로 변환하는 toCharArray()로 타입을 일치시켰다.

 6-3) 원본 대상과 복사할 대상의 타입이 일치하지 않으면 ArrayStoreException가 발생한다.

 6-4) 실행결과를 보면 src.toCharArray()에 저장된 값(src = "12345")이 오른쪽으로 출력되었음을 알 수 있다.

  6-4-1) length - SRC_LGTH

            result의 5번째(index)부터 "12345"를 저장하면 되므로 length(10)와 SRC_LGTH(5)를 뺀다.

*length로 char[]을 생성하므로 length와 result.length는 동일하다.

// (1) fillZero메서드를 작성하시오.
public static String fillZero(String src, int length) {
    final int SRC_LGTH = src.length();  // 상수로 정의 : 불필요한 메서드 호출을 막기 위함
    
    // 1. src가 널이거나 src.length()가 length와 같으면 src를 그대로 반환한다.
    if (src == null || SRC_LGTH == length) return src;
    // 2. length의 값이 0보다 같거나 작으면 빈 문자열("")을 반환한다.
    if (length <= 0) return "";
    // 3. src의 길이가 length의 값보다 크면 src를 length만큼 잘라서 반환한다.
    if (SRC_LGTH > length) return src.substring(length);  // 1

    // 4. 길이가 length인 char배열을 생성한다.
    char result[] = new char[length];
    // 5. 4에서 생성한 char배열을 '0'으로 채운다.
    Arrays.fill(result, '0');

    // 6. src에서 문자배열을 뽑아내서 4에서 생성한 배열에 복사한다.
    System.arraycopy(src.toCharArray(), 0, result, length - SRC_LGTH, SRC_LGTH);
    // 7. 4에서 생성한 배열로 String을 생성해서 반환한다.
    return new String(result);
}

 

 

 

 

[9-7] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

[Hint] String클래스의 indexOf()를 사용할 것

메서드명 : contains

기       능 : 첫 번째 문자열(src)에 두 번째 문자열(target)이 포함되어 있는지 확인한다.

                 포함되어 있으면 true, 그렇지 않으면 false를 반환한다.

반환타입 : boolean

매개변수 : String src

                 String target

class Exercise9_7 {
    /*
        (1) contains메서드를 작성하시오.
    */

    public static void main(String[] args) {
        System.out.println(contains("12345", "23"));
        System.out.println(contains("12345", "67"));
    }
}
// true
// false

 

나의 답 : 제시된 힌트를 못보고 String.contains()를 사용했다.

// (1) contains메서드를 작성하시오.
public static boolean contains(String src, String target) {
    return src.contains(target);
}

 

해설 :

indexOf()는 지정된 문자열(src)에서 특정 문자열(target)을 찾아 그 위치를 알려준다.

만일 찾지 못한다면 -1을 반환하므로 indexOf()의 결과가 -1인지만 확인해서 그 결과를 돌려주면 된다.

찾으면 포함되었으므로 조건식(n != -1) true, 찾지 못하면 포함되지 않았으므로 조건식(-1 != -1) false를 반환한다.

※ != (같지않음) : 조건식에서 사용했을 때 A와 B가 같지 않으면 true를, 같으면 false를 반환한다.

// (1) contains메서드를 작성하시오.
public static boolean contains(String src, String target) {
    return src.indexOf(target) != -1;
}
// return -1 != -1;
// return false;

 

 

 

 

[9-8] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

[Hint] Math.round()와 Math.pow()를 이용하라.

메서드명 : round

기       능 : 주어진 값을 반올림하여, 소수점 이하 n자리의 값을 반환한다.

                 예를 들어 n의 값이 3이면, 소수점 4째 자리에서 반올림하여 소수점 이하 3자리의 수를 반환한다.

반환타입 : double

매개변수 : double d - 변환할 값

                  int n - 반올림한 결과의 소수점 자리

class Exercise9_8 {
    /*
        (1) round메서드를 작성하시오.
    */
    
    public static void main(String[] args) {
        System.out.println(round(3.1415, 1));
        System.out.println(round(3.1415, 2));
        System.out.println(round(3.1415, 3));
        System.out.println(round(3.1415, 4));
        System.out.println(round(3.1415, 5));
    }
}
// 3.1
// 3.14
// 3.142
// 3.1415
// 3.1415

 

나의 답 :

[9-8] 소수점의 이동
[9-8] 소수점의 이동

double pi = 3.14;

System.out.println("*");
System.out.println(pi * 10);
System.out.println(pi * 100);
System.out.println(pi * 1000);

System.out.println("/");
System.out.println(pi / 10);
System.out.println(pi / 100);
System.out.println(pi / 1000);

// *
// 31.400000000000002
// 314.0
// 3140.0
// /
// 0.314
// 0.031400000000000004
// 0.00314
public static long round(double a)  // Math.round() : 소수점 첫 째 자리에서 반올림하여 long 타입의 정수로 반환
public static double pow(double a, double b)  // Math.pow(double a, double b) : a의 b제곱을 반환

 

1) Math.round()는 소수점 첫 째 자리에서 반올림하므로 소수점 2째 자리에서 반올림하려면 10을 곱해야 한다.

 1-1) Math.pow(10, n)

 1-1-1) 3째 자리(100), 4째 자리(1000), 5째 자리(10000) : 10의 제곱만큼 곱하므로 Math.pow()로 동적인 코드 작성 가능

 1-1-2) Math.pow(10, n) : Math.pow(10, 1) → 10, Math.pow(10, 2) → 100, Math.pow(10, 3) → 1000

2) 10을 곱한 후 반올림을 수행하면 31.0이 되는데 소수점 이하 1자리의 수(3.1)를 얻으려면 10을 다시 나눠야 한다.

 2-1) / Math.pow(10, n)

 2-1-1) 2자리의 수(100), 3자리의 수(1000), 4자리의 수(10000) : 마찬가지로 10의 제곱이므로 Math.pow()로 동적인 코드 작성 가능

 2-1-2) Math.pow(10, n) : Math.pow(10, 2) → 100, Math.pow(10, 3) → 1000, Math.pow(10, 4) → 10000

// (1) round메서드를 작성하시오.
public static double round(double d, int n) {
    return Math.round(d * Math.pow(10, n)) / Math.pow(10, n);
}

 

 

 

 

[9-9] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

[Hint] StringBuffer와 String클래스의 charAt(int i)과 indexOf(int ch)를 사용하라.

메서드명 : delChar

기       능 : 주어진 문자열에서 금지된 문자들을 제거하여 반환한다.

반환타입 : String

매개변수 : String src - 변환할 문자열

                 String delCh - 제거할 문자들로 구성된 문자열

class Exercise9_9 {
    /*
        (1) delChar메서드를 작성하시오.
    */

    public static void main(String[] args) {
        System.out.println("(1!2@3^4~5)"+" -> " + delChar("(1!2@3^4~5)","~!@#$%^&*()"));
        System.out.println("(1 2 3 4\t5)"+" -> " + delChar("(1 2 3 4\t5)"," \t"));
    }
}
// (1!2@3^4~5) -> 12345
// (1 2 3 4 5) -> (12345)

 

나의 답 :

StringBuffer에 src을 담은 후 indexOf()로 금지된 문자를 찾으면 해당 위치의 문자를 삭제하는 방식으로 구현했다.

이중 반복문은 비효율적이라 반복문 하나로 구현하려 했으나 공백을 제거할 때 첫 번째 공백만 제거되는 문제로 인해,

이중 반복문으로 sb에 저장된 문자 하나하나가 금지된 문자를 포함하는지 확인했다.

1)  String.valueOf(delCh.charAt(i) : StringBuffer.indexOf()는 매개변수로 String 타입을 받으므로 String으로 변환해야 한다.

2) new String(sb) : delChar()의 반환타입이 String이므로 작업 완료된 sb의 내용을 생성한 String인스턴스에 담아 반환한다.

 2-1) StringBuffer을 문자열로 변환하려면 String클래스의 생성자에 StringBuffer을 인자로 넣으면 된다.

*String.valueOf() : 매개변수로 전달한 값을 String(문자열)으로 변환하여 반환하는 메서드

// (1) delChar메서드를 작성하시오.
public static String delChar(String src, String delCh) {
    StringBuffer sb = new StringBuffer(src);  // 변경(삭제)가능한 StringBuffer 생성

    // 상수로 정의 : 불필요한 메서드 호출을 막기 위함(반복문 : 매 반복마다 호출)
    final int DEL_CHAR_LGTH = delCh.length();
    final int SB_LGTH = sb.length();

    for (int i = 0; i < DEL_CHAR_LGTH; i++) {
        for (int j = 0; j < SB_LGTH; j++) {  // sb에 저장된 문자 하나하나가 금지된 문자를 포함하는지 확인
            if (!(sb.indexOf(String.valueOf(delCh.charAt(i))) == -1)) {        // sb가 금지된 문자를 포함하는지 확인
                sb.deleteCharAt(sb.indexOf(String.valueOf(delCh.charAt(i))));  // 삭제(sb의 금지된 문자 위치)
            }
        }
    }
    return new String(sb);
}

 

해설 :

int indexOf(int ch)      // 문자열에서 특정 문자(ch)를 찾을 때 사용
                         // 매개변수 타입이 int지만 char값을 넣어도 무방(유니코드)
int indexOf(String str)  // 문자열에서 특정 문자열(str)을 찾을 때 사용

 

반복문을 이용하여 주어진 문자열(src)의 문자를 순서대로 가져와 삭제할 문자열(delCh)을 포함하는지 확인한다.

포함되어 있지 않을 때만(index()의 결과가 -1일 때), StringBuffer에 추가한다.

// (1) delChar메서드를 작성하시오.
public static String delChar(String src, String delCh) {
    StringBuffer sb = new StringBuffer(src.length());

    for(int i = 0; i < src.length(); i++) {
        char ch = src.charAt(i);
        
        // ch가 delCh에 포함되지 않으면(indexOf()로 못찾으면) sb에 추가
        if(delCh.indexOf(ch) == -1)  // indexOf(int ch) 호출
            sb.append(ch);
    }
    return sb.toString();  // StringBuffer에 저장된 내용을 String으로 반환
}

 

 

 

 

[9-10] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

메서드명 : format

기       능 : 주어진 문자열을 지정된 크기의 문자열로 변환한다. 나머지 공간은 공백으로 채운다.

반환타입 : String

매개변수 : String src - 변환할 문자열

                 int length - 변환된 문자열의 길이

                 int alignment - 변환된 문자열의 정렬조건(0: 왼쪽 정렬, 1: 가운데 정렬, 2: 오른쪽 정렬)

class Exercise9_10 {
    /*
       (1) format메서드를 작성하시오
        1. length의 값이 str의 길이보다 작으면 length만큼만 잘라서 반환한다.
        2. 1의 경우가 아니면, length크기의 char배열을 생성하고 공백으로 채운다.
        3. 정렬조건(alignment)의 값에 따라 문자열(str)을 복사할 위치를 결정한다. (System.arraycopy() 사용)
        4. 2에서 생성한 char배열을 문자열로 만들어 반환한다.
    */

    public static void main(String[] args) {
        String str = "가나다";

        System.out.println(format(str, 7, 0));  // 왼쪽 정렬
        System.out.println(format(str, 7, 1));  // 가운데 정렬
        System.out.println(format(str, 7, 2));  // 오른쪽 정렬
    }
}
// 가나다
//   가나다
//     가나다

 

나의 답 :

1) length < STR_LGTH : STR_LGTH의 값이 더 크다면 substring()로 처음부터(0) length의 길이(7)만큼 자른다.

 1-1) length는 출력되어야 할 배열의 길이로 사용된다.

 1-2) STR_LGTH가 더 큰 경우에 자르지 않고 사용하면 배열을 복사할 때 IndexOutOfRangeException가 발생한다.

2) Arrays.fill(result, ' '); : 배열을 같은 내용으로 초기화할 때 유용하다.

3) 정렬 조건에 따라 result[]에 SOURCE("가나다") 값을 저장할 위치가 달라진다.

 3-1) 왼쪽 정렬 : 값을 처음부터 저장하면 되므로 복사를 시작할 위치로 0을 지정한다.

 3-2) 가운데 정렬 : 값을 중간에 저장해야 하므로 복사를 시작할 위치로 length를 2로 나눈 값을 지정한다.

        "가운데"라는 말만 보고 배열을 반으로 나눈 위치에 "가나다"를 저장하면 될 줄 알았다.

        배열을 그림으로 그려보니 중간에 값을 저장하려면 공백이 앞 뒤로 두 번 들어간 2에 저장해야 한다.

        이는 length(7) - str.length()(3) / 2로 얻을 수 있다. (7 - 3 / 2 = 2)

         

[9-10] 가운데 정렬 배열 그림(나의 답)
[9-10] 가운데 정렬 배열 그림(나의 답)
[9-10] 가운데 정렬 배열 그림(정답)
[9-10] 가운데 정렬 배열 그림(정답)

 

 3-3) 오른쪽 정렬 : 값을 오른쪽에 저장해야 하므로 복사를 시작할 위치로 length - STR_LGTH를 지정한다.

  3-3-1) length(7) - STR_LGTH(3) : SOURCE("가나다") 값을 4번째부터 저장하므로 앞(1~3번째)은 공백이 된다.

4) char[]을 문자열로 변환하려면 String클래스의 생성자에 char[]을 인자로 넣으면 된다.

*length로 char[]을 생성하므로 length와 result.length는 동일하다.

*해설을 보고 난 후 가독성을 위해 else-if문을 swith문으로 변경하였다.

*결과가 어떤 값(0, 1, 2)에 따라 나뉜다면 else-if문 보다는 switch문을 사용하도록 코드를 작성하는 습관을 들여야겠다.

// (1) format메서드를 작성하시오
public static String format(String str, int length, int alignment) {
    // 상수로 정의 : 불필요한 메서드 호출을 막기 위함(여러 번 사용 : 사용할 때마다 호출)
    final int STR_LGTH = str.length();
    final char[] SOURCE = str.toCharArray();

    // 1. length의 값이 str의 길이보다 작으면 length만큼만 잘라서 반환한다.
    if (length < STR_LGTH) return str.substring(0, length);

    // 2. 1의 경우가 아니면, length크기의 char배열을 생성하고 공백으로 채운다.
    char result[] = new char[length];
    Arrays.fill(result, ' ');

    // 3. 정렬조건(alignment)의 값에 따라 문자열(str)을 복사할 위치를 결정한다. (System.arraycopy() 사용)
    switch (alignment) {
        case 0:
        default :
            System.arraycopy(SOURCE, 0, result, 0, STR_LGTH);
            break;
        case 1:
            // System.arraycopy(SOURCE, 0, result, length / 2, STR_LGTH);  // 오답
            System.arraycopy(SOURCE, 0, result, (length - STR_LGTH) / 2, STR_LGTH);
            break;
        case 2:
            System.arraycopy(SOURCE, 0, result, length - STR_LGTH, STR_LGTH);
            break;
    }
    
    // 4. 2에서 생성한 char배열을 문자열로 만들어 반환한다.
    return new String(result);
}

 

해설 : (해설이 없어 앞서 설명한 나의 답을 정리함)

1) diff에 length - str.length()를 저장한다.

 1-1) diff에 저장된 값이 0보다 작을 때 substring()로 처음부터(0) length의 길이(7)만큼 잘라 반환한다.

 1-2) diff에 저장된 값이 음수라면 length 보다 str.length()의 값이 더 크다는 의미이다.

 1-3) str.length()의 값이 더 클 때 자르지 않고 사용하면 배열을 복사할 때 IndexOutOfRangeException가 발생한다.

2) System.arraycopy()를 이용하여 str의 값을 result[]에 복사해야 하므로 str를 char[]로 변환해야 한다.

 2-1) 원본 대상과 복사할 대상의 타입이 일치하지 않으면 ArrayStoreException가 발생한다.

3) 정렬 조건에 따라 result[]에 source("가나다") 값을 저장할 위치가 달라진다.

 3-1) 왼쪽 정렬 : 값을 처음부터 저장하면 되므로 복사를 시작할 위치로 0을 지정한다.

 3-2) 가운데 정렬 : 복사를 시작할 위치로 2를 지정한다.

  3-2-1) 그림을 그려보면 "가나다" 앞 뒤로 공백이 두 번 들어간다.

  3-2-2) 이는 diff(length(7) - str.length()(3)) / 2로 얻을 수 있다. (7 - 3 / 2 = 2)

 3-3) 오른쪽 정렬 : 값을 오른쪽에 저장해야 하므로 복사를 시작할 위치로 diff(length - str.length())를 지정한다.

  3-3-1) diff(length(7) - str.length()(3)) : SOURCE("가나다") 값을 4번째부터 저장하므로 앞(1~3번째)은 공백이 된다.

4) char[]을 문자열로 변환하려면 String클래스의 생성자에 char[]을 인자로 넣으면 된다.

// (1) format메서드를 작성하시오
public static String format(String str, int length, int alignment) {
    // 1. length의 값이 str의 길이보다 작으면 length만큼만 잘라서 반환한다.
    int diff = length - str.length();
    if (diff < 0) return str.substring(0, length);

    // 2. 1의 경우가 아니면, length크기의 char배열을 생성하고 공백으로 채운다.
    char[] source = str.toCharArray();  // 문자열을 char배열로 변환
    char[] result = new char[length];

    for(int i = 0; i < result.length; i++)
        result[i] = ' ';  // 배열 result를 공백으로 채운다.
    
    // 3. 정렬조건(alignment)의 값에 따라 문자열(str)을 복사할 위치를 결정한다.
    switch(alignment) {
        case 0 :
        default :
            System.arraycopy(source, 0, result, 0, source.length);
            break;
        case 1 :
            System.arraycopy(source, 0, result, diff/2, source.length);
            break;
        case 2 :
            System.arraycopy(source, 0, result, diff, source.length);
            break;
    }
    
    // 4. 2에서 생성한 char배열을 문자열로 만들어 반환한다.
    return new String(result);
}

 

 

 

 

[9-11] 커맨드라인으로 2~9사이의 두 개의 숫자를 받아서 두 숫자사이의 구구단을 출력하는 프로그램을 작성하시오.

예를 들어 3과 5를 입력하면 3단부터 5단까지 출력한다.

// 실행결과
C:\jdk1.8\work\ch9>java Exercise9_11 2
시작 단과 끝 단, 두 개의 정수를 입력해주세요.
USAGE : GugudanTest 3 5

C:\jdk1.8\work\ch9>java Exercise9_11 1 5
단의 범위는 2와 9사이의 값이어야 합니다.
USAGE : GugudanTest 3 5

C:\jdk1.8\work\ch9>java Exercise9_11 3 5
3*1=3
3*2=6
3*3=9
3*4=12
3*5=15
3*6=18
3*7=21
3*8=24
3*9=27

4*1=4
4*2=8
4*3=12
4*4=16
4*5=20
4*6=24
4*7=28
4*8=32
4*9=36

5*1=5
5*2=10
5*3=15
5*4=20
5*5=25
5*6=30
5*7=35
5*8=40
5*9=45

 

나의 답 :

처음에는 실행 결과로 출력된 문장이 예외 메시지가 아닌 println()로 출력된 건 줄 알았다.

1) 주석으로 from, to의 값이 2보다 작거나 9보다 클 때의 조건식을 적어놨는데, 가독성이 떨어진다.

 1-1) 조건식으로 예외를 처리할 때는 해당 되는 값이 아닐 때 예외를 처리하도록 하는 코드가 더 직관적이다.

2) 생성자 Exception()안에 넣은 문장은 예외 메시지이며, e.getMessage()로 얻을 수 있다.

 2-1) e.getMessage()로 예외 메시지를 얻어 출력한 후 System.exit(0);에 의해 프로그램이 정상적으로 종료된다.

3) 시작 단(from)은 끝 단(to)보다 작아야 하므로, 시작 단이 크다면 두 값을 바꾼다.(큰 값이 to에 저장되도록 함)

4) 시작 단(from)부터 끝 단(to)까지 출력한다.

class Exercise9_11 {
    public static void main(String[] args) {
        int from = Integer.parseInt(args[0]);
        int to = Integer.parseInt(args[1]);

        try {
            if (args.length != 2) {
                throw new Exception("시작 단과 끝 단, 두 개의 정수를 입력해주세요.");
            }

            // if (from < 2 || from > 9 || to < 2 || to > 9) {
            if (!(from >= 2 && from <= 9 && to >= 2 && to <= 9)) {  // 1
                throw new Exception("단의 범위는 2와 9사이의 값이어야 합니다.");
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());  // 2
            System.out.println("USAGE : GugudanTest 3 5");
            System.exit(0);
        }

        // 3
        if (from > to) {
            int temp = from;
            from = to;
            to = temp;
        }

        // 4
        for (int i = from; i <= to; i++) {
            for (int j = 1; j <= 9; j++) {  // from * j(1~9)
                System.out.println(i + "*" + j + "=" + i * j);
            }
            System.out.println();
        }
    }
}

 

 

 

 

[9-12] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

[주의] Math.random()을 사용하는 경우 실행결과와 다를 수 있음.

메서드명 : getRand

기       능 : 주어진 범위(from~to)에 속한 임의의 정수값을 반환한다.(양쪽 경계값 모두 범위에 포함)

                  from의 값이 to의 값보다 클 경우도 처리되어야 한다.

반환타입 : int

매개변수 : int from - 범위의 시작값

                 int to - 범위의 끝값

[Hint] Math.random(), 절대값을 반환하는 Math.abs(int a), 둘 중에 작은 값을 반환하는 Math.min(int a, int b)를 사용하라.

class Exercise9_12 {
    /*
        (1) getRand메서드를 작성하시오.
    */

    public static void main(String[] args) {
        for(int i = 0; i < 20; i++)
            System.out.print(getRand(1, -3) + ",");
        }
    }
}
// 0,-1,1,0,-2,-2,1,1,-3,0,-1,1,1,1,0,-1,1,0,-1,-3,

 

해설(+나의 생각) :

그동안 Math.random()를 양수로만 다뤄왔던 터라 Math.abs()와 Math.min()가 왜 필요한지 파악하지 못했다.

 

1) 0~9범위의 임의의 값을 구하려면, 아래와 같은 식으로 구할 수 있다.

0 <= (int) (Math.random() * 10) < 10

 

2) 1~10범위의 임의의 값을 구하려면, 아래와 같은 식으로 구할 수 있다.

 2-1) Math.random()에 1을 더하면 양변에 1씩 더해진다.

 2-2) from(1)의 값은 범위에 포함(<=)되지만, to(11)의 값은 범위에 포함되지 않으므로(<) 범위는 1~10이다.

1 <= (int) (Math.random() * 10) + 1 < 11
(int) (Math.random() * 5) - 3   // 범위(-3 ~ 1) : -3 <= x < 2(양변 -3)
(int) (Math.random() * 45) + 1  // 범위(1 ~ 45) : 1 <= x < 46(양변 +1)

 

3) Math.random()에 곱하는 값(10)은 범위에 해당하는 정수의 개수이다. (1~10 : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 → 10개)

 3-1) 1~10의 범위라면 10개의 정수 중 하나가 임의로 선택된다.

 3-2) 10 - 1 + 1을 계산한 결과는 10이 된다.

 3-3) 즉, 정수의 개수는 끝값 - 시작값 + 1로 구할 수 있다.

1 <= (int) (Math.random() * (to - from + 1) + 1 < 11

 

4) 문제의 조건을 보면 시작 값(from)이 끝 값(to) 보다 큰 경우도 처리해야 한다.

 4-1) Math.abs()를 이용하여 끝 값(to) - 시작 값(from)이 음수가 되지 않도록 한다.

 4-2) 이 때, to - from 연산에 따라 값이 달라질 수 있으므로 1은 괄호 밖에서 더해야 한다.

1 <= (int) (Math.random() * (Math.abs(to - from) + 1) + 1 < 11
System.out.println(Math.abs(1 - 10 + 1));  // 8
System.out.println(Math.abs(10 - 1 + 1));  // 10
System.out.println(Math.abs(10 - 1) + 1);  // 10
System.out.println(Math.abs(1 - 10) + 1);  // 10

 

5) Math.random()에 더하는 값(1)은 범위의 시작값이다.

 5-1) 앞서 0~9의 범위에서 Math.random()에 1을 더하면 1~10의 범위가 되므로 1이 범위의 시작값이 된다.

 5-2) 시작 값(from)이 끝 값(to) 보다 클 수도 있기 때문에 시작 값(from)과 끝 값(to) 중 작은 값이 더해져야 한다.

  5-2-1) getRand(1, -3) : 시작 값(from = 1)이 끝 값(to = -3) 보다 더 크므로 끝 값(to = -3)을 Math.random()에 더한다.

1 <= (int) (Math.random() * 10) + 1 < 11
1 <= (int) (Math.random() * (Math.abs(to - from) + 1) + 1 < 11
1 <= (int) (Math.random() * (Math.abs(to - from) + 1) + Math.min(from, to) < 11
1 <= (int) (Math.random() * (Math.abs(to - from) + 1) + (from < to ? from : to) < 11  // 삼항 연산자 사용

 

// (1) getRand메서드를 작성하시오.
public static int getRand(int from, int to) {
    return (int) (Math.random() * (Math.abs(to - from) + 1)) + Math.min(from, to);
}

 

 

 

 

[9-13] 다음은 하나의 긴 문자열(source) 중에서 특정 문자열과 일치하는 문자열의 개수를 구하는 예제이다.

빈 곳을 채워 예제를 완성하시오.

public class Exercise9_13 {
    public static void main(String[] args) {
        String src = "aabbccAABBCCaa";
        System.out.println(src);
        System.out.println("aa를 " + stringCount(src, "aa") + "개 찾았습니다.");
    }

    static int stringCount(String src, String key) {
        return stringCount(src, key, 0);
    }

    static int stringCount(String src, String key, int pos) {
        int count = 0;
        int index = 0;

        if (key == null || key.length() == 0)
            return 0;

        /*
            (1) 알맞은 코드를 넣어 완성하시오.
        */
        return count;
    }
}
// aabbccAABBCCaa
// aa를 2개 찾았습니다.

 

나의 답 :

1) String.indexOf(대상, 찾기 시작할 위치)

 1-1) 특정 문자열(src)에서 찾으려는 문자열(key)이 존재하면 해당 문자열이 시작하는 위치를 반환하고,

 1-2) 존재하지 않으면 -1을 반환한다.

 1-3) while문은 조건식이 true인 동안 반복되며, src에 key가 존재하지 않을 때(-1) 종료된다.

2) 일치하는 부분을 찾으면 count의 값을 1 증가시킨다.

 2-1) 이는 특정 문자열(src)에 포함된 찾으려는 문자열(key)의 개수가 된다.

3) 일치하는 부분을 찾았으면, 그 다음으로 일치하는 부분을 찾기 위해 pos(찾기 시작할 위치)를 변경해야 한다.

 3-1) pos를 변경하지 않으면, 계속 같은 위치(처음으로 찾은 위치)를 반환하므로 무한 반복에 빠지게 된다.(프로그램 종료X)

final int KEY_LGTH = key.length();

while ((index = src.indexOf(key, pos)) != -1) {  // 1
    count++;                                     // 2
    pos = index + KEY_LGTH;                      // 3
}

 

 

 

 

[9-14] 다음은 화면으로부터 전화번호의 일부를 입력받아 일치하는 전화번호를 주어진 문자열 배열에서 찾아서 출력하는 프로그램이다. 알맞은 코드를 넣어 프로그램을 완성하시오.

[Hint] Pattern, Matcher클래스를 사용하라.

import java.util.*;
import java.util.regex.*;

class Exercise9_14 {
    public static void main(String[] args) {
        String[] phoneNumArr = {
            "012-3456-7890",
            "099-2456-7980",
            "088-2346-9870",
            "013-3456-7890"
        };

        ArrayList list = new ArrayList();
        Scanner s = new Scanner(System.in);

        while(true) {
            System.out.print(">>");
            String input = s.nextLine().trim();

            if(input.equals("")) {
                continue;
            } else if(input.equalsIgnoreCase("Q")) {
                System.exit(0);
            }

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

            if(list.size() > 0) {
                System.out.println(list);
                list.clear();
            } else {
                System.out.println("일치하는 번호가 없습니다.");
            }
        }
    } // main
}
// >>
// >>
// >>asdf
// 일치하는 번호가 없습니다.
// >>
// >>
// >>0
// [012-3456-7890, 099-2456-7980, 088-2346-9870, 013-3456-7890]
// >>234
// [012-3456-7890, 088-2346-9870]
// >>7890
// [012-3456-7890, 013-3456-7890]
// >>q

 

해설 :

예제 9-33를 참고하며 풀려고 했으나, 정규직을 정의하는 것부터 감이 잡히지 않았다.

pattern = "(0\\d{1,2})-(\\d{3,4})-(\\d{4})"를 활용하려 했으나 화면으로부터 입력받은 값은 어디서 검사해야 할지 막혔다.

해설을 본 후 주석으로 간단하게 정리해뒀다. 정규식 관련 문제들도 많이 접해봐야 할 필요성을 느꼈다.

1) 입력받을 문자열로 배열에 저장된 전화번호를 검색해야 하므로 input값을 이용하여 패턴(정규식)을 정의해야 한다.

 1-1) ".*" + input + ".*" : 입력받은 문자열을 포함하는 모든 문자열을 의미하는 패턴(정규식)이다.

 1-2) .* : 모든 문자열을 의미한다.

2) 반복문으로 배열 phoneNumArr의 전화번호를 하나씩 읽어 패턴과 일치하는지 확인한다.

 2-1) 사용자가 "234"를 입력했을 때 "012-3456-8790"과 "088-2346-9870"가 모두 검색될 수 있도록

 2-2) 전화번호에서 "-"를 제거한 후 패턴과 일치하는지 확인해야 한다.

 2-3) replace(A, B)는 엄연히 말하자면 제거 보다는 A를 B로 변경하는 메서드이다.

3) 패턴과 일치하는 부분을 찾으면, list에 phoneNum(예 : "012-3456-7890")을 추가한다.

 3-1) list에 NonHyphen_phoneNum를 저장하면 "-"가 제거된 전화번호가 저장된다.

String pattern = ".*" + input + ".*";  // 정규식 정의
Pattern p = Pattern.compile(pattern);  // pattern 인스턴스 생성(정규식)

for (int i = 0; i < phoneNumArr.length; i++) {
    String phoneNum = phoneNumArr[i];  // "012-3456-7890"
    String NonHyphen_phoneNum = phoneNum.replace("-", "");  // "-"을 ""으로 변경(제거)

    Matcher m = p.matcher(NonHyphen_phoneNum);  // Matcher 인스턴스 생성(정규식으로 비교할 대상)

    if (m.find()) {
        list.add(phoneNum);
    }
}