목차
역할, 책임, 협력
객체 지향의 사실과 오해에 대해 알아보려면 우선 객체 지향이 뭔지에 대해서 알아야 한다. 우리는 지금 자바 스터디를 하고있다. 자바가 객체 지향 언어라는 것은 알고있지만 객체 지향에대해 따로 생각해본적은 없었다. 그렇다면 객체 지향이란?
그래서 객체 지향은?
자바를 생각하면서 객체 지향을 떠올리면 가장 먼저 떠오르는것은 클래스 이다. 비록 다른 사람의 글을 보면서 글을 적고 있지만 이 사람 처럼 수업 시간에 배웠던것 처럼 자바 => 클래스 가 공감이 된다. 우리 학교도 수업시간에 자바를 하면 클래스만 주구장창 배웠기 떄문이다.
코딩의 관점에서 본다면 클래스가 객체를 만들기위한 틀, 도구이기에 객체지향을 클래스 라고 착각할수가 있다. 객체지향의 주인공은 클래스가 아니라 객체 이다. 이 객체를 분류 하기 위한 것이 클래스 이다. 따라서 코드를 담는 클래스의 관점이 아닌 메시지를 주고 받는 객체의 관점으로 애플리케이션을 바라 보아야 한다.
1. 협력
2. 역할
3. 책임
하는 것 (doing)
아는 것 (knowing)
(그냥 내 생각인데 이는 자바에서 코드를 짤때 메서드를 호출해 직접 상태를 변형 시키거나 다른 객체의 상태를 변형 시키는 것, 이때 변환 시킬 객체를 아는 것 등을 의미하는것 같다)
상태와 행위의 캡슐화
사적인 캡슐화
Immutable Object란? 객체 지향 프로그래밍에서 불변 객체는 생성후 그 상태를 바꿀수 없는 객체를 의미한다. 당연히 반대인 가변 객체도 있다. 가변 객체로 생성후 불변객체로의 변환도 가능하다. 객체의 전체가 불변인 경우도 있고, C++에서 Const 데이터 멤버 처럼 일부 속성만 불변인 경우도 있다. 또 경우에 따라서는 내부 속성이 변화 해도 외부에서 그 객체의 상태가 변하기 않는다고 보이면 불변 객체로 보기도 한다. 불변 객체를 사용시 복제나 비교를 위한 조작을 단순화 할수 있으며, 성능 개선에 도움을 준다. 하지만 객체가 변경이 가능한 데이터를 많이 가지고 있다면 오히려 부적적할 경우가 있다.
String str = "abc";
str = "cba";
위 코드 처럼 재할당이 가능하다. = str 이 처음에 "abc" 를 참조했는데 이 값이 "cba" 로 바뀌는 것이아니라 "cba"라는 새로운 객체가 생기고 그 객체를 str 이 재참조 하는것이다. 이때 "abc" 는 아무도 참조를 하고있지 않게 된다.
그렇다면 계속 str에 재할당을 한다면 객체가 생성된채로 낭비가 된다는 것인데 이는 어떻게 처리하는게 옳은 방법일까?
class ImmutableClass {
private final int age;
private final String name;
public ImmutableClass(int age, String name) {
this.age = age;
this.name = name;
}
}
위의 코드는 외부에서 수정이 불가능한 불변 객체가 된다.
불변 객체가 된 가장 큰이유는 멤버 변수를 private final로 선언하고 setter를 구현하지 않았기 때문이다.
class MutableClass {
public int age;
public String name;
public MutableClass(int age, String name) {
this.age = age;
this.name = name;
}
}
위 코드는 외부에서 수정이 가능하므로 불변 객체가 X
원시타입만 있는 경우
public class BaseObject {
private int value;
public BaseObject(final int value) {
this.value = value;
}
public void setValue(int new Value) {
this.value = newValue;
}
}
public class BaseObject {
private final int value;
public BaseObject(final int value) {
this.value = value;
}
}
참조 타입이 있는 경우
public class Animal {
private final Age age;
public Animal(final Age age) {
this.age = age;
}
public Age getAge() {
return age;
}
}
class Age {
private int value;
public Age(final int value) {
this.value = value;
}
public void setValue(final int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public class ArrayObject {
private final int[] array;
public ArrayObject(final int[] array) {
this.array = Arrays.copyOf(array,array.length);
}
public int[] getArray() {
return (array == null) ? null : array.clone();
}
}
collection.unmodifiableList
를 사용 import java.util.Collections;
import java.util.List;
public class ListObject {
private final List<Animal> animals;
public ListObject(final List<Animal> animals) {
this.animals = new ArrayList<>(animals);
} // this.animals에 새롭게 리스트를 만들어 대입
public List<Animal> getAnimals() {
return Collections.unmodifiableList(animals); //getter방지
}
}
자바가 제공하는 다양한 연산자를 학습하자.
주의 : 우선순위가 같은 연산자들은 연산의 진행 방향에 따라 연산순서가 정해짐
구분 | 연산자 | 의미 |
---|---|---|
+ | 더하기 | |
- | 빼기 | |
산술 연산자 | * | 곱하기 |
/ | 나누기 | |
% | 나머지 값 구하기 |
🏳️ int 보다 크기가 작은 타입은 int로 변환
🏳️ 피연산자 중 표현범위가 큰 타입으로 형변환
사칙연산자 (+, -, /, *)
int a = 1000000;
int b = 1000000;
long c = a * b;
System.out.println(c);
// -727379968
char c = 'a' + 1;
System.out.println(c);
// b
char c = 'a'
char c1 = c + 1;
System.out.println(c);
// 오류
나머지 연산자 (%)
int a = 5;
int b = 3;
int c = a % b;
System.out.println(c);
// 2
쉬프트 연산자 ( <<. >>, >>>)
<<
연산자 : 피연산자의 부호에 상관없이 자리를 왼쪽으로 이동시키면서 빈칸을 0 으로 채움>>
연산자 : 오른쪽으로 이동 시키기 때문에 음수인 경우 부호를 유지 시켜주기 위해서 음수인 경우 빈자리 1로 채움>>>
연산자 : 부호에 상관없이 항상 0 으로 빈자리를 채움x | y | x|y | x&y | x^y |
---|---|---|---|---|
1 | 1 | 1 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
0 | 1 | 1 | 0 | 1 |
0 | 0 | 0 | 0 | 0 |
int a = 3;
int b = 4;
if ( a < b ) {
System.out.println(true);
} else {
System.out.println(false);
}
// true
``
x | y | x||y | x&&y |
---|---|---|---|
true | true | true | true |
true | false | true | false |
false | true | false | true |
false | false | false | false |
char x = 'j';
if ( (x >= 'a' && x<= 'z') {
System.out.println("유효");
} else {
System.out.println("유효X");
}
// 유효
instanceof( 참조 연산자 )
참조변수가 참조하고 있는 인스턴스의 타입을 알아보기 위해(간단하게 객체의 타입을 알아보기 위한) 주로 사용하며 조건문과 함께 사용된다. (주로 부모 객체인지 자식 객체인지 확인할때 쓴다)
instanceof 의 왼쪽 피연산자로는 참조변수, 오른쪽 피연산자에는 타입(클래스명)이 피연산자로 위치한다. 이때 결과 값은 boolean값으로 반환된다. (true = 참조변수(왼쪽)가 검사한 타입(오른쪽)으로 형변환이 가능하다는 뜻)
Ex)
class A{}
class B extends A{}
class instanceofEx01 {
public static void main(String[] args) {
A a = new A();
B b = new B();
System.out.println("a instanceof A : " + (a instanceof A));
System.out.println("b instanceof A : " + (b instanceof A));
System.out.println("a instanceof B : " + (a instanceof B));
System.out.println("b instanceof B : " + (b instanceof B));
}
}
// a : 부모
// b : 자식
// true
// true
// false
// true
대입 연산자 | 설명 |
---|---|
= | 왼쪽의 피연산자에 오른쪽 피연산자 대입 |
+= | 왼쪽의 피연산자에 오른쪽의 피연산자를 더한후, 그 결과 값을 왼쪽의 피연산자를 대입 |
-= | 왼쪽의 피연산자에 오른쪽의 피연산자를 뺀후, 그 결과 값을 왼쪽의 피연산자를 대입 |
*= | 왼쪽의 피연산자에 오른쪽의 피연산자를 곱한후, 그 결과 값을 왼쪽의 피연산자를 대입 |
/= | 왼쪽의 피연산자에 오른쪽의 피연산자를 나눈후, 그 결과 값을 왼쪽의 피연산자를 대입 |
%= | 왼쪽의 피연산자에 오른쪽의 피연산자를 나눈후, 그 나머지 값을 왼쪽의 피연산자를 대입 |
&= | 왼쪽의 피연산자에 오른쪽의 피연산자와 비트 & 연산후, 그 결과 값을 왼쪽의 피연산자를 대입 |
|= | 왼쪽의 피연산자에 오른쪽의 피연산자와 |연산후, 그 결과 값을 왼쪽의 피연산자를 대입 |
^= | 왼쪽의 피연산자에 오른쪽의 피연산자와 ^연산후, 그 결과 값을 왼쪽의 피연산자를 대입 |
<<= | 왼쪽의 피연산자에 오른쪽의 피연산자만큼 왼쪽 시프트 후, 그 결과 값을 왼쪽의 피연산자를 대입 |
>>= | 왼쪽의 피연산자에 오른쪽의 피연산자만큼 부호 유지 오른쪽 시프트후, 그 결과 값을 왼쪽의 피연산자를 대입 |
>>>= | 왼쪽의 피연산자에 오른쪽의 피연산자만큼 부호에 상관없이 오른쪽 시프트 후, 그 결과 값을 왼쪽의 피연산자를 대입 |
람다함수란? 프로그래밍 언어에서 사용되는 개념으로 익명함수를 지칭한다. 람다 대수는 이름이 필요가 없다.
익명 클래스란?
익명 클래스는 클래스의 instantiation과 동시에 클래스를 정의하는 클래스를 의미하며 특정 클래스가 여러번 호출 되지 않거나, 클래스 내부에 필드나 여러개의 메소드를 정의할 필요가 있을때 익명 클래스로 정의해 사용한다.
익명 클래스의 특징으로는 new 를 사용하고 instantiation시 파라미터가 없다.
화살표 연산자는, 익명함수라고 불리는 람다식의 등장으로 사용되는 연산자다.(자바 8 버전부터 사용한다 한다)
기본 형태
(매개변수목록) -> { 함수몸체 }
ex)
// Thread - traditional
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World.");
}
}).start();
를 람다식으로 변형 하면
// Thread - Lambda Expression
new Thread(()->{
System.out.println("Hello World.");
}).start();
처럼 변형시킬수 있다. 이 처럼 람다식을 사용하게 되면 코드의 간결성이 높아지고 가독성이 높아지게 된다.
if () {
} else if {
}
(boolean) ? c(true일경우) : d(false일경우)
int a = 1;
int b= (a == 1) ? 1 : 0;
// a가 1일 경우 1반환 false 일 경우 0 을 b 에 대입;
== int a = 1;
int b = 0;
if ( a == 1) {
b = 1;
} else {
b = 0;
}
switch (day) {
case Monday:
break;
case Friday:
case Sunday:
}
위 코드의 문제점은 break 문으로 인해 오류가 나는 경우가 많다는 점이다. 실제 학교 과제를 할때에 break 문을 잘못 걸어두어 오류가 발생하는 경우도 많았고 디버깅을 하는 과정에 코드 가독성이 떨어서 쉽게 찾지 못해 수정하는데 시간이 많이 소요되는 경우가 많았다.switch (day) {
case Monday -> numDay = 1:
case Friday -> numDay =2:
case Sunday -> numDay =3:
}
case 의 label 이 매칭이 되면 -> 이후 코드를 실행하고 switch문을 탈출하게 된다.switch (day) {
case Monday:
break 1;
case Friday -> numDay =2:
case Sunday -> numDay =3:
}
자바 13switch (day) {
case Monday:
yield 1;
case Friday -> numDay =2:
case Sunday -> numDay =3:
}