1. 생성자
① 생성자의 정의
- 사용자가 new 연산자를 통해서 객체를 생성할 때, 제일 먼저 호출되는 일종의 메서드이다.
② 생성자의 특징과 역할
- 생성자는 return 타입이 존재하지 않는다.
- 개발자가 생성자를 만들지 않은 상태로 컴파일을 하면
매개변수가 없는 생성자가 자동으로 생성된다.
이때 매개변수가 없는 생성자를 기본 생성자라고 한다.
- 클래스가 객체화될 때, 객체의 멤버 변수를 초기화하는 역할을 한다.
- 생성자 메서드는 자바 프로그램이 실행될 때
딱 한 번만 실행된다.
③ 일반 메서드, 생성자 메서드 비교
//일반 메소드
class Example {
int add(int a, int b){
//'return 타입''메소드 명'(매개변수){/*명령 입력*/}
}
}
//생성자 메소드
class Exmaple {
Example(int c, int d){
//'class 명''(매개변수)'{/*명령 입력*/}
}
}
일반 메서드는 return 타입을 가지고 있지만
생성자 메서드는 return 타입을 없기 때문에
클래스와 동일한 이름을 가져야 한다.
(생성자 메서드의 이름이 클래스 명과 다른 경우에는
해당 메서드는 실행되지 않는다.)
④ 생성자 예제
//생성자 예제
//홍길동과 홍길순을 만들고
//이름과 나이를 말하게 하시오.
//예시
//이름: 홍길동, 나이: 22
class Main {
public static void main(String[] args) {
사람 a홍길동 = new 사람();
//값을 입력하면
//입력한 값이 나온다.
a홍길동.age = 22;
a홍길동.Name = "홍길동";
System.out.println("=== 홍길동 정보 ===");
System.out.printf("이름: %s\n", a홍길동.Name);
System.out.printf("나이: %d\n", a홍길동.age);
}
}
class 사람 {
int age;
String Name;
}
사람 객체를 하나 만든 다음 이름과 나이를 입력한 다음에 프로그램을 실행했을 때
입력한 값이 나온 것을 확인할 수 있을 것이다.
이번에는 홍길동의 나이를 지우고 다시 실행하면 아래와 같은 결과가 나온다.
//생성자 예제
//홍길동과 홍길순을 만들고
//이름과 나이를 말하게 하시오.
//예시
//이름: 홍길동, 나이: 22
class Main {
public static void main(String[] args) {
사람 a홍길동 = new 사람();
a홍길동.Name = "홍길동";
System.out.println("=== 홍길동 정보 ===");
System.out.printf("이름: %s\n", a홍길동.Name);
System.out.printf("나이: %d\n", a홍길동.age);
}
}
class 사람 {
int age;
String Name;
사람(){
this.age = 22;
}
}
'사람' 클래스의 생성자 메서드를 통해서 멤버변수 age의 초기 값을 22로 설정했기에
main 클래스에서 객체에 데이터를 입력하지 않아도 나이 값이 22로 나오는 것을 확인할 수 있다.
2. this
① this 개념
- this: 현재 객체, 자기 자신을 가리킨다.
- 클래스가 객체화됐을 때, 자기 자신의 메모리 주소를 담고 있는 키워드
- 객체의 주소를 가지고 있으므로 '.'(마침표, 도트연산자)를 사용해서
객체가 가진 멤버 변수나 메서드를 사용할 수 있다.
this 키워드는 주로 아래와 같은 방식으로 사용된다.
- 객체의 멤버 변수와 메서드 혹은 생성자의 매개변수의 이름이 같은 경우
class 사람 {
String Name;
int age;
boolean isMarried;
사람(int age){
age = age;
//변수 age가 age를 가리킨다.
}
}
'사람' 클래스를 만들고, 해당 클래스의 생성자를 정의하는 코드이다.
여기서 사람 클래스의 멤버 변수의 이름과 생성자의 매개변수의 이름은 같지만 문제가 생기지는 않는다.
이제 다시 'age = age;' 구문을 살펴보자.
여기서 어떤 것이 객체의 멤버변수 age인지, 생성자의 매개변수 age인지 구분할 수 있는가?
이를 구분하기 위해서 사용하는 것이 'this' 키워드이다.
class 사람 {
String Name;
int age;
boolean isMarried;
사람(int age){
this.age = age;
//this.age => 객체의 맴버변수 age
//age => 메소드(생성자)의 매개변수 age
}
}
- Overloading 된 다른 생성자 혹은 메서드 호출
Overloading: 매개변수의 개수나 타입을 다르게 해서 같은 이름의 메서드가 존재할 수 있게 하는 방법
(Overloading에 대해서는 추후에 개별적으로 설명하도록 하겠다.)
public class Main {
public static void main(String[] args){
사람 a사람 = new 사람("홍길동", 25, true);
사람 b사람 = new 사람();
}
}
class 사람 {
String Name; //이름
int age; //나이
boolean isMarried;
사람 (String Name, int age, boolean isMarried){
this.Name = Name;
this.age = age;
this.isMarried = isMarried;
System.out.println("=== 사람 정보 ===");
System.out.printf("이름 : %s\n", Name);
System.out.printf("나이 : %d\n", age);
System.out.printf("결혼 여부 : %b\n", isMarried);
}
//생성자 메소드는 프로그램이 실행될 때
//딱 한번 실행된다.
//객체를 생성할 때, 이름과 나이, 결혼 여부를 기입하면
//해당 정보가 그대로 출력된다.
//Overloading
사람 (){
this("무명", 0, false);
//이름, 나이, 결혼 여부를 입력하지 않으면
//위의 정보가 출력되게 하는 생성자 메소드
}
}
프로그래밍을 하다 보면 클래스 안에 Overloading 된 같은 이름의 메서드나 생성자가 존재할 수도 있다.
상단의 코드를 살펴보면 동일한 이름의 생성자가 두 개 존재하는 것을 확인할 수 있을 것이다.
1번 생성자는 'Name', 'age', 'isMarried' 3가지를 매개변수로 가지고 있다.
2번 생성자는 Overloading 된 생성자 메서드로 매개변수는 없다.
여기서 'this()' 키워드를 사용했는데 해당 키워드는 같은 클래스 내에서 다른 생성자나 메서드를 호출할 수 있게 해 준다.
즉, 매개변수가 없는 2번 생성자는 'this()'를 통해서 1번 생성자를 호출하게 된다.
main 클래스에서 객체를 생성할 때
정보를 입력하지 않은 경우에는 2번 생성자가 호출되고
아래와 같은 정보를 출력하게 된다.
3. 생성자의 상속
Number라는 클래스와
Number로부터 상속받아서 사칙연산을 수행하는 sum 클래스가 있다.
여기서 Number는 부모 클래스이고, sum은 자식 클래스다.
sum이 상속받는 것은 Number가 가진 멤버 변수와 메서드만 상속을 받고
Number가 가지고 있는 생성자는 상속되지 않는다.
하지만 자식 클래스의 객체를 만들 때, 제일 먼저 호출되는 것은 부모의 생성자이다.
그렇지만 자식은 부모의 생성자를 상속하지 않았기 때문에
자식 클래스에서 부모의 생성자를 호출하고, 이를 담아둘 자신 만의 생성자가 필요하다.
자식 클래스의 생성자를 별도로 명시하지 않았다면
컴파일러에서 자동으로 만들어주지만 그렇지 않은 경우도 존재한다.
그 이유를 아래 두 가지로 나눠서 설명해 보도록 하겠다.
① 부모 생성자 == 기본 생성자일 경우
여기서 기본 생성자란 매개변수가 존재하지 않는 생성자를 말한다.
//생성자의 상속
//기본 생성자 (매개변수가 없는 생성자)
class Number {
int A;
int B;
Number(){
A = 10;
B = 5;
}
}
class sum extends Number {
void plus(){
System.out.println(A+B);
}
}
class Main {
public static void main(String[] args) {
sum a = new sum();
a.plus();
}
}
Number 클래스에서 변수 A, B를 선언하고
해당 클래스의 객체를 만들 때, 생성자를 통해서 A와 B의 값이 10, 5로 초기화된다.
sum 클래스는 Number로부터 상속받는 클래스로
A와 B를 더한 값을 출력하는 plus() 메서드를 가지고 있다.
이후 Main 클래스에서 sum의 객체를 만들고, 참조변수 a를 통해서
plus() 메서드를 실행하였고 결과 값이 15로 출력된 것을 확인할 수 있다.
sum에서 A와 B의 값을 초기화하지 않았는데도 왜 이러한 결과 값이 나왔을까?
sum의 객체를 만들 때, 부모 클래스 Number의 생성자를 먼저 호출한다.
그리고 호출한 생성자를 담아야 하는데
이를 담을 자식 클래스 sum의 생성자를 명시하지 않았기 때문에
컴파일러에서 자동으로 기본 생성자를 하나 만들어준다.
그리고 sum의 생성자에 부모의 생성자를 담았기 때문에
메서드를 실행할 때 부모의 생성자에서 초기화한 값을 참조할 수 있게 된 것이다.
② 부모 생성자 == 기본 생성자가 아닐 경우
//생성자의 상속
class Number {
int A;
int B;
Number(int A, int B){
this.A = A;
//this.A => Number의 변수 A
//A => 생성자의 매개변수 A
this.B = B;
//this.B => Number의 변수 B
//B => 생성자의 매개변수 B
}
}
class sum extends Number {
void plus(){
System.out.println(A+B);
}
}
public class Main {
public static void main(String[] args){
sum a = new sum();
a.plus();
}
}
상단의 소스코드를 실행하지 말고, 경고 메시지를 확인해 보자.
(어차피 실행해도 컴파일 에러가 발생할 것이다.)
왜 이런 식의 경고 메시지가 나왔을까?
왜냐하면 부모의 생성자가 매개변수가 있는 생성자만 있어서 발생한 것이다.
부모 클래스의 생성자가 하나만 존재하며, 매개변수도 가지고 있다면
컴파일러는 자식 클래스의 기본 생성자를 만들어주지 않는다.
따라서 이러한 문제를 해결하는 방법은
그냥 자식 클래스의 생성자를 명시하고 부모의 생성자를 직접 호출까지 하면 되는 일이다.
바로 소스 코드를 수정해 보자.
//생성자의 상속
class Number {
int A;
int B;
Number(int A, int B){
this.A = A;
//this.A => Number의 변수 A
//A => 생성자의 매개변수 A
this.B = B;
//this.B => Number의 변수 B
//B => 생성자의 매개변수 B
}
}
class sum extends Number {
sum(){
super(10,5); //??
}
void plus(){
System.out.println(A+B);
}
}
public class Main {
public static void main(String[] args){
sum a = new sum();
a.plus(); //프로그램 실행 결과 참고
}
}
super
=> 부모로부터 상속받은 변수나 메서드를 자식 클래스에서 참조할 수 있게 하는 키워드
=> super(10, 5);
부모의 생성자를 호출하는 키워드로
부모 클래스 Number의 생성자 중에서 매개변수를 두 개 가지고 있는 생성자를
자식 클래스에서 참조하게 한다.
super()에서 매개변수를 두 개 입력해서
입력한 매개변수의 개수와 같은 부모의 생성자를 호출했기 때문에
변수 A와 B를 더한 값이 15로 출력될 수 있었다.
//생성자의 상속
class Number {
int A;
int B;
Number(){
this.A = 10;
this.B = 5;
}
Number(int A, int B){
this.A = A;
this.B = B;
}
}
class sum extends Number {
//별도의 생성자를 명시하지 않았다면
//프로그램 실행 시 컴파일러가 자식 생성자를 만들고
//부모의 기본 생성자를 참조한다.
void SUM(){
System.out.printf("A + B = %d\n", A+B);
}
}
class imsub extends Number {
imsub(){
super(10,5);
//super() 키워드로 오버로딩한 부모의 생성자
//매개변수를 두 개 가진 생성자를 자식 클래스에서 참조
}
void IMSUB(){
System.out.printf("A - B = %d\n", A-B);
}
}
public class Main {
public static void main(String[] args){
sum a = new sum();
a.SUM(); //A + B = 15
imsub b = new imsub();
b.IMSUB(); //A - B = 5
}
}
부모 클래스에서 기본 생성자와 오버로딩한 생성자가 동시에 존재한다면
자식 클래스에서 생성자를 명시하지 않으면 컴파일러가 부모의 기본 생성자를 호출한다는 것을
상단의 프로그램 실행 결과를 통해서 확인할 수 있다.
'Programming > Java' 카테고리의 다른 글
[Java] 13. 오버로딩 (Overloading) (0) | 2023.01.17 |
---|---|
[Java] 12. 캡슐화와 접근 제어자 (0) | 2023.01.16 |
[Java] 10. 상속 (Inheritance) (0) | 2022.12.22 |
[Java] 9. static (0) | 2022.12.21 |
[Java] 8. Scanner (0) | 2022.12.04 |