Basics
Basics ๊ด๋ จ
๊ณตํต
์์ฃผ์ฐ๋ Singleton Pattern
public final class FooBar {
private static final FooBar INSTANCE = null;
private FooBar() {}
public static synchronized FooBar getInstance() {
if (INSTANCE == null)
INSTANCE = new FooBar();
return INSTANCE;
}
// ...[์๋ต]...
}
class FooBar {
companion object {
@Volatile private var INSTANCE: FooBar? = null
@JvmStatic fun getInstance(): FooBar = INSTANCE ?: synchronized(this) {
INSTANCE ?: build().also { INSTANCE = it }
}
private fun build() = FooBar()
}
}
์์ฃผ์ฐ๋ Enum Pattern
Lombok์ ์ฌ์ฉํ์ฌ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์์ฑ
package com.example.markiiimark;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Getter
@AllArgsConstructor
public enum FooBar {
A("a", "1", 1),
B("b", "2", 2),
C("c", "3", 3);
private String code;
private String a;
private int b;
private static Map<String, FooBar> findMap
= new ConcurrentHashMap<>();
static {
for (Foobar item: values())
findMap.put(item.getCode(), item);
}
public static Foobar findByCode(int i) { return findMap.get(i); }
// ...[์๋ต]... ์ด ๋ถ๋ถ ๋ถํฐ๋ ์์์ ์์ฉ ์ฝ๋ ์ ์ฉ
}
enum class FooBar(
val code: String,
val a: String,
val b: Int
) {
A("a", "0", 0),
B("b", "1", 1),
C("c", "2", 2);
companion object {
private val findMap: MutableMap<Int, FooBar>
= ConcurrentHashMap<Int, FooBar>()
init {
values().forEach { findMap[it.code] = it }
}
@JvmStatic fun findByCode(code: Int): FooBar?
= findMap[code]
}
}
์์ฃผ์ฐ๋ Documentation
for Javadoc
package com.example.markiiimark;
/**
* FooGaze
*
* ํด๋์ค๋ ๋ฌด์ ๋ฌด์์ ํฉ๋๋ค.
* @since 2022.12.12
* @author chlee
* @see com.example.marikiimark.FooBar
*/
public class FooGaze {
/**
* doStuff
* ๋ญ ํ ๊ฒ์ธ๊ฐ
*
* @param input {@link String} ์
๋ ฅ๊ฐ
* @returns {@link String} ์ถ๋ ฅ๊ฐ
*/
public String doStuff(String input) {
//...[์๋ต]...
return input + " a";
}
}
for KDoc
package com.example.markiiimark
/**
* [FooGaze]
* ํด๋์ค๋ ๋ฌด์ ๋ฌด์์ ํฉ๋๋ค.
*
* @since 2022.12.12
* @author chlee
* @see com.example.markiiimark.FooBar
*/
class FooGaze {
/**
* [FooGaze.doStuff]
* ๋ญ ํ ๊ฒ์ธ๊ฐ
*
* @param input [String] ์
๋ ฅ๊ฐ
* @returns [String] ์ถ๋ ฅ๊ฐ
*/
fun doStuff(input: String): String {
// ... [์๋ต] ...
return input + " a"
}
}
Kotlin
์์ฃผ์ฐ๋ Builder Pattern
data class FooBar(
val a: String = ""
val b: String = ""
val c: Boolean = false,
val d: Boolean = false,
val e: String = ""
) {
class Builder {
private var bA: String = ""; fun a(block: () -> String) { bA = block() }
private var bB: String = ""; fun b(block: () -> String) { bB = block() }
private var bC: String = ""; fun c(block: () -> String) { bC = block() }
private var bD: String = ""; fun d(block: () -> String) { bD = block() }
private var bE: String = ""; fun e(block: () -> String) { bE = block() }
fun build(): FooBar = FooBar(bA, bB, bC, bD, bE)
}
companion object {
inline fun builder(block: Builder.() -> Unit): FooBar = Builder().apply(block).build()
}
}
in
, out
, and where
`in`, `out`, and `where`
They are variance modifiers and they help us allowing subtyping when using generics
Suppose we have a class called Case
that will indicate if we either consume a weapon or produce it
class Case<T: Weapon> {
private val contents = mutableListOf<T>()
fun produce(): T = contents.last()
fun consume(item: T) = contents.add(item)
}
You might think that this is indeed possible thanks to polymorphism
val sniperRifle: Case<SniperRifle> = Case<SniperRifle>()
var rifle = Case<Rifle>()
rifle = sniperRifle // NOPE
Normally assigning a child instance to a parent instance is possible. But this time it isn't because generics doesn't allow subtyping by default and by that I mean we have to yous keywords like out
and in
to be able to use subtyping.
If we declare T
with an out
modifier, it will be convariant, so now we preserve subtyping but we cannot consume (take T
as parameters) T
, we can just produce (return) it.
class Case<out T: Weapon> {
private val contents = mutableListOf<T>()
fun produce(): T = contents.last()
// this is nolonger possible
// fun consume(item: T) = contents.add(item)
}
This is now possible
val sniperRifle: Case<SniperRifle> = Case<SniperRifle>()
var rifle = Case<Rifle>()
rifle = sniperRifle
rifle.produce()
if we declare T
with an in
modifier, it will be contravariant. Think of it as the other way around (sort of), because we can now consume T
but we can't produce T
class Case<oin T: Weapon> {
private val contents = mutableListOf<T>()
// this is nolonger possible
// fun produce(): T = contents.last()
fun consume(item: T) = contents.add(item)
}
But here is where things get a little bit weird. Now a parent class will be the child of its child class, so rifle
is now a subclass of sniperRifle
so you treat it as if rifle
is extending sniperRifle
or as if weapon is extending knife and rifle
val sniperRifle: Case<SniperRifle> = Case<SniperRifle>()
var rifle = Case<Rifle>()
sniperRifle = rifle
sniperRifle.consume(Rifle())
Finally what is all about with the where
keyword? We use it when we want to extend from several interfaces and not just one. This is also called an uppoer bound. Suppose we have a function to sell weapons and we want to sell just weapons and usable ones (note the new interface I made usable). If the data type we pass is a weapon and is usable, we can proceed to use the function, otherwise we can't. This is only possible with function
fun <T> sellWeapon(weapon: T): String where T : Weapon, T : Usable {
print("$weapon was just sold")
return "success"
}
Java ๊ด๋ จ
Properties ํ์ผ๊ฐ์ฒด ๊ตฌ์ฑ
ํ์ผ:
src/main/resources/props/
globals.properties
package com.example.markiiimark;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class FooBar {
private Properties config = null;
// ...[์๋ต]...
public void loadProperties() {
try(InputStream io = Thread.currentThread().getContextClassLoader().getResourceAsStream("props/globals.properties")) {
this.config = new Properties();
this.config.load(io);
} catch(IOException e) {
// ...[์๋ต]...
}
}
}