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

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;
  }
}@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;
  }
}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;
  //  }
}
// 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);
    }
  }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);
  }
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 => 서로 다른 인스턴스이다.
  }
}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에서 상속 받은 것을 오버라이딩하였다.
    // - 문자열이 같은지 비교한다.
    //
  }
}
String s1;
s1 = new String("Hello");
s2 = new String("Hello");String x = "Hello";
String y = "Hello";String s1 = new String("hello");
String s2 = s1.intern();
String s3 = "hello";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());
  }
}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()));
  }
}    String s1 = new String("Hello");
    String s2 = s1.toString();
    String s2 = s1; //위의 코드와 동일한 코드이다.
    // => String이 오버라이딩한 toString()은 this 주소를 그대로 리턴한다.
    System.out.println(s1 == s2); // truepublic 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);
  }
}