sm 기술 블로그

객체지향 (유효범위[this] /생성자 /상속[super] /오버로딩 /오버라이딩 /클래스패스 /패키지[import]) 본문

Java

객체지향 (유효범위[this] /생성자 /상속[super] /오버로딩 /오버라이딩 /클래스패스 /패키지[import])

sm_hope 2022. 4. 15. 22:10
유효범위
public class ScopeDemo {
 
    static void a() {
        int i = 0; // 지역변수
    } // 유효범위 (scope)
 
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            a();
            System.out.println(i);
        }
    }
 
}

메소드(a) 안에서 i와 반복문 i는 연관이 없다.

 

public class ScopeDemo2 {
    static int i; //전역변수
     
    static void a() {
        i = 0;
    }
 
    public static void main(String[] args) {
        for (i = 0; i < 5; i++) {
            a();
            System.out.println(i);
        }
    }
 
}

위 코드는 0을 무한히 출력할 것이다.

1) a 메소드 안에 int i

public class ScopeDemo2 {
    static int i; //전역변수
     
    static void a() {
       int i = 0;
    }
 
    public static void main(String[] args) {
        for (i = 0; i < 5; i++) {
            a();
            System.out.println(i);
        }
    }
 
}

2) main 메소드 안에 int i

public class ScopeDemo2 {
    static int i; //전역변수
     
    static void a() {
        i = 0;
    }
 
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            a();
            System.out.println(i);
        }
    }
 
}

다음과 같이 int를 붙여주면 오류를 해결할 수 있다. 

1) a() 안에 i 앞에 int를 붙여준다면 a() 메소드 안에서만 선언된 지역변수이다.

2) main 안에 i 앞에 int를 붙여준다면 main에서만 선언된 지역변수이다.

 

유용한 기능)

우측 클릭 -> Compare With -> Each Other
두 파일을 비교해준다.

 

문제)

public class ScopeDemo6 {
    static int i = 5;
 
    static void a() {
        int i = 10;
        b();
    }
 
    static void b() {
        System.out.println(i);
    }
 
    public static void main(String[] args) {
        a();
    }
 
}

출력 값은?.......... 5

 

정적인 유효범위(Statice scope) : 메소드 안에 선언된 지역변수나 전역 변수만 출력될 수 있다.

동적인 유효범위(dynamic scope) : 만약 b를 호출했는데 a가 가지고 있는 10이 출력된다면 동적인 유효범위이다.

 

this

class C3 {
    int v = 10;
 
    void m() {
        int v = 20;
        System.out.println(v);
        System.out.println(this.v);
    }
}
 
public class ScopeDemo9 {
 
    public static void main(String[] args) {
        C3 c1 = new C3();
        c1.m();
    }
 
}

출력 결과는 20과 10이 나온다.

this를 붙여줌으로써 전역 변수를 선택해 주는 것이다.

 

만약! m메소드 안에 v20(지역변수)이 없다면 this를 붙이지 않아도 자바는 알아서 전역 변수를 찾아서 진행된다.

 


생성자 

 

class Calculator {
    int left, right;
 
    public Calculator(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
 
public class CalculatorDemo1 {
 
    public static void main(String[] args) {
 
        Calculator c1 = new Calculator(10, 20); 
        // 메소드를 호출한다 -> 생성자이다. 즉, 인스턴스를 생성하는 생성자이다.
        
        c1.sum();
        c1.avg();
 
        Calculator c2 = new Calculator(20, 40);
        c2.sum();
        c2.avg();
    }
 
}

현재 Calculator의 클래스 안에 Calculator라는 메소드가 있다.

 

메소드 Calculator을 생성자(constructor)라고 한다.

 

생성자는 꼭 필요한 존재이며, 비어있는 생성자는 자바에서 자동으로 만들어준다.

public Calculator(){}

 

[단, 생성자가 하나도 없는 상태에서만 자동으로 부여해줌. 매개변수가 있는 생성자를 만들어 줬다면 자동으로 만들어 주지는 않음. 필요하면 직접 추가해야함.]


상속

새로운 객체가 기존에 있던 객체의 변수, 메소드를 받아 추가/변경을 하는 것

 

부모 클래스와 자식 클래스의 관계를 상위(super) 클래스와 하위(sub) 클래스라고 표현하기도 한다. 또한 기초 클래스(base class), 유도 클래스(derived class)라고도 부른다. 

 

 

기존의 계산기에서 뺄셈이 가능하도록 추가해보자

package org.opentutorials.javatutorials.Inheritance.example1;

class Calculator {
//		 부모클래스
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
 
class SubstractionableCalculator extends Calculator {
//		 자식클래스
//		 extends를 통해 마치 substract 메소드를 가지게 만들 수 있음.
//		 extends Calculator로 setOprands , sum, avg는 Calculator(부모 클래스)를 찾아간다.

    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorDemo1 {
 
    public static void main(String[] args) {
 
        SubstractionableCalculator c1 = new SubstractionableCalculator();
        c1.setOprands(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
 
}

다음은 곱하기를 추가해보자

package org.opentutorials.javatutorials.Inheritance.example1;

class MultiplicationableCalculator extends Calculator {
    public void multiplication() {
        System.out.println(this.left * this.right);
    }
}
 
public class CalculatorDemo2 {
 
    public static void main(String[] args) {
 
        MultiplicationableCalculator c1 = new MultiplicationableCalculator();
        c1.setOprands(10, 20);
        c1.sum();
        c1.avg();
        c1.multiplication();
    }
 
}

자식 클래스는 매우 많이 생성이 가능하다.

※ package로 묶는 경우 추가적으로 선언하지 않아도 Calculator를 찾아간다.

 

나누기도 추가해보자

class DivisionableCalculator extends MultiplicationableCalculator {
    public void division() {
        System.out.println(this.left / this.right);
    }
}
 
public class CalculatorDemo3 {
 
    public static void main(String[] args) {
 
        DivisionableCalculator c1 = new DivisionableCalculator();
        c1.setOprands(10, 20);
        c1.sum();
        c1.avg();
        c1.multiplication();
        c1.division();
    }
 
}

이렇게 추가하면 덧셈, 평균, 곱하기, 뺄셈 전부를 사용할 수 있다.

만약 나눗셈의 부모를 Calculator로 바로 지정한다면 곱하기의 기능은 쓰지 못한다.

 

지금까지 상속의 개념을 그림으로 그려보면,

상속을 통해 코드의 중복을 제거, 가독성 향상 등의 이점이 있다.

 


상속과 생성자

1) 문제없이 돌아감

public class ConstructorDemo {
    public static void main(String[] args) {
        ConstructorDemo  c = new ConstructorDemo();
    }
}

2) 에러 발생

public class ConstructorDemo {
    public ConstructorDemo(int param1) {} // 메소드 -> 생성자
    public static void main(String[] args) {
        ConstructorDemo  c = new ConstructorDemo();
    }
}

인자가 없이 생성자를 인스턴스화 해서 에러가 발생한 것이다.

 

class Calculator {
    int left, right;
     
    public Calculator(int left, int right){
        this.left = left;
        this.right = right;
    }
     
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
 
class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorConstructorDemo5 {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

암시적으로 부모 Calculator()라는 생성자가 정의되어 있지 않다. 명시적으로 정의해야 한다.

-> 자바는 기본 생성자를 생성해주지 않아 반드시 생성해 주어야 한다.

위 같은 코드에서 기본생성자를 넣어주고 에러를 해결하는 가장 간단한 방법은 다음과 같이 만들 수 있다.

class Calculator {
    int left, right;
    
    public Calculator(){} //추가된 부분 (기본 생성자)
    
    public Calculator(int left, int right){
        this.left = left;
        this.right = right;
    }
     
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
 
class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorConstructorDemo5 {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

이렇게 기본생성자를 추가하지 않고도 에러를 해결할 수 있는데

 

super

class Calculator {
    int left, right;
     
    public Calculator(){}
     
    public Calculator(int left, int right){
        this.left = left;
        this.right = right;
    }
     
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        super(left, right); //super를 생성함!
    }
 
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorConstructorDemo5 {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

super을 통해서 자식 클래스가 부모 클래스를 호출해서 부모 클래스의 기능을 사용할 수 있다.

즉, super는 부모 클래스를 의미한다. (this와 비슷한 의미)


오버라이딩 (Overriding)

부모가 갖고 있는 메소드를 물려받기는 했지만, 부모가 갖고 있는 메소드를 쓰지 않고 자식 클래스에
필요에 따라 재 정의하는 것

 

class Calculator {
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
 
class SubstractionableCalculator extends Calculator {
     
    public void sum() {
        System.out.println("실행 결과는 " +(this.left + this.right)+"입니다.");
    }
     
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorDemo {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator();
        c1.setOprands(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

과연 sum은 어느 클래스의 sum을 사용할까? 

부모 클래스가 갖고 있는 메소드를 자식 클래스가 재정의를 하여(overriding) 자식 클래스의 sum을 사용하여

출력 결과는 "실행 결과는 30입니다."가 출력될 것이다.

 

오버라이딩 조건

 

 

class Calculator {
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
 
class SubstractionableCalculator extends Calculator {
     
    public void sum() {
        System.out.println("실행 결과는 " +(this.left + this.right)+"입니다.");
    }
     
    public int avg() {
        return (this.left + this.right)/2;
    }
     
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorDemo {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator();
        c1.setOprands(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

이 경우 avg는 에러가 발생한다

그 이유는 부모는 avg가 void 형식이지만 자식은 avg가 int 형식이기 때문에 에러가 발생한다.

 

따라서 생성자 형식과 생성자 이름, 매개변수 데이터 타입, 개수가 일치해야 오버라이딩이 가능하다.

  • 메소드의 이름
  • 메소드 매개변수의 숫자와 데이터 타입 그리고 순서
  • 메소드의 리턴 타입
class Calculator {
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public int avg() {
        return ((this.left + this.right) / 2);
    }
}
 
class SubstractionableCalculator extends Calculator {
     
    public void sum() {
        System.out.println("실행 결과는 " +(this.left + this.right)+"입니다.");
    }
     
    public int avg() {
        return ((this.left + this.right) / 2);
    }
     
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorDemo {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator();
        c1.setOprands(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
}

그런데 int로 바꿔줬더니 부모와 자식 클래스에서 avg가 동일하다.

 

class Calculator {
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public int avg() {
        return ((this.left + this.right) / 2);
    }
}
 
class SubstractionableCalculator extends Calculator {
     
    public void sum() {
        System.out.println("실행 결과는 " +(this.left + this.right)+"입니다.");
    }
     
    public int avg() {
        return super.avg();
    }
     
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorDemo {
    public static void main(String[] args) {
        SubstractionableCalculator c1 = new SubstractionableCalculator();
        c1.setOprands(10, 20);
        c1.sum();
        System.out.println("실행 결과는" + c1.avg());
        c1.substract();
    }
}

super를 이용하면 중복 코드가 필요 없어짐.

 

그런데.. 만약 여러 개의 자식 클래스들이 있고 각 자식클래스들이 원하는 게 다를 때 (하나는 리턴 하나는 다른 매개변수) 어떻게 되지..?


오버로딩(overloading)

같은 이름이지만 서로 다른 매개변수 형식을 가진 메소드 여러 개를 정의할 수 있는 방법

class Calculator{
    int left, right;
    int third = 0; //굳이 0으로 초기화 안해줘도 된다.
      
    public void setOprands(int left, int right){
        System.out.println("setOprands(int left, int right)");
        this.left = left;
        this.right = right;
    }
     
    public void setOprands(int left, int right, int third){
        System.out.println("setOprands(int left, int right, int third)");
        this.left = left;
        this.right = right;
        this.third = third;
    }
     
    public void sum(){
        System.out.println(this.left+this.right+this.third);
    }
      
    public void avg(){
        System.out.println((this.left+this.right+this.third)/3);
    }
}
  
public class CalculatorDemo {
      
    public static void main(String[] args) {
          
        Calculator c1 = new Calculator();
        c1.setOprands(10, 20);
        c1.sum();       
        c1.avg();
        c1.setOprands(10, 20, 30);
        c1.sum();       
        c1.avg();
         
    }
  
}

클래스 내에 동일한 생성자가 두 개 있다.

둘이 매개변수의 개수가 다른데 이것은 문제없이 작동한다

자바는 메소드 이름이 같다고 하더라도 매개변수의 데이터 타입, 개수가 다르면 다른 메소드로 인식한다.

 

하지만 this.left = left , this.rigth =right는 중복이 발생한다.

 

class Calculator{
    int left, right;
    int third = 0; //굳이 0으로 초기화 안해줘도 된다.
      
    public void setOprands(int left, int right){
        System.out.println("setOprands(int left, int right)");
        this.left = left;
        this.right = right;
    }
     
    public void setOprands(int left, int right, int third){
        this.setOprands(left,right);
        System.out.println("setOprands(int left, int right, int third)");
        this.third = third;
    }
     
    public void sum(){
        System.out.println(this.left+this.right+this.third);
    }
      
    public void avg(){
        System.out.println((this.left+this.right+this.third)/3);
    }
}
  
public class CalculatorDemo {
      
    public static void main(String[] args) {
          
        Calculator c1 = new Calculator();
        c1.setOprands(10, 20);
        c1.sum();       
        c1.avg();
        c1.setOprands(10, 20, 30);
        c1.sum();       
        c1.avg();
         
    }
  
}

다음과 같이 중복을 제거할 수 있다.

일단 여기서는

//출력결과
setOprands(int left, int right)
30
10
setOprands(int left, int right)
setOprands(int left, int right, int third)
60
20

다음과 같이 나온다 -> setOprands(int left, int rigth)가 안 나오도록 생각해 보자.

 

오버로딩 규칙

public class OverloadingDemo {
    void A (){System.out.println("void A()");}
    void A (int arg1){System.out.println("void A (int arg1)");}
    void A (String arg1){System.out.println("void A (String arg1)");}
    //int A (){System.out.println("void A()");}
    public static void main(String[] args) {
        OverloadingDemo od = new OverloadingDemo();
        od.A();
        od.A(1);
        od.A("coding everybody");
    }
}
//출력결과
void A()
void A (int arg1)
void A (String arg1)

int A ()가 에러 나는 이유.

메소드의 이름이 같고 매개변수 형식이 같지만 리턴 값이 다르면 오류가 발생한다. -> 자바가 이해를 할 수 없다. 

 

오버로딩과 오버라이딩

public class OverloadingDemo2 extends OverloadingDemo{
   /* 1 */ void A (String arg1, String arg2){System.out.println("sub class : void A (String arg1, String arg2)");}
   /* 2 */ void A (){System.out.println("sub class : void A ()");}
    public static void main(String[] args) {
        OverloadingDemo2 od = new OverloadingDemo2();
        od.A();
        od.A(1);
        od.A("coding everybody");
        od.A("coding everybody", "coding everybody");
         
    }
}

1번 -> 오버로딩   2번 -> 오버 라이딩

 

 

 

 

 

 

 

1번의 메소드는 부모 클래스 정의되어있지 않다. 따라서 재정의를 하는 것이 아닌 새로 정의한 것으로 이름은 같지만 서로 다른 매개변수 형식을 가지고 있기 때문에 오버로딩이 된다.

 

반면에 2번은 부모 클래스에 정의되어있다. 부모클래스에 정의된 것을 자식 클래스에 가져와서 재정의 한 것은 오버라이딩이 된다.

 

 

오버라이딩(Overriding) : 부모가 정의한 메소드를 자식 클래스에서 올라탄다(재정의).

 

오버로딩(Overloading) : 같은 이름 하지만 다른 메소드 여러 개 정의한다. (매개변수가 다름)

 


클래스 패스

클래스가 위치하는 경로를 지정해서 자바가 필요로 하는 클래스를 로드할 수 있는 방법을 지정하는 것

 

이 수업은 터미널(cmd)로 진행되므로 기본 문법은 [java -classpath ".;폴더" 자바명]이다

-classpath는 뒤에 경로에서 클래스를 찾으라는 말이다.

 

https://www.youtube.com/watch?v=1HFicQuRwu8&list=PLuHgQVnccGMCeAy-2-llhw3nWoQKUvQck&index=93 

환경변수 

운영체제 안에서 어디에 있든 간에 접근할 수 있는 글로벌한 변수이다.

이것을 이용하면 [java -classpath ".;폴더"] 이 부분을 입력하지 않아도 자동적으로 찾는다.

 


패키지

여러 개의 클래스들이 존재할 때 이름이 여러 주체에 의해 만들어 충돌할 수 있다. 
즉, 이름의 충돌을 해결하기 위해 만든 것 

 

패키지는 일종의 디렉터리의 개념으로도 알 수 있다.

package org.opentutorials.javatutorials.packages.example1;
public class A {}
package org.opentutorials.javatutorials.packages.example1;
 
public class B {
    public static void main(String[] args) {
        A a = new A();
    }
}

클래스 A를 a에 인스턴스화 하는 것은 패키지로 연결되어 있어 아무 문제없다.

 

다른 패키지에 있는 클래스를 사용할 수 없다. -> 이것을 import를 통해 해결할 수 있다.

package org.opentutorials.javatutorials.packages.example2;
import org.opentutorials.javatutorials.packages.example1.A;
 
public class C {
    public static void main(String[] args) {
        A a = new A();
    }
}

다음과 같이 C클래스는 A클래스와 다른 패키지에 있지만 import를 통해 로드할 수 있는 것이다

[모든 것을 로드할 때는. A를.*로 바꿔 준다.]

 

개발도구를 활용하지 않고 여러 패키지를 컴파일하는 것은 다음 동영상을 보자.

https://www.youtube.com/watch?v=Po9O_Nobh0g&list=PLuHgQVnccGMCeAy-2-llhw3nWoQKUvQck&index=98 

IDE 없이는 개발하는 경우가 적기 때문에 참고로만 알아두자.

 

만약 example1과 example2에 B라는 클래스가 있다고 해보자. 이때는 어느 example의 B클래스를 정하 지를 못해 중복 문제가 발생한다.

이때 해결방법은

package org.opentutorials.javatutorials.packages.example3;
import org.opentutorials.javatutorials.packages.example1.*;
import org.opentutorials.javatutorials.packages.example2.*;
 
public class D {
    public static void main(String[] args) {
        org.opentutorials.javatutorials.packages.example2.B b = new org.opentutorials.javatutorials.packages.example2.B();
    }
}

다음과 같이 상세로 써주면 해결할 수 있다.

 

Comments