。゚(*´□`)゚。

코딩의 즐거움과 도전, 그리고 일상의 소소한 순간들이 어우러진 블로그

[네이버클라우드] 클라우드 기반의 개발자 과정 7기/웹프로그래밍

[NC7기-42일차(6월23일)] - 웹프로그래밍 23일차

quarrrter 2023. 6. 23. 14:00

A obj = new A2();라는 문장은 A 클래스의 변수 obj를 선언하고, new A2()를 통해 A2 클래스의 객체를 생성하여 obj에 대입하는 것입니다. 이렇게 하면 objA 타입의 변수지만, 실제로는 A2 클래스의 인스턴스를 참조합니다.

 

내가 잘 모르는거: 

A2 extends A

A obj = new A2();

A의 인스턴스, A2의 인스턴스

둘다 나옴 / 수퍼클래스의 변수 나오고, 그다음 서브꺼 나옴

 

A2 obj = new A(); 는 안됨 !! 상위 수퍼 클래스를 담을 수 없음.

하위 레퍼런스로 상위 레퍼런스를 가르킬수없음... 

 

레퍼런스로 담을 수 있는건 같은 레벨이나 하위만 담을 수 있음 .. 

 

 

 

 

A obj = new A2();

obj.m()  obj가 A2를 갖고 있음. obj는 A2를 가리키는 거임, A2의 m()// new 뒤에 붙은게 가리키는 거임 ,, ! 

obj.x(); 는 안됨. 컴파일러 입장에서 obj는 A여서....((A2)

 

A obj2 = new A3();

obj2.m();  // A2의 m을 말하는 거임 

// 다형적 변수와 오버라이딩 - 레퍼런스와 메서드 호출
package com.eomcs.oop.ex06.d;


class A {
public void m() {
System.out.println("A의 m() 호출!");
}
}




class A2 extends A {
@Override // 컴파일러에게 오버라이딩을 제대로 했는지 검사하라고 명령한다.
public void m() {
System.out.println("A2의 m() 호출!");
}


public void x() {
System.out.println("A2에서 추가한 메서드 x()");
}
}




class A3 extends A2 {
public void y() {
System.out.println("A3에서 추가한 메서드 y()");
}
}




public class Exam0110 {
public static void main(String[] args) {
A a = new A();
a.m(); // A의 멤버 호출. OK!
// ((A2)a).x(); // A 객체를 A2 객체라 우기면, 컴파일러는 통과! 실행은 오류!
System.out.println("--------------------");


A2 a2 = new A2();
a2.m(); // A2가 오버라이딩 한 메서드 호출! 즉 A2의 m() 호출! OK!
a2.x(); // A2의 메서드 호출! OK!
System.out.println("----------------------");


A obj = new A2();
obj.m(); // A2의 m() 호출.
// 레퍼런스가 하위 클래스의 인스턴스를 가리킬 때,
// => 레퍼런스를 통해 호출하는 메서드는
// 레퍼런스가 실제 가리키는 하위 클래스에서 찾아 올라 간다.
//
// 그렇다고 해서 A2에서 추가한 메서드를 호출할 수는 없다.
// => 즉 레퍼런스의 클래스를 벗어나서 사용할 수는 없다.
// 컴파일러가 허락하지 않는다.
// obj.x(); // 컴파일 오류!


// 물론 a3가 실제 A2 객체를 가리키기 때문에
// A2로 형변환을 수행한 후에는 A2의 멤버를 사용할 수 있다.
((A2)obj).x(); // OK!


System.out.println("----------------------");


A obj2 = new A3();
obj2.m(); // A2의 m() 호출
// a4가 실제 가리키는 A3 클래스부터 상위 클래스로 따라 올라가면서
// 첫 번째로 만난 m()을 호출한다.
System.out.println("--------------------");
}
}

*추상클래스는 인스턴스 생성불가! 

*추상클래스는 인스턴스 생성불가! 

Car c = new Car(); <--불가! 

Car c = new Sedan();  가능 ! --c가 가리키는 건 Sedan

c.run(); -> Sedan의 run()


final 

1. 서브 클래스 생성 불가

final class A <- 상속해줄 수 없는 class .. 

서브클래스가 수퍼클래스 역할을 대체할 수 있는 경우 해킹 위험 ,,,! 해서 .. 

다형적 변수의 허술함,,을 막기 위해서 ,,! 

1. 서브 클래스 생성 막기

2. 수퍼클래스의 메서드 재정의(overriding) 불가!

메서드 앞에 final을 붙이면 상속 받은 서브클래스에서 final 메서드 재정의 불가

클래스 전체를 교체하지 못하게 막기 보다는 일부 메서드만 교체하지 못하게 막는 문법. 

 

3. 변수의 값 변경 불가! = 상수 만들기 

final int a = 100 ; 

보통 public static으로 함. 어차피 고정이니까 ~ 

static 변수는 static 블럭이 생성되고 초기화 문장이 안으로 들어감. 

 

4. 파라미터 변경불가 

public void mq(final int a ) {  }

파라미터는 외부에서 값을 받아온 것이기 때문에 임의로 변경하면 처음 받은 값을 사용하지 못하기 때문에 final로 선언하고 사용하기. 물론 바꾸려면 바꾸는 거지만 유지보수 위해서 final로 ! 

 


Object를 조상으로 갖기때문에 Object의 메서드를 사용할 수 있따 !

자바의 모든클래스는 Object의 자손이다. 

 

instanceof 연산자 

// Object 클래스의 주요 메서드

// 1) toString()

// => 클래스이름과 해시코드를 리턴한다.

// 2) equals()

// => 같은 인스턴스인지 검사한다.

// 3) hashCode()

// => 인스턴스를 식별하는 값을 리턴한다.

// 4) getClass()

// => 인스턴스의 클래스 정보를 리턴한다.

// 5) clone()

// => 인스턴스를 복제한 후 그 복제 인스턴스를 리턴한다.

// 6) finalize()

// => 가비지 컬렉터에 의해 메모리에서 해제되기 직전에 호출된다.


  1) toString()
        - 클래스 정보를 간단히 출력한다.
        - 패키지명.클래스명@16진수해시값
        - 예) ch15.My1@1e81f4dc
    
    System.out.println(obj.toString());

     println()에 넘겨주는 값이 String 타입이 아니라면
     println()은 그 객체에 대해 toString() 호출한 후 그 리턴 값을 출력한다.
     따라서 다음 코드는 위의 코드와 같다.
    System.out.println(obj);

     해시값?
     - 인스턴스 마다 부여된 고유의 식별자이다.
     - 주의! 주소 아니다!
     - 인스턴스가 같은지 검사할 때 사용할 수 있다.
     - hashCode()를 재정의하지 않고 원래 메서드를 그대로 사용하면
       무조건 인스턴스마다 새 해시값이 부여된다.

 

=> 개발을 하다 보면 인스턴스의 현재 값을 간단히 확인하고 싶을 경우가 있다. 그럴 경우 toString()을 오버라이딩 하라!

public String toString() {

return "My [name=" + name + ", age=" + age + "]";

}

 

2. .equals

System.out.println(obj1 == obj2);  //false 

// Object에서 상속 받은 equals()는 == 연산자와 마찬가지로 인스턴스가 같은지를 비교한다.

// 만약 그 내용물이 같은지 비교하고 싶다면 equals()를 재정의 하라!

System.out.println(obj1.equals(obj2));  //false

2-1

     => String 클래스에서 Object의 equals()를 오버라이딩 했기 때문에 인스턴스가 다르더라도 문자열이 같으면 true를 리턴하도록  equals() 메서드를 재정의하였다.
     => 그래서 String에 대해 equals()를 호출하면  Member와 달리 true를 리턴한다.

StringBuffer sb1 = new StringBuffer("Hello"); 는 재정의 안 됐음 ! 

다시 공부하기

3.hashCode()

hashCode()는 인스턴스마다 고유의 4바이트 정수 값을 리턴한다

 

hash value?

Data(1GB) 10억바이트인 데이터 두개를 비교하려면 10억번을 비교하면서 return해야됨; 

수학공식으로 짧은 값을 뽑아 값 비교 ! (32 byte든 머든,,) 짧은 값 = 디지털 지문= hash value

수학공식 = Hash Algorithm(종류: MD4, MD5, SHA-1, SHA-128, SHA-256)

가장 정확한 건 원본데이터 비교지만 너무 오래걸리니까 해시알고리즘으로 해시밸류를 뽑아내서 비교! 

 

해쉬코드 오버라이딩 (이클립스 source에 기본 기능활용해서)

String 클래스의 hashCode() 메서드는 인스턴스가 다르더라도 같은 문자열에 대해 같은 해시값을 리턴한다.

 

  // Map에 값을 저장하는 key로 사용할 때 hashCode()를 오버라이딩 하라!
    // 보통 값이 같은지 비교할 때 equals()와 함께 사용된다.
    // 그래서 hashCode()를 오버라이딩 할 때 equals()도 함께 오버라이딩 한다.

 

HashSet

해시코드를 저정하고 해시코드가 기존에 저장된거랑 같은건지 비교한다. 

 

List와 다른점:

LinkedList는 내가 add한 순서대로 출력됨 

HashSet은 해시값이 낮은 순서대로 저장된 그 순서대로 출력됨 - 순서를 중요시할땐 set을 사용해선 안됨 

해시값이 낮은 순서대로 생성됨 . // 해쉬값 = HashCode의 리턴값

 집합?
     => 중복 값을 저장할 수 없다.
    
     HashSet = Hash + Set
     => 값을 저장할 때 해시값을 계산하여 저장 위치를 알아낸다.
     => 집합 방식으로 목록을 다룬다. 즉 중복 값을 저장하지 않는다., 순서가 필요헙다
     => 저장 과정:
        1) equals()와 hashCode()를 호출하여 중복 여부를 검사한다.
        2) equals()의 리턴 값도 true이고 hashCode()의 리턴 값도 같을 경우,
           같은 객체로 판단하여 저장하지 않는다.
        3) 저장할 때 저장 위치는 hashCode()의 리턴 값을 사용하여 계산한다.

 

용도 => 

1. 값이 중복 저장되지 않아야한다. 

2. 순서는 중요하지 않다. 

 

해쉬코드랑 equals재정의 같이 가야함.그래야 그 객체를 온전히 해시셋에 저장할수있음.

 

// hash code 응용 - 문제 해결!
package com.eomcs.basic.ex01;


import java.util.HashSet;
import java.util.Objects;


public class Exam0151 {


static class Student {
String name;
int age;
boolean working;


public Student(String name, int age, boolean working) {
this.name = name;
this.age = age;
this.working = working;
}


@Override
public int hashCode() {
return Objects.hash(age, name, working);
}


@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
return age == other.age && Objects.equals(name, other.name) && working == other.working;
}


}


public static void main(String[] args) {
Student s1 = new Student("홍길동", 20, false);
Student s2 = new Student("홍길동", 20, false);
Student s3 = new Student("임꺽정", 21, true);
Student s4 = new Student("유관순", 22, true);


System.out.println(s1 == s2);


System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
System.out.println(s4.hashCode());
System.out.println("--------------------");


// 해시셋(집합)에 객체를 보관한다.
HashSet<Student> set = new HashSet<Student>();
set.add(s1);
set.add(s2); // 이미 s2의 해시값과 같은 객체(s1)가 들어 있기 때문에 중복을 막기 위해 s2는 저장되지 않는다.
set.add(s3);
set.add(s4);


// 해시셋에 보관된 객체를 꺼낸다.
Object[] list = set.toArray();
for (Object obj : list) {
Student student = (Student) obj;
System.out.printf("%s, %d, %s\n",
student.name, student.age, student.working ? "재직중" : "실업중");
}


// 인스턴스가 다르더라도 인스턴스의 필드 값이 같을 경우
// HashSet에 중복 저장되지 않도록 하려면,
// hashCode()와 equals() 모두 오버라이딩 하라!
// => hashCode()는 같은 필드 값을 갖는 경우 같은 해시코드를 리턴하도록 변경하고,
// => equals()는 필드 값이 같을 경우 true를 리턴하도록 변경한다.
//
}
}

Hashmap

map: key객체와 value객체로 이루어져있음

값을 저장할 때 push 

값을 꺼낼때 key를 get

equals의 리턴값(true)와 해시코드로 같은 키인지 비교한다. Hashmap에서 key로 사용하는 객체는 equals와 해시코드 재정의 필수 !!! 

// 두 키 객체 k3와 k6가 내용물이 같다 하더라도, (둘다 "haha"이다.)

// hashCode()의 리턴 값이 다르고, equals() 비교 결과도 false 라면

// HashMap 클래스에서는 서로 다른 key로 간주한다.

 

// k3와 k6는

// hashCode()의 리턴 값이 같다

// equals() 비교 결과도 true 이기 때문에

// HashMap 클래스에서는 서로 같은 key라고 간주한다.

객체를 테이블 형태로 저장

// hash code 응용 II - MyKey의 hashCode()와 equals() 오버라이딩 하기
package com.eomcs.basic.ex01;

import java.util.HashMap;


public class Exam0153 {

  static class MyKey2 {
    String contents;

    public MyKey2(String contents) {
      this.contents = contents;
    }

    @Override
    public String toString() {
      return "MyKey2 [contents=" + contents + "]";
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((contents == null) ? 0 : contents.hashCode());
      return result;
    }
    

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      MyKey2 other = (MyKey2) obj;
      if (contents == null) {
        if (other.contents != null)
          return false;
      } else if (!contents.equals(other.contents))
        return false;
      return true;
    }
  }
  public static void main(String[] args) {
    HashMap<MyKey2,Student> map = new HashMap<>();

    MyKey2 k1 = new MyKey2("ok");
    MyKey2 k2 = new MyKey2("no");
    MyKey2 k3 = new MyKey2("haha");
    MyKey2 k4 = new MyKey2("ohora");
    MyKey2 k5 = new MyKey2("hul");

    map.put(k1, new Student("홍길동", 20, false));
    map.put(k2, new Student("임꺽정", 30, true));
    map.put(k3, new Student("유관순", 17, true));
    map.put(k4, new Student("안중근", 24, true));
    map.put(k5, new Student("윤봉길", 22, false));

    System.out.println(map.get(k3));

    // 다른 key 객체를 사용하여 값을 꺼내보자.
    MyKey2 k6 = new MyKey2("haha");

    System.out.println(map.get(k6)); // OK! 값을 정상적으로 꺼낼 수 있다.
    // k3와 k6는
    // hashCode()의 리턴 값이 같다
    // equals() 비교 결과도 true 이기 때문에
    // HashMap 클래스에서는 서로 같은 key라고 간주한다.

    System.out.println(k3 == k6); // 인스턴스는 다르다.
    System.out.printf("k3(%s), k6(%s)\n", k3, k6);
    System.out.println(k3.hashCode()); // hash code는 같다.
    System.out.println(k6.hashCode()); // hash code는 같다.
    System.out.println(k3.equals(k6)); // equals()의 비교 결과도 같다.
  }
}

해시맵에 데이터를 저장할 때 주로 string이나 int를 많이 씀 

int와 string을 오버라이딩 했기때문에 

// hash code 응용 III - Wrapper 클래스를 key 객체로 사용하기
package com.eomcs.basic.ex01;

import java.util.HashMap;

public class Exam0154 {
  public static void main(String[] args) {
    // hash 코드는 Map에서 값을 저장하기 위해 key로 사용한다.
    HashMap<Integer,Student> map = new HashMap<>();
    
    // Map은 값을 저장할 때 key를 이용한다.
    // => key: 
    //    값을 저장할 위치를 계산할 때 사용한다.
    //    key 객체의 hashCode()를 호출하여 그 리턴 값을 사용하여 위치를 계산한다. 
    //    따라서 key 객체의 해시코드가 다르면 위치도 다르다.
    // => map.put(key, value);
    //
    
    // key로 사용할 객체를 준비한다.
    Integer k1 = new Integer(101);
    Integer k2 = new Integer(102);
    Integer k3 = new Integer(103);
    
    System.out.println(k1 == k2);
    System.out.println(k1 == k3);
    System.out.println(k2 == k3);

    // 위에서 준비한 key 객체를 가지고 Student 객체를 보관한다.
    map.put(k1, new Student("홍길동", 20, false));
    map.put(k2, new Student("임꺽정", 30, true));
    map.put(k3, new Student("유관순", 17, true));
    
    String str = new String("ohora");
    //map.put(str, new Student("김구", 50, true)); // 컴파일 오류!
    // HashMap 객체를 만들 때 key 타입으로 Integer를 사용하기로
    // 선언했기 때문에 다른 타입을 키로 사용할 수 없다.

    // put(Object key, Object value)
    // => put() 메서드는 key로 넘겨받은 객체에 대해 hashCode()를 호출하여 
    //    정수 값을 얻는다.
    // => 그렇게 리턴 받은 정수 값(해시 코드)를 사용하여 Student 객체를 저장할 위치를 계산한다.
    // => 그런 후 그 위치에 해당하는 배열(배열로 관리한다면)에 저장한다.
    // 
    
    
    // 다음과 같이 int를 key로 사용할 수 있다.
    // => key 값으로 int를 넘겨준다면,
    //    컴파일러가 컴파일 할 때 auto-boxing을 수행하여 Integer 객체를 만든다.
    //    그리고 그 객체를 넘겨주는 것이다.
    map.put(104 /* new Integer(104)*/, new Student("안중근", 24, true));
    map.put(105 /* new Integer(105)*/, new Student("윤봉길", 22, false));
    
    // 값을 저장할 때 사용한 key로 다시 값을 꺼내보자!
    System.out.println(map.get(k2));

    // k2와 같은 정수값을 가지는 key를 새로 생성한다.
    Integer k6 = new Integer(102);

    // k2와 같은 값을 갖는 k6로 값을 꺼내보자!
    System.out.println(map.get(k6));
    
    // 다음과 같이 k2와 k6는 분명히 다른 객체이다.
    System.out.println(k2 == k6); 
    
    // 그러나 k2와 k6는 같은 해시코드를 갖는다.
    System.out.println(k2.hashCode()); // hash code는 같다.
    System.out.println(k6.hashCode()); // hash code는 같다.
    
    // 또한 equals()의 리턴 값도 true이다.
    System.out.println(k2.equals(k6)); // equals()의 비교 결과도 같다.
    
    // 결론!
    // => k2와 k6는 다른 객체지만, 
    //    hashCode()의 리턴 값이 같고, equals()의 리턴 값이 true이기 때문에
    //    두 객체는 같은 key로 간주된다.
    
    // get(key) 실행원리!
    // => key 파라미터로 받은 객체에 대해 hashCode() 호출하여 정수 값을 얻는다.
    // => 그리고 정수 값을 이용하여 값이 저장된 위치를 찾는다.
    //    원래의 키와 같은지 equals()로 한 번 더 비교한다.
    //    만약 같다면 같은 key로 간주하여 해당 값을 꺼내 리턴한다.
    //
    // 따라서 k2로 저장한 값을 k6로 꺼낼 수 있다.
    // 
    
    // Object 클래스에서 상속 받은 hashCode()는 
    // 인스턴스 필드의 값이 같은지 따지지 않고 무조건 인스턴스마다 고유의 해시 값을 리턴한다.
    // 또한 equals()는 인스턴스 필드의 값이 같은지 비교하지 않고 같은 인스턴스인지만 따진다.
    // 
    // 그러나 Integer 클래스 등의 wrapper 클래스와 String 클래스는 
    // Object 클래스에서 상속 받은 hashCode()와 equals()를 오버라이딩 했기 때문에 
    // 인스턴스가 다르더라도 인스턴스 필드의 값이 같으면 
    // hashCode()의 리턴 값이 같고 equals()가 true를 리턴한다.
    // 
    // 그래서 wrapper 클래스와 String 클래스는 HashMap/Hashtable의 
    // key로 사용할 수 있는 것이다.
    // 실무에서도 String이나 primitive type(결국 wrapper 객체로 전환되기 때문)을 
    // key로 자주 사용한다.
    // 
    
  }

}

자바는 문자열에 대해 대소문자를 구분하기 때문에 "haha"와 "Haha"는 다른 객체로 취급한다