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