컬렉션

  • 불변 컬렉션: 읽기 전용 컬렉션 (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으로 숨긴다
      • private constructor()
    • getInstance()라는 클래스의 단일 인스턴스를 반환하는 static 메서드를 제공한다
    • 멀티-스레드 환경에서도 안전하게 유일한 인스턴스를 반환해야 한다.
  • 다양한 구현 방법들
    • DCL(Double Check Locking)
      • JVM 환경에선 거의 사용 안함
    • 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(); 
    	}
    
    }
    
    • JPA의 엔티티 LazyLoading 기능
    @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")}
    
}

'Development > Kotlin&Android' 카테고리의 다른 글

Kotlin 문법 Part 4  (0) 2023.03.23
Kotlin 문법 Part 3  (0) 2023.03.16
Kotlin 문법 Part 1  (0) 2023.02.28
스프링의 코틀린 지원  (0) 2023.02.27
Java와 Kotlin의 차이  (0) 2023.02.27

+ Recent posts