개발하고 싶은 초심자
220829 D+5 Class(클래스) / OOP(객체지향 프로그래밍), Access Modifier(접근제한자), Instance(인스턴스) / this vs this() 본문
220829 D+5 Class(클래스) / OOP(객체지향 프로그래밍), Access Modifier(접근제한자), Instance(인스턴스) / this vs this()
정새얀 2022. 8. 29. 18:001. OOP(Object Oriented Programming / 객체지향 프로그래밍)
: Class를 정의하여 객체를 생성, 사용하는 방식.
→ 중복된 코드를 줄이고 코드의 재사용성을 높이면서 유지보수가 편리해진다.
(1) 객체(Object)
class Villager {
// 변수
private String name;
private String gender;
// 메소드
void personaility() { ... };
void job() { ... };
}
class Villagers {
public static void main(String[] args) {
Villager Haley = new Villager(); // Villager 클래스를 기반으로 생성된 Haley 인스턴스
Villager Emily = new Villager(); // Villager 클래스를 기반으로 생성된 Emily 인스턴스
Villager Harvey = new Villager(); // Villager 클래스를 기반으로 생성된 Harvey 인스턴스
}
(2) 객체의 생성
클래스명 참조_변수명; // 인스턴스를 참조하기 위한 참조변수 선언
참조_변수명 = new 생성자(); // 인스턴스 생성 후, 객체의 주소를 참조 변수에 저장
특정 클래스 타입의 참조 변수(실제 데이터가 저장되어 있는 힙 메모리의 주소값)
선언 후 new 키워드와 생성자를 통해 인스턴스를 생성하여 참조 변수에 할당한다.
클래스명 참조_변수명 = new 생성자();
→ new 키워드는 생성된 객체를 힙 메모리에 넣으라는 의미를 가지고 있는데,
생성자(클래스와 동일한 이름을 가졌지만 뒤에 소괄호가 붙음)를 통해 객체가 만들어지면
해당 객체를 힙 메모리에 넣는 역할을 수행하게 된다.
⇒ new 키워드와 생성자를 통해 클래스의 객체를 생성한다는 것
= 해당 객체를 힙 메모리에 넣고 그 주소값을 참조변수에 저장하는 것
클래스 Person과 참조 변수 p는 각각 클래스 영역과 스택 영역이라는 다른 위치에 저장된다.
생성자로 만들어진 인스턴스는 힙 메모리 영역에 들어가며 객체 내부에는 클래스의 멤버들이 위치하게 된다.
‣ 메서드 구현 코드는 클래스 영역에 저장되고 객체 안에서는 그 위치를 가리키고 있다.
→ 같은 클래스로 만든 모든 객체는 동일한 메서드 값을 공유하기 때문에 여러 번 같은 메서드를 선언해주는 것이 아니라
한번만 저장해두고 필요한 경우에만 클래스 영역에 정의된 메서드를 찾아 사용할 수 있다.
⇒ 생성된 객체에서 필드값은 실제 저장공간이 객체 내부에 있음
⇒ 메소드는 다른 영역에 하나만 저장해놓고 공유한다
(3) 객체의 활용
// 포인트 연산자 . 을 활용하여 특정 인스턴스 객체의 필드와 메소드(객체의 멤버들)에 접근할 수 있음.
참조 변수명.필드명 // 필드값 불러오기
참조 변수명.메서드명() // 메서드 호출
public class Villagers {
public static void main(String[] args) {
Villager Harvey = new Villager("Harvey", "Male"); // 객체 생성.
System.out.println("그의 이름은 " + Harvey.name + "이고 " + "성별은 " + Harvey.gender + "입니다."); // 필드 호출
Haley.personality(); // 메소드 호출
Haley.job();
}
}
class Villagers {
public String name; // 필드 선언
public String age;
public Villagers(String name, String gender) { // 인스턴스 초기화를 위한 생성자 함수.
this.name = name;
this.age = age;
}
void personality() { // 메서드 선언
System.out.println("성격은 친절합니다.");
}
void job() {
System.out.println("직업은 의사입니다.");
}
}
// 출력값
그의 이름은 Harvey이고 성별은 Male입니다.
성격은 친절합니다.
직업은 의사입니다.
‣ 캡슐화: 값의 보호를 위해 객체의 필드, 메소드를 필요에 의해 감추거나 들어내는 것
‣ 다형성: 동일한 타입을 가진 여러 객체가 같은 속성을 가지는 성질
‣ 상속성: 특정 클래스를 부모 클래스로 지정하여 내용을 물려받아 기능을 확장하는 것
① 클래스: 객체를 만들기 위한 설계도(틀)
→ 모든 코드는 클래스 안에 작성해야 함.
→ 객체를 생성하는 데 사용되는 하나의 틀일 뿐이다(객체 그 자체가 아니다).
② 객체: 구현할 대상, 클래스에 선언된 모양 그대로 생성된 실제. 모든 인스턴스를 포괄하는 넓은 의미.
③ 인스턴스: 클래스를 통해 생성된 객체. 클래스를 바탕으로 실제로 만들어진 실체.
→ 해당 객체가 어떤 클래스로부터 생성된 것인지 강조한다.
④ 인스턴스화(instantiate): 클래스로부터 객체를 만드는 과정
// 클래스명은 주로 대문자로 시작한다
public class UserData {
// 클래스 상태
}
UserData data; // 객체
data = new UserData(); // 인스턴스화
// data는 UserData 클래스의 '인스턴스'(객체를 변수에 할당)
⇒ 클래스를 한 번 잘 정의해두면 매번 객체를 생성할 때마다 어떻게 객체를 만들어야 할 지에 대해 고민하지 않고
클래스로부터 객체를 생성해서 사용하기만 하면 된다.
2. 접근제한자(Access Modifier)
: 클래스, 변수, 메소드의 접근 권한을 명시적으로 작성한 키워드
→ 클래스의 변수와 메소드를 다른 클래스에서 사용할 수 있는 지 없는 지 정의할 필요가 있기 때문에 나온 것으로 이해.
‣ public : 모든 접근을 허용
‣ protected : 같은 패키지(폴더)에 있는 객체와 상속관계의 객체들만 허용
‣ default : 같은 패키지(폴더)에 있는 객체들만 허용
‣ private : 현재 객체 내에서만 허용
package Korea;
public class Samsung {
private Phone = "Galaxy";
default PhoneOS = "Android";
protected PhonePatent = "Camera technology";
public string cellPhonePurchase() {
return Phone;
}
private void ChangePhonePatent(String otherPatent) {
PhonePatent = otherPatent;
}
}
public class SamsungSDS extends Samsung {
private void ChangeData(String other) {
this.Phone = other; // 불가
this.PhoneOS = other; // 가능
this.PhonePatent = other; //가능
cellPhonePurchase(); // 가능
ChangePhonePatent("new technology"); // 불가
}
}
package Korea;
public class LG {
Samsung samsung = new Samsung();
private void ChangeData(String other){
Samsung.Phone = other; // 불가
Samsung.PhoneOS = other; // 가능
Samsung.PhonePatent = other; //불가
Samsung.cellPhonePurchase(); // 가능
Samsung.ChangePhonePatent("new technology"); // 불가
}
}
package America;
import Korea.*;
// import는 다른 폴더의 파일을 불러 올때 사용
// * 은 폴더 내 모든 파일을 접근할 때 사용
public class apple {
// import 선언 없이 Korea.Samsung samsung = new Samsung(); 으로 쓸 수 있다.
Samsung samsung = new Samsung();
private void ChangeData(String other){
Samsung.Phone = other; // 불가
Samsung.PhoneOS = other; // 불가
Samsung.PhonePatent = other; //불가
Samsung.cellPhonePurchase(); // 가능
Samsung.ChangePhonePatent("new technology"); // 불가
}
}
3. 클래스
① 클래스의 기본 구조
package Data; // package는 파일 경로
public class UserData {
}
접근제한자 | class | 이름 {
//클래스 내용
}
// 해당 클래스가 어떤 파일 구조 아래 있는지 명시적으로 작성하는 것을 권고
// 클래스를 만들 때 대부분 새로운 .java 파일을 만들지만 한 파일 안에 여러 개의 클래스를 작성해도 됨
② 클래스의 구성 멤버
→ 구성 멤버들은 생략되기도 하고 여러 개가 작성될 수도 있다.
public class ClassName {
//필드
private String name;
//생성자
public ClassName () { ... }
//메소드
public void methodName() { ... }
}
//UserData.java
package Data;
public class UserData {
private String name; // 필드
private String age; // 필드
// public UserData 블록이 클래스 생성자. 클래스가 인스턴스 되면 실행된다.
public UserData(String name, String age) {
this.name = name;
this.age = age;
}
// Setter, Getter 메소드.
// 클래스 내부의 private 변수나 메소드에 접근하기 위해 사용한다.
public void SetName(String name) {
// name 변수에 값을 적용한다.
this.name = name;
}
public void SetAge(String age) {
// age 변수에 값을 적용한다.
this.age = age;
}
public String GetName() {
return this.name;
}
public String GetAge() {
// 객체에 age 변수를 리턴.
return this.age;
}
//메소드를 호출하면 객체의 age 변수에 1을 더하는 메소드
public void AddAge() {
this.age = this.age + 1;
}
}
‣ 필드(클래스 변수, 인스턴스 변수)
: 클래스에 포함된 변수, 객체의 속성을 나타내는 변수.
객체의 고유 데이터, 부품 객체, 상태 정보를 저장하는 곳.
→ 선언 형태가 변수를 선언하는 것과 비슷하지만 변수라고 부르지는 않는다.
변수는 생성자와 메소드 내에서만 사용되고, 생성자와 메소드가 실행 종료되면 자동으로 소멸되지만
필드는 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 객체와 함께 존재한다.
// 선언된 위치에 따라 그 종류가 결정되며 각각 다른 유효 범위를 갖게 된다
class Example { // => 클래스 영역
int instanceVariable; // 인스턴스 변수
static int classVariable; // 클래스 변수(static 변수, 공유변수)
void method() { // => 메서드 영역
int localVariable = 0; // 지역 변수. {} 블록 안에서만 유효
}
}
⇒ static 키워드가 함께 선언된 것은 클래스 변수, 아닌 것은 인스턴스 변수.
두 가지 변수 유형에 포함되지 않고 메소드 내에 포함된 모든 변수는 지역 변수.
→ instanceVariable 과 classVariable 은 클래스 영역에 선언되었기 때문에 멤버 변수
→ static 키워드의 유무에 따라 classVariable 변수가 클래스 변수, nstanceVariable 변수가 인스턴스 변수,
메소드 내부의 블럭에 선언되어있는 지역변수 localVariable.
1) 인스턴스 변수(instance variable)
: 인스턴스가 가지는 각각의 고유한 속성을 저장하기 위한 변수. new 생성자()를 통해 인스턴스가 생성될 때 만들어진다.
→ 클래스를 통해 만들어진 인스턴스는 힙 메모리의 독립적인 공간에 저장되고,
동일한 클래스로부터 생생되었어도 객체의 고유한 개별성을 가진다.
→ 고유한 특성을 정의하기 위한 용도로 사용됨.
2) 클래스 변수(class variable)
: 공통된 저장공간을 공유하며 static 키워드를 통해 선언한다.
→ 한 클래스로부터 생성되는 모든 인스턴스들이 특정한 값을 공유해야하는 경우에 주로 클래스 변수를 선언하게 된다.
→ 인스턴스를 따로 생성하지 않아도 언제든 클래스명.클래스변수명을 통해 사용 가능함.
(클래스 변수는 클래스 영역에 저장되어 그 값을 공유하기 때문에 가능한 것)
3) 지역 변수(local variable)
: 메소드 내에 선언되며 메소드 내 {} 블록에서만 사용가능한 변수.
→ 멤버 변수와 다르게 스택 메모리에 저장되어 메소드가 종료되는 것과 동시에 소멸되어 더 이상 사용할 수 없게 된다.
→ 힙 메모리에 저장되는 필드 변수는 객체가 없어지지 않는 한 절대 삭제되지 않는 반면,
스택 메모리에 저장되는 지역 변수는 한동안 사용되지 않는 경우 가상 머신에 의해 자동으로 삭제된다.
✷ 지역 변수는 직접 초기화하지 않으면 값을 출력할 때 오류가 발생,
필드 변수는 직접적으로 초기화를 실행하지 않더라도 강제로 초기화가 이루어진다.
→ 힙 메모리에는 빈 공간이 저장될 수 없기 때문에 이곳에 저장되는 필드는 강제로 초기화되지만,
스택 메모리는 강제로 초기화되지 않으므로 지역 변수는 선언시 반드시 초기화를 실행해주어야 한다.
① 필드 선언
: 중괄호 블록 어디서든 존재할 수 있으며 생성자 선언과 메소드 선언이 앞과 뒤, 어느 곳에서나 필드 선언이 가능하다.
// 필드 선언
접근제한자 | 타입 | 필드 | = 초기값;
// 초기값은 선택적으로 적을 수 있다.
// ex)
private String company;
private String model = "Kona";
private int speed;
public int rpm = 9;
public Body body;
public Engine engine;
② 필드 사용
: 필드 값을 읽고 변경하는 작업.
클래스 내부의 생성자나 메소드를 사용할 경우 단순히 필드 이름으로 읽고 변경하면 된다.
클래스 외부에서 사용할 경우 우선적으로 클래스로부터 객체를 생성한 뒤 필드를 사용해야 한다.
→ 필드는 객체에 소속된 데이터이기 때문에 객체가 존재하지 않으면 필드도 존재하지 않기 때문이다.
// Car.java
public class Car {
public String company = "hyundai";
public String model = "PALISADE";
public String color = "blue";
public int zerotohundred = 10;
public int speed;
}
// CarExample.java
public class CarExample {
public static void main(String[] args)
// 인스턴스 생성
Car myCar = new Car();
// 필드값 읽기
System.out.println("company : " + myCar.company); //comapany : hyundai
System.out.println("model : " + myCar.model); //model : PALISADE
System.out.println("color : " + myCar.color); //color : blue
// 필드값 변경
myCar.speed = 120;
System.out.println("speed : " + myCar.speed); //speed : 120
}
}
외부 클래스에서 Car 클래스의 speed 필드값을 사용하려면 Car 객체를 우선 생성해야 한다.
myCar 변수가 Car 클래스를 참조하게 되면 . 연산자를 사용해서 speed 필드에 접근할 수 있음을 사용하여
speed의 필드 값을 120으로 변경하고 있다.
4) static 키워드
: 클래스의 멤버(필드, 메서드, 이너 클래스)에 사용하는 키워드.
static 키워드가 붙어있는 멤버를 우리는 정적 멤버(static member)라고 부르고
static이 붙어있지 않은 인스턴스 변수와 구분한다.
✷ 인스턴스 멤버
: 객체를 생성한 후 사용할 수 있는 필드(인스턴스 필드)와 메소드(인스턴스 메소드).
→ 인스턴스 필드와 인스턴스 메소드는 객체에 소속된 멤버이기 때문에 객체 없이 사용 불가능.
‣ 인스턴스 멤버는 반드시 객체를 생성한 이후에 변수와 메서드에 접근하여 해당 멤버를 사용가능한 반면,
static 키워드로 정의되어 있는 클래스 멤버들은 인스턴스의 생성 없이도 클래스명.멤버명 만으로도 사용이 가능하다.
→ 정적 멤버도 객체를 생성한 이후 참조변수를 통해 사용이 가능하지만,
애초에 정적 멤버임을 표시하기 위해서 클래스명.멤버명 의 형태로 사용할 것을 권장하고 있다.
‣ static 키워드를 사용하는 정적 멤버를 클래스명.멤버명 으로 사용할 수 있는 것 또한 메모리의 저장 위치와 관련이 있다.
new 키워드를 통해 생성된 인스턴스는 힙 메모리에 생성되고 독립적인 저장공간을 가지게 된다.
반면 static 키워드로 선언된 정적 멤버는 클래스 내부에 저장 공간을 가지고 있기 때문에
객체 생성 없이 곧바로 사용할 수 있다.
ex)
public class StaticTest {
public static void main(String[] args) {
StaticExample staticExample = new StaticExample();
System.out.println("인스턴스 변수: " + staticExample.num1); // static 키워드가 없는 인스턴스 변수
System.out.println("클래스 변수: " + StaticExample.num2); //static 키워드가 있는 클래스 변수
}
}
class StaticExample {
int num1 = 10;
static int num2 = -10;
}
//출력값
인스턴스 변수: 10
클래스 변수: -10
StaticExample 클래스에 static 키워드가 있는 참조변수 num2 와 static 키워드가 없는 참조 변수 num1 에
각각 -10과 10을 할당하고 StaticTest 클래스에서 각각의 필드 값을 호출했다.
다만 인스턴스 변수는 우리가 아는 일반적인 방법으로 객체 생성 후에 포인트 연산자를 사용하여 값을 불러왔고,
클래스 변수는 객체 생성 없이 클래스명을 사용하여 값을 불러왔다.
→ 정적 필드는 객체 간 공유 변수의 성질이 있다.
이는 메소드에도 동일하게 적용된다.
일반적인 메서드 앞에 static 키워드를 사용하면 해당 메소드는 정적 메소드가 된다.
정적 메서드도 정적 필드와 마찬가지로 클래스명만으로 바로 접근이 가능하다.
→ 정적 메서드의 경우 인스턴스 변수 또는 인스턴스 메서드를 사용할 수 없는데,
이는 메서드는 인스턴스 생성 없이 호출이 가능하기 때문에
정적 메서드가 호출되었을 때 인스턴스가 존재하지 않을 수 있기 때문이다.
// 정적 필드 간에 값 공유가 일어나는 것
public class StaticFieldTest {
public static void main(String[] args) {
StaticField staticField1 = new StaticField(); // 객체 생성
StaticField staticField2 = new StaticField();
staticField1.num1 = 100;
staticField2.num1 = 1000;
System.out.println(staticField1.num1);
System.out.println(staticField2.num1);
staticField1.num2 = 150;
staticField2.num2 = 1500;
System.out.println(staticField1.num2);
System.out.println(staticField2.num2);
}
}
class StaticField {
int num1 = 10;
static int num2 = 15;
}
// 출력값
100
1000
1500
1500
StaticField 클래스에 인스턴스 필드(num1)와 정적 필드(num2)를 각각 선언하고,
대조를 위해 staticField1와 staticField2 객체를 생성했다.
num1의 경우에는 각각의 변수가 고유성을 가지기 때문에 100과 1000으로 따로 출력되는 반면,
num2의 경우는 앞서 배웠던 것처럼 값 공유가 일어나 1500이 출력값으로 두 번 반복되고 있다.
→ static 키워드를 사용하면 모든 인스턴스에 공통적으로 적용되는 값을 공유할 수 있다.
⇒ static 키워드는 클래스의 멤버 앞에 붙일 수 있다.
정적 멤버의 가장 큰 특징은 인스턴스를 따로 생성하지 않아도 클래스명만으로도 변수나 메소드 호출이 가능하다는 점이고,
이는 메모리의 저장 위치와 관련이 있다는 사실을 알 수 있다.
‣ 메소드(특정 작업을 수행하는 일련의 명령문들의 집합)
: 클래스의 기능을 나타내는 함수. 객체의 동작에 해당하는 중괄호 {}블록.
→ 중괄호 블록은 이름을 가지고 있는데, 이것이 메소드 이름이다.
메소드를 호출하게 되면 중괄호 블록에 있는 모든 코드들이 일괄적으로 실행된다.
메소드는 필드를 읽고 수정하는 역할도 하지만 다른 객체를 생성해서 다양한 기능을 수행하기도 하고,
객체 간의 데이터 전달의 수단이 되기도 하기 때문에 외부로부터 파라미터를 받을 수도 있고
실행 후 어떤 값을 리턴할 수도 있습니다.
① 메소드 선언
메소드 선언은 선언부와 실행 블록으로 구성된다.
접근제한자 | 리턴타입 | 메소드이름 (매개변수) {
...
// 실행블록
}
자바제어자 반환타입 메소드명(매개 변수) { // 메소드 시그니처
메소드 내용 // 메소드 바디
}
메소드의 시그니처는 순서대로
해당 메소드가 어떤 타입을 반환하는 가(반환 타입),
메소드 이름이 무엇(메서드명)이며
해당 작업을 수행하기 위해서 어떤 재료들이 필요한지(매개 변수)에 대한 정보를 포함하고 있다.
메소드의 바디는 괄호{}안에 해당 메소드가 호출되었을 때 수행되어야하는 일련의 작업들을 표시한다.
// 메소드명은 관례적으로 소문자로 표기한다.
public static int add(int x, int y) { // 메소드 시그니처
int result = x + y; // 메소드 바디
return result;
}
⇒ 메소드의 반환타입이 void가 아닌 경우 메소드 바디({})안에 반드시 return 문이 존재해야한다.
리턴문은 작업을 수행한 결과값을 호출한 메서드로 전달하는데,
여기서 결과값은 반드시 반환타입과 일치하거나 적어도 자동 형변환이 가능한 것이어야 한다.
void printHello() { // 반환타입이 void인 메소드
System.out.println("hello!");
}
// 반환값이 없는 void이므로 printHello()메소드는 호출되면 hello!라는 내용을 출력하고 종료된다.
int getNumSeven() { // 매개변수가 없는 메소드
return 7;
}
// int 타입의 결과값을 반환하는 매개변수가 없는 메소드이므로 호출되면 7을 반환하면 된다.
// 따로 매개변수가 필요없다.
Double multiply(int x, double y) { // 매개변수가 있는 메소드
double result = x * y;
return result;
}
// double형을 산술 연산하면 범위가 더 큰 타입으로 자동으로 형 변환이 이루어진다.
② 리턴 타입
: 메소드가 실행 후 리턴하는 값의 타입.
메소드는 리턴 값이 있을 수도 있고 없을 수도 있습니다.
메소드가 실행된 후 결과를 호출한 곳에 값을 넘겨주는 경우에는 리턴 값이 있어야 하는데,
리턴 값이 없는 경우는 void라는 리턴 타입을 쓴다.
그러나 void의 경우 리턴이라는 단어를 못 쓰는 것은 아니고,
return; 이런 형태로 작성하는 것이 기본이나 생략한다.
③ 메소드 호출
메소드는 클래스 내/외부의 호출에 의해서 실행된다.
클래스 내부의 다른 메소드에 의해 호출 될 경우에는 단순히 메소드 이름으로 호출하면 되지만,
클래스 외부에서 호출할 경우에는 우선 클래스로부터 인스턴스를 생성한 뒤(메소드도 클래스의 멤버이므로),
참조 변수를 이용해서 메소드를 호출해야 한다. 객체가 존재해야 메소드도 존재하기 때문이다.
// 객체 내부에서 메소드를 호출할 경우의 예시 코드
// Calculator.java
public class Calculator {
public int plus(int x, int y){
int result = x + y;
return result;
}
public double ave(int x, int y){
double sum = plus(x,y);
double result = sum /2;
return result;
}
public void execute() {
double result = avg(10, 20);
printOnConsole("결과 : " + result);
}
public void printOnConsole(String message){
System.out.println(message);
}
}
클래스 내부에서 메소드를 호출할 때에는 단순히 메소드 이름으로 호출하면 된다.
혹은 리턴 값이 있는 메소드를 호출하고 리턴 값을 받고 싶다면, 변수를 선언하고 이에 대입을 하는 방법을 사용하면 된다.
이때 주의점은 변수 타입은 메소드 리턴 타입과 동일하거나, 타입 변환이 될 수 있어야 한다는 것이다.
// 객체 외부에서 메소드를 호출할 경우의 예시 코드
//CalculatorExamlple.java
public class CalculatorExample {
public static void main(String[] args) {
Calculator myCalc = new Calculator();
myCalc.excute();
}
}
외부클래스인 CalculatorExample에서 Calculator 클래스의 메소드를 호출하기 위해 해당 클래스의 인스턴스를 생성했다.
메소드는 클래스에 소속된 멤버이므로, 해당 클래스의 인스턴스가 존재하지 않으면 메소드도 존재하지 않기 때문이다.
인스턴스가 생성되면 참조 변수와 함께 . 연산자로 메소드를 호출한다.
→ 메소드 호출 시 괄호()안에 넣어주는 입력값을 인자라고 한다.
인자의 개수와 순서는 반드시 메소드를 정의할 때 선언된 매개변수와 일치되어야 한다(그렇지 않은 경우 에러 발생).
→ 인자의 타입도 매개변수의 그것과 일치하거나 자동 형 변환이 가능한 것이어야 한다.
‣ 메소드 오버로딩(Method Overloading)
: 하나의 클래스 안에 같은 이름의 메소드를 여러 개 정의하는 것.
→ 하나의 메소드에 하나의 기능만 구현하는데, 같은 이름의 메소드를 여러 기능으로 구현함.
public class Overloading {
public static void main(String[] args) {
Shape s = new Shape(); // 객체 생성
s.area(); // 메서드 호출
s.area(5);
s.area(10,10);
s.area(6.0, 12.0);
}
}
class Shape {
public void area() { // 메서드 오버로딩. 같은 이름의 메서드 4개.
System.out.println("넓이");
}
public void area(int r) {
System.out.println("원 넓이 = " + 3.14 * r * r);
}
public void area(int w, int l) {
System.out.println("직사각형 넓이 = " + w * l);
}
public void area(double b, double h) {
System.out.println("삼각형 넓이 = " + 0.5 * b * h);
}
}
//출력값
넓이
원 넓이 = 78.5
직사각형 넓이 = 100
삼각형 넓이 = 36.0
⇒ 무조건 같은 메서드명을 사용한다고해서 오버로딩이 되는 것이 아니다
✷ 오버로딩이 성립하기 위한 조건들
① 같은 이름의 메소드명을 사용해야 한다.
② 매개변수의 개수나 타입이 다르게 정의되어야 한다.
→ 이 조건들 중 하나라도 충족되지 않으면 중복 정의로 간주되어 컴파일 에러가 발생하게 된다.
✷ 오버로딩의 장점
: 하나의 메소드로 여러 경우의 수를 해결할 수 있음. 대표적인 예시로는 println()메소드.
→ println() 메서드를 사용했을 때 아무 값이나 괄호()안에 인자로 넣어서 사용하는데 문제가 없었지만,
그 내부를 살펴보면 매개변수의 타입에 따라서 호출되는 println 메서드가 달라진다는 것을 알 수 있다.
→ 오버로딩을 통해 같은 기능을 하는 메소드의 이름을 계속 반복적으로 지어주지 않아도 되고,
이름만 보고도 기능을 쉽게 예측할 수 있다.
‣ 생성자(Constructor)
: 클래스의 객체를 생성하는 역할. new 키워드로 호출되는 특별한 중괄호 {} 블록.
→ 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메소드.
생성자의 역할은 객체 생성 시 초기화를 담당한다. 필드를 초기화하거나, 메소드를 호출해서 객체를 사용할 준비를 한다.
생성자를 실행시키지 않고는 클래스로부터 인스턴스를 만들 수 없다.
⇒ 인스턴스 생성을 담당하는 것은 new 키워드, 생성자는 인스턴스 변수들을 초기화 하는데에 사용하는 특수한 메소드.
✷ 생성자와 메소드의 차이
‣ 생성자의 이름은 반드시 클래스의 이름과 같아야 한다.
→ 클래스의 이름과 생성자의 이름이 다르면 그 메소드는 더 이상 생성자로서의 기능을 수행할 수 없다.
‣ 생성자는 리턴 타입이 없지만 void 키워드는 사용하지 않는다.
→ 무언가를 리턴하지 않는다를 의미하는 void와 달리
생성자는 아예 리턴 타입 자체가 존재하지 않는다.
① 생성자 선언
모든 클래스는 생성자가 반드시 존재하며, 하나 이상을 가질 수 있다.
// 클래스의 선언 형식
접근제한자 | 클래스 () {}
클래스명(매개변수) { // 생성자 기본 구조
...생략...
}
생성자는 클래스 이름으로 되어 있고 리턴 타입이 없다. 매개변수는 있을 수도 있고 없을 수도 있다.
생성자는 일반적으로 필드에 초기값을 저장하거나 메소드를 호출하여 객체 사용 전에 필요한 준비를 한다.
ex)
public Car () {}
중괄호 {} 블록 내용이 비어있는 생성자를 기본 생성자라고 한다.
만약 우리가 클래스 내부에 생성자 선언을 생략했다면, 컴파일러는 기본생성자를 자동으로 추가시킨다.
외부에서 받아올 변수가 없다면 기본 생성자만으로도 충분하지만
받아올 파라미터가 있다면 그에 맞는 형식의 생성자를 만들어줘야 한다.
// new 키워드로 생성자를 호출할 때 총 3개의 파라미터를 사용
Car myCar = new Car("아반떼", "흰색", 7);
이 파라미터는 외부의 값을 생성자 블록 내부로 전달하는 역할을 한다.
이때, 앞의 두 파라미터는 String 타입이고, 마지막은 int타입인 것을 볼 수 있다.
이렇듯 세 매개 값을 생성자가 받기 위해서는 다음과 같이 생성자를 선언해야 한다.
public class Car {
Car(String model, String color, int zerotohundred) {
...
}
}
클래스에 생성자가 명시적으로 선언되어 있을 경우 반드시 선언된 생성자의 형식에 맞춰서 호출하여 객체를 생성해야 한다.
public class Car {
// 생성자
Car(String model, String color, int zerotohundred) {
...
}
}
public class CarExample {
Car myCar = new Car ("아반떼", "회색", 10);
// Car myCar = new Car(); 기본 생성자는 호출 할 수 없음.
}
② 생성자도 오버로딩이 가능하기 때문에 한 클래스 내에 여러 개의 생성자가 존재할 수 있다.
public class ConstructorExample {
public static void main(String[] args) {
Constructor constructor1 = new Constructor();
Constructor constructor2 = new Constructor("Hello World");
Constructor constructor3 = new Constructor(5,10);
}
}
class Constructor {
Constructor() { // (1) 생성자 오버로딩
System.out.println("1번 생성자");
}
Constructor(String str) { // (2)
System.out.println("2번 생성자");
}
Constructor(int a, int b) { // (3)
System.out.println("3번 생성자");
}
}
class Villagers {
public String name; // 필드 선언
public String age;
public Villagers(){} // 기본 생성자. 생성자가 없는 경우 자동 생성.
// 생성자 오버로딩
public Villagers(String name, String gender) { // 매개변수가 있는 생성자.
this.name = name;
this.age = age;
}
void personality() { // 메서드 선언
System.out.println("성격은 친절합니다.");
}
void job() {
System.out.println("직업은 의사입니다.");
}
}
// 출력값
그의 이름은 Harvey이고 성별은 Male입니다.
성격은 친절합니다.
직업은 의사입니다.
③ 기본 생성자
: 매개변수가 없는 생성자.
클래스명(){} //기본 생성자
DefaultConst(){} // 예시) DefaultConst 클래스의 기본 생성자
→ 컴파일러가 자동으로 추가해주는 기본 생성자에는 매개 변수도 없고 바디에 내용이 없다.
만약 생성자가 이미 추가되어 있는 경우에는 기본 생성자가 아닌 이미 추가되어 있는 생성자를 기본으로 사용하게 된다.
④ 매개변수가 있는 생성자
: 메소드처럼 매개변수를 통해 호출 시 해당 값을 받아 인스턴스를 초기화하는 데 사용한다.
→ 고유한 특성을 가진 인스턴스를 계속 만들어야 하는 경우 인스턴스마다 각기 다른 값을 가지고 초기화할 수 있어 유용함.
public class ConstructorExample {
public static void main(String[] args) {
Villager h = new Villager("Harvey", 25, "Male");
System.out.println("그는 " + h.getName() + "이고, 나이는 " + c.getAge() + "입니다.");
}
}
class Car {
private String getName;
private int getAge;
private String getGender;
public Villager(String getName, int getAge, String getGender) {
this.getName = getName;
this.getAge = getAge;
this.getGender = getGender;
}
public String getName() {
return getName;
}
public String getAge() {
return getAge;
}
}
//Output
그는 Harvey이고, 나이는 25입니다.
Villager 인스턴스 생성 시 매개변수가 있는 생성자를 사용하게 되면
인스턴스를 만든 후 인스턴스의 필드값을 일일이 설정해줄 필요가 없다.
→ 생성과 동시에 원하는 값으로 설정해줄 수 있어 편리하다.
→ 기본 생성자의 경우에는 매개변수가 없었기 때문에 new 키워드와 생성자를 호출하면 됐지만,
매개변수가 있는 경우에는 그 개수와 타입에 알맞게 생성자를 호출해주어야 한다.
‣ 이너 클래스
: 클래스 내부의 클래스.
import java.util.Scanner;
public class SimpleCalculatorTest {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in); // 외부 입력을 받을 수 있는 Scanner 객체 생성
System.out.println("첫 번째 숫자를 입력하세요.");
String str1 = scan.nextLine(); // 첫 번째 숫자 입력
System.out.println("사칙연산자를 입력하세요.");
String op = scan.nextLine(); // 사칙연산자 입력
System.out.println("두 번째 숫자를 입력하세요.");
String str2 = scan.nextLine(); // 두 번째 숫자 입력
int num1 = Integer.parseInt(str1); // 입력받은 첫 번째 문자를 숫자형으로 변환
int num2 = Integer.parseInt(str2); // 입력받은 두 번째 문자를 숫자형으로 변환
int result;
if(op.equals("+")) { // 덧셈 연산
result = num1 + num2;
}
else if(op.equals("-")) { // 뺄셈 연산
result = num1 - num2;
}
else if(op.equals("/")) { // 나누기 연산
result = num1 / num2;
}
else {
result = num1 * num2; // 곱하기 연산
}
System.out.println(str1 + " " + op + " " + str2 + " = " + result); // 결과값 출력
}
}
‣ 멤버 내부 클래스
: 인스턴스 내부 클래스와 정적 내부 클래스를 하나로 묶어 통칭한다.
→ 인스턴스 내부 클래스는 객체 내부에 멤버의 형태로 존재하며,
외부 클래스의 모든 접근 지정자의 멤버에 접근할 수 있다.
class Outer { //외부 클래스
private int num = 1; //외부 클래스 인스턴스 변수
private static int sNum = 2; // 외부 클래스 정적 변수
private InClass inClass; // 내부 클래스 자료형 변수 선언
public Outer() {
inClass = new InClass(); //외부 클래스 생성자
}
class InClass { //인스턴스 내부 클래스
int inNum = 10; //내부 클래스의 인스턴스 변수
void Test() {
System.out.println("Outer num = " + num + "(외부 클래스의 인스턴스 변수)");
System.out.println("Outer sNum = " + sNum + "(외부 클래스의 정적 변수)");
}
}
public void testClass() {
inClass.Test();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
System.out.println("외부 클래스 사용하여 내부 클래스 기능 호출");
outer.testClass(); // 내부 클래스 기능 호출
}
}
// 출력값
외부 클래스 사용하여 내부 클래스 기능 호출
Outer num = 1(외부 클래스의 인스턴스 변수)
Outer sNum = 2(외부 클래스의 정적 변수)
→ 인스턴스 내부 클래스는 외부 클래스의 내부에 위치해 있으며
private 접근 제어자(해당 클래스 안에서만 접근 가능한 멤버에 사용)를 사용하고 있음에도
내부에서 외부 클래스의 인스턴스 변수와 정적 변수에 각각 접근하여 해당 값을 사용하고 있다.
⇒ 인스턴스 내부 클래스는 반드시 외부 클래스를 생성한 이후에 사용해야 한다.
따라서 클래스의 생성과 상관없이 사용할 수 있는 정적 변수와 정적 메서드는 인스턴스 내부 클래스에서 선언할 수 없다.
⇒ 내부 클래스는 기본적으로 외부 클래스의 존재에 의존하고 있다.
‣ 정적 내부 클래스
: 내부 클래스가 외부 클래스의 존재와 무관하게 정적 변수를 사용할 수 있게 하기 위해 사용하는 것.
→ 정적 내부 클래스는 인스턴스 내부 클래스와 동일하게 클래스의 멤버 변수 위치에 정의하지만 static 키워드를 사용한다.
class Outer { // 외부 클래스
private int num = 3; // 내부 클래스의 인스턴스 변수
private static int sNum = 4;
void getPrint() {
System.out.println("인스턴스 메서드");
}
static void getPrintStatic() {
System.out.println("스태틱 메서드");
}
static class StaticInClass { // 정적 내부 클래스
void test() {
System.out.println("Outer num = " +sNum + "(외부 클래스의 정적 변수)");
getPrintStatic();
// num 과 getPrint() 는 정적 멤버가 아니라 사용 불가.
}
}
}
public class Main {
public static void main(String[] args) {
Outer.StaticInClass a = new Outer.StaticInClass(); //정적 이너 클래스의 객체 생성
a.test();
}
}
//출력값
Outer num = 4(외부 클래스의 정적 변수)
스태틱 메서드
‣ 지역 내부 클래스
: 클래스의 멤버가 아닌 메소드 내에서 정의되는 클래스→ 지역 변수와 유사하게 메소드 내부에서만 사용 가능함.일반적으로 메소드 안에서 선언 후 바로 객체를 생성해서 사용함.
class Outer { // 외부 클래스
int num = 5;
void test() {
int num2 = 6;
class LocalInClass { // 지역 내부 클래스
void getPrint() {
System.out.println(num);
System.out.println(num2);
}
}
LocalInClass localInClass = new LocalInClass();
localInClass.getPrint();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}
// 출력값
5
6
→ 지역 내부 클래스 LocalInClass가 메소드 안에서 선언되고 생성된 후에
정의된 메소드를 호출하여 외부 클래스의 변수들을 출력하고 있다.
4. 인스턴스(Instance)
자바에서 모든 클래스는 객체
public class myClass {
public String myName() {
return "Haley";
}
}
// App.java
public static void main(){
// 인스턴스는 new 키워드로 선언할 수 있다
// 인스턴스화
myClass my = new myClass();
my.myName();
// myClass my는 객체를 만든 것
// new myClass()는 인스턴스화 한것
// 리턴 값을 바로 사용하고 싶을 때는 println() 안에 함수를 선언해도 된다
// System.out.println(my.myName());
}
→ 자바에서 객체는 자료형으로 취급된다.
→ myClass도 참조 자료형으로 볼 수 있다.
5. this
: 객체 내부에서 인스턴스 멤버에 접근하기 위해 사용한다.
(자바스크립트에서의 this는 다양한 용도로 사용하지만 자바는 한 가지의 용도로 사용한다)
// UserData.java
package Data;
public class UserData {
private String name;
private String age;
public UserData(){
} //클래스 기본 생성자. 클래스가 인스턴스 되면 실행된다.
//Setter, Getter 메소드 입니다.
//클래스 내부의 private 변수나 메소드에 접근하기 위해 사용합니다.
public void SetName(String name){
this.name = name;
} // name 변수에 값을 적용한다.
public void SetAge(String age){
this.age = age;
} // age 변수에 값을 적용한다.
public String GetName(){
return this.name;
} // 객체에 name 변수를 리턴한다.
public String GetAge(){
return this.age;
} // 객체에 age 변수를 리턴한다.
// 메소드를 호출하면 객체의 age 변수에 1일 더하는 메소드.
public void AddAge(){
this.age = this.age + 1;
}
}
public UserData(String name, String age){
this.name = name;
this.age = age;
} // 클래스 생성자. 클래스가 인스턴스 되면 실행된다.
this는 주로 생성자와 메소드의 파라미터 이름이 필드와 같을 경우, 인스턴스 멤버의 필드임을 명시하고자 할 때 사용한다.
따라서, 위의 생성자에 따라서 인스턴스를 생성한다면 파라미터로 들어온 name과 age를 가지는 유저 데이터의 인스턴스가 만들어지게 되고, this는 결과적으로 인스턴스를 의미하게 된다.
UserData SamData = new UserData(Sam, 22);
UserData Gus = new UserData(Gus, 43);
→ 파라미터값으로 생성자를 통해 인스턴스를 만든다면 여기서 this는 각각의 인스턴스를 의미한다.
public class ConstructorExample {
public static void main(String[] args) {
Villager h = new Villager("Harvey", 25, "Male");
System.out.println("그는 " + h.getName() + "이고, 나이는 " + c.getAge() + "입니다.");
}
}
class Car {
private String getName;
private int getAge;
private String getGender;
public Villager(String getName, int getAge, String getGender) {
this.getName = getName;
this.getAge = getAge;
this.getGender = getGender;
}
public String getName() {
return getName;
}
public String getAge() {
return getAge;
}
}
//Output
그는 Harvey이고, 나이는 25입니다.
→ 이름만으로 인스턴스 변수와 매개변수를 구분하기 어려워질 때 구분하기 위한 용도로 this 키워드를 사용한다.
→ 모든 메소드에는 자신이 포함된 클래스의 객체를 가리키는 this라는 참조변수가 있는데,
일반적인 경우에는 컴파일러가 this.를 추가해주기 때문에 생략하는 경우가 많다.
ex) 현재 Villager 클래스의 getName이라는 인스턴스 필드를 클래스 내부에 출력하고자 한다면
원래는 System.out.println(this.getName) 라고 작성해주어야 한다.
⇒ this는 인스턴스 자신을 가리킨다.
참조변수를 통해 인스턴스의 멤버에 접근할 수 있는 것처럼 this를 통해 인스턴스 자신의 변수에 접근할 수 있다.
(자바 프로그래밍에서 많은 경우 메서드의 지역 변수명이 필드명과 동일하게 구성되어있다)
6. this()
: 자신이 속한 클래스에서 다른 생성자를 호출하는 경우에 사용한다.
✷ this()메소드를 사용하기 위해 충족시켜야 하는 조건
① 반드시 생성자 내부에서만 사용할 수 있다.
② 반드시 생성자의 첫 줄에 위치해야 한다.
public class Test {
public static void main(String[] args) {
Example example = new Example(); // 기본 생성자
Example example2 = new Example(5); // int 타입의 매개변수를 받고 있는 생성자
}
}
class Example {
public Example() {
System.out.println("Example의 기본 생성자 호출!");
};
public Example(int x) {
this(); // 두 번째 생성자 내부의 첫번째 줄에 사용한 this()메소드
System.out.println("Example의 두 번째 생성자 호출!");
}
}
//Output
Example의 기본 생성자 호출!
Example의 기본 생성자 호출!
Example의 두 번째 생성자 호출!