처음에 생각한 구현은 Stack에 최소값만 담는 방식 이었습니다.... 그리하여,...
input
["MinStack","push","push","push","push","pop","getMin","pop","getMin","pop","getMin"]
[[],[512],[-1024],[-1024],[512],[],[],[],[],[],[]]
expected
[null,null,null,null,null,null,-1024,null,-1024,null,512]
512 도 담길 수 있다.
작은 값만 stack에 담으면 왜 안 될까?
0 | 1 | 2 | 3 | |
---|---|---|---|---|
stack | 512 | -1024 | -1024 | 512 |
min | -1024 | -1024 |
새로 넣을 때, 기존 값과 비교해서 작은 값을 계속 넣습니다.
0 | 1 | 2 | 3 | |
---|---|---|---|---|
stack | 512 | -1024 | -1024 | 512 |
min | 512 | -1024 | -1024 | -1024 |
class MinStack {
Stack<Element> stack = new Stack<>();
public MinStack() {
stack = new Stack<>();
}
public void push(int val) {
if(stack.isEmpty()) {
stack.push(Element.of(val, val));
return;
}
stack.push(Element.of(val, stack.peek().getMinVal(val)));
}
public void pop() {
stack.pop();
}
public int top() {
return stack.peek().getValue();
}
public int getMin() {
return stack.peek().getMinValue();
}
private Element toArr(int stack, int minStack) {
return Element.of(stack, minStack);
}
static class Element {
private int stackVal;
private int minVal;
private Element(int stackVal, int minVal){
this.stackVal = stackVal;
this.minVal = minVal;
}
public static Element of(int stackVal, int minVal){
return new Element(stackVal, minVal);
}
public int getMinVal(int newVal){
return Math.min(this.minVal, newVal);
}
public int getValue(){
return this.stackVal;
}
public int getMinValue(){
return this.minVal;
}
}
}
처음에 생각한 구현은 Stack에 최소값만 담는 방식 이었습니다.
➜ 단순한 논리적 오류 : minStack 이 비었을 때는 stack 값 동일하게 넣어주고, 최소값을 유지하면 된다.
그래서
하지만... 오류 😮💨
Stack<Integer> minStack = new Stack<>();
으로 Integer로 객체간 비교해야 했습니다.==
는 안 되네요 ? @Test
void primitiveAndWrapper() {
Integer number = 1;
int toInt = number;
int intNumber = 1;
System.out.println(toInt);
System.out.println(toInt == number);
System.out.println(intNumber == number);
}
public class Stack<E> extends Vector<E> {
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
}
구현 코드는 GitHub
그렇게 Stack을 보게 되었 습니다. ㅎㅎ
Vector 클래스를 상속 받고 있습니다.
Queue (interface) 와 다르게 Stack은 구현 클래스로,
Vector 에는 indexOf() 위의 elementAt() 과 같이 자료구조 관련 기능들이 있었고,
Stack은 Stack으로서의 기능들을(LIFO 형태의 자료구조 방식)담당하고 있었습니다.
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
protected Object[] elementData;
/**
* The number of valid components in this {@code Vector} object.
* Components {@code elementData[0]} through
* {@code elementData[elementCount-1]} are the actual items.
*
* @serial
*/
protected int elementCount;
/**
* The amount by which the capacity of the vector is automatically
* incremented when its size becomes greater than its capacity. If
* the capacity increment is less than or equal to zero, the capacity
* of the vector is doubled each time it needs to grow.
*
* @serial
*/
protected int capacityIncrement;
public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}
return elementData(index);
}
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
public synchronized E firstElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(0);
}
그러다가 제 의식은 제네릭에 ...
Object[] elementData;
가 어떻게 반환은E
로 할 수 있지?
Java에서 배열은 공변(covariant)로 만든 이유는 무엇인가? 🙂 감사합니다.
저는 자바의 정석부터 읽어 봤습니다.
제네릭스가 제한되는 상황이 크게 2가지가 있습니다.
사실 저는 외우진 않고, IDE에서 알려주면 '아, 맞다맞다' 하면서 static 등 고쳤습니다.
(실수도 많이 하면 빨리 고칠 수 있고, 그게 손이 동작하기 전에 고치면 아는 듯이 보이고... 말로 표현하지 못하는 어떤 Dreams Come True )
이 참에 정리라도 해보자 싶었습니다.
먼저 제 코드의 잘못을 IDE를 통해서 알 수 있었습니다. 컴파일 에러!
제네릭은 컴파일 시 타입 체크와 형변환을 통해 타입 안정성과 편리성을 제공
class Stack<T> {
T[] elemendts;
T[] extendsArray() {
T[] arr = new T[elements.length * 2];
return arr;
}
// Vector 클래스 예시
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
public synchronized E firstElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(0);
}
}
위에서 T[] elemendts;
는 괜찮지만, new T[elements.length * 2];
에서 컴파일 에러 발생합니다.
Object[] objArr = new Integer[10];
는 컴파일 에러가 발생하지 않습니다. 컴파일하면서 Integer로 형변환 됐겠지 싶은데요.objArr[1] = "test";
처럼 컴파일 과정에서는 다른 타입으로 대입도 괜찮지만, 실행 하면 에러 발생 합니다. - 타입 안정성 X Object[] objArr = new Integer[10];
objArr[0] = 1;
objArr[1] = "test"; // 실행 후 ArrayStoreException
System.out.println(objArr[0]);
System.out.println(objArr[1]);
Vector 클래스의 return elementData(0);
은 Obejct 타입이고 메서드는 E가 반환타입 입니다.
정리
제네릭은 컴파일을 통해 형변환을 대신 해주고, 그러기 위해서 타입 체크를 합니다.
다만, 배열은 제네릭 타입의 선언은 되지만 new 연산자로 생성은 불가합니다.
public class TestGeneric<T> {
private T[] arr;
public TestGeneric() {
this.arr = (T[])new Object[10];
}
public T testGeneric() {
// arr = (T[])new Integer[10];
// arr[0] = (T)"문자열 넣기";
arr = (T[])new Object[10];
return (T)arr;
}
@Test
void testGenericArr() {
TestGeneric<String> stringTest = new TestGeneric<>();
String s = stringTest.testGeneric();
System.out.println(s);
}
}
static Class<?> defineClass(String name, byte[] bytes, int off, int len,
final ClassLoader parentClassLoader)
{
ClassLoader newLoader = AccessController.doPrivileged(
new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return new DelegatingClassLoader(parentClassLoader);
}
});
return unsafe.defineClass(name, bytes, off, len, newLoader, null);
}
T inst = (T) ca.newInstance(initargs);
가 진행 됩니다. @CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, clazz, modifiers);
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
protected Object[] elementData;
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public class TestGeneric<T> {
private Object arr;
public TestGeneric(T element) {
this.arr = element;
}
public T toTestGeneric() {
return (T)arr;
}
@Test
void testGenericArr() {
Integer[] expected = {1, 2, 3, 4};
TestGeneric<Integer[]> arrTest = new TestGeneric(expected);
Integer[] actual = arrTest.toTestGeneric();
}
}
그리고 Vector 가 상속받은 RandomAccess 인터페이스