공변성 : 한 변수가 변하면 다른 변수도 변하는 성질.
제너릭(Generic)은 자바에서 타입 안정성을 확보하고 재사용성을 높이기 위해 도입된 기능입니다. 제너릭은 클래스, 인터페이스, 메서드 등에서 사용할 수 있으며, 컴파일 시에 타입 체크를 수행하여 타입 안정성을 보장합니다.
제너릭을 사용함으로써 다음과 같은 이점을 얻을 수 있습니다
1 . 타입 안정성(Type Safety)
제너릭은 컴파일 시에 타입 체크를 수행하여 타입 안정성을 보장합니다. 즉, 잘못된 타입 사용으로 인한 런타임 에러를 사전에 방지할 수 있습니다. 타입 체크를 통해 컴파일러는 프로그램에서 발생할 수 있는 타입 관련 오류를 찾아내고 경고나 에러를 발생시킵니다.
2 . 재사용성(Reusability)
제너릭을 사용하면 타입을 일반화하여 재사용 가능한 코드를 작성할 수 있습니다. 타입 매개변수를 사용하여 클래스, 인터페이스, 메서드 등을 정의하고, 실제 사용 시에 타입을 지정함으로써 코드의 재사용성을 높일 수 있습니다. 이로 인해 코드의 중복을 줄이고 유지 보수성을 향상시킬 수 있습니다.
3 . 타입 변환 감소(Decreased Type Casting)
제너릭을 사용하면 타입 매개변수를 통해 원하는 타입을 직접 사용할 수 있습니다. 이로써 타입 변환을 하지 않고도 타입에 안전하게 접근할 수 있으며, 코드의 가독성을 높일 수 있습니다.
4 . 컬렉션 안정성(Collection Safety)
제너릭은 컬렉션 프레임워크에서 특히 유용하게 사용됩니다. 컬렉션 클래스에 제너릭을 적용하면 컴파일러가 컬렉션에 잘못된 타입의 요소를 추가하는 것을 방지해줍니다. 이로써 컬렉션에서 발생할 수 있는 런타임 에러를 사전에 방지할 수 있습니다.
1. 클래스 또는 인터페이스에서 제너릭을 사용하는 경우
class(interface) 이름<타입 매개변수> {
// 코드
}
class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
2. 제너릭 타입의 변수를 선언하는 경우
타입<타입 매개변수> 변수이름;
위의 문법에서 타입은 실제 타입으로 대체될 타입을 나타내며, <타입 매개변수>는 사용할 타입 매개변수를 지정합니다. 변수이름은 변수의 식별자입니다.
예를 들어, Box 클래스를 제너릭으로 선언한 경우 다음과 같이 제너릭 타입의 변수를 선언할 수 있습니다.
Box<Integer> integerBox; // Integer 타입을 사용하는 Box 변수
Box<String> stringBox; // String 타입을 사용하는 Box 변수
위의 예제에서 Box<Integer>는 integerBox 변수가 Box 클래스의 인스턴스를 참조하며, Box<String>은 stringBox 변수가 Box 클래스의 인스턴스를 참조합니다. 이러한 변수 선언을 통해 제너릭 타입에 대한 타입 안정성을 확보할 수 있습니다.
3. 메서드에서 제너릭을 사용하는 경우
<타입 매개변수> 반환타입 메서드이름(매개변수...) {
// 코드
}
위의 문법에서 <타입 매개변수>는 실제 타입으로 대체될 수 있는 임의의 이름입니다. 보통 T, E, K, V 등의 알파벳을 사용합니다. 하지만 실제 타입 이름과 동일하게 명시하는 것도 가능합니다.
제너릭을 사용할 때 타입 매개변수는 클래스나 메서드 내부에서 여러 곳에서 사용될 수 있습니다.
이러한 타입 매개변수는 해당 위치에서 실제 타입으로 대체됩니다.
예를 들어, 제너릭 클래스에서 T를 타입 매개변수로 사용하면, 클래스 내부의 필드, 메서드 매개변수, 반환 타입 등에서 T가 실제 타입으로 대체됩니다.
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
위의 예제에서 Box에서의 T는 타입 매개변수로 사용되며, content 필드의 타입과 setContent, getContent 메서드의 매개변수 및 반환 타입에서 사용될 때 실제 타입으로 대체됩니다.
class Util {
public static <T> T getLastElement(T[] array) {
if (array != null && array.length > 0) {
return array[array.length - 1];
}
return null;
}
}
// 사용 예제
String[] stringArray = {"Apple", "Banana", "Cherry"};
String lastElement = Util.getLastElement(stringArray);
System.out.println(lastElement); // "Cherry"
4. 변수를 선언하고 실제 컬렉션 타입의 클래스를 동적으로 생성하고 대입하는 경우
타입<타입 매개변수> 변수이름 = new 실제타입<>();
위의 문법에서 타입은 실제 타입으로 대체될 컬렉션 타입을 나타내며, <타입 매개변수>는 사용할 타입 매개변수를 지정합니다. 변수이름은 변수의 식별자입니다. new 실제타입<>()는 실제 타입으로 생성된 객체를 생성자를 통해 생성하여 변수에 할당하는 부분입니다.
예를 들어, ArrayList 클래스를 제너릭으로 사용하여 동적으로 생성하고 대입하는 예제를 살펴보겠습니다:
ArrayList<String> stringList = new ArrayList<>();
stringList.add("Apple");
stringList.add("Banana");
stringList.add("Cherry");
System.out.println(stringList); // 출력: [Apple, Banana, Cherry]
위의 예제에서 ArrayList은 stringList 변수가 ArrayList 클래스의 인스턴스를 참조하며, new ArrayList<>()를 통해 ArrayList 객체를 동적으로 생성하여 변수에 할당하고 있습니다. 이후에 stringList 변수를 사용하여 문자열 요소를 추가하고 출력하는 예제입니다.
동적으로 생성된 컬렉션 타입의 클래스를 활용하여 다양한 작업을 수행할 수 있습니다. 위의 예제에서는 ArrayList를 사용하였지만, 다른 컬렉션 타입인 LinkedList, HashSet 등도 동일한 방법으로 활용할 수 있습니다.
5. 제한된 제너릭 타입
class MathUtil {
public static <T extends Number> double sum(T[] numbers) {
double total = 0.0;
for (T number : numbers) {
total += number.doubleValue();
}
return total;
}
}
// 사용 예제
Integer[] integers = {1, 2, 3};
double sum = MathUtil.sum(integers);
System.out.println(sum); // 6.0
위 예제에서 T 타입은 Number 클래스를 상속한 타입으로 제한됩니다.
타입 소거(Type Erasure)는 자바에서 제너릭(Generic)을 구현하는 방식 중 하나입니다. 자바에서 제너릭은 컴파일 시에 타입 체크를 수행하고, 타입 안정성을 보장하기 위해 사용됩니다. 하지만 제너릭 타입은 런타임 시에는 타입 정보가 소거되고, 일반적인 Object 타입으로 처리됩니다.
타입 소거는 컴파일러가 제너릭 코드를 일반적인 코드로 변환하는 과정입니다. 타입 소거의 주요 특징은 다음과 같습니다:
타입 매개변수 제거 : 제너릭 타입에서 사용되는 타입 매개변수는 타입 소거 시에 제거됩니다. 타입 매개변수는 실제 타입으로 대체되어 코드가 변환됩니다.
타입 경계(Bound) 제거 : 제너릭 타입에서 타입 경계가 정의되어 있는 경우, 타입 경계도 타입 소거 시에 제거됩니다. 타입 경계는 원시 타입으로 변환됩니다.
다형성 유지 : 제너릭 타입의 다형성은 타입 소거를 통해 유지됩니다. 타입 소거 후에도 제너릭 타입이 상속 계층 구조에 따라 다른 타입으로 캐스팅되는 것이 가능합니다.
다음은 타입 소거의 예시 코드입니다
public class Box<T> { // 다른 대문자(Integer) 아무거나 가능.
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
public class Main {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.setContent(10);
Integer value = integerBox.getContent();
System.out.println(value);
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String strValue = stringBox.getContent();
System.out.println(strValue);
}
}
위의 코드에서 Box 클래스는 제너릭 타입 T를 사용하는 예시입니다.
하지만 실제로는 타입 소거가 일어나므로 Box 클래스의 모든 T는 컴파일러가 Object 타입으로 처리됩니다.
컴파일러는 integerBox와 stringBox의 setContent 메서드에서 전달되는 값의 타입을 체크하여 적절한 변환 코드를 생성합니다.
런타임 시에는 getContent 메서드를 호출할 때 형변환이 발생하여 정확한 타입으로 변환됩니다.
즉, 제너릭 코드는 컴파일 시에는 타입 체크를 수행하고, 타입 소거를 통해 런타임 시에는 타입 정보가 소거되고 일반적인 Object 타입으로 처리됩니다.
타입 경계를 사용함으로써 특정 타입에 대한 제약 조건을 부여할 수 있고, 보다 구체적인 타입 체크를 수행할 수 있습니다.
타입 경계의 종류는 다음과 같습니다
상위 타입 경계 (Upper Bound) : extends 키워드를 사용하여 특정 클래스나 인터페이스의 상위 타입으로 제한합니다. 예를 들어, 는 Number 클래스 또는 Number의 하위 클래스들로 타입 매개변수 T를 제한합니다.
하위 타입 경계 (Lower Bound) : super 키워드를 사용하여 특정 클래스나 인터페이스의 하위 타입으로 제한합니다. 하위 타입 경계는 제너릭 메서드에서 super 키워드를 사용하여 특정 타입의 슈퍼 타입으로 제한하는 경우에 주로 사용됩니다.
타입 경계의 사용 예시를 살펴보겠습니다:
1. 상위 타입 경계 사용 예시
public class Box<T extends Number> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
public class Util {
public static <T extends Comparable<T>> T getMax(T[] array) {
if (array == null || array.length == 0) {
return null;
}
T max = array[0];
for (T item : array) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
}
위의 예시 코드에서 Box 클래스의 타입 매개변수 T는 Number 클래스 또는 Number의 하위 클래스로 제한됩니다. 이로써 Box 객체를 생성할 때 T에는 숫자 타입만 사용할 수 있게 됩니다.
또한, Util 클래스의 제너릭 메서드 getMax에서는 상위 타입 경계를 사용하여 T를 Comparable 인터페이스를 구현한 타입으로 제한합니다.
이로써 getMax 메서드는 Comparable 인터페이스를 구현한 타입의 배열에서 최댓값을 찾는 기능을 제공할 수 있습니다.
2. 하위 타입 경계
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
public class Util {
public static void addNumbers(List<? super Integer> numbers) {
numbers.add(10);
numbers.add(20);
numbers.add(30);
}
}
와일드카드(Wildcard) 타입
와일드카드 타입은 ?를 사용하여 특정 타입을 미리 정의하지 않고, 알 수 없는 타입을 나타냅니다.
와일드카드는 제너릭 타입을 사용할 때 다양한 타입을 수용하기 위한 유연성을 제공합니다.
와일드카드 상한(? extends 타입)과 와일드카드 하한(? super 타입)을 사용하여 특정 타입의 상위 타입 또는 하위 타입을 제한할 수 있습니다.
위의 코드에서 Util 클래스의 addNumbers 메서드는 List 인터페이스를 구현한 객체를 매개변수로 받습니다.
이때 ? super Integer는 Integer 클래스의 하위 타입으로 제한된 리스트를 받는 것을 의미합니다.
addNumbers 메서드를 호출할 때 List, List, List와 같은 리스트를 전달할 수 있지만, List과 같이 Integer의 상위 타입인 리스트는 전달할 수 없습니다. 이는 addNumbers 메서드에서 해당 리스트에 Integer 객체를 추가하므로, 그 이상의 하위 타입만을 받을 수 있기 때문입니다.
하위 타입 경계를 사용하면 제너릭 타입 매개변수를 특정 클래스의 하위 타입으로 제한하여 유연한 코드를 작성할 수 있습니다. 이를 통해 런타임 시에 타입 안정성을 유지하면서도 다양한 타입의 객체를 다룰 수 있습니다.
이처럼 타입 경계를 사용함으로써 제너릭 타입의 유연성과 타입 안정성을 확보할 수 있으며, 특정 타입에 대한 제약 조건을 부여할 수 있습니다.
제너릭 타입의 상속과 인터페이스 구현
제너릭 클래스는 다른 제너릭 클래스를 상속하거나, 제너릭 인터페이스를 구현할 수 있습니다. 상속 또는 구현 시에도 타입 매개변수를 유지하거나 구체적인 타입으로 지정할 수 있습니다.
아래는 제너릭 타입의 상속과 관련된 코드 예제입니다:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// Box 클래스를 상속받는 자식 클래스
public class ExtendedBox<T> extends Box<T> {
// 추가적인 기능이나 메서드를 구현할 수 있음
}
// Box 클래스를 상속받은 자식 클래스
class GenericBox<T> extends Box<T> {
}
public class Main {
public static void main(String[] args) {
// Box 클래스의 상속 예제
Box<Integer> integerBox = new Box<>();
integerBox.setContent(10);
System.out.println(integerBox.getContent()); // 10
ExtendedBox<String> stringBox = new ExtendedBox<>();
stringBox.setContent("Hello");
System.out.println(stringBox.getContent()); // Hello
// GenericBox 클래스의 예제
GenericBox<Double> doubleBox = new GenericBox<>();
doubleBox.setContent(3.14);
System.out.println(doubleBox.getContent()); // 3.14
}
}
public interface Collection<E> extends Iterable<E> {
}
이와 같이 제너릭 타입은 상속과 인터페이스 구현을 통해 다른 제너릭 클래스나 인터페이스를 확장하고 활용할 수 있습니다.
배열의 공변성(covariance)은 자바 배열의 특징 중 하나입니다. 이는 배열이 하위 타입으로 형변환될 수 있는 성질을 가리킵니다.
공변성은 배열이 다른 타입의 배열로 할당될 수 있는 것을 의미합니다. 이는 배열의 타입이 배열 요소의 타입의 하위 타입이면 형변환이 가능하다는 것을 의미합니다.
예를 들어, 다음과 같은 코드를 살펴봅시다
String[] stringArray = new String[5];
Object[] objectArray = stringArray; // 배열의 공변성
위의 코드에서 stringArray는 String 타입의 배열입니다.
그리고 objectArray는 Object 타입의 배열로 stringArray를 참조하도록 할당되었습니다.
이러한 할당이 가능한 이유는 배열이 공변성을 가지기 때문입니다.
공변성은 컴파일 시 타입 안전성을 보장하기 위해 적용되는 규칙입니다. 배열이 공변성을 가지기 때문에 하위 타입으로 형변환될 수 있어도 배열의 타입과 요소의 타입은 여전히 일치해야 합니다.
즉, 배열이 String[] 타입으로 선언되었다면, 할당받는 배열도 String[] 또는 Object[]와 같이 배열의 요소 타입이 일치하거나 상위 타입이어야 합니다.
공변성은 배열의 할당과 배열 복사와 같은 상황에서 유용하게 사용될 수 있습니다. 그러나 배열의 공변성은 런타임에 타입 안정성을 보장하지 않습니다. 따라서, 배열을 사용할 때는 주의해야 합니다. 배열을 사용하는 동안 형변환 문제나 배열 요소의 타입 불일치로 인한 문제가 발생하지 않도록 해야 합니다.
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog[] dogs = new Dog[3];
dogs[0] = new Dog();
dogs[1] = new Dog();
dogs[2] = new Dog();
Animal[] animals = dogs; // 배열의 공변성
animals[0].makeSound(); // Dog barks
animals[1].makeSound(); // Dog barks
animals[2].makeSound(); // Dog barks
}
}
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
myNumber[0] = 3.14; // heap
힙 오염(Heap Pollution)은 제네릭 타입 시스템에서 발생할 수 있는 타입 안전성 위반을 가리키는 용어입니다. 제네릭을 사용하여 타입을 매개변수화하면 컴파일러가 타입 체크를 수행하여 타입 안정성을 유지할 수 있지만, 몇 가지 상황에서는 힙 오염이 발생할 수 있습니다.
힙 오염은 주로 배열, 가변 인자(varargs), 원시 타입(raw type)과 같은 상황에서 발생할 수 있습니다. 힙 오염이 발생하면 제네릭을 사용하는 타입 안정성이 깨지고, 런타임 시 ClassCastException과 같은 예외가 발생할 수 있습니다.
예를 들어, 다음과 같은 코드를 살펴봅시다
List<String>[] stringLists = new List[2];
List<Integer> intList = List.of(1, 2, 3);
Object[] objArray = stringLists;
objArray[0] = intList;
String str = stringLists[0].get(0); // ClassCastException 발생
위의 코드에서 stringLists는 List 타입의 배열로 선언되었습니다.
그러나 실제로는 List 타입의 배열을 할당했습니다. objArray는 Object 배열로 선언되고, stringLists를 참조하도록 했습니다. 그리고 intList를 objArray[0]에 할당했습니다.
이제 stringLists의 첫 번째 요소에는 List가 들어가 있습니다.
이후 stringLists에서 값을 가져올 때 ClassCastException이 발생합니다.
왜냐하면 배열은 공변성을 가지기 때문에 컴파일 시에는 타입 안전성이 보장되지만, 런타임 시에는 타입 정보가 손실되어 제대로된 타입 체크를 할 수 없기 때문입니다.
힙 오염을 방지하기 위해서는 배열 대신 제네릭 컬렉션을 사용하거나, 경고를 억제하는 @SuppressWarnings("unchecked") 애너테이션을 사용하여 해당 부분의 경고를 무시할 수 있습니다.
또한, 가능하면 제네릭 타입을 사용하여 명시적인 타입 체크를 수행하고, 힙 오염이 발생하지 않도록 주의해야 합니다.
Raw type(원시 타입)은 제네릭 타입에서 타입 매개변수를 사용하지 않고, 원래의 타입으로 사용되는 것을 말합니다. 제네릭 타입은 타입 안정성과 코드의 가독성을 향상시키기 위해 도입되었지만, 원시 타입은 이러한 이점을 잃을 수 있습니다.
원시 타입은 제네릭 타입에서 타입 매개변수를 생략하고 사용한 경우를 말합니다. 예를 들어, 다음과 같은 제네릭 클래스가 있다고 가정해봅시다
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
위의 Box 클래스는 제네릭 타입으로 선언되었습니다. 그러나 원시 타입인 Box로 사용할 수도 있습니다
Box box = new Box();
box.setValue("Hello");
String value = (String) box.getValue();
위의 코드에서 Box는 원시 타입으로 사용되었습니다. 타입 매개변수 T를 생략하였기 때문에 컴파일러는 타입 체크를 수행하지 않고 경고를 발생시킵니다. 또한, 값을 가져올 때 타입 캐스팅이 필요합니다.
원시 타입을 사용하는 것은 타입 안정성을 잃을 수 있으며, 컴파일러가 제공하는 타입 체크와 형변환의 이점을 누리지 못합니다. 따라서 가능하면 원시 타입 대신 제네릭 타입을 사용하여 타입 안전성과 가독성을 유지하는 것이 좋습니다.
원시 타입은 Java에서 하위 호환성을 유지하기 위해 남아있는 개념이지만, 새로운 코드에서는 제네릭 타입을 적극 활용하는 것이 권장됩니다.
제너릭 타입의 배열 생성은 일부 제약 사항이 있습니다. 제너릭 배열 생성은 컴파일러 경고를 발생시키는데, 배열 생성 시에 타입 안전성을 보장할 수 없기 때문입니다. 이를 해결하기 위해서는 배열 대신 컬렉션을 사용하거나 타입 캐스팅을 수행해야 합니다.
제네릭 배열의 생성에 관련된 한계는 Java에서의 제네릭 타입과 배열의 동작 방식의 불일치로 인해 발생합니다. Java에서는 다음과 같은 제네릭 배열 생성을 허용하지 않습니다
List<String>[] stringLists = new List<String>[10]; // 컴파일 에러
위의 코드에서는 List 타입을 요소로 갖는 배열을 생성하려고 시도했는데, 컴파일러는 이를 허용하지 않습니다.
이는 Java가 제네릭 타입의 타입 안전성을 보장하기 위해 배열과 제네릭의 조합을 제한하기 때문입니다.
제네릭 배열 생성을 허용하지 않는 이유는 다음과 같습니다:
Object[] objArray = new List<String>[10]; // 가정하고 허용된다면
objArray[0] = new ArrayList<Integer>(); // 실행 중 오류 발생
List<String> stringList = objArray[0]; // 타입 불일치
위의 코드에서 배열은 List의 배열이라고 선언되었지만, 실제로는 ArrayList의 인스턴스가 저장되어 있습니다. 이는 타입 안정성을 위반하게 됩니다.
이러한 이유로 Java에서는 제네릭 배열 생성을 허용하지 않으며, 컴파일러에서는 이에 대한 경고를 발생시킵니다.
이러한 한계를 우회하기 위해서는 배열 대신 컬렉션(List, Set 등)을 사용하거나, 제네릭 메서드에서 T[]와 같은 형태로 배열을 리턴하는 방법을 사용할 수 있습니다.
<T> T[] toArray(T[] a);
제네릭 메서드에서 T[]라는 리턴 타입을 정의하는 것은 제네릭 배열을 리턴하겠다는 의미입니다.
는 메서드에 사용되는 타입 매개변수를 선언하는 부분이고, T[]는 해당 타입 매개변수 T를 요소로 갖는 배열을 의미합니다.
즉, 메서드가 호출되면 실제로는 T에 해당하는 타입으로 구체화된 배열을 리턴하게 됩니다.
이것은 제네릭 배열의 생성에 관련된 한계를 극복하기 위한 방법 중 하나입니다. Java에서는 제네릭 배열을 직접적으로 생성하는 것이 불가능한데, 컴파일러에서 해당 경고를 발생시킵니다.
따라서, T[]와 같은 배열을 리턴하는 것은 실제로는 컴파일러가 내부적으로 타입 캐스팅과 배열 생성을 수행하여 제네릭 배열의 한계를 우회하는 방식입니다.
하지만, 주의해야 할 점은 T에 대한 구체적인 타입이 런타임에 알려지지 않을 경우, 컴파일러가 타입 안전성을 보장할 수 없다는 점입니다.
이러한 경우 경고 메시지가 발생할 수 있으며, 이를 경고 없이 처리하기 위해서는 @SuppressWarnings("unchecked") 어노테이션을 사용하여 경고를 억제할 수 있습니다.
즉, T[]는 제네릭 메서드에서 배열을 리턴하는 타입을 정의하기 위한 방법이며, 컴파일러의 제네릭 배열 한계를 우회하기 위해 사용됩니다.
// 클래스
public class ArrayList<E> {
}
// 인터페이스
public interface Collection<E> {
}
transient Set<K> keySet;
transient Collection<V> values;
Iterator<E> iterator(); // 인터페이스의 추상 메서드
// 클래스 컨스트럭터
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
// 객체의 메서드
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}