아래 3가지 조건을 충족하면 일급 객체라고 할 수 있다
- 변수에 담을 수 있어야 한다
- 매개변수로 함수를 전달할 수 있어야 한다
- 객체의 리턴값으로 사용할 수 있어야 한다
🔸 자바 ➡️ 불가능
public class Test {
public static void test1() {
System.out.println("test1");
public static void main(String[] args) {
System.out.println("main");
//Object function = test1(); -> 불가능
}
}
🔹 파이썬 ➡️ 가능
def add(input1, input2):
return input1 + input2
copy_add = add
print(copy_add)
print(add)
print(copy_add(1,2))
copy_add와 add가 같은 주소값을 가르킴
🔸 자바 ➡️ 불가능
public class Test {
public static void test1() {
System.out.println("test1");
}
public static void print(Object function) {
function(); //불가능
}
public static void main(String[] args) {
print((Object) test1); //불가능
}
}
🔹 파이썬 ➡️ 가능
def add(a, b):
return a + b
def main(func, a, b):
print(func(a, b):
main(add, 10, 12) #22출력
🔸 자바 ➡️ 불가능
🔹 파이썬 ➡️ 가능
def test(name):
def test_inner():
print(f"{name}!")
return test_inner
func = test("Velog")
func() #Velog! 출력
✔️ 자바 8부터 람다를 지원하면서 함수를 일급 객체처럼 다울 수 있는 방법을 제공
➰ 변수 할당
import java.util.function.Function;
public class Test {
public static void test1() {
System.out.println("test1");
}
public static void main(String[] args) {
System.out.println("main");
//Object function = test1();
//Function 인터페이스를 사용하여 람다 표현식을 변수에 할당
Function<Integer, Integer> square = x -> x*x;
int result = square.apply(5);
}
}
➰ 매개변수로 함수 전달
import java.util.function.Consumer;
public class Main {
public static void print(Consumer<String> c, String str) {
c.accept(str);
}
public static void main(String[] args) {
print((t) -> System.out.println(t) ,"Hello World");
}
}
➰ 함수 리턴
import java.util.function.Function;
public class Test {
public static Function<String, String> hello() {
return (t) -> {
System.out.println(t );
return t;
};
}
public static void main(String[] args) {
Function<String, String> func = hello();
func.apply("Hello World");
}
}
//Hello World 출력
Collection(List, Map..)을 Wrapping하면서 그 외 다른 멤버 변수가 없는 상태
## 변경 전
public class Market {
private String name;
private List<Item> items;
}
public class Item {
private String name;
private int price;
}
➰ 아래 상태로 변경하게 되면 Items 클래스가 일급컬렉션이 된다
## 변경 후
public class Market {
private String name;
private Items items;
}
public class Items {
private List<Item> items;
}
public class Item {
private String name;
private int price;
}
import java.util.List;
public class Market2 {
private String name;
private List<Coffee> coffees;
public Market2(String name, List<Coffee> coffees) {
validate(coffees);
this.name = name;
this.coffees = coffees;
}
private void validate(List<Coffee> coffees) {
if (coffees.size() >= 5) {
throw new IllegalArgumentException("5개 이상의 커피메뉴를 등록할 수 없습니다.");
}
}
}
해당 코드에서 Market메뉴가 coffee하나 인 경우 복잡해 보이지 않지만 만약 메뉴가 여러개 추가되는 경우 해당 validation 메서드가 market 클래스에 추가되기 때문에 market 클래스이 역할이 무거워지고, 중복코드가 많아진다
import java.util.List;
public class Market {
private String name;
private Coffees coffees;
}
public class Coffees {
private List<Coffee> coffees;
public Coffees(List<Coffee> coffees) {
this.coffees = coffees;
}
private void validate(List<Coffee> coffees) {
if (coffees.size() >= 5) {
throw new IllegalArgumentException("5개 이상의 커피메뉴를 등록할 수 없습니다.");
}
}
}
public class Coffee {
private String name;
private int price;
}
커피를 일급 컬렉션으로 만들어 검증은 커피 클래스 내부에서 수행할 수 있게된다
✔️ 컬렉션의 값을 변경할 수 있는 메소드가 없는 컬렉션을 만들어서 불변성을 보장할 수 있다
➕ 자세히! 생성자와 필요한 값만 반환하는 메서드만 존재하는 경우!!
만약, 생성자로 받은 컬렉션 값을 그대로 반환하는 getter가 존재하게 되면
List<Item> items = new ArrayList<>();
items.add(new Item("커피", 1000));
Items item = new Items(items);
item.getItem.add(new Item("쿠키", 2000));
이런 상황에서 값이 변경될 수 있다
만약 컬렉션 값을 그대로 반환하는 getter가 필요한 경우
1. 생성할 때 주소값을 재할당 한다
2. getter내에 unmodifiableList 사용한다
원시 타입의 값(int, double, char 등)을 객체로 감싸는 것으로 원시 타입에 이름을 부여하고 해당 값에 대한 유효성 검사, 비즈니스 로직의 추가 등을 가능하게 하여 코드의 가독성과 유지보수성을 높인다
#변경 전
public class Market {
private String menu;
private int price;
public Market(String menu, int price) {
validateMenu(menu);
validatePrice(price);
this.menu = menu;
this.price = price;
}
private void validateMenu(String menu) {
if (menu.length() < 1) {
throw new IllegalArgumentException("이름은 한 글자 이상이어야 합니다.");
}
}
private void validatePrice(int price) {
if (price < 100) {
throw new IllegalArgumentException("가격은 1원 이상이여야 합니다.");
}
}
}
해당 소스를 보면 마켓 클래스에서 메뉴, 가격에 대한 상태관리도 책임지고 있다
두 컬럼이여서 복잡해보이지 않지만 만약 컬럼이 5개만 넘어가도 마켓 클래스가 거대해질 수 있다
#변경 후
public class Market {
private Menu menu;
private Price price;
public Market(String menu, int price) {
this.menu = new Menu(menu);
this.price = new Price(price);
}
}
public class Menu {
private String menu;
public Menu(String menu) {
if (menu.length() < 1) {
throw new IllegalArgumentException("이름은 한 글자 이상이어야 합니다.");
}
this.menu = menu;
}
}
public class Price {
private int price;
public Price(int price) {
if (price < 100) {
throw new IllegalArgumentException("가격은 1원 이상이여야 합니다.");
}
this.price = price;
}
}
메뉴와 가격에 대한 유효성 검사, 상태값을 스스로 관리할 수 있게되어 책임이 명확해지게 된다
유지보수성 측면에서
카페, 빵집, 음식점 클래스를 만든다고 가정했을 때
각각 클래스는 모두 '가격'이라는 데이터가 필요하게 된다
각 클래스에서 원시값 price를 사용하는게 아닌
원시값을 포장하여 Price 객체를 사용하게 되면 추후 가격에 대한 추가적인 로직이 필요한 경우 Price 클래스에 메소드를 추가함으로써 새로운 기능을 쉽게 확장할 수 있다