점프투자바

SUADI·2022년 5월 2일

공부를 하루 안하다보니 이런저런 핑계를 대다보니 2주동안이나 공부를 하지 못했다. 영어 회화도 따로 공부해서 shadowing 영상을 유튜브에 업로드 했었는데 영상 업로드 한지도 꽤 오래되었다. 처음 영어랑 자바 공부할 때 각각 1일 1업로드를 목표로 시작했는데 어느순간 포기해 버렸다. 포기를 했다고 마음이 편했냐? 그것도 아니다. 마음 한켠에 해야할 일을 하지 못하는 것에 대한 죄책감이 계속 나를 괴롭혔다. 포기하거나, 꾸준히 하거나. 그래야 내 마음이 편해질 것 같다. 근데 포기하는건 안된다. 무조건 개발쪽으로 이직할 거고, 외국인 친구도 사귀고 싶다. 적어도 몇 개월 정도는 매일 포스팅해내고 싶다. TIL 시작한다.

6. 객체지향 프로그래밍

(1) OOP란?

객체지향 프로그래밍(OOP, Object Oriented Programming)이란 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 서로 독립적으로 데이터를 주고받고, 처리할 수 있다.
예를 들어 자바로 계산기 프로그램을 만든다고 할 때,
3 + 4 = 7 , 7 + 3 = 10 과 같이 계산기는 이 전에 계산한 결과값을 기억하고 있어야 한다는 점을 생각하고 코드를 짜면,

class Calculator {
 	static int result = 0;
    
    static int add(int num) {
    	result += num;
        return result;
    }
}
 
public class Sample {
	public static void main(String[] args) {
 		System.out.println(Calculator.add(3)); 		
        System.out.println(Calculator.add(4));

    }
}

여기서 add 메소드는 매개변수 num으로 받은 값을 이전에 받았던 결과값에 더한후 돌려주는 메소드이다. 이전에 계산한 결과값을 유지하기 위해서 result 전역변수(static)을 사용하였다.

만약 2개의 계산기 프로그램을 만들려면 어떻게 해야할까? 이전에 계산한 결과값을 유지하려면 calculator 클래스가 두개 있어야 할 것이다.

class Calculator1 {
	static int result = 0;
    
    static int add(int num) {
    	result += num;
        return result;
    }
}

class Calculator2 {
	static int result = 0;
    
    static int add(int num) {
    	result += num;
        return result;
    }
}

public class Sample {
	public staitc void main(String[] args) {
    	System.out.println(Calculator1.add(3));
        System.out.println(Calculator1.add(4));
        
    	System.out.println(Calculator2.add(3));
    	System.out.println(Calculator2.add(7));
    }
}

서로 다른 클래스를 생성했기 때문에 각각의 계산기가 결과에 영향을 미치지 않는다. 하지만 계산기를 여러개 만들어야 한다고 하면 이렇게 반복된 줄을 사용하는 것이 과연 효율적일까? 이럴 때 객체를 사용하면 된다.

class Calculator {
	int result = 0;
    int add(int num) {
    	result += num;
        rerurn result;
    }
}

public class Sample {
	public static void main(String[] args) {
		Calculator cal1 = new Calculator();
		Calculator cal2 = new Calculator();

		Systwm.out.println(cal1.add(3));			
        Systwm.out.println(cal1.add(4));
		Systwm.out.println(cal1.add(3));
		Systwm.out.println(cal1.add(7));
    }
}

각각의 객체 cal1, cal2는 독립적으로 역할을 수행한다.

(2) 클래스

ㄱ) 객체

class Animal {
}

public class Sample {
	public static void main(String[] args) {
    	Animal dog = new Animal();
        Animal cat = new Animal();
    }
}
  • 보통 클래스는 새로 자바파일(.java)을 생성한 후 단독으로 작성하는 것이 일반적인 방법이지만 설명을 위해서 한 파일(Sample.java)에 모두 작성하였다.

  • Animal 클래스의 내용은 없고 선언만 할 수 있는 클래스이다.
    이 클래스를 사용하여 새로운 객체를 만들 수 있다.

  • main메소드에서 new 키워드를 사용해 원하는 이름의 객체를 생성할 수 있다. 여기서 dog는 Animal 클래스의 인스턴스이다.

  • 객체와 인스턴스의 비슷하지만 다르다. 인스턴스란 특정 개체가 어떤 클래스의 객체인지를 관계위주로 나타낼 때 쓰는 표현이다.
    (dog는 객체, dog는 Animal의 인스턴스)

빈 껍데기였던 클래스에 이름을 설정할 수 있는 내용을 추가해 보자.

ㄴ) 객체변수

class animal {
	String name;
}

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

클래스 내에 name이라는 String 변수를 추가했다. 클래스에 선언된 이 변수를 객체변수라고 한다. 다른 클래스로 객체변수를 불러올 수 있는 방법은

객체.객체변수;
dog.name;

객체변수에 아무것도 대입하지 않았을 때 출력해보면 null값이 나온다.

ㄷ) 메소드

클래스에는 객체변수와 더불어 메소드라는 것이 있다. 메소드는 파이썬으로 치면 함수와 비슷한 개념이다. 메소드를 이용해서 객체변수에 값을 대입해 보자.

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

public class Sample {
	public static void main(String[] args) {
    	Animal dog = new Animal();
        dog.setName("Hanuel");
        System.out.println(dog.name);        
    }
}
  • 객체에 이름을 저장할 수 있는 기능을 가진 setName이라는 메소드를 생성했다. 여기서 this의 의미는 Animal 클래스에 의해서 생성된 객체를 의미한다. 여기서 this의 의미는 새로 생성된 객체 dog와 같다.
  • 다른 클래스로 메소드를 불러오는 방법은
    객체.메소드;
    dog.setName();
  • 위의 코드에서 객체를 하나 더 생성하면
Animal cat = new Animal();
cat.setName("Tom");
System.out.println("cat.name");

총 두개의 객체(dog,cat)이 생성된다. 그 각각의 객체변수에 값을 집어넣었을 경우에 OOP의 특성상 각각의 객체는 독립적으로 작용하기 때문에 값이 변경되거나 하지 않고

Hanuel
Tom

으로 출력이 된다.

(3) 메소드

ㄱ) 메소드 사용 이유

코드를 작성하다 보면 똑같은 내용을 반복적으로 적어야 하는 경우가 생기는데 그럴때 반복적인 내용을 한 뭉치로 묶어서 "어떤 입력값을 주었을 때 특정 리턴값을 돌려준다"라는 식으로 코드를 작성하면 반복적으로 적지 않아도 된다. 이러한 장치를 메소드라고 한다.
예를 들어 반복적인 사칙연산을 수행하는 계산기를 프로그래밍하면

public class Sample {
	int sum(int a, int b) { // a,b는 매개변수
    	return a+b;
    }
    
    public static void main(String[] args) {
    	int a = 3;  // a=3, b=4는 인수
        int b = 4;
        
        Samples sample = new Sample();
        int c = sample.sum(a,b);
        System.out.println(c); // 7출력
    }
}
  • sum 이라는 메소드를 생성하여 두 개의 입력값을 받아 두개를 더해서 리턴하는 반복을 처리한다.

  • Sample 클래스의 main메소드에 자기 자신의 객체를 생성했다.

Sample sample = new Sample();

작성한 클래스를 단독으로 실행시켜 테스트해보기 위함이다.

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

ㄴ) 메소드의 구조

리턴자료형 메소드명(입력자료형1 매개변수1, 입력자료형2, 매개변수2...) {
	...
    return 리턴값; // return 자료형이 void인 경우 return 없다.
}
  • 입력과 출력이 모두 있는 경우(일반적)
int add(int a, int b) {
	return a+b;
}
리턴값 받을 함수 = 객체.메소드명(입력인수1, 입력인수2..);
  • 입력이 없는 경우
String say() {
	return "hi";
}

public class Sample{
	public static void main(String[] args) {
 		Sample sample = new Sample();
        String c = sample.say();
        System.out.println(c); // Hi 출력
    }
}
리턴값 받을 함수 = 객체.메소드;
  • 리턴값이 없는 경우
void sum(int a, int b) {
	System.out.printf("%d와 %d의 합은 %d이다.",a,b,a+b);
}

public class Sample{
	public static void main(String[] args) {
 		Sample sample = new Sample();
        sample.sum(3,4); // 3과 4의 합은 7이다.
    }
}
객체.메소드(입력인수1, 입력인수2...);
  • 입력,리턴값 모두 없는 경우
void say() {
	System.out.println("Hi");
}
public class Sample{
	public static void main(String[] args) {
 		Sample sample = new Sample();
        sample.say(); // Hi
    }
}
객체.매소드;

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

메소드 안에서의 변수와 밖에서의 변수의 이름을 동일하게 한다면 어떻게 될까?

public class Sample {
    void varTest(int a) {
        a++;
    }

    public static void main(String[] args) {
        int a = 1;
        Sample sample = new Sample();
        sample.varTest(a);
        System.out.println(a); // 1 출력
    }
}
  • 마지막의 a 값으로 2가 출력될 것 같지만 main 메소드에서 선언한 a = 1이 출력된다. varTest 메소드의 영향은 전혀 없다. 오해가 생길 수 있는 경우가 몇가지 있는데 첫번째는 varTest의 변수와 메소드 밖에서의 변수의 이름이 a로 같기 때문에 a++로 증가할 것 같지만 varTest의 a(매개변수)와 main메소드에서의 a(인자)는 다르다. 두번째는 두개가 같다고 하여도 varTest메소드에서의 return값이 없기 때문에 메인메소드에서 a를 출력하더라도 똑같이 1이 출력된다.

  • 메소드에서 쓰이는 변수는 메소드 밖에서 쓰이는 변수의 이름과 전혀 상관이 없다. 이렇게 메소드 내에서만 쓰이는 변수를 로컬변수라고도 말한다.

마지막 a의 값이 2가 출력되도록 코드를 짜면

public class Sample {
	int varTest(int a) {
    	a++;
        return a;
    }
    
    public static void main(String[] args) {
    	int a = 1;
        Sample sample = new Sample();
        a = sample.varTest(a);
        System.out.println(a); // 2 출력
    }
}

varTest에서 1만큼 증가시킨 값을 리턴하고, main메소드에서 a값에 varTest를 거친 값을 덮어씌워주면 2가 출력된다.

[이해 안가는 부분]
만약 메소드 내의 입력값이 int 자료형이 아니라 객체라면 어떻게 될까? 객체를 메소드의 입력값으로 넘기고 메소드가 객체의 속성값(객체변수값)을 변경한다면 수행 후에도 객체는 변경된 값을 유지하게 된다. 이러한 차이가 나는 이유는 메소드의 입력값으로 값을 전달하느냐, 객체를 전달하느냐에 따라 차이가 난다.

public class Sample {
	int a;
    
    void varTest(Sample sample) {
    	sample.a++;
    }
    
    public static void main(String[] args) {
    	Sample sample = new Sample();
        sample.a = 1;
        sample.varTest(sample);
        System.out.println(sample.a); // 2 출력
    }
}
  • main 메소드에 a를 선언했던 것과는 달리 이번엔 Sample 클래스의 객체변수 a로 선언했다.
  • varTest 메소드에서는 Sample 클래스의 객체 sample을 입력으로 받아 해당 객체의 객체변수 a 값을 1만큼 증가시키도록 했다.
  • main 메소드에서 varTest 메소드에 a=1을 전달했던 것과는 달리 sample 객체를 전달하였다.
  • 이렇게 프로그램을 수정시키면 출력값으로 2가 출력된다.

ㄹ) call by value

메소드에 값(primitive type)을 전달하는 것과 객체(reference type)을 전달하는 것은 차이가 있다고 앞서 언급했다. 메소드에 객체를 전달할 경우 메소드에서 객체의 객체변수 값을 변경할 수 있다.

class Updater {
    void update(int count) {
        count++;
    }
}

class Counter {
    int count = 0;  // 객체변수
}

public class Sample {
    public static void main(String[] args) {
        Counter myCounter = new Counter();
        System.out.println("before update:"+myCounter.count);
        Updater myUpdater = new Updater();
        myUpdater.update(myCounter.count);
        System.out.println("after update:"+myCounter.count);
    }
}
// 결과값
before update : 0
after update : 0
  • Updator 클래스 내에 update 메소드가 있다.
  • Countor 클래스 내에 객체변수 count를 선언했다.
  • java 파일 내에 두 개 이상의 클래스가 있을 시 이름과 같은 클래스 앞에는 public 붙이는 것이 관례이다.
  • before 값은 객체의 객체변수 값을 출력했으므로 당연히 0이다. after 값은 1이 출력될거라고 오해할 수 있다. myCountor 객체 내의 update 메소드를 거쳐서 값이 증가했을거라고 생각할 수 있다. 하지만 위에서 언급했듯이 메소드의 입력값으로 값(int count)이 들어오면 객체변수 값을 변경할 수 없다.

그렇다면 메소드의 입력값을 객체로 변경해서 코드를 다시 짜보면

class Updater {
    void update(Counter counter) {
        counter.count++;
    }
}

class Counter {
    int count = 0;
}

public class Sample {
    public static void main(String[] args) {
        Counter myCounter = new Counter();
        System.out.println("before update : " + myCounter.count);
        Updater myUpdater = new Updater();
        myUpdater.update(myCounter);
        System.out.println("after update : " + myCounter.count);
    }
}
  • update 메소드에서 전엔 int count를 입력값으로 받았다면, 지금은 Counter counter 객체를 입력값으로 받고 있다.
  • update 메소드를 main메소드에서 호출할 때도 객체의 이름(myCounter)을 넣는 방식으로 바뀌었다.
  • 이렇게 메소드에 객체를 전달하면 객체변수의 값이 변경될 수 있다.
// 결과값
before update : 0
after update : 1

0개의 댓글