fun main(){
val t1 = TestClass1()
println("t1.a1 : ${t1.a1}")
t1.testFun1()
println("TestClass1.a2 : ${TestClass1.a2}")
TestClass1.testFun2()
// java 파일에 정의된 static 맴버 사용
println("JavaMain.javaA1 : ${JavaMain.JavaA1}")
JavaMain.javaMethod1()
}
class TestClass1{
// 일반 멤버 변수
var a1 = 100
// companion 멤버
companion object{
var a2 = 200
@JvmStatic var kotlinA3 = 300
@JvmStatic fun kotlinMethod3(){
println("KotlinMethod3")
}
fun testFun2(){
println("testFun2")
println("a2 : $a2")
// companion object 입장에서 봤을 때
// 클래스를 가지고 객체를 생성했다는 것을 100% 보장받을 수 없기 때문에
// 일반 멤버의 접근이 불가능하다.
// testFun1()
// println("a1 : $a1")
}
}
// 일반 메서드
fun testFun1(){
println("testfun1")
println("a1 : $a1")
// 객체 입장에서는 companion 맴버가 메모리에 올라가 있다는 것을
// 보장받을 수 있으므로 사용이 가능하다.
testFun2()
println("a2 : $a2")
}
}
public class JavaMain {
public static int JavaA1 = 100;
public static void javaMethod1(){
System.out.println("javaMethod1");
}
public static void main(String [] args){
// kotlin에서 정의한 companion 멤버 사용
int a1 = TestClass1.Companion.getKotlinA3();
System.out.printf("a1 : %d\n", a1);
TestClass1.Companion.kotlinMethod3();
}
}
자바에서
static과 동일하다. 클래스내에companion멤버로 정의된 요소들은 객체 생성 없이 사용이 가능하며, 클래스 이름을 통해 접근한다.companion변수의 경우 하나만 생성되어 사용할 수 있다.
@JavaStatic은companion멤버를 자바에서 사용할 수 있게 해주는 이노테이션으로, JVM 버전에 따라 실행이 안될 수 있기 때문에 사용을 권장한다.
fun main(){
// 일반 클래스로 객체를 생성한다.
var obj1 = TestClass1(100, 200)
var obj2 = TestClass2(100, 200)
// 멤버 사용
println("obj1.a1 : ${obj1.a1}")
println("obj1.a2 : ${obj1.a2}")
println("obj2.a1 : ${obj2.a1}")
println("obj2.a2 : ${obj2.a2}")
obj1.testMethod1()
obj2.testMethod2()
println("------------------------------------------")
// 부생성자를 이용한 객체 생성
var obj3 = TestClass1(100, 200, 300)
var obj4 = TestClass2(100, 200, 200)
println("obj3.a1 : ${obj3.a1}")
println("obj3.a2 : ${obj3.a2}")
println("obj3.a3 : ${obj3.a3}")
println("obj4.a1 : ${obj4.a1}")
println("obj4.a2 : ${obj4.a2}")
println("obj4.a3 : ${obj4.a3}")
println("------------------------------------------")
var obj5 = TestClass1(100, 200, 300)
var obj6 = TestClass1(100, 200, 300)
// 일반 클래스를 통해 만들어진 객체들을 객체의 ID가 같은지를 비교한다.
if(obj5 == obj6){
println("obj5와 obj6은 같은 객체 입니다")
} else {
println("obj5와 obj6은 다른 객체 입니다")
}
var obj7 = TestClass2(100, 200, 300)
var obj8 = TestClass2(100, 200, 300)
// data 클래스는 주 생성자를 통해 정의된 멤버 변수의 값이 같은지를 비교한다.
if(obj7 == obj8){
println("obj7과 obj8은 같은 객체 입니다")
} else {
println("obj7과 obj8은 다른 객체 입니다")
}
println("------------------------------------")
val obj9 = obj7.copy()
println("obj7.a1 : ${obj7.a1}")
println("obj9.a1 : ${obj9.a1}")
// obj9.a1의 값을 변경한다.
obj9.a1 = 1000
println("obj7.a1 : ${obj7.a1}")
println("obj9.a1 : ${obj9.a1}")
println("------------------------------------")
// data class를 통해 만든 객체는 주 생성자에 정의한 멤버 변수를
// componentN 메서드로 값을 받아올 수 있다.
val num1 = obj7.component1()
val num2 = obj7.component2()
println("num1 : $num1")
println("num2 : $num2")
println("-------------------------------------------")
val (num10, num11) = obj7
println("num10 : $num10")
println("num11 : $num11")
}
// 일반 클래스
class TestClass1(var a1:Int, var a2:Int){
var a3:Int = 0
init{
println("TestClass1의 init")
}
constructor(a1:Int, a2:Int, a3:Int) : this(a1, a2){
this.a3 = a3
}
fun testMethod1(){
println("TestClass1의 testMethod1입니다")
}
}
data class TestClass2(var a1:Int, var a2:Int){
var a3:Int = 0
init{
println("TestClass2의 init")
}
constructor(a1:Int, a2:Int, a3:Int) : this(a1, a2){
this.a3 = a3
}
fun testMethod2(){
println("TestClass2의 testMethod2 입니다")
}
}
객체의 멤버를 보다 쉽게 관리할 수 있는 기능이 추가되어 있고, abstract, open, sealed, inner 클래스로 정의할 수 없다. 반드시 주생성자를 생성해야한다. 이렇게 주 생성자를 강제적으로 작성하는 이유는 멤버 변수를 갖기 위해서이다. 추가적으로 부생성자, init 블록을 포함 시킬 수 있다.
data class TestClass2(var a1:Int, var a2:Int){
var a3:Int = 0
init{
println("TestClass2의 init")
}
constructor(a1:Int, a2:Int, a3:Int) : this(a1, a2){
this.a3 = a3
}
fun testMethod2(){
println("TestClass2의 testMethod2 입니다")
}
}
데이터 클래스는 equals(), hashCode(), copy(), toString, ComponentN()이 제공된다.
if(obj5 == obj6){
println("obj5와 obj6은 같은 객체 입니다")
} else {
println("obj5와 obj6은 다른 객체 입니다")
}
var obj7 = TestClass2(100, 200, 300)
var obj8 = TestClass2(100, 200, 300)
if(obj7 == obj8){
println("obj7과 obj8은 같은 객체 입니다")
} else {
println("obj7과 obj8은 다른 객체 입니다")
}
val obj9 = obj7.copy()
println("obj7.a1 : ${obj7.a1}")
println("obj9.a1 : ${obj9.a1}")
val num1 = obj7.component1()
val num2 = obj7.component2()
println("num1 : $num1")
println("num2 : $num2")
println("-------------------------------------------")
val (num10, num11) = obj7
println("num10 : $num10")
println("num11 : $num11")
클래스나 함수를 작성할 때 타입을 추상화하여 일반화된 상태로 작성하는 것으로 코드의 재사용성과 유연성을 높일 수 있다. 제네릭을 사용하면 컴파일 할 때 객체의 자료형을 확인 하기 때문에 객체 자료형의 안정성을 높일 수 있고 형 변환의 번거러움을 줄일 수 있다.
val t7:TestClass5<SubClass1> = TestClass5<SubClass1>()
// 불변성 (제네릭에 키워드를 붙히지 않는다)
class TestClass5<A>()
// 공변성
val t10:TestClass6<SubClass1> = TestClass6<SubClass1>()
val t11:TestClass6<SuperClass1> = TestClass6<SubClass1>()
// val t12:TestClass6<SubClass2> = TestClass6<SubClass1>()
class TestClass6<out A>()
val t13:TestClass7<SubClass1> = TestClass7<SubClass1>()
// val t14:TestClass7<SuperClass1> = TestClass7<SubClass1>()
val t15:TestClass7<SubClass2> = TestClass7<SubClass1>()
// 반 공변성
class TestClass7<in A>()
class Outer1{
var outerV1 = 100
fun outerMethod(){
println("Outer1의 outerMethod 입니다")
}
inner class Inner1{
fun innerMethod(){
println("outerV1 : $outerV1")
outerMethod()
}
}
}
내부에 있는 클래스의 객체 생성은 외부 클래스로 부터 생성한 객체를 통해 생성할 수 있다. 내부의 클래스를 가지고 만든 객체는 외부 클래스를 통해 만든 객체가 무조건 있다는 것을 보장받을 수 있기 때문에 외부 클래스에 정의한 멤버의 접근이 자유롭다.
interface Inter1{
fun interMethod1()
fun interMethod2()
}
class TestClass1 : Inter1{
override fun interMethod1() {
println("TestClass1의 interMethod1")
}
override fun interMethod2() {
println("TestClass1의 interMethod2")
}
}
interface Inter2{
fun interMethod3()
fun interMethod4()
}
인터페이스를 구현하거나 클래스를 상속 받은 다음에 메서드를 오버라이딩한 클래스로 만들고 위로 올라가서 객체 생성해 사용해야한다. 만약 클래스를 통해 생성하는 객체가 두 개 이상이면 클래스를 정의하고 객체 생성해서 사용한다.
val value1:String = str1!!
println("value1 : $value1")
!! 연산자 : 널을 허용하는 타입의 변수 값을 널을 허용하지 않는 타입으로 변환하여 널을 허용하지 않는 타입의 변수에 담을 수 있도록 한다.
val value1:String = str1 ?: "기본문자열"
println("value1 : $value1")
?: 연산자 : null 아닌 객체의 ID가 들어있으면 그 ID를 지정된 기본값으로 반환한다.
println("t1.str1 : ${t1?.str1}")
println("t1.str2 : ${t1?.str2}")
t1?.testMethod1()
?. 연산자
참조변수?.멤버변수 : 참조변수에 null값 들어 있다면 null이 반환된다.
참조변수?.멤버 메서드 : 참조변수에 null값 들어 있다면 메서드를 호출하지 않는다.
if(str1 != null){
val value1:String = str1
println("value1 : $value1")
}
만약 변수의 값이 null인 경우 코드가 동작하지 않도록 처리해주면 null 안정성을 확보할 수 있다. 이때, null을 허용하는 변수를 null을 허용하지 않는 변수처럼 자유롭게 사용할 수 있다.