카테고리 보관물: 2. 프로그래밍

Kotlin 안드로이드 앱 프로그래밍 (10)

보조 생성자
보조 생성자는 클래스의 본문에 constructor 키워드로 선언하는 함수, 클래스 본문에 선언하므로 여러 개를 추가할 수 있습니다.
다음 코드에서는 매개변수를 다르게 구성한 보조 생성자 2개를 선언했습니다. 보조 생성자도 생성자 이므로 객체를 생성할 때 자동으로 호출됩니다. 그리고 보조 생성자는 클래스 본문에 선언하므로 생성자 본문을 중괄호 {}로 묶어서 객체 생성과 동시에 실행할 영역을 지정할 수 있습니다.

보조 생성자 선언

class User{
   constructor(name: String){
      println("constructor(name: String) call...")
   }
   constructor(name: String, count: Int){
      println("constructor(name: String, count: Int) call...")
   }
}
fun main(){
   val user1 = User("kkang")
   val user2 = User("kkang", 10)
}

>> 실행결과
constructor(name: String) call...
constructor(name: String, count: Int) call...

보조 생성자에 주 생성자 연결
코틀린의 생성자는 주 생성자와 보조 생성자로 나뉩니다. 클래스를 선언할 때 둘 중 하나만 선언하면 문제가 없지만, 만약 주 생성자와 보조 생성자를 모두 선언한다면 반드시 생성자끼리 연결해 주어야 합니다.

주 생성자와 보조 생성자 선언 시 오류

class User(name: String){
   constructor(name: String, count: Int){ // 오류
---
   }
}

클래스에 주 생성자와 보조 생성자를 모두 선언하였습니다. 그런데 위의 코드는 오류가 발생합니다. 주 생성자가 없다면 보조 생성자를 선언하는 데 문제가 없지만 주 생성자가 있으므로 보조 생성자에서 주 생성자를 호출해 주어야 합니다.
보조 생성자는 객체를 생성할 때 호출되며, 이때 클래스 내에 주 생성자가 있다면 this() 구문을 이용해 주 생성자를 호출해야 합니다.

보조 생성자에서 주 생성자 호출

class User(name: String){
   constructor(name: String, count: Int): this(name){ // 성공
---
   }
}
fun main(){
   val user = User("kkang", 10)
}

보조 생성자 선언부에 this(name)만 추가한 코드입니다. 이렇게 하면 보조 생성자로 객체를 생성할 때 주 생성자가 함꼐 호출됩니다.
만약 주 생성자가 있는 상태에서 보조 생성자를 여러 개 선언한다면 보조 생성자에서 this() 로 다른 보조 생성자를 호출할 수도 있습니다. 그런데 이때에도 보조 생성자로 객체를 생성한다면 어떤 식으로든 주 생성자가 호출되게 해야 합니다.

보조 생성자가 여럿일 때 생성자 연결

class User(name: String){
   constructor(name: String, count: Int): this(name){
---
   }
   constructor(name: String, count: Int, email: String): this(name, count) {
---
   }
}
fun main(){
   val user = User("kkang", 10, "a@a.com")
}

같은 모양으로 묶음

클래스를 재사용하는 상속 – 상속과 생성자
클래스를 선언할 때 다른 클래스를 참조해서 선언하는 것을 상속 이라고 합니다. 코틀린에서 어떤 클래스를 상속받으려면 선언부에 콜론: 과 함께 상속받을 클래스 이름을 입력합니다. 이렇게 하면 기존 클래스를 재 사용할수 있습니다.

클래스 상속 형식

open class Super{ // 상속할 수 있게 open 키워드 이용
}
class Sub: Super(){ // Super를 상속받아 Sub 클래스 선언

상속 관계에서 상속 대상이 되는 클래스를 상위 클래스라고 하고, 상속받는 클래스를 하위 클래스라고 합니다. 코틀린의 클래스는 기본적으로 다른 클래스가 상속할 수 없습니다. 다시 말해 class Super{} 처럼 클래스를 선언하면 다른 클래스에서 Super 클래스를 상속할 수 없습니다. 만약 다른 클래스에서 상속할 수 있게 선언하려면 open 키워드를 사용해야합니다. 즉 open class Super {} 라고 선언하면 Super 클래스의 상속을 허용합니다.
하위 클래스를 선언할 때는 이름 뒤에 콜론(:)을 입력하고 상속받을 상위 클래스 이름을 입력합니다. class Sub: Super() {} 코드는 Super 클래스를 상속받아 Sub 클래스를 선언한 구문입니다.
상위 클래스를 상속받은 하위 클래스의 생성자에서는 상위 클래스의 생성자를 호출해야 합니다. class Sub: Super() {}코드에서 Super()는 Super 클래스를 상속받으면서 이 클래스의 매개변수가 없는 생성자를 호출합니다. 만약 매개변수가 있는 상위 클래스의 생성자를 호출할 때는 다음처럼 매개변수 구성에 맞게 인자를 전달해야 합니다.

매개변수가 있는 상위 클래스의 생성자 호출

open class Super(name: String){
}
class Sub(name: String): Super(name){
}

상위 클래스의 생성자 호출문을 꼭 클래스 선언부에 작성할 필요는 없습니다. 만약 하위 클래스에 보조 생성자만 있다면 상위 클래스의 생성자를 다음처럼 호출할 수 있습니다.

하위 클래스에 보조 생성자만 있는 경우 상위 클래스의 생성자 호출

open class Super(name: String){
}
class Sub: Super{
   constructor(name: String): super(name){
   }
}

Kotlin 안드로이드 앱 프로그래밍 (9)

주 생성자
코틀린에서의 클래스는 생성자를 주 생성자와 보조 생성자로 구분합니다. 한 클래스 안에 주 생성자만 선언할 수도 있고 보조 생성자만 선언할 수도 있습니다. 물론 둘 다 선언 가능합니다.
주 생성자는 constructor 키워드로 클래스 선언부에 선언합니다. 주 생성자 선언은 필수는 아니며 한 클래스에 하나만 가능합니다.

주 생성자 선언

class User constructor() {
}

주 생성자를 선언할 때 constructor 키워드는 생략할 수 있습니다.

constructor 키워드 생략 예

class User() {
}

만약 개발자가 클래스의 주 생성자를 선언하지 않으면 컴파일러가 매개변수가 없는 주 생성자를 자동으로 추가합니다.

매개변수가 없는 주 생성자 자동 선언

class User() {
}

주 생성자의 매개변수
주 생성자를 선언할 때 필요에 따라 매개변수를 선언할 수도 있습니다.

주 생성자의 매개변수

class User(name: String, count: Int){
}

앞의 코드는 User 클래스를 선언하면서 주 생성자에 매개변수를 2개 선언하였습니다. 그러면 객체를 생성할 때 매개변수의 타입과 개수에 맞는 인자를 전달해야 합니다.

매개변수가 있는 생성자 호출

val user = User("kkang", 10)

주 생성자의 본문 – init 영역
주 생성자를 이용해 객체를 생성할 때 특정한 로직을 수행할 수 있습니다. 그런데 주 생성자의 실행 영역인 본문을 다음처럼 추가하면 오류가 발생합니다.

주 생성자 중괄호 때문에 오류 발생

class User(name: String, count: Int){
     // 주 생성자 본문
}{   // 오류
     // 클래스 본문
}

보통 클래스나 함수의 본문(실행 영역)은 중괄호 {}로 감싸지만 주 생성자에는 {}를 추가할 수 없습니다. 왜냐하면 주 생성자는 클래스 선언부에 있기 때문입니다. 이럴 때 init 키워드를 이용해 주 생성자의 본문을 구현할 수 있습니다.
코틀린의 클래스 안에서 init 키워드로 지정한 영역은 객체를 생성할 때 자동으로 실행됩니다. 클래스에서 init 영역은 꼭 선언할 필요는 없으므로 주 생성자의 본문을 구현하고 싶을 때 사용합니다.
init 영역은 주 생성자뿐만 아니라 잠시 후에 다루는 보조 생성자로 객체를 생성할 때도 실행됩니다. 하지만 보조 생성자는 클래스 안에 선언하므로 {}를 이용해 본문을 지정할 수 있습니다. 따라서 init 영역은 일반적으로 주 생성자의 본문을 구현하는 용도로 사용합니다.

init 키워드로 주 생성자의 본문 지정

class User(name: String, count: Int){
   init {
      println("i am init...")
   }
}
fun main() {
   val user = User("kkang", 10)
}

>> 실행결과
i am init...

User라는 클래스에 주 생성자를 선언하고 클래스 본문에 init 영역을 두어 주 생성자의 본문을 작성하였습니다. 이렇게 하면 main() 함수에서 User 클래스의 객체를 생성할 때 init 영역에 작성한 코드가 자동으로 실행됩니다.

생성자의 매개변수를 클래스의 멤버 변수로 선언하는 방법
생성자의 매개변수는 기본적으로 생성자에서만 사용할 수 있는 지역 변수입니다.

생성자의 매개변수를 init 영역에서 사용하는 예

class User(name: String, count: Int){
   init{
      println("name : $name, count : $count") // 성공
   }
   fun someFun(){
      println("name : $name, count : $count") // 오류
   }
}

위 코드에서는 주 생성자에 name, count 매개변수를 선언하였습니다. 그리고 이 변수를 init 영역과 someFun() 이라는 함수에서 사용하려고 합니다. 생성자를 호출할 때 init 영역이 실행되므로 이곳에서 생성자의 매개변수에 접근할 수 있습니다.
하지만 생성자의 매개변수는 지역 변수이므로 다른 함수에서는 사용할 수 없습니다. 만약 생성자의 매개변수를 클래스의 멤버 변수처럼 다른 함수에서 사용해야 한다면 다음처럼 작성해야 합니다.

생성자의 매개변수를 다른 함수에서 사용하는 예

class User(name: String, count: Int){
   // 클래스 멤버 변수 선언
   var name: String
   var count: Int
   init{
      // 클래스 멤버 변수에 생성자 매개변숫값 대입
      this.name = name
      this.count = count
}
   fun someFun(){
      println("name : $name, count : $count") // 성공
   }
}
fun main(){
   val user = User("kkang", 10)
   user.someFun()
}

>> 실행결과
name : kkang, count : 10

클래스의 멤버 함수 someFun()에서 생성자의 매개변수를 이용하고자 클래스의 멤버 변수를 선언하고 주 생성자의 본문인 init 영역에서 매개변숫값을 클래스의 멤버 변수에 대입하였습니다.
그런데 이 방법 말고도 생성자의 매개변수를 클래스의 멤버 변수로 선언하는 방법이 있습니다. 이렇게 하면 코드를 조금 더 간단하게 작성할 수 있습니다. 주 생성자의 매개변수는 생성자 안에서만 사용할 수 있는 지역 변수지만 매개변수를 var나 val 키워드로 선언하면 클래스의 멤버 변수가 됩니다.

생성자의 매개변수를 클래스의 멤버 변수로 선언하는 방법

class User(val name: String, val count: Int){
   fun someFun(){
      println("name : $name, count : $count") // 성공
   }
}
fun main(){
   val user = User("kkang", 10)
   user.someFun()
}

>> 실행결과
name : kkang, count : 10

원래 함수는 매개변수를 선언할 때 var나 val 키워드를 추가할 수 없습니다. 그런데 주 생성자에서만 유일하게 var나 val 키워드로 매개변수를 선언할 수 있으며 이렇게 하면 클래스의 멤버 변수가 됩니다. 따라서 위 코드에서 생성자의 매개변수 name과 count를 someFun()이라는 멤버 함수에서 사용할 수 있습니다.

Spring 입문 (JPA에서의 동적 쿼리)

현재 상품 주문 시스템에서 검색 기능을 활성화 하기 위해서는 동적 쿼리를 해결해야한다.

방법 1. JPQL로 처리

public List<Order> findAllByString(OrderSearch orderSearch) {
        //language=JPAQL
        String jpql = "select o From Order o join o.member m";
        boolean isFirstCondition = true;

        //주문 상태 검색
        if (orderSearch.getOrderStatus() != null) {
            if (isFirstCondition) {
                jpql += " where";
                isFirstCondition = false;
            } else {
                jpql += " and";
            }
            jpql += " o.status = :status";
        }

        //회원 이름 검색
        if (StringUtils.hasText(orderSearch.getMemberName())) {
            if (isFirstCondition) {
                jpql += " where";
                isFirstCondition = false;
            } else {
                jpql += " and";
            }
            jpql += " m.name like :name";
        }
        TypedQuery<Order> query = em.createQuery(jpql, Order.class)
                .setMaxResults(1000); //최대 1000건
        if (orderSearch.getOrderStatus() != null) {
            query = query.setParameter("status",
                      orderSearch.getOrderStatus());
        }
        if (StringUtils.hasText(orderSearch.getMemberName())) {
            query = query.setParameter("name", 
                      orderSearch.getMemberName());
        }
        return query.getResultList();
    }

JPQL 쿼리를 문자로 생성하기는 번거로움
실수로 인한 버그가 충분히 발생할 수 있음

방법 2. JPA Criteria로 처리

public List<Order> findAllByCriteria(OrderSearch orderSearch) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Order> cq = cb.createQuery(Order.class);
        Root<Order> o = cq.from(Order.class);
        Join<Order, Member> m = o.join("member", JoinType.INNER);
        List<Predicate> criteria = new ArrayList<>();

        //주문 상태 검색
        if (orderSearch.getOrderStatus() != null) {
            Predicate status = cb.equal(o.get("status"),
                    orderSearch.getOrderStatus());
            criteria.add(status);
        }

        //회원 이름 검색
        if (StringUtils.hasText(orderSearch.getMemberName())) {
            Predicate name =
                    cb.like(m.<String>get("name"), "%" +
                            orderSearch.getMemberName() + "%");
            criteria.add(name);
        }
        cq.where(cb.and(criteria.toArray(new Predicate[criteria.size()])));
        TypedQuery<Order> query = em.createQuery(cq).setMaxResults(1000);
        return query.getResultList();
    }

JPA Criteria는 JPA 표준 스펙이지만 실무에서 사용하기에 너무 복잡
결국 다른 대안이 필요
해결책 ==> Querydsl 제시

방법 3. Querydsl로 처리

public List<Order> findAll(OrderSearch orderSearch){
        QOrder order = QOrder.order;
        QMember member = QMember.member;

        return query
                .select(order)
                .from(order)
                .join(order.member, member)
                .where(statusEq(orderSearch.getOrderStatus())),
                        nameLink(orderSearch.getMemberName()))
                .limit(1000)
                .fetch();
    }

    private BooleanExpression statusEq(OrderStatus statusCond){
        if(statusCond == null){
            return null;
        }
        return  order.status.eq(statusCond);
    }

    private BooleanExpression nameLink(String nameCond){
        if (!StringUtils.hasText(nameCond)){
            return null;
        }
        return member.name.link(nameCond);
    }

훨씬 간결한 방법으로 동적 쿼리 문제를 해결
dsl에 대한 학습이 필수적으로 필요해 보임

Kotlin 안드로이드 앱 프로그래밍 (8)

For과 While
코틀린에서도 반복문 for과 while을 지원합니다. for 문은 제어 변숫값을 증감하면서 특정 조건이 참일 때까지 구문을 반복해서 실행합니다. 이때 for 문의 조건에는 주로 범위 연산자인 in을 사용합니다.

for-in 반복문

fun main(){
   var sum: Int = 0
   for (i in 1..10){
      sum += i
   }
   println(sum)
}
>> 실행결과
55 (1부터 10까지 순서대로 합함)

위 소스는 for 문을 이용해 1부터 10까지 더하기를 수행합니다. for 문의 소괄호 안에 있는 i in 1..10 코드는 1부터 10까지 1씩 증가하면서 for 문의 실행 영역을 반복하라는 의미입니다. 따라서 총 10번 반복해서 실행합니다.
for 문의 조건은 이와 같이 단순히 1씩 증가 또는 감소하게 만들 수도 있고 2씩 증가하게 만드는 등 다양하게 작성할 수 있습니다.

for (i in 1..10) {…} -> 1부터 10까지 1씩 증가
for (i in 1 until 10) {…} -> 1부터 9까지 1씩 증가(10은 미포함)
for (i in 2..10 step 2) {…} -> 2부터 10까지 2씩 증가
for (i in 10 downTo 1) {…} -> 10부터 1까지 1씩 감소

증감 조건을 숫자로 명시하지 않고 컬렉션 타입의 데이터 개수만큼 반복하게 할 수도 있습니다.

반복 조건에 컬렉션 타입 활용

fun main(){
   var data = arrayOf<Int>(10, 20, 30)
   for (i in data.indices){
      print(data[i])
      if(i !== data.size - 1) print(",")
   }
}
>> 실행결과
10,20,30

배열의 크기만큼 for 문을 반복하게 작성한 소스입니다. indices는 컬렉션 타입의 인덱스값을 의미하므로 for 문을 반복하면서 0,1,2 값을 i에 대입합니다. 만약 for 문을 반복하면서 인덱스와 실제 데이터를 함께 가져오려면 withIndex() 함수를 이용합니다.

인덱스와 데이터를 가져오는 withIndex() 함수

fun main(){
   var data = arrayOf<Int>(10,20,30)
   for ((index, value) in data.withIndex()){
      print(value)
      if (index !== data.size-1) print(",")
   }
}
>> 실행결과
10,20,30

for 문 외에 while 문을 이용해 반복문을 작성할 수도 있습니다. while 문은 조건이 참이면 중괄호 {}로 지정한 영역을 반복해서 실행합니다.

while 반복문

fun main(args: Array<String>){
   var x = 0
   var sum1 = 0
   while (x < 10) {
      sum += ++x
   }
   println(sum1)
}
>> 실행결과
55

클래스와 생성자
클래스 선언
코틀린에서 클래스는 class 키워드로 선언합니다. 다음 코드에서 class User 부분이 클래스의 선언부이며 중괄호 {} 영역이 본문입니다. 만약 클래스의 본문에 입력하는 내용이 없다면 {}를 생략할 수 있습니다.

[ 클래스 선언 – class User { } ]

클래스의 멤버는 생성자, 변수, 함수, 클래스로 구성됩니다. 이 중에서 코틀린의 생성자는 constructor 라는 키워드로 선언하는 함수입니다. 그리고 클래스 안에 다른 클래스를 선언할수도 있습니다.

클래스의 멤버

class User {
   var name = "kkang"
   constructor(name: String){
      this.name = name
   }
   fun someFun(){
      println("name : $name")
   }
   class SomeClass {}
}

클래스는 객체를 생성해 사용하며 객체로 클래스의 멤버에 접근합니다. 그런데 코틀린에서는 객체를 생성할 때 new 키워드를 사용하지 않습니다.

객체 생성과 멤버 접근
val user = User("kim")
user.someFun()

User(“kim”)이 객체를 생성하는 구문이며 클래스 이름과 같은 함수로 객체를 생성합니다.
객체를 생성할 때 생성자가 자동으로 호출되므로 소괄호 안에 전달한 인자는 클래스에 선언된 생성자의 매개변수와 들어맞아야 합니다. 앞에서 작성한 User 클래스의 생성자는 constructor(name: String)이므로 문자열 데이터를 전달받는 매개변수가 있습니다. 따라서 객체를 전달할 때 User(“kim”)처럼 문자열 데이터를 전달해 주어야 합니다.

thymeleaf 타임리프 적용(스프링입문 html)

간단한 html파일에 타임리프를 적용해 보았다.
기존 코드는 그대로 두고 th:를 통해 덧대었다.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
 <meta charset="utf-8">
 <link href="../css/bootstrap.min.css"
 th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
 <div class="py-5 text-center">
 <h2>상품 목록</h2>
 </div>
 <div class="row">
 <div class="col">
 <button class="btn btn-primary float-end"
onclick="location.href='addForm.html'"
 th:onclick="|location.href='@{/basic/items/add}'|"
type="button">상품 등록</button>
 </div>
 </div>
 <hr class="my-4">
 <div>
 <table class="table">
 <thead>
 <tr>
 <th>ID</th>
 <th>상품명</th>
 <th>가격</th>
 <th>수량</th>
 </tr>
 </thead>
 <tbody>
 <tr th:each="item : ${items}">
 <td><a href="item.html" th:href="@{/basic/items/{itemId}
(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
 <td><a href="item.html" th:href="@{|/basic/items/${item.id}|}"
th:text="${item.itemName}">상품명</a></td>
 <td th:text="${item.price}">10000</td>
 <td th:text="${item.quantity}">10</td>
 </tr>
 </tbody>
 </table>
 </div>
</div> <!-- /container -->
</body>
</html>

타임리프 사용 선언

<html xmlns:th="http://www.thymeleaf.org">


속성 변경 – th:href
th:href="@{/css/bootstrap.min.css}"
href="value1"th:href="value2" 의 값으로 변경
타임리프 뷰 템플릿을 거치면 원래 값을 th:xxx 값으로 변경. 만약 값이 없다면 새로 생성
HTML을 그대로 볼 때는 href 속성이 사용되고, 뷰 템플릿을 거치면 th:href 의 값이 href
대체되면서 동적으로 변경 가능
대부분의 HTML 속성을 th:xxx 로 변경 가능


타임리프 핵심
핵심은 th:xxx 가 붙은 부분은 서버사이드에서 렌더링 되고, 기존 것을 대체한다. th:xxx 이 없으면 기존 html의 xxx 속성이 그대로 사용
HTML을 파일로 직접 열었을 때, th:xxx 가 있어도 웹 브라우저는 th: 속성을 알지 못하므로 무시
따라서 HTML을 파일 보기를 유지하면서 템플릿 기능 가능


URL 링크 표현식 – @{…},
th:href="@{/css/bootstrap.min.css}"
@{…} : 타임리프는 URL 링크를 사용하는 경우 @{…} 를 사용. 이것을 URL 링크 표현식
URL 링크 표현식을 사용하면 서블릿 컨텍스트를 자동으로 포함


상품 등록 폼으로 이동
속성 변경 – th:onclick
onclick="location.href='addForm.html'"
th:onclick="|location.href='@{/basic/items/add}'|"
여기에는 다음에 설명하는 리터럴 대체 문법이 사용


리터럴 대체 – |…|
|…| :이렇게 사용
타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 더해서 사용해야 함

<span th:text="'Welcome to our application, ' + ${user.name} + '!'">

다음과 같이 리터럴 대체 문법을 사용하면, 더하기 없이 편리하게 사용 가능

<span th:text="|Welcome to our application, ${user.name}!|">

결과를 다음과 같이 만들어야 하는데
location.href='/basic/items/add'
그냥 사용하면 문자와 표현식을 각각 따로 더해서 사용해야 하므로 다음과 같이 복잡해짐
th:onclick="'location.href=' + '\'' + @{/basic/items/add} + '\''"
리터럴 대체 문법을 사용하면 다음과 같이 편리하게 사용 가능
th:onclick="|location.href='@{/basic/items/add}'|"


반복 출력 – th:each

<tr th:each="item : ${items}">


반복은 th:each 를 사용. 이렇게 하면 모델에 포함된 items 컬렉션 데이터가 item 변수에 하나씩 포함되고, 반복문 안에서 item 변수를 사용 가능
컬렉션의 수 만큼 <tr>..</tr>이 하위 테그를 포함해서 생성


변수 표현식 – ${…}

<td th:text="${item.price}">10000</td>

모델에 포함된 값이나, 타임리프 변수로 선언한 값을 조회 가능
프로퍼티 접근법을 사용 ( item.getPrice() )


내용 변경 – th:text

<td th:text="${item.price}">10000</td>

내용의 값을 th:text 의 값으로 변경
여기서는 10000을 ${item.price} 의 값으로 변경


URL 링크 표현식2 – @{…},

th:href="@{/basic/items/{itemId}(itemId=${item.id})}"

URL 링크 표현식을 사용하면 경로를 템플릿처럼 편리하게 사용 가능
경로 변수( {itemId} ) 뿐만 아니라 쿼리 파라미터도 생성
예) th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"
생성 링크: http://localhost:8080/basic/items/1?query=test


URL 링크 간단히
th:href="@{|/basic/items/${item.id}|}"
리터럴 대체 문법을 활용해서 간단히 사용 가능

참고
타임리프는 순수 HTML을 파일을 웹 브라우저에서 열어도 내용을 확인할 수 있고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있다. JSP를 생각해보면, JSP 파일은 웹 브라우저에서 그냥 열면 JSP 소스코드와 HTML이 뒤죽박죽 되어서 정상적인 확인이 불가능하다. 오직 서버를 통해서 JSP를 열어야 한다.
이렇게 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 네츄럴 템플릿(natural templates)이라 한다.

Kotlin 안드로이드 앱 프로그래밍 (7)

조건문과 반복문
조건문 if-else와 표현식

if-else 문

fun main() {
   var data = 10
   if (data > 0) {
      println("data > 0") */ data 0 보다 크면 */
   } else {
      println("data <= 0") */ data 0 보다 작거나 같을때 */
   }
}
> 실행결과
data > 0

if 문에 명시한 조건을 만족하면 if 부분을 실행, 그렇지 않으면 else 부분을 실행합니다. 또한 else if 문을 이용해서 아래와 같은 조건을 통해서 여러 개 나열할 수도 있습니다.

조건을 여러 개 나열한 예

fun main() {
   var data = 10
   if (data > 10) {
      println("data > 10") */ data 10 보다 클때 10 제외 */
   } else if (data > 0 && data <= 10") */ data 0 보다 크거나 10 보다 같거나 작을 때 */
      println("data > 0 && data <= 10")
   } else {
      println("data <= 0") */ data 0 또는 0 보다 작을 때 */
   }
}
> 실행 결과
data > 0 && data <= 10

위의 if-else문 else if 은 대부분의 프로그래밍 언어에서 제공하는 조건문과 차이가 없습니다. 그런데 코틀린에서는 if-else문은 표현식으로도 사용가능합니다. 표현식이란 결괏값을 반환하는 계산식을 말합니다. if-else문은 단순히 조건에 맞는 영역을 실행하는 용도로 사용해도 되지만, 결괏값을 반환하는 표현식으로도 사용가능합니다.

표현식으로 사용한 예

fun main() {
   var data = 10
   val result = if (data > 0) {
      println("data > 0")
      true
} else {
      println("data <= 0")
      false
   }
   println(result)
}
> 실행결과
data > 0
true

위의 소스를 보면 var result = if () {} else {} 형태로 작성하였습니다. 즉 if-else문의 조건에 맞는 코드를 실행하는 동작 외에 그 결괏값을 result 라는 변수에 대입하고 있는 if-else문 표현식 입니다.
if-else문을 표현식으로 사용하려면 else를 생략할 수 없습니다. if만 사용하거나 if-else를 if로 사용할 수 없다는 뜻입니다. 항상 else 구문이 있어야 하며 if-else 혹은 if-else if-else 형태로 사용하여야 합니다.
그리고 if-else 표현식이 반환하는 결괏값은 각 영역의 마지막 줄에 해당합니다. 즉 if-else 부분을 여러 줄로 작성했더라도 마지막 줄의 실행 결과만 반환합니다.

조건문 when
코틀린은 if-else문 말고도 when이라는 조건문을 작성할 수도 있습니다.

when 문 사용 예시

fun main() {
   var data = 10
   when (data) {
      10 -> println("data is 10")
      20 -> println("data is 20")
      else -> {
         println("data is not valid data")
      }
   }
}
> 실행 결과
data is 10

when 키워드 다음의 소괄호 () 안에 넣은 데이터가 조건이 되고 이 값에 따라 각 구문을 실행 합니다. 10 -> println() 코드는 when의 조건으로 지정한 데이터가 10 이면 -> 오른쪽에 있는 구문을 실행합니다.
when 문의 조건으로 정수가 아닌 다른 타입의 데이터를 지정할 수도 있습니다.

문자열 타입을 조건으로 사용

fun main() {
   var data = "hello"
   when (data) {
      "hello" -> println("data is hello")
      "world" -> println("data is world")
      else -> {
         println("data is not valid data")
      }
   }
}
> 실행 결과
data is hello

위 코드는 when 문에 지정한 데이터가 문자열이며 문자열 값에 따른 조건을 명시했습니다. 이처럼 when 문에서는 조건을 데이터 타입, 범위 등으로 다양하게 명시할 수 있습니다.

다양한 유형의 조건 제시

fun main() {
   var data: Any = 10
   when (data) {
      is String -> println("data is String") // data가 문자열 타입
      20, 30 -> println("data is 20 or 30") // data가 20 또는 30
      in 1..10 -> println("data is 1..10") // data가 1-10의 값
      else -> println("data is not valid")
   }
}
> 실행 결과
data is 1..10

위 소스에서 is는 타입을 확인하는 연산자이며 in은 범위 지정 연산자입니다. is String은 데이터가 String 타입이면 참이고, in 1..10은 데이터가 1부터 10까지 범위이면 참입니다.
또한 when 문을 이용하면서 데이터를 명시하지 않고 조건만 명시할 수도 있습니다.

조건만 명시한 예

fun main() {
   var data = 10
   when {
      data <= 0 -> println("data is <= 0")
      data > 100 -> println("data is > 100")
      else -> println("data is valid")
   }
}
> 실행 결과
data is valid

when은 if 문과 마찬가지로 표현식으로도 사용할 수 있습니다. when 문을 표현식으로 사용할 때는 else 문을 생략할 수 없습니다.

when 문을 표현식으로 사용

fun main() {
   var data = 10
   var result = when {
      data <= 0 -> "data is <= 0"
      data > 100 -> "data is > 100"
      else -> "data is valid"
   }
   println(result)
}
> 실행 결과
data is valid

대구대 정보 통합 검색 기능 개발 (1)

대구대 사이트에 있는 정보들을 하나로 통합하여 검색할 수 있으면 좋을 것 같아 개발하였습니다.

현재 대구대 사이트에는 아래와 같은 시설 검색이 가능합니다.

  • 편의 시설 정보(매점, 복사실, 편의점)
  • 학과 정보(학과 홈페이지 안내)
  • 교직원 연락처 정보(내선 번호)

추가적으로 비공개 데이터도 있습니다

  • 강의실 목록

이러한 데이터를 검색하려면 메뉴를 누르고 링크를 타고 접속해야 검색이 가능합니다.

예를 들어 전화번호를 검색하려면
① 상단 메뉴에서 대학안내 → 대학개요 → 전화번호안내 링크를 통해 접속하고
② 검색 분류를 선택한 뒤
③ 전화번호를 검색 해야 합니다.

이런 파편화된 데이터를 하나의 검색 박스에서 위 나열된 것을 검색하도록 하는것이 목표입니다.

우선 현재 데이터는 전부 JSON 으로 저장되어 있습니다. 예시 JSON 은 다음과 같습니다.

# 강의실 정보
{
  "id":"법행1100",
  "name":"1층 공통공간",
  "floor":"1층",
  "type":"17558688",
  "location":"법행정대학관"
}

# 시설 정보
{
  "id": "중앙도서관열람관",
  "sectors": "편의점",
  "name": "이마트24",
  "floor": "지하",
  "office_phone": "",
  "phone_number": "010-****-****",
  "type": "10263233"
}

# 학과 정보
{
  "id":"인문대학",
  "name":"한국어문학부",
  "url":"http://koreandu.daegu.ac.kr/",
  "image_url":"http://koreandu.daegu..."
}

# 연락처 정보
{

  "name_kr": "강**",

  "upmu": null,
  "buseo": "재활과학대학 재활건강증진학과",
  "user_upmu": "교육지원조교",
  "bojik_nm": " ",
  "sosok": null,
  "jik_id": "*****",
  "user_telno": "6095",
  "e_mail": "********@naver.com",
  "hompy_addr": null,
  }

이 정보를 키워드 형태로 검색할 수 있게 가공해야 합니다.

MariaDB 같은 RDBMS에 집어넣으려면 JSON을 표 처럼 변환해줘야 합니다. 검색과 별개로 사용자에게 보여줄 정보는 필요하기 때문에 필수적인 항목만 추려 보았습니다.

이제 위 형태로 변환시키는 코드를 만듭니다. 배열로 처리 할 수 있지만 기억력도 좋지 않은데다 표를 봐 가면서 해야하기 때문에 dataclass 로 정의해보도록 하겠습니다.

from dataclasses import dataclass
from typing import Optional

@dataclass
class CsvForm:
    idx: Optional[int] = None
    blah_id: Optional[str] = None
    name: Optional[str] = None
    location: Optional[str] = None
    tel_no: Optional[str] = None
    mobile_no: Optional[str] = None
    category: Optional[str] = None
    web_address: Optional[str] = None
    email_address: Optional[str] = None
    tags: Optional[str] = None

    def __get_data_array(self):
        return [
            self.idx, self.blah_id, self.name, self.location,
            self.tel_no, self.mobile_no, self.category, self.web_address,
            self.email_address, self.tags,
        ]

    def __getitem__(self, item):
        return self.__get_data_array()[item]

    def __repr__(self):
        return self.__get_data_array()

    def __len__(self):
        return len(self.__get_data_array())

어쨌든 CSV 로 변환해야 하기 때문에 class 형태지만 배열처럼 동작해야 합니다. __getitem__()__len__() 을 구현해 줌으로써 배열 처럼 동작하게 할 수 있습니다.

(2) 에서는 sqlite3 을 사용해서 메모리에 데이터베이스를 올리고 쿼리 생성까지 만들어 보겠습니다.

Kotlin 안드로이드 앱 프로그래밍 (6)

함수의 매개변수에는 기본값(default value)을 선언할 수 있습니다. 만약 어떤 매개변수에 기본값을 선언했다면 호출할 때 인자를 전달하지 않아도 되며 이때 선언문에 명시한 기본값이 적용됩니다.

* 기본값 활용

fun main(){
    fun some(data1: Int, data2: Int = 10): Int{
        return data1 * data2
}
    println(some(10))
    println(some(10, 20))
}

>> 실행결과
100
200

어떤 함수의 매개변수가 여러 개면 호출할 때 전달한 인자를 순서대로 할당합니다. 즉, 첫 번째 인자를 첫 번째 매개변수에 할당합니다. 그런데 호출할 때 매개변수명을 지정하면 매개변숫값의 순서를 바꿔도 됩니다.

* 매개변수명 생략 - 매개변수 순서대로 할당

fun some(data1: Int, data2: Int): Int {
    return data1 * data2
}
println(some(10, 20))

위 소스처럼 some() 이라는 함수에 매개변수를 2개 선언하고 some(10, 20)으로 함수를 호출하면 data1에 10, data2에 20을 대입합니다. 그런데 오른쪽처럼 매개변수명을 지정해서 호추할 수 있습니다.

* 매개변수명을 지정하여 호출

some(data2 = 20, data1 = 10)

매개변수명을 지정하여 호출하는 것을 명명된 매개변수라고 합니다. 이렇게 하면 함수 선언문의 매개변수 순서에 맞춰 호출하지 않아도 됩니다.

컬렉션 타입
여러개의 데이터를 표현하는 방법이며, Array List Set Map 이 있습니다.
Array – 배열 표현
코틀린의 배열은 Array 클래스로 표현합니다. Array 클래스의 생성자에서 첫 번째 매개변수 배열의 크기이며 두 번째 매개변수는 초깃값을 지정하는 함수 입니다. 배열의 타입은 제네릭으로 표현합니다. Array<Int>로 선언하면 정수 배열을 의미하며 Array<String>으로 선언하면 문자열 배열을 의미합니다.

* Array 클래스의 생성자

<init>(size: Int, init: (Int) -> T)

오른쪽 코드는 Array() 생성자의 첫 번째 인자가 3이고 두번 째 인자는 0을 반환하는 람다 함수이므로 0으로 초기화한 데이터를 3개 나열한 정수형 배열을 선언합니다.

* 배열 선언 예

val data1: Array<Int> = Array(3, {0})

배열의 데이터에 접근할 때는 대괄호([])를 이용해도 되고 set()이나 get() 함수를 이용할 수도 있습니다.

* 배열의 데이터에 접근하는 예

fun main(){
    val data1: Array<Int> = Array(3, {0})
    data[0] = 10
    data[1] = 20
    data.set(2, 30)

    println(
        """
    array size : ${data1.size}
    array data : ${data1[0]}, ${data1[1]}, ${data1.get(2)}
    """
    )
}

>> 실행결과
array size : 3
array data : 10, 20, 30

기초 타입의 배열
기초 타입이라면 Array 클래스를 사용하지 않고 각 기초 타입의 배열을 나타내는 클래스를 이용할 수도 있습니다. 즉 BooleanArray, ByteArray, CharArray, DoubleArray, FloatArray, IntArray, LongArray, ShortArray 클래스를 이용할 수도 있습니다.

* 기초 타입 배열 선언

val data1: IntArray = IntArray(3, {0})
val data2: BooleanArray = BooleanArray(3, {false})

또한 arrayof() 라는 함수를 이용하면 배열을 선언할 때 값을 할당할 수도 있습니다.

* 배열 선언과 동시에 값 할당

fun main(){
    val data1 = arrayOf<Int>(10, 20, 30)
    println(
         """
    array size : ${data1.size}
    array data : ${data1[0]}, ${data1[1]}, ${data1.get(2)}
    """
    )
}

>> 실행결과
array size : 3
array data : 10, 20, 30

arrayOf() 함수도 기초 타입을 대상으로 하는 booleanArrayOf(), byteArrayOf(), charArrayOf(), doubleArrayOf(), floatArrayOf(), intArrayOf(), longArrayOf(), shortArrayOf() 함수를 제공합니다.

* 기초 타입 arrayOf() 함수

val data1 = intArrayOf(10, 20, 30)
val data2 = booleanArrayOf(true, false, true)

List Set Map
Collection 인터페이스 타입으로 표현한 클래스이며 통틀어서 컬렉션 타입 클래스라고 합니다.
List : 순서가 있는 데이터 집합으로 데이터 중복을 허용
Set : 순서가 없으며 데이터의 중복을 허용하지 않음
Map : 키와 값으로 이루어진 데이터 집합으로 순서가 없으며 키의 중복은 허용하지 않음
Collection 타입의 클래스는 가변 클래스와 불변 클래스로 나뉩니다.
불변 클래스 : 초기에 데이터를 넣으면 이제 변경 불가
가변 클래스 : 초기에 데이터를 넣은 후에도 추가하거나 변경 가능
List를 예로 들면 코틀린에서는 가변과 불변이라는 2가지 타입의 클래스를 제공합니다. List는 불변 타입이므로 size(), get() 함수만 제공하고 데이터를 추가하거나 변경하는 add(), set() 함수는 제공하지 않습니다. 그런데 MutableList는 가변 타입이므로 size(), get() 함수 이외에 add(), set()함수를 이용할 수 있습니다. 이는 Set, Map 도 마찬가지 입니다. Set, Map 은 불변 타입이며 MutableSet, MutableMap은 가변 타입입니다.

* 가변 타입과 불변 타입성 * 

<구분> <타입>        <함수>               <특징>
List   List          listOf()            불변
       MutableList   mutableListOf()     가변
Set    Set           setOf()             불변
       MutableSet    mutableSetOf()      가변
Map    Map           mapOf()             불변
       MutableMap    mutableMapOf()      가변

다음 소스는 listOf() 함수로 List 객체를 만들며 매개변수에 초깃값을 대입합니다. List 객체의 데이터는 배열처럼 대괄호를 이용해 얻을 수도 있지만 get() 함수를 사용해도 됩니다.

* 리스트 사용 예

fun main(){
    var list = listOf<Int>(10, 20, 30)
    println(
    """
    list size : ${list.size}
    list data : ${list[0]}, ${list.get(1)}, ${list.get(2)}
    """
    )
}

>> 실행결과
list size : 3
list data : 10, 20, 30

MutableList는 mutableListOf() 함수로 만들며 데이터를 추가하거나 변경할 때는 add(), set() 함수를 이용할 수 있습니다.

* 가변 리스트 사용 예

fun main() {
    var mutableList = mutableListOf<Int>(10, 20, 30)
    mutableList.add(3, 40)
    mutableList.set(0, 50)
    println(
    """
    list size : ${mutableList.size}
    list data : ${mutableList[0]}, ${mutableList.get(1)},
                ${mutableList.get(2)}, ${mutableList.get(3)}
    """
   )
}

>> 실행결과
list size : 4
list data : 50, 20, 30, 40

Map 객체는 키와 값으로 이루어진 데이터의 집합입니다. Map 객체의 키와 값은 Pair 객체를 이용할 수도 있고 ‘키 to 값’ 형태로 이용할 수도 있습니다.

* 집합 사용 예

fun main() {
    var map = mapOf<String, String>(Pair("one", "hello"), "two" to "world")
    println(
    """
    map size : ${map.size}
    map data : ${map.get("one")}, ${map.get("two")}
    """
    )
}

>> 실행결과
map size : 2
map data : hello, world

위 코드에서 mapOf() 함수를 이용해 Map 객체를 만들었습니다. 이때 <String, String> 제네릭 타입을 지정하였고 따라서 Map 객체에 대입되는 데이터의 키와 값은 모두 String 타입이 됩니다. Pair(“one”, “hello”)처럼 키값을 Pair 객체로 표현해서 Map에 대입할 수도 있고, “two” to “world” 처럼 Map 객체에 대입할 수도 있습니다.

Elasticsearch bool query

새로 배운 내용


집계나 조건 검색 할때 query 보다 필터가 더 빠름
이유는 필터는 100% 일치하는 문서만 반환하는 데 비해 쿼리는 입력값과 비슷한 문서도 검색 하고 점수계산을 시행함.

(+) 필터의 이점은 자주 사용하는 필터는 엘라스틱이 자동으로 캐시해준다.
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html

여러개의 필터(조건)를 걸려면 부울 쿼리 사용
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html

{
  "bool" : {
    "must" : [],
    "should" : [],
    "must_not" : [],
    "filter": []
  }
}

부울 쿼리에서 사용되는 모드

  • must
    • All of these clauses must match. The equivalent of AND.
    • must 안에 있는 쿼리들은 모두 일치해야 함.
  • must_not
    • All of these clauses must not match. The equivalent of NOT.
    • must_not 안에 있는 쿼리들은 일치하지 않아야 함
  • should
    • At least one of these clauses must match. The equivalent of OR.
    • should 쿼리중 n개 이상 매치되어야 함. minimum_should_match 로 최소 몇개가 매치되어야 하는지 조절할 수 있음.
  • filter
    • Clauses that must match, but are run in non-scoring, filtering mode.
    • must match 같이 동작하지만 점수 계산 안 함.

선택정렬(Selection Sort)

선택 정렬(Selection Sort)이란?

1. 리스트의 최솟값을 찾는다.

2. 그 값을 리스트의 맨 앞자리 값과 교체한다.
(맨 앞자리 값이 최솟값일 경우 Pass)

3. 1,2 과정을 정렬이 완료 시점까지 반복.

출처 : Naver 이미지

선택 정렬 C코드

#include<stdio.h>
int main()
{
    int A[10], i, j, c, k;
    for(i=0 ; i<10 ; i++)
    {
        scanf("%d",&A[i]);        //숫자 10개를 입력 받는다.
    }
    for(i=0 ; i<9 ; i++)              //맨 앞자리 숫자를 잡아준다.
    {
        for(j=i+1 ; j<10 ; j++)//맨 앞자리 숫자를 제외한 리스트 값을 비교한다.
        {
            if(A[i] > A[j]) // 최솟값이 순서대로 Swap한다.
            {
                c = A[i];
                A[i] = A[j];
                A[j] = c;
            }
        }
        printf("정렬 %d회 : ",i+1); //정렬 과정을 출력한다.
        for(k=0;k<10;k++)
            printf("%d ",A[k]);
        printf("\n");
    }
}

선택 정렬 알고리즘 핵심

for(i=0;i<9;i++)              
{
   for(j=i+1;j<10;j++) //j=i+1 초기 값 설정으로 정렬된 리스트에 영향 X
   {
       if(A[i]>A[j])//조건 식에 따라 오름차순 또는 내림차순으로 정렬 가능.
       {
           c=A[i];     //모든 정렬에서 핵심 부분이다.
           A[i]=A[j];  //각 칸마다 있는 리스트 숫자를 서로 Swap 위해서는
           A[j]=c;     //변수c를 사용하여 리스트의 숫자를 보관한다. 
       }
    }
}

선택 정렬 추가 알고리즘(함수)

int SelectionSort(int SortList, int n)
{
   int i, j, min = 0, sp = 0;
   for (i = 0; i < n - 1; i++)
   {
      min = i;
      for (j = i + 1; j < n; j++)
      {
	if (SortList[min] > SortList[j])
	min = j;
      }
      //리스트 중 가장 작은 값과 SortList[i]값 Swap Code.
      sp = SortList[i];
      SortList[i] = SortList[min];
      SortList[min] = sp;
      }
}

선택 정렬 알고리즘을 함수(SelectionSort)로 사용하는 방법도 있습니다.

활동을 하면서 글의 주제 선정과 방향성에 대해서도 아직 부족하다고 느낍니다.
프로그래밍 언어와 관련된 코딩 공부와 전공에 관한 실습에 대해서도 추가적으로 공부하여 글을 올려볼 예정입니다. 저의 글을 보시고 편하게 피드백 해주시면 감사하겠습니다.