본문 바로가기
Java/생활코딩

생활코딩 - 자바입문 (시즌2)

by 자연송어 2021. 5. 5.

객체지향프로그래밍 (1/3) : 오리엔테이션

객체는 변수와 메소드를 그룹핑한 것이다.(쉽게 설명했을 떄)

객체란 무엇인지 자세하게 설명할 수 있어야 함. <- 중요. 조금 더 서치해보자

(2/3) : 추상화

- 위의 그림은 런던의 지도다.  

- 왼쪽 상단의 지도는 현실의 복잡함을 나타낸다. 오른쪽 하단의 지도는 지하철 탑승자의 관심사만을 반영하고 있다. 

- 역 간의 거리나 실제 위치와 같은 요소들은 모두 배제하고 있다.

- 복잡함 속에서 필요한 관점만을 추출하는 행위를 추상화라고 한다.

 

- 지하철 노선도가 디자인의 추상화라고 한다면 프로그램을 만든다는 것은 소프트웨어의 추상화라고 할 수 있다.

- 객체 지향 프로그래밍은 좀 더 현실을 잘 반영하기 위한 노력의 산물이다.

- 좋은 설계는 문법을 배우는 것보다 훨씬 어려운 일이다. 심지어 이것은 지식을 넘어서 지혜의 영역이다.

 

- 언어가 지원하는 객체지향 문법을 배우고, 이것들이 어떻게 동작하는지를 충분히 이해한 다음에

비로소 설계 원칙도 이야기할 수 있고, 객체와 사물의 비유도 시도해 볼 수 있을 것이다. 

 

(3/3) : 부품화

- 하나의 프로그램이 여러 개의 형태의 로직으로 이루어져 있다.

- 그 로직과 관련된 변수와 메소드들을 그룹핑 시켜놓은 것을 객체라고 한다.

- 객체를 만들어 놓으면 다른 곳에서 재활용할 수 있게 된다.

- 객체는 다양한 곳에서 부품처럼 사용할 수 있다는 것.

 

객체 지향 프로그래밍의 핵심은 연관된 메소드와 그 메소드가 사용하는 변수들을 분류하고 그룹핑하는 것이다. 바로 그렇게 그룹핑 한 대상이 객체(Object)다. 

 

은닉화 : 객체가 어떻게 생겼는지 몰라도 메소드의 사용방법만 알아도 사용 가능하도록 하겠다는 것.

 

 

인터페이스

- 잘 만들어진 부품이라면 부품과 부품을 서로 교환 할 수 있어야 한다.

- 집에 있는 컴퓨터에 A사의 모니터를 연결하다가 B사의 모니터를 연결 할 수 있다.

- 이러한 연결점을 인터페이스(interface)라고 한다. 

- 인터페이스란 이질적인 것들이 결합하는 것을 막아주는 역할도 하는 것이다. 즉 인터페이스는 부품들 간의 약속이다. 

 

클래스와 인스턴스 그리고 객체지향 (1/5) : 객체지향 이전의 코딩

 

public static void sum(int left, int right) {
        System.out.println(left + right);
    }
 
    public static void main(String[] args) {
        sum(10, 20);
        sum(20, 40);

메소드를 통한 리팩토링의 결과 ↑ 코드의 개선

- 유지보수의 편의성 

- 가독성의 향상

 

클래스와인스턴스그리고객체지향 (2/5) : 객체지향이전의 코딩 2

- right는 오른쪽이라는 뜻도 있지만 권리라는 뜻도 있기 떄문에 같은 코드 사이에 섞일 가능성도 있음.

- 하나의 프로그램 안에서 메소드나 변수의 의미가 달라지게 되는 것.

- 코드가 복잡해짐에 따라서 버그는 많아지고, 팀웍은 서서히 깨지기 시작할 것이다.

- 이런 맥락에서 객체 지향이라는 개념이 등장하기 시작하는 것이다. 

- 변수로 상징되는 데이터와 연산으로 상징되는 메소드를 연관되어 있는 것 끼리 그룹핑 할것이냐에 대한 고민에서

출발한 것이 객체지향이라는 것.

 

클래스와인스턴스그리고객체지향 (3/5) : 객체화

 

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);
    }
}
  
public class CalculatorDemo4 {
      
    public static void main(String[] args) {
          
        Calculator c1 = new Calculator();
        c1.setOprands(10, 20);
        c1.sum();       
        c1.avg();       
          
        Calculator c2 = new Calculator();
        c2.setOprands(20, 40);
        c2.sum();       
        c2.avg();
    }

클래스

class Calculator {

이 로직들을 대표하는 이름을 계산기라는 의미의 Calculator라고 정하고 이것들을 Calculator이라는 이름으로 그룹핑하고 싶다. 이럴 때 사용하는 키워드가 class이다. 

- 클래스는 연관되어 있는 변수와 메소드의 집합이다.

 

인스턴스

클래스는 일종의 설계도다. 클래스를 정의하는 것 자체로는 할 수 있는 일이 많지 않다. 설계도를 구체적인 제품으로 만들어야 한다. 그 때 사용하는 키워드가 new이다.

Calculator c1 = new Calculator();

- new Calculator()은 클래스 Calculator를 구체적인 제품으로 만드는 명령이다. 이렇게 만들어진 구체적인 제품을 인스턴스(instance)라고 부른다.

- 위의 코드는 new를 이용해서 만든 인스턴스를 변수 c1에 담고 있다. 변수 c1에 인스턴스를 담은 이유는 c1을 통해서 인스턴스를 제어해야 하기 때문이다. 

 

클래스와인스턴스그리고객체지향 (5/5) : 객체화 3

- int c 로 값을 입력 : 내부적으로(하드웨어적) 봤을 때는 물리적인 메모리의 공간에 입력한 값을 넣겠다는 것.

- String[] args : 문자열 타입의 arg라는 이름의 배열 생성.

- String[10] args : 10개의 문자열이 들어갈 수 있는 메모리 확보해서 나중에 넣는 것. 

- 클래스를 만드는 것 : 변수를 넣는 공간을 직접 만드는 것. 사용자 정의 데이터 타입을 만드는 것. (시스템 내부적으로)

- 코드를 더 효율적이고 경제적으로 작성할 수 있는 도구, 수단 제공 

 

클래스 맴버, 인스턴스 맴버 (1/4) : 맴버란?

 

클래스 맴버, 인스턴스 맴버 (2/4) : 클래스 변수

class Calculator {
    static double PI = 3.14;
    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);
    }
}
 
public class CalculatorDemo1 {
 
    public static void main(String[] args) {
 
        Calculator c1 = new Calculator();
        System.out.println(c1.PI);
 
        Calculator c2 = new Calculator();
        System.out.println(c2.PI);
 
        System.out.println(Calculator.PI);

- 원주율인 3.14는 이미 알려져있는 수이다. 따라서 각각의 인스턴스 마다 원주율의 값을 별도로 가지고 있을 필요가 없다. 이런 경우 변수를 클래스의 맴버로 만들면 된다. 

static double PI = 3.14;

- 변수 PI의 앞에 static이 붙었다. static을 맴버(변수,메소드) 앞에 붙이면 클래스의 맴버가 된다. 

 

class Calculator2 {
    static double PI = 3.14;
    // 클래스 변수인 base가 추가되었다.
    static int base = 0;
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        // 더하기에 base의 값을 포함시킨다.
        System.out.println(this.left + this.right + base);
    }
 
    public void avg() {
        // 평균치에 base의 값을 포함시킨다.
        System.out.println((this.left + this.right + base) / 2);
    }
}
 
public class CalculatorDemo2 {
 
    public static void main(String[] args) {
 
        Calculator2 c1 = new Calculator2();
        c1.setOprands(10, 20);
        // 30 출력
        c1.sum();
 
        Calculator2 c2 = new Calculator2();
        c2.setOprands(20, 40);
        // 60 출력
        c2.sum();
 
        // 클래스 변수 base의 값을 10으로 지정했다.
        Calculator2.base = 10;
 
        // 40 출력
        c1.sum();
 
        // 70 출력
        c2.sum();

클래스 변수의 용도를 정리해보면 아래와 같다.

  • 인스턴스에 따라서 변하지 않는 값이 필요한 경우 (위의 예에서는 PI)
    (이런 경우 final을 이용해서 상수로 선언하는 것이 바람직 하지만 final을 아직 배우지 않았기 때문에 언급하지 않았다)
  • 인스턴스를 생성할 필요가 없는 값을 클래스에 저장하고 싶은 경우
  • 값의 변경 사항을 모든 인스턴스가 공유해야 하는 경우

클래스 맴버, 인스턴스 맴버 (3/4) : 클래스 메소드

컨트롤 + 코드 누르면 링크되어 원하는 곳으로 바로 이동가능

 

class Calculator3{
  
    public static void sum(int left, int right){
        System.out.println(left+right);
    }
     
    public static void avg(int left, int right){
        System.out.println((left+right)/2);
    }
}
 
public class CalculatorDemo3 {
     
    public static void main(String[] args) {
        Calculator3.sum(10, 20);
        Calculator3.avg(10, 20);
         
        Calculator3.sum(20, 40);
        Calculator3.avg(20, 40);

- 메소드가 인스턴스 변수를 참조하지 않는다면 클래스 메소드를 사용해서 불필요한 인스턴스의 생성을 막을 수 있다.

 

클래스 맴버, 인스턴스 맴버 (4/4) : 타입별 비교

  1. 인스턴스 메소드는 클래스 맴버에 접근 할 수 있다.
  2. 클래스 메소드는 인스턴스 맴버에 접근 할 수 없다.

인스턴스 변수는 인스턴스가 만들어지면서 생성되는데, 클래스 메소드는 인스턴스가 생성되기 전에 만들어지기 때문에 클래스 메소드가 인스턴스 맴버에 접근하는 것은 존재하지 않는 인스턴스 변수에 접근하는 것과 같다.

 

class C1{
    static int static_variable = 1;
    int instance_variable = 2;
    static void static_static(){
        System.out.println(static_variable);
    }
    static void static_instance(){
        // 클래스 메소드에서는 인스턴스 변수에 접근 할 수 없다. 
        //System.out.println(instance_variable);
    }
    void instance_static(){
        // 인스턴스 메소드에서는 클래스 변수에 접근 할 수 있다.
        System.out.println(static_variable);
    }
    void instance_instance(){        
        System.out.println(instance_variable);
    }
}
public class ClassMemberDemo {  
    public static void main(String[] args) {
        C1 c = new C1();
        // 인스턴스를 이용해서 정적 메소드에 접근 -> 성공
        // 인스턴스 메소드가 정적 변수에 접근 -> 성공
        c.static_static();
        // 인스턴스를 이용해서 정적 메소드에 접근 -> 성공
        // 정적 메소드가 인스턴스 변수에 접근 -> 실패
        c.static_instance();
        // 인스턴스를 이용해서 인스턴스 메소드에 접근 -> 성공
        // 인스턴스 메소드가 클래스 변수에 접근 -> 성공
        c.instance_static();
        // 인스턴스를 이용해서 인스턴스 메소드에 접근 -> 성공 
        // 인스턴스 메소드가 인스턴스 변수에 접근 -> 성공
        c.instance_instance();
        // 클래스를 이용해서 클래스 메소드에 접근 -> 성공
        // 클래스 메소드가 클래스 변수에 접근 -> 성공
        C1.static_static();
        // 클래스를 이용해서 클래스 메소드에 접근 -> 성공
        // 클래스 메소드가 인스턴스 변수에 접근 -> 실패
        C1.static_instance();
        // 클래스를 이용해서 인스턴스 메소드에 접근 -> 실패
        //C1.instance_static();
        // 클래스를 이용해서 인스턴스 메소드에 접근 -> 실패
        //C1.instance_instance();

- 여기 이해가 가면서 완벽하게 이해가 가지 않는 느낌 여러번 반복하기

 

유효범위(1/4) : 유효범위란?

int left; //변수 이름
public void sum(){} //메소드의 이름

프로그램이 커지면 여러 가지 이유로 이름이 충돌하게 된다. 이를 해결하기 위해서 고안된 것이 유효범위라는 개념이다. 흔히 스코프(Scope)라고도 부른다.

 

- 유효범위 : 메소드 안에서 변수를 선언하면 그것은 메소드 안에서만 유효하다는 것 

 

유효범위 (2/4) - 전역변수, 지역변수

두개의 파일간 차이점 비교하는 방법

 

 

public class ScopeDemo {
	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);
            
// 원하는 결과값은 01234 
// 하지만 실제 결과값은 0000....

결과값이 0 무한반복이 나타나는 이유

- 클래스 바로 밑에 선언한 static int i; 가 전역변수임.

- 전역변수는 모든 클래스와 문, 모든 중괄호에 영향을 미치기 때문에 a(); 실행시 i = 0으로 초기화 되는 것. 

 

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

- 코드에 표시한 부분에 int를 추가하면 그 메소드 안에서만 유효하게 됨.

- 즉, 다른 메소드에 영향을 주지 않게 되어 결과값이 정상적으로 01234가 출력됨.

- 맨 위의 전역변수(static int i)와 전혀 무관한 변수가 됨. (지역변수)

 

유효범위 (3/4) : 다양한 유효범위

public class ScopeDemo4 {
    static void a(){
        String title = "coding everybody";
    }
    public static void main(String[] args) {
        a();
        //System.out.println(title);
public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }
        System.out.println(i);

- int가 for문으로 인해 선언이 됨. 중괄호 사이에만 유효하기 때문에 오류가 나타남.

 

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

- 결과는 어떻게 나오게 될까?

 

 

지역변수를 통해 테스트 진행

- 초기 결과값은 5

- 메인메소드의 지역변수 (int i = 1;)는 결과에 영향을 미치지 못했음. (결과값 5)

- b() 메소드에 지역변수(int i = 30;) 추가하자 (결과값 30)

- 테스트 결과 b() 메소드에 지역변수 추가하기 전에는 i가 전역변수 5의  영향을 받아서 결과값 5출력

- 결과 : b()를 제외한 모든 메소드의 지역변수는 결과값에 영향을 주지 않았음. 

 

유효범위 (4/4) : this

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

과정

- c() 클래스를 c1 인스턴스에 담는다.

- c1인스턴스에 있는 m()이라는 메소드를 호출함.

- m()은 v를 호출한다. 

- v는 전역변수이고 10의 데이터를 가지고 있다.

- 화면에 10을 출력

 

중간에 m()의 지역변수로 int v= 20을 추가한 경우

- 전역변수(int v= 10) 보다 지역변수(int v = 20)가 더 우선순위가 높기 떄문에 결과값은 20이 출력됨.

- 전역변수는 모든 메소드와 중괄호 안에서 공통적으로 사용할 수 있는 기본값 
- 지역변수를 별도로 선언하는 것은 지정된 메소드 안에서만 값을 바꾸고 싶을 떄 사용 

 

this = 인스턴스(c1) 자신을 의미. this를 통해 전역변수 10 출력 가능

 

동일하게 동작하는 코드의 모습

- this를 지우고 오른쪽에 언더바(_)를 추가함.

- 왼쪽 this는 전역변수 left, right를 의미, 오른쪽은 지역변수 left, roght 의미

- 오른쪽에 언더바(_)를 추가하면 전역변수와 지역변수가 다른 문자가 되기 떄문에 프로그램에서 전역변수를 자동으로 찾을 수 있어서 this를 지워도 되는 것.

 

교훈

- 유효범위란 변수를 전역변수, 지역변수 나눠서 좀 더 관리하기 편리하도록 한 것이다. 

- 객체라는 개념이 존재하지 않는 절차지향 프로그래밍에서는 모든 메소드에서 접근이 가능한 변수의 사용을 죄악시하는 경향이 있다. 

- 전역적인 사용의 효용이 분명한 데이터에 한해서 제한적으로 전역변수를 사용하도록 하고 있는 것이다. 객체지향 프로그래밍은 바로 이런 문제를 극복하기 위한 노력이라도고 볼 수 있다.

- 즉 연관된 변수와 메소드를 그룹핑 할 수 있도록 함으로서 좀 더 마음놓고 객체 안에서 전역변수를 사용할 수 있도록 한 것이다. 전역변수는 더 이상 죄악시할 대상이 아닌 것이 된다.  

- 부품의 관점에서도 생각해볼 수 있다. 어떤 메소드가 전역변수를 사용하고 있다는 것은 그 메소드는 그 전역변수에 의존한다는 의미다. 전역변수에 의존한다는 것은 이 메소드가 다른 완제품의 부품으로서 사용될 수 없다는 의미다.

- 객체지향 프로그래밍에서도 가급적이면 전역변수의 사용을 자제하는 것이 좋고, 동시에 단일 객체가 너무 비대해지지 않도록 적절하게 규모를 쪼개는 것도 중요하다.

 

생성자

- 어떤 일을 시작하기 전에 준비를 하게 되는데 이것을 다른 말로 초기화라고 한다. (출근 후 책상정리, 컴퓨터 부팅)

- 객체 지향 프로그래밍도 초기화에 해당하는 기능이 제공되는데 이것을 생성자(constructor)라고 한다.

 

Calculator c1 = new Calculator();
c1.setOprands(10, 20);
c1.sum();       
c1.avg();   

c1.setOprands()로 인해 입력값을 부여하여 결과값 정상 출력

 

Calculator c1 = new Calculator();
c1.sum();       
c1.avg();

c1.setOprands()가 없어서 결과값 출력 안됨.

 

생성자 덕분에 Calculator 객체를 사용하기 위해서 사실상 반드시 필요한 작업이라고 할 수 있는 좌항(left)과 우항(right)의 값을 설정하는 과정을 객체 생성 과정에서 강제할 수 있게 되었다.

(Calculator 클래스 안에 같은 이름의 메소드를 추가해서 Calculator 옆에 무조건 left, right 인자를 부여하게끔 강제함.)

(생성자의 역할) 클래스가 생성될 떄 생성자가 먼저 실행되도록 약속. 그 객체가 가장 먼저해야할 일(초기화 작업)을 하게 끔 강제할 수 있음.

 

객체를 생성할 떄 객체가 반드시 해야되는 일이 있다면 별도의 메소드(생성자)를 만들어서 숙지해야되는 사안을 더 줄이고, 반드시 해야되는 일을 놓치지 않도록 해야 함.

 

호출한 Calculator는 클래스가 아닌 생성자를 의미함.

생성자의 특징

  • 값을 반환하지 않는다.
    생성자는 인스턴스를 생성해주는 역할을 하는 특수한 메소드라고 할 수 있다. 그런데 반환 값이 있다면 엉뚱한 객체가 생성될 것이다. 따라서 반환 값을 필요로 하는 작업에서는 생성자를 사용하지 않는다. 반환 값이 없기 때문에 return도 사용하지 않고, 반환 값을 메소드 정의에 포함시키지도 않는다.
  • 생성자의 이름은 클래스의 이름과 동일하다.
    자바에서 클래스의 이름과 동일한 메소드는 생성자로 사용하기로 약속되어 있다.

 

 상속 (1/3) : 상속의개념

- 객체지향을 통해서 달성하고자 하는 목표 중에서 가장 중요한 것은 재활용성일 것이다. 상속은 객체지향의 재활용성을 극대화시킨 프로그래밍 기법이라고 할 수 있다. 동시에 객체지향을 복잡하게 하는 주요 원인이라고도 할 수 있다.

- 상속(Inheritance)이란 물려준다는 의미다. 어떤 객체가 있을 때 그 객체의 필드(변수)와 메소드를 다른 객체가 물려 받을 수 있는 기능을 상속이라고 한다. 

 

Calculator c1 = new Calculator();
c1.setOprands(10, 20);
c1.sum();
c1.avg(); 

이 코드에 c1.substract(); 빼기 기능을 추가하고 싶다.

 

아래와 같은 경우에 속한다면 객체에 메소드를 추가하는 것이 어렵다.

  1. 객체를 자신이 만들지 않았다. 그래서 소스를 변경할 수 없다. 변경 할 수 있다고 해도 원 소스를 업데이트 하면 메소드 substarct이 사라진다. 이러한 문제가 일어나지 않게 하기 위해서는 지속적으로 코드를 관리해야 한다.
  2. 객체가 다양한 곳에서 활용되고 있는데 메소드를 추가하면 다른 곳에서는 불필요한 기능이 포함될 수 있다. 이것은 자연스럽게 객체를 사용하는 입장에서 몰라도 되는 것까지 알아야 하는 문제가 된다.

기존의 객체를 그대로 유지하면서 어떤 기능을 추가하는 방법이 없을까? 이런 맥락에서 등장하는 것이 상속이다. 즉 기존의 객체를 수정하지 않으면서 새로운 객체가 기존의 객체를 기반으로 만들어지게 되는 것이다. 이때 기존의 객체는 기능을 물려준다는 의미에서 부모 객체가 되고 새로운 객체는 기존 객체의 기능을 물려받는다는 의미에서 자식 객체가 된다. 

 

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

 

상속 (2/3) : 코드로 알아보는 상속

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 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();

새로운 클래스인 SubstractionableCalculator을 정의했다. 이 클래스의 본체에는 sbstract라는 메소드만이 존재한다.

 

- extends Calculator = 클래스 Calculator를 상속 받는다는 의미

- SubstaractableCalculator는 Calculator에서 정의한 메소드 setOprands, sub, avg를 사용할 수 있게됨.

- 상속을 통해서 코드의 중복을 제거할 수 있었고, 또 부모 클래스을 개선하면 이를 상속받고 있는 모든 자식 클래스들에게 그 혜택이 자동으로 돌아간다. 다시 말해서 유지보수가 편리해진다는 것이다.

 

 (3/3) : 다양한 종류의 상속

 

클래스 다이어그램 : 클래스와 클래스의 관계를 그림으로 표현한 것.

SC = SubstractionableCalculator (뺄셈이 가능한 계산기)

MC = MultiplicationableCalculator (곱하기 가능한 계산기)

DC = DivisionableCalculator (나눗셈이 가능한 계산기)

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();

상속을 통해 코드 중복의 제거 가능, 가독성의 증가, 유지보수의 편의성, 재활용성의 증대

각자 목적은 다르지만 함께 하는 관계.

상속과 생성자 (1/2) : 기본 생성자

 

  public ConstructorDemo(int param1) {}

위처럼 매개변수가 있는 생성자가 있을 때는 자동으로 기본 생성자를 만들어주지 않는다.

 

따라서 위의 예제는 존재하지 않는 생성자를 호출하고 있다. 이 문제를 해결하기 위해서는 아래와 같이 기본 생성자를 추가해줘야 한다.

 

 

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

 

클래스 밑에 인자가 없는 기본 생성자를 추가해주면 에러가 발생하지 않음.

 

상속과 생성자 (2/2) : super

 

class Calculator {
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }

클래스 Calculator가 메소드 setOprands가 아니라 생성자를 통해서 left, right 값을 설정하도록 하고 싶다면

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;
    }
 
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    Implicit super constructor Calculator() is undefined. Must explicitly invoke another constructor

그러면 이런 오류가 나타난다. 상위 클래스인 Calculator의 생성자가 존재하지 않는다는 의미다. 하위 클래스가 호출될 때 자동으로 상위 클래스의 기본 생성자를 호출하게 된다. 그런데 상위 클래스에 매개변수가 있는 생성자가 있다면 자바는 자동으로 상위 클래스의 기본 생성자를 만들어주지 않는다. 따라서 존재하지 않는 생성자를 호출하게 되기 때문에 에러가 발생했다. 

 

class Calculator {
    int left, right;
     
    public Calculator(){
         
    }
     
    public Calculator(int left, int right){
        this.left = left;
        this.right = right;

이 문제를 해결하기 위해서는 위와 같이 상위 클래스에 기본 생성자를 추가하면 된다.

 

class SubstractionableCalculator extends Calculator {
    public SubstractionableCalculator(int left, int right) {
        super(left, right);
    }
 
    public void substract() {
        System.out.println(this.left - this.right);
    }
}

super 키워드는 부모 클래스를 의미한다. 여기에 ()붙이면 부모 클래스의 생성자를 의미하게 된다. 이렇게 하면 부모 클래스의 기본 생성자가 없어져도 오류가 발생하지 않는다.

 

하위 클래스의 생성자에서 super를 사용할 때 주의할 점은 super가 가장 먼저 나타나야 한다는 점이다. 즉 부모가 초기화되기 전에 자식이 초기화되는 일을 방지하기 위한 정책

 

overriding (1/2)

 

하위 클래스가 부모 클래스의 기본적인 동작방법을 변경할 수 있어야 한다. 이런 맥락에서 도입된 기능이 메소드 오버라이딩(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();
    }
}
class SubstractionableCalculator extends Calculator {
     
    public void sum() {
        System.out.println("실행 결과는 " +(this.left + this.right)+"입니다.");
    }
     
    public void substract() {
        System.out.println(this.left - this.right);
    }
}

- 부모클래스에 동일하게 sum이 선언되어 있지만 실행결과를 수정하기 위해 하위클래스(SubstractionableCalculator)에서 오버라이딩을 통해 다시 sum을 재정의한 것. (하위클래스의 sum 메소드 우선순위를 높게 설정)

 

- 부모클래스에서 어떠한 메소드를 정의하는 것은 상속받고 있는 모든 클래스의 기본적인 동작방법(공통부분)을 정의한다는 것.

- 하위 클래스에서 똑같은 메소드를 정의하는 것은 부모 클래스가 가지고 있는 메소드보다 높은 우선순위로 실행을 설정하는 것.

 

overriding (2/2)

오버라이딩의 조건

 

class Calculator {
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
    
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }

class SubstractionableCalculator extends Calculator {
     
    public int avg() {
        return (this.left + this.right)/2;

상위 클래스에서 정의하고 있는 메소드 avg는 계산 결과를 화면에 출력하고 있다. 그런데 계산 결과를 좀 더 다양하게 사용하기 위해서 메소드 avg가 화면에 결과를 출력하는 대신 계산 결과를 리턴해주기 위해 코드를 수정함.

 

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    The return type is incompatible with Calculator.avg()

 

overriding을 하기 위해서는 메소드의 리턴 형식이 같아야 한다. 즉 클래스 Calculator의 메소드 avg는 리턴 타입이 void이다. 그런데 이것을 상속한 클래스 SubstractionableCalculator의 리턴 타입은 int이다.

오버라이딩을 하기 위해서는 아래의 조건을 충족시켜야 한다.

  • 메소드의 이름
  • 메소드 매개변수의 숫자와 데이터 타입 그리고 순서
  • 메소드의 리턴 타입

위와 같이 메소드의 형태를 정의하는 사항들을 통털어서 메소드의 서명(signature)라고 한다. 즉 위의 에러는 메소드들 간의 서명이 달라서 발생한 문제다. 아래와 같이 상위 클래스의 코드를 변경해서 이 문제를 우회하자.

 class Calculator {
    int left, right;
 
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = 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 int avg() {
        return ((this.left + this.right) / 2);

- 메소드 avg의 부모와 자식 클래스가 같은 로직을 가지고 있다. 중복은 제거 되어야 한다. 생성자와 마찬가지로 super를 사용하면 이 문제를 해결 할 수 있다

 

class SubstractionableCalculator extends Calculator {

public int avg() {
        return super.avg();

 

 

overloading (1/2)

계산기 예제로 돌아가보자. 우리의 계산기는 2개의 값(left, right)에 대한 연산(sum, avg) 만을 수행 할 수 있다. 그런데 만약 3개의 값을 대상으로 연산을 해야 한다면 어떻게 해야할까? 우선 아래와 같이 입력값을 3개 받아야 할 것이다. 

 

c1.setOprands(10, 20, 30);
class Calculator{
    int left, right;
    int third = 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();

 

아래는 기본 예제와의 차이점이다.

 

    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;

 

- 이를 통해서 알 수 있는 것은 매개변수의 숫자에 따라서 같은 이름의, 서로 다른 메소드를 호출하고 있다는 것을 알 수 있다. 

- 이름은 같지만 시그니처는 다른 메소드를 중복으로 선언 할 수 있는 방법을 메소드 오버로딩(overloading)이라고 한다.

- 같은 이름, 하지만 다른 매개변수인 경우에 동일한 이름의 메소드를 여러개 정의하는 것. = 오버로딩 

 

    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;

- 위 코드 변수 left,right 중복의 제거

 

overloading (2/2)
결론적으로 말하면 메소드 오버로딩은 매개변수를 사용한다. 즉 매개변수가 다르면 이름이 같아도 서로 다른 메소드가 되는 것이다. 반면에 매개변수는 같지만 리턴타입이 다르면 오류가 발생한다. 아래의 코드를 보자.

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");
    }
}

3행과 4행의 메소드 A는 매개변수의 숫자가 다르다. 4행과 5행의 메소드 A는 인자의 숫자는 같지만 매개변수의 데이터 타입이 다르다. 이런 경우는 오버로딩이 가능하다. 메소드를 호출 할 때 전달되는 인자의 데이터 타입에 따라서 어떤 메소드를 호출할지를 자바가 판단 할 수 있기 때문이다. 하지만 메소드의 반환값은 메소드를 호출하는 시점에서 전달되지 않는 정보이기 때문에 오버로딩의 대상이 될 수 없다.

public class OverloadingDemo2 extends OverloadingDemo{
    void A (String arg1, String arg2){System.out.println("sub class : void A (String arg1, String arg2)");}
    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");
        
//결과값은
sub class : void A ()
void A (int arg1)
void A (String arg1)
sub class : void A (String arg1, String arg2)

클래스 OverloadingDemo2는 OverloadingDemo을 상속 받고 있다.

- OverloadingDemo2의 3행에서 정의된 메소드 A는 문자열을 데이터타입으로 하는 두개의 매개변수를 가지고 있다.

이러한 형태의 변수는 부모 클래스에서는 정의되어 있지 않기 때문에 메소드 오버로딩이 되는 것이다.

 

- 반면에 4행에서 정의된 메소드 A는 매개변수가 없다. 그리고 부모 클래스에는 이미 매개변수가 없는 메소드 A가 존재한다. 이 둘은 매개변수의 형태가 같기 때문에 오버로딩이 아니라 오버라이딩에 해당한다.

 

overriding VS overloading
오버라이딩과 오버로딩은 용어가 참으로 헷갈린다. 당연하다. 중요한 것은 오버라이딩이 무엇이고 오버로딩이 무엇인가를 구분하는 것은 아니다.

 

- riding(올라탄다)을 이용해서 부모 클래스의 메소드의 동작방법을 변경하고, - loading을 이용해서 같은 이름, 다른 매개변수의 메소드들을 여러개 만들 수 있다는 사실을 아는 것이 중요하다.

 

다만 학습이나 협업의 과정에서 개념을 주고 받을 때는 용어가 중요해진다. 필자의 생각에 이 개념들이 헷갈리는 이유는 over라는 공통분모 때문이다. over를 제외하고 알아두면 덜 헷갈리지 않을까 싶다. (참고로 overriding는 재정의라는 사전적인 의미가 있습니다.)

 

오버로딩 : 부모클래스와 자식클래스가 있을때, 부모클래스의 메소드를 메소드의 이름과 형식, 리턴타입이 동일한 (시그니처, 서명이 같은) 메소드를 자식 클래스에서 새롭게 동작방법을 재정의 하는 것.

 

클래스패스 (1/3) : 컴파일과 클래스

class Item2{
    public void print(){
        System.out.println("Hello world");  
    }
}
 
class ClasspathDemo2 {
    public static void main(String[] args){
        Item2 i1 = new Item2();
        i1.print();
    }
}

메모장에 코드를 붙여놓고 cmd에서 자바 컴파일을 하려고 했는데 안됨. 

 

- Item2와 ClasspathDemo2의 클래스파일이 폴더에 생성되었을때

- 현재 디렉터리 하위에 lib폴더를 만들고 여기에 Item2.class 파일을 이동한다.

- 현재 디렉터리에는 Item2.class 파일이 없어야 한다. 그리고 ClasspathDemo2를 실행한다.

F:\dropbox\생활코딩\실습\java_tutorials\srcbin>java ClasspathDemo2
Exception in thread "main" java.lang.NoClassDefFoundError: Item2
        at ClasspathDemo2.main(ClasspathDemo2.java:9)
Caused by: java.lang.ClassNotFoundException: Item2
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)

그러면 이러한 에러가 발생하게 된다.

이것은 item.class 파일이 현재 디렉터리에 존재하지 않기 때문에 찾을 수 없다는 메시지다.

java -classpath ".;lib" ClasspathDemo2

// java -classpath ".:lib" ClasspathDemo2
// 리눅스, OSX와 같은 유닉스 계열의 시스템이라면 아래와 같이 콜론을 사용해야 한다.

 

- 옵션 -classpath는 자바를 실행할 때 사용할 클래스들의 위치를 가상머신에게 알려주는 역할을 한다.

- 점(.)은 현재 디렉토리에서 클래스를 찾는다는 뜻

- 세미콜론(;)은 경고롸 경로를 구분해주는 구분자

- lib 은 현재 디렉터리에 없다면 현재 디렉터리의 하위 디렉터리 중 lib에서 클래스를 찾는다는 의미다.

- 클래스 패스라는 것은 자바를 실행할 때 클래스의 위치를 지정하는 역할을 하는 것이다. 

- 클래스 패스는 자바 애플리케이션이 사용하고 있는 클래스가 여러 경로에 분산되어 있을 때 유용하게 사용할 수 있는 방법이다.

 클래스패스 (3/3) : 환경변수

환경변수

- 자바 애플리케이션을 실행할 때 결국은 클래스를 사용하게 되는데

- 클래스가 컴퓨터상에 어디에 위치하는가를 지정하는 것이 환경변수

 

필요한 이유

- 프로그램을 만들 떄 처음부터 끝까지 혼자서 만드는 경우는 없다.
- 다른 사람이 만든 로직을 자신이 만든 프로그램으로 가져오는 방식을 사용하여 프로그램을 만들게 됨.

- 다른 사람이 만든 로직(클래스)를 자신이 만들고 있는 클래스로 가져오기 위해서는 경로를 지정할 필요가 있고,

여러 개의 애플리케이션이 공통된 로직을 사용하고 있는 경우에도 각각의 애플리케이션이 공통된 클래스를 읽어오기 위해서는 경로를 지정할 필요가 있다.

 

- 실행할 떄마다 클래스패스를 지정하는 것은 번거롭기 때문에 환경변수를 이용해서 클래스패스를 지정하는 것

 

- 환경변수는 운영체제에 지정하는 변수로 자바 가상머신과 같은 애플리케이션들은 환경변수의 값을 참고해서 동작하게 된다.

- 자바는 클래스 패스로 환경변수 CLASSPATH를 사용하는데 이 값을 지정하면 실행할 때마다 -classpath 옵션을 사용하지 않아도 되기 때문에 편리하다.

- 하지만 운영체제를 변경하면 클래스 패스가 사라지기 때문에 이식성면에서 불리할 수 있다.

 

- 상수, 변수, 연산자, 조건문, 반복문을 가장 기본적인 프로그래밍의 수단이라고 생각한다. 다시 말해서 로직 자체라고 생각 할 수 있다.

- 반면에 메소드, 유효범위, 클래스, 클래스 패스와 같은 것들을 관통하는 공통분모는 로직의 체계적인 관리수단이라고 할 수 있다. 

 

환경변수 지정하는 방법

내pc - 속성 - 시스템 - 고급시스템 설정 - 고급 - 환경설정 - 새로만들기 - (전체경로지정하기)

-> 올바르게 설정 시 

-> java -classpath ".;lib" ClasspathDemo2 (클래스패스 지정할 필요 없이)

-> java ClasspathDemo2 (쉽게 이름만 입력해서 실행할 수 있음.)

 

패키지 (1/4) : 패키지의개념

- 패키지(Package)는 하나의 클래스 안에서 같은 이름의 클래스들을 사용하기 위한 방법이라고 할 수 있다.

 

- 서로 다른 내용의 파일 java.txt가 하나의 컴퓨터에 동시에 공존할 수는 없다. 그래서 고안된 것이 디렉토리다. java.txt 파일을 각각 a와 b라는 디렉터리에 저장한다면 하나의 컴퓨터 안에 같은 이름의 파일이 공존할 수 있게 된다. 누군가에게 'a 디렉터리에 있는 java.txt'를 이메일로 보내달라고 요청할 수 있게 되는 것이다.

 

패키지도 이와 유사하다. 클래스가 많아짐에 따라서 같은 이름을 가진 클래스가 생겨날 가능성이 높아지게 되는데 이름의 충돌을 방지하기 위한 고안된 것이 패키지라고 할 수 있다.

 

정보 공학에서는 '이름의 충돌'이라는 문제를 해결하기 위해서 다양한 노력을 하고 있다.

전역변수와 지역변수, 객체도 그런 연장선에 있다고 볼 수 있다.

 

패키지 (2/4) : 패키지사용

- 패키지 이름과 파일 경로가 일치하는 것을 확인할 수 있음.
-> 패키지는 일종의 디렉토리의 개념이라고 볼 수 있다는 것.

 

패키지의 이름은 조직이나 개인이 소속되어있는 도메인의 이름을 사용하는 경우가 일반적 (중복될 일이 없기 때문)

 

- example2 패키지에는 B,C 파일이 있다.

- B클래스가 A를 사용하려고 하지만 example2 패키지 안에는 A파일이 없어서 실행이 안되는 상황.

- 다른 패키지에 있는 클래스를 로드하는 것은 불가능하다.

- 같은 패키지에 있는 클래스만 로드할 수 있다.

 

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

서로 다른 패키지에 있는 클래스를 가져오려면 import를 통해서 다른 패키지의 클래스를 현재의 소스코드로 불러와야 한다. 

특정 패키지에 있는 모든 클래스를 로드하고 싶다면 위와 같이 하면 된다.

*는 '모든'이라는 뜻이다.

import org.opentutorials.javatutorials.packages.example1.*;
//패키지 안 모든 클래스 로드

import org.opentutorials.javatutorials.packages.example1.A;
//패키지 안 A파일만 로드

패키지 (3/4) : 손컴파일

bin (binary) : 이진수, 컴퓨터(virtual machine)가 이해할 수 있는 형태의 언어, 컴파일된 클래스 파일이 들어있다.

src : 사람이 이해할 수 있는 코드. 소스 코드가 들어있다.

 

javac src/org/opentutorials/javatutorials/packages/example3/*.java -d bin

패키지 (4/4) : 로드된 패키지들간의 중복

만약 import 한 패키지 안에 같은 이름의 클래스가 존재하고 이 클래스를 사용하고 싶다면 어떤 문제가 발생할까? 아래 코드는 import 하고 있는 두개의 패키지에 클래스 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) {
        B b = new B();
    }
}
Exception in thread "main" java.lang.Error: Unresolved compilation problems: 
    The type B is ambiguous
    The type B is ambiguous

- 클래스 B의 이름이 중복되기 때문에 애매함(ambiguous)의 문제가 발생한다.

 

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();
    }
}

- 정확하게 어떤 클래스를 로드하고 싶은지 지정해서 오류를 해결한 것.

 

API와 API문서 (1/2) : API

API와 API문서 (1/2) : API

System.out.println(1);

- 문법적으로 봤을 때 println은 메소드가 틀림없다. 그런데 메소드 앞에 Sytem.out이 있다.

- System은 클래스이고 out은 그 클래스의 필드(변수)이다.

- 이 필드가 메소드를 가지고 있는 것은 이 필드 역시 객체라는 것을 알 수 있다. 

- System을 인스턴스화한적이 없음에도 불구하고 필드 out에 접근할 수 있는 것은 out이 static이라는 것을 암시한다.

패키지 java.lang은 자바 프로그래밍을 하기 위해서 필수적인 클래스들을 모아둔 패키지다. 따라서 사용자의 편의를 위해서 자동으로 로딩을 하고 있는 것이다.

 

UI와API

 

UI(User Interface)

- 사람과 사물 또는 시스템, 특히 기계, 컴퓨터 프로그램 등 사이에서 의사소통을 할 수 있도록 일시적 또는 영구적인 접근을 목적으로 만들어진 물리적, 가상적 매개체

- 입력장치, 출력장치, 중개자에 해당하는 것. 사용자를 대면하는 접점이 되는 지점

- 사용자 인터페이스는 사람들이 컴퓨터와 상호 작용하는 시스템

 

API(Application Programming Interface)

- 응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스

- 주로 파일 제어, 창 제어, 화상 처리, 문자 제어 등을 위한 인터페이스를 제공한다. 

API와 API문서 (2/2) : API 문서 보는법

프로그램을 만든다는 것 : 자바 플랫폼(프로그램언어)을 API를 이용해서 제어하는 것.

- 메소드의 시그니쳐가 다른 경우에 같은 이름으로 다른 동작을 하는 메소드를 사용할 수 있는 기능(오버로딩)

- 오버로딩을 통해 매개변수의 데이터타입을 정의 해놓은 것.

- API 문서를 자주 사용하면서 필요한 정보를 쉽게 찾을 수 있는 능력을 키워야 함. 

 

접근 제어자 2 : 문법

접근 제어자는 클래스의 맴버(변수와 메소드)들의 접근 권한을 지정한다. 

System.out.println(a.y());
// 코드가 정상 실행된다.

System.out.println(a.z());
// 오류를 발생시킨다.
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    The method z() from the type A is not visible
    at org.opentutorials.javatutorials.accessmodifier.AccessDemo1.main(AccessDemo1.java:15)

메소드 z에 접근 할 수 없다는 의미다. 

private String z(){
    return "public void z()";
}

- 메소드가 키워드 private으로 시작되고 있다. private은 클래스(A) 밖에서는 접근 할 수 없다는 의미다.

- 바로 이 private의 자리에 오는 것들을 접근 제어자(access modifier)라고 한다. 

- 사용할 수 없는 메소드를 왜 정의하고 있는 것일까? 내부적으로 사용하기 위해서다. 

public String x(){
    return z();
}

- 접근 제어자가 public이기 때문에 호출 할 수 있다.

- 메소드의 내용을 보면 내부적으로 메소드 z를 호출하고 있다.

- 메소드 z는 정상적으로 호출된다.  메소드 x와 메소드 z는 같은 클래스의 소속이기 때문이다.

- 따라서 메소드 x에서 z를 호출 할 수 있는 것이다. (직접적으로 호출은 안됨.)

 

접근제어자 3 : 사용이유

에플리케이션이 커진다는 것은 다른 말로 망가질 확률이 커진다는 의미와 같다.

특히 로직이 망가지는 첫번째 용의자는 사용자다. 즉 객체를 사용하는 입장에서 객체 내부적으로 사용하는 변수나 메소드에 접근함으로서 개발자가 의도하지 못한 오동작을 일으키게 되는 것이다.

 

- 이런 문제로부터 객체의 로직을 보호하기 위해서는 맴버에 따라서 외부의 접근을 허용하거나 차단해야 할 필요가 생긴다. 마치 은행이 누구나 접근 할 수 있는 창구와 관계자외에는 출입이 엄격하게 통제되는 금고를 구분하고 있는 이유와 같다.

- 접근 제어자를 사용하는 또 다른 이유는 사용자에게 객체를 조작 할 수 있는 수단만을 제공함으로서 결과적으로 객체의 사용에 집중 할 수 있도록 돕기 위함이다.

 

package org.opentutorials.javatutorials.accessmodifier;
 
class Calculator{
    private int left, right;
     
    public void setOprands(int left, int right){
        this.left = left;
        this.right = right;
    }
    private int _sum(){
        return this.left+this.right;
    }
    public void sumDecoPlus(){
        System.out.println("++++"+_sum()+"++++");
    }
    public void sumDecoMinus(){
        System.out.println("----"+_sum()+"----");
    }
}
  
public class CalculatorDemo {
    public static void main(String[] args) {        
        Calculator c1 = new Calculator();
        c1.setOprands(10, 20);
        c1.sumDecoPlus();
        c1.sumDecoMinus();
    }
}

 

//실행결과
++++30++++
----30----

'Java > 생활코딩' 카테고리의 다른 글

생활코딩 자바입문 (나의앱 만들기)  (0) 2021.05.10
생활코딩 자바 입문 리뉴얼  (0) 2021.05.10
생활코딩 - 자바입문 (시즌1)  (0) 2021.05.02