객체의 상속 관계를 알고 넘어간 지난 시간에 이어 오늘은 '다형성(polumorphism)'에 대해 배웠다. 일단 다형성이 무엇인가에 대한 이야기는 나중으로 미루도록 하고 예제를 보며 설명을 하도록 하겠다.

 

   Lion, Rabbit, Monkey라는 클래스가 Animal이라는 클래스를 상속받은 채 각자 메서드를 가지고 있다. 우리는 이를 main() 영역에서 어떠한 함수를 실행시킬 때마다 해당 동물에게 먹이를 준다는 문구를 작성하고 싶은데 각 객체를 불러오는 과정에서 데이터타입(클래스명)을 입력하다 보니 오버로딩으로 매개변수 타입만 바꾼 함수 세 개를 작성하게 되었다. 불러 올 이름만 다를 뿐 내용은 똑같은데도 같은 내용을 세번이나 쓴 격이다. 지금이야 3개지만 다른 동물이 들어올 때마다 해당 함수가 또 늘어나야 할 텐데 과연 이같은 방법 뿐일까?

 

   그래서 우리는 다형성이라는 것을 배웠다. 이론적으로 본다면 어렵게 되어있었지만 실습을 통해 풀어가자 그 사이의 규칙만 이해한다면 충분히 납득할 수 있는 내용이었다.

A obj = new A();
기존 객체를 구현할 때 쓰는 참조변수 선언 방식이다.
참조변수(obj) 앞에 데이터 타입(클래스명)이 따라오고 대입연산자 뒤로는 new 생성자()를 사용하는 방식

A obj = new B();
다형성을 이용한 객체 생성 방식이다.
이것은 B라는 클래스가 A클래스를 부모로 삼았을 경우에만 가능하다. (extends)

 

   기존에는 데이터 타입 자리에 오는 클래스명과 생성자의 클래스명이 일치해야지만 객체 생성이 가능하다고 알고 있었다. 하지만 상속을 배우고, 부모를 가진 클래스는 부모타입의 참조변수에 담길 수 있는데 이것을 다형성이라고 한다. 위에서 B라는 클래스가 A라는 클래스를 상속받았을 경우, B클래스를 생성하게 되면 A클래스가 자동으로 만들어진다.(A가 기본 생성자만 있는 경우) 이는 B의 생성자 안에 A생성자가 포함된 구조를 가졌기 때문인데, 이렇게 되면 B클래스는 A클래스의 주소를 가지고 있다는 뜻이 되며 이는 곧 A타입의 참조변수에 접속할 수 있다는 뜻이 된다. 하지만 반대로 B타입에 A생성자를 담게되면 어떻게 될까, 당연히 되지 않는다. 부모인 A클래스에는 B라는 생성자를 가지고 있지 않기 때문이다.

 

   다만 B클래스를 A타입에 담았다고 해서 B객체가 원래 지니고 있는 변수나 함수를 불러올 수 있는 건 당연히 아니다. A타입의 참조변수에 담기기 위해서 B가 가지고 있는 A주소를 가져갔기 때문인데 A에서는 B클래스에 접속할 수 없기 때문이다. 그렇다면 굳이 이런 번거로운 방법을 써야하는 이유가 있을까? 그 해답이 바로 처음 나온 예제에 있다.

 

 

   처음 예제에 있던 각각 지정해주었던 부분을, 공통적으로 상속 받았던 Animal로 바꾸어 객체를 선언하고 매개변수를 받음으로써, 함수가 하나지만 각각 실행되어 콘솔창에 나타나게 된다. 하지만 아까는 분명 부모요소가 자식요소가 가지고 있는 변수나 함수는 불러오지 못한다고 하지 않았던가? 그 중, 예외가 있는데 그게 바로 '오버라이딩 된 함수' 이다. 자식이 부모가 가진 함수를 재사용하여 내용을 바꿨을 경우, 부모로 객체를 만들어도 부모가 가진 함수가 아니라 자식이 바꿔둔 함수가 출력되게 된다. 물론 그러기 위해서는 오버라이딩을 할 수 있도록 부모도 자식과 똑같은 이름의 함수를 가지고 있어야한다.

 

   하지만 예제를 확인해보면 Animal이라는 클래스에는 생성자 외의 함수는 존재하지 않는다. 그렇다면 함수를 찾을 수 없다는 에러가 뜨는 것이 정상이지 않을까? 일단 동물들이 가진 함수에 대해 다시 자세히 살펴보도록 하자.

 

 

   toString, 사용자가 직접 입력한 메서드명이 아니라 기존 Object가 가지고 있는 메서드였다. 여기서 Object란 모든 클래스의 조상이 되는 객체인데, 만약 아무 상속도 받지 않은 클래스가 존재할 경우 자동으로 상속받는 클래스이다. 고로, 아무런 상속도 받지 않은 Animal은 자동적으로 Object를 상속받게 되는 것이고 Animal을 상속 받은 다른 동물 클래스들은 자동으로 Object가 가지고 있는 메서드에 접근 할 수 있었던 것이다.

   자바를 배우기 전에 HTML을 잠깐 했었는데, 그 때마다 태그에 관해서 부모와 자식이라는 단어를 많이 사용했었다. 처음엔 이해하기 쉬우라고 예를 들어 설명하는 줄 알았지만 지금까지 배워오니 그 관계를 깨닫는 것이 얼마나 중요한 부분인지를 알게 되었다. 여기서 상속이란, 부모가 자식에게 넘겨주는 그 상속을 일컫는 것이 맞다. 부모가 가지고 있는 기존의 코드나 변수를 그대로 물려줌으로써 불필요한 중복을 피하고, 코드문을 간결하게 만들 수 있다. 여기서 상속을 받는 클래스는 자식 클래스가 되며, 물려주는 클래스는 부모 혹은 조상 클래스가 된다.

클래스를 생성할 때 extends를 사용하여 상속을 할 수 있다. (단, 한 번의 상속만 가능하다. 다중상속 X)
class (자식이 될 클래스 명) extends (부모가 될 클래스 명) {  }

 

 

   Student 클래스와 Teacher 클래스에 exam_40_per 클래스를 상속하자, 메인 함수에서 불러올 때 자식의 참조변수를 통해 불러냈음에도 부모요소가 가지고 있던 함수의 값이 그대로 출력되었다. 부모가 가지고 있는 변수와 함수 모두 자식이 사용할 수 있도록 상속이 일어난 것이다. 하지만 반대의 경우는 호출할 수 없다, 부모는 자식이 가진 함수나 변수를 사용할 수 없다는 의미다. 그렇다면 만약 자식의 클래스에서는 부모의 함수나 변수를 바꿔 출력하고 싶을 경우에는 어떻게 해야할까.

 

 

   생성자를 만들 때, 혹은 중복된 이름의 함수를 작성할 때 오버로딩이 쓰였던 것을 기억하자. 여기서 자식이 부모의 함수를 가져와 새로 작성하는 것오버라이딩이라고 한다. 주로 프로젝트(협업)을 위해 사용되거나 이후에 배울 다향성에 관해서 주로 사용되는 기능이다. 지금 여기서는 부모가 가진 메서드를 자식이 이름만 들고와 다른 기능으로 바꿀 수 있다는 것을 알아두자.

(물론, 자식에서 바꾼 것이기 때문에 부모가 가진 메서드 자체가 바뀌는 것은 아니다)

 

   Leader클래스에서 오버라이딩 한 say() 함수를 살펴보자. 출력문을 바꾼 것과 동시에, super를 사용하여 다시 한 번 같은 함수를 호출하고 있다. this를 기억한다면 이해하기 쉬운데, super는 해당 클래스의 부모 요소를 직접적으로 안내해주는 참조변수와 같다. this는 해당 클래스, super는 해당 클래스의 부모를 가리키는데, 부모의 sya()를 실행하라는 뜻이므로 Leader의 함수 say()를 불러오면 "선생님께 인사" 와 "선생님, 안녕하세요" 가 둘 다 출력되게 된다.

 

   그렇다면 상속받은 부모와 자식 관계에서 생성자에는 변함이 없을까? 만약 부모가 존재하지 않는 상태인데 자식만 존재하는 상태가 될 수 있을까, 정답은 그렇지 않다. 그래서 우리는 상속받은 클래스에 한해서 부모 생성자를 포함시켜주어야 하는데 보통 생략하면 프로그램 내에서 자동으로 추가해주기는 한다. 하지만 정석으로는 super(); 생성자를 자식 클래스 생성자 안에 포함시켜주는게 맞다. 그래야 자식 클래스가 객체화 되었을 때, 부모도 같이 존재하는 상태가 되기 때문이다.

 

 

   그렇다면 이를 응용한 문제를 확인해보자. SportsCar라는 클래스가 Car2를 상속받은 상태가 되었고, 각 클래스에는 필요한 변수와 함수가 나열되어 있다. 여기서 Car2의 생성자는 파라미터를 받을 수 있는 형태로 만들어져 있고, SportsCar 역시 필요한 파라미터를 받아 초기화 할 수 있는 생성자를 만들었다. 하지만 생성할 수 없다며 에러가 떴다. 그 이유는 Car2의 생성자는 color값을 받아 초기화하는 생성자만 있기 때문이었다. 기본적으로 자식 클래스 생성자는 부모 클래스의 기본형태의 생성자가 자동으로 들어간다. 하지만 Car2의 생성자를 보면 기본생성자가 없기 때문에 초기화를 받을 값이 없다며 에러가 뜨는 상태였다. 이를 해결하기 위해선 Car2에 기본 생성자를 만들어주거나, 아니면 부모 생성자 안에 파라미터 값을 입력해주면 에러없이 정상적으로 만들어지는 것을 확인할 수 있다.

 

   하지만 SportCar 생성자에서 드는 의문이 하나 있을 것이다. 바로 color라는 변수를 가지고 있지 않음에도 this를 사용하여 변수의 초기화를 진행하자 아무런 에러가 뜨지 않은 것인데, 이는 부모의 변수도 함께 상속받음으로써 SportsCar 클래스 내에 부모의 변수가 존재하기 때문이다. 그래서 super를 써도 불러올 수 있지만, this로도 불러올 수 있는 것이다.

(니것도 내것 내것도 내것)

 

 

'아이티에듀넷' 카테고리의 다른 글

2024-09-19 :: 039 함수 정리하기  (0) 2024.09.19
2024-09-13 :: 038 객체의 다형성  (0) 2024.09.13
2024-09-11 :: 036 String에 관하여  (0) 2024.09.11
2024-09-10 :: 035 생성자  (0) 2024.09.10
2024-09-09 :: 034 객체  (0) 2024.09.09
String : java에서 문자열을 담을 때 사용하는 변수, 큰따옴표(" ") 안에 내용을 입력하여 사용한다.

 

   기존에 알고있던 String에 대한 개념이다. int는 정수, float는 실수, char는 문자, boolean은 논리 등 변수를 선언하기 위해 앞에 붙는 데이터 타입처럼 String 또한 문자열에 대한 데이터 타입처럼 취급되어 사용된다. 하지만 실제 이클립스에 입력을 하게 되면 혼자만 데이터 타입이 조금 다르다는 것을 알 수 있다.

 

   이렇듯 입력하게 되면 표시되는 색상이 혼자만 다른데, 이는 바로 String은 객체(클래스)에 속하기 때문이다.

   함축된 표현 방식을 쓰기 전 모양새를 살펴보면 마치 인스턴스를 생성하여 참조변수에 주소값을 담던 방식과 비슷하다. 비슷할 뿐 아니라 실제로도 그렇게 동작하기 때문인데 그렇다면 하나 의문점이 생길 것이다. 바로 참조변수는 해당 객체가 생성될 때 부여받은 주소값을 저장한다는 부분이다. 이를테면 한 클래스를 인스턴스화 시켜 참조변수에 해당 주소값을 담았다 치자. 그리고 그 값을 콘솔창에 불러오게 되면 @표시가 붙은 뒤로 영어와 숫자의 조합으로 이루어진 주소가 나타나게 될 것이다. 즉, 참조변수 자체로는 해당 객체가 가지고 있는 특정 값을 표시한다거나 할 수 없다는 얘기다.

 

   하지만 이렇게 String 변수를 콘솔창에 불러내보면 text라는 변수에는 마치 문자열이 담긴 것 마냥 출력이 되는 것을 확인할 수 있다. 사실 깊게 들어가기에는 아직 배움이 얕아 정확하게 설명을 해낼 순 없지만, 일단 String은 객체가 맞긴 하나 해당 참조변수는 자동적으로 String의 매개변수에 담긴 데이터 값을 참조하여 일반 변수처럼 동작한다는 것이다.

 

   즉, String 타입을 가진 변수는 참조변수라 할 수 있으며 그로 인해 함수를 호출하여 사용할 수 있다. String은 일단 문자열을 파라미터로 받는다. 여기서 charAt이란, 하나의 문자가 여러개 이어져 만들어진 문자열 중에 한 글자를 하나의 인덱스로 보고 해당 위치로 찾아가 반환하는 함수이다. 위에서는 0번째 인덱스를 출력하라 명령했고 그 결과 '안'이라는 글자가 나왔다. length는 배열 객체에서도 자주 사용하다 싶이, 객체가 가진 길이를 뜻한다. charAt으로 한 글자씩 가져오는 것이 가능했다는 것은 글자 하나하나를 인덱스로 볼 수 있다는 의미다. 그래서 "안녕하세요"의 길이 5를 반환한 것이다.

 

   그렇다면 이제 문제의 비교연산자 구문을 확인해보자. 첫번째는 equals() 함수를 사용했고 두번째는 다른 변수의 값을 비교하는 것처럼 비교연산자를 사용하였다. 하지만 같은 문자열을 제시했음에도 두 개의 값이 다른데 그 이유가 바로 String이 일반 변수가 아니라는 뜻이 된다.

 

   Call by value, 일반 변수는 기본적으로 어떤 메서드에 사용되거나 호출이 될 때 변수에 들어있는 값을 그대로 꺼내어 가져오게 된다. 정수, 실수, 논리, 문자가 그러하게 동작하기 때문에 비교연산자를 사용하여 제어문을 쓰거나 반복문을 쓰는 것이 가능한데 String은 그렇지 않다. 일단 객체기 때문이다. 출력할 때 데이터 값을 참조하여 나타내는 참조변수, 즉 해당 변수에 담긴 값은 "안녕하세요" 라는 문자열이 아니라 정확히는 그 문자열이 담긴 주소를 나타내는 것이 된다. 그렇기 때문에 비교연산자로 문자열을 비교하게 되면 주소와 문자열을 비교하는 것이 되기 때문에 대부분의 논리값은 false가 나오게 될 것이다.

(간혹 true가 뜨기도 한다지만 추천하는 방법이 아니다)

 

   그렇다면 문자열을 비교할 방법은 없는 것일까? 그건 아니다. 위에 방법대로 equals 함수를 통해 비교하는 건 가능하다. 이는 해당 객체 안에 존재하는 "안녕하세요" 라는 값을 꺼내어 직접 문자열을 대조하기 때문에 비교가 가능하게 되는 것이다. 그래서 함수를 통해 콘솔창에 불러냈을 때는 true값이 뜨게 되고, 비교연산자를 썼을 경우에는 false가 나오게 된 것이다. 이렇듯 객체 내에서 값을 바꾸거나 참조하는 경우는 Call by reference 라고 한다. 모든 객체가 이러한 방식으로 동작하지만 String은 어딘가 예외인 것처럼 돌아가는 것이 어렵기도 하면서 이해가 되는 것 같기도 하다….

 

   그렇다면 이제 진짜 String의 정체(?)와 사용방법에 대해 알게 되었으니 응용을 해보도록 하자.

'아이티에듀넷' 카테고리의 다른 글

2024-09-13 :: 038 객체의 다형성  (0) 2024.09.13
2024-09-12 :: 037 객체의 상속  (0) 2024.09.12
2024-09-10 :: 035 생성자  (0) 2024.09.10
2024-09-09 :: 034 객체  (0) 2024.09.09
2024-09-06 :: 033 반복문과 배열 변수  (1) 2024.09.06

   어제는 인스턴스를 통해 객체를 생성하고 할당된 주소값을 참조변수에 담아 해당 클래스의 변수의 초기화를 진행하는 방식을 배웠다. 하지만 이런 방식은 번거로운 구조라는 생각이 들었다. 인스턴스 변수에 값을 넣기 위해서 매번 참조변수를 통해 변수를 하나하나 입력해줘야 하는 일이 100개가 되고 1000개가 되면 여간 고생스러운 일이 아닐 수 없기 때문이다. 그래서 오늘은 생성자라는 것을 통해 어떤 식으로 변수 값을 넣어 주는지, 그리고 기존의 방식 또한 어떻게 동작하고 있었는지에 대해 알아보겠다.

<기본 생성자 작성법>
클래스명( ) { }

- 생성자 메서드(함수)의 이름은 클래스와 동일해야 한다.
- 기본 생성자를 작성하지 않았다면 프로그램이 자동으로 구현해둔다. (단, 하나라도 생성자가 있을 경우 만들지 않음)
- 반환하는 값이 없기 때문에 return타입도 존재하지 않는다. (데이터 타입이 없음)
- 호출되면 클래스가 가지고 있는 변수의 초기화를 진행한다.

 

   먼저 기본 생성자부터 알아보자. 지난 시간에는 new와 참조변수만 있으면 하나의 인스턴스를 만드는 건 쉬웠다. 하지만 그냥 new를 써서 객체를 만드는 것이 그냥 띡 하고 되는 일이 아니었다. 바로 생성자가 있어야만 인스턴스를 만들 수 있었는데, 기존 클래스에 사용자가 기본 생성자를 작성하지 않았더라도 프로그램에서 디폴트 값으로 기본 생성자를 만들어주어 가능했던 것이었다. 여기서 생성자란 인스턴스를 생성할 때 호출되면 클래스가 가진 변수의 값을 자동으로 초기화 시켜주는 메서드의 일종이라고 보면 된다. 다만 void가 붙은 것처럼 return값이 없다는 것도 하나의 특징이라고 할 수 있다.

 

 

   기본 생성자가 메서드라는 것을 알았다. 그렇다면 메서드가 가질 수 있는 매개변수를 통해 실제 인스턴스 변수에 값을 담는 것도 가능하지 않을까? exam_39라는 클래스 안에 세 개의 인스턴스 변수가 있다. 모델은 고정이기 때문에 변수를 선언함과 동시에 초기화를 진행했고, 나머지 두 값은 사용자의 데이터를 받기 위해 초기화를 진행하지 않았다. 그리고 메서드를 작성하는 것 처럼 클래스명을 메서드명으로 정하고 매개변수 값에 받을 데이터 타입과 변수명을 입력해준다.

 

   여기서 매개변수에 있는 변수명과 클래스에 선언되어 있는 변수명이 같지만 서로 다른 지역에 선언되었기 때문에 중복 사용이 가능하다. 기본적으로 함수 내에 작성되는 변수는 지역변수이고, 클래스에 선언된 변수는 전역변수기 때문. 하지만 이름이 같기 때문에 함수 안에서 color를 불러오면 지역변수만 선택할 수 있게 된다. 여기서 파라미터를 받아 올 매개변수의 값을 인스턴스 변수의 값으로 대입하고 싶어 this라는 명령어를 배우게 됐다. this는 자바스크립트에서도 사용했지만 사용하는 위치에 따라 인식하는 범위가 다른데, 이 곳에서는 함수 밖의 전역을 의미한다. 그러니 this.변수명을 입력하게 되면 전역에 있는 변수를 가져올 수 있게 되는 것이다. 이렇게 작성해서 하나의 생성자가 완성되었다, 그렇다면 직접 데이터를 넣어보자.

 

   일반 메서드를 사용하는 것 처럼, new를 붙여 생성한 객체의 괄호 안에 직접 파라미터를 입력하니 참조변수를 통해 넣은 값처럼 똑같이 출력된다. 만들어둔 생성자 덕분에 참조변수를 하나하나 입력하여 수정할 일이 없어진 것이다. 하지만 이렇게 임의로 생성자를 만들어 사용한 뒤, 기존에 생성하던 것 처럼 생성하게 되면 '해당 메서드를 찾을 수 없습니다.' 라는 에러가 뜨게 된다. 기본적으로 만들어 주던 기본 생성자가 사용자가 작성함으로써 이번에는 만들어지지 않았기 때문이다. 만약 파라미터 값을 받아오는 형식이 아니라 직접 입력하고 싶을 때 인스턴스를 생성하고 싶다면 추가적으로 클래스명과 함께 기본생성자 메서드를 작성하면 된다.

 

   하지만 여기서 의문이 들 것이다. 과연 같은 클래스명을 가진 메서드가 둘, 셋... 혹은 그 이상 존재해도 되는걸까?

 

   메서드명이 같다면 당연히 안된다. 하지만 메서드마다 가질 수 있는 매개변수가 서로 다르다면, 혹은 생성자가 아닌 일반 메서드에서 return값을 받아 올 데이터 타입이 다르다면 가능하다.

 

   클래스 함수로 세 개의 메서드를 작성했다. 하지만 세 개의 메서드의 이름은 모두 sum으로 동일하다. 하지만 출력했을 때 에러없이 잘 작동되는 것을 확인할 수 있는데, 바로 세 개의 메서드가 가진 매개변수와 return의 데이터 타입이 다르기 때문이다. 첫 번째 sum 함수는 매개변수를 두 개를 가지고 있고, 두 번째와 세번째는 세 개의 매개변수를 가지고 있다. 그리고 두 번째는 정수로, 세 번째는 실수로 데이터 타입을 정하고 함수를 사용할 때 해당하는 값을 넣어주면 메서드에 입력된 기능이 제대로 동작하는 것을 볼 수 있었다. 이렇듯 구조와 타입은 다르지만 이름이 같은 메서드를 여러개 정의하는 것을 오버로딩라고 부른다. 다만 함수를 사용할 때는 입력될 값의 데이터 형식을 알아야 사용할 수 있다는 것이 옥에 티이긴 하나, ctrl을 누른 채 함수를 클릭하면 해당 메서드의 구조를 보여주니까 참고하도록 하자.

+ Recent posts