제육's 휘발성 코딩
반응형

객체 지향

OOP is A PIE

  • Abstraction (추상화) : 현실 객체를 추상화해서 클래스를 구성
  • Polymorphism(다형성) : 하나의 객체를 여러 가지 타입으로 참조 (메서드 다형성, 타입 다형성)
  • Inheritance(상속) : 부모 클래스를 물려받아 자식이 재정의
  • Encapsulation(캡슐화) : 데이터를 외부에 직접 노출시키지 않고 메서드를 이용해 보호

Type

Primitive Type (기본형)

image

  • 미리 정해진 크기의 Memory Size로 표현
  • 변수 자체에 값 저장
  • long 보다 더 큰 값을 표현하고 싶을 땐 무한대에 가까운 BigInteger를 사용한다.

Reference Type(참조형)

  • 크기가 미리 정해질 수 없는 데이터의 표현
  • 변수에는 실제 값을 참조할 수 있는 주소만 저장
  • String x = "abc"; 라는 문장이 있다면 x 는 스택영역에 참조변수로 저장되고 힙영역에 abc를 저장해둔 주솟값을 가리킨다.

형변환

  • 형변환의 크기 비교는 값의 크기, 타입의 크기가 아닌 타입의 표현 범위가 커지는 방향으로 비교한다.
  • byte -> short, char -> int -> long -> float -> double (boolean은 형변환 X)
  • 범위가 큰 곳에서 작은 곳으로 갈때는 값 손실이 발생할 수 있으므로 묵시적이 아닌 명시적 형변환을 해줘야 한다.
public class Sample01 {
    public static void main(String[] args) {
        int a = Integer.MAX_VALUE;
        int b = 1;

        // 범위 초과
        long c = a + b;
        System.out.println(c); 

        // 범위 초과 : 형변환 전에 (a + b) 연산 먼저 처리 되기 때문
        long c2 = (long) (a + b);
        System.out.println(c2);

        // 정상 출력
        long c3 = (long) a + b;
        System.out.println(c3); 
    }
}

연산자

비트 연산자

  • & : 두 피연산자의 비트 값이 모두 1인 경우에 1, 나머지 0
  • | : 두 피연산자의 비트 값이 모두 0인 경우만 0, 나머지 1
  • ^ : 두 피연산자의 비트 값이 서로 다르면 1, 같으면 0
  • ~ : 피 연산자의 모든 비트를 반전 -> 1의 보수

비트 이동연산자(쉬프트)

  • a << b : a를 b만큼 왼쪽으로 이동하고 빈공간은 0으로 채움
  • a >> b : a를 b만큼 오른쪽으로 이동하고 빈공간은 음수는 1, 양수는 0으로 채움
  • a >>> b : a를 b만큼 오른쪽으로 이동하고 빈공간은 0으로 채움
  • *2 , /2 보다 속도가 빠르다.

논리 연산자

  • && vs & : &는 앞의 피연산자가 false 여도 뒤에를 검사하지만, &&는 앞의 피연산자가 false면 뒤에를 검사하지 않는다.
  • || vs | : 위와 동일하게 ||는 앞의 피연산자가 true 면 뒤의 피연산자를 검사하지 않는다.
public class Sample01 {
    public static void main(String[] args) {
        int a = 5;
        int b = 0;
        System.out.println(a != 0 || ++b != 0);
        print(a,b); //5, 0
        System.out.println(a == 0 && b++ != 0);
        print(a,b); // 5, 0

        System.out.println(a == 0 | (b++) != 0);
        print(a,b); // 5, 1
        System.out.println(++a != 5 & (b++) != 0);
        print(a,b); // 6, 2

    }

    private static void print(int a, int b) {
        System.out.println(a +" " + b);
    }
}

객체지향프로그래밍

  • Object Oriented Programming (OOP)
  • 객체지향 : 우리 주변에 있는 모든 것을 프로그래밍의 대상(객체)으로 삼는 것
  • 모듈화된 프로그래밍으로 높은 신뢰성, 유지보수, 재사용에 용이하다. (절차지향과의 차별점)
  • 현실의 객체가 갖는 속성과 기능은 추상화되어 클래스에 정의, 클래스는 구체화 되어 객체가 된다.

클래스

  • 객체를 정의해 놓는 틀
  • 클래스는 직접 사용하는 것이 아닌 사용되는 객체를 만들기 위한 장소를 제공

객체

  • 실세계에 존재하는 유/무형의 모든 것을 클래스를 통해 데이터 타입으로 메모리에 생성된 것
  • 속성 : 상태 값 (data, property)이 변수가 된다. 변수에는 객체 변수인 non-static 변수, 클래스 변수인 static 변수가 존재
  • 행위 : 기능, 동작(function, behavior)이 메서드가 된다.

생성자

  • 객체 초기화 목적으로 작성하며, 객체 생성 시 호출되는 특별한 메서드
  • 생성자명은 class 명 , return문, 상속 X, 다중 정의 가능 (오버 로딩)
  • 만약 생성자에 return 문이 들어간다면, 생성자가 아닌 메서드가 되어버린다.

this

  • 생성되고 있는 객체 자기 자신이나 호출되고 있는 객체 자기 자신을 표현 (Runtime에 결정됨 - 동적 바인딩) 생성자, instance 메서드, instance initialize 안에서만 사용 가능 (static 사용 불가능)
  • 멤버변수와 지역변수 구분, 자신의 생성자에서 또다른 생성자를 호출 (재사용), 메서드 호출 매개변수나 리턴 값으로 자기 자신 객체를 전달(StringBuilder 의 append 함수는 return this;로 자기자신을 반환)하기 위해 사용한다.
public class Dummy {
    private int age;

    public void setAge(int age) {
        this.age = age; // this.age 는 멤버필드의 age를 가리킴
    }
}
  • 멤버변수와 지역변수 구분하는 용도의 this
Person (String a, int b) {
  this.a = a;
  this.b = b;
}

Person (String a, int b, String c) {
  this(a,b); // 자신의 생성자 안에서 또 다른 생성자를 this로 호출하여 재사용 
  this.c = c;
}
  • 자신의 생성자에서 또다른 생성자를 호출하는 this
@Override
@HotSpotIntrinsicCandidate
public StringBuilder append(String str) {
  super.append(str);
  return this;
}
  • 리턴 값으로 자기 자신 객체를 전달하는 용도의 this

super

class Parent {
    int a = 10;
}

class Child extends Parent {
    int a = 20;

    void chk() {
        System.out.println(a);
        System.out.println(this.a);
        System.out.println(super.a);
    }
}

public class Sample01 {
    public static void main(String[] args) {
        Child child = new Child();
        child.chk();
    }
}
---- 실행 결과 ----
20
20
10
  • super 키워드는 부모 클래스로부터 상속받은 필드나 메서드를 자식 클래스에서 참조할 때 사용하는 참조 변수이다. 만약 Child 클래스에 a 멤버변수가 없었다면 a , this.a는 모두 부모를 가리키며 10, 10, 10이 출력된다.

super()

  • this()는 같은 클래스의 다른 생성자에서 기존의 생성자를 호출할 때 사용되지만, super()는 부모 클래스의 생성자를 호출할 때 사용된다.
class Parent {
    int a = 10;
    Parent(){} // 생성자가 없는 경우 기본적으로 등록 (생략)
}

class Child extends Parent {
    int a = 20;

    public Child() { // 자식 클래스에서도 마찬가지로 부모 생성자 초기화 (생략)
        super();
    }

    void chk() {
        System.out.println(a);
        System.out.println(this.a);
        System.out.println(super.a);
    }
}

public class Sample01 {
    public static void main(String[] args) {
        Child child = new Child();
        child.chk();
    }
}
  • 자식 클래스의 인스턴스를 생성하면, 해당 인스턴스에는 자식 클래스의 고유 멤버와 부모 클래스의 모든 멤버까지도 포함되어 있다. 그렇기 때문에 부모 클래스의 멤버를 초기화 하기 위해 부모 클래스의 생성자까지 호출해야한다. 이 호출은 최상위 레벨인 Object까지 거슬러 올라간다.

추상화 (Abstract)

  • 추상화란 공통된 내용(필드나 메서드)들을 추출하여 통일된 내용으로 작성하도록 규격화하는 것이다. 상속받은 클래스들은 필요한 메서드나 필드만 추가로 정의하고, 추상 메서드들을 오버라이딩하여 클래스마다 다르게 실행될 로직을 작성한다. 이렇게 추상화를 사용하면 공통적으로 작성해야할 부분들을 쉽게 관리하며, 변경 및 유지보수 등 규격화하기에 용이해진다. 이러한 추상화에는 추상클래스와 인터페이스가 있다.

추상 클래스

  • 상속(is ~ a 관계)을 통해 자식 클래스에서 완성하도록 유도하는 방식이다. 구현이 덜 된 미완성 클래스로 직접 객체 생성이 불가하며, 하위 클래스가 요구 된다. 이러한 추상 클래스는 abstract 제한자를 통해 구현된다.
  • 추상 클래스는 추상 메서드가 있으면 반드시 추상 클래스가 되어야하며, 추상 메서드가 없는 경우는 설계 상의 목적으로 개발자가 강제로 추상 클래스화를 만든 경우라고 보면 된다.

인터페이스

  • 추상 클래스가 미완성 설계도라면, 인터페이스는 기본 설계도라고 볼 수 있다. 인터페이스는 추상 클래스와 같이 하위 클래스를 작성하는데 도움을 주는 목적으로 작성하지만, 추상 클래스와는 다르게 다중 상속이 가능하다. (has ~ a 관계)

추상 클래스와 인터페이스의 차이는 인터페이스는 다중 상속이 가능하지만, 모든 클래스에서 재정의 해야하는 번거로움이 있는 반면, 추상 클래스는 일반 메서드를 필요한 부분만 재정의할 수 있지만 단일 상속만 가능하다. 하지만, Java 8버전 부터 인터페이스에서 default 메서드로 구현체를 가질 수 있게 되어서 추상 클래스와 인터페이스의 차이가 유연해졌다.

다형성 (Polymorphism)

image

  • c/t : 컴파일 타임으로 상위 타입에 정의된 것만 사용 가능, r/t : 런타임 타입으로 하위 타입에 호출하려는 메서드 재정의 여부를 확인하여 (동적 바인딩 기능) 재정의 되어있다면, 하위 타입의 메서드를 호출한다.
Employee e = new Engineer();
  • 해석은 '우변 객체를 좌변 객체로써 사용한다'로 위의 문장은 Engineer 객체를 Employee로써 사용하므로 e.a, e.b만 사용 가능하며 e.c는 사용할 수 없다. 만약 Engineer 객체에서 b 기능을 b2라고 재정의(오버라이딩)했다면, c/t에서 e.b를 가리키지만, r/t에서 동적 바인딩의 기능으로 e.b2의 기능을 실행한다. 즉, 메서드를 재정의하면 런타임 시점에서 하위 타입의 메서드를 사용할 수 있다.
  • 동적 바인딩과 추상클래스의 활용으로 무조건 재정의를 하도록 만들 수 있다. 이 기능은 곧 추상 클래스를 사용하는 이유가 된다.

이형 집합 배열 다형성

Employee[] list = new Employee[2];
list[0] = new Engineer();
list[1] = new Manager();
  • 직원들은 모두 배열에 담을 때도 다형성을 적용할 수 있다. Employee 대신 Object도 사용할 수 있지만, 위로 갈수록 추상적이기 때문에 Employee의 멤버변수에 접근할 수 없게 된다. 즉, list[0].name 과 같은 정보를 가져오기 위해선 Employee를 사용하는 것이 바람직하다.

매개변수의 다형성

void increaseSalary(Employee e) {
 // Engineer, Manager 객체 모두 한꺼번에 적용 
}

리턴타입의 다형성

Object or Employee xxx () {
  return new Engineer();
  //return new Manager();
}
  • 리턴 타입은 경우에 따라 다를 수 있으므로, 메서드의 기능에 맞게 판단이 필요하다.

상속 (Inheritance)

image

  • 클래스 상속은 extends 키워드를 사용하며, 기본적으로 모든 클래는 최상위 클래스인 Object를 상속 받으며, 생략되어 있다. 문자열을 사용할 때 equals, hashCode, toString 등과 같은 메서드를 사용하는 것이 바로 Object 클래스를 상속 받아서 사용하는 것이다. UML은 실선이며, 단일 상속만 가능하다.
  • is ~ a 관계 (부모 - 자식 간의 상속 관계), has ~ a 관계 (상속 관계가 아닌 객체 사용) - 결합도 문제 해결

상속 예제

public class SpiderManTest {
    public static void main(String[] args) {
        SpiderMan sman = new SpiderMan(); // Person을 상속받은 SpiderMan
        sman.isSpider = true; // SpiderMan 에서 Spider를 has a 관계 사용
        sman.eat(); // Person class
        sman.fireWeb(); // SpiderMan class
        sman.jump(); // Person class
    }
}

public class Person {
    String name;

    void eat() {
        System.out.println("맛있게");
    }
    void jump() {
        System.out.println("폴짝");
    }

}

public class Spider {
    void jump() {
        System.out.println("엄청난 점프실력!!");
    }
    void fireWeb() {
        System.out.println("슉슉~");
    }
}

public class SpiderMan extends Person{
    boolean isSpider;
    // is~a 관계는 사용 중이므로 has ~ a 관계로 객체 생성
    Spider spider = new Spider();
    void fireWeb() {
        if(isSpider) {
            spider.fireWeb();  //Spider의 fireWeb 사용
        }
        else System.out.println("사람일때는 못쏘지~");
    }
}
  • SpiderMan은 이미 Person을 상속받아 is ~ a 관계이다. Spider의 fireWeb()은 SpiderMan의 fireWeb()보다 좋은 기능을 갖고 있다고 생각하면, Spider의 기능을 상속받고 싶을 것이다. 그럴 경우 has ~ a관계를 사용하여 SpiderMan에서 Spider 객체를 생성해서 만들어준다.
class Parent {
    public Parent() {
        System.out.println("Custom");
        function();
    }

    public static void print() {
        System.out.println("Custom hello");
    }

    public void function(){
        System.out.println("Custom method");
    }
}

class Child extends Parent {
    private static final Child instance = new Child();

    public Child() {
        System.out.println("Sub");
    }

    public static Child getInstance() {
        return instance;
    }

    public static void print() {
        System.out.println("Sub Hello");
    }

    public void function() {
        System.out.println("Sub method");
    }
}

public class Sample02 {
    public static void main(String[] args) {
        Child.print();
        Parent instance = Child.getInstance();
        instance.function();
    }
}
----- 출력 결과 ---- 
Custom
Sub method
Sub
Sub Hello
Sub method
  • Child.print()가 실행되면 Child()의 생성자를 들린다. 이 안에 super(); 첫줄에 생략되어 있으므로, Custom이 출력되고 이 안에 function()문을 호출할 때 동적 바인딩에 의하여 자식의 function()문이 실행된다. 이어서 Child()의 생성자에 남아있는 Sub가 호출되고 남은 print()가 호출된다.

캡슐화 (Encapsulation)

  • 정보를 보호하기 위해 접근 제한자 및 데이터 은닉과 보호를 사용한다.
  • 보통 멤버 변수를 private으로 막아서 외부에서 직접 접근하지 못하게 하고 Getter & Setter를 통해 간접적으로 접근하게 한다. 또한, Getter & Setter 메서드 안에 필요한 추가 로직을 만들어서 간접 접근에도 제한을 준다.

제한자

  • 클래스, 변수, 메서드 선언부에 함께 사용되어 부가적인 의미 부여
접근 제한자

image

  • 메서드를 재정의(오버라이딩)하는 경우 접근 제한자가 있을 때 자식 메서드에서 같거나 넓게 접근제한자를 사용해야한다.
그 외 제한자
  • static : 클래스 레벨의 요소 설정
  • final : 수정할 수 없도록 설정 (값 변경, 오버라이딩 , 상속 방지 등)
  • abstract : 추상 메서드 및 추상 클래스 작성
  • synchronized : 멀티 쓰레드 환경에서 동기화 처리

JVM

image

class area

  • 클래스 정보를 담는 공간
  • Field, Method, 타입, 상수 풀 등이 저장

method stack

  • 메서드들의 실행 공간
  • Thread 별로 별도 관리하며, 메서드 호출 순서대로 쌓이는 구조

heap

  • 객체를 저장하기 위한 영역으로 Thread에 의해 공유 된다.
  • 생성된 객체는 프로그래머가 삭제할 수 없고 GC만이 제어 가능하다.

Garbage Collector

image

멤버참조

class UpperClassA {
    String var = "상위 클래스의 변수";
    void method() {
        System.out.println("상위 클레스의 메서드");
    }
}

class LowerClassB extends UpperClassA{
    String var = "하위 클래스의 변수";
    void method() {
        System.out.println("하위 클래스의 메서드");
    }
}
public class Sample01 {
    public static void main(String[] args) {
        UpperClassA a = new LowerClassB();
        System.out.println(a.var); // 상위 클래스의 변수 - 부모에 var 가없으면 에러
        a.method(); // 하위 클래스의 메서드 - 재정의하지 않았다면 부모 클래스 메서드로 실행 (동적 바인딩)
    }
}
  • 객체는 변수(멤버필드)와 메서드로 구성되지만, 외부로부터 변수와 메서드에 액세스하는 원칙은 서로 다르다. 즉, 변수에 액세스할 때는 참조하는 개체의 타입을 사용하지만, 메서드에 액세스할 때는 실제 객체의 타입을 사용한다. 메서드에 액세스할 때는 실제 객체의 타입을 사용하는 방식은 동적 바인딩 때문이다.
class Parent {
    int a;
    Parent(){
        a = 10;
    }
    Parent(int a) {
        this.a = a;
    }
}

class Child extends Parent {
    int b;

    public Child() {
        // super() : 생략 , super(30) 을  쓴다면 30이 결과
        b = 20;
    }

    void chk() {
        System.out.println(a); // 10
        System.out.println(b); // 20
    }
}

public class Sample01 {
    public static void main(String[] args) {
        Child child = new Child();
        child.chk();
    }
}
  • 다음과 같이 super()가 생략되어 있는 것을 확인할 수 있다.
class Parent {
    int x;
    public Parent(int x) {
        super();
        this.x = x + 5;
    }
    public void method() {
        x+=50;
    }
}

class Child extends Parent {
    int x;
    public Child(int x){
        super(x+2);
        this.x = x+4;
    }
    public void method() {
        super.x += 100;
        this.x += 200;
    }
}

public class Sample01 {
    public static void main(String[] args) {
        Parent p = new Child(0);
        Child p2 = new Child(0);
        p.method();
        p2.method();
        System.out.println(p.x); //107
        System.out.println(p2.x); //204
    }
}
  • '객체의 생성은 오른쪽 객체를 왼쪽 객체로 쓰겠다'로 해석하면 된다. 즉, p.x 는 Parent의 p이며 멤버 변수는 Parent의 x를 사용한다. 하지만 Child 객체에서 메서드를 재정의한 경우 자식 메서드를 사용한다. (동적 바인딩)
class Parent {
    int x;
    public Parent(int x) {
        super();
        this.x = x + 5;
    }
    public void method() {
        x+=50;
    }
}

class Child extends Parent {
//    int x;
    public Child(int x){
        super(x+2);
        this.x = x+4;

    }
    public void method() {
        super.x += 100;
        this.x += 200;

    }
}

public class Sample01 {
    public static void main(String[] args) {
        Parent p = new Child(0);
        p.method();
        System.out.println(p.x); // 304 출력
    }
}

Inner class

  • 어떤 클래스가 하나의 클래스에만 사용될 경우 내부에 정의하여 사용하면 편리하다.
  • 내부 클래스는 외부클래스의 한 멤버가 되므로, public private 등 접근 제한자를 가질 수 있으며, 외부 클래스의 멤버 변수 및 메서드에 액세스할 수 있다.
class Outside {
    public class Inside { // 접근 제한자 설정 가능 

    }
}

public class InnerClassTest {
    public static void main(String[] args) {
        Outside outer = new Outside();
        Outside.Inside inner = outer.new Inside(); // 내부 클래스 객체 생성을 위해 외부 클래스 객체 생성
    }
}

Singleton

image

방법 1

class Company {
  static Company C = new Company();
  private Company(){} // 외부 접근 방지 
  public Company getInstance() {
    return C;
  }
}
  • 실행될 때 객체가 바로 생성된다는 단점이 있다.

방법2

class Company {
  private static Company c;
  private Company(){}
  public synchronized static company getInstance(){
    if(c == null) c = new Company();
    return c;
  }
}
  • 객체가 호출될 때만 생성되도록 만들었지만, 멀티쓰레드 환경에서 동시성문제를 고려해서 synchronized를 사용해야한다. 즉, 객체 접근 시 마다 사용중인지 여부를 확인해야하기 때문에 오버헤드 발생의 여지가 있는 단점이 있다.
반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^