관리 메뉴

HAMA 블로그

스칼라 강좌 (8) - ArrayBuffer 와 Vector 그리고 공변성 본문

Scala

스칼라 강좌 (8) - ArrayBuffer 와 Vector 그리고 공변성

[하마] 이승현 (wowlsh93@gmail.com) 2016. 6. 26. 14:07

ArrayBuffer

 
 
각 언어에서 가장 많이 사용되는 컬렉션을 말하자면  (개인적인 경험으로)  C++ 에서는 vector , map 이고 
자바에서는 ArrayList , HashMap   Python 에서는 [] , {}  즉 리스트 와 딕셔너리 였던거 같습니다. 
다른 언어를 사용할때 가장 먼저 찾게되는것이 바로 저런 가장 기본적인 컬렉션들인데요.
스칼라는 무엇일까요?  JVM 상에서 돌아가는 스칼라에도 자바처럼 ArrayList (동적으로 사이즈가 변하는 배열) 같은게 있을까요? 
 
특성
 
* 스칼라에는 자바의 java.util.ArrayList 가 없다. 대신 ArrayBuffer 가 있다. mutable 속성을 지녔다.

* 자바의 ArrayList 는 C++ 의 vector 와 유사하다. (즉 링크드리스트로 구현되지 않았다. 자바는 LinkedList 가 있고 C++ STL 에서는 list 가 있음)
* ListBuffer 는 링크드 리스트 역할이다.

 

설명 
 

http://stackoverflow.com/questions/8287360/scala-equivalent-of-java-util-arraylist  참고

 

질문 : ArrayList 와 동등한 스칼라의 컬렉션은 무엇인지요? 

답변 : ArrayList 는 자바에서 "디폴트" 리스트로 대부분의 사람들이 오용하고 있습니다. 요소들이 추가되고 제거될때 심각한 퍼포먼스 문제가 있는데  Array 의 보충된 컬렉션으로 자연스럽게 사용하고 있습니다. 그래서 제가 이 질문을 보았을때 3가지의 다른 답변을 생각해 보았는데요. 

 

  • 스칼라의 디폴트 컬렉션은 무엇인가?
  • 어떤 스칼라 컬렉션이 자바의 ArrayList 와 비슷할까?
  • 스칼라에서 ArrayList 에 대체품으로 좋은것은 무엇인가?  

 

구체적으로 설명해 보자면 

 

스칼라의 디폴트 컬렉션은 ?

스칼라에서 자바의 List 인터페이스와 동등한것은 Seq 입니다. GenSeq 라는 더 제네릭한 인터페이스도 존재하구요. 중요한 차이점은 GenSeq 는 구현에 따라서 직렬 또는 병행 처리하는 연산을 가질 수 있다는 점이에요.

스칼라는 프로그래머에게 Seq 를 팩토리로서 사용할 수 있게 하는데요, 특정 구현을 하도록 강제 하지 않습니다. 그 결과 스칼라의 리스트나 벡터를 선택할 수 있게 되었습니다. 둘 다 변경 불가능 하구요. 벡터는 인덱스 접근시 좋은 성능을 보여줍니다. 리스트도 벡터가 갖지 못한 고유의  장점을 갖고 있죠. 

 

ArrayList 와 비슷한 스칼라의 컬렉션은 무엇인가?

scala.collection.mutable.ArrayBuffer 라고 해야 할거 같습니다. mutable 입니다.

scala.collection.immutable.Vector 도 있다고 해야할 거 같습니다.immutable 입니다.

 

 

스칼라에서 Array !!!

글쎄요, 좋은 뉴스는 스칼라에서 그냥 Array 를 사용 할 수 있다는 점입니다. Array 가 자바에서는 제네릭과 일반적으로 호환되지 않기때문에 기피하는 경향이 있습니다.  자바에서 Array 는 co-variance 컬렉션인데 반면 제네릭은 invariant 입니다. 컬렉션이 변경가능한 성질을 지녔을때에 공변성 성질은 프로그램을 위험하게 만들 수 있습니다. 


* 공변성 과 불변성 

위의 내용은  한번에 이해하기 어려운 내용인데요, 간단히 설명해 보겠습니다.

먼저 다음을 생각해 보세요.

String 이 Object 의 서브타입이라면,  String[]  은 Object[] 의 서브타입인가?

String 이 Object 의 서브타입이라면, ArrayList <String> 은 ArrayList<Object> 의 서브타입인가?

 

즉 어떤 Container[T2] 가 Container[T1] 의 하위 타입이라고 할 수 있는가? 인데요

Container[T2] 은 Container[T1] 의 하위 클래스이다.     = 공변성

Container[T2] 은 Container[T1]  과 아무 관계가 없다.   = 불변성  

 

* 왜 자바 Array 는 공변성(co-variant)인가? (자바는 따로 Array라는 키워드를 사용하지 않고, 타입[] 방식을 배열(Array)에 사용합니다.) 

  자바가 처음 등장했을때에는 제네릭(generics) 은 없었습니다. 이때 배열들을 섞거나 비교하는 함수를 만들 때, void doSomthing(Object[] a)  같은거 말이죠.  이 메소드의 매개변수가  만약 불변성으로 다루어 진다면, Object[] 형의 매개 변수만 넣는게 가능할 것 입니다. 하지만  공변성 성질을 가진 덕분에 String[] 도 넣을 수 있게 됬습니다.    
따라서 sort 같은 함수도 여러 타입의  매개변수를 넣을 수 있게 되겠지요. 

* 왜 자바 제네릭은 불변성(invariant)인가? 

  List<Dog> 는 List<Animal> 이 아닙니다. 비록 Dog 가 Animal 의 자식타입이라 하더라도 말이죠.

  예를들어서 

  List<Dog> dogs = new List<Dog>(); // 강아지에 대한 리스트를 만듭니다.

  List<Animal> a = dogs;  // 음.. 부모형을 타입으로 참조를??

  a.add(new Cat());  // 역시 Animal의 하위타입인 고양이를 추가??   

  네 먼가 이상해 집니다.

* 왜 공변성은 mutable 한 성질을 지닌 컬렉션에서 위험한가?    

   변경가능한 컬렉션이란 안의 내용을 마구 조작할 수 있다는걸 말한다고 생각해본다면, 안에 있는 요소의 
   타입이 이것도 될 수 있고, 저것도 될 수 있다고 생각하면  먼가 꺼림직하지 않겠습니까? 
   인생이 잘 풀리면 좋겠지만 가끔은 잘못된 객체가 함수안에 들어가서 이상하게 변경 될 수 도 있겠지요.

 * 왜 스칼라 Array 는 자바와 다르게 불변성(invariant)인가?

   스칼라의 Array 는 제네릭으로 동작하기 때문입니다. Array[Int] , Array[String] 처럼 말이죠. 
 
 * 왜 스칼라 List는 공변성(covariant)인가?
 
    즉 val arr:Array[Any] = Array[Int](1,2,3) 은 안되는데  val list:List[Any] = List[Int](1,2,3) 은 됩니다.
    하나 예를 들어 보면
     val arr:Array[Int] = Array[Int] (1,2,3) 
     val arr2:Array[Any] = arr
     arr2(0) = 2.54   //  Int 배열에 실수가 들어 갔습니다.!!
 
     즉  Array 는 mutable 속성이기때문에 문제가 생기지만 List 는 immutable 이기때문에 괜찮은겁니다.   
 

 

스칼라에서 Array는 자바의 Array와 비슷한데 불변성입니다. 이것은 많은 문제들을 없앨수 있는데요. 스칼라는 AnyVal (프리미티브와 동등) 을 "제너릭스" 에 대한 타입으로써 받아 드립니다.  모든 Seq 메소드는 Array 에서도 활용 될 수 있게 됩니다. 


 

Array 와 ArrayBuffer 의 차이 

 

Array 와  ArrayBuffer 는 둘다 mutable 입니다. 따라서 다음이 가능하죠.

a(i) = 'hello' 

차이점은 

ArrayBuffer 는 사이즈 조정이 자동적이다라는 점입니다.

ArrayBuffer 에 요소를 추가하면 자동적으로 늘어난다는 것이죠

그럼 Array 에 변경이 아니라 추가를 하면?

새로 만들어진 Array 가 생겨납니다. 예를 들어 보면 

 var array = Array( 1, 2, 3 )  // 배열 생성

 array +:= 4   // 배열 앞에 4 추가한 새로운 배열 생성

 array :+= 0   // 배열 뒤에 4 추가한 새로운 배열 생성

 

ArrayBuffer 예제
val buf = mutable.ArrayBuffer.empty[Int]
buf += 1
buf += 10
val ar = buf.toArray
ar.foreach(println(_))

 

 

Vector
http://allaboutscala.com/tutorials/chapter-6-beginner-tutorial-using-scala-immutable-collection/scala-tutorial-learn-use-immutable-vector/

 

Comments