1 Week
객체와 클래스 그리고 인스턴스
- 객체
- 물리적, 추상적으로 생각할 수 있는것 중에서 자신의 속성을 가지고 있고 다른것과 식별 가능한 것.
- 소프트웨어 에서 구현할 대상
- 클래스
- java에서 객체를 생성하기 위한 일종의 설계도
- 속성(필드)을 가지고 있고, 행위(메소드)를 가지고있다.
- oop의 관점에서 클래스 타입으로 선언되었을 때 ‘객체’라고 부른다.
- 인스턴스
- 클래스를 가지고 객체를 실체화 한것.
- 메모리에 할당되어 실제로 사용되는 객체
절차형, 객체지향, 함수형 패러다임
절차형 프로그래밍 (Procedural Programming)
위에서부터 아래로의 순차적인 처리를 중요시 하며 루틴, 서브루틴, 메소드, 함수 등을 이용한 프로그래밍 패러다임 * 장점 * 컴퓨터 처리구조와 유사해 실행속도가 빠르다. * 단점 * 코드가 길어지면 가독성이 떨어지며 유지보수가 어렵다. * 정해진 순서대로 입력을 해야하므로 순서가 바뀌면 결과를 보장할 수 없다. * 대형 프로젝트에 적합하지 않다.
객체지향 프로그래밍 (OOP)
모든 것을 객체로 취급하고 객체가 처리요청을 받았을 때, 객체 내부의 기능을 사용해 처리하는 방법이다.
클래스 디자인과 객체들의 관계를 중심으로 설계가 이루어진다.
따라서, 상태, 멤버변수, 메서드 등이 긴밀한 관계를 가지고 있고, 멤버변수가 어떤 상태를 가지고 있는가에 따라 결과가 달라진다.
* 특징
* 캡슐화 - 정보를 은닉하고 노출시키지 않는다.
* 추상화 - 공통적인 속성이나 기능을 묶는다.
* 상속 - 아래 더 자세히..
* 다형성 - 아래 더 자세히..
* 장점
* 재사용에 용이하다. (확장, 유지보수)
* 분석과 설계의 전환이 쉽다.
* 단점
* 처리속도가 상대적으로 느리다.
* 모든 객체의 역할과 기능을 설계해야 하기 때문에 많은 시간이 소요된다.
함수형 프로그래밍
순수함수를 사용해 상태를 제어하지 않고 빠른 처리에 집중하는 방식, 함수 자체가 일급객체가 된다.
OOP와는 데이터(상태)를 다루는 개념과, 간결한 코드 작성에 대한 관점차이가 있다.
일급객체 - 다른 요소들과 아무런 차별이 없는 객체, 함수를 변수로 사용할 수 있다는 의미도 된다.
- 장점
- 함수가 하는일은 명확하기 때문에 코드의 가독성이 좋아지고 테스트가 쉬워진다.
- 값이 한번 변수에 할당되면 이후에 변경되지 않기 때문에 부수효과가 발생하지 않으며 참조투명성을 가진다. 즉, 멀티코어 프로세스에서 교착상태에 빠지지 않는다.
- 동시성 프로그래밍에 용이하다.
- 단점
- 상태의 조작이 불가능하다.
-
그래서 뭐가 좋은데?
자바8에서 함수형 프로그래밍을 도입했다.
객체지향을 추구하는 자바진영에서 왜 함수형 프로그래밍을 도입했을까?
데이터 자체를 핸들링하는 함수형 프로그래밍은 어떤 데이터가 들어가도 로직 수행 후 의도한 데이터를 리턴한다. 하지만 객체지향은 여기에 상태까지 관리해 줘야 하기 때문에 신경 쓸것이 많다.
현대 어플리케이션은 함수형과 객체지향을 모두 사용한다.
예를들면, 내가 매일 작업하는 로직들은 함수형 이지만, 디비에 접근하는 순간만큼은 함수형일 수가 없다.
데이터를 저장해야 하고 값을 리턴받아야 하고 등의 상태값이 존재하기 때문이다.
이외에도 여러가지가 있겠지만 자바8에 추가된 람다의 함수형 프로그래밍도 이와 같다.
람다로 코드를 짤 경우 데이터의 무결성을 보장한다.
왜냐하면 전통적인 for루프의 경우 객체를 작업하지만 람다의 경우 데이터 그 자체를 핸들링 하기 때문이다.
결국 시대의 흐름에 맞게 병렬 프로세싱을 잘 활용하기위해 자바 8에서 도입을 했고
뭐가 더 좋다기 보단 둘다 적재적소에 사용하는게 중요하겠다.
자바의 Type
Primitive Type(원시타입)
- 정수, 실수, 문자, 논리 리터럴등의 실제 데이터값을 저장하는 타입
- 기본값이 있기때문에 Null이 존재하지 않는다. 기본형 타입에 Null을 넣고싶다면 래퍼클래스를 활용한다.
- 제네릭 타입에서 사용할 수 없다.
- 실제값을 저장하는 공간으로 Stack 영역에 저장된다.
- 실제값이 Stack영역에 있으므로 참조타입보다 접근속도가
Reference Type(참조타입)
- 객체의 주소를 저장하는 타입으로 메모리 번지값을 통해 객체를 참조하는 타입
- 기본형 타입을 제외한 모든 타입은 참조형 타입이다. (문자열, 배열, 클래서, 열거, 인터페이스 등)
- 제네릭 타입에서 사용할 수 있다.
- 실제 객체는 Heap 영역에 저장되고 Stack영역에 객체의 주소를 저장해서 참조한다.
- 실제값이 Heap 영역에 있고 Stack메모리에는 참조값만 있으므로 값이 필요할 때마다 최소 2번 메모리 접근을 해야하고 박싱/언박싱 과정을 거쳐야 해서 원시타입보다 접근속도가 느리다.
- 실제 사용하는 메모리양도 원시타입보다 월등히 많다.
- String?
- 참조타입에 속하지만 기본적인 사용은 원시타입이다.
- 불변객체이다. 그래서 String 클래스의 값을 변경하는 메소드를 사용해도 값이 변경되는 것이 아닌 새로운 String 객체를 만든다.
- 기본형 비교는 == 사용이지만 String 간 객체비교는 equals()메소드를 사용한다.
Wrapper Class
원시타입을 객체로 다루기 위해 사용하는 클래스이다. 기본타입의 값을 내부에 두고 포장해서 포장객체 라고도 불리는데 래퍼클래스로 감싸고 있는 값은 외부에서 변경이 불가능하고 변경하려면 새로운 포장객체를 만들어야 한다. 자바의 모든 객체가 그러하듯 Object를 상속받는다.
박싱 / 언박싱
- 원시타입을 포장객체로 만드는게 박싱, 반대로 포장객체에서 원시타입을 얻는게 언박싱이다.
- JDK 1.5 이후로 오토 박싱/언박싱을 지원 한다.
- 래퍼객체 내부값을 비교하려면 equals를 사용해야 한다. == 는 래퍼 객체의 참조 주소를 비교하기 때문이다.
- 래퍼클래스와 원시타입 비교는 둘다 가능하다. JDK 1.5 이후로 오토 박싱/언박싱을 지원하기 때문이다
왜 쓰는데?
- 원시타입을 클래스화 하여 클래스의 장점을 활용할 수 있다. (클래스에 지원되는 여러 메소드들)
- null 지원
- 기본타입을 객체로 써야할 경우가 있음
- ex) List<Integer> 에 원시타입 int 값을 넣는다던가… (오토박싱/언박싱 지원)
- 제네릭에 원시타입은 쓸수가 없음
- 일급콜렉션
- 콜렉션을 래핑할때, 그 외에 다른 멤버변수가 없는 상태를 일급콜렉션 이라한다.
- 이렇게 되면 하나의 인스턴스에서 비즈니스 로직을 관리할 수 있게 되는 장점이 있다.
- ex) 같은 객체를 여러개 생성해야 할때, 이 모든 인스턴스들을 담는 일급콜렉션을 만들다던가..
- 비즈니스 로직을 도메인에서 관리 (비즈니스에 종속적인 자료구조를 만들 수 있음)
동일성과 동등성
동일성
- 두 객체가 완전히 같은 경우.
주소값이 같기 때문에 하나의 객체로 봐도 무방하며 두 변수가 같은 객체를 참조한다. - == 연산자를 사용해 판별할 수 있다.
- 원시타입은 객체가 아닌 스택메모리에 직접 값이 올라가기 때문에 == 결과가 같으면 동일하다.
동등성
- 두 객체가 같은 정보를 가지고 있는 경우.
두 객체의 주소가 달라도 내용이 같으면 동등하다고 말할 수 있다. - equals를 사용해 판별할 수 있다.
- equals
- equals 메소드를 재정의 하지 않으면 == 연산자와 다르지 않다.
- 객체에서 재정의 할 경우 == 연산자를 통해 두객체가 동일하면 true를 반환하고, 다르다면 동등성을 판단한다. 재정의 하지 않을 경우 동일성 여부만 판단하기 때문에 동등성비교가 필요하다면 꼭 재정의 해주어야 한다.
- hashcode?
- equals를 재정의 해줄 때, hashcode도 같이 재정의하는걸 권장한다. 이유가 무엇일까?
- Hash Table을 사용하는 자료형일 경우 문제가 된다.
동등한 객체 2개를 생성해서 Set에 담게되면 의도한 대로 담기지 않는다.
Set 자료구조는 중복을 허용하지 않기 때문인데, Hash Table을 사용하는 자료형은 해싱 알고리즘을 사용한다. 해싱된 결과를 주소값으로 찾아가서 같은 자료가 있는지 확인하는데 동등한 두 객체는 다른 주소값을 가지고 있기 때문에 객체의 동등성을 위해 equals메소드와 hashcode메소드는 같이 오버라이드 되어야 지만 동등성을 보장받을 수 있다.
To Do
자바의 접근제어자
객체지향 패러다임에서 정보은닉이란 필요한 정보만 오픈한다는 개념이다.
자바에서는 정보은닉을 위해 접근제어자 라는 기능을 제공하여 클래스 외부에서 직접적인 접근을 허용하지 않는
변수, 메서드, 생성자를 설정할 수 있다.
단, 인터페이스의 접근제어자는 무조건 public 이다.
인터페이스는 구현클래스가 아니라 추상클래스 이기 때문에
해당 인터페이스를 구현하는 구현클래스에서 구현이 꼭 필요하다. 그렇기 때문에 접근제어자를 생략하더라도 컴파일시 public으로 열어둔다.
또한 클래스의 접근제한은 public 과 default만 적용할 수 있다.
-
private
private 으로 선언한 메서드나 변수는 해당 클래스 내에서만 접근이 가능하다.
외부에 공개되지 않으며, 외부에서는 private멤버에 직접 접근이 불가능하며 해당 객체의 public 메소드를
통해서만 접근이 가능하다.
그래서 보통 private 멤버는 클래스 내부의 세부적인 로직을 수행하는데 사용된다.
또한 생성자를 private 으로 생성하여 인스터스화를 방지할 수 있다. -
public
public으로 선언된 클래스 멤버는 외부에서 접근이 가능하며, 프로그램 어디에서도 접근이 가능하다. 자바는 public메소드를 통해서만 해당 객체의 private멤버에 접근할수 있고, 따라서 public 메소드는 private 멤버와 외부사이의 인터페이스 역할을 수행한다고 할 수 있다. -
default
아무것도 선언하지 않으면 default 접근자가 붙는다. 같은 패키지내에서만 접근이 가능하다. -
protected
default 접근제어자 처럼 같은패키지 내에서 접근이 가능하지만, 좀더 범위가 넓다.
protected멤버는 부모 클래스에게는 public 멤버처럼 취급되며, 외부에서는 private 멤버로 취급된다.
결국 protected로 선언된 멤버는 아래와 같은 접근 영역을 가진다.- protected를 선언한 클래스의 멤버
- protected를 선언한 클래스가 속한 패키지의 멤버
- protected를 선언한 클래스를 상속받은 자식 클래스의 멤버
왜 쓰는데?
- 클래스 내부의 데이터를 보호하기 위해
- 객체지향에서 캡슐화 라고 부른다.
상속
자바의 상속(Inheritance)은 부모클래스와 자식클래스 관계에서 발생하며,
상속을 받은 자식클래스는 부모클래스의 private 멤버를 제외한 모든 멤버를 물려받아 사용할 수 있다.
만약 둘이 다른패키지에 있다면 default접근제한을 갖는 멤버도 자식클래스에서 사용이 불가능하다.
부모클래스가 변경되면 자식클래스는 영향을 받지만 반대의 경우는 영향을 주지 않는다.
또한, 자바는 다중상속을 지원하지 않으며 단일상속만 가능하다.
- Overriding
부모클래스의 정의된 메서드를 자식클래스에서 수정이 필요할 경우 오버라이딩을 이용한다.
오버라이딩은 동일한 시그니처를 가져야 한다.- 동일한 시그니처 : 메서드의 이름, 매개변수, 반환타입이 같아야 하고 중괄호 안의 내용만 바뀔 수 있다.
- 인터페이스 상속
implements 키워드로 상속받을 수 있다. 일반 상속과 다르게 인터페이스는 상속은 다중상속이 가능하다.
인터페이스를 상속받은 클래스는 반드시 인터페이스의 메소드를 재정의(@Override) 해야한다.
인터페이스 상속은 보통 설계 목적으로 구현한다.
-
왜 쓰는데?
외부로부터 다형성을 보장하면서 내부구현코드를 모두 구현하지않고 공통로직을 그대로 사용할 수 있다.
이미 만들어진 클래스를 재사용할 수 있기 때문에 효율적이며, 중복코드가 많이 줄어들게 된다.
또한 부모클래스 멤버를 사용할 경우, 수정이 필요할 때 부모클래스만 수정하면 되기때문에 유지보수에 좋다.public class Person { String name; String age; String gender; public Person(String name, int age, String gender) { this.name = name; this.age = age; this.gender = gender; } } public class Student extends Person { String studentId; }학생과 사람의 클래스이다. 학생도 사람이기 때문에 Person클래스가 가지는 속성을 모두 가지고 있다.
공통적인 기능을 부모클래스에 정의해두면 여러개의 자식클래스에서 사용이 가능하기때문에 확장성 또한 용이하다.
학생 클래스에서 이름 나이 성별을 정의하지 않았지만 super 키워드로 부모클래스로부터 물려받은 속성에 접근할 수 있다.
이처럼, 상속을 이용할 경우 유지보수가 좋아지고 확장성이 좋아지며 중복코드를 많이 줄일 수 있게 된다.
- 단점
- 자식클래스에서도 부모클래스의 public메서드가 외부로 노출되기 때문에 이는 캡슐화를 위반할 수 있다.
- 클래스 간의 결합도가 높아진다.
- 부모클래스의 버그가 생길경우, 자식클래스에 어떠한 사이드이펙트가 갈지 예측하기 어렵다.
- 상속 구조가 복잡해 질수록 상위 클래스에서 의미있던 기능이 하위클래스에서 필요없는 기능일 수 있다.
- 상속으로 인해 결합도가 높아질 수록 하나의 기능을 추가하거나 수정하기위해 불필요하게 많은 클래스를 추가하거나 수정해야 한다.
- 그로인해 단일상속만 지원하는 자바에서는 오히려 중복코드의 양이 늘어날 수 있다.
- 어떻게 쓰면 좋을까?
SOLID원칙의 리스코프치환 원칙에서 알 수 있다.
상속받은 자식클래스는 부모클래스를 대체할 수 있을 경우에만 상속을 해야한다.
부모클래스의 외부로 노출되는 메서드는 자식클래스 에도 같은 의미로 제공되어야 한다고 말하고 있다.
즉, 상속은 클래스의 행동을 확장하는 것이 아닌 정제할 때 사용하는것이 좋다.
또한 재정의 되지 않길 원하는 메서드일 경우 final 메서드로 만들어 두는것도 좋은 방법이겠다.
합성(Compotistion)
구현에 의존하지 않고, public interface에만 의존한다.객체 관계가 수평적인 관계가 된다.
- 두 객체 사이 의존성을 런타임에 해결한다. (상속은 컴파일단계에서 해결)
- 코드의 재사용만 생각하면 상속보다는 합성을 사용하는게 좋을 수 있다.
- 상속의 is-a 관계가 아닌 has-a관계
- is-a
- A는 B이다. -> 학생은 사람이다.
- 추상화 사이의 포함관계를 의미한다.
- 한 클래스 A가 다른 클래스 B의 파생클래스임을 나타낸다.
- is-a 관계를 통해 생성된 클래스는 상속관계에서 밀접하게 결합된다.
- 결합도가 높기 때문에 계층구조에 좀 더 적합하다.
- has-a
- 구성 포함 관계를 의미한다. -> 경찰은 총을 가진다 (경찰클래스 안에 권총클래스의 객체를 멤버로 가지고 있는 경우)
- 한 오브젝트가 다른 오브젝트에 속한다 라고 말한다.
- 다른 클래스의 기능(변수 혹은 메서드)를 받아들여 사용한다.
- 객체의 멤버필드라고 불리는 객체를 말하며 계층구조를 형성하기위해 결합하는 경우를 말한다.
- has-a 관계를 통해 생성된 클래스는 느슨하게 결합된다.
- 상속에 비해 변경이 발생하더라도 구성요소를 쉽게 변경할 수 있다.
- is-a
전략패턴(Strategy Pattern)
합성을 이용한 디자인 패턴으로 프로그램 실행중에 알고리즘을 선택할 수 있게하는 패턴이다.
즉, 특정 Context에서 알고리즘을 별도로 분리하는 설계 방법이다.
변하지 않는 부분을 Context에 두고 변하는 부분을 Strategy 인터페이스 구현체에 작성한다.
전략에 해당하는 Strategy인터페이스와 구현체에는 비즈니스 로직 이외에 아무런 로직이 없기때문에 공통 로직이 변경되어도 아무런 영향이 없다.
- 장점
- 런타임에 객체 내부의 알고리즘을 바꿀 수 있다.
- 공통로직이 부모클래스에 있지 않고 Context라는 별도의 클래스에 존재하기 떄문에 구현체들에 대한 영향도가 적다.
- Context 가 Strategy 라는 인터페이스를 의존하고 있기 때문에 구현체를 갈아끼우기 쉽다.
- 결국 확장에 유리하다.
- 단일책임원칙을 지키면서 새로운 전략을 추가할 수 있다.
- 단점
- 클래스로 분리된 전략에 대해 모두 알아야 한다.
- 알고리즘 변경이 많지 않다면, 오히려 분기를 타는것보다 코드를 복잡하게 만들 수 있다.
- 로직이 늘어날때마다 구현클래스가 늘어난다.
- 어떻게 쓰는데?
과일매장은 상황에 따라 다른 가격할인정책을 적용하고 있다.- 첫손님에게 10% 할인
- 마지막손님은 20% 할인
- 신선도가 떨어진 과일은 20%할인
할인이라는 알고리즘을
DiscountPolicy라는 인터페이스로 분리해보자.public interface DiscountPolicy { double calculateWithDisCountRate(Item item); } public class FirstCustomerDiscount implements DiscountPolicy { @Override public double calculateWithDisCountRate(Item item) { return item.getPrice() * 0.9; } } public class LastCustomerDiscount implements DiscountPolicy{ @Override public double calculateWithDisCountRate(Item item) { return item.getPrice() * 0.8; } } public class UnFreshFruitDiscount implements DiscountPolicy{ @Override public double calculateWithDisCountRate(Item item) { return item.getPrice() * 0.8; } }그리고 이를
Calculator클래스에서 생성자를 통해 필요한 하위타입을 주입받아 사용해보자.
이렇게 되는 경우 외부에서 특정 경우(첫번째 손님, 마지막 손님, 싱싱하지 않은 과일)에 대한 할인정책을 생성자를 통해 전달해줄 수 있다.public class Calculator { private final DiscountPolicy discountPolicy; public Calculator(DiscountPolicy discountPolicy) { this.discountPolicy = discountPolicy; } public double calculate(List<Item> items) { double sum = 0; for (Item item : items) { sum += discountPolicy.calculateWithDisCountRate(item); } return sum; } }아래의 경우는 첫번째 손님 할인정책을 적용하는 코드이다.
일반적으로 Controller는 사용자의 요청 등을 매핑하여 받아오기 때문에 특정 알고리즘(첫번째 손님 계산)을 선택되었다는걸 알 수 있다.
요청에 맞는 객체를 Calculator에 주입해주는 방식을 통해 전략패턴을 구현했다.public class FruitController { public static void main(String[] args) { Calculator calculator = new Calculator(new FirstCustomerDiscount()); calculator.calculate(Arrays.asList( new Item("Apple", 3000), new Item("Banana", 3000), new Item("Orange", 2000), new Item("Pitch", 4000) )); } }
상속 vs 합성?
상속은 is-a, 합성은 has-a
결국, 코드의 재사용성 만을 위해 상속을 사용하는 방식은 여러 문제점을 가지고 있기 때문에 재사용성 뿐만 아니라 클래스간의 확실한 계층구조를 만들때 상속을 사용하며 그 외에는 의존성이 낮은 합성을 사용하는 것이 좋다고 생각이 든다.
다형성
변화에 유연한 소프트웨어를 만들기 위한 객체지향 패러다임의 가장 중추적인 방법이다.
다형성이란 하나의 타입에 여러 객체를 대입할 수 있는 성질이고, 이것을 위해서 여러 객체들중 공통 특성으로
타입을 추상화하고 그것을 상속 또는 인터페이스라면 구현해야한다.
- 업캐스팅
- 서로 다른 클래스의 인스턴스를 동일한 타입에 할당할 수 있게 해준다.
- 부모클래스의 인스턴스 대신 자식클래스의 인스턴스를 사용해도 메시지를 처리하는데 문제가 없으며, 컴파일러는 명시적인 타입변환 없이도 자식클래스가 부모클래스를 대체할 수 있게 허용한다.
- 동적 바인딩
- 메시지를 수신했을 때 실행될 메서드가 런타임에 결정된다.
- 이와같이 런타임에 실행될 메서드를 결정하는 방식을 동적바인딩 혹은 지연바인딩 이라고 한다.
-
오버로딩(Overloading)
자바PrintStream클래스에는 매개변수만 다른println이라는 메서드가 여러개 정의되어 있다.public class PrintStream { ... public void println() { newLine(); } public void println(boolean x) { synchronized (this) { print(x); newLine(); } } public void println(char x) { synchronized (this) { print(x); newLine(); } } ... }- 매개변수로 어떤 타입을 넣어주든, 모두
println이라는 메서드 시그니처를 호출하여 원하는 내용을 출력하는 기능을 수행한다. - 오버로딩은 여러종류의 타입을 받아 같은 기능을 하도록 만들기 위함이고 런타임 시점에 특정 메서드를 바인딩 할수 있다.
- 즉, 다형성 이라고 할 수 있다.
- 매개변수로 어떤 타입을 넣어주든, 모두
- 오버라이딩(Overriding)
앞서 상속에서도 정리했던 오버라이딩은 부모클래스의 메서드를 하위클래스에서 재정의 하는것을 의미한다.
아래Figure라는 추상클래스에서는 하위 클래스에서 오버라이딩 해야할 메서드를 정의해 두었다.public abstract class Figure { protected int dot; protected int area; public Figure(final int dot, final int area) { this.dot = dot; this.area = area; } public abstract void display(); }Figure를 상속받는 하위클래스 들이다.public class Triangle extends Figure { public Triangle(final int dot, final int area) { super(dot, area); } @Override public void display() { System.out.printf("넓이가 %d인 삼각형입니다.", area); } } public class Square extends Figure { public Square(final int dot, final int area) { super(dot, area); } @Override public void display() { System.out.printf("넓이가 %d인 사각형입니다.", area); } }- 이런식으로 새로운 도형 객체가 추가될 때
Figure를 상속받고 메서드를 재정의 해주면 실제로 사용되는 비즈니스로직의 변경을 최소화 할 수 있다. - 다형성을 사용하지 않고 도형 객체를 추가하려고 하면 if-else 분기가 늘어나게 되버린다.
- 이런식으로 새로운 도형 객체가 추가될 때
- 함수형 인터페이스(Funtional Interface)
람다식을 사용하기 위한 API로 자바에서 제공하는 인터페이스에 구현할 메서드가 하나 뿐인 인터페이스 이다.
enum과 함께 사용할 시 다형성의 장점을 경험할 수 있다.
문자열 계산기 이다.
각각의 연산자를enum으로 정의하고 연산 방식을BiFuntion을 사용한 람다식으로 정의한다면 연산자를 추가 해야 할 때,enum에만 추가해주면 실제로 연산을 수행하는calculate메서드는 아무 수정없이 기능확장을 할 수 있다.public enum Operator { PLUS("+", (a, b) -> a + b), MINUS("-", (a, b) -> a - b), MULTIPLY("*", (a, b) -> a * b), DIVIDE("/", (a, b) -> a / b); private final String sign; private final BiFunction<Long, Long, Long> bi; Operator(String sign, BiFunction<Long, Long, Long> bi) { this.sign = sign; this.bi = bi; } public static long calculate(long a, long b, String sign) { Operator operator = Arrays.stream(values()) .filter(v -> v.sign.equals(sign)) .findFirst() .orElseThrow(IllegalArgumentException::new); return operator.bi.apply(a, b); } }
댓글남기기