[안드로이드 Android] Kotlin 언어로의 전환 4. 배열 Array & 컬렉션 Collection
이번 글에서는 코틀린에서 대량의 데이터를 저장하는 배열 및 컬렉션에 대해 알아보겠습니다.
앱 개발과정에서 가장 많이 사용하는 문법이니 주의깊게 읽어보시기 바랍니다. 기존 자바와 만드는 방식의 차이가 꽤 있습니다.
새로운 코틀린 파일 Test04Basic4.kt 를 만들고 문법을 알아보겠습니다.
프로그램의 시작인 main() 함수까지 작성하겠습니다.
6. 배열 Array & 컬렉션 Collection
자바처럼 코틀린도 대량의 데이터를 저장하는 방법은 크게 2 종류가 있습니다.
요소의 개수 변경이 가능한지에 따라 배열과 컬렉션이 존재합니다. 자바와 차이가 존재하기에 주의깊게 살펴보시기 바랍니다.
1) 요소개수의 변경이 불가능한 배열 : Array
배열 참조변수도 역시 var, val 키워드로 만들게 됩니다. 자바와는 다르게 변수선언할때 타입표시에 []가 없습니다. 대신 이 변수가 배열이라는 것을 명시적으로 알수있는 Array 클래스가 배열의 자료형이며 배열요소의 자료형은 제네릭<>으로 지정합니다.
배열 객체를 생성하는 방법도 new 키워드를 사용하는 것이 아니고 배열객체를 생성해 주는 arrayOf()라는 내장함수를 사용합니다.
문법 : [ var 참조변수명 : Array<요소자료형> = arrayOf(값1, 값2, 값3, ....) ]
var arr:Array<Int> = arrayOf(10,20,30) //정수형 배열요소 3개짜리 배열객체 및 참조변수
※ 주의 !! Array<Int> 제네릭 뒤에 대입연산자 = 를 붙여서 작성하면 비교연산자 >= (크거나 같다)와 혼동될 수 있어 문법적 에러로 처리함
참조변수를 만들 때 데이터타입 생략을 통한 자동추론도 가능합니다.
var arr = arrayOf(10,20,30) //데이터타입 생략을 통한 자동추론 가능함 Array<Int>
1.1) 요소의 값을 사용하는 문법은 자바와 같습니다. 배열참조변수명[인덱스 번호]
println(arr[0]) // arr배열의 0번방 요소 값 출력 : 10
println(arr[1]) // arr배열의 1번방 요소 값 출력 : 20
println(arr[2]) // arr배열의 2번방 요소 값 출력 : 30
1.2) 당연하게도 인덱스 번호가 틀리면 문법적 에러로 표시되지는 않지만 실행 하면 실행 도중에 에러(예외)가 발생합니다.
println(arr[3]) //인덱스번호가 틀리면 Exception발생
1.3) 배열 요소의 값 변경도 특별할 것 없습니다. 배열참조변수명[인덱스 번호] = 값
arr[0]= 100 //arr배열의 0번방에 100 대입
println(arr[0]) //출력: 100
1.4) 특이한 점은 코틀린의 모든 변수는 객체이므로 당연히 배열도 객체이며 모든 배열은 자동으로 get(), set() 함수와 size변수를 기본으로 가지고 있다는 겁니다. 그래서 값을 읽어올때나 변경할 때 get(), set() 메소드를 이용해도 된다는 겁니다.
//마치 자바의 ArrayList 요소 값 얻어오기 처럼...사용가능
println(arr.get(0)) //출력 : 100
println(arr.get(1)) //출력 : 20
println(arr.get(2)) //출력 : 30
println()
arr.set(1, 200) //배열 1번방에 200을 대입
println(arr.get(0)) //출력 : 100
println(arr.get(1)) //출력 : 200
println(arr.get(2)) //출력 : 30
get(), set() 이 존재하기는 하지만 코틀린에서는 코드 가독성 면에서 [인덱스번호] 사용을 권장합니다. 당분간은 병행하며 소개하겠습니다.
1.5) 배열의 길이 size 프로퍼티(멤버변수)[ 자바에서는 length ]
println("배열의 길이 : ${arr.size}") //출력 : 배열의 길이 : 3
1.6) 출력을 일일이 하는거 짜증나죠? 반복문을 이용한 요소값 순차 접근
for(i in 0 until 3){ // 0..3쓰면 OutOfIndex Exception발생함
println(arr.get(i))
}
//**출력**
100
200
30
for문에 소개 때 사용되었던 0..5는 사실 배열같은 것 입니다. 즉, 요소 6개짜리 배열을 놓은 것과 같습니다.
in키워드는 자바의 확장된 for문(foreach문) 같은 역할을 하게 됩니다. 다시말해 0..5자리에 배열이 놓여지고 i는 각 요소들의 차례로 대입되는 임시 제어변수 같은 것이 되기에 배열의 값을 더 쉽게 접근하도록 코딩하는 것이 가능합니다.
1.7) 배열요소에 순차접근하는 foreach문 같은 for문 사용법
//배열요소값 for문으로 출력하기 (Java의 foreach문 같은 코드)
for(n in arr){ //n은 인덱스번호가 아님.... 주의!!! 요소임.
println(n)
}
//**출력**
100
200
30
훨씬 코드가 간결하고 요소값에 접근하기 위해 get()이나 [인덱스번호]를 사용하지 않기에 실수의 여지도 줄일 수 있습니다.
제어변수 n이 인덱스 번호가 아니고 요소참조변수 임을 혼동하지 않으시기 바랍니다.
1.8) 혹시 그럼에도 인덱스로 얻어오고 싶다면?? [ indices[인디시즈] : index의 복수형 ]
for(i in arr.indices){ //i는 인덱스번호를 저장하는 Int형 제어변수
println(i)
}
//**출력**
0
1
2
1.9) 혹시 인덱스와 값을 동시에 가져오고 싶다면 [ withIndex() ]
for( (i, v) in arr.withIndex() ){ // (인덱스번호, 요소값) - 순서변경 불가, 소괄호 필수
println( "[ $i ] : $v ")
}
//**출력**
[ 0 ] : 100
[ 1 ] : 200
[ 2 ] : 30
1.10) 배열의 forEach 기능 메소드
다른 함수형 프로그래밍 언어들의 배열처럼 요소값 각각을 반복적으로 접근할때마다 {}의 코드가 실행되도록 하는 forEach 기능이 있습니다. {}안에서는 생략된 변수 it 이 있으며.. it이 요소의 값을 가지고 있습니다. it의 자료형은 배열요소의 자료형으로 자동 지정됩니다.
추후에 고차함수에 대한 실습으로 {} 표기법에 대한 추가 소개할 예정이고 지금은 {}영역안에 코드가 실행된다고
arr.forEach { //배열 요소의 개수만큼 {} 영역이 실행됨
println(it) //{}영역 안에서 it 이라는 이름의 숨겨진 변수가 요소값을 가지고 있음
}
//**출력**
100
200
30
for()문법안에 arr 배열을 지정하여 반복하는 것 보다는 arr배열에게 요소 각각에 대하여 반복하여 {} 영역을 실행하라는 명령구조가 보다 직관적이고 대상이 명확하여 필자가 가장 선호하는 배열 요소의 순차 반복 처리 기능입니다.
- 코틀린 arrayOf() 배열의 특이한 점 -
1.11) 서로다른 자료형의 배열요소
배열 각 요소의 자료형을 다르게 하면 각 요소의 타입은 자동 Any타입이 됩니다.
var arr2= arrayOf(10, "Hello", true) // Int, String, Boolean ==> Array<Any> 타입으로 추론
값 가져오는 것은 특별한 문제 없습니다.
println(arr2[0]) //출력 : 10
println(arr2[1]) //출력 : Hello
println(arr2[2]) //출력 : true
각 요소의 값 변경도 특별한 문법 없이 대입연산자로 가능합니다.
arr2[0]= 20 //0번방의 값을 10 -> 20으로 변경
arr2.set(1, "bbb") //1번방의 값을 "Hello" -> "bbb"로 변경
arr2[2]= 3.14 //기존 자료형을 변경해도 됨. Boolean --> Double [Any타입 이므로]
아무 자료형이나 마구 넣어도 되고 기존 자료형을 신경쓰지 않아도 되니 쓰기 편하고 좋아 보이나요? 그럼 배열요소를 <Any> 타입으로 지정하는 것이 아주 편해 보이지만 요소의 타입이 저장된 값의 자료형으로 되어 있지 않기에 곧바로 연산에 사용 못하는 문제가 발생합니다.
println( arr2[0] + 5 ) //ERROR - arr2[0]의 값은 20 이기에 산술 덧셈 + 5 가 가능해 보이지만 본인이 Int가 아니라 Any로 인식하여 산술연산 불가
만약 산술연산을 해야 하는 상황이라면 형변환 연산자 as 를 사용하여 Int로 변경한 후 덧셈 연산을 수행해야 합니다.
println( arr2[0] as Int + 5) //요소를 as 연산자로 Int로 변환해서 사용해야 함. [ 참조타입의 형변환 연산자 as ]
println( arr2[1] as String + "AAA") //요소를 String으로 변환하여 문자열 결합 "bbb"+"AAA"
번거롭네요. 그래서 보통 배열을 사용할때는 타입을 명시하여 같은 자료형만 저장하는 방식을 선호합니다. [원래 배열의 특징]
1.12) arrayOf()에 Type을 지정하여 배열을 만들기 [ 제네릭 <> ]
var arr3= arrayOf<Int>(10,20,30) //다른 자료형은 넣으면 error
<Int> 제네릭 표기법이 보기 싫다면 기본 타입 배열을 만들어주는 전용함수 이용 [ arrayOf() --> intArrayOf() ]
var arr4= intArrayOf(10,20,30)
배열 참조변수만 먼저 만들고 나중에 배열객체 대입하려면 변수만들때 자료형을 표시
var arr5: IntArray //Int형 배열 Array 참조변수
arr5= intArrayOf(1,2,3)
Boolean부터 Double까지의 기초 자료형들만 xxxArrayOf()가 존재합니다. StringArrayOf()는 제공하지 않습니다.
1.13) 배열 요소값의 시작이 null값을 가진 배열 만들기 [길이:5] arrayOfNulls()
val arr6= arrayOfNulls<Double>(5) //요소 개수가 5개인 null값을 가지고 만들어진 배열
for(t in arr6){
println(t)
}
//**출력**
null
null
null
null
null
주목! 위 코드에서 arr6 배열참조변수를 val 로 만들었기에 다른 배열객체로 변경하여 참조할 수 없습니다.
arr6= arrayOfNulls<Double>(3) //ERROR - val 읽기전용 변수의 참조값 변경 불가능
배열 참조변수의 자료형을 명시할 때 null값을을 요소가 가질 수 있도록 제네릭에 ? 가 추가된 nullable 변수로 지정해야 함
val arr7:Array<Float?> = arrayOfNulls<Float>(3); //null배열을 만들때 참조변수의 타입을 명시할때 제네릭 자료형에 ?(nullalbe) 키워드 필요함.
**배열 기본 문법 정리. 한번 더 소개 ** 참조변수에 배열의 타입을 명시하기 - []표기는 없어짐 ***
//var arrr:Int[] = arrayOf(10,20) //error
var arrr:Array<Int> = arrayOf(10,20) //Array타입을 명시할때는 반드시 <>제네릭을 표시해야함.
//var arrr2:Array<Int>= arrayOf(100,200) // <> 다음에 = 대입연산자를 곧바로 붙이면 에러. 띄어쓰기 필요 [ **주의**]
** arrayOf()는 배열의 개수는 변경할 수 없음 ***
2) 자바의 Collection 과 같은 목적의 클래스들 : Collection [ List, Set, Map ]
대량의 데이터를 어떻게 저장하고 관리하는지에 대해 정리한 이론인 자료구조를 실제로 구현한 클래스들의 모음을 Collection 이라고 부릅니다. 코틀린의 컬렉션도 자바의 컬렉션과 마찬가지로 데이터를 취급하는 방식에 따라 크게 3가지로 구분할 수 있습니다.
* List : 요소가 순서대로 저장됨. 인덱스번호가 자동부여. 중복데이터 허용
* Set : 요소가 순서대로 저장되어 있지 않음. 인덱스번호 없음. 중복데이터 불허
* Map : 요소가 순서대로 저장되어 있지 않음. [키,벨류]쌍으로 요소 저장. 별도 지정 key로 요소 식별. 중복 key 불허, 중복데이터 허용
구분 종류는 같지만 사용하는 문법에는 큰 차이가 있습니다.
코틀린의 Collection 들은 자바와 다르게 요소의 추가/삭제 및 변경이 불가한 종류와 가능한 종류로 나뉘어져 있음.
2.1) 요소개수의 추가/삭제 및 변경이 불가능한 컬렉션 : listOf(), setOf(), mapOf() [배열과 다르게 요소값 변경도 불가능]
2.2) 요소개수의 추가/삭제 및 변경이 가능한 mutable 컬렉션 : mutableListOf(), mutableSetOf(), mutableMapOf()
각각에 대해 알아보겠습니다. 배열을 만들때도 new 키워드 대신에 arrayOf()라는 기능함수를 이용했듯이 컬렉션을 만들때도 listOf() 와 같은 함수로 만들게 됩니다.
2.1) 요소개수의 추가/삭제 및 변경이 불가능한 컬렉션 : listOf(), setOf(), mapOf()
2.1.1) List - 요소가 순서대로 저장됨. 인덱스번호가 자동부여. 중복데이터 허용
val list:List<Int> = listOf(10,20,30,20) //중복데이터[20] 허용 [*주의* 제네릭 <> 타입에 = 대입연산자가 붙어 있으면 에러. 띄어쓰기 해야함.
for( i in 0..3) {
println( list.get(i) ) // .get(인덱스번호)메소드를 통해 요소의 값 얻어오기
}
//**출력**
10
20
30
20 //중복 데이터 존재 확인
리스트는 저장된 순서대로 저장되어 있다는 것과 [1]번 방과 [3]번 방에 중복된 값 20이 저장될 수 있다는 것을 확인해 봤습니다.
값의 추가/삭제/변경에 관련된 기능메소드는 없습니다.
//값의 추가/삭제/변경에 관련된 기능메소드가 없음
list.add(20) //error - add()메소드 없음
list.remove(0) //error - remove()메소드 없음
list.set(1, 200) //error - set()메소드 없음
2.1.2) Set - 요소가 순서대로 저장되어 있지 않음. 인덱스번호 없음. 중복데이터 불허
val set:Set<Double> = setOf(3.14, 5.55, 2.22, 5.55, 1.56) //중복데이터[5.55]는 자동으로 저장안됨.
for( e in set) println(e)
//**출력**
3.14
5.55
2.22
1.56
//중복 데이터 5.5가 저장되어 있지 않음
Set은 중복된 데이터를 저장하지 않도록 저장해야 할때 사용합니다. 앱개발로 예를 든다면 중복되지 않는 식별 key값들을 저장하거나 안드로이드의 블루투스 장치들의 리스트를 받을 때 Set을 사용됩니다. 주변 블루투스 장치들을 찾는 방식이 전파를 쏘고 돌아오는 신호를 수신하여 정보를 취득하는 것인데 전파의 전반사로 인해 같은 기기에 도달한 전파신호가 여러개일 수도 있습니다. 이럴때 같은 기기 정보를 저장하지 않는 가장 좋은 방법이 Set 구조로 저장하는 것입니다. 그래서 실제 android 의 블루투스 디바이스 장치 목록은 Set으로 받아오도록 만들어져 있습니다.
2.1.3) Map - 요소가 순서대로 저장 안됨. [키,벨류]쌍으로 요소 저장. 별도 지정 key로 요소 식별. 중복 key 불허, 중복데이터 허용
코틀린의 Map 사용은 키-벨류 쌍을 어떤 방식으로 만드는지에 따라 2가지 방법이 있습니다. 기능은 똑같기에 본인 편하신걸 사용하시면 됩니다.
[1] Pair()객체를 이용한 키-벨류 지정
val map:Map<String, String> = mapOf( Pair("title","Hello"), Pair("msg","nice to meet you.") ); //Pair()객체를 이용하여 [키,벨류] 쌍으로 요소 추가
println("요소개수 : ${map.size}") //요소 개수 확인해보기
for ( (key, value) in map){ //키와 벨류값을 받아오는 for-in .. (key, value) 소괄호 필수. 순서중요
println("$key : $value")
}
//**출력**
요소개수 : 2
title : Hello
msg : nice to meet you.
[2] Pair()객체 대신에 to 연산자 이용하여 키-벨류 지정
val map2:Map<String, String> = mapOf( "id" to "mrhi", "pw" to "1234" ) // key to value
for( (k, v) in map2) println( "$k : $v") // (키,벨류)의 변수명은 원하는 단어로 변경가능
//**출력**
id : mrhi
pw : 1234
2.2) 요소개수의 추가/삭제 및 변경이 가능한 mutable 컬렉션 : mutableListOf(), mutableSetOf(), mutableMapOf()
2.2.1) MutableList
val aaaa:MutableList<Int> = mutableListOf(10,20,30) // 요소개수 3개짜리 리스트객체 생성
println("요소개수 ${aaaa.size}") //출력: 요소개수 3
요소추가 [ 가장 마지막에 추가 - 3번방에 40 추가]
aaaa.add(40)
특정 위치에 추가 가능 [ 0번방에 50 추가 ]
aaaa.add(0,50)
추가된 요소의 개수 확인해보기
println("요소개수 ${aaaa.size}") //출력: 요소개수 5
특정 요소값 변경
aaaa.set(1,200) //1번방에 200 설정
코틀린은 리스트의 특정 요소값 변경을 set()메소드 대신에 배열처럼 [] 인덱싱 방법을 권장함
aaaa[1] = 200 //1번방에 200 설정
지금까지 추가하고 변경한 리스트 요소값들을 반복문으로 확인해 보겠습니다.
for( e in aaaa) println(e)
//**출력**
50
200
20
30
40
2.2.2) MutableSet
val bbbb:MutableSet<Double> = mutableSetOf<Double>() //셋의 초기 요소개수를 0개로 생성할 수 있음
println("요소개수 ${bbbb.size}") //출력: 요소개수 0
요소 추가 - 중복데이터도 추가해보기
bbbb.add(5.55)
bbbb.add(3.14)
bbbb.add(5.55) //중복데이터는 자동 무시
println("요소개수 ${bbbb.size}") //출력: 요소개수 2
for( e in bbbb) println(e)
//**출력**
5.55
3.14
2.2.3) MutableMap
val cccc:MutableMap<String, String> = mutableMapOf("name" to "sam", Pair("tel", "01012345678")) //to연산자와 Pair()를 동시에 사용해도 문제없음
println("요소개수 ${cccc.size}") //출력: 요소개수 2
요소 추가하기 [ 키-"addr", 벨류-"seoul" ]
cccc.put("addr", "seoul")
println("요소개수 ${cccc.size}") //출력: 요소개수 3
반복문으로 키-벨류 를 얻어와서 출력해보기
for( (k,v) in cccc) println( "$k : $v")
//**출력**
name : sam
tel : 01012345678
addr : seoul
※ 보통 개발자가 Collection 을 사용한다는 것은 유동적으로 요소값들을 제어하는 경우가 대부분이기에 처음 사용하실 때는 특별한 사유가 없다면mutableXXXOf()로 만드는 것을 추천합니다. 다만, 조금 익숙해지셨다면 추후 서버나 DB 데이터를 load하여 UI로 뿌려 주는 등의 작업을 할때 요소의 개수나 값이 실수로 변경되는 것을 방지하기 위해 mutable이 아닌 리스트를 사용하시는 것이 좋습니다. 추후 관련하게 소개하도록 하겠습니다.
♣ 별외. mutable이 익숙지 않다면 자바의 ArrayList, HashSet, HashMap 에 대응하는 클래스가 존재합니다.
자바개발자들에게 편의를 제공하는 클래스이기에 코틀린을 사용하시겠다면 코틀린 특성에 맞게 설계된 Mutable 컬렉션 사용을 권장합니다. 하여 코드 소개는 가급적 간결하게 주석으로만 소개하여 이런게 있구나 정도로 살펴보고 넘어가도 될 것 같습니다.
// Java의 ArrayList 같은 배열
val arrList:ArrayList<Any> = arrayListOf<Any>(10, "Hello", true)
for( e in arrList) println(e)
println()
/**출력**
10
Hello
true
*******/
//요소 추가 및 삭제
arrList.add(20)
arrList.add(3.14) //원래 있던 자료형이 아닌 것이 추가되면 에러였으나 이제 가능함
arrList.forEach { println(it) } //리스트객체의 forEach 기능사용하기 [한번 해보기]
println()
/**출력**
10
Hello
true
20
3.14
*******/
//특정 위치에 추가하기
arrList.add(0, "Nice")
for( e in arrList){
println(e)
}
println()
/**출력**
Nice
10
Hello
true
20
3.14
*******/
//인덱스번호로 지우기
arrList.removeAt(0)
for( e in arrList){
println(e)
}
println()
/**출력**
10
Hello
true
20
3.14
*******/
//요소값으로 지우기
arrList.remove(3.14)
for( e in arrList){
println(e)
}
println()
/**출력**
10
Hello
true
20
*******/
//요소개수나 set(), get()은 동일하게 존재함
println("요소개수 : ${arrList.size}")
arrList.set(0,20) //코틀린은 set()보다는 아래처럼 []인덱싱을 이용하여 설정하는 방식을 권장함
//arrList[0]=20
println("0번요소의 값 : ${ arrList.get(0) }") //변수명이 아니라 함수를 호출하는 것도 가능함
//println("0번요소의 값 : ${ arrList[0] }") //코틀린은 []인덱싱 방식을 권장함
println()
/**출력**
요소개수 : 4
0번요소의 값 : 20
********/
val hashSet: HashSet<Any> = hashSetOf(100, "Good", false)
hashSet.add(200)
hashSet.add("Good") //중복데이터 자동 무시
println("요소개수 : ${hashSet.size}")
for( e in hashSet) println(e)
println()
/**출력**
요소개수 : 4
200
100
Good
false
********/
val hashMap: HashMap<String, String> = hashMapOf("apple" to "사과", Pair("house", "집"))
hashMap.put("car","자동차")
hashMap.put("car","차") //같은 key 를 사용하면 마지막 값으로 변경
for ( (k,v) in hashMap) println("$k : $v")
println()
/**출력**
apple : 사과
house : 집
car : 차
*********/
다시 말씀드리지만 자바에만 익숙한 개발자라면 기존의 Collection 클래스로 만들어도 무방합니다.그렇지 않다면 mutableXXX 로 사용하는 것을 권장합니다. (언어의 고유특징에 맞게 설계된 개발방법을 권장함)
3) 2차원 배열 및 리스트
코틀린의 2차원 배열로 자바처럼 1차원 배열객체 여러개를 요소로 가진 배열로 만들게 됩니다.
val arrays= arrayOf( arrayOf(10,20,30), arrayOf("aa", "bb"), arrayOf(true, false)) //1차원배열 3개를 가진 2차원배열 객체 1개 생성
2차원 배열이기에 요소값에 반복 접근하는 것은 중첩 for 문을 사용해야 합니다.
for( ar in arrays){
for( e in ar){
print(e)
print(" ")
}
println()
}
println()
//**출력**
10 20 30
aa bb
true false
리스트도 2차원 리스트가 가능합니다.
val arrays2= mutableListOf<MutableList<Int>>( mutableListOf<Int>(10,20,30), mutableListOf<Int>(100,200,300,400) )
println("2차원배열 arrays2의 사이즈 : ${arrays2.size}") //2차원배열 arrays2의 사이즈 : 2
2차원 리스트 안에 요소인 1차원 리스트의 개수들 확인해보기
for( i in 0 until arrays2.size ){
}
or
for ( index in arrays2.indices ){ //인덱스 번호들 얻어오기
println(" arrays[$index] 리스트요소의 사이즈 : ${arrays2[index].size} ")
}
//**출력**
arrays[0] 리스트요소의 사이즈 : 3
arrays[1] 리스트요소의 사이즈 : 4
새로운 1차원 리스트를 추가해보기
arrays2.add( mutableListOf(1000,2000) )
for( li in arrays2 ){
for( e in li){
print("$e, ") //1차원 배열의 요소값 출력
}
println()
}
println()
//**출력**
10, 20, 30,
100, 200, 300, 400,
1000, 2000,
4) Null 값 저장을 무시하는 Collection : xxxOfNotNull
- [String? : nullable 자료형은 추후 추가로 소개 : null을 저장할 수 있는 자료형]
val aaaaa:List<String?> = listOf(null, "nice")
for ( e in aaaaa ) println(e)
println()
/**출력**
null
nice
********/
val aaaaa2:List<String?> = listOfNotNull(null, "aaa") //null값 저장이 자동으로 무시됨
for( e in aaaaa2) println(e)
println()
/**출력**
aaa
********/
val bbbbb: Set<String?> = setOfNotNull(null,"good"); //null 무시
for( e in bbbbb) println(e)
println()
/**출력**
good
********/
// map 방식은 NotNull 관련 함수가 없음.
//val ccccc: Set<String?> = mapOfNotNull(); //존재하지 않음
// mutableXXX()도 NotNull 관련 함수가 없음.