1.  RelativeLayout

👩🏻‍🚀  렐러티브레이아웃은 상대 레이아웃이라고도 하며, 이름처럼 레이아웃 내부에 포함된 위젯을 상대적인 위치로 배치
👩🏻‍🚀  렐러티브레이아웃 안에 포함된 위젯은 렐러티브레이아웃의 어디쯤에 위치시킬 것인지 지정해야 함
👩🏻‍🚀  렐러티브레이아웃에 있는 위젯의 위치와 관련된 속성은 크게 두 부류로 나눌 수 있음
      1) 렐러티브레이아웃의 상하좌우에 배치하는 경우  2) 다른 위젯의 상대 위치에 배치하는 경우

 

상하좌우에 배치

  • layout_alignParentLeft
  • layout_alignParentRight
  • layout_alignParentTop
  • layout_alignParentBottom
  • layout_centerHorizontal
  • layout_centerVertical
  • layout_centerInParent

  ⚡️ 렐러티브레이아웃에 있는 위젯을 부모(렐러티브레이아웃)의 어느 부분에 위치시킬지를 결정. 각 속성의 값은 true 또는 false.
  ⚡️  예를 들어 우측하단에 위젯을 배치하려면 layout_alignParentBottom 과 layout_alignParentRight 속성에 true를 설정하면 됨

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:text="위쪽" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:text="좌측" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="중앙" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:text="우측" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="아래" />

</RelativeLayout>

 

 


 

다른 위젯의 상대 위치에 배치 

 

👾  렐러티브레이아웃 안에서 다른 위젯의 특정한 곳에 배치하는 방법. 다른 위젯과 관련된 속성.

👾  각 속성의 값에는 다른 위젯의 아이디를 지정하면 되는데 '@+id/기준 위젯의 아이디'와 같은 형식으로 사용

 

 

  • 상하좌우에는 layout_above, layout_below, layout_toLeftOf, layout_toRightOf
  • 상단, 중앙, 하단  layout_alignTop, layout_alignBaseline, layout_alignBottom
  • 좌측, 우측 기준에는 layout_alignLeft, layout_alignRight

 

        <Button
            android:id="@+id/baseButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="기준 위젯"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@+id/baseButton"
            android:text="above"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/baseButton"
            android:text="below"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@+id/baseButton"
            android:text="toLeftOf"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/baseButton"
            android:text="toRightOf"/>


 <Button
        android:id="@+id/baseButton"
        android:layout_width="wrap_content"
        android:layout_height="400dp"
        android:layout_centerInParent="true"
        android:text="기준 위젯"/>
    <Button
        android:layout_width="100dp"
        android:layout_height="70dp"
        android:layout_alignTop="@+id/baseButton"
        android:text="alignTop"/>
    <Button
        android:layout_width="100dp"
        android:layout_height="70dp"
        android:layout_alignBaseline="@+id/baseButton"
        android:text="alignBaseline"/>
    <Button
        android:layout_width="100dp"
        android:layout_height="70dp"
        android:layout_alignBottom="@+id/baseButton"
        android:text="alignBottom"/>

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  람다식   Lambda Expression

🚀  람다식은 람다 표현식, 람다라고도 불림
🚀  람다식을 한 줄로 표현하면 '람다식은 마치 값처럼 다룰 수 있는 익명함수'

      ⚡️  람다식을 값처럼 다룰 수 있다는 말은, 람다식 자체가 함수의 인수가 되거나 반환값이 될 수 있다는 의미

    val sayHello = fun() { println("Hello world!") }
    sayHello()

 

1)  람다식 정의

    ✓  람다를 이용하여, 인수 숫자의 제곱값을 반환

    val squareNum: (Int) -> Int = { number -> number * number }
    println(squareNum(12))
  • squareNum : 람다식을 저장할 변수의 이름을 지정
  • (Int) : 람다식의 인수 자료형을 지정
  • Int : 람다식의 반환 자료형을 지정. 이 경우에는 정수를 넣고 정수를 반환.
  • number : 인수 목록을 나열. number의 자료형은 자료형에서 명시해주었으므로 형추론이 되어 number는 Int가 됨.
  • number * number : 람다식에서 실행할 코드를 지정

 

 📌  자료형은 인수목록에서 명시해주어도 됨. 앞의 코드와 동일하게 작동하는 함수

    val squareNum2 = { number: Int -> number * number }
    println(squareNum2(12))

 

📌  람다식의 인수가 한 개이면 인수를 생략하고 it으로 지칭할 수 있음

    val squareNum3: (Int) -> Int = { it * it }
    println(squareNum3(12))

 


    2)  람다를 표현하는 다양한 방법

 람다는 '값처럼' 사용할 수 있는 익명 함수. 값처럼 사용한다는 것은 함수의 인수로도 넣어줄 수 있다.

    fun invokeLambda(lambda: (Int) -> Boolean): Boolean { //람다식을 인수로 받음.
        return lambda(5)
    }
    // 이 함수는 다음과 같이 람다식을 인수로 넣어 사용할 수 있음.
    val paramLambda: (Int) -> Boolean = { num -> num == 10 }
    println(invokeLambda(paramLambda)) // 람다식의 인수로 넣은 5 != 10 이므로 -> false
    // 변수를 사용하지 않고도 바로 넣어줄 수도 있음.
    // 다음의 람다식들은 모두 똑같이 작동
    invokeLambda({ num -> num == 10 }) // 람다식 바로 넣어주기
    invokeLambda({it == 10}) // 인수가 하나일 때 it으로 변경 가능
    invokeLambda(){ it == 10 } // 만약 함수의 마지막 인수가 람다일 경우 밖으로 뺄 수 있음.
    invokeLambda{ it == 10 } // 그 외 인수가 없을 때 () 생략 가능.

 

3)  SAM(Single Abstract Method) 변환

📍  안드로이드를 개발하다 보면 다음과 같은 코드를 아주 많이 작성하게 됨

    button.setOnClickListener {
        // 버튼이 눌렸을 때 작성할 코드
    }

 

  👾  함수의 인수가 하나이고 람다식인 경우에 ()를 생략하고 {}에 코드를 작성할 수 있음
  👾  setOnClickListener() 함수는 람다식이 아닌 OnClickListener 인터페이스를 인수로 받음

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }


  👾  OnClickListener는 다음과 같이 추상 메서드 하나가 있는 인터페이스

public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }


  ⚡️  setOnClickListener 는 이와 같이 람다식이 아님에도 마치 람다식처럼 취급되고 있음
         ▶️  이것이 가능한 이유가 바로 자바 8에서 소개된 SAM 변환
             

🤓  SAM 변환은 두 가지 조건이 있다

    1. 코틀린 인터페이스가 아닌 자바 인터페이스
    2. 인터페이스 내에는 딱 한 개의 추상 메서드만 존재

    이 조건을 만족하는 경우 익명 인터페이스 객체 생성에 람다식을 사용할 수 있음.
    람다식을 사용하면 코드가 훨씬 간결해지고 가독성이 높아짐.

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  지연 초기화

코틀린은 지연 초기화를 사용하는데 이는 클래스의 코드에 Nullable 처리가 남용되는 것을 방지해 줌

 

1) lateinit

개발을 하다 보면 클래스 안에서 변수(프로퍼티)만 Nullable로 미리 선언하고 초기화(생성자 호출)를 나중에 해야 할 경우가 있는데, 이럴 경우 lateinit 키워드를 사용할 수 있음

Nullable로 선언하는 일반적인 방법


  👾  일반적인 선언 방식으로 처음에 null 값을 입력해두고, 클래스의 다른 메서드 영역에서 값을 입력함

class Person {
    var name: String? = null
    init {
        name = "Jane"
    }
    fun process() {
        name?.plus(" Messi")
        println("이름의 길이 = ${name?.length}")
        println("이름의 첫글자 = ${name?.substring(0, 1)}")
    }
}

 

  📍 이 방식은 변수에 입력된 값의 메서드나 프로퍼티를 사용할 때 Safe Call(?.)이 남용되어 가독성을 떨어트리는 문제가 있음

 

 lateinit을 사용하는 방법


  👾  lateinit을 사용하면 Safe Call을 쓰지 않을 수 있기 때문에 코드에서 발생할 수 있는 수많은 ?를 방지할 수 있음

class Person2 {
    lateinit var name: String
    init {
        name = "Jane"
    }
    fun process() {
        name.plus(" Messi")
        println("이름의 길이 = ${name.length}")
        println("이름의 첫글자 = ${name.substring(0, 1)}")
    }
}

 

 

lateinit의 특징


  ✓  var로 선언된 클래스의 프로퍼티에서만 사용할 수 있음
  ✓  null 은 허용되지 않음
  ✓  기본 자료형 Int, Long, Double, Float 등은 사용할 수 없음

  ✓  lateinit은 변수를 미리 선언만 해놓은 방식이기 때문에 초기화되지 않은 상태에서 메서드나 프로퍼티를 참조하면 null 예외가 발생해서 앱이 종료. 따라서 변수가 초기화되지 않은 상황이 발생할 수 있다면 Nullable이나 빈 값으로 초기화하는 것이 좋음

 


2)  lazy

👩🏻‍💻  lazy는 읽기 전용 변수인 val을 사용하는 지연 초기화
👩🏻‍💻  lateinit이 입력된 값을 변경할 수 있는 반면, lazy는 입력된 값을 변경할 수 없음
👩🏻‍💻  val로 변수를 먼저 선언한 후 코드의 뒤쪽에 by lazy 키워드를 사용, 그리고 by lazy 다음에 나오는 중괄호 {}에 초기화할 값을 써주면 됨

class Company {
    // by lazy를 사용하면 반환되는 값의 타입을 추론할 수 있기 때문에 변수의 타입을 생략할 수 있음.
    val person: Person by lazy { Person() }
    init {
        // lazy는 선언 시에 초기화 코드를 함께 작성 하기 때문에 초기화 과정이 필요없음.
    }
    fun process() {
        println("person은 이름은 ${person.name}") // 최초 호출하는 시점에 초기화.
    }
}

 

lazy의 특징


  ✓  선언 시에 초기화 코드를 함께 작성하기 때문에, 따로 초기화 할 필요가 없음
  ✓  lazy로 선언된 변수가 최초 호출되는 시점에 by lazy{} 안에 넣은 값으로 초기화
  ✓  코드에서 Company 클래스가 초기화 되더라도 person에 바로 Person()으로 초기화 되지 않고, process() 메서드에서 person.name이 호출되는 순간 초기화

💡  lazy는 주의해서 사용. 지연 초기화는 말 그대로 최초 호출되는 시점에 초기화 작업이 일어나기 때문에 초기화하는데 사용하는 리소스가 너무 크면 (메모리를 많이 쓰거나 코드가 복잡한 경우) 전체 처리 속도에 나쁜 영향을 미칠 수 있음

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  스프링 Web MVC의 특징

출처 :&nbsp;https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-mvc-intro.html
출처 :&nbsp;https://tvd12.com/posts/spring-mvc-request-lifecycle

 

스프링 Web MVC는 기본적으로 서블릿 API를 좀 더 추상화된 형태로 작성된 라이브러리지만, 서블릿 / JSP를 사용할 때 필요한 많은 기능들을 기본적으로 제공해서 개발의 생산성과 안정성을 높여줌

 

  📍 스프링 Web MVC는 이름에서 알 수 있듯이 Web MVC 패턴으로 구현된 구조
  📍 스프링 Web MVC가 기존 구조에 약간의 변화를 주는 부분

Front-Contoller 패턴을 이용해 모든 흐름의 사전 / 사후 처리를 가능하도록 설계된 점
어노테이션을 적극적으로 활용해서 최소한의 코드로 많은 처리가 가능하도록 설계된 점
HttpServletRequest / HttpServletResponse를 이용하지 않아도 될 만큼 추상화된 방식으로 개발 가능

 

구성요소 설명
DispatcherServlet 클라이언트 요청을 전달받아 요청에 맞는 컨트롤러가 리턴한 결과값을 View에 전달하여 알맞은 응답을 생성
HandlerMapping 클라이언트의 요청 URL을 어떤 Controller가 처리할 지 결정
Controller 클라이언트의 요청을 처리한 뒤, 결과를 DispatcherServlet에게 리턴
ModelAndView Controller가 처리한 결과 정보 및 View 선택에 필요한 정보를 담음
ViewResolver Controller의 처리 결과를 생성할 View를 결정
View Controller의 처리 결과 화면을 생성

 

출처 : https://velog.io/@codemcd/


1)  DispatcherServlet 과 FrontController

스프링 MVC에서 가장 주요한 사실은 모든 요청 Request이 반드시 DispatcherServlet이라는 존재를 통해서 실행
  ✓ 
객체지향에서 이렇게 모든 흐름을 하나의 객체를 통해서 진행되는 패턴을 퍼사드 facade 패턴이라 하는데  구조에서는 Front-Controller 패턴 이라도 부름
  ✓ 
Front-Controller 패턴을 이용하면 모든 요청이 반드시 하나의 객체를 지나서 처리되기 때문에 모든 공통적인 처리를 프론트 컨트롤러에서 처리할 수 있게 됨

 

 

 

👾  스프링 MVC에서는 DispatcherServlet이라는 객체가 프론트 컨트롤러의 역할을 수행
👾  프론트 컨트롤러가 사전 / 사후에 대한 처리를 하게 되면 중간에 매번 다른 처리를 하는 부분만 별도로 처리하는 구조를 만들게 됨 

     ➡️  스프링 MVC 에서는 이를 컨트롤러라고 하는데 @Controller를 이용해서 처리

 

 

 


2)  스프링 MVC 사용하기

👻  spring-webmvc 라이브러리는 미리 추가되었으므로 스프링 MVC관련 설정을 추가 

 

㉮  스프링 MVC관련 설정을 추가하기 위해 WEB-INF 폴더에 servlet-context.xml 파일을 생성
  ✓  root-context.xml을 이용할 수도 있지만, 일반적으로는 구조를 분리하듯이 웹을 다루는 영역이라 별도의 설정 파일을 이용하는 것이 일반적이다.

 

㉯  프로젝트 내부의 webapp 폴더 아래에 'resources'라는 폴더를 하나 더 생성해 둠

  ✓  나중에 정적 파일들(html, css, js, 이미지등)을 서비스하기 위한 경로

 

 servlet-context.xml  설정 코드
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
        
    <mvc:annotation-driven/>
    <mvc:resources mapping="/resources/**" location="/resources/"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

 

  ⚡️  <mvc:annotation-driven> 설정 : 스프링 MVC 설정을 어노테이션 기반으로 처리한다는 의미와 스프링 MVC의 여러 객체들을 자동으로 스프링의 빈으로 등록하게 하는 기능을 의미
  ⚡️  <mvc:resources> 설정 : 이미지나 html 파일과 같이 정적인 파일의 경로를 지정
        ✓  /resources 경로로 들어오는 요청은 정적 파일을 요구하는 것이라고 생각하고 스프링 MVC에서 처리하지 않는다는 의미
        ✓  location 속성 값은 webapp 폴더에서 만들어둔 폴더를 의미
        ✓  InternalResourceViewResolver라는 이름의 클래스로 빈이 설정되어 있는데,
             InternalResourceViewResolver는 스프링 MVC에서 제공하는 뷰(view)를 어떻게 결정하는지에 대한 설정을 담당
        ✓  prefix와 suffix는 뷰를 이용할때 파일명만 주어지면 조합해서 '/WEB-INF/view/파일명.jsp' 같은 형식을 만들 수 있음

 

 

web.xml의 DispatcherServlet 설정

 

스프링 MVC를 실행하려면 front controller 역할을 하는 DispatcherServlet을 설정해야하는데,  web.xml을 이용해서 처리

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

 

  ⚡️  <servlet> 설정과 <servlet-mapping> 설정이 추가
        ✓  <servlet> 설정은 DispatcherServlet을 등록하는데 DispatcherServlet이 로딩할 때 servlet-context.xml을 이용하도록 설정
        ✓  load-on-startup 설정의 경우 톰캣 로딩 시에 클래스를 미리 로딩해 두기 위한 설정
        ✓  <servlet-mapping> 설정모든 경로의 요청에 대한 처리를 담당하기 때문에 '/'로 지정

 


3)  스프링 MVC 컨트롤러 

스프링 MVC 컨트롤러는 전통적인 자바의 클래스 구현 방식과 다름

스프링 MVC의 컨트롤러 특징

 

  ✓  상속이나 인터페이스를 구현하는 방식을 사용하지 않고 어노테이션만으로 처리가 가능
  ✓  오버라이드 없이 필요한 메소드들을 정의
  ✓  메소드의 파라미터를 기본 자료형이나 객체 자료형을 마음대로 지정
  ✓  메소드의 리턴타입도 void, String, 객체 등 다양한 타입을 사용할 수 있음

 

㉮  controller 패키지를 추가하고 SampleController 클래스를 추가

@Log4j2
@Controller
public class SampleController {

    @GetMapping("/hello")
    public void hello() {
        log.info("hello(_)...");
    }
}

 

  ⚡️  몇 가지 특이한 어노테이션들이 사용
      ✓  @Controller는 해당 클래스가 스프링 MVC에서 컨트롤러 역할을 한다는 것을 의미하고 스프링의 빈으로 처리되기 위해서 사용
      ✓  @GetMapping은 GET 방식으로 들어오는 요청을 위해 사용

 


 

A) servlet-context.xml의 component-scan


controller 패키지에 존재하는 컨트롤러 클래스들을 스프링으로 인식하기 위해서는 해당 패키지를 스캔해서 @Controller 어노테이션이 추가된 클래스들의 객체들을 스프링이 빈으로 설정되게 만들어야 함

servlet-context.xml에 component-scan을 적용
<context:component-scan base-package="com.example.spring.controller"/>

 

WEB-INF에 view 폴더 생성후 hello.jsp 작성
<html>
<head>
    <title>Title</title>
</head>
<body>
  <h1>Hello JSP</h1>
</body>
</html>


 

B) @RequestMapping과 파생 어노테이션들


스프링 컨트롤러에서 가장 많이 사용하는 어노테이션은 @RequestMapping
  ⚡️  @RequestMapping은 말 그대로 '특정한 경로의 요청 Request을 지정'하기 위해서 사용
  ⚡️  컨트롤러 클래스의 선언부에도 사용할 수 있고, 컨트롤러의 메서드에도 사용할 수 있음

 

controller 패키지에 TodoController 클래스 추가
@Log4j2
@Controller
@RequestMapping("/todo")
public class TodoController {
    @RequestMapping("/list")
    public void list() {
        log.info("todo list...");
    }

    @RequestMapping(value = "/register", method = RequestMethod.GET)
    public void register() {
        log.info("todo register...");
    }
}

 

  ⚡️  TodoController의 @RequestMapping의 value 값은 '/todo'이고 list()는 '/list'이므로 최종경로는 '/todo/list' 
  ⚡️  JSP 파일이 없어서 에러가 나지만 로그 출력은 확인

 

  ⚡️  @RequestMapping을 이용하는 것만으로도 여러 개의 컨트롤러를 하나의 클래스로 묶을 수 있고, 각 기능을 메소드 단위로 설계할 수 있게 되므로 실제 개발에서 많은 양의 코드를 줄일 수 있음

  ⚡️  @RequestMapping에는 method 속성을 이용해서 GET / POST 방식을 구분해서 처리했지만, 스프링 4버전 이후에는 @GetMapping, @PostMapping 어노테이션이 추가되면서 GET / POST 방식을 구분해서 처리할 수 있게 됨
      ➡️  예를 들어 Todo 등록의 경우 GET 방식으로 '/todo/register'를 이용하면 입력 가능한 화면을 보여주고, POST방식은 처리를 해야 함

@Log4j2
@Controller
@RequestMapping("/todo")
public class TodoController {
    @RequestMapping("/list")
    public void list() {
        log.info("todo list...");
    }

//    @RequestMapping(value = "/register", method = RequestMethod.GET)
    @GetMapping("/register")
    public void register() {
        log.info("todo register...");
    }

    @PostMapping("/register")
    public void registerPOST(TodoDTO todoDTO) {
        log.info("POST todo register...");
        log.info(todoDTO);
    }
}

 


C)  파라미터 자동 수집과 변환


👩🏻‍💻  스프링 MVC가 인기를 끌게 된 여러 이유 중에는 개발 시간을 단축할 수 있는 편리한 기능들이 많기 때문
👩🏻‍💻  개발자들에게 가장 필요한 파라미터 자동 수집 기능

 

  ⚡️  파라미터 자동 수집 기능은 간단히 말해서 DTO나 VO등을 메서드의 파라미터로 설정하면 자동으로 전달되는

         HttpServletRequest의 파라미터들을 수집해 주는 기능
  ⚡️  단순히 문자열만이 아니라 숫자도 가능하고, 배열이나 리스트, 첨부 파일도 가능

 

파라미터 수집은 다음과 같은 기준으로 동작


  ✓  기본 자료형의 경우는 자동으로 형 변환처리가 가능
  ✓  객체 자료형의 경우는 setXXX()의 동작을 통해서 처리
  ✓  객체 자료형의 경우 생성자가 없거나 파라미터가 없는 생성자가 필요

 

단순 파라미터의 자동 수집

 

  📍 SampleController에 ex1() 추가

    @GetMapping("/ex1")
    public void ex1(String name, int age) {
        log.info("ex1() ...");
        log.info("name: " + name);
        log.info("age: " + age);
    }

 

  ✓  문자열 name과 int 타입의 age 선언
  ✓  브라우저를 이용해서 name과 age를 지정하면 'ex1?name=aaa&age=16'와 같이 호출 되었을 때 자동으로 처리되는 것 확인

 

 

  

 

 

[ 내용 참고 : IT 학원 강의 ]


1.   제네릭

제네릭 Generics은 입력되는 값의 타입을 자유롭게 사용하기 위한 도구

📌 다음은 실제 MutableList의 선언부

public interface MutableList<E> : List<E>, MutableCollection<E>


  ✓  클래스명 옆에 <E>부분에 String 같은 특정 타입이 지정되면 클래스 내부에 선언된 모든 E에 String 타입으로 지정
  ✓  결과적으로 var list: Array<E>가 var list: Array<String>으로 변경이 되는 것

  ⚡️  이렇게 설계된 클래스는 주로 구현하는 용도로 사용하며 컬렉션이나 배열에서 입력되는 값의 타입을 특정하기 위해 다음과 같이 사용

var list: MutableList<String> = mutableListOf("abc", "def", "ghi") 
fun testGenerics() {
    // String으로 제네릭을 사용했기 때문에 list 변수에는 문자열만 담을 수 있음.
    var list: MutableList<String> = mutableListOf()
    list.add("abc")
    list.add("def")
    list.add("ghi")
//    list.add(30) // 입력 오류가 발생

    // String 타입의 item 변수로 꺼내서 사용할 수 있음.
    for (item in list) {
        println("리스트에 저장된 값은 ${item}입니다")
    }
}

 


2.  Null

코틀린은 Null 값의 처리에 많은 공을 들인 언어. null은 프로그래밍하면서 항상 이슈의 중심에 있는데 null로 인해 프로그램 전체, 혹은 앱 자체가 멈출 수 있기 때문.

 

 프로그램이 멈출 수 있는 상황 예시

   

      main() 함수 안에 one 변수를 하나 선언하고 타입으로 클래스를 지정.
     그리고 특정 조건이 만족할 때만 선언한 변수에 생성자를 호출해서 저장하는 조건문 if를 만듦.
     그리고 변수를 통해 해당 클래스의 메서드를 호출.

class One {
    fun printMe() {
        print("null safety")
    }
}

fun main() {
    var one: One
    if (1 > 2) {
        one = One()
    }
 //   one.printMe()
}

 

  👾  이 코드에서 조건이 false이기 때문에 one 변수는 아무것도 없는 null 상태가 됨
         ➡️  print 메서드를 호출하면 null 포인터 예외가 발생하면서 프로그램이 다운, 물론 IDE에서 오류를 발생시켜 컴파일되지 않도록 막아줌
  👾  코드양이 많아 지면 이런 상황이 언제든지 발생할 수 있는데, 코틀린은 이런 상황을 방지하기 위해서 안전장치를 마련해둠
         ➡️  그 결과물이 Null Safety


1)  null 값 허용하기 : ?


코틀린에서 지정하는 기본 변수는 모두 null이 입력되지 않음
null 값을 입력하기 위해서는 변수를 선언할 때 타입 뒤에 ? (Nullable, 물음표)를 입력.

var variable : String?

 

 

변수에 null 허용하기

변수의 타입 뒤에 물음표를 붙이지 않으면 null 값을 입력할 수 없음.
null 예외를 발생시키고 싶지 않다면 기본형으로 선언.
fun main() {
    var nullable: String? // 타입 다음에 물음표를 붙여서 null 값을 입력할 수 있음.
    nullable = null

    var notNullable: String
//    notNullable = null // 일반 변수에는 null을 입력할 수 없음.
}

함수 파라미터에 null 허용 설정하기

함수의 파라미터에도 null 허용 여부를 설정할 수 있음.
함수의 파라미터가 null을 허용하려면 해당 파라미터에 대해서 null 체크를 먼저 해야만 사용할 수 있음.
단, 파라미터를 조건문에서 null인지 아닌지를 체크해야만 사용할 수 있음
fun nullParameter(str: String?) {
    // 파라미터 str에 null이 허용되었기 때문에 함수 내부에서 null 체크를 하기 전에는 str을 사용할 수 없음.
    if (str != null) {
        var length = str.length
    }
}

함수의 리턴 타입에 null 허용 설정하기

함수의 리턴 타입에도 물음표를 붙여서 null 허용 여부를 설정할 수 있음.
함수의 리턴 타입에 Nullable이 지정되어 있지 않으면 null 값을 리턴할 수 없음.
fun nullReturn(): String? {
    return null
}

 

2)  안전한 호출 : ?.


변수를 Nullable로 만들기 위해서 물음표를 사용.
?.(Safe Call, 물음표와 점)을 사용하면 null 체크를 간결하게 할 수 있음
  ✓  Nullable인 변수 다음에 ?.을 사용하면 해당 변수가 null일 경우 ?. 다음의 메서드나 프로퍼티를 호출하지 않음

fun testSafeCall(str: String?): Int? {
    // 문자열 길이를 반환하는 length 프로퍼티를 호출했는데 str 변수 자체가 null일 경우는 
    // length 프로퍼티를 호출하지 않고 null을 반환.

    var result: Int? = str?.length
    return result
}

3)  Null 값 대체하기 : ?:


?:(Elvis Operator, 물음표와 콜론)을 사용하면 원본 변수가 null 일 때 넘겨줄 기본값을 설정할 수 있음

 

다음 코드에서 Safe Call 다음에 호출되는 프로퍼티 뒤에 다시 ?:을 붙이고 0이라는 값을 표시.
이렇게 호출하면 str 변수가 null일 경우 가장 뒤에 표시한 0을 반환.
fun testElvis(str: String?): Int {
    // length 오른쪽에 ?:을 사용하면 null일 경우 ?: 오른쪽의 값이 반환.
    var resultNonNull: Int = str?.length ?: 0
    return resultNonNull
}

 


 

💫  물음표의 위치와 형태에 따라서 Nullable, Safe Call, Elvis Operator가 구분

🤓  Nullable

표기법 : 선언하는 변수의 타입 다음에 ? 표기.
사용 목적 : null을 입력받기 위해 사용.
사용 예 : var nullable: String?
🥸  Safe Call

표기법 : 선언된 변수의 이름 다음에 ?. 표기.
사용 목적 : null 일 때 ?. 다음에 나오는 속성이나 명령어를 처리하지 않기 위해 사용.
사용 예 : var result = 변수?.length
🧐  Elvis Operator

표기법 : 선언된 변수의 이름 다음에 ?: 표기.
사용 목적 : null일 때 ?: 다음에 나오는 값을 기본 값으로 사용.
사용 예 : var result = 변수 ?: 0

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1. 접근 제한자

코틀린에서 정의되는 클래스, 인터페이스, 메서드, 프로퍼티는 모두 접근 제한자 Visibility Modifiers를 가질 수 있음.
함수형 언어라는 특성 때문에 코틀린은 기본 객체지향에서 접근 제한자의 기준으로 삼았던 패키지 대신에 모듈 개념이 도입

💡 코틀린에서 모듈이란?
    -  코틀린에서 모듈이란 한 번에 같이 컴파일되는 모든 파일을 말함.
        안드로이드를 예로 든다면 하나의 앱이 하나의 모듈이 될 수 있음. 또한 라이브러리도 하나의 모듈.


1) 접근 제한자의 종류


  🐰  접근 제한자는 서로 다른 파일에게 자신에 대한 접근 권한을 제공하는 것인데 각 변수나 클래스 이름 앞에 아무런 예약어를 붙이지
않았을 때는 기본적으로 public 접근 제한자가 적용

 

각각의 접근 제한자가 공개하는 범위
  • public : 코틀린의 기본 접근 제한자. 어디서나 접근 가능
  • internal : 같은 모듈 내에서 접근 가능. 안드로이드 개발 시에는 한 프로젝트 안에 있으면 같은 모듈
                    만약 한 프로젝트에 여러 모듈을 만든다면 이는 모듈 간 접근이 제한
  • protected : 자식 클래스에서는 접근할 수 있음
  • private : 해당 클래스 내부에서만 접근할 수 있음

2) 접근 제한자의 적용


  🐰  접근 제한자를 붙이면 해당 클래스, 멤버 프로퍼티 또는 메서드에 대한 사용이 제한

open class Parent {
    private val privateVal = 1
    protected open val protectedVal = 2
    internal val internalVal = 3
    val defaultVal = 4
}

class Child: Parent() {
    fun callVariables() {
 //       println(privateVal) // 호출이 안됨.
        println(protectedVal) // 상속 관계이므로 접근할 수 있음.
        println(internalVal)  // 동일한 모듈이므로 접근할 수 있음
        println(defaultVal)
    }
}

// 상속 관계가 아닌 외부 클래스에서 Parent 클래스를 생성하고 사용.
// 상속 관계가 아니기 때문에 public과 internal 에만 접근할 수 있음
class Stranger {
    fun callVariables() {
        val parent = Parent()
//        println(parent.privateVal) // 호출이 안됨.
//        println(parent.protectedVal)  // 호출이 안됨.
        println(parent.internalVal)
        println(parent.defaultVal)
    }
}

 

 

 

[ 내용 참고 : IT 학원 강의 ]

+ Recent posts