컬렉션
- 불변 컬렉션: 읽기 전용 컬렉션 (immutable)
- 가변 컬렉션: 삽입, 수정, 삭제와 같은 쓰기 작업이 가능한 컬렉션 (mutable)
import java.util.LinkedList
fun main(){
// immutable 리스트를 만들어주는 팩토리 함수
val currencyList = listOf("달러","유로","원")
// mutable
val mutableCurrencyList = mutableListOf<String>()
mutableCurrencyList.add("달려")
mutableCurrencyList.add("유로")
mutableCurrencyList.add("원")
// 좀 더 가독성 있게 변경
// mutable
val mutableCurrencyList2 = mutableListOf<String>().apply {
add("달러")
add("유로")
add("원")
}
// immutable set
val numberSet = setOf(1,2,3,4)
// mutable set
val mutableSet = mutableSetOf<Int>().apply {
add(1)
add(2)
add(3)
add(4)
}
// imutable map
val numberMap = mapOf("one" to 1,"two" to 2)
// mutable map
val mutableNumberMap = mutableMapOf<String,Int>()
mutableNumberMap["one"] = 1
mutableNumberMap["two"] = 2
mutableNumberMap["three"] = 3
// 컬렉션 빌더 안에서는 값을 넣을 수 있지만 바깥에서는 값을 넣을 수 없음
val numberList: List<Int> = buildList{
add(1)
add(2)
add(3)
add(4)
}
// numberList.add(4)
// linkedList
val linkedList = LinkedList<Int>().apply {
addFirst(3)
add(2)
addLast(1)
}
//
val arrayList = ArrayList<Int>().apply {
add(1)
add(2)
add(3)
}
val iterator = currencyList.iterator()
while(iterator.hasNext()){
println(iterator.next())
}
println("===============")
for(currency in currencyList){
println(currency)
}
println("=================")
currencyList.forEach {
println(it)
}
// for loop -> map
val lowerList = listOf("a","b","c")
val upperList = mutableListOf<String>()
for(lowerCase in lowerList){
upperList.add(lowerCase.uppercase())
}
// map을 사용한 간소화
val upperList2 = lowerList.map{it.uppercase()}
println(upperList2)
lowerList.map{it.uppercase()}
println(upperList)
println("=================")
// filter 함수 구현
val filteredList = mutableListOf<String>()
for(upperCase in upperList2){
if(upperCase == "A"||upperCase=="C"){
filteredList.add(upperCase)
}
}
println(filteredList)
// filter 추상화되 함수 사용
val filteredList2 = upperList.filter { it=="A"||it=="C" }
println(filteredList2)
// 시퀀스 api 사용 데이터 5만~10만 건에서는 추천하지 않는 방법
// 다루는 데이터량이 많아지거나 filter을 연속해서 사용해야 하는 경우 추천
val filteredList3 = upperList
.asSequence()
.filter { it=="A"||it=="C" }
.filter { it=="C" }
.toList()
println(filteredList3)
}
데이터 클래스
- 데이터를 저장하고 전달하기 위한 목적으로 만들어진 클래스
- 가변객체는 멀티스레드 환경에서 문제가 발생 할 수 있음
data class Person(var name:String, var age:Int)
// 위에 한줄을 인텔리제이의 자바로 디컴파일 기능을 사용하여 변환시킨 것
// BasicClassKt.java
import kotlin.Metadata;
@Metadata(
mv = {1, 7, 0},
k = 2,
d1 = {"\\u0000\\b\\n\\u0000\\n\\u0002\\u0010\\u0002\\n\\u0000\\u001a\\u0006\\u0010\\u0000\\u001a\\u00020\\u0001¨\\u0006\\u0002"},
d2 = {"main", "", "BasicClass"}
)
public final class BasicClassKt {
public static final void main() {
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
// Person.java
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Metadata(
mv = {1, 7, 0},
k = 1,
d1 = {"\\u0000 \\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0000\\n\\u0002\\u0010\\b\\n\\u0002\\b\\t\\n\\u0002\\u0010\\u000b\\n\\u0002\\b\\u0004\\b\\u0086\\b\\u0018\\u00002\\u00020\\u0001B\\u0015\\u0012\\u0006\\u0010\\u0002\\u001a\\u00020\\u0003\\u0012\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005¢\\u0006\\u0002\\u0010\\u0006J\\t\\u0010\\u000b\\u001a\\u00020\\u0003HÆ\\u0003J\\t\\u0010\\f\\u001a\\u00020\\u0005HÆ\\u0003J\\u001d\\u0010\\r\\u001a\\u00020\\u00002\\b\\b\\u0002\\u0010\\u0002\\u001a\\u00020\\u00032\\b\\b\\u0002\\u0010\\u0004\\u001a\\u00020\\u0005HÆ\\u0001J\\u0013\\u0010\\u000e\\u001a\\u00020\\u000f2\\b\\u0010\\u0010\\u001a\\u0004\\u0018\\u00010\\u0001HÖ\\u0003J\\t\\u0010\\u0011\\u001a\\u00020\\u0005HÖ\\u0001J\\t\\u0010\\u0012\\u001a\\u00020\\u0003HÖ\\u0001R\\u0011\\u0010\\u0004\\u001a\\u00020\\u0005¢\\u0006\\b\\n\\u0000\\u001a\\u0004\\b\\u0007\\u0010\\bR\\u0011\\u0010\\u0002\\u001a\\u00020\\u0003¢\\u0006\\b\\n\\u0000\\u001a\\u0004\\b\\t\\u0010\\n¨\\u0006\\u0013"},
d2 = {"LPerson;", "", "name", "", "age", "", "(Ljava/lang/String;I)V", "getAge", "()I", "getName", "()Ljava/lang/String;", "component1", "component2", "copy", "equals", "", "other", "hashCode", "toString", "BasicClass"}
)
public final class Person {
@NotNull
private final String name;
private final int age;
@NotNull
public final String getName() {
return this.name;
}
public final int getAge() {
return this.age;
}
public Person(@NotNull String name, int age) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
this.age = age;
}
@NotNull
public final String component1() {
return this.name;
}
public final int component2() {
return this.age;
}
@NotNull
public final Person copy(@NotNull String name, int age) {
Intrinsics.checkNotNullParameter(name, "name");
return new Person(name, age);
}
// $FF: synthetic method
public static Person copy$default(Person var0, String var1, int var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = var0.name;
}
if ((var3 & 2) != 0) {
var2 = var0.age;
}
return var0.copy(var1, var2);
}
@NotNull
public String toString() {
return "Person(name=" + this.name + ", age=" + this.age + ")";
}
public int hashCode() {
String var10000 = this.name;
return (var10000 != null ? var10000.hashCode() : 0) * 31 + Integer.hashCode(this.age);
}
public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Person) {
Person var2 = (Person)var1;
if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
return true;
}
}
return false;
} else {
return true;
}
}
}
data class Person(var name:String, var age:Int)
class Person2(val name:String, val age:Int){
override fun equals(other: Any?): Boolean {
if(this===other) return true
if(javaClass != other?.javaClass) return false
other as Person
if(name != other.name) return false
if(age != other.age) return false
return true
}
/* override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + age
return result
}*/
}
fun main(){
val person1 = Person(name = "tony",age = 12)
val person2 = Person(name = "tony",age =12)
val personNew2 = person1.copy();
// component의 수는 프로퍼티 수와 동일하다.
println("이름${person1.component1()}, 나이${person2.component2()}")
// 데이터 클래스가 equals 를 자동으로 생성해주기 때문에 true
println(person2==person1)
val set = hashSetOf(person1)
println(set.contains(person2))
println(set.contains(personNew2))
person1.name = "skung"
// copy를 해서 만들어도 원본의 값을 변경하면 달라진다.
println(set.contains(person2))
println(set.contains(personNew2))
println("#######################")
val person3 = Person2(name = "tony",age = 12)
val person4 = Person2(name = "tony",age =12)
val set2 = hashSetOf(person3)
println(set2.contains(person4))
println(person3.toString())
println("#######################")
// 구조분해 할당
var superMan = Person(name="히어로",age=300)
val (name, age) = superMan
println("이름=${name}, 나이=${age}")
}
싱글톤
- 클래스의 인스턴스를 하나의 단일 인스턴스로 제한하는 디자인 패턴
- 싱글톤 패턴을 구현할때는 몇가지 제약사항을 통해 구현한다
- 주로 유틸리티 클래스를 만들때 사용된다.
- 동반객체: 클래스 내부에서 생성되는 객체
- 제약사항
- 직접 인스턴스화 하지 못하도록 생성자를 private으로 숨긴다
- getInstance()라는 클래스의 단일 인스턴스를 반환하는 static 메서드를 제공한다
- 멀티-스레드 환경에서도 안전하게 유일한 인스턴스를 반환해야 한다.
- 다양한 구현 방법들
- DCL(Double Check Locking)
- Enum 싱글톤
- 이른 초기화
- 지연 초기화
- 자바에서 많이 쓰이는 구현 방식
public class JavaSingleton{
private static final JavaSingleton INSTANCE = new JavaSingleton();
private JavaSingleton(){
}
public JavaSingleton getInstance(){
return INSTANCE.INSTANCE;
}
}
public class JavaSingleton{
private JavaSingleton(){
}
public JavaSingleton getInstance(){
return LazyHolder.INSTANCE;
}
private static final LazyHolder{
private static final JavaSingleton INSTANCE = new JavaSingleton();
}
}
- 코틀린의 싱글턴
import java.time.LocalDateTime
// 싱글톤 클래스는 object 타입으로 선언한다.
object Singleton{
val a = 1234
fun printA() = println(a)
}
object DatetimeUtils{
val now : LocalDateTime
get() = LocalDateTime.now()
const val DEFAULT_FORMAT = "YYYY-MM-DD"
fun same(a: LocalDateTime, b:LocalDateTime):Boolean{
return a == b
}
}
class MyClass{
// 생성자를 호출하지 못 하도록 private 선언
private constructor()
// 동반객체 키워드 companion
companion object{
val a = 1234
fun newInstance() = MyClass()
}
}
fun main(){
println(Singleton.a)
Singleton.printA()
println("######")
println(DatetimeUtils.now)
println(DatetimeUtils.now)
println(DatetimeUtils.now)
println(DatetimeUtils.DEFAULT_FORMAT)
val now = LocalDateTime.now()
println(DatetimeUtils.same(now,now))
println("######")
println(MyClass.a)
println(MyClass.newInstance())
}
실드 클래스
- 하나의 상위 클래스 또는 인터페이스에서 하위 클래스에 대한 정의를 제안 할 수 있는 방법
// 쉴드 클래스
sealed class Developer{
abstract val name: String
abstract fun code(language:String)
}
data class BackendDeveloper(override val name:String):Developer(){
override fun code(language: String) {
println("저는 백엔드 개발자입니다 ${language}를 사용합니다")
}
}
data class FrontendDeveloper(override val name:String):Developer(){
override fun code(language: String) {
println("저는 프론트엔드 개발자입니다 ${language}를 사용합니다")
}
}
data class AndroidDeveloper(override val name: String): Developer(){
override fun code(language: String) {
println("저는 안드로이드 개발자입니다 ${language}를 사용합니다")
}
}
data class IosDeveloper(override val name: String): Developer(){
override fun code(language: String) {
println("저는 Ios 개발자입니다 ${language}를 사용합니다")
}
}
// 쉴드클래스로 작성된 상위 클래스에 하위클래스가 새롭게 추가가 된다면
// 새롭게 추가한 클래스도 구현해 주어야 함
object OtherDeveloper: Developer(){
override val name: String = "익명"
override fun code(language: String) {
TODO("Not yet implemented")
}
}
// 싱글톤 클래스
object DeveloperPool{
val pool = mutableMapOf<String, Developer>()
fun add(developer: Developer) = when(developer){
is BackendDeveloper -> pool[developer.name] = developer
is FrontendDeveloper -> pool[developer.name] = developer
is AndroidDeveloper -> pool[developer.name] = developer
is IosroidDeveloper -> pool[developer.name] = developer
is OtherDeveloper -> println("지원하지 않는 개발자종류입니다")
}
fun get(name:String) = pool[name]
}
fun main(){
val backendDeveloper = BackendDeveloper(name="토니")
DeveloperPool.add(backendDeveloper)
val frontendDeveloper = FrontendDeveloper(name="스타크")
DeveloperPool.add(frontendDeveloper)
val androidDeveloper = AndroidDeveloper(name="샘숭")
DeveloperPool.add(androidDeveloper)
val iosDeveloper = IosDeveloper(name="애플")
DeveloperPool.add(iosDeveloper)
println(DeveloperPool.get("토니"))
println(DeveloperPool.get("스타크"))
println(DeveloperPool.get("애플"))
println(DeveloperPool.get("샘숭"))
}
확장 함수
- 클래스를 상속하거나 데코레이터 패턴과 같은 디자인 패턴을 사용하지 않아도 클래스를 확장할 수 있는 방법을 제공
- 확장 함수를 만들때 동일한 시그니처를 가진 함수가 있다면 그것이 우선 됨
- 이를 방지하기 위해서는 매개변수를 다르게 받거나 다른 명칭을 사용할 필요가 있음
fun String.first() : Char{
return this[0]
}
fun String.addFirst(char: Char):String{
return char + this.substring(0)
}
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 7, 0},
k = 2,
d1 = {"\\u0000\\u0016\\n\\u0000\\n\\u0002\\u0010\\u0002\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0000\\n\\u0002\\u0010\\f\\n\\u0002\\b\\u0002\\u001a\\u0006\\u0010\\u0000\\u001a\\u00020\\u0001\\u001a\\u0012\\u0010\\u0002\\u001a\\u00020\\u0003*\\u00020\\u00032\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005\\u001a\\n\\u0010\\u0006\\u001a\\u00020\\u0005*\\u00020\\u0003¨\\u0006\\u0007"},
d2 = {"main", "", "addFirst", "", "char", "", "first", "BasicClass"}
)
public final class BasicClassKt {
public static final char first(@NotNull String $this$first) {
Intrinsics.checkNotNullParameter($this$first, "$this$first");
return $this$first.charAt(0);
}
@NotNull
public static final String addFirst(@NotNull String $this$addFirst, char var1) {
Intrinsics.checkNotNullParameter($this$addFirst, "$this$addFirst");
byte var4 = 0;
String var10000 = $this$addFirst.substring(var4);
Intrinsics.checkNotNullExpressionValue(var10000, "this as java.lang.String).substring(startIndex)");
String var3 = var10000;
return var1 + var3;
}
public static final void main() {
char var0 = first("ABCD");
System.out.println(var0);
String var1 = addFirst("ABCD", 'Z');
System.out.println(var1);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
fun String.first() : Char{
return this[0]
}
fun String.addFirst(char: Char):String{
return char + this.substring(0)
}
class MyExample{
fun printMessage() = println("클래스 출력")
}
// 확장 함수를 만들때 동일한 시그니처를 가진 함수가 있다면 동작하지 않음
fun MyExample.printMessage() = println("확장 출력")
fun MyExample.printMessageSu(message:String) = println(message)
fun MyExample?.printNullOrNotNull(){
if(this == null) println("널인 경우에만 출력")
else println("널이 아닌 경우에만 출력")
}
fun main(){
var myExample: MyExample? = null
myExample.printNullOrNotNull()
myExample = MyExample()
myExample.printNullOrNotNull()
println("ABCD".first())
println("ABCD".addFirst('Z'))
// 클래스의 메소드가 호출됨
MyExample().printMessageSu("확장 출력")
}
제네릭
// 공변성 구현
class MyGenenrics<out T>(val t:T){
}
class Bag<T>{
fun saveAll(
// 반공변성 구현
to: MutableList<in T>,
from: MutableList<T>,
){
to.addAll(from)
}
}
fun main(){
val bag = Bag<String>()
// 위에 in 키워드로 인해 CharSequence가 원래 String 보다 상위개념이지만 하위 개념으로 취급
bag.saveAll(mutableListOf<CharSequence>("1","2"), mutableListOf<String>("3","4"))
val generic = MyGenenrics<String>("테스트")
// String의 상위 개념 CharSequence
val charGenerics : MyGenenrics<CharSequence> = generic
// 제네릭을 사용한 클래스의 인스턴스를 만드려면 타입아규먼트를 제공
//val genenrics = MyGenenrics<String>("test")
// 타입추론 기능으로 인해 생략가능
val genenrics = MyGenenrics("test")
// 변수의 타입의 제네릭을 사용한 경우
val list1:MutableList<String> = mutableListOf()
// 타입아규먼트를 생성자에서 추가
val list2 = mutableListOf<String>()
// 스타 프로제션
val list3 : List<*> = listOf<String>("test")
val list4 : List<*> = listOf<Int>(1,2,3,4)
// PECS(Producer-Extends, Consumer-Super
// 공변성은 자바 제네릭의 extends 코틀린에선 out <상위클래스> = <하위클래스> (대입)
// 반공변성은 자바 제네릭의 super 코틀린에선 in <하위클래스> = <상위클래스> (대입)
}
지연초기화
- 대상에 대한 초기화를 미뤘다가 실제 사용시점에 초기화하는 기법을 말한다
- 웹페이지에서 특정 스크롤에 도달했을때 컨텐츠를 보여주는 무한 스크롤
- 싱글톤 패턴의 지연초기화
public class JavaSingleton{
private JavaSingleton(){
}
public JavaSingleton getInstance(){
return LazyHolder.INSTANCE;
}
private static final LazyHolder{
private static final JavaSingleton INSTANCE = new JavaSingleton();
}
}
@OneToMany(fetch = FetchType.LAZY)
- 이외에도 지연초기화는 많은 상황에서 사용된다.
class HelloBot{
// 지연 초기화 키워드 by lazy 기본적으로 멀티스레드 환경에서 안전하게 동작되도록 설계됨
// 불변 객체는 멀티스레드 환경에서 안정성을 제공해줌
// (LazyThreadSafetyMode.SYNCHRONIZED)는 생략 가능
val greeting: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
println("초기화 로직 수행")
getHello()
}
fun sayHello() = println(greeting)
}
fun getHello() = "안녕하세요"
class LateInit{
// (?) not null 타입이 아닌데도 컴파일 오류 발생 x
// by lazy 키워드와 동일
lateinit var text:String
// isInitialized을 외부에서 사용하는 방법
val textInitialized: Boolean
get() = this::text.isInitialized
fun printText(){
// text = "안녕하세요"
// isInitialized 는 클래스 외부에서 사용이 불가능
if(this::text.isInitialized){
println("초기화됨")
}else{
text = "안녕하세요"
}
println(text)
}
}
fun main(){
val test = LateInit()
test.printText()
println("#################")
val helloBot = HelloBot()
for(i in 1..5){
Thread{
helloBot.sayHello()
}.start()
}
}
Pair, Triple과 구조분해 할당
// f((1,3)) = 1 + 3 = 4
// f(1,3) = 1 + 3 = 4
//data class Tuple(val a:Int, val b:Int)
fun plus(pair:Pair<Int,Int>) = pair.first+pair.second
fun plus(a:Int, b:Int) = a + b
// Pair, Triple 클래스는 구조분해 할당에 하기 좋은 자료구조
fun main(){
println(plus(1,3))
// plus의 매개변수는 2개인대 Tuple Class 하나만 들어가도 성립됨
val plus = plus(Pair(1,3))
println(plus)
// 페어 클래스의 각 값은 불변
val pair = Pair("A",1)
val newPair = pair.copy(first="B")
println(newPair)
val second = newPair.component2()
println(second)
// 인뮤터블 형태의 리스트 제공
val list = newPair.toList()
println(list)
// 3개의 데이터를 담을 수 있는 Triple 까지 제공됨 그 이상은 따로 구현 해야 함
val triple = Triple("A","B","C")
println(triple)
// 해당 값들은 triple과 똑같이 인뮤터블
triple.first
triple.second
triple.third
val newTriple = triple.copy(third = "D")
println(newTriple)
println(newTriple.component3())
// 구조 분해 할당
val(a:String,b:String,c:String) = newTriple
println("$a, $b, $c")
val list3:List<String> = newTriple.toList()
val (a1,a2,a3) = list3
println("$a1,$a2,$a3")
list3.component1()
list3.component2()
list3.component3()
//list3.component4()
//list3.component5()
// map의 구조분해 할당
//val map = mutableMapOf("임수한" to "개발자")
val map = mutableMapOf(Pair("임수한","개발자"))
for((key,value)in map){
println("${key}의 직업은 ${value}")
}
}
람다식
fun main() {
val sum = {x:Int, y:Int -> x+y}
println(sum(10,20))
// 해당 함수는 함수( int를 받아 int를 리턴하는 함수 a)를 인자로 받는 함수
fun lamdaTest(a:(Int)->Int):Int{
return a(10)
}
// 정석에 맞게 함수에 함수를 인자로 넣어서 호출한 경우
println(lamdaTest({x->x+10}))
// (추론을 통한 생략) 인자 타입 생략 가능, 인자가 1개 뿐이므로 인자리스트 생략
println(lamdaTest({it+10}))
// 람다가 함수의 유일한 인자이면, 함수의 괄호를 생략 할 수 있음
println(lamdaTest{it+10})
}
data class MyClass(val a:Int, val b:String)
fun lambdaTest(a:(Int)->Int):Int{
return a(10)
}
fun main() {
val sum = {x:Int, y:Int -> x+y}
println(sum(10,20))
val array = arrayOf(MyClass(10,"class1"),MyClass(20,"class2"),MyClass(30,"class3"))
println(array.filter({c:MyClass->c.a<15}))
// 람다가 함수 인자의 마지막으로 사용되면, 함수의 괄호 밖으로 뺄 수 있음.
array.filter(){c: MyClass -> c.a <15}
// 람다가 함수의 유일한 인자이면, 함수의 괄호를 생략 할 수 있음.
array.filter{c: MyClass->c.a<15}
// 인자 타입 생략 가능한 경우
array.filter{c->c.a<15}
// 디폴트 매개변수 이름으로 it를 사용할 수 있음
array.filter{it.a<15} // 일반적으로 많이 사용되는 형태
println(lambdaTest{it+10})
val title="Num:"
val list = listOf(1,2,3,4)
list.forEach{println("$title $it")}
}