back_Java_04

05-01 객체지향 프로그래밍이란?

  • 클래스를 활용한 객체를 만들고, 클래스의 메소드를 활용하지만, 독립적인 값을 가지게 만드는 방식이 객체 지향 방식이다.

class Calculator { 
	int result = 0; 

	int add(int num) { 
		result += num; 
		return result; 
	} 
	int sub(int num) {
		result -= num;
		return result;
	}
} 

public class Sample {
	public static void main(String[] args) { 
		Calculator cal1 = new Calculator(); // 계산기1 객체를 생성한다. 
		Calculator cal2 = new Calculator(); // 계산기2 객체를 생성한다. 
		
		System.out.println(cal1.add(3)); 
		System.out.println(cal1.add(4)); 
		System.out.println(cal2.add(3)); 
		System.out.println(cal2.add(7)); 
	} 
}

05-02 클래스

1. 객체에 대하여

  • 클래스를 선언하는 순간, 객체를 만들수 있는 기능이 가능해진다.

class Animal {

}

public class Sample {
	public static void main(String[] args) {
		Animal cat = new Animal();
	}
}

✅ 객체와 인스턴스

클래스에 의해서 만들어진 객체를 인스턴스라고도 한다. 인스턴스라는 말은 특정 객체가 어떤 클래스의 객체인지를 '관련성' 혹은 '관계'를 표현하기 위한 표현이라고 보면 된다.

2. 객체 변수(Instance varaible)

  • 객체변수 는 인스턴스 변수, 멤버 변수, 속성이라고 불린다.

  • 객체 변수는 기본적으로 도트연산자(.)를 통해 접근이 가능하다.

3. 메소드

  • 객체 변수에 접근 방법까지 했으나, 객체 변수에 접근하는 것이 아니라, 메소드를 활용할 수도 있다.

class Animal { 
	String name; 
	public void setName(String name) { 
		this.name = name; 
	} 

} public class Sample { 
	public static void main(String[] args) { 
		Animal cat = new Animal(); 
		System.out.println(cat.name); 
	} 
}
  • this 키워드는 해당 객체를 지칭하는 역할을 하게 된다.

4. 객체 변수는 공유되지 않는다.

  • 새롭게 할당하는 객체 대해서는 각 객체의 멤버 변수를 공유하지 않는 새로운 덩어리로 생성된다.

05-03 메소드

1. 메소드를 사용하는 이유?

  • 똑같은 내용을 반복적으로 사용하는 경우 메소드로 작성되어 사용된다.

2. 매개변수와 인수

  • 매개변수와 인수는 혼용되어 사용되는데 헷갈리면 안된다.

  • 매개변수는 메소드에 입력으로 전달된 값을 받는 변수, 메소드를 호출할 때 전달하는 실제 입력값을 의미한다.

3. 메소드의 입력값과 리턴값

  • 메소드는 들어오는 입력값을 가지고 처리하여 결과값으로 돌려준다.

입력값 ----> 메소드(블랙박스) ----> 리턴값
  • 입력값이 없는 메소드는 다음처럼 작성된다.

String say() {
	return "hi";
}
  • 리턴 값이 없는 메소드

void sum(int a, int b) {
	...
}
  • 입력값, 리턴값이 둘다 없는 메소드

void say() {
	System.out.printlin("Hi");
}

4. return 의 또다른 쓰임새

  • 당연히 기존의 사용 방식과 동일하지만, 추가적으로 메소드를 빠져나오고 싶을 때 사용도 가능하다.

5. 메소드 내에서 선언된 변수의 효력범위

  • 메소드 입력 항목이 값인지 객체인지 구분은, 입력 항목의 자료형이 primitive 자료형이라면 값을 복사하며, 그 이외의 경우는 객체 참조형으로 전달된다.

05-04 Call by Value

  • 자바는 기본적으로 primitive 자료형은 반드시 값으로, class로 선언된 객체에 대하여서는 call by Reference 로 전달된다.

  • 즉, 매개변수로 전달될 때 자바는 특징적으로 primitive와 reference 타입으로 구분된다고 이해하고 넘어가면 되는 것으로 보여진다.

05-05 상속

상속

class Animal {
	String name;

	void setName(String name) { this.name = name; }
}

class Dog extends Animal {}

public class Sample {
	public static void main(String[] args) {
		Dog dog = new Dog();
		dog.setName("Poppy");
		System.out.println(dog.name);
	}
}

부모 클래스의 기능을 확장

  • 자식 클래스에서 추가적인 기능을 확장하여, 부모 기능 + 자식 기능을 둘다 사용할 수 있음을 나타낸다.

IS-A 관계

  • 동물 클래스 - 개 클래스가 존재하는 형태라면, 개는 동물의 하위 개념이라고 볼 수 있고, 이러한 관계는 Is-A관계라고 부른다. 따라서 이러한 방식으로 관계도 성립된다.

Animal dog = new Dog();
  • 단, 이 경우 Animal 자료형으로 다 담기지 못해, sleep 기능과 같이 자식 클래스의 메소드를 사용할 수는 없 된다. 이때 오버로딩해두거나 라이딩 해둔다면 이 문제는 해결이 될 수 있다.

  • 당연히, 하위 클래스에서 상위클래스로 인스턴스를 선언하고 할당하는 것은 불가능하다.

메소드 오버라이딩

  • 부모 클래스의 메소드를 자식 클래스가 동일한 형태로 다시 구현하는 행위가 오버 라이딩이다.

  • 동작만 정의하는 것이므로, 선언부는 기존 메소드와 완전히 같아야 한다.

메소드 오버로딩

  • 동일한 메소드지만, 메소드의 매개변수를 다르게 하여 기능적으로도 다르게 동작하게 만든것이다.

다중 상속

  • 자바는 다중 상속을 지원하지 않는다.

05-06 생성자

생성자

  • 생성자는 클래스가 생성될 때 객체 변수를 위한 초기값을 설정하게 만들고, 이를 통해서만 객체가 생성될 수 있게 하여서, 초기 값이 지정되지 않아 생길 오류를 막는 역할을 한다.

디폴트 생성자

  • 아무런 값 없이 생성자를 선언해놓거나, 선언이 존재하지 않아 컴파일러가 자동으로 만들어준 것이 디폴트 생성자이다.

생성자 오버로딩

  • 여러개의 멤버 변수들에 여러 방향의 생성자용. 메소드 오버로딩과 동일한 개념이라 생각해도 무방하다.

05-07 인터페이스

인터페이스는 왜 필요한가

// 난 동물원의 사육사이다. 
// 육식동물들이 들어오면 난 먹이를 던져 준다. 
// 호랑이가 오면 사과를 던져준다. 
// 사자가 오면 바나나를 던져준다. 
class Animal {
	String name;
	void setName(String name) {
		this.name = name;
	}
}
class Tiger extends Animal {}
class Lion extends Animal {}
class ZooKeeper {
	void feed(Tiger tiger) {
		System.out.println("feed apple.");
	}
	void feed(Lion lion) {
		System.out.println("feed banana.");
	}
}

public class Sample {
	public static void main(String[] args) {
		Zookeeper zooKeeper = new ZooKeeper();
		Tiger tiger = new Tiger();
		Lion lion = new Lion();
		ZooKeeper.feed(tiger);
		ZooKeeper.feed(lion);
	}
}
  • 기존의 클래스를 사용하는 방식으로 고려하게 되면, 점점 추가되는 파생클래스에 맞춰 수정하게 되고 이는 매우 비효율적인 작업이다.

인터페이스 작성하기

//interface
interface Predator {
	String getFood() {};
}

class Animal{
	String name;

	void setName(Strin name) {
		this.name = name;
	}
}

class Tiger extends Animal implements Predator {
	public String getFood() {
		return "apple";
	}
}
class Lion extends Animal implements {
	public String getFood() {
		return "banana";
	}
}
class ZooKeeper {
	void feed(Predator predator) {
		System.out.println("feed" + predator.getFood());
	}

}
  • 기존 클래스의 메소드 입력으로는 클래스에 따라 자료형이 정의되게 되고, 따라서 하나의 인터펭스 기본 클래스와 클래스 상의 상속된 것들의 관리성을 제고 시킨다.

  • 클래스의 구현체와 상관업시 인터페이스를 기준으로 주요 클래스 작성을 해야 한다.

인터페이스의 메소드

  • 인터페이스의 메소드는 항상 public 키워드로 구현해야 한다. 그리고 여기서 함수 오버라이딩을 활용한다고 보면 된다.

인터페이스의 핵심과 개념

  • 인터페이스 클래스는 해당하는 인터페이스만을 이해하는 것으로, 어떤 클래스를 사용해야하는 클래스로 하여금 어느 클래스든지간에 받아들일 수 있도록 만들었고, 이는 함수 작성에 있어 훨씬 더 적은 노동력을 동원할 수 있다는 점을 보여준다.

  • 더불어 상속과도 유사할 수 있다. 하지만 상속은 자식 클래스가 부모 클래스의 메소드를 오버라이딩 하지 않고 사용할 수 있어, 반드시 구현해야 한다는 강제성이 존재하지 않느다.

  • 이에 비해 인터페이스는 반드시 지정되었을 대, 구현을 해야 하는 강제적 특성을 가진다는 점을 기억하자.

디폴트 메서드

  • 자바 8버전 이후부터 생겨난 것으로, 인터페이스의 메서드는 몸통(구현체)를 가지지 못하지만, 실제론 기본값처럼 쓸 수 있게 되는 역할을 한다.

interface Predator {
	String getFood();
	default void printFood() {
		System.out.printf("my food is %s\n", getFood());
	}
}
  • 디폴트 메서드는 메서드 명 앞에 default라는 키워드를 표기해야 하며, 이를 통해 실제 클래스에선 printFood 라는 클래스를 구현하지 않아도, 값을 가지며, 사용도 가능해졌다.

  • 또한 디폴트 메서드는 그냥 사용도 가능하면서도 오버라이딩이 가능하기에, 실제 사용하는 클래스에서 다시 정의하여 사용하는 것도 가능하다.

스태틱 메서드

  • 자바 버전 8부터 새롭게 추가된 기능으로, 인터페이스명.스테틱메서드명과 같은 방식으로 사용이 가능하다. 일반 클래스의 static 메서드를 사용하는 것처럼 쓸 수 있다.

interface Predator {
	String getFood();
	
	default void printFood() {
		System.out.printf("my food is %s\n", getFood());
	}
	int LEG_COUNT = 4; // Macro define 역할을 하는 것으로 보인다. 
	static int speed() {
		return LEG_COUNT * 30;	
	}
}

✅ 인터페이스 상수

인터페이스에서 인터페이스를 사용할 때 필요한 상수들을 기록해두는 형태로, 필수적인 것들을 지정하면서 사용하는 것으로 보인다. 여기서 핵심은 `public static final`이라는 키워드를 굳이 적지 않아도 자동으로 적용된다는 점으로 보인다.

05-08 다형성

  • 복잡한 분기문을 보다 간단하게 처리하는 등에서 효과적으로 사용될 수 있다.

  • 다형성이란 하나의 객체가 여러개의 자료타입을 가질 수 있는 것을 의미한다.

Sample.java

interface Predator {
    (... 생략 ...)
}

class Animal {
    (... 생략 ...)
}

class Tiger extends Animal implements Predator {
    (... 생략 ...)
}

class Lion extends Animal implements Predator {
   (... 생략 ...)
}

class ZooKeeper {
    (... 생략 ...)
}

class Bouncer {
    void barkAnimal(Animal animal) {
        if (animal instanceof Tiger) { // instanceof 키워드를 통해 받은 객체의 클래스를 구분한다. 
            System.out.println("어흥");
        } else if (animal instanceof Lion) {
            System.out.println("으르렁");
        }
    }
}

public class Sample {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        Lion lion = new Lion();

        Bouncer bouncer= new Bouncer();
        bouncer.barkAnimal(tiger);
        bouncer.barkAnimal(lion);
    }
}
  • bouncer 라는 클래스를 통해 각 animal의 하위 클래스의 객체들을 집어넣고, instnaceof 를 통해 분기를 처리하는 것을 볼 수 있다.

  • 이러한 개념은 IS-A를 통해 구현된다고 볼 수 있는데, 만약 여기서 더 많은 동물 하위 클래스가 생성된다면, 불가피하게 Bouncer 클래스 내부의 barkAnimal메서드의 코드 추가가 불가피해 진다.

  • 따라서 이를 개선하고자 Barkable 인터페이스를 구현하는 방식을 써볼 수 있다.

interface Predator {
    (... 생략 ...)
}

interface Barkable {
    void bark();
}

class Animal {
    (... 생략 ...)
}

class Tiger extends Animal implements Predator, Barkable { // 인터페이스화 시키고
    public String getFood() {
        return "apple";
    }

    public void bark() {
        System.out.println("어흥"); // public 인터페이스 기능으로 오버라이딩
    }
}

class Lion extends Animal implements Predator, Barkable {
    public String getFood() {
        return "banana";
    }

    public void bark() {
        System.out.println("으르렁");
    }
}

class ZooKeeper {
    (... 생략 ...)
}

class Bouncer {
    void barkAnimal(Barkable animal) {  // Animal 대신 Barkable을 사용
        animal.bark(); // 받는 부모 클래스가 아니라, 해당 인터페이스의 클래스 명을 통해 지정해줌으로써 
    }
}

public class Sample {
    (... 생략 ...)
}
  • 예시 코드를 보면서 알 수 있는점은 다음과 같다.

    • 인터페이스는 '기능'의 클래스화 라는 느낌이다.

    • 기능의 클래스이기 때문에 기존의 '객체' 클래스들과는 다른 원리로 작동하고, 기본적으로 가상 테이블 구조로 짜여진 자바의 특성 상 '동일한 기능'이 있음을 명확히하는 것은 클래스 사용성을 높일 수 있다고 보여진다.

    • 따라서 클래스 상에서 인터페이스라고 지칭만 해주고 implements {class name}, 기능에 대한 정의인 만큼, '강제적'으로 함수 오버라이딩이 필수가 된다. (즉, 기능 자체가 존재한다는 사실이 완벽하게 보장된다.)

    • 이러한 특징덕에 다형성을 구현할 때 부모 클래스를 통한 접근도 가능은 하지만, 인터페이스를 활용하는 경우가 발생하고, 위 예시코드처럼 작성하는것이 가능하다.

  • 다형성의 특성상 클래스 추론이 가장 중요할 수 있는데, 이러한 추론 자체가 매우 손쉬워진다는 점이 장점으로 보인다.

// 다형성으로 객체 선언이 가능한 경우는 아래와 같다. 
Tiger tiger = new Tiger(); // 자기 자신 클래스로 선언
Animal animal = new Tiger(); // 부모 클래스로 선언
Predator predator = new Tiger(); // 인터페이스 선언
Barkable breakable  = new Tiger(); // 인터페이스 선언
  • 인터페이스는 extends 를 이용해서 여러개의 인터페이스를 묶은 새로운 인터페이스를 만들수 있다. 즉 다중 상속 이 가능하다. 이는 인터페이스는 '강제성'을 띄기 때문에 오버라이딩이 필수이고, 기능적 정의를 묶는 것에서 문제가 생기지 않을 것이란 자바 설계자의 철학이 담긴것이라고 볼 수 있다.

interface Predator {
    (... 생략 ...)
}

interface Barkable {
    void bark();
}

interface BarkablePredator extends Predator, Barkable {
}

(... 생략 ...)
interface Predator {
    String getFood();

    default void printFood() {
        System.out.printf("my food is %s\n", getFood());
    }

    int LEG_COUNT = 4;  // 인터페이스 상수

    static int speed() {
        return LEG_COUNT * 30;
    }
}

interface Barkable {
    void bark();
}

interface BarkablePredator extends Predator, Barkable {
}

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Tiger extends Animal implements Predator, Barkable {
    public String getFood() {
        return "apple";
    }

    public void bark() {
        System.out.println("어흥");
    }
}

class Lion extends Animal implements BarkablePredator {
    public String getFood() {
        return "banana";
    }

    public void bark() {
        System.out.println("으르렁");
    }
}

class ZooKeeper {
    void feed(Predator predator) {
        System.out.println("feed " + predator.getFood());
    }
}

class Bouncer {
    void barkAnimal(Barkable animal) {
        animal.bark();
    }
}

public class Sample {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        Lion lion = new Lion();

        Bouncer bouncer = new Bouncer();
        bouncer.barkAnimal(tiger);
        bouncer.barkAnimal(lion);
    }
}

05-09 추상 클래스

  • 추상클래스는 인터페이스 역할을 하면서도 클래스의 기능도 가지고 있는 돌연변의적인 클래스로, 이는 인터페이스를 대체하는 기능도 한다고 볼 수 있다.

  • Predator 인터페이스를 다음과 같이 추상클래스로 변경할 수 있다.

// 기존 인터페이스 형식의 구조
interface Predator {
    String getFood();

    default void printFood() {
        System.out.printf("my food is %s\n", getFood());
    }

    int LEG_COUNT = 4;  // 인터페이스 상수 자동 static 화 

    static int speed() {
        return LEG_COUNT * 30;
    }
}
/*====================================================================*/
abstract class Predator extends Animal {
	abstract String getFood();

	void printFood() { // default 를 제거한다. 
		System.out.printf("my food is %s\n", getFood());
	}

	static int LEG_COUNT = 4; // 추상 클래스의 상수는 static 선언 
	static int speed() {
		return LEG_COUNT * 30;
	}
}

(... 생략 ...)
  • 추상클래스를 만들려면 abstract 라는 키워드를 붙여야 하고, 인터페이스의 메소드 역할을 하는 메소드도 abstract 를 붙여야 한다.

  • 추상클래스는 C++ 처럼 몸통이 존재하지 않고, 상속하는 클래스에서 해당 메소드를 구현해야하는 부분은 동일하다.

  • 더불어 인터페이스에서는 디폴트메서드는 더이상 사용이 안됨. 따라서 일반 메서드로 변경해야 하고, LEG_COUNT 와 같은 상수도 인터페이스에선 자동으로 static 으로 인식 하지만 추상클래스는 명시적으로 static 키워드를 적어줘야 한다.

  • 여기서 Predator 인터페이스를 추상 클래스로 만들어 버렸고, 이로써 BarkablePredator 인터페이스는 사용이 불가능하여, 이를 인터페이스로 사용하고, 추상클래스를 상속하는 형태로 바뀌게 된다.

abstract class Predator extends Animal {
	(... 생략 ...)
}

interface Barkable {
	(... 생략 ...)
}

class Animal {
	(... 생략 ...)
}

class Tiger extends Predator implements Barkable { // 기존 인터페이스 형태에서는 implements 키워드로 다중 상속이 가능했음.
	(... 생략 ...)
}

class Lion extends Predator implements Barkable {
	(... 생략 ...)
}

class ZooKeeper {
	(... 생략 ...)
}

class Bouncer {
	(... 생략 ...)
}
public class Sample {
	(... 생략 ...)
}
  • 추상 클래스로 선언된 클래스와 그 메소드들은 인터페이스의 메소드와 마찬가지로 구현이 반드시 되어야 한다.

  • 추상클래스에서 추상 메소드(순수 가상함수...? C++ 기준) 외에도 일반적인 실제 메소드를 추가 하여 사용이 가능하다.

인터페이스와 추상클래스의 차이 자바 8버전부터 인터페이스에 default 메서드가 추가되어, 추상클래스와의 그 경계가 모호해졌다. 그러나 추상클래스는 인터페이스와 달리 일반 클래스처럼 객체, 변수, 생성자, private 메서드 등을 가질 수있다는 차이를 가지고 있다.

Last updated