자바 좋은데 왜 다들 코틀린씀 ?
대한민국은 자바 공화국이다.
그런데 요즘 심심치 않게 코틀린 채용공고가 보인다.
도대체 뭐가 좋아서 코틀린을 다들 쓰는지 알아보도록 하자.
코틀린의 역사
코틀린은 Jet Braoms사에서 개발되었는데,
너무 많은 보일러플레이트 코드 +
Null 안전성 부족 +
코드 포현력의 한계로
코틀린을 개발하게 되었고,
자바와 상호 운용성을 목표로 99% 호환되는 코틀린으로 자바 기반 Intellij IDEA개발에 활용했다고 한다.
Kotlin vs Java
(당신이라면 무엇을 채택하겠는가?)
코틀린
- 간결함 -> 보일러플레이트 코드 감소
- Null safety
- 확장 함수
- 데이터 클래스
- 디폴트 파라미터
- 함수형 프로그래밍
- 코루틴
자바
- 성숙한 생태계
- 상대적으로 빠른 컴파일 속도
- 대규모 인력 풀
- 명시적인 문법
- 메모리 사용 최적화
자세히 살펴보자
코틀린
1. 간결함
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
(자바)
vs
class Person(var name: String, var age: Int)
(코틀린)
2. Null Safety
public String getUppercaseName(Person person) {
if (person != null && person.getName() != null) {
return person.getName().toUpperCase();
}
return null;
}
// 사용 시
String result = getUppercaseName(person);
if (result != null) {
System.out.println(result);
}
(자바)
vs
fun getUppercaseName(person: Person?): String? {
return person?.name?.uppercase()
}
// 사용 시
getUppercaseName(person)?.let { println(it) }
(코틀린)
3. 확장 함수
// Java에서는 유틸리티 클래스를 만들어야 함
public class StringUtils {
public static boolean isValidEmail(String email) {
return email.contains("@") && email.contains(".");
}
}
// 사용할 때
String email = "test@example.com";
boolean valid = StringUtils.isValidEmail(email); // 어색한 사용법
(자바)
vs
// String 클래스에 새로운 함수를 추가
fun String.isValidEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
// 사용할 때
val email = "test@example.com"
val valid = email.isValidEmail() // 자연스러운 사용법
(코틀린)
4. 데이터 클래스
public class Person {
private String name;
private int age;
// 생성자
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getter들
public String getName() { return name; }
public int getAge() { return age; }
// setter들
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
// 객체 비교를 위한 equals
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
// 해시코드 생성
@Override
public int hashCode() {
return Objects.hash(name, age);
}
// 문자열 출력을 위한 toString
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + '}';
}
...
}
(자바)
vs (50줄 이상 vs 1줄)
data class Person(val name: String, val age: Int)
(코틀린)
5. 디폴트 파라미터
public class DatabaseConnection {
public void connect() {
connect("localhost", 3306, "default_db");
}
public void connect(String host) {
connect(host, 3306, "default_db");
}
public void connect(String host, int port) {
connect(host, port, "default_db");
}
public void connect(String host, int port, String database) {
// 실제 연결 로직
}
}
(자바)
vs
class DatabaseConnection {
fun connect(host: String = "localhost", port: Int = 3306, database: String = "default_db") {
// 실제 연결 로직
}
}
(코틀린)
6. 함수형 프로그래밍
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
(자바)
vs
val names = listOf("Alice", "Bob", "Charlie", "David")
val result = names
.filter { it.length > 3 }
.map { it.uppercase() }
(코틀린)
자바
1. 성숙한 생태계(라이브러리 활용)
// Apache Commons Lang 사용
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
// Spring Framework 사용
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
}
// Hibernate JPA 사용
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String username;
}
// Jackson JSON 처리
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(user);
User user = mapper.readValue(jsonString, User.class);
(자바)
vs
// 자바 라이브러리 사용 시 코틀린 스타일과 맞지 않을 수 있음
val mapper = ObjectMapper() // Java 라이브러리
val jsonString = mapper.writeValueAsString(user) // Java 스타일
(코틀린)
2. 빠른 컴파일 속도
코틀린이 느린 이유
1. 코틀린은 Null Safety, 확장함수, 코루틴 등 자바보다 훨씬 많은 문법적 기능을 제공하여
바이트코드로 바꿀 시 자바보다 변환 과정이 복잡해짐
ex)
val name: String? 같은 null 체크
(→ 자바에서는 없는 개념이라 변환 로직이 필요함.)
data class → equals, hashCode, toString 같은 메서드를 자동 생성
(→ 이 과정에서 추가 코드 생성이 일어남.)
2. 컴파일 아키텍처 차이
자바 컴파일러(javac)는 수십년간 최적화되어 매우 빠르나,
코틀린 컴파일러(kotlinc)는 상대적 신생이라 최적화 수준이 낮음.
3. 코틀린의 멀티플랫폼 특화 기능
코틀린은 JVM 뿐 아니라 JS, Native등 멀티 플랫폼 지원을 목표로 하고 있어서,
컴파일러 구조가 자바보다 복잡하게 설계됨 -> 성능에 악영향
3. 명시적인 문법
public class UserValidator {
public boolean isValidUser(User user) {
// 각 단계가 명확하게 보임
if (user == null) {
return false;
}
if (user.getName() == null || user.getName().trim().isEmpty()) {
return false;
}
if (user.getAge() < 0 || user.getAge() > 150) {
return false;
}
if (user.getEmail() == null || !isValidEmail(user.getEmail())) {
return false;
}
return true;
}
private boolean isValidEmail(String email) {
return email.contains("@") && email.contains(".");
}
}
(자바)
vs
class UserValidator {
fun isValidUser(user: User?) = user?.let {
it.name?.trim()?.isNotEmpty() == true &&
it.age in 0..150 &&
it.email?.isValidEmail() == true
} ?: false
private fun String.isValidEmail() = contains("@") && contains(".")
}
(코틀린) 함축적이고 이해가 어려움
총평:
| 코틀린 | 자바 | |
| 가독성 | 상 | 중 |
| 생산성 | 상 (간결함 + null safety) | 중 (명확하지만 코드량 많음) |
| 코드수 | 상 (보일러플레이트 최소화) | 하 (게터/세터, DTO 등) |
| 학습 곡선 | 중 | 상 (단순하고 직관적) |
| 컴파일 속도 | 하 | 상 (빠르고 최적화 잘됨) |
| 안정성 | 중 | 상 (수십 년간 검증) |
결론:
자바와 코틀린의 특장점은 명확하다.
이거다~ 싶은걸 채택하면 되겠다.
끝