2022.07.27/자바 정리/String class

Jimin·2022년 7월 27일
0

비트캠프

목록 보기
10/60
post-thumbnail

오늘 공부한 내용

  • board-app 프로젝트 수행
    • 공통 코드(필드,메서드)를 공유하는 방법 : 상속 (다시 연습)
  • String 클래스 사용법(eomcs-java-lang: com.eomcs.basic.ex02.*)

상속과 추상 클래스, 추상 메서드, 콘크리트 클래스

  • Concrete class는 인스턴스를 생성할 수 있다.
  • Abstract class는 인스턴스를 생성할 수 없다.

  • Object class
    • 객체(클래스)라면 기본적으로 가져야 할 필드와 메서드를 가지고 있다.
  • AbstractCollection class
    • 목록을 다루는 객체(클래스)라면 기본적으로 가져야 할 필드와 메서드를 가지고 있다.
    • 추상메서드: 메서드 일부를 서브 클래스가 자신의 역할에 맞추어 Method signature만 구현하도록 형식만 정의했다.
  • AbstractList class
    • 등록한 순서대로 목록을 다루는 객체가 가져야 할 필드와 메서드를 가지고 있다.
    • 상속받은 추상 메서드들 중에 일부를 구현하고, 일부는 서브 클래스에게 미룬다.
  • AbstractSequentialList class
    • 인덱스로 목록을 다루는 객체가 가져야 할 필드와 메서드를 가지고 있다.
    • 상속 받은 추상 메서드들 중에 일부를 구현하고, 일부는 서브 클래스에게 미룬다.
  • LinkedList class
    • 연결리스트를 이용하여 목록을 다루는데 필요한 필드와 메서드를 가지고 있다.
    • 아직까지 구현하지 않은 모든 추상 메서드를 구현한다.

board-app 프로젝트 수행

→ 공통 코드(필드, 메서드)를 공유하는 방법: 상속

  • 클래스들의 공통 코드를 추출하여 수퍼 클래스(Object class)를 정의한다.
  • 수퍼 클래스의 필드명 및 메서드명은 일반적인 용도로 변경한다.

1단계 - BoardList와 MemberList의 공통 필드와 메서드를 찾아 분리한다.

  • com.bitcamp.util.ObjectList 클래스 생성
    • 여러 프로젝트에서 사용할 수 있도록 패키지를 조정한다.
package com.bitcamp.util;

public class ObjectList {
  private static final int DEFUALT_CAPACITY = 10;
  private int size;
  protected Object[] elementData;

  public ObjectList() {
    elementData = new Object[DEFUALT_CAPACITY]; // this 생략
  }
  public ObjectList(int initialCapacity) {
    elementData = new Object[initialCapacity];
  }

  public void add(Object e) {
    if(size == elementData.length) {
      grow();
    }
    elementData[size++] = e;
  }

  public Object[] toArray() {
    Object[] arr = new Object[size];
    for (int i = 0; i < arr.length; i++) {
      arr[i] = elementData[i];
    }
    return arr;
  }

  public Object get(int index) {
    if(index <0 || index >= size) {
      return null;
    }
    return elementData[index];
  }

  public boolean remove(int index) {
    if(index <0 || index >= size) {
      return false;
    }
    for(int i=index+1;i<size;i++) {
      elementData[i-1]=elementData[i];
    }
    elementData[--size] = null;
    return true;
  }

  public int size() {
    return size;
  }


  // private method는 밑에 두자
  private void grow() {
    int newSize = elementData.length + (elementData.length >> 1);
    Object[] newArray = new Object[newSize];
    for (int i = 0; i < elementData.length; i++) {
      newArray[i] = elementData[i];
    }
    elementData = newArray;
  }
}
  • private method는 public보다 밑에 둔다.

2단계 - ObjectList를 상속 받아 BoardList와 MemberList를 정의한다.

  • com.bitcamp.board.dao.BoardList 클래스 변경
    • get메서드를 재정의, 오버라이딩 해주었는데, 수퍼 클래스에 있는 get을 서브클래스에서 이용하고 싶기 때문에 super.get()과 같이 super를 붙여서 수퍼 클래스의 get을 이용해준다.
@Override
  public Board get(int boardNo) {
    for (int i = 0; i < size(); i++) {
      Board board = (Board)super.get(i);
      if (board.no== boardNo) {
        return board;
      }
    }
    return null;
  }
package com.bitcamp.board.dao;

import com.bitcamp.board.domain.Board;
import com.bitcamp.util.ObjectList;

// 게시글 목록을 관리하는 역할
//
public class BoardList  extends ObjectList{
  // 자동으로 증가하는 게시글 번호
  private int boardNo = 0;

  // 게시글을 저장할 때 자동으로 증가한 번호를 게시글 번호로 설정할 수 있도록
  // add() 메서드를 재정의한다.
  @Override
  public void add(Object e) {
    // 넘어오는 Object 객체를 Board로 형변환해줘야 한다!
    Board board = (Board) e;
    board.no = nextNo();
    super.add(e);
  }

  // 목록에 인덱스로 해당 항목을 찾는 get() 메서드를 오버라이딩하여
  // 게시글을 등록할 때 부여한 일련 번호로 찾을 수 있도록
  // get() 메서드를 재정의(overriding)한다.
  // => 오버라이딩 메서드의 리턴 타입은 원래 타입의 서브 클래스로 변경할 수 있다.
  @Override
  public Board get(int boardNo) {
    for (int i = 0; i < size(); i++) {
      Board board = (Board)super.get(i);
      if (board.no== boardNo) {
        return board;
      }
    }
    return null;
  }


  // 수퍼 클래스의 remove()는 인덱스로 지정한 항목을 삭제한다.
  /// 이것을 게시글 번호에 해당하는 항목을 삭제하도록 상속받은 메소드를 재정의(overriding)한다.
  @Override
  public boolean remove(int boardNo) {
    for (int i = 0; i < size(); i++) {
      Board board = (Board)super.get(i);
      if (board.no == boardNo) {
        return super.remove(i);
      }
    }
    return false;
  }


  private int nextNo() {
    return ++boardNo;
  }
}
  • com.bitcamp.board.dao.MemberList 클래스 변경
    • ObjectList를 상속 받은 후 몇몇의 메서드를 오버라이딩 할 필요가 있음 확인하기 위해 기능을 변경한다.
    • 회원 번호 대신 이메일로 찾고 삭제하도록 변경한다.
    • 같은 기능을 수행하는 메서드라면, 이름을 같게 하는 것이 일관성이 있어서 좋다!
      ⇒ 메서드 오버로딩(Method Overloading)

메서드 오버로딩

  • 동일한 클래스 내에서나 혹은 상속 관계에서 실행할 수 있다.
  • 파라미터 타입, 개수, 순서가 다르더라도 같은 기능을 수행하는 메서드에 대해 같은 이름을 부여함으로써 프로그래밍의 일관성을 제공하는 문법이다.
  • MemberList에서 회원 번호가 아닌, 이메일로 데이터를 검색하려고 하기 때문에
    몇몇 메소드의 파라미터 값이 int에서 String으로 변했지만, 기능은 여전히 회원 데이터를 찾는 것으로 동일하기 때문에
    일관성을 위해, 메서드 명을 그대로 유지해서 메서드를 오버로딩한다.
  • add 함수는 수퍼 클래스 그대로 이용한다.
  • no관련 메서드는 사용하지 않으므로 삭제한다.
package com.bitcamp.board.dao;

import com.bitcamp.board.domain.Member;
import com.bitcamp.util.ObjectList;

// 회원 목록을 관리하는 역할
//
// ObjectList를 상속 후 메서드를 오버라이딩 할 필요가 있음을 확인하기 위해 회원 번호 대신 이메일로 접근한다.
public class MemberList extends ObjectList{
  //private int memberNo = 0;

  // 인덱스 대신 이메일로 회원 데이터를 찾을 수 있도록
  // 메서드를 추가한다.
  // 오버로딩: 파라미터 타입이나, 개수, 순서가 다르더라도 같은 기능을 수행하는 메서드에 대해 같은 이름을 부여함으로써 프로그래밍의 일관성을 제공하는 문법!
  // 오버로딩을 하려면 메서드 끼리 같은 기능을 수행해야 한다!!!!!! -> 일관성 유지
  // 얘는 수퍼 클래스에서의 get과 파라미터 타입이 다르므로 결론적으로 오버로딩을 한 것이다!
  // 메서드 호출할 때 일관되게 사용할 수 있다. => 오버로딩!
  public Member get(String email) {
    for (int i = 0; i < size(); i++) {
      Member member = (Member)get(i); // 여기는 BoardList와 달리, get과 메서드 이름이 달라서 super붙일 필요 없다!
      if (member.email.equals(email)) {
        return member;
      }
    }
    return null;
  }

  //  @Override
  //  public void add(Object e) {
  //    Member member = (Member)e;
  //    member.no = nextNo();
  //    super.add(member);
  //  }

  // @Override: 컴파일러야, 수퍼 클래스의 메서드를 재정의하기 위해 다음 메서드를 만들었는데, 이거 제대로 검사했는지 확인해줄래?
  // 근데 밑의 remove 메소드는 오버로딩이라 @Override를 붙여주면 오류가 나게 된다!
  // 인덱스 대신 이메일로 회원 데이터를 찾아 삭제하는 메서드

  // 수퍼 클래스로부터 상속 받은 메서드와 같은 일을 하며, 메서드 이름도 같다 -> 오버로딩!
  public boolean remove(String email) {
    for (int i = 0; i < size(); i++) {
      Member member = (Member)get(i); // 여기는 BoardList와 달리, get과 메서드 이름이 달라서 super붙일 필요 없다!
      if (member.email.equals(email)) {
        return super.remove(i);
      }
    }
    return false;
  }

  //  private int nextNo() {
  //    return ++memberNo;
  //  }
}

3단계 - 상속의 일반화를 통해 구조가 변경된 것에 맞추어 XxxHandler를 변경한다.

  • com.bitcamp.board.handler.BoardHandler 클래스 변경
// boardList 인스턴스에 들어 있는 데이터 목록을 가져온다.
    Object[] list = this.boardList.toArray();

    for (Object item : list) {
      Board board = (Board)item;
      Date date = new Date(board.createdDate);
      String dateStr = formatter.format(date); 
      System.out.printf("%d\t%s\t%d\t%s\t%s\n",
          board.no, board.title, board.viewCount, board.writer, dateStr);
    }

  }
  • com.bitcamp.board.handler.MemberHandler 클래스 변경
private void onList() {
    System.out.println("[회원 목록]");
    System.out.println("이메일\t이름");

    Object[] list = this.memberList.toArray();

    for (Object item : list) {
      Member member = (Member)item;
      System.out.printf("%s\t%s\n", member.email, member.name);
    }
  }

  private void onDetail() {
    System.out.println("[회원 상세보기]");

    String email = Prompt.inputString("조회할 회원의 이메일? ");

    Member member = memberList.get(email);

    if (member == null) {
      System.out.println("해당 이메일의 회원이 없습니다!");
      return;
    }

    System.out.printf("이름: %s\n", member.name);
    System.out.printf("이메일: %s\n", member.email);
    Date date = new Date(member.createdDate);
    System.out.printf("등록일: %tY-%1$tm-%1$td %1$tH:%1$tM\n", date);

  }



String 클래스 사용법

  • = 기호를 사용해서 레퍼런스 변수 비교하면, 레퍼런스 변수에 저장된 주소 값을 비교하게 되므로 String값이 같아도 객체가 다르면, 즉 주소가 다르면 값이 다르다고 나오게 된다
  • 따라서 다음 그림에서 s1 == s2의 결과는 문자열의 값이 같더라도, false가 나오게 된다.
    인스턴스 주소가 New를 통해 각각 생성되어서 다르기 때문이다.
  • 따라서 진짜 문자열의 값을 비교하고 싶다면, String 클래스에서 Object 클래스의 equals를 오버라이딩 한 것을 이용해서 비교할 수 있다.
public class Exam0110 {
  public static void main(String[] args) {
    // String 레퍼런스
    // - String은 자바 기본 타입이 아니다.
    // - 클래스이다.
    String s1; // s1은 String 인스턴스 주소를 담는 레퍼런스이다.

    // String 인스턴스
    // - 힙에 Hello 문자 코드를 저장할 메모리를 만들고 그 주소를 리턴한다.
    // - 내용물의 동일 여부를 검사하지 않고 무조건 인스턴스를 생성한다.
    // - 가비지가 되면 가비지 컬렉터에 의해 제거된다.
    s1 = new String("Hello");
    String s2 = new String("Hello");

    // 인스턴스가 같은지를 비교해보면,
    System.out.println(s1 == s2); // false => 서로 다른 인스턴스이다.
  }
}
  • 즉, 인스턴스 비교가 아닌, 문자열 값 자체를 비교하기 위해서는 equals() 메소드를 사용해야한다.
public class Exam0120 {
  public static void main(String[] args) {

    String s1 = new String("Hello");
    String s2 = new String("Hello");

    // 두 String 인스턴스는 분명히 서로 다르다.
    System.out.println(s1 == s2);

    // 두 인스턴스가 갖고 있는 문자열이 같은지를 비교하고 싶다면,
    System.out.println(s1.equals(s2));

    // equals()?
    // - Object에 정의되어 있는 메서드이다.
    // - 인스턴스가 같은지 비교한다.
    //
    // String의 equals()?
    // - Object에서 상속 받은 것을 오버라이딩하였다.
    // - 문자열이 같은지 비교한다.
    //
  }
}
  • equalsIgnoreCase()를 사용하면 대소문자 구별 없이 문자열끼리 비교한다.

- new를 통한 문자열 인스턴스 생성과 큰 따옴표("")를 통한 문자열 인스턴스 생성

  • new를 통해 String 인스턴스 생성하기
    • Heap에 인스턴스가 생성된다.
    • new마다 인스턴스를 생성한다.
    • 중복 검사를 안해서 같은 문자열의 값을 같더라도 각각 다른 인스턴스로 생성된다.
String s1;
s1 = new String("Hello");
s2 = new String("Hello");
  • 큰 따옴표를 통해 String 인스턴스 생성하기
  • String Constant(상수) pool(상수풀)에 인스턴스가 생성된다.
  • String pool에 넣으려는 문자값이 있는지 확인, 즉 중복값을 확인한다.
  • 문자열의 값이 중복되면 같은 레퍼런스 주소값을 갖게 된다.
  • 상수 -> 변할 수 없다.
String x = "Hello";
String y = "Hello";
  • A instanceof B
    • A가 B의 인스턴스이냐?
    • B에 A를 담거나 가르킬 수 있냐?
    • 위의 질문의 대답을 boolean 값으로 리턴해준다.

String.intern()

String s1 = new String("hello");
String s2 = s1.intern();
String s3 = "hello";
  • s1.intern() -> s1과 같은 문자열을 가진 String 객체를 String pool에서 찾아서 그 인스턴스의 주소값을 리턴하고, 만약 없으면, String pool에 생성하고 그 주소 값을 리턴한다.
  • 따라서 위의 코드에서, s2와 s3의 ==연산자 결과값은 true가 나오게 될 것이다.

Object.equals()

Object class

  • equals(): 인스턴스 주소가 같은지 비교, == 연산자와 동일하다.
  • toString(): 클래스명, 식별번호 리턴 -> "클래스명@식별번호" (이거 메모리 주소 절대 아니다!)
  • hashCode(): 식별 번호 리턴

String class(extends Object)

  • equals() 오버라이딩
public class Exam0122  {
  static class Member {
    String name;
    int age;

    public Member(String name, int age) {
      this.name = name;
      this.age = age;
    }
  }

  public static void main(String[] args) {

    Member m1 = new Member("홍길동", 20);
    Member m2 = new Member("홍길동", 20);

    System.out.println(m1 == m2); // false

    // Member 클래스는 Object에서 상속 받은 equals()를 오버라이딩 하지 않았다.
    // 따라서 단순히 인스턴스가 같은지를 비교할 것이다.
    System.out.println(m1.equals(m2)); // false
    System.out.println(m1.toString());
    System.out.println(m2.toString());
  }
}
  • 현재 특정 클래스를 상속 받고 있지 않기 때문에 결국 Exam0122클래스는 Object 클래스를 상속받고 있다.
  • 또한 오버라이딩이나 오버로딩이 이루어지고 있지 않기 때문에, Object 클래스 메소드 자체를 그대로 사용한다.
  • 특히, equals가 Object에서는 인스턴스를 비교하기 때문에 여기서도 같은 문자열 값을 갖고 있더라도,
    String 클래스를 통해 접근하고 있지 않기 때문에, 인스턴스가 다르기 때문에 다르다는 결과값을 반환해준다.
  • String 클래스는 Object클래스의 equals()메소드를 오버라이딩해서 우리가 원하는 결과값인,
    문자열 값 자체를 비교해주는 결과값을 얻을수 있게 해준다.
  • Member 인스턴스를 통해 호출하는 기본 내장 메서드(equals)는 모두 Object클래스의 메서드이다.
  • 비록 m1과 m2는 같은 값을 갖고 있지만 인스턴스가 다르다.
  • 따라서, ==연산자를 사용해서 값을 비교하면 false가 리턴된다.
  • Member 클래스는 Object에서 상속 받은 equals()를 오버라이딩 하지 않았다.
  • 따라서 단순히 인스턴스가 같은지를 비교할 것이다.

StringBuffer 클래스

  • 얘는 또 String 클래스와 다르게 equals를 재정의, 오버라이딩 하지 않았다.
    따라서 얘도, 문자열 자체의 값을 equals를 통해 비교할 수 없다.
  • 즉, StringBuffer이지만 문자열 값 자체를 비교하고 싶다면,
    먼저 String을 StringBuffer에서 꺼내서 비교해야한다.
public class Exam0125 {
  public static void main(String[] args) {

    StringBuffer b1 = new StringBuffer("Hello");
    StringBuffer b2 = new StringBuffer("Hello");

    // StringBuffer 에 들어 있는 문자열을 비교하려면?
    // - StringBuffer에서 String을 꺼내 비교하라!
    //
    // String s1 = b1.toString();
    // String s2 = b2.toString();
    // System.out.println(s1.equals(s2));
    //
    System.out.println(b1.toString().equals(b2.toString()));
  }
}

hashcode()

  • 문자열값이 같고, new를 통해 각각 객체를 생성했을 때, 인스턴스는 다르게 나오지만 String 클래스에서의 hashcode는 문자열 값이 같으면 hashcode가 같게 나온다.
  • 하지만, String 클래스를 통해 오버라이딩되지 않은 hashcode를 사용하면 문자열 값이 같아도 다른 hashcode값이 나오게 된다.

toString()

  • String 클래스는 toString()도 오버라이딩 했다.
    • String 클래스에서 오버라이딩한 toString()은 연결된 객체의 인스턴스 주소를 리턴해준다.
    String s1 = new String("Hello");
    String s2 = s1.toString();
    String s2 = s1; //위의 코드와 동일한 코드이다.
    // => String이 오버라이딩한 toString()은 this 주소를 그대로 리턴한다.
    System.out.println(s1 == s2); // true
  • 따라서 다음 코드의 결과는 true가 나온다.
public class Exam0141 {
  public static void main(String[] args) {

    Object obj = new String("Hello"); // 인스턴스 주소가 100이라 가정하자;

    String x1 = (String) obj; // x1 <--- 100

    // obj에 대해 toString()을 호출할 때,
    // => 일단 obj 클래스에 선언된 멤버(필드와 메서드)만 사용할 수 있다.
    // => 단 멤버는 실제 obj가 가리키는 클래스부터 찾아 올라 간다.
    // => 위 예에서 obj가 가리키는 것은 String 이기 때문에
    // => 이 경우 toString()을 호출할 때 String 클래스에서부터 찾는다.
    // => String 클래스가 toString()을 오버라이딩 했기 때문에
    //    결국 이 오버라이딩 메서드를 호출할 것이다.
    String x2 = obj.toString(); // x2 <---- 100

    System.out.println(x1 == x2);

    // 레퍼런스를 통해 메서드를 호출할 때
    // => 레퍼런스가 가리키는 객체의 클래스부터 메서드를 찾아 올라간다.
    // => 따라서 obj가 가리키는 객체의 클래스가 String이기 때문에
    // obj.toString()은 String 클래스부터 해당 메서드를 찾는다.
  }
}
  • Object 클래스로 만든 객체는 사용하기 전에 무조건 내가 사용할 데이터 타입으로 형변환을 지정해줘야 한다.

  • 형변환을 이용하고 사용하는 코드 한 번에 합쳐버리기 -> 괄호 이용

public class Exam0142 {
  public static void main(String[] args) {

    Object obj = new String("Hello");
    String s = (String)obj;
    String str2 = s.toLowerCase();

    // obj가 String 객체를 가리키더라도 
    // obj의 타입이 Object이기 때문에 Object에 선언한 멤버만 사용할 수 있다.
    // obj가 가리키는 원래 클래스의 메서드를 호출하고 싶다면
    // 다음과 같이 원래 타입으로 형변환하라.
    String str = ((String) obj).toLowerCase();
    System.out.println(str);

    // 또는 다음과 같이 원래 타입의 레퍼런스에 저장한 다음 사용하라.
    String x1 = (String) obj;
    str = x1.toLowerCase();
    System.out.println(str);
  }
}
profile
https://github.com/Dingadung

0개의 댓글