<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Strong 감자의 공부</title>
    <link>https://java0u0.tistory.com/</link>
    <description>어쩌다보니 감자라는 별명이 생겼습니다. 그래서 감자의 블로그~!
자바, 안드로이드, 코틀린 공부 기록합니다! 
 -Potato.Lee</description>
    <language>ko</language>
    <pubDate>Fri, 26 Jun 2026 19:39:45 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ugyeong</managingEditor>
    <image>
      <title>Strong 감자의 공부</title>
      <url>https://tistory1.daumcdn.net/tistory/5951602/attach/03f43eb69ae34ecd8f389a2702f12a1d</url>
      <link>https://java0u0.tistory.com</link>
    </image>
    <item>
      <title>Retrofit 사용해보기 #1</title>
      <link>https://java0u0.tistory.com/60</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Retrofit을 사용해 api 통신을 설정해보겠습니다~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 싱글톤 객체로 Retrofit 객체 생성하기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤은 전역에서 한번만 객체가 생성되어&lt;span&gt;&amp;nbsp;&lt;/span&gt;일관된 설정이 가능하고 코드 중복을 최소화할 수 있어요&lt;/p&gt;
&lt;pre id=&quot;code_1735551116687&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;package kr.co.gamja.study_hub.data.repository

import ...


object RetrofitManager {

    private const val BASE_URL = &quot;https://...&quot;
    val gson =GsonBuilder().setLenient().create()

    // Retrofit 객체를 구성합니다.
    val retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()
	
    // retrofit.create : Retrofit은 인터페이스를 기반으로 동적으로 구현체(Proxy 객체)를 생성.
    val api = retrofit.create(Api::class.java) // Api :  API classs는 api 요청을 정의하는 인터페이스

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. api 요청을 처리하는 인터페이스&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1735551103837&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package kr.co.gamja.study_hub.data.repository

import ...

interface Api {
// suspend 키워드를 사용하면 비동기적으로 네트워크 요청을 수행하고, 결과를 반환할 때까지 호출을 일시 중단.
    @GET(&quot;/api/...&quot;) 
    suspend fun study(
        @Query(&quot;page&quot;) page: Int,
        @Query(&quot;size&quot;) size: Int
    ):Response&amp;lt;ParticipatedStudyRequestDto&amp;gt;
    
}

// HTTP 응답 데이터를 포함하는 객체
data class ParticipatedStudyRequestDto(
    val participateStudyData: ParticipateStudyData,
    val totalCount: Int
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response는 Retrofit 라이브러리에서 네트워크 요청의 &lt;b&gt;결과&lt;/b&gt;를 나타내는 클래스이며, 이 클래스는 HTTP 요청에 대한 &lt;b&gt;응답 정보&lt;/b&gt;를 캡슐화하며, 요청의 성공 또는 실패 여부와 서버에서 반환된 데이터를 처리하는 데 사용돼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 호출 부&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1735551398015&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; fun updateEngagedListSize() {
        viewModelScope.launch {
            val response = RetrofitManager.api.study(0, 10)
            try {
                if (response.isSuccessful) {
                  
                }
            } catch (e: Exception) {
              
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음엔 jwt를 사용한 코드를 소개할게요!&amp;nbsp;&lt;/p&gt;</description>
      <category>앱개발/StudyHub 앱 출시 회고</category>
      <category>Retrofit</category>
      <category>레트로핏</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/60</guid>
      <comments>https://java0u0.tistory.com/60#entry60comment</comments>
      <pubDate>Mon, 30 Dec 2024 18:41:41 +0900</pubDate>
    </item>
    <item>
      <title>달력 만들기</title>
      <link>https://java0u0.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이틀만에 다시 뵙습니다~!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 달력 만들기를 소개하려해요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능을 만들 때 타 블로그를 참고해서 만들었어요 차이점이 있다면 제가 구현한 달력은 시작날짜와 끝날짜를 선택해서 기간을 설정하는 방식이예요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 완성본&lt;/h3&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 2... 일 자가 리사이클러뷰 item이고 이를 반복해 달력을 만들었어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 하얀색 네모에 1, 2, 3 .. 날짜가 표시될 것이고 모여서 달력이 구성돼요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;날짜를 클릭하면 색이 변경돼야하므로 이를 button으로 구성했어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;파일명 : calendar_call_item.xml&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;985&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmDLHD/btsLBthAptn/COrlWtmEFXXKr6oHf2ozk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmDLHD/btsLBthAptn/COrlWtmEFXXKr6oHf2ozk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmDLHD/btsLBthAptn/COrlWtmEFXXKr6oHf2ozk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmDLHD%2FbtsLBthAptn%2FCOrlWtmEFXXKr6oHf2ozk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;179&quot; height=&quot;285&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;985&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1735456727126&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;layout ... &amp;gt;

    &amp;lt;data&amp;gt;

        &amp;lt;variable
            name=&quot;model&quot;
            type=&quot;kr.co.gamja.study_hub.feature.studypage.createStudy.InfoOfDays&quot; /&amp;gt;
    &amp;lt;/data&amp;gt;

    &amp;lt;LinearLayout
        android:layout_width=&quot;45dp&quot;
        android:layout_height=&quot;45dp&quot;
        android:background=&quot;@color/syswhite&quot;
        android:orientation=&quot;vertical&quot;&amp;gt;

        &amp;lt;androidx.appcompat.widget.AppCompatButton
            android:id=&quot;@+id/txt_day&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_gravity=&quot;center&quot;
            android:background=&quot;@drawable/solid_syswhite&quot;
            android:textColor=&quot;@color/sysblack1&quot;
            android:textSize=&quot;18dp&quot;
            tools:text=&quot;@{model.infoDay}&quot; /&amp;gt;
    &amp;lt;/LinearLayout&amp;gt;

&amp;lt;/layout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;871&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WGz9P/btsLBtaNV3v/JwAczJyt6eEYs6RvIx9Ee1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WGz9P/btsLBtaNV3v/JwAczJyt6eEYs6RvIx9Ee1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WGz9P/btsLBtaNV3v/JwAczJyt6eEYs6RvIx9Ee1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWGz9P%2FbtsLBtaNV3v%2FJwAczJyt6eEYs6RvIx9Ee1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;261&quot; height=&quot;455&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;871&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;파일명 : fragment_calendar.xml&lt;/h4&gt;
&lt;pre id=&quot;code_1735457362950&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;layout ...&amp;gt;

    &amp;lt;data&amp;gt;

        &amp;lt;variable
            name=&quot;viewModel&quot;
            type=&quot;kr.co.gamja.study_hub.feature.studypage.createStudy.CreateStudyViewModel&quot; /&amp;gt;
    &amp;lt;/data&amp;gt;

    &amp;lt;RelativeLayout
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot; ... &amp;gt;

        &amp;lt;View
            android:id=&quot;@+id/view_top&quot;
            ... /&amp;gt;
			
        // &quot;완료&quot; 버튼
        &amp;lt;androidx.appcompat.widget.AppCompatButton
            android:id=&quot;@+id/btn_ok&quot;
           	... /&amp;gt;
            
            
        // 이전 달로 가는 &quot;&amp;lt;&quot; 버튼
        &amp;lt;androidx.appcompat.widget.AppCompatButton
            android:id=&quot;@+id/btn_left&quot;
            ... /&amp;gt;
            
            
        // &quot;month&quot; 글자
        &amp;lt;TextView
            android:id=&quot;@+id/txt_Month&quot;
            .../&amp;gt;
            
        // 다음 달로 가는 &quot;&amp;gt;&quot; 버튼
        &amp;lt;androidx.appcompat.widget.AppCompatButton
            android:id=&quot;@+id/btn_right&quot;
            .../&amp;gt;
            
        // 일 ~ 토 요일 표현
        &amp;lt;LinearLayout
            android:id=&quot;@+id/layout_linear&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
          ...&amp;gt;

            &amp;lt;TextView
                android:id=&quot;@+id/txt_sunday&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_weight=&quot;1&quot;
                android:text=&quot;@string/sunday&quot;
                android:textColor=&quot;@color/BG_80&quot;
                android:gravity=&quot;center&quot;
                android:textSize=&quot;14dp&quot; /&amp;gt;

            &amp;lt;TextView
                android:id=&quot;@+id/txt_monday&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_weight=&quot;1&quot;
                android:gravity=&quot;center&quot;
                android:text=&quot;@string/monday&quot;
                android:textColor=&quot;@color/BG_80&quot;
                android:textSize=&quot;14dp&quot; /&amp;gt;

            &amp;lt;TextView
                android:id=&quot;@+id/txt_tuesday&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_weight=&quot;1&quot;
                android:text=&quot;@string/tuesday&quot;
                android:gravity=&quot;center&quot;
                android:textColor=&quot;@color/BG_80&quot;
                android:textSize=&quot;14dp&quot; /&amp;gt;

            &amp;lt;TextView
                android:id=&quot;@+id/txt_wednesday&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_weight=&quot;1&quot;
                android:text=&quot;@string/wednesday&quot;
                android:gravity=&quot;center&quot;
                android:textColor=&quot;@color/BG_80&quot;
                android:textSize=&quot;14dp&quot; /&amp;gt;

            &amp;lt;TextView
                android:id=&quot;@+id/txt_Thursday&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_weight=&quot;1&quot;
                android:text=&quot;@string/Thursday&quot;
                android:gravity=&quot;center&quot;
                android:textColor=&quot;@color/BG_80&quot;
                android:textSize=&quot;14dp&quot; /&amp;gt;

            &amp;lt;TextView
                android:id=&quot;@+id/txt_friday&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_weight=&quot;1&quot;
                android:text=&quot;@string/friday&quot;
                android:gravity=&quot;center&quot;
                android:textColor=&quot;@color/BG_80&quot;
                android:textSize=&quot;14dp&quot; /&amp;gt;

            &amp;lt;TextView
                android:id=&quot;@+id/txt_Saturday&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_weight=&quot;1&quot;
                android:gravity=&quot;center&quot;
                android:text=&quot;@string/Saturday&quot;
                android:textColor=&quot;@color/BG_80&quot;
                android:textSize=&quot;14dp&quot; /&amp;gt;

        &amp;lt;/LinearLayout&amp;gt;


        // 1~몇 일 표현
        &amp;lt;androidx.recyclerview.widget.RecyclerView
            android:id=&quot;@+id/recycler_day&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            ... /&amp;gt;
    &amp;lt;/RelativeLayout&amp;gt;

&amp;lt;/layout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 리사이클러뷰의 어댑터를 보러 갈게요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일명 : CalandarAdapter.kt&lt;/h3&gt;
&lt;pre id=&quot;code_1735468698543&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package kr.co.gamja.study_hub.feature.studypage.createStudy

import ...


class CalendarAdapter(private val context: Context) :
    RecyclerView.Adapter&amp;lt;CalendarAdapter.CalendarHolder&amp;gt;() {
    ...
    var daysInfo = ArrayList&amp;lt;InfoOfDays&amp;gt;() // 날짜
    private var onCalendarItemClickListener: OnCalendarItemClickListener? = null
    ...
	
    // CalendarHolder라는 ViewHolder 객체 생성 및 바인딩 객체를 ViewHolder에 전달하여, 나중에 데이터를 뷰와 연결.
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CalendarHolder {
        val binding =
            CalendarCellItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return CalendarHolder(binding)
    }
	
    // position을 통해 해당하는 데이터를 viewHolder에 넣어주기
    override fun onBindViewHolder(holder: CalendarHolder, position: Int) {
        val day = daysInfo.get(position)
        holder.setDays(day)
    }

    // onBindViewHolder로 받은 데이터 실제 뷰와 연결하기
    inner class CalendarHolder(val binding: CalendarCellItemBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun setDays(item: InfoOfDays) {
            binding.model = item // 1일... day

            // 날짜가 없는 경우 선택 불가
            if (daysInfo[absoluteAdapterPosition].infoDay.isNotEmpty()) {
            
            /*
            selectedPosition: 사용자가 선택한 아이템의 위치. absoluteAdapterPosition: 현재 ViewHolder가 관리하는 아이템의 위치.
            현재 ViewHolder가 selectedPosition의 아이템을 관리하고 있는지 확인.
            
            ⭐⭐⭐ 
            recycelrView는 뷰를 재활용하므로 꼭 확인해야함. 안그러면 재활용 된거와 전의 뷰 홀더 구분을 못하는거 같음
             
            */   
             
                if (selectedPosition == absoluteAdapterPosition) {
                   // 선택한것으로 표시
                } else {
                    // 선택 취소하기
                }
                // 날짜 클릭이벤트가 발생했을 경우 
                if (onCalendarItemClickListener != null) {
                    binding.txtDay.setOnClickListener {
                        onCalendarItemClickListener?.onItemClick(item, absoluteAdapterPosition)
                        if (selectedPosition != absoluteAdapterPosition) {
                            binding.setChecked(false)
                            notifyItemChanged(selectedPosition)
                            selectedPosition = absoluteAdapterPosition
                            // 기존 선택한 날찌 체크 풀기
                            selectedYearMonthDay.selectedYearMonth = null
                            selectedYearMonthDay.selectedDay = null
                        }

                    }
                }
            }
            
            if (startDate.selectedYearMonth.isNullOrEmpty() &amp;amp;&amp;amp; startDate.selectedDay.isNullOrEmpty()) {
            	// 날짜 선택 x 경우 - 현재날짜보다 전날인경우 회색    
            } else {
                // 날짜 선택 o 경우 - 시작날짜 포함 전 날짜들은 회색 처리 +todo(&quot;현재날짜보다 전날인경우 회색&quot;)
                if (item.infoDay.isNotEmpty()) {
                    
                }
            }
        }
    }
    
    // 외부(CalendarFragment)에서 클릭 이벤트 처리하기 위함
    fun setOnCalendarItemClickListener(listener: OnCalendarItemClickListener) {
        onCalendarItemClickListener = listener // listener는 CalendarFragment에서 전달받은 것 
    }
    // 총 몇개의 데이터가 나타나야하는지 RecyclerView에게 알려주기 (viewHolder개수 아님)
     override fun getItemCount(): Int {
        return daysInfo.size
    }

    private fun CalendarCellItemBinding.setChecked(selected: Boolean) {
       // 선택된 날짜 색 바꾸기
    }


    private fun CalendarCellItemBinding.setUncheked() {
       // 시작 날짜 이전 날짜는 선택 못하게 하기 
    }

}

interface OnCalendarItemClickListener {
    fun onItemClick(item: InfoOfDays, position: Int)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;파일명 : CalandarFragment.kt&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB1VPO/btsLBtWBpKc/xHrCL3BtWKaJocZiFEy0x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB1VPO/btsLBtWBpKc/xHrCL3BtWKaJocZiFEy0x0/img.png&quot; data-alt=&quot;이전(스터디 생성) 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB1VPO/btsLBtWBpKc/xHrCL3BtWKaJocZiFEy0x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB1VPO%2FbtsLBtWBpKc%2FxHrCL3BtWKaJocZiFEy0x0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;174&quot; height=&quot;1046&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이전(스터디 생성) 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작하는 날 혹은 종료하는 날 박스를 클릭하면 달력이 나타나요. 아래 코드의 bundle로 시작하는 날과 끝 날짜로 구분해요&lt;/p&gt;
&lt;pre id=&quot;code_1735468912263&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package kr.co.gamja.study_hub.feature.studypage.createStudy
import ...

class CalendarFragment : BottomSheetDialogFragment() {

    private lateinit var binding: FragmentCalendarBinding
    private val viewModel: CreateStudyViewModel by activityViewModels()
    
    private lateinit var selectedDate: LocalDate// 년 월
    private lateinit var today: String // 오늘 날짜
    private lateinit var currentYearMonth: String // 오늘이 속한달
    private lateinit var changedYear: String // 바뀐 년
    private lateinit var changedMonth:String // 바뀐 달
    private lateinit var formattedDate: String // 포멧한 값
    private lateinit var whatDay: String // 시작인지 끝날짜인지 구분 tag(스터디 생성에서 가져옴)
    private lateinit var changedYearMonth:String // 지난날짜 회색처리 위한 바뀐 날짜 yyyyMM
    var newSelectedYearMonth=InfoOfSelectedDay(null,null) // 선택된 날짜
    private lateinit var newStartDate:StartDate// 시작 날짜 &amp;lt; 끝 날짜
    
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_calendar, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.viewModel = viewModel
        binding.lifecycleOwner = viewLifecycleOwner
        
        val receiveBundle = arguments // 이전(스터디 생성: 위 사진) 화면에서 받아오는 값(달력Fragment 하나로 사용하기 때문)
        var startYearMonth =&quot;&quot;
        var startDay = &quot;&quot;
        
        if (receiveBundle != null) {
            val value = receiveBundle.getString(&quot;whatDayKey&quot;)
            startYearMonth= receiveBundle.getString(&quot;startYearMonth&quot;).toString()
            startDay=receiveBundle.getString(&quot;startDay&quot;).toString()
            newStartDate=StartDate(startYearMonth,startDay)
            if (value != null) whatDay = value
            else Log.e(tag, &quot;시작날짜인지 끝인지 못받아옴&quot;)
        }
        if(whatDay==&quot;0&quot;){ // 시작 날짜 선택이면
            selectedDate = LocalDate.now()
            today = LocalDate.now().dayOfMonth.toString()
            currentYearMonth = toYearMonth(LocalDate.now())
        }else{ // 끝 날짜 선택이면 
            val combined = &quot;$startYearMonth$startDay&quot;
            val formatter = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd&quot;)
            selectedDate =LocalDate.parse(combined,formatter)
            today = selectedDate.dayOfMonth.toString()
            currentYearMonth = startYearMonth
        }

        setMonthView() // 달력 업데이트
        
        // 이전 달 보기 버튼
        binding.btnLeft.setOnClickListener {
            selectedDate = selectedDate.minusMonths(1)
            setMonthView() // 달력 업데이트
        }
        // 다음 달 보기 버튼
        binding.btnRight.setOnClickListener {
            selectedDate = selectedDate.plusMonths(1)
            setMonthView() // 달력 업데이트
        }
        
        binding.btnOk.setOnClickListener {
        	// 날짜 선택 완료
        }

    }
	
    // ✔️ 달력 업데이트 함수 
    fun setMonthView() {
        binding.txtMonth.setText(monthYearFromDate(selectedDate)) // &quot;yyyy-MM&quot; 달력 상단 년도 월 표시
        
        val newDayList = daysInMonthArray(selectedDate) // 해당 달에 해당하는 &quot;일&quot;들 만들어서 리턴 
        
        val adapter = CalendarAdapter(requireContext()).apply {
        
            setOnCalendarItemClickListener(object:OnCalendarItemClickListener{
                override fun onItemClick(item: InfoOfDays, position: Int) {
                    
                    newSelectedYearMonth=InfoOfSelectedDay(item.yearMonthDay,item.infoDay) // 선택된 날짜
                    formattedDate = &quot;${changedYear}년 ${changedMonth}월 ${item.infoDay}일&quot; // 이전 화면 표시용 날짜
                    binding.btnOk.isEnabled = true
                    binding.btnOk.setTextColor(ContextCompat.getColor(requireContext(), R.color.O_50))
                }
            })
        }
        val infoOfDaysList:ArrayList&amp;lt;InfoOfDays&amp;gt; =ArrayList()
        for(day in newDayList){ // day 는 1,2 ~, daysInMonthArray함수에서 받은 ArrayList&amp;lt;String&amp;gt; -&amp;gt; ArrayList&amp;lt;InfoOfDays&amp;gt; 맞추기 위해 
            val info = InfoOfDays(yearMonthDay = changedYearMonth, infoDay =day, isSelected = false)
            infoOfDaysList.add(info)
        }
        adapter.daysInfo=infoOfDaysList
        adapter.currentYearMonth=currentYearMonth // yyyyMM
        adapter.currentDay=today // 오늘 날짜 표시 
        adapter.selectedYearMonthDay=newSelectedYearMonth // 선택된 년도 달
        adapter.startDate=newStartDate // 시작 날짜
        
        binding.recyclerDay.adapter = adapter
        binding.recyclerDay.layoutManager = GridLayoutManager(requireContext(), 7)
    }
    
    
    
    // ✔️ 달력 만들기
    fun daysInMonthArray(newDate: LocalDate): ArrayList&amp;lt;String&amp;gt; {
        val newDayList = ArrayList&amp;lt;String&amp;gt;() // 달력 &quot;일&quot; 리스트 
        val newYearWithMonth = toYearMonth(newDate) // yyyyMM 형식으로 변경
        changedYear=getOnlyYear(newDate) // yyyy 형식으로 변경
        changedMonth=getOnlyMonth(newDate) //MM 형식으로 변경 
        changedYearMonth=toYearMonth(newDate) // yyyyMM
        val newMonth = YearMonth.from(newDate) // 2023-10 형식
        
        val firstDayOfMonth = selectedDate.withDayOfMonth(1)
        val lastDayOfMonth = newMonth.lengthOfMonth()
        val dayOfWeek = firstDayOfMonth.dayOfWeek.value

        // 지난달 조회 불가 &amp;lt; 화살표 예) 202310&amp;gt;202409
        if (newYearWithMonth.toInt() &amp;gt; currentYearMonth.toInt()) {           
        } else {
        
        ...
        
        }
        // 7*6 자리로 달력 만들기 
        for (i in 1 until 42) {
            if (i &amp;lt;= dayOfWeek || i &amp;gt; lastDayOfMonth + dayOfWeek)
                newDayList.add(&quot;&quot;)
            else newDayList.add((i - dayOfWeek).toString())
        }
        return newDayList
    }
   
}

data class InfoOfDays(
    var yearMonthDay:String, // yyyyMM
    val infoDay:String,
    var isSelected:Boolean
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요없는 코드들 최대한 뺐지만... 여전히 복잡해보이네요 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에 붙인 이모티콘 위주로 봐주시면 됩니다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>앱개발/StudyHub 앱 출시 회고</category>
      <category>달력 만들기</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/59</guid>
      <comments>https://java0u0.tistory.com/59#entry59comment</comments>
      <pubDate>Mon, 30 Dec 2024 17:56:57 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 api 통신_callback함수</title>
      <link>https://java0u0.tistory.com/57</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;api 통신 시 callback 함수와 viewModelScope를 사용했다.(callback은 비동기/ 동기로 사용가능하며, callback을 호출하는 함수에 따라 callback이 비동기/동기 중 어떻게 작동할지 결정된다.)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동기 콜백&lt;/b&gt;: 호출하는 함수가 콜백을 호출한 뒤, 콜백 작업이 완료될 때까지 호출하는 함수가 대기합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비동기 콜백&lt;/b&gt;: 호출하는 함수가 콜백을 호출한 뒤 대기하지 않고 바로 종료됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 글만 읽으면 이해가 안가니 예제를 가져오겠슴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 동기식 callback예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다로 구성된 아래 코드를 간단히 설명하자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;executeCallback {} 안에 있는 코드가(= println(&quot;Inside callback&quot;) ) callback() 호출 시 실행되는 문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;executeCallback(callback: () -&amp;gt; Unit)를 뜯어보자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;()는 함수의 매개변수, Unit은 함수 호출 후 반환값이 없음을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;println(&quot;Inside callback&quot;)은 단순 프린트 문이라 매개변수도, 반환값도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;람다를 사용하지 않고 함수 작성할때도 리턴값 없이 프린트 문만 출력 한다면 반환값을 void로 하는것과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1735292302599&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun executeCallback(callback: () -&amp;gt; Unit) {
    println(&quot;Before callback&quot;)
    callback() // 콜백 호출
    println(&quot;After callback&quot;)
}

fun main() {
    executeCallback {
        println(&quot;Inside callback&quot;) // 콜백의 본문 내용
    }
}

// 결과
Before callback
Inside callback
After callback&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 매개변수랑 리턴값이 있을 때를 궁금해할 사람들을 위해 chat gpt에서 예시 긁어왔슴당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input은 callback으로 전달되는 &quot;kotlin&quot;입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1735292796570&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun executeCallback(callback: (String) -&amp;gt; String) {
    println(&quot;Before callback&quot;)
    val result: String = callback(&quot;Kotlin&quot;) // 콜백 함수의 반환값을 받음
    println(&quot;Callback returned: $result&quot;) // 반환값 출력
}

fun main() {
    executeCallback { input -&amp;gt;
        println(&quot;Inside callback with input: $input&quot;) // 매개변수를(executeCallback에서 callback(&quot;kotlin&quot;)) 사용해 문자열 반환
        &quot;Hello, $input!&quot; // 리턴값
    }
}

// 결과 
Before callback
Inside callback with input: Kotlin
Callback returned: Hello, Kotlin!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. 비동기식 callback예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 알아둬야 할건 &lt;b&gt;네트워크 요청은 비동기&lt;/b&gt;이다. 그렇기 때문에 &lt;u&gt;&lt;b&gt;api 호출하는 함수를 일반함수로만 작성하면 안된다!!&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 내가 작성한 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1735293806630&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// fragment.kt 코드

private val viewModel: ChangeNickNameViewModel by viewModels()

...

binding.btnComplete.setOnClickListener {
            viewModel.isDuplicationNickname(object : CallBackListener {
                override fun isSuccess(result: Boolean) {
                    if (result) {
                      // 성공 시
                    } else {
                       // 실패 시
                    }
                }
            })
        }
        
        
        
// viewModel.kt 코드

fun isDuplicationNickname(params: CallBackListener) {
        viewModelScope.launch {
            val response =
                RetrofitManager.api.checkNicknameDuplication(nickname = nickname.value.toString())
            if (response.isSuccessful) {
                params.isSuccess(true)
            } else {
                params.isSuccess(false)
            }
        }
    }
    
    
 // interface.kt 코드
 
 interface CallBackListener {
    fun isSuccess(result: Boolean)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  코드 작동 순서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fragment에서 btnComplete라는 버튼이 눌리면 viewModel.kt에 작성된 isDuplicationNickname함수가 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;호출 시 fragment에서 구체화한 CallBackListener 구현체(isSuccess)가 호출 함수( isDuplicationNickname )&lt;/u&gt;에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;전달&lt;/b&gt;된다. 다시 fragment.kt로 반환되는거 아님!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 의문점...!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 호출된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;isDuplicationNickname은 네트워크 응답값을 기다렸다가 끝나나? ❌&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 일반 함수는 그냥 끝난다. = 끝나니까 네트워크 응답값이 나중에 와도 못받음!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;호출된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;isDuplicationNickname 함수가 종료되면 해담 함수 안의 &amp;nbsp;params.isSuccess(true) or (false)은 어떻게 동작하는가?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;viewModelScope.launch로 코루틴을 시작하면, 이 코루틴은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;백그라운드에서 네트워크 요청을 처리&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;isDuplicationNickname 함수는 종료되더라도 코루틴 내의 네트워크 작업은 계속 진행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️ 만약&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;viewModelScope.launch와 같은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;코루틴 스코프&lt;/b&gt;로 감싸지 않으면?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코루틴이 아닌 일반 함수로 동작하게 된다.&lt;/li&gt;
&lt;li&gt;네트워크 요청이 실행되더라도 함수 자체는 결과를 기다리지 않고 그대로 종료된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TⓂ️ℹ️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 서버와 통신하는 기능을 구현할 때 callback 개념을 몰라 넘 막막했다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴 스코프같은 비동기 컨텍스트도 몰라 api 요청 시 함수 종료로 에러가 발생했는데 그 당시 나땜에 프젝 진행 안될까봐 발동동 구르며 전전긍긍 앓았다 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하기위해 구글링하는 과정에서 많은 북마크를 날렸고 그때의 급급함이 북마크에 고스란히 남아있다..(북마크 언제 정리하지 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;164&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMokDa/btsLzuBIYHq/H9uKOn8NvMJFc9PTYAGQpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMokDa/btsLzuBIYHq/H9uKOn8NvMJFc9PTYAGQpk/img.png&quot; data-alt=&quot;2022-2023 심정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMokDa/btsLzuBIYHq/H9uKOn8NvMJFc9PTYAGQpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMokDa%2FbtsLzuBIYHq%2FH9uKOn8NvMJFc9PTYAGQpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;156&quot; height=&quot;163&quot; data-origin-width=&quot;164&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2022-2023 심정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발에 불 떨어지면 어떻게든 하는 나.. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>앱개발/StudyHub 앱 출시 회고</category>
      <category>android api</category>
      <category>Callback</category>
      <category>Retrofit</category>
      <category>비동기</category>
      <category>안드로이드 api</category>
      <category>앱개발</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/57</guid>
      <comments>https://java0u0.tistory.com/57#entry57comment</comments>
      <pubDate>Fri, 27 Dec 2024 19:28:21 +0900</pubDate>
    </item>
    <item>
      <title>앱 게시 문제_#심사는 끝났는데 플레이 스토어에 앱이 안보일 때</title>
      <link>https://java0u0.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앱 심사는 끝난거 같은데 플레이스토어에 앱이 안올라온 경험이 있습니다.  옾채통해 해결했네요ㅠ(도와주신 익명님 감사합니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐⭐⭐앱이 플레이스토어에 출시됐을 경우(&lt;b&gt;플레이스토어에 내 앱이 보이는 경우&lt;/b&gt;) : 플레이콘솔 &quot;&lt;b&gt;홈&lt;/b&gt;&quot; -&amp;gt; 앱상태 &quot;&lt;b&gt;프로덕션&lt;/b&gt;&quot; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐⭐⭐플레이 스토어에 출시 안됐을 경우(플레이스토어에 내 앱이 안보이는 경우) : 앱상태는 &quot;출시 안됨&quot;으로 표기 됩니다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CbfWm/btsLBJKN07w/whmiKHIQNYwc2MzpK774K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CbfWm/btsLBJKN07w/whmiKHIQNYwc2MzpK774K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CbfWm/btsLBJKN07w/whmiKHIQNYwc2MzpK774K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCbfWm%2FbtsLBJKN07w%2FwhmiKHIQNYwc2MzpK774K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;271&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔️만약 앱 심사 기간이 지났고 별다른 이상 메시지가 오지도 않았는데 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;플레이콘솔 &quot;홈&quot; -&amp;gt; 앱상태 &quot;출시 안됨&quot; 으로 표기된다면? &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;동시에 아래 사진처럼 해당 앱 대시보드 -&amp;gt; 출시개요에는 &quot;프로덕션&quot;이라고 뜬다면?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Au1lC/btsLzp8epSg/qg0ahHdkgBUMWxV6YYzbC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Au1lC/btsLzp8epSg/qg0ahHdkgBUMWxV6YYzbC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Au1lC/btsLzp8epSg/qg0ahHdkgBUMWxV6YYzbC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAu1lC%2FbtsLzp8epSg%2Fqg0ahHdkgBUMWxV6YYzbC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;607&quot; height=&quot;283&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 앱 &amp;gt; 테스트 및 출시 &amp;gt; 설정 &amp;gt; 고급 설정 &amp;gt; 앱 이용 가능 여부가 출시됨으로 되어있는지 확인해보세요!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2455&quot; data-origin-height=&quot;1118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8tTw1/btsLzZ2vP3R/ZMHmAC2HbOde9ij8V58iUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8tTw1/btsLzZ2vP3R/ZMHmAC2HbOde9ij8V58iUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8tTw1/btsLzZ2vP3R/ZMHmAC2HbOde9ij8V58iUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8tTw1%2FbtsLzZ2vP3R%2FZMHmAC2HbOde9ij8V58iUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;262&quot; data-origin-width=&quot;2455&quot; data-origin-height=&quot;1118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>앱개발/StudyHub 앱 출시 회고</category>
      <category>앱 출시</category>
      <category>플레이 콘솔 프로덕션</category>
      <category>플레이스토어 앱 게시 문제</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/56</guid>
      <comments>https://java0u0.tistory.com/56#entry56comment</comments>
      <pubDate>Fri, 27 Dec 2024 18:14:20 +0900</pubDate>
    </item>
    <item>
      <title>앱 출시 회고_intro</title>
      <link>https://java0u0.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 7월부터 기획한 앱을 24년 3월에 플레이 스토어에 출시하고 유지보수를 거쳐 드.디.어 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;24년12월&lt;span&gt; &lt;/span&gt;&lt;/span&gt;1차 마무리 지었어요!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년 6개월 동안 부캠 병행에 앱 출시까지...! 넘 수고했돠...!(뿌듯)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;192&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC3j64/btsLyt3DBn2/4R2c6LgdO72K5eBgSNhMSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC3j64/btsLyt3DBn2/4R2c6LgdO72K5eBgSNhMSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC3j64/btsLyt3DBn2/4R2c6LgdO72K5eBgSNhMSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC3j64%2FbtsLyt3DBn2%2F4R2c6LgdO72K5eBgSNhMSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;192&quot; height=&quot;159&quot; data-origin-width=&quot;192&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림 기능 등 더 추가하고 싶은 기능은 많지만...! 취업이 우선이라 취뽀 후 취미로 앱 고도화 할 계획이예요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  포스팅 작성하는 첫번째 이유는...&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;앱 개발할 땐 시간에 쫓겨 기능이 작동만하면 넘어갔기 때문에 스스로 회고하기 위함입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;두번째 이유는 앱개발을 시작하는 누군가에게 도움이 되기를 바라는 마음에 작성합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;바야흐로 22년 9월, 저는 처음 안드로이드 앱개발을 접했습니다. 그 당시 비동기를 활용한 로그인 화면 개발 조차 못했어요ㅜㅜ 이때 도움을 준 사람이 교내 동아리 스터디원이였는데 정말 이 친구 없었으면 개발을 마치지 못했을거예요..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그 친구가 모르는 부분 딱 한부분을 짚어줬는데 정말 그 덕분에 앱 개발의 실마리를 찾고 출시까지 할 수 있었어요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그래서 앱 개발을 시작하는 누군가에게 도움이 되기를 바라면 포스팅시작합니다!&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;먼저 말씀드릴건&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;1. &lt;i&gt;제 코드가 별로일 가능성 99%입니다!&lt;/i&gt; 위에서 언급했듯이 개발 시 시간에 쫓겨 후다닥 만들었기에 코드 정리 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;2. 그럼 지금 코드 정리하면 되지않나? 싶겠지만 저는 지금 취준 병행이고 안드로이드 포스팅 자체는 취미라... 코드 리팩터보단 사용했던 개념 소개나 트러블슈팅에 집중할 계획입니다.(코드 리팩터는 취뽀 후... 나중에 취뽀 전 후 코드 변천사를 보는것도 재밌을거 같네요 ㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모쪼록 앱 개발 초심자에게 도움이 되기를 바라며 포스팅 마칩니다!! + 언제든 코드 훈수 대환영⭐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=kr.co.gamja.study_hub&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://play.google.com/store/apps/details?id=kr.co.gamja.study_hub&amp;amp;hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1735214381496&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;StudyHub(스터디허브) - Google Play 앱&quot; data-og-description=&quot;스터디 팀원을 찾고 계신가요? 인천대학교 학생을 위한 스터디 매칭 서비스, 스터디 허브에서 스터디를 시작해 보세요!&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=kr.co.gamja.study_hub&amp;amp;hl=ko&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=kr.co.gamja.study_hub&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rV5gD/hyXSDrM43g/1LGkNuCPxO2Y7pdUgp4zNk/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/c8b48g/hyXSwGcftt/zdkrZ2RteQdvnIwnlkGuik/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/MEsSb/hyXSwzrm6p/715gkzEwsl2izGFmbT4p50/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=kr.co.gamja.study_hub&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=kr.co.gamja.study_hub&amp;amp;hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rV5gD/hyXSDrM43g/1LGkNuCPxO2Y7pdUgp4zNk/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/c8b48g/hyXSwGcftt/zdkrZ2RteQdvnIwnlkGuik/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/MEsSb/hyXSwzrm6p/715gkzEwsl2izGFmbT4p50/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;StudyHub(스터디허브) - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스터디 팀원을 찾고 계신가요? 인천대학교 학생을 위한 스터디 매칭 서비스, 스터디 허브에서 스터디를 시작해 보세요!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>앱개발/StudyHub 앱 출시 회고</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/55</guid>
      <comments>https://java0u0.tistory.com/55#entry55comment</comments>
      <pubDate>Thu, 26 Dec 2024 20:56:42 +0900</pubDate>
    </item>
    <item>
      <title>Ch_14 코루틴 처리</title>
      <link>https://java0u0.tistory.com/53</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;- 목차&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 코루틴 동시성 알아보기&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 코루틴 정보 전달 알아보기&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 코루틴의 기본개념&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코루틴의 주요 특징은 서브루틴(함수가 호출되면 결과를 반환, 순차적 처리)과는 다르게 실행중 중단이 가능하며 필요할 때 다시 그 지점부터 실행 재개가 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLu1ll/btsz9XOSUjG/HDzCPMZBp61uad4GditfRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLu1ll/btsz9XOSUjG/HDzCPMZBp61uad4GditfRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLu1ll/btsz9XOSUjG/HDzCPMZBp61uad4GditfRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLu1ll%2Fbtsz9XOSUjG%2FHDzCPMZBp61uad4GditfRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;780&quot; height=&quot;349&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt; non-blocking에 대해&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;b&gt;중단 및 재개&lt;/b&gt;: 코루틴은 특정 작업(예: I/O 작업)을 실행할 때 중단될 수 있습니다. 이때, 코루틴은 현재 상태를 저장하고 실행을 일시 중지합니다. 이러한 기능 덕분에 코루틴이 블로킹 작업을 요청하더라도, 다른 코루틴으로 실행 흐름을 넘겨 프로그램의 나머지 부분이 계속 실행될 수 있습니다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이벤트 루프 및 스케줄러&lt;/b&gt;: 코루틴은 이벤트 루프나 스케줄러와 같은 메커니즘을 사용하여 관리됩니다. 이러한 메커니즘은 코루틴 간에 실행을 효율적으로 전환하고, I/O와 같은 논블로킹 작업이 완료되었을 때 코루틴을 재개합니다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;b&gt;비동기 I/O 작업&lt;/b&gt;: 코루틴은 비동기 I/O 작업에 최적화되어 있습니다. 예를 들어, 네트워크 요청을 할 때 코루틴은 요청을 보내고, 응답을 기다리는 동안 중단됩니다. 이때 다른 코루틴은 계속 실행될 수 있으며, 응답이 도착하면 원래 코루틴은 다시 활성화되어 나머지 작업을 수행합니다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;b&gt;효율적인 자원 사용&lt;/b&gt;: 코루틴은 전통적인 멀티스레딩에 비해 상대&amp;nbsp;코&lt;span style=&quot;color: #0f0f0f; text-align: left;&quot;&gt;루틴은 스레드에 비해 적은 메모리를 사용합니다. 각 스레드는 일반적으로 고정된 스택 메모리(예: 수 MB)를 할당받지만, 코루틴은 필요한 만큼의 메모리만 사용하며, 이는 대개 훨씬 적은 양입니다.&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;적으로 적은 오버헤드로 많은 수의 동시 작업을 처리할 수 있습니다. 또한,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컨텍스트 스위칭&lt;/b&gt;&lt;span style=&quot;color: #0f0f0f; text-align: left;&quot;&gt;: 코루틴 간의 컨텍스트 스위칭은 스레드 간 스위칭보다 오버헤드가 훨씬 적습니다. 스레드 간의 컨텍스트 스위칭은 운영 체제의 개입을 필요로 하며, 이는 상대적으로 많은 시간과 자원을 소모합니다. 반면, 코루틴은 사용자 레벨에서의 경량 스위칭을 통해 빠르고 효율적으로 실행 상태를 전환합니다. (&lt;span style=&quot;color: #0f0f0f; text-align: start;&quot;&gt;코루틴이 실행 상태를 전환할 때 필요한 데이터와 상태 정보를 최소화하여 관리한다는 의미)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코루틴은 스레드를 사용하지만, 스레드와 다른 점은 블로킹을 하지 않고 난블로킹 상태로 코루틴을 스레드 환경에서 실행한다. 그래서 일반적인 스레드 처리보다 메모리 성능이 뛰어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  코루틴 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드와 달리 코루틴은 코루틴이 실행되는 별도의 영역인 코루틴 스코프를 구성해야한다. 그리고 이 코루틴 스코프에서 코루틴 빌더 함수와 일시중단 함수를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코루틴 스코프 : GlobalScope, CoroutineScope는 코루틴 스코프를 구성해서 내부에 코루틴 빌더 함수로 코루틴을 계층 구성하고 내부에 일시 중단 함수 등을 사용해서 코루틴을 처리하는 영역입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코루틴 빌더 함수 : launch, runBlocking 등은 코루틴 스코프를 생성하는 함수이거나 코루틴을 처리하는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 일반 중단 함수 : delay 함수는 코루틴 내부의 특정 기능을 하는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코루틴 결과 처리 :&amp;nbsp; Job과 Deffeered는 코루틴의 결과를 반환하거나 코루틴 처리의 중단 등을 처리 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  런블로킹 작동원리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런블러킹 방식은 스레드와 코루틴의 중간정도의 요건을 처리한다.세북적인 처리 내용을 보면 코루틴을 스레드에서 간단하게 처리하는 방식을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점 : 현재 처리되는 스레드가 종료되면 같이 종료됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 방식은 메인 함수는 일반 스레드가 작동하고 그 내부에 tunBlocking이 빌더한 경우만 코루틴으로 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  주요 코루틴 빌더 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- launch 확장함수 : 코루틴 스코프에 코루틴을 빌더하는 함수이고 반환값은 Job이다. 이 Job으로 코루틴을 중단할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- async 확장함수 : 코루틴 스코프에 코루틴을 빌더하는 함수이다. 반환값은 Deffered&amp;lt;T&amp;gt;이고 이에 코루틴의 처리결과도 같이 반환된다. 그래서 await로 반환값을 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- withContext 일시중단함수 : 컨텍스트를 변경하면서 처리하는 코루틴 빌더이다. 특히 예외 처리가 필요할 때, finally 내에 noncallable과 같이 처리하면 세로운 코루틴을 실행시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- withTimeOut 일시중단함수 :특정 코루틴 처리를 특정 시간까지 작동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  GlobalScope, CoroutineScope차이&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;글로벌 스코프: 앱의 생명 주기와 함께 동작하기 때문에 앱이 실행되는 동안은 별도의 생명주기 관리가 필요하지 않습니다. 만약 앱의 시작부터 종료될 때까지 혹은 장시간 실행되어야 하는 코루틴이 있다면 GlobalScope를 사용하면 됩니다.&lt;/li&gt;
&lt;li&gt;코루틴 스코프: 버튼을 클릭해서 서버의 정보를 가져오거나 파일을 여는 용도라면 필요할 때 만 열고 완료되면 닫는 CoroutineScope를 사용해야 합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  join() 메서드는 코루틴이 실행이 종료된 후 메인스레드 코루틴이 종료합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;코루틴 스코프 안에 선언된 여러 개의 launch 블록은 모두 새로운 코루틴으로 분기되면서 동시에 처리되기 때문에 순서를 정할 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이럴 때 launch 블록 끝에 join() 메서드를 사용하면 각각의 코루틴이 순차적으로 실행됩니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;&lt;code&gt;CroutineScope(Dispatchers.default).launch() {
    launch {
        for (i in 0..5) {
            delay(500)
            Log.d(&quot;코루틴&quot;, &quot;결과1 = $i&quot;)
        }
    }.join()

    launch {
        for (i in 0..5) {
            delay(500)
            Log.d(&quot;코루틴&quot;, &quot;결과2 = $i&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;코루틴은 launch와 async로 시작할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- launch() : 호출하는 것만으로도 코루틴을 생성, 반환되는 Job을 변수에 넣어두고 상태관리용으로 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- async() : 상태를 관리하고&amp;nbsp;await를 이용해 반환값을 받을 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;puppet&quot; style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;&lt;code&gt;CoroutineScope(Dispatchers.Default).async {
    val deferred1 = async {
        delay(500)
        350
    }
    val deferred2 = async {
        delay(1000)
        200
    }
    Log.d(&quot;코루틴&quot;, &quot;연산 결과 = ${deferred1.await() + deferred2.await()}&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; cancel() : &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;코루틴의 동작을 멈추는 상태 관리 메서드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하나의 스코프 안에 여러 개의 코루틴이 있다면 하위의 코루틴도 모두 동작을 멈춥니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음 코드의 job의 cancel메서드가 호출되면 job 뿐만 아니라 같은 스코프에 있는 job1의 코드도 모두 동작을 중단합니다. &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;job의 cancel메서드가 호출되면 job 뿐만 아니라 같은 스코프에 있는 job1의 코드도 모두 동작을 중단합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;&lt;code&gt;val job = CoroutineScope(Dispatchers.Default).launch {
    var job1 = launch {
        for (i in 0..10) {
            delay(500)
            Log.d(&quot;코루틴&quot;, &quot;결과 = $i&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; suspend&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코루틴 안에서 suspend 키워드로 선언된 함수가 호출되면 이전까지의 코드 실행이 멈추고, suspend 함수가 처리가 완료된 후에 멈춰 있던 원래 스코프의 다음 코드가 실행됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- subRoutine() 함수를 suspend 키워드로 선언합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CoroutineScope가 실행되면 (코드 1)이라고 작성된 부분이 실행된 후 subRoune() 함수가 호출됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 suspend 키워드를 사용했기 때문에 subRoutine() 안의 코드가 모두 실행된 후에 (코드 2)가 실행됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;suspend가 코루틴을 가장 잘 나타내는 이유는 subRoutine()이 실행되면서 호출한 측의 코드를 잠시 멈췄지만 스레드의 중단이 없기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 이 코드를 스레드로 작성했다면??&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;부모에 해당하는 &amp;lsquo;(코드 1)&amp;rsquo;이 동작하는 스레드를 멈춰야만 가능한데, 코루틴에서는 부모 루틴의 상태 값을 저장한 후 subRoutine()을 실행하고, 다시 subRoutine()이 종류된 후 부모 루틴의 상태 값을 복원하는 형태로 동작하므로 스레드에는 영향을 주지 않습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 구조가 스레드의 동시성에서 발생할 수 있는 성능 저하도 막아줍니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;&lt;code&gt;suspend fun subRoutine() {
    for (i in 0..10) {
        Log.d(&quot;subRoutine&quot;, &quot;$i&quot;)
    }
}

CoroutineScope(Dispatchers.Main).launch {
    // (코드 1)
    subRoutine()
    // (코드 2)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Dispatcher&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;코루틴이 실행될 스레드를 지정하는 것이라고 생각하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;-디스패처의 종류&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코루틴이 실행될 스레드를 정하는 디스패처&lt;span&gt;&amp;nbsp;&lt;/span&gt;(Dispatcher)&lt;span&gt;&amp;nbsp;&lt;/span&gt;는 IO, Main, Default, Unconfined 등이 있는데, 모두 사용할 필요는 없고 우선은 IO와 Main을 잘 조합해서 사용하면 됩니다.&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #111111; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Dispatchers.Default&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;CPU를 많이 사용하는 작업을 백그라운드 스레드에서 실행하도록 최적화되어 있는 디스패처입니다. 안드로이드의 기본 스레드풀 (Thread Pool)을 사용합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f5f5f5;&quot;&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Dispatchers.IO&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;이미지 다운로드, 파일 입출력 등의 입출력에 최적화되어 있는 디스패처입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Dispatchers.Main&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;안드로이드의 기본 스레드에서 코루틴을 실행하고 UI와 상호작용에 최적화되어 있는 디스패처입니다. 택스트뷰에 글자를 입력해야 할 경우 Main 컨텍스트를 사용해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f5f5f5;&quot;&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Dispatchers.Unconfined&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;자신을 호출한 컨텍스트를 기본으로 사용하는데, 중단 후 다시 실행하는 시점에 컨텍스트가 바뀌면 자신의 컨텍스트도 다시 실행하는 컨텍스트를 따라갑니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; withContext로 디스패처 분리&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;suspend 함수를 코루틴 스코프에서 호출할 때 호출한 스코프와 다른 디스패처를 사용할 때가 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 호출 측 코루틴은 main 디스패처에서 UI를 제어하는데, 호출되는 suspend함수는 디스크에서 파일을 일거와야 하는 경우가 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이럴 때 withCotext를 사용해서 호출되는 suspend 함수의 디스패처를 IO로 변경할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;호출되는 suspend 함수에 반환 값이 있다면 변숭 저장하고 사용할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;&lt;code&gt;suspend fun readFile(): String {
    return &quot;파일 내용&quot;
}

CoroutineScope(Dispatchers.Main).launch {
    // 화면 처리
    val result = withContext(Dispatchers.IO) {
        readFile()
    }
    Log.d(&quot;코루틴&quot;, &quot;파일 결과 = $result&quot;)
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>CS/문법_Kotlin</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/53</guid>
      <comments>https://java0u0.tistory.com/53#entry53comment</comments>
      <pubDate>Sun, 12 Nov 2023 10:49:03 +0900</pubDate>
    </item>
    <item>
      <title>Ch_ 13 파일 입출력과 스레드 처리</title>
      <link>https://java0u0.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;chapter13 내용 중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;02. 스레드에 대해 포스팅 해보겠습니다!&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 프로세스와 스레드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로세스 : 특정 프로그램을 task로 보고 이 task를 실행단위로 처리. 특히 멀티스레드를 지원하면서 스레드 단위로 프로그램을 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스레드 : 프로세스 내에 작은 단위로 실행할 수 있는 기능. 하나의 프로세스에서 처리되는 스레드는 프로그램을 실행한 스택을 따로 생성하지만, 메모리 등은 프로세스에서 제공하는 것을 공유해서 처리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로세스의 자원을 사용해서 자기 스레드가 실행되는 동안 다른 스레드를 처리하지 못하게&lt;u&gt; 블로킹&lt;/u&gt;한다. 그 다음에 자기 스레드가 블로킹되면 다른 스레드가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로세스는 보통 기본 메인 스레와 다른 작업을 실행하는 별도의 스레드로 관리된다. 별도의 스레드를 생성하지 않으면 메인스레드 하나로 프로그램을 처리한다. 멀티 스레드 처리는 메인 스레드 외에 별도의 스레드를 생성해서 프로그램을 나눠 처리하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 프로세스와 스레드 확인&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코어 개수 = Runtime.getRuntime().availableProcessors()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재스레드 개수 = Thread.currentThread()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 쓰레드 상태 알아보기&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드를 만들려면 Thread 클래스를 상속하거나 Runnable 인터페이스와 Thread 클래스를 사용해서 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start : 실제 스레드 환경을 구성한 환경을 실행하는 메서드. 이 메서드를 실행해야 내부에 있는 run메서드가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run : 스레드 클래스를 정의할 때 내부에서 실행될 코드를 작성하는 메서드&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;join : 스레드가 실행된 다음 종료할 때까지 기다린다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sleep : 스레드를 잠시 중단하고 다른 스레드를 처리한 후 다시 자기 스레드를 작동할 수 있게 만든다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main(){
    println(&quot;클래스 참조 :  ${Thread::class}&quot;)
    println(&quot;인터페이스 참조 : ${Runnable::class}&quot;)
    println(&quot;현재 스레드 개수 : ${Thread.activeCount()}&quot;)
    println(&quot; 현재 스레드 : ${Thread.currentThread()} &quot;)

    fun &amp;lt;T:Any&amp;gt; T.dir():Set&amp;lt;String&amp;gt;{
        val a = this.javaClass.kotlin
        println(a.simpleName)
        val ll = a.members.map { it.name }
        return ll.toSet()
    }

    val tr1 = Thread()
    println(&quot;스레드 멤버 개수 : ${tr1.dir().count()}&quot;)
    println(&quot;스레드 그룹 : ${tr1.threadGroup}&quot;)
    println(&quot;스레드 생존여부 ${tr1.isAlive}&quot;)
    tr1.start()
    println(&quot;스레드 생존여부 ${tr1.isAlive}&quot;)
    tr1.join()
}
// 결과 
클래스 참조 :  class java.lang.Thread
인터페이스 참조 : class java.lang.Runnable
현재 스레드 개수 : 2
현재 스레드 : Thread[main,5,main]
Thread
스레드 멤버 개수 : 88
스레드 그룹 : java.lang.ThreadGroup[name=main,maxpri=10]
스레드 생존여부 false
스레드 생존여부 true

Process finished with exit code 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 스레드 생성 &amp;lt;- 스레드 클래스 상속&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드를 정의할 때는 run 메서드를 재정의하지만, 내부의 start메서드가 자동 실행되면서 run 메서드를 실행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-join은 스레드를 처리할 때 메인스레드가 먼저 종료할 수 있으므로 현재 진행 중인 스레드가 모두 처리될 때까지 기다리려고 join메서드를 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-실행된 결과를 보면 메인스레드와 보조 스레드를 사용해서 각각의 기능을 처리한 것을 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;import kotlin.system.exitProcess

fun exec(tr: Thread){
    println(&quot;${tr}: 보조 스레드 작동중&quot;)
}
class MyThread: Thread(){
    override fun run() {
        super.run()
        val tr = Thread.currentThread()
        exec(tr)
        println(&quot;${tr} : 보조 스레드 종료&quot;)
    }
}

fun main(){
    val mtr =Thread.currentThread()
    println(&quot;${mtr} : 메인스레드 작동중&quot;)
    val myThread = MyThread()
    myThread.start()
    exec(myThread)
    println(&quot;${mtr}: 대기중&quot;)
    myThread.join()
}
// 결과 
Thread[main,5,main] : 메인스레드 작동중
Thread[Thread-0,5,main]: 보조 스레드 작동중
Thread[Thread-0,5,main]: 보조 스레드 작동중
Thread[main,5,main]: 대기중
Thread[Thread-0,5,main] : 보조 스레드 종료&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 스레드 실행하면서 상태 확인&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드의 상태여부를 확인하면서 스레드가 작동하는지 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;u&gt; 한번 사용한 스레드는 다시 시작할 수 없다, 스레드는 실행이 필요할 때마다 생성해서 start 메서드&lt;/u&gt;로 실행해야 한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class SimpleThread:Thread(){
    override fun run() {
        super.run()
        println(&quot;현재 스레드 이름 : ${this.name}&quot;)
    }
}
fun main(){
    println(&quot;메인 스레드 : ${Thread.currentThread()}&quot;)
    val thread =SimpleThread()
    thread.run()
    println(&quot;스레드 활동여부1 : &quot; + thread.isAlive)
    thread.join()
    println(&quot;스레드 활동여부2 : &quot; + thread.isAlive)
    thread.run()
    thread.join()
    println(&quot;스레드 활동여부3 : &quot; + thread.isAlive) // start를 하지 않았기에 false로만 나온다.

    val th2 = SimpleThread()
    th2.start()
    println(&quot;스레드 활동여부4 : &quot; + th2.isAlive)
    th2.join()
    println(&quot;스레드 활동여부5 : &quot; + th2.isAlive)
}
// 결과 
메인 스레드 : Thread[main,5,main]
현재 스레드 이름 : Thread-0
스레드 활동여부1 : false
스레드 활동여부2 : false
현재 스레드 이름 : Thread-0
스레드 활동여부3 : false
스레드 활동여부4 : true
현재 스레드 이름 : Thread-1
스레드 활동여부5 : false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 인터페이스로 스레드 정의 후 스레드 생성&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Runnable 인터페이스를 상속해서 run 메서드를 클래스 내에 구현하고 이 클래스의 객체를 생성해 Thread 클래스의 생성자에 전달해서 스레드 객체를 만든다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;class First:Runnable{
    override fun run() {
        println(&quot;Helper 5000 대기중&quot;)
        Thread.sleep(5000)
    }
}
class Second:Runnable{
    override fun run() {
        println(&quot;Tester 5000 대기중&quot;)
        Thread.sleep(5000)
    }
}
fun main(){
    val obj1 = Second()
    val th1 =Thread(obj1)

    val obj2 = First()
    val th2 = Thread(obj2)

    th1.start()
    println(&quot;스레드 1 이름 : &quot;+th1.name)
    println(&quot;스레드 1 ID : &quot;+ th1.id)
    println(&quot;스레드 1 우선순위 : &quot;+ th1.priority)
    println(&quot;스레드 1 상태 : &quot;+ th1.state)
    th2.start()
    println(&quot;스레드 2 이름 : &quot;+th1.name)
    println(&quot;스레드 2 ID : &quot;+ th1.id)
    println(&quot;스레드 2 우선순위 : &quot;+ th1.priority)
    println(&quot;스레드 2 상태 : &quot;+ th1.state)
    th1.join()
    th2.join()
}

// 결과 
스레드 1 이름 : Thread-0
스레드 1 ID : 16
스레드 1 우선순위 : 5
스레드 1 상태 : BLOCKED

스레드 2 이름 : Thread-0
스레드 2 ID : 16
스레드 2 우선순위 : 5
Tester 5000 대기중
스레드 2 상태 : BLOCKED
Helper 5000 대기중
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; object 표현식으로 스레드 생성&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;인터페이스 상속과 다르게 더 간단히 object 표현식으로 객체를 생성한다. 하지만 스레드 객체를 상속받는 점은 결국 같다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;fun createThread():Thread{
    return object : Thread(){ // 스레드 객체 상속
        override fun run() {
            super.run()
            println(&quot;5000 중단 &quot;)
            Thread.sleep(5000)
        }}
}
fun main(){
    val th1 =createThread()
    val th2 = createThread()

    th1.start()
    println(&quot;스레드 1 이름 : &quot;+th1.name)
    println(&quot;스레드 1 ID : &quot;+ th1.id)
    println(&quot;스레드 1 우선순위 : &quot;+ th1.priority)
    println(&quot;스레드 1 상태 : &quot;+ th1.state)
    th2.start()
    println(&quot;스레드 2 이름 : &quot;+th1.name)
    println(&quot;스레드 2 ID : &quot;+ th1.id)
    println(&quot;스레드 2 우선순위 : &quot;+ th1.priority)
    println(&quot;스레드 2 상태 : &quot;+ th1.state)
    th1.join()
    th2.join()
}

// 결과 
5000 중단
스레드 1 이름 : Thread-0
스레드 1 ID : 16
스레드 1 우선순위 : 5
스레드 1 상태 : TIMED_WAITING
스레드 2 이름 : Thread-0
스레드 2 ID : 16
스레드 2 우선순위 : 5
스레드 2 상태 : TIMED_WAITING
5000 중단&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 코틀린에서 제공하는 스레드 함수 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;val myThread = thread(){&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;println(&quot;${Thread.currentThread()} : 보조 스레드 작동&quot; // 스레드 함수에 익명함수 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;} // 코틀린에서 제공하는 함수로 스레드를 만든다. 이 함수의 인자로 함수를 받으므로 람다표현식으로 함수를 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 사용자 정의 스레드 함수 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-스레드 함수의 매개변수는 기본으로 start가 있다. 즉 스레드를 자동으로 실행하는 것인데 isDeamon은 백그라운드에서 스레드를 실행하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-데몬 :&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;멀티태스킹 운영체제&lt;/span&gt;&lt;/span&gt;&lt;span&gt;에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;데몬&lt;/b&gt;&lt;span&gt;은 사용자가 직접적으로 제어하지 않고, 백그라운드에서 돌면서 여러 작업을 하는 프로그램이다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일반 스레드와 비교해서 가장 크게 다른 점은 프로그램이 종료할 때 발생한다. 사용자의 어플리케이션이 종료될 때, 사용자가 생성한 모든 일반 스레드으 수행이 종료되어야 JVM이프로세스가 종료된다. 하지만 데몬은 우선순위가 낮은 스레드로 JVM은 데몬스레드르의 종료를 기다리지 않고 셧다운 작업을 진행한다. 이런 특성으로 종료사 특별한 처리가 필요한 작업의 경우 데몬스레드에서 실행되도 되는지 생각해봐야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 주 스레드가 종료되면 데몬 스레드는 강제적으로 자동 종료된다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import kotlin.concurrent.thread

fun makethread(start: Boolean = true,
               isDamon: Boolean = false,
               contextClassLoader: ClassLoader? = null,
               name: String? = null,
               priority: Int = -1,
               block: () -&amp;gt; Unit
): Thread {
    val thread = object : Thread() {
        override fun run() {
            super.run()
            block()
        }
    }
    if (isDamon) thread.isDaemon = true // 데몬처리
    if (priority &amp;gt; 0) thread.priority = priority
    name?.let { thread.name = it }
    contextClassLoader?.let {
        thread.contextClassLoader = it // 클래스 로더 처리
    }
    return thread
}

fun main() {
    val ss = &quot;스레드 처리 : ${Thread.currentThread()}&quot;
    val th1 = makethread(block = { println(ss) })

    println(th1.javaClass)
    th1.start()
    println(&quot;스레드 종료&quot;)
    th1.join()
}
// 결과 
class ExcKt$makethread$thread$1
스레드 종료
스레드 처리 : Thread[#1,main,5,main]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Thread[#1,main,5,main]에서 #1 : 스레드의 이름이 따로 설정되지 않았기 때문에 JVM이 자동으로 할당한 이름입니다. 첫번째 &quot;main&quot;은 메인스레드 이름, &quot;5&quot;는 스레드의 우선순위, &lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: start;&quot;&gt;마지막 &quot;main&quot;은 스레드 그룹의 이름&lt;/span&gt;&amp;nbsp;나타냅니다. &quot;main&quot; 스레드 그룹은 JVM이 시작할 때 생성되며, 메인 스레드를 포함하는 기본 스레드 그룹입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 쓰레드 풀사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드를 무작정 만들면 컴퓨터의 자원을 너무 많이 사용한다. 특정 개수의 스레드를 풀(pool)을 만들어서 특정 스레드를 계속 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 스레드 여러개 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 처리의 스레드를 여러 개 생성해서 리스트 객체에 넣고 이를 내부 순환으로 실행&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import kotlin.concurrent.thread

fun main() {
    var count = 0
    val threads = List(10) { // read-only list 
        thread {
            Thread.sleep(10)
            print(&quot;.&quot;)
            count += 1
            print(count)
        }
    }
    threads.forEach(Thread::join) // 각각의 스레드가 종려될때까지 기다림
}
// 결과
.1.2.3.4.5.6.7.8.9.10&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 스레드 풀을 만들어 스레드 실행&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스레드 풀의 스레드는 execute로 실행. 실행할 코드는 람다표현식으로 전달&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.concurrent.Executors

fun main() {
    val executor = Executors.newFixedThreadPool(2) // 특정 스레드 개수만큼만
    var count = 0
    repeat(3) {
        executor.execute {
            Thread.sleep(10)
            println(Thread.currentThread().name)
            count++
            println(count)
        }
    }
    println(executor.isTerminated) // 스레드 풀 미종료
    executor.shutdown() // 스레드 풀 종료
    println(executor.isShutdown) // 스레드 풀 종료 확인
}
// 결과 
false
true
pool-1-thread-2
1
pool-1-thread-1
2
pool-1-thread-2
3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 스레드 풀을 사용해 처리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 스레드 풀에서 스레드를 실행하는 execute 메서드가&amp;nbsp; Runnable 객체를 인자로 전달하면 내부적으로 스레드를 실행해준다. 스레드를 5개 만들고 스레드 풀은3개로 지정.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

class Task(val name:String) : Runnable{
    override fun run() {
        val d = Date()
        val ft = SimpleDateFormat(&quot;hh:mm:ss&quot;)
        println(&quot;초기 시간 확인&quot;+&quot;task name- &quot;+name+&quot;=&quot;+ft.format(d))
        Thread.sleep(100)
    }
}
fun main(){
    val MAX_T=5
    val r1=Task(&quot;task1&quot;) // 테스크 객체 생성
    val r2=Task(&quot;task2&quot;)
    val r3=Task(&quot;task3&quot;)
    val r4=Task(&quot;task4&quot;)
    val r5=Task(&quot;task5&quot;)

    val pool = Executors.newFixedThreadPool(MAX_T) // 스레드 풀 생성
    pool.execute(r1) // 풀에서 스레드 실행
    pool.execute(r2)
    pool.execute(r3)
    pool.execute(r4)
    pool.execute(r5)
    pool.awaitTermination(3000L,TimeUnit.MICROSECONDS) // 스레드를 특정 시간까지 대기한 후 종료 
}

// 결과 
초기 시간 확인task name- task4=11:34:15
초기 시간 확인task name- task3=11:34:15
초기 시간 확인task name- task1=11:34:15
초기 시간 확인task name- task2=11:34:15
초기 시간 확인task name- task5=11:34:15&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 스레드 풀을 사용해서 순환 실행&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스레드 풀을 사용해서 5개의 스레드가 반복해서 사용됨.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.concurrent.Executors

fun main(){
    val task =object :Runnable{
        override fun run() {
            println(&quot;Thread : &quot;+Thread.currentThread().name)
        }
    }
    val service= Executors.newFixedThreadPool(5)
    for(i in 1..10){
        service.submit(task) // 스레드 실행
    }
    service.shutdown()
}
// 결과 
Thread : pool-1-thread-3
Thread : pool-1-thread-2
Thread : pool-1-thread-4
Thread : pool-1-thread-1
Thread : pool-1-thread-5
Thread : pool-1-thread-1
Thread : pool-1-thread-2
Thread : pool-1-thread-4
Thread : pool-1-thread-5
Thread : pool-1-thread-3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 스레드 하나씩 실행 반환값 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드가 실행된 결과를 반환받아서 그 결과를 확인하거나 다른 용도로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스레드 풀에서 submit 메서드로 스레드를 실행하면 스레드의 결과를 반환값으로 받을 슈 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반환 결과를 get메서드로 가져오면 스레드 반환결과를 확인 할 수 있다. 또한, 이 get 메서드에 특정시간을 넘기면 특정식간을 기다렸다가 정보를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반환결과를 cancel 메서드로 스레드를 중단할 수 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

fun main() {
    val callable = {
        Thread.sleep(10)
        println(&quot;Thread:&quot; + Thread.currentThread().name)
        Thread.currentThread().name+&quot;반환값&quot;
    }
    val executeService = Executors.newFixedThreadPool(2)
    val future1 = executeService.submit(callable)
    println(&quot;퓨쳐 객체 :&quot; + future1.javaClass)

    val future2 = executeService.submit(callable)
    val future3 = executeService.submit(callable)

    var value = future1.get() // 작업 끝날때까지 기다려 값 받기
    println(&quot;value1 : &quot; +value)
    var canceled = future2.cancel(true) // 작업취소, 취소 여부를 돌려받는다.
    println(&quot;canceled : &quot;+canceled)
    value = future3.get(500, TimeUnit.MILLISECONDS) // 500밀리세컨드 동안만 기다려 값 받기
    println(&quot;value2 : &quot; +value)
    
}
// 결과
퓨쳐 객체 :class java.util.concurrent.FutureTask
Thread:pool-1-thread-1
Thread:pool-1-thread-2
value1 : pool-1-thread-1반환값
canceled : false
Thread:pool-1-thread-2
value2 : pool-1-thread-2반환값&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancel(true)&amp;nbsp;현재&amp;nbsp;실행&amp;nbsp;중인&amp;nbsp;경우에도&amp;nbsp;즉시&amp;nbsp;중단을&amp;nbsp;시도해야&amp;nbsp;함을&amp;nbsp;나타냅니다.&amp;nbsp;즉,&amp;nbsp;스레드에&amp;nbsp;Interrupt&amp;nbsp;신호를&amp;nbsp;보내&amp;nbsp;작업을&amp;nbsp;중단시키려고&amp;nbsp;시도합니다.&amp;nbsp;그리고&amp;nbsp;작업이&amp;nbsp;성공적으로&amp;nbsp;취소되었으면&amp;nbsp;true,&amp;nbsp;그렇지&amp;nbsp;않으면&amp;nbsp;false를&amp;nbsp;반환합니다. &lt;br /&gt;반면에,&amp;nbsp;cancel(false)를&amp;nbsp;호출하면&amp;nbsp;현재&amp;nbsp;실행&amp;nbsp;중인&amp;nbsp;작업을&amp;nbsp;중단하지&amp;nbsp;않고,&amp;nbsp;만약&amp;nbsp;작업이&amp;nbsp;대기&amp;nbsp;중이라면&amp;nbsp;실행되지&amp;nbsp;않도록&amp;nbsp;취소합니다.&amp;nbsp;실행&amp;nbsp;중인&amp;nbsp;작업에는&amp;nbsp;영향을&amp;nbsp;주지&amp;nbsp;않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;❓var canceled = future2.cancel(true) 값이 false인 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;future2.cancel(true) 호출이 취소에 실패한 이유는 future2가 이미 실행되었거나 실행 중이었기 때문일 가능성이 높습니다.&lt;br /&gt;위 코드에서 future1, future2, future3 모두 거의 동시에 제출(submit)됩니다. 고정 스레드 풀의 크기가 2이기 때문에 future1과 future2는 거의 동시에 실행을 시작할 것입니다. 따라서 future2.cancel(true)가 호출될 때, future2는 이미 실행 중일 가능성이 높습니다.&lt;br /&gt;cancel(true)&amp;nbsp;메서드는&amp;nbsp;실행&amp;nbsp;중인&amp;nbsp;작업을&amp;nbsp;중단하려고&amp;nbsp;시도하지만,&amp;nbsp;작업&amp;nbsp;내부에서&amp;nbsp;인터럽트&amp;nbsp;상태를&amp;nbsp;적절하게&amp;nbsp;처리하지&amp;nbsp;않으면&amp;nbsp;실제로&amp;nbsp;중단되지&amp;nbsp;않을&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-chat gpt 센빠이가 알려주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 ) 데몬 - &lt;b&gt;&lt;a href=&quot;https://hbase.tistory.com/285&quot;&gt;https://hbase.tistory.com/285&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>CS/문법_Kotlin</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/52</guid>
      <comments>https://java0u0.tistory.com/52#entry52comment</comments>
      <pubDate>Tue, 31 Oct 2023 23:00:31 +0900</pubDate>
    </item>
    <item>
      <title>api 응답 값 에러 시 확인 절차</title>
      <link>https://java0u0.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 프론트에서 api통신하는데 에러가 났다면 서버가 정상인지 확인한다.(+ 프론트에서 log찍고 응답코드 확인 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swagger 사용 안 한다면 postman을 통해 서버가 제대로 된 응답값을 반환하는지부터 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. swagger나 postman에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;서버의 응답값이&lt;/span&gt; 잘 도착한다면 응답 코드를 봐본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;성공적인 응답값 코드는 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;[200,300) 사이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3. 서버의 응답값이 너무 멀쩡하다 = 프론트 문제이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 원인은 다양하겠지만 생각나는거 적자면...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 프론트에서 response.code를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- response.code가 400이라면 프론트에서 요청값 형식을 서버에 맞지 않게 전달한거다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 서버에서 요구하는 형식이 파라미터인지, body인지 등 확인하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;(&lt;/span&gt;parameter는 Http Request의 쿼리 매개변수, body는 Http Request의 body)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2) 프론트에서 response.code를 특정 숫자로 제한했는지 확인하기 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;예를 들면 if(response.code = 200) 인 경우만 성공으로 걸어둔건지 확인 필수&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;성공적인 응답값 코드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[200,300) 사이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 스웨거2 사진스..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/58DR7/btsuRIJJPcY/kH80eEycZH6NqeufylPa00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/58DR7/btsuRIJJPcY/kH80eEycZH6NqeufylPa00/img.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;681&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;32.86&quot; data-filename=&quot;blob&quot; style=&quot;width: 32.4758%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/58DR7/btsuRIJJPcY/kH80eEycZH6NqeufylPa00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F58DR7%2FbtsuRIJJPcY%2FkH80eEycZH6NqeufylPa00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1110&quot; height=&quot;681&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Hohn/btsu8iQozs3/SWxYcHcdkMvGhZkZRke2k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Hohn/btsu8iQozs3/SWxYcHcdkMvGhZkZRke2k0/img.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;251&quot; data-is-animation=&quot;false&quot; style=&quot;width: 66.3615%;&quot; data-widthpercent=&quot;67.14&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Hohn/btsu8iQozs3/SWxYcHcdkMvGhZkZRke2k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Hohn%2Fbtsu8iQozs3%2FSWxYcHcdkMvGhZkZRke2k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;836&quot; height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;왼쪽은 http통신 시 parameter 오른쪽은 body 형식&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>앱개발/StudyHub 앱 출시 회고</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/50</guid>
      <comments>https://java0u0.tistory.com/50#entry50comment</comments>
      <pubDate>Thu, 21 Sep 2023 14:44:58 +0900</pubDate>
    </item>
    <item>
      <title>Ch_10 함수 추가 사항 알아보기</title>
      <link>https://java0u0.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;목차-&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 함수형 프로그래밍이란?&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;a. 일급 객체 함수(first Class function)&lt;/li&gt;
&lt;li&gt;b. 지연 평가 함수 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 고차함수, 합성함수, 재귀함수 알아보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 함수의 추가 기능 알아보기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;a. 스코프 함수&lt;/li&gt;
&lt;li&gt;b. SAM 인터페이스(Single Abstract Method)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;01 함수형 프로그래밍이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a. 일급 객체 함수(first Class function)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;▪️ 함수를 변수에 할당할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;▪️&lt;span&gt; 함수를 매개변수 인자로 전달할 수 있음&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;▪️&lt;span&gt; 함수를 반환값으로 사용할 수 있음&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;▪️&lt;span&gt; 함수를 컬렉션 자료구조에 할당할 수 있음&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이처럼 일급객체로 만든 것은 함수도 정수나 문자처럼 객체로 사용할 수 있는 것을 말한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    val add1 = fun(x: Int, y: Int): Int = x + y // 익명함수를 변수에 할당
    val add2 = { x: Int, y: Int -&amp;gt; x + y } // 람다표현식을 변수에 할당
    val add3: (Int, Int) -&amp;gt; Int = { x: Int, y: Int -&amp;gt; x + y } // 위 람다식에서 자료형을 추가
    println(add1(10, 20)) // 30 
    println(add2(10, 20)) // 30
    println(add3(10, 20)) // 30

    fun highfunc(sum: (Int, Int) -&amp;gt; Int, a: Int, b: Int): Int = sum(a, b) // 매개변수에 함수 자료형 정의
    println(highfunc({ x: Int, y: Int -&amp;gt; x + y }, 10, 20))  // 30, 람다 표현식을 인자로 전달

    fun refunc(): (Int, Int) -&amp;gt; Int { // 함수 반환자료형의 함수
        return { x: Int, y: Int -&amp;gt; x + y } // 람다표현식으로 반환
    }

    val rf = refunc() // 함숳 호출(객체로)
    println(rf(10, 20)) // 30


}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b. 지연 평가 함수 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 정의 를 필요할 경우 호출 처리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-최상위 속성을 정의할 때 by로 속성위임에 lazy함수를 사용할 수 있다. 이 속성을 참조할 때 람다표현식이 실행돼 속성의 초깃값이 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-무한 시퀀스도함수인데 이 시퀀스를 만들면 실제 실행이 되지 않고 액션 toList메서드가 실행되어야 무한 시퀀스가 실행되어 take에 전달된 정수만큼만 원소를 가져와 리스트로 반환.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-외부함수와 내부함수를 정의 후 외부함수를 실행해도 최종적으로 내부 함수가 실행되어야 함수가 종료된다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    val func by lazy { { x: Int -&amp;gt; x } }
    println(func(100))

    val seq = generateSequence(0) { it + 100 } // 무한 시퀀스 정의
    println(seq.take(5).toList()) // 특정 시점에 값을 실행

    fun outer(x: Int): (Int) -&amp;gt; Int { // 부분함수 정의
        fun inner(y: Int): Int = x + y // 내부함수가 실제 연산 결과 반환
        return ::inner // 함수 참조로 반환
    }

    val out = outer(100)
    println(out(200))
}
// 결과값
100
[0, 100, 200, 300, 400]
300&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한 시퀀스 함수에 대해&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;fun&amp;nbsp;&amp;lt;T&amp;nbsp;:&amp;nbsp;Any&amp;gt;&amp;nbsp;generateSequence( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;seed:&amp;nbsp;T?, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;nextFunction:&amp;nbsp;(T)&amp;nbsp;-&amp;gt;&amp;nbsp;T? &lt;br /&gt;):&amp;nbsp;Sequence&amp;lt;T&amp;gt; &lt;br /&gt;Returns&amp;nbsp;a&amp;nbsp;sequence&amp;nbsp;&lt;u&gt;&lt;b&gt;defined&amp;nbsp;by&amp;nbsp;the&amp;nbsp;starting&amp;nbsp;value&amp;nbsp;seed&amp;nbsp;&lt;/b&gt;&lt;/u&gt;and&amp;nbsp;the&amp;nbsp;function&amp;nbsp;nextFunction,&amp;nbsp;which&amp;nbsp;is&amp;nbsp;invoked&amp;nbsp;to&amp;nbsp;calculate&amp;nbsp;the&amp;nbsp;next&amp;nbsp;value&amp;nbsp;based&amp;nbsp;on&amp;nbsp;the&amp;nbsp;previous&amp;nbsp;one&amp;nbsp;on&amp;nbsp;each&amp;nbsp;iteration. &lt;br /&gt;The&amp;nbsp;sequence&amp;nbsp;produces&amp;nbsp;values&amp;nbsp;&lt;u&gt;until&amp;nbsp;it&amp;nbsp;encounters&amp;nbsp;first&amp;nbsp;null&amp;nbsp;value.&amp;nbsp;If&amp;nbsp;seed&amp;nbsp;is&amp;nbsp;null,&amp;nbsp;an&amp;nbsp;empty&amp;nbsp;sequence&amp;nbsp;is&amp;nbsp;produced.&lt;/u&gt; &lt;br /&gt;The&amp;nbsp;sequence&amp;nbsp;can&amp;nbsp;be&amp;nbsp;iterated&amp;nbsp;multiple&amp;nbsp;times,&amp;nbsp;each&amp;nbsp;time&amp;nbsp;starting&amp;nbsp;with&amp;nbsp;seed.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;c. 함수 인터페이스를 상속해 실행 연산 함수 구현&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    object : () -&amp;gt; Unit { // object표현식 정의
        override fun invoke(): Unit { // 함수 인터페이스에 실행연산자가 없어서 구현
            println(&quot;함수 객체가 만들어지고 실행&quot;)
        }
    }

    val f = AddFunc() // 객체 생성
    println(f(10, 20))

    fun func(): (Int) -&amp;gt; Int {
        return { x: Int -&amp;gt;
            println(&quot;함수 표현식 전달&quot;);
            x
        }
    }

    val c = object : () -&amp;gt; ((Int) -&amp;gt; Int) {
        override fun invoke(): (Int) -&amp;gt; Int {
            println(&quot;함수 반환&quot;)
            return { y: Int -&amp;gt; y }
        }
    }
    println(func().invoke(100))
    val funC=c()
    println(funC.invoke(200))
}

class AddFunc : (Int, Int) -&amp;gt; Int {
    override fun invoke(p1: Int, p2: Int): Int {
        println(&quot;클래스 실행연산자 호출&quot;)
        return p1 + p2
    }
}
// 결과값
클래스 실행연산자 호출
30
함수 표현식 전달
100
함수 반환
200&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;d. 커링함수 알아보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 함수에서 매개변수를 분리해 외부함수와 내부함수로 저장해서 처리가능. 이처럼 함수를 부분으로 나눠 처리하는 것을 커링 함수라고 한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    fun a(n: Int): (d: Int) -&amp;gt; Int {
        var acc = n
        return { x -&amp;gt;
            acc += x;
            acc // Int값 반환
        }
    }

    val a100 = a(100)
    println(a100(5)) // 105
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e. 연속 호출하는 체이닝 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수나 메서드가 연속으로 호출돼서 처리하는 것을 메서드 체이닝이라고 한다. 객체를 반환하면 그 내부 메서드를 연속에서 실행할 수 있다. 다만 너무 많이 사용하면 실제 코드를 이해하는데 너무 어려울수 있어 적당히 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-함수 연속 호출&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    fun Outer(x: Int): (Int) -&amp;gt; Int {
        fun inner(y: Int): Int {
            return x + y
        }
        return ::inner
    }
    println(Outer(100)(200)) // 300, 함수 연속 실행
    // 계층 처리
    val out = Outer(100)
    println(out(200)) // 300 

    val lamda = { x: Int -&amp;gt; { y: Int -&amp;gt; { z: Int -&amp;gt; x + y + z } } }
    println(lamda(100)(200)(300)) // 600, 함수 연속 실행
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메서드 체인 처리&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {

    val c = Car(&quot;감자&quot;, &quot;레드&quot;)
    c.changeOwner(&quot;돌자반&quot;).repair(&quot;파랑&quot;).info() // 소유자=돌자반, 색상 =파랑
}
class Car(var ownerName: String, var color: String) {
    fun changeOwner(newName: String): Car {
        this.ownerName = newName
        return this
    }

    fun repair(newColor: String): Car {
        this.color = newColor
        return this
    }

    fun info(): Unit =
        println(&quot;소유자=$ownerName, 색상 =$color&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;02 고차함수, 확장함수, 재귀함수 알아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;a. 고차함수란 함수를 객체로 생각해서 인자로 전달되거나 반환값으로 처리되는 함수 패턴을 말한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;typealias f = (Int, Int) -&amp;gt; Int

fun main() {
    fun one(vararg x: Int, op: f): Int {
        return x.toList().reduce(op)
    }
    println(one(1, 2, 3, 4, op = { x: Int, y: Int -&amp;gt; x + y })) // 10, 함수를 매개변수로 받는 고차함수

    fun add(x: Int, y: Int): Int = x + y
    println(one(1, 2, 3, 4, 5, op = ::add)) // 15

    fun two(): f {
        return { x, y -&amp;gt; x + y }
    }
    println(two()(10, 20)) // 30
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b. 합성함수의 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 함수를 하나의 함수로 연결하는 것을 합성함수라고 한다. 함수를 구성할려면 함수의 매개변수와 자료형이 일치해야하므로 함수를 합칠때는 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 두 함수의 매개변수 개수와 반환자료형이 같아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-두 함수를 결합한 함수도 두 함수의 매개변수와 자료형이 같아야한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    fun composeF(f: (Int) -&amp;gt; Int, g: (Int) -&amp;gt; Int): (Int) -&amp;gt; Int {
        return { p1: Int -&amp;gt; f(g(p1)) }
    }

    val f = { x: Int -&amp;gt; x + 2 }
    val g = { y: Int -&amp;gt; y + 3 }
    val composeFunc = composeF(f, g)

    println(f(g(3))) //8
    println(composeFunc(3)) //8, 3은 p1값
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;c. 재귀함수와 꼬리재귀 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀함수: 재귀할 때마다 함수가 메모리 스택에 올라간다. 성능상의 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼬리 재귀: tailrec 예약어를 붙인다. 내부적으로 코드를 변환해서 메모리 스택을 최소화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점: 누적값을 매개변수로 지정해서 재귀함수와 다른 연산을 처리하지 않고 항상 함수 호출만 발생하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    fun factorial(n: Int): Long {
        if (n == 1) {
            return n.toLong()
        } else {
            return n * factorial(n - 1)
        }
    }

    var result = factorial(4)
    println(result) // 24

    // 꼬리 재귀
    tailrec fun tailFactorial(n: Int, total: Int = 1): Long {
        if (n == 1) {
            return total.toLong()
        } else {
            return tailFactorial(n - 1, n * total)
        }
    }
    result = tailFactorial(4)
    println(&quot;꼬리 재귀 연산 : $result&quot;) // 24

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;03 함수의 추가 기능 알아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a. 스코프 함수&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/txJev/btsudNLprmR/QGDiqtg82UfgmrIJY1pcYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/txJev/btsudNLprmR/QGDiqtg82UfgmrIJY1pcYK/img.png&quot; data-alt=&quot;출처 : https://medium.com/@limgyumin/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%9D%98-apply-with-let-also-run-%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80-4a517292df29&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/txJev/btsudNLprmR/QGDiqtg82UfgmrIJY1pcYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtxJev%2FbtsudNLprmR%2FQGDiqtg82UfgmrIJY1pcYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;178&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://medium.com/@limgyumin/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%9D%98-apply-with-let-also-run-%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80-4a517292df29&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. apply&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;inline&amp;nbsp;fun&amp;nbsp;&amp;lt;T&amp;gt;&amp;nbsp;&lt;u&gt;&lt;b&gt;T&lt;/b&gt;&lt;/u&gt;.apply( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;block:&amp;nbsp;&lt;u&gt;&lt;b&gt;T.()&lt;/b&gt;&lt;/u&gt;&amp;nbsp;-&amp;gt;&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;Unit&lt;/span&gt;&lt;/b&gt; &lt;br /&gt;):&amp;nbsp;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;&lt;u&gt;T&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QDrSp/btsut2txeZd/yvCrMKSkYY5cy30zt7svMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QDrSp/btsut2txeZd/yvCrMKSkYY5cy30zt7svMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QDrSp/btsut2txeZd/yvCrMKSkYY5cy30zt7svMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQDrSp%2Fbtsut2txeZd%2FyvCrMKSkYY5cy30zt7svMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;230&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply는 모든 명령이 수행되고 나면 명령들이 적용되어 새로 생성된 인스턴스를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. also&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;inline&amp;nbsp;fun&amp;nbsp;&amp;lt;T&amp;gt;&amp;nbsp;&lt;u&gt;&lt;b&gt;T&lt;/b&gt;&lt;/u&gt;.also( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;block:&amp;nbsp;&lt;u&gt;&lt;b&gt;(T)&lt;/b&gt;&lt;/u&gt;&amp;nbsp;-&amp;gt;&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;Unit&lt;/span&gt;&lt;/b&gt; &lt;br /&gt;):&amp;nbsp;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;u&gt;&lt;b&gt;T&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxVqSm/btsuqChpHzY/sThUfTEqy05o0dWe5I4kTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxVqSm/btsuqChpHzY/sThUfTEqy05o0dWe5I4kTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxVqSm/btsuqChpHzY/sThUfTEqy05o0dWe5I4kTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxVqSm%2FbtsuqChpHzY%2FsThUfTEqy05o0dWe5I4kTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;242&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. run&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;inline&amp;nbsp;fun&amp;nbsp;&amp;lt;T,&amp;nbsp;R&amp;gt;&amp;nbsp;&lt;u&gt;&lt;b&gt;T&lt;/b&gt;&lt;/u&gt;.run( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;block:&amp;nbsp;&lt;u&gt;&lt;b&gt;T.()&lt;/b&gt;&lt;/u&gt;&amp;nbsp;-&amp;gt;&amp;nbsp;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;u&gt;&lt;b&gt;R&lt;/b&gt;&lt;/u&gt;&lt;/span&gt; &lt;br /&gt;):&amp;nbsp;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;u&gt;&lt;b&gt;R&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cX1gm0/btst7SzIy0m/Y1K9O6MsDejerMahg7dmt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cX1gm0/btst7SzIy0m/Y1K9O6MsDejerMahg7dmt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cX1gm0/btst7SzIy0m/Y1K9O6MsDejerMahg7dmt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcX1gm0%2Fbtst7SzIy0m%2FY1K9O6MsDejerMahg7dmt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;240&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;import java.awt.print.Book

fun main() {
    var a = Book(&quot;해로의 모험&quot;, 10000).apply {
        name = &quot;[세일중]&quot; + name
        discount()
    }
    var bookCost = a.run {
        println(&quot;상품명: ${name}, 가격: ${price}&quot;)
        price + 2000
    }
    println(&quot;원가는 $bookCost 입니다.&quot;) // 이 값이 반환
}

class Book(var name: String, var price: Int) {
    fun discount() {
        price -= 2000
    }
}
// 결과값
상품명: [세일중]해로의 모험, 가격: 8000
원가는 10000 입니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply와의 차이점 apply는 객체를 반환하지만 run은 스코프내 실행결과값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. let&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;inline&amp;nbsp;fun&amp;nbsp;&amp;lt;T,&amp;nbsp;R&amp;gt;&amp;nbsp;&lt;u&gt;&lt;b&gt;T.l&lt;/b&gt;&lt;/u&gt;et( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;block:&amp;nbsp;&lt;b&gt;&lt;u&gt;(T)&lt;/u&gt;&lt;/b&gt;&amp;nbsp;-&amp;gt;&amp;nbsp;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;R&lt;/span&gt; &lt;br /&gt;):&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&amp;nbsp;R&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EVerL/btst7TZKPeV/ta7oJpWmcU4srKbpWZh6x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EVerL/btst7TZKPeV/ta7oJpWmcU4srKbpWZh6x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EVerL/btst7TZKPeV/ta7oJpWmcU4srKbpWZh6x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEVerL%2Fbtst7TZKPeV%2Fta7oJpWmcU4srKbpWZh6x0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;243&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. with&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public&amp;nbsp;inline&amp;nbsp;fun&amp;nbsp;&amp;lt;T,&amp;nbsp;R&amp;gt;&amp;nbsp;with( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;u&gt;&lt;b&gt;&amp;nbsp;receiver:&amp;nbsp;T,&lt;/b&gt;&lt;/u&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;block:&amp;nbsp;T.()&amp;nbsp;-&amp;gt;&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;u&gt;R&lt;/u&gt;&lt;/span&gt;&lt;/b&gt; &lt;br /&gt;):&amp;nbsp;&lt;b&gt;&lt;u&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;R&lt;/span&gt;&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ9n0Y/btsugmfbS89/a0rLS3wdBoIKr2KBSfrXX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ9n0Y/btsugmfbS89/a0rLS3wdBoIKr2KBSfrXX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ9n0Y/btsugmfbS89/a0rLS3wdBoIKr2KBSfrXX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ9n0Y%2FbtsugmfbS89%2Fa0rLS3wdBoIKr2KBSfrXX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;583&quot; height=&quot;259&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b. SAM 인터페이스(Single Abstract Method)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 추상메서드만 있는 인터페이스를 보다 단순하게 처리하기 위해 함수를 정의하는 fun 예약어를 인터페이스 앞에 붙여 SAM인터페이스를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점: 상속하고 구현해서 처리할 수도 있고, 상속없이 재정의 해 직접 람다식으로 받아서 처리가능&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val str = object : StringAble { // 인터페이스처럼 상속해서 재정의 가능
        override fun accept(s: String) {
            println(s)
        }
    }
    str.accept(&quot;object 표현식으로 익명 객체 처리&quot;)
    val consume = StringAble { s -&amp;gt; println(s) } // 람다표현식으로 할당
    consume.accept(&quot;바로 람다 표현식을 전달해서 재정의&quot;)
    consume.hello()
}

fun interface StringAble { // SAM인터페이스는 앞에 fun 붙임
    fun accept(s: String) // 추상메서드 한개
    fun hello() = println(&quot;일반메소드&quot;) // 일반메서드
}

//결과값
object 표현식으로 익명 객체 처리
바로 람다 표현식을 전달해서 재정의
일반메소드&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고) 스코프 함수- &lt;a href=&quot;https://haero.tistory.com/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://haero.tistory.com/21&lt;/a&gt;&lt;/p&gt;</description>
      <category>CS/문법_Kotlin</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/48</guid>
      <comments>https://java0u0.tistory.com/48#entry48comment</comments>
      <pubDate>Wed, 13 Sep 2023 13:17:12 +0900</pubDate>
    </item>
    <item>
      <title>Ch_07 클래스 관계 등 추가 사항 알아보기</title>
      <link>https://java0u0.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;-목차&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 속성과 메서드 재정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 특정 자료를 다루는 클래스 알아보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 속성과 메서드 재정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  1.1 속성 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚫ 속성의 메서드 세터를 비공개 처리하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 속성의 변경 제한을 처리하려면 속성의 세터 메서드를 private 처리한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 ++을 어디에 두느냐에 따라 getter호출 수가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 예제의 get호출되는 수가 예상과 달라서 ++과 print문을 옮겨가며 찍어봤다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val counter = Counter()
    for (i in 1..2) {
        counter.inc()
        println(counter.value)
    }

}

class Counter {
    var value: Int = 0
        get() {
            println(&quot;get value $field&quot;)
            return field
        }
        private set // 값 수정은 비공개 처리 -&amp;gt; 클래스 안 메서드로 접근

    fun inc() = ++value // value값 가져올때 1, 더해서 return시 2, 위에서 println 할때 또 get 3
}
// 결과값 
get value 0
get value 1
get value 1
1
get value 1
get value 2
get value 2
2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 디컴파일 한 것이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// ExpKt.java

public final class ExpKt {
   public static final void main() {
      Counter counter = new Counter();
      int var1 = 1;

      for(byte var2 = 2; var1 &amp;lt;= var2; ++var1) {
         counter.inc();
         int var3 = counter.getValue();
         System.out.println(var3);
      }

   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
// Counter.java

public final class Counter {
   private int value;

   public final int getValue() {
      String var1 = &quot;get value &quot; + this.value;
      System.out.println(var1);
      return this.value;
   }

   public final int inc() {
      this.value = this.getValue() + 1;
      return this.getValue();
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    val counter = Counter()
    for (i in 1..2) {
        counter.inc()
        println(counter.value)
    }

}

class Counter {
    var value: Int = 0
        get() {
            println(&quot;get value $field&quot;)
            return field
        }
        private set // 값 수정은 비공개 처리 -&amp;gt; 클래스 안 메서드로 접근

    fun inc() = value++ // 더해서 return시 1, 위에서 print 2
}
// 결과값
get value 0
get value 1
1
get value 1
get value 2
2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 디컴파일 한 것이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// ExpKt.java

public final class ExpKt {
   public static final void main() {
      Counter counter = new Counter();
      int var1 = 1;

      for(byte var2 = 2; var1 &amp;lt;= var2; ++var1) {
         counter.inc();
         int var3 = counter.getValue();
         System.out.println(var3);
      }

   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

// Counter.java

public final class Counter {
   private int value;

   public final int getValue() {
      String var1 = &quot;get value &quot; + this.value;
      System.out.println(var1);
      return this.value;
   }

   public final int inc() {
      int var1;
      this.value = (var1 = this.getValue()) + 1;
      return var1; // var1++이라 var1을 return
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 책에 있는 예제이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    val counter = Counter()
    for (i in 1..2) {
        counter.inc()
    }
    println(counter.value)
}

class Counter {
    var value: Int = 0
        get() {
            println(&quot;get value $field&quot;)
            return field
        }
        private set // 값 수정은 비공개 처리 -&amp;gt; 클래스 안 메서드로 접근

    fun inc() = value++
}
// 결과값
get value 0 // inc
get value 1 // inc
get value 2 // print 할 때 접근
2 // print(valuea)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚫ 배킹핑드 사용하지 않기&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class Thing(val name: String) 

class Container(private val maxCapacity: Int) {
    private val things = mutableListOf&amp;lt;Thing&amp;gt;()
    val capacity: Int
        get() = maxCapacity - things.size
    val isFull: Boolean
        get() = things.size == maxCapacity

    fun put(thing: Thing): Boolean =
        if (isFull) false
        else {
            things += thing // 삽입
            true
        }

    fun take(): Thing = things.removeAt(0)
    fun query(): List&amp;lt;String&amp;gt; = things.map { it.name }
}

fun main() {
    var container = Container(3)
    container.put(Thing(&quot;침대&quot;))
    container.put(Thing(&quot;의자&quot;))
    container.put(Thing(&quot;받침대&quot;))

    println(container.isFull)
    println(container.query())
    println(container.take().name)
    println(container.query())

}
// 결과값 
true
[침대, 의자, 받침대]
침대
[의자, 받침대]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  1.2 연산자 오버로딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚫ 연산자 오버로딩 처리 규직&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. operator fun 으로 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 클래스 내부의 메서드나 확장함수로 정의 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 메서드나 함수의 오버로딩 규칙에 따라 매개변수의 개수나 자료형이 다르면 여러개 정의 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚫ 아래는 오버로딩 규칙에 따라 두개의 plus 메서드를 정의한 예이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val amt01 = Amount(200, 100)
    val amt02 = Amount(300, 100)
    val amt03 = amt01 + amt02
    val amt04 = amt02 + 100 
    
    println(amt03) // Amount(500, 200)
    println(amt04) // Amount(400, 200)
}

class Amount(var total: Int, var balance: Int) {
    operator fun plus(other: Amount) = Amount(
        this.total + other.total, 
        this.balance + other.balance
    )

    operator fun plus(scale: Int) = Amount(this.total + scale, this.balance + scale)
    override fun toString(): String = &quot;Amount($total, $balance)&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;⚫ 확장함수 연산자 오버로딩&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class Amount(var total: Int, var balance: Int) {
    operator fun plus(other: Amount) = Amount(
        this.total + other.total,
        this.balance + other.balance
    )

    operator fun plus(scale: Int) = Amount(this.total + scale, this.balance + scale)
    override fun toString(): String = &quot;Amount($total, $balance)&quot;
}

// 확장함수 연산자 오버로딩
operator fun Amount.times(other: Amount): Amount {
    // 타 클래스안 메서드처럼 리시버클래스 안 속성 접근이 가능하다. 
    return Amount(total * other.total, balance * other.balance)
}

fun main() {
    val amt01 = Amount(200, 100)
    val amt02 = Amount(300, 100)
    val amt05 = amt01 * amt02
    print(amt05) // Amount(60000, 10000)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚫infix 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. infix fun 처럼 함수나 메서드 정의시 맨 앞에 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 매개변수가 1개여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 매개변수에 초기값을 지정할 수 없다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    val a = Add(100)
    println(a add 20)
    println(a.add(200))
    println(a times 200)
    println(a * 200)
    println(a div 200)

    // 결과값
    120
    300
    20000
    20000
    0

}

class Add(var x: Int = 0) {
    infix fun add(y: Int) = x + y
    infix operator fun times(y: Int) = x * y
    infix fun divide(y: Int) = x / y
}

infix fun Add.div(y: Int) = this.divide(y) // 확장함수에 infix 지정하기


&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  1.3 메서드 재정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚫ 메서드 재정의 규칙&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. method overload : 클래스 안에 동일한 이름의 메서드를 여러개 정의. 매개변수의 개수, 자료형에 따라 다르게 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. method override : 클래스 상속관계에서 슈퍼클래스의 메서드를 서브클래스 메서드에 동일한 이름과 매개변수로 정의.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서브클래스에서 추가로 기능을 재정의 가능. 항상 서브클래스가 먼저 참조되고 없으면 슈퍼클래스의 메서드 검색.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서브클래스에서 앞으로의 재정의를 막을려면 &quot;final override fun 메서드이름&quot; 처럼 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 부모클래스에 있는 것을 재정의 할 때, 부모클래스꺼엔 open을 붙이고, 자식클래스에선 override를 붙인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 부모클래스에 있는 속성들은 기본이 final이라 open을 붙이지 않으면 재정의 할 수 없다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;open class Person(var name: String = &quot;돌자반&quot;, var food: String = &quot;치킨&quot;) {
    fun eat() {
        println(&quot;$name 은 굽네를 먹는다.&quot;)
    }

    open fun sleep() {
        println(&quot;$name 은 오늘도 일찍 일어난다.&quot;)
    }
}

class Student(name: String) : Person(&quot;감자&quot;, &quot;설렁탕&quot;) {

    override fun sleep() {
        println(&quot;$name 은 늦게 일어났다...뽈&quot;)
    }

    fun activity() {
        println(&quot;$name 신나게 $food 먹는다&quot;)
    }

    fun doAll() {
        eat()
        sleep()
        activity()
    }
}

fun main() {
    var st = Student(&quot;돌자르르르ㅡ반&quot;)
    st.doAll()
    // 결과값
    감자 은 굽네를 먹는다.
    감자 은 늦게 일어났다...뽈 
    감자 신나게 설렁탕 먹는다
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 특정 자료를 다루는 클래스 알아보기(데이터, 이넘 클래스)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  데이터 클래스&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스일 때 == 객체끼리 연산이 false&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    val p1 = Person(&quot;감자&quot;)
    val p2 = Person(&quot;감자&quot;)

    println(p1)
    println(p2)
    println(p1 == p2)
    println(p1 === p2)
    
    // 결과값
    Person@27d6c5e0
    Person@4f3f5b24
    false
    false
}

class Person(name: String)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 클래스 일 때 == 연산이 true&lt;/p&gt;
&lt;pre class=&quot;isbl&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    val p1 = Person(&quot;감자&quot;)
    val p2 = Person(&quot;감자&quot;)

    println(p1)
    println(p2)
    println(p1 == p2)
    println(p1 === p2)

    // 결과값
    Person(name=감자)
    Person(name=감자)
    true
    false
}

data class Person(val name: String)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  데이터 클래스는 주생성자에 정의된 속성값이 같으면 동일한 값의 객체로 본다. 즉 주생성자의 속성 값이 같을 때 == 연산시 true가 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 클래스가 상속 받더라도 값이 같은지(==)의 기준은 데이터 클래스의 주생성자이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  2.2 이넘 클래스 -&amp;gt; kotlin 1.9.0 에서는 entries 추천&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚫ 이넘 클래스 작성법&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. enum class 이렇게 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 생성할 객체를 모두 내부에 정의하고 객체의 이름을 상수처럼 사용하고 대문자로 작성해야한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    println(Card.PLATINUM.ordinal) // 객체 순서번호 출력
    println(Card.PLATINUM.name) // 객체 이름 출력
    println(Card.GOLD &amp;lt; Card.SIVER) // 객체 간 순서 비교
    
    // 결과값
    2
    Card.PLATINUM
    false

}

enum class Card {
    SIVER, GOLD, PLATINUM // 0번, 1번, 2번 순서
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웬만하면 oridinary를 쓰지 않는다. 새로운 값이 들어왔을 때 순서가 바뀌기 때문&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서가 필요하다면 속성을 추가하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 이넘 클래스에 속성 추가&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;import java.awt.Color.orange
import java.awt.Color.red

fun main() {
    println(Card.PLATINUM.color) // 내부 속성 조회
    println(Card.valueOf(&quot;GOLD&quot;)) // 객체 있는지 조회
    val type = enumValueOf&amp;lt;Card&amp;gt;(&quot;GOLD&quot;) // GOLD 검색
    println(type)
    
    println(Card.PLATINUM.name)
    println(Card.PLATINUM.color)

    // 결과값 1
    orange
    Card.GOLD
    Card.GOLD
    Card.PLATINUM
    orange
    
    val array1 = Card.values() // Array타입
    array1.forEach { println(&quot;${it.name}=${it.color}&quot;) }
    
    // 결과값 2
    Card.SIVER =red
    Card.GOLD =gold
    Card.PLATINUM =orange

    val array2 = enumValues&amp;lt;Card&amp;gt;() // Array타입
    array2.forEach { println(&quot;${it.name}=${it.color}&quot;) }
    
    // 결과값 3
    Card.SIVER =red
    Card.GOLD =gold
    PLATINUM=orange
    
    array2.filter { it.color == &quot;red&quot; }.map { it.color }.forEach { println(it) }
    
    // 결과값 4
    red
    
}

enum class Card(var color: String) {
    SIVER(&quot;red&quot;), GOLD(&quot;gold&quot;), PLATINUM(&quot;orange&quot;) // 0번, 1번, 2번 순서
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이넘 클래스에 동반객체를 작성할 수 있다. -&amp;gt; 메서드를 클래스 이름으로 바로 접근 가능&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    for (i in Card.getIter()) { 
        println(i)
    }
}
// 결과값
SIVER
GOLD
PLATINUM

enum class Card(var color: String) {
    SIVER(&quot;red&quot;), GOLD(&quot;gold&quot;), PLATINUM(&quot;orange&quot;); // 마지막 객체엔 ;를 넣어줘야한다!!

    companion object {
        fun getIter() = values() // 반복자
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 이넘 클래스 내부에 추상메서드를 정의하면 모든 객체 내부에 메서드를 재정의한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
println(Card.SIVER.calculate(3)) // 6000
}

enum class Card(var color: String) {
    SIVER(&quot;red&quot;) {
        override fun calculate(grade: Int): Int = when (grade) {
            in 0..3 -&amp;gt; 6000
            in 4..5 -&amp;gt; 404
            else -&amp;gt; 3333
        }
    },
    GOLD(&quot;gold&quot;) {
        override fun calculate(grade: Int): Int = when (grade) {
            in 0..3 -&amp;gt; 6000
            in 4..5 -&amp;gt; 404
            else -&amp;gt; 3333
        }
    };

    abstract fun calculate(grade: Int): Int // 객체 선언 후 뒤에 적어야 함
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 인터페이스를 상속받아 내부에 구현할 수 있다. 상수가 객체마다 다른 경우에는 각각 구현하고 전체가 같을 경우엔 하나만 구현.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;fun main() {
    println(Card.SIVER.calculate(3)) // 6000
}

interface Calculable {
    fun calculate(grade: Int): Int
}

enum class Card(var color: String) : Calculable {
    SIVER(&quot;red&quot;),
    GOLD(&quot;gold&quot;);

    override fun calculate(grade: Int): Int = when (grade) {
        in 0..3 -&amp;gt; 6000
        in 4..5 -&amp;gt; 404
        else -&amp;gt; 3333
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;kotli 1.9.0에서 인라인 클래스에 세컨더리 컨스트럭터가 생겼다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;data object가 생겼고 toString(), equals(), hashcode 으로 비교도 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;용도 : 데이터의 성격을 띤 오브젝트 명시, data class에서 속성이 없을 때&amp;nbsp;&lt;/p&gt;</description>
      <category>CS/문법_Kotlin</category>
      <author>ugyeong</author>
      <guid isPermaLink="true">https://java0u0.tistory.com/46</guid>
      <comments>https://java0u0.tistory.com/46#entry46comment</comments>
      <pubDate>Fri, 25 Aug 2023 19:19:39 +0900</pubDate>
    </item>
  </channel>
</rss>