#equals() in the Class Object
Object 클래스는 두 개체가 동일한 개체인 경우에만 true를 반환하는 public equals() 메서드를 구현한다
x.equals(y) == true iff x and y are (references to) the same object
일부 하위 클래스의 경우, 특히 equality comparison의 개념이 실질적으로 의미가 없는 유형에 적합하다
#Identity vs Equality
identity:
동일한 것의 관계.
x와 y가 동일한 객체인 경우에만 x는 y와 동일하다.
Java에서는 == 연산자에 의해 테스트된다.
equality:
동일한 값를 갖는 관계
x는 x와 y가 어떤 유용한 의미에서 동등한 함량을 갖는 경우에만 y와 같다.
x와 y는 동일한 객체일 수도 있고 아닐 수도 있다.
자바에서, 이것은 equals() 방법으로 테스트된다
equals 메서드는 null이 아닌 개체 참조에 equivalence relation를 구현한다. equals()는 다음과 같은 특성을 가지고 있다
reflexive: non-valuary 기준 값 x에 대해 x.valu(x)는 true를 반환해야 한다
symmetric: null이 아닌 reference x와 y에 대해, x.equals(y)는 y.eqauals(x)가 true를 반환하는 경우에만 true를 반환해야 한다
transitive:null이 아닌 referencex와 y와 z에 대해, x.equals(x)가 true를 반환하고 y.eqauals(y)가 true를 반환하는 경우 x.equals(z)도 true를 반환해야 한다
또한:
Equals는 일관성이 있다: 임의의 null이 아닌 reference값 x와 y에 대해, 객체에 대한 동등한 비교에 사용되는 정보가 수정되지 않는다면 x.equals(y)의 여러 호출은 일관되게 참 또는 거짓을 반환한다
임의의 non-null reference값 x에 대해 x.equals(null)은 false를 반환해야 한다
#User-defined 클래스
public class FileEntry {
public Long offset; // offset of record in file
public String record; // record contents
public FileEntry(long offset, String data) {
this.offset = offset;
this.record = data;
}
. . .
}
다음은 파일에서 레코드에 액세스하는 프로그램에서 사용할 수 있는 클래스이다.
public class FileEntry {
. . .
public boolean equals(Object other) {
// Make sure there really IS another object;
// something never equals nothing...
if ( other == null ) return false;
// Make sure the other object is of the correct type:
if ( !this.getClass().equals(other.getClass()) )
return false;
. . .
}
}
#Specialized equals() Features
우리는 이러한 유형에 대해 equality가 무엇을 의미하는지에 대한 합리적인 정의를 구현할 필요가 있다
public class FileEntry {
. . .
public boolean equals(Object other) {
. . .
// Get a reference of the appropriate type:
FileEntry o = (FileEntry) other;
// Perform the type-specific test for equality:
return ( this.offset.equals(o.offset) );
}
}
public class FileEntry {
. . .
public boolean equals(Object other) {
// Make sure there really IS another object:
if ( other == null ) return false;
// Make sure it's of the correct type:
if ( !this.getClass().equals(other.getClass()) )
return false;
// Get a reference of the appropriate type:
FileEntry handle = (FileEntry) other;
// Perform the type-specific test for equality:
return ( this.offset.equals(handle.offset) );
}
}
코드가 equals를 호출하는지 알고싶을 때는 다음과 같이 디버깅하는것도 방법이다
public class FileEntry {
. . .
public boolean equals(Object other) {
System.out.println("Call made to FileEntry.equals()");
. . .
}
}
#Primitive Java Generic Class
버퍼 풀은 응용 프로그램의 성능을 향상시키기 위해 디스크 파일에서 검색된 레코드를 캐시하는 데이터 구조이다.
일반적으로 풀은 디스크 파일에서 레코드가 발생하는 오프셋과 함께 일종의 데이터 개체를 저장한다.
데이터 개체는 거의 모든 유형일 수 있으며 중복 클래스를 쓰는 것을 싫어하므로 파일 오프셋과 데이터 개체를 캡슐화하는 단일 클래스를 작성하려고 한다고 하자.
public class BPEntry {
private Long Offset;
private Object Value; // could be anything!
public BPEntry(Long offset, Object value) {
Offset = offset;
Value = value;
}
다음 코드와 같이 짰을때 무엇이 문제일까?
String 객체로 표현될 수 있는 레코드 파일이 있다고 가정하자
...
Long offset = raf.getFilePointer();
String record = raf.readLine();
BPEntry bp = new BPEntry(offset, record); // OK
하지만 우리가 다음과 같은 실수를 했다고 가정하자
BPEntry bp = new BPEntry(offset, offset); // ??
Long은 Object를 사용하는 Object의 하위 유형이므로 컴파일타임에서 모든 유형 검사가 제거되므로 컴파일 및 실행된다
#간단한 자바 Generic 클래스
public class BPEntry<T> {
private Long Offset;
private T Value;
public BPEntry(Long offset, T value) {
Offset = offset;
Value = value;
}
Long offset = raf.getFilePointer();
String record = raf.readLine();
BPEntry<String> bp = new BPEntry(offset, record); // OK
bp = new BPEntry<String>(offset, offset); // error!!
#개선된 버전
현재 버전에서는 오프셋 필드에 Longs를 사용하도록 제한하지만, 파일이 작으면 오프셋이 Integer(정수)(및 메모리 저장)가 될 수 있다.
다음과 유연성을 향상시킬 수 있다
public class BPEntry<K, T> {
private K Offset;
private T Value;
public BPEntry(K offset, T value) {
Offset = offset;
Value = value;
}
그러나 이제 부주의한 사용자가 오프셋에 대해 숫자가 아닌 유형을 지정할 수 있다
bp = new BPEntry<String, Integer>(record, offset);
#최종 버전
유형 매개 변수(Java 용어로 바운드)에 제한을 둘 수 있다
public class BPEntry<K extends Number, T> {
private K Offset;
private T Value;
public BPEntry(K offset, T value) {
Offset = offset;
Value = value;
}
이제 사용자는 오프셋에 숫자가 아닌 유형을 사용할 수 없지만 사용자는 여전히 비정수 유형을 지정할 수 있다
#Generic과 equals() 메소드
public class BPEntry<K extends Number, T> {
. . .
public boolean equals(Object other) {
if ( other == null ) return false;
if ( !this.getClass().equals(other.getClass()) ) {
return false;
}
BPEntry<K, T> obj = ( BPEntry<K, T> ) other;
return ( this.Key.equals(obj.Key) );
}
#간단한 Generic 메소드
contains() 메서드를 사용하여 모든 유형의 개체를 포함하는 배열을 검색할 수 있다
public static <T> boolean contains( T[] array, T x) {
for ( T value : array ) {
if ( x.equals(value) )
return true;
}
return false;
}
Integer[] array = new Integer[10];
for (int pos = 0; pos < 10; pos++) {
array[pos] = pos * pos;
}
if ( contains( array, new Integer(15) ) ) {
System.out.println("Found value in array.");
}
else {
System.out.println("Could not find value in array.");
}
#Type Bound의 필요성
public static <T> T findMax( T[] array) {
int maxIndex = 0;
for ( int i = 1; i < array.length; i++) {
if ( array[i].compareTo(array[maxIndex]) > 0 )
maxIndex = i;
}
return array[maxIndex];
}
D:\Code\Generics>javac exFindMax1.java
exFindMax1.java:20: cannot find symbol
symbol : method compareTo(T)
location: class java.lang.Object
if ( array[i].compareTo(array[maxIndex]) > 0 )
^
1 error
문제점: 자바 컴파일러가 일반 타입 T가 루프 내에서 테스트에 사용된 메소드 compareTo()를 구현하는 실제 타입을 나타낼 것이라는 것을 알 방법이 없다
#Type Bound 적용
public static <T extends Comparable<T> > T findMax( T[] array) {
int maxIndex = 0;
for ( int i = 1; i < array.length; i++) {
if ( array[i].compareTo(array[maxIndex]) > 0 )
maxIndex = i;
}
return array[maxIndex];
}
다음과 같이 타입 매개변수 T를 비교 가능한 인터페이스를 구현하는 타입으로 제한하여 비교 대상() 호출이 유효함을 보장한다
하지만 이것에도 문제점이 있다: Shape클래스가 comaprable<Shape>를 implement한다고 가정하자, 또한 Square가 Shape를 extend한다고 가정하자. 그렇다면 우리는 Square가 Comparable<Shape>를 implement한다는 것을 알게된다.
그러면 필요한 메소드가 실제로 사용 가능하더라도 Square는 위에서 사용한 조건을 만족시키지 못할 것이다.
그렇기 때문에 우리는 T가 그 자체로 Comparable()을 구현하는 슈퍼 클래스에서 파생될 수 있도록 하는 제한이 필요하다
public static <T extends Comparable<? super T> > T findMax( T[] array) {
. . .
}
Wildcards:
기호 '?'는 와일드카드이다.
와일드카드는 임의 클래스를 나타내며 제한이 뒤따른다.
이 경우 제한은 임의 클래스가 T의 슈퍼 클래스여야 한다는 것이다.
따라서, 이것은 T가 Comparable<X>인 기본 클래스 X를 확장해야 한다고 나타낸다.
그래서, T is-a Comparable.
그래서, T는 필요한 메소드를 implement한다
#Limitation: Type Erasure
컴파일러는 type erase라고 불리는 기술로 일반적이고 매개 변수화된 유형을 번역한다.
기본적으로 형식 매개 변수 및 형식 인수와 관련된 모든 정보를 삭제한다
예를 들어 매개 변수화된 유형 List<String>은 소위 원시 유형인 List 유형으로 변환된다
매개 변수화된 유형 List<Long>에서도 동일한 현상이 발생하며 바이트 코드에서도 List로 표시된다
형식 삭제로 변환한 후 형식 매개 변수 및 형식 인수에 대한 모든 정보가 사라졌다
결과적으로 동일한 제네릭 유형의 모든 인스턴스는 동일한 런타임 유형, 즉 원시 유형을 공유한다
#Type Erasure의 결과
형식 삭제의 사용은 형식적인 자바 제네릭의 유용성을 제한한다. 예를들면
public class Foo<T> {
private T[] array; // fine
public Foo(int Sz) {
array = new T[Sz]; //Illegal
}
}
Illegal:
코드가 컴파일되면 T는 그 바운드(단순히 객체일 수 있음)로 대체된다.
컴파일러는 또한 new로부터의 반환 값에 대한 타입 캐스트를 자동으로 생성한다.
개체[]가 T[]가 아니기 때문에 타입 캐스팅이 실패한다.
다음과 같이 코드를 구성하면 이문제를 고칠 수 있다:
public class HashTable<T extends Hashable> {
T[] Table;
...
public HashTable(int Sz, probeOption Opt) {
...
Table = (T[]) new Hashable[Sz]; //적절한 "콘크리트" 유형의 배열 할당
//generic type으로 타입캐스팅
//하지만 컴파일러가 윗줄의 타입캐스트하는 코드가 안전할 것이라는 것을 알 수 없기 때문에
//경고를 생성할 수 있다
'Computer Science > Data Structures & Algorithms' 카테고리의 다른 글
[Lecture 7] 점근(asymptotic) 표기법/ Big-O 표기법 (1) | 2023.01.06 |
---|---|
[Lecture 6] Algorithm Analysis / 알고리즘 분석 (1) | 2023.01.05 |
[Lecture 4] Hash Function / 해시 함수 (1) | 2023.01.03 |
[Lecture 1] BST / Binary Search Tree / 이진탐색트리 (0) | 2023.01.01 |
[Lecture 0 - I] Java File I/O (1) | 2023.01.01 |