본문 바로가기
Programming/JAVA

생활코딩 JAVA OOP

by DONGKU 2020. 6. 29.

출처: 생활코딩 JAVA OOP


JAVA 객체 지향 프로그래밍

객체 지향

클래스를 중심으로 프로그램의 구조를 만들어가는 프로그래밍

인스턴스: 클래스의 복제본, like 카게 분신술..
상속: 부모 클래스를 자식 클래스에게 물려줌
인터페이스: 클래스의 설계도


남의 클래스 & 남의 인스턴스

import java.io.FileWriter;
import java.io.IOException;

public class OthersOOP {

    public static void main(String[] args) throws IOException {
        // class: System, Math, FileWriter
        // instance: f1, f2

        // 일회용으로 작업을 끝내면 되는 것들, 메소드나 변수를 클래스에 있는 것 그대로 사용
        System.out.println(Math.PI);  // Math라는 클래스 안에 PI라는 "변수"
        System.out.println(Math.floor(1.8));  // Math라는 클래스 안에 floor라는 "메소드"
        System.out.println(Math.ceil(1.8));  // Math라는 클래스 안에 ceil라는 "메소드"

        // 긴 맥락을 가지고 작업을 해야 하는 경우, 클래스를 직접 사용하는 것이 아닌 클래스를 복제(new)해서 사용
        FileWriter f1 = new FileWriter("data.txt"); // FileWriter 정보를 파일에 기록할 때 쓰는 클래스를 복제(new)해서 f1에 넣음, Project 우클릭 refresh 해줘야 data파일 생성
        f1.write("Hello");
        f1.write(" Java");
        f1.close();

        FileWriter f2 = new FileWriter("data2.txt");
        f2.write("Hello");
        f2.write(" Java2");
        f2.close();
    }
}

변수 메소드

이클립스의 메소드 생성 기능

Method 변환 원하는 부분 드래그
마우스 우클릭 Refactor 선택
Extract Method 선택

public class MyOOP {

    public static String delimiter = "";  // 전역변수 설정
    public static void main(String[] args) {
        // 각기 다른 구분자 사용 어려움
//        printA();
//        printA();
//        printB();
//        printB();
//
//        printA();
//        printA();
//        printB();
//        printB();

        // 이렇게 바꿔도 1억개가 있다면 불편함
//        printA("----");
//        printA("----");
//        printB("----");
//        printB("----");
//        
//        printA("****");
//        printA("****");
//        printB("****");
//        printB("****");

//        String delimiter = "----";  
//        printA(delimiter);
//        printA(delimiter);
//        printB(delimiter);
//        printB(delimiter);
//        
//        delimiter = "****";
//        printA(delimiter);
//        printA(delimiter);
//        printB(delimiter);
//        printB(delimiter);

        // 깔끔하고 유지보수 쉬운 코드가 됨
        delimiter = "----"; // 전역변수로 delimiter 설정되있으므로 값만 설정
        printA();
        printA();
        printB();
        printB();

        delimiter = "****";
        printA();
        printA();
        printB();
        printB();

    }

    // 각기 다른 구분자를 사용하기 어려움
//    public static void printA() {
//        System.out.println("----");
//        System.out.println("A");
//        System.out.println("A");
//    }
//
//    public static void printB() {
//        System.out.println("----");
//        System.out.println("B");
//        System.out.println("B");
//    }
    // 각기 다른 구분자를 사용하기 위해 메소드의 매개변수(파라미터)를 줌
    public static void printA() {
        System.out.println(delimiter);
        System.out.println("A");
        System.out.println("A");
    }

    public static void printB() {
        System.out.println(delimiter);
        System.out.println("B");
        System.out.println("B");
    }

}

클래스

소스코드를 컴파일해서 실행 시키게 되면
자바는 파일의 이름과 똑같은 클래스를 찾아서
그 클래스의 메인 메소드를 실행하도록 약속

하나의 파일 안에서 클래스를 여러개 만들면
그 각각의 클래스가 파일로서 존재

분산시킬 class 드래그 -> Refactor -> Moe Type to New File
자바 소스 파일 하나로 애플리케이션을 만들 수도 있지만
기능에 따라서 "파일"로 적당히 분산시켜 정리정돈 가능

MyOOP.java

public class MyOOP {
    public static void main(String[] args) {
        Print.delimiter = "----";  // // Print class 사용
        Print.A();
        Print.A();
        Print.B();
        Print.B();

        Print.delimiter = "****";  // Print라는 "Class" 소속의 delimiter값 설정
        Print.A();                         // Print로 돌려막기
        Print.A();
        Print.B();
        Print.B();
    }
}

Print.java

class Print {
    public static String delimiter = "";

    public static void A() {
        System.out.println(delimiter);
        System.out.println("A");
        System.out.println("A");
    }

    public static void B() {
        System.out.println(delimiter);
        System.out.println("B");
        System.out.println("B");
    }
}

인스턴스

여러 상태의 클래스가 동시에 필요할 때는 클래스 앞에 new를 붙여서 클래스의 복제본을 만들어서 서로 다른 상태를 유지할 수 있습니다. 클래스의 복제본을 인스턴스라고 합니다

MyOOP.java

public class MyOOP {
    public static void main(String[] args) {
        // Instance 사용하여 중복 제거
        Print p1 = new Print();  //  Print라는 Class 복제한  복제본 인스턴스 생성
        p1.delimiter = "****";   // class와는 독립된 p1 "Instance" 소속의 delimiter 값 설정
        p1.A();                  // 각각의 복제본은 서로 내부적으로 다른 데이터 유지 가능, p1으로 돌려막기 가능
        p1.A();
        p1.B();
        p1.B();

        Print p2 = new Print();  //  복제본 인스턴스 생성
        p2.delimiter = "****";  // class와는 독립된 p2 "Instance" 소속의 delimiter 값 설정
        p2.A();
        p2.A();
        p2.B();
        p2.B();

        p1.A();
        p2.A();
        p1.A();
        p2.A();

    }
}

Print.java

class Print{  // class 선언, 메소드는 A,B 멤버는 delimiter, A, B
//    public static String delimiter = "";  // static은 static 뒤에 따라오는 String이 Class 소속이라는 것
    public String delimiter = "";  // instance 소속이게 하려면 static을 없애줘야함

//    public static void A() {
    public void A() {  // instance 소속이게 하려면 static을 없애줘야함
        System.out.println(delimiter);
        System.out.println("A");
        System.out.println("A");
    }
//    public static void B() {
    public void B() {  // instance 소속이게 하려면 static을 없애줘야함
        System.out.println(delimiter);
        System.out.println("B");
        System.out.println("B");
    }

}

Class vs Instance

같은 기능을 가지고 있으나 수치가 다른 각각의 개체로 사용할 수 있다


static

static은 class 소속

static이 없으면 instance 소속

classVar를 바꾸면 모든 instance의 classVar 값이 바뀐다
instance에서 classVar를 바꾸면 class의 classVar이 바뀌고,
그 값을 사용하는 모든 instance의 값도 바뀐다
※static 메소드 안에서는 인스턴스 변수는 접근 불가

class를 통해 직접 instanceVar과 instanceMethod에 직접 접근하는 것은 금지되어있다.
instance는 class의 복제본이기 때문에 class에 있는 여러가지 멤버들을 복제해옴
f1의 classVar는 실제 값이 존재하지 않고 Foo라고 하는 Class를 가리키고 있을 뿐, 링크가 걸려있다
f1의 instanceVar는 값까지 복제, 링크 걸려있지 않음(instance의 값이 바뀐다고 class의 값이 바뀌지 않음, 독립적)

class Foo{
    public static String classVar ="I class var";
    public String instanceVar = "I instance var";  // static x
    public static void classMethod() {
        System.out.println(classVar);  // OK
//        System.out.println(instanceVar); // Error, static 메소드 안에서는 인스턴스 변수는 접근 불가, static은 class의 소속인데 instance의 변수에 접근하려면 어떤 instance의 변수인지 알 수가 없다
    }
    public void instanceMethod() {  // static x
        System.out.println(classVar);  // OK
        System.out.println(instanceVar); // OK
    }
}
public class StaticApp {

    public static void main(String[] args) {
        System.out.println(Foo.classVar);  // OK, Class를 통해서는 당연히 classVar 사용 가능
//        System.out.println(Foo.instanceVar);  // Error, Instance는 Instance를 통해 사용해야함
        Foo.classMethod();  // OK
//        Foo.instanceMethod(); // Error, Instance 메소드는 Instance 소속이기 때문에 Class를 통해서 접근하는 것은 금지

        Foo f1 = new Foo();  // Instance 생성
        Foo f2 = new Foo();
//      
        System.out.println(f1.classVar); // I class var
        System.out.println(f1.instanceVar); // I instance var
//      
        f1.classVar = "changed by f1";
        System.out.println(Foo.classVar); // changed by f1
        System.out.println(f2.classVar);  // changed by f1
//      
        f1.instanceVar = "changed by f1";
        System.out.println(f1.instanceVar); // changed by f1
        System.out.println(f2.instanceVar); // I instance var
    }

}

생성자(constructor)와 this

생성자

인스턴스가 생성될 때 반드시 처리해야 될, 최초로 꼭 실행 되어야 하는 어떠한 작업들을 해결
생성자의 주요한 작업은 초기화

this

클래스가 인스턴스화 되었을 때의 생성한 인스턴스를 가리키는 특수한 이름

MyOOP.java

public class MyOOP {
    public static void main(String[] args) {
        Print p1 = new Print("----");
        p1.A();
        p1.A();

    }
}

Print.java (생성자 사용)

class Print{  
    public String delimiter = "";  // instance 소속이게 하려면 static을 없애줘야함
    public Print(String _delimiter) {  // 생성자: Class 와 똑같은 이름의 메소드를 정의, static이나 return 데이타타입 지정 x
                                       // 클래스가 인스턴스화 될 때 실행 되어야 될 코드를 생성자 메소드 안에 정의 하는 걸 통해 초기화의 목적을 달성
        delimiter = _delimiter;  // ----
    }  
    public void A() {  // instance 소속이게 하려면 static을 없애줘야함
        System.out.println(delimiter);
        System.out.println("A");
        System.out.println("A");
    }
//    public static void B() {
    public void B() {  // instance 소속이게 하려면 static을 없애줘야함
        System.out.println(delimiter);
        System.out.println("B");
        System.out.println("B");
    }

}

Print.java(this 사용)

class Print{  
    public String delimiter = "";  
    public Print(String delimiter) {        
                    this.delimiter = delimiter;  // this: 생성한 인스턴스를가리키는 이름
                                                               // instance의 delimiter를 가리킴 -> public String delimiter = ""; , 매개변수의 delimiter를 가리키는 것이 x
    }  
    public void A() { 
        System.out.println(this.delimiter);  // this
        System.out.println("A");
        System.out.println("A");
    }
//    public static void B() {
    public void B() { 
        System.out.println(this.delimiter);  // this
        System.out.println("B");
        System.out.println("B");
    }

}

클래스와 인스턴스의 활용

class Accounting{
    public double valueOfSupply;  // instance 사용하므로 static 제거
    public static double vatRate = 0.1;  // 어떤 instance건 다 동일하므로 static으로 내버려둠
    public Accounting(double valueOfSupply) {  // 생성자, this 사용
        this.valueOfSupply = valueOfSupply;
    }
    public double getVAT() {  // instance 사용하므로 static 제거
        return valueOfSupply * vatRate;
    }
    public double getTotal() {  // instance 사용하므로 static 제거
        return valueOfSupply + getVAT();
    }
}
public class AccountingApp {

    public static void main(String[] args) {
//        // Class를 돌려쓰는 형태라 개수가 많을 경우 버그의 위험이 높다
//        Accounting.valueOfSupply = 10000.0;
//        System.out.println("Value of supply : " + Accounting.valueOfSupply);
//        Accounting.valueOfSupply = 20000.0;
//        System.out.println("Value of supply : " + Accounting.valueOfSupply);
//        
//        Accounting.valueOfSupply = 10000.0;
//        System.out.println("VAT : " + Accounting.getVAT());
//        Accounting.valueOfSupply = 20000.0;
//        System.out.println("VAT : " + Accounting.getVAT());
//        
//        Accounting.valueOfSupply = 10000.0;
//        System.out.println("Total : " + Accounting.getTotal());        
//        Accounting.valueOfSupply = 20000.0;
//        System.out.println("Total : " + Accounting.getTotal());        


          // Instance 사용
//          Accounting a1 = new Accounting();
//          a1.valueOfSupply = 10000.0;
//        
//          Accounting a2 = new Accounting();
//          a2.valueOfSupply = 20000.0;

          // 생성자(constructor) 사용
          Accounting a1 = new Accounting(10000.0);

          Accounting a2 = new Accounting(20000.0);



          System.out.println("Value of supply : " + a1.valueOfSupply);
          System.out.println("Value of supply : " + a2.valueOfSupply);

          System.out.println("VAT : " + a1.getVAT());
          System.out.println("VAT : " + a2.getVAT());

          System.out.println("Total : " + a1.getTotal());
          System.out.println("Total : " + a2.getTotal());

    }

}

앞으로 필요한 것들..

Parent가 갖고 있는 method의 개선 또는 버그들을 해결하게 되면
Parent를 copy한 모든 코드를 하나하나 다 직접 수정해야함
"상속"은 이러한 문제를 해결하기 위한 것이다.
Parent의 method 수정하면 상속받는 모든 자식들의 method도 변경

 

인터페이스에는 메소드의 이름, 파라미터, 리턴값 형식만 표시 (Class의 설계도)

implements Contract를 적어 놓게 되면 그 Contract에 적혀 있는 형태의 메소드를 
구체적으로 구현해야되는 책임을 콘크리트1과2가 가짐

같은 이름의 Class가 존재하기 위해서는 서로 다른 package에 담는다
class가 많아지면 많아진 class를 정리정돈위해 디렉토리로서 사용


댓글