개발/Java

☕Java:: {OOP(Object Oriented Programming, 객체 지향 프로그래밍) - 스태틱 & 생성자} rough note

hyuunii 2022. 9. 4. 19:24

OOP - 6 Static / 스태틱

class Foo를 만들고

✔static String classVar = "I class var" →변수 하나의 이름은 classVar고, 앞에 static이 붙어 있으므로 얘는 class 소속.

System.out.println(Foo.classVar); // OK

✔String instanceVar = "I instance var" →또 하나는 instanceVar라는 이름의 변수인데, 얘는 앞에 static이 없으므로 instance 소속.

System.out.println(Foo.instanceVar); // Error (클래스 통해서 직접 인스턴스에 접근할 수 없음)

✔static classMethod

System.out.println(classVar); // Ok

System.out.println(instanceVar); // Error (클래스 메쏘드 안에서는, 클래스 변수에는 접근이 되는데 인스턴스 변수에는에 접근할 수 없음)

✔instanceMethod(인스턴스 메쏘드 안에서는 클래스 변수 및 인스턴스 변수 모두에 접근 가능함)

System.out.println(classVar); // Ok

System.out.println(instanceVar); // Ok

Foo.classMethod();

Foo.instanceMethod(); //error(인스턴스 메쏘드는 인스턴스 소속이므로 클래스 통한 접근은 금지됨)

instance f1 만들고

✔static String classVar = "I class var" →변수 하나의 이름은 classVar고, 앞에 static이 붙어 있으므로 얘는 class 소속. 그러므로 f1에는 실제 값이 존재하는 게 아니라 그냥 class Foo를 가리키고 있을 뿐.

✔String instanceVar = "I instance var" →또 하나는 instanceVar라는 이름의 변수인데, 얘는 앞에 static이 없으므로 instance 소속. Foo에서 f1이라는 인스턴스가 생성될 때 instanceVar라는 변수가 생성되면서, 만약 클래스의 값이 셋팅되어 있다면 그 값까지 복제가 됨. 그리고 class의 String instanceVar과 instance f1의 String instanceVar은 서로 링크되어 있지 않기 때문에 후자를 바뀐다고 전자가 바뀌지 않음. 그러나 instance f1의 static String classVar를 바뀌면 class Foo의 Static String classVar 바뀌며 이것은 역도 동일.(시1발 존1나 이해 안됨😌)

✔static classMethod(클래스를 참조하는 메쏘드)

✔instanceMethod (금지: 클래스의 인스턴스 메쏘드를 복제한 것. 서로는 독립된 존재.)

class Foo의 변수를 바꾸면 모든 인스턴스의 변수의 값이 바뀜

인스턴스의 그 클래스 변수를 바꿀 수도 있는데, 그렇게 되면 클래스 변수가 바뀌며 걔를 사용하는 모든 인스턴스의 값이 바뀜.

생활코딩 '싱와'님 댓글 +)

이전부터 생각해왔지만 instance와 static 키워드는 정확하지 않은 뇌피셜이지만 제약회사의 오리지널약과 복제약들의 관계를 생각하면 이해가 편할것 같다.

오리지널약의 성분을 복사해가서 조금 더 싼 값에 팔거나, 대량생산하기 위해 파는 약들을 복제약이라고 한다.

이러한 복제약들의 성분은 기존 오리지널약들과 똑같은 성분을 가지고 있다.

특별히 오리지널약에서 중요한 성분들이나, 함부로 복사할 수 없는 성분들을 원래 제약회사에서 "Static" 표시를 해놓는다.

복제약들은 그 성분을 함부로 건드릴 수 없으며, 단지 원래 성분이 이것이라고, 지칭해놓을 뿐이다. 그래도 가져다 써야한다.

만약에 복제약회사에서 static이라고 되어있는 이러한 부분을 건드려 성분을 조작한다면 원래회사의 신뢰도를 위해 바뀐 내용그대로 원래회사에서도 바뀌어야만 한다.

그런데, 원래 제약회사에서 조금씩 풀어주는 부분이 있다. 복제약들을 만드는 회사들은 기회다.

static 이 붙어 있지 않은 부분들을 조금씩 개조해서 더 좋은 약을 만들고는 한다.

이때 이건 이 회사의 개발역량에 달린거다. 이거 바뀐다고 원래회사에서 성분 바꿔야하는 큰일? 벌어지지 않는다.

정리) 원래 제약회사 Class는 static으로 특허권을 가지고 있다. 이건 함부로 복제해서 못건드린다.

그래서 instance가 static의 내용을 바꾸기 위해서는 원래 제약회사인 class와 충분한 협의를 거쳐 동의하에 같이 바꾸어야 한다. instance가 static 건드려서 바꾸면 class에서도 무조건 바꾸어야하고, 이 약을 복제해간 다른 회사도 무조건 바꾸어야 한다.

그니까. 함부로 instance 복제약 회사가 멋대로 바꿀 수 없다.

그런데, static이 붙어져 있지 않은 오픈소스의 개발부분은 instatnce 회사가 그냥 자유롭게 개발할 수 있다.

이거 바꾼다고 Class측에서 득달같이 법무팀 데리고 와서 따지지 않는다. 자기들이 풀어준거니까.

생활코딩 HalfMoon'님 댓글 +)

Static - 공유폴더의 내용물. 안의 내용물을 볼 수 있고, 사용할 수 있지만, 폴더의 내용물을 수정하면 해당 폴더를 공유하고 있는 모든 사용자에게도 수정된 결과물이 보여짐

ex) 공유폴더 안에 있는 txt 파일의 내용을 수정하면, 다른 사용자 또한 내가 수정한 내용을 보게 됨.

Instance - 공유폴더의 내용물을 내 컴퓨터로 복사한 것. 그렇기 때문에 내가 수정한 것은 내게만 영향을 미침

ex) 공유폴더 안에 있는 txt파일을 복사해서 내 컴퓨터에 가져온 뒤 내용을 수정


OOP - 7 생성자(Constructor)와 this

개괄) 생성자? 인스턴스를 생성할 때 해야 할 초기화 작업을 정의하는 것. 인스턴스를 생성할 때 딜리미터 값을 지정하지 않으면, 우리가 만든 클래스가 인스턴스화되지 못하도록 한다면 사용자의 실수 가능성을 원천 차단 가능.

목표) 딜리미터를 인스턴스를 생성할 때 지정하는 것. constructor의 정의가 필요함.

🔥constructor은 아주 특수한 메쏘드를 구현할 기능을 제공하고, 얘의 주요한 작업은 '초기화'임.

🔥클래스와 똑같은 이름의 메쏘드를 정의하면 걔가 바로 생성자임.

🔥인스턴스를 생성할 때, 자바는 클래스와 동일한 이름의 메쏘드가 있다면 그 메쏘드를 호출하도록 약속됨.

🔥클래스가 인스턴스화 될 때 실행될 코드를 컨스트럭트 메쏘드 안에 정의함으로써 초기화의 목적을 달성할 수 있음.

🔥생성자는 초기에 주입할 필요가 있는 값을 전달하거나 초기 작업을 수행하도록 하는 데 사용.

🔥this라는 특수 키워드는 그 클래스가 인스턴스화 되었을 때 인스턴스를 가리키는 특수한 이름임.

생활코딩 '싱와'님 댓글 +)

초기화라는것은 어떤 일들을 시작하기 전에 하는 루틴같은겨.

왜 야구선수 투수들이 등판하기 이전에 루틴으로 커피를 세잔 무조건 마셔야한다던지,

잠은 왼쪽으로 그날은 자야한다던지.. 등판하기전에 무조건 해야하는 그런것들 있잖슈?..

그런게 초기화다..

그런것 처럼, 프로그램도 이것이 실행되기 이전에 무조건 실행되야하는 과정들이 있을 수 있단 말이야..

그런데 자식이 부모마음 모르듯, 사용자들은 개발자들의 의도 그런거 몰라.. 야구선수 선발 루틴? 그런게 알게뭐야..

그냥 쓰는거야. 그럼 어떻게 되겠어? 프로그램 에러나지. 그날 게임 진단 말이여.

그.래.서.

아싸리 이걸 그냥 강제시켜버리는겨. 사용자는 영문도 모르고 이거 해야한다니까 하는데, 왜하는지는 몰라.

근데 강제로 시키니까 개발자 의도 정확하게 따라가버리고, 초기값도 그냥 셋팅되버리네? 오류가 줄어드네?

엄마가 공부하라고 할때는 하기 싫었는데 하고나니까 엄마의 의도를 알아버렸네?!

이러한 놈들을 "생성자, Constructor" 라고 한다. 이말입니다.

요놈들은 인스턴스를 생성할때 원래 클래스에 클래스 이름하고 똑같은~ 이름을 가진 놈을 메소드로 만들어버리는거다.

Public sangsungza() {} 이렇게..

예를 하나 들어보자.

내가 인스턴스를 하나 만들었는데, 요놈에 매개변수(파라미터)를 추가해서 특정 정수값이 무조건 들어가게 하고 싶네?

원래 클래스 이름이 sangSungza 라고 한다면,

Class sangSungza {

int a ;

public sangSungza(int a) {

this.a = a;

(후략)

}

sangSungza foo = new sangSungza(1) {

.

.

}

이런식으로 초기에 1이라는 정수값을 지정해줄 수 있다는 사실~

그래서, 정리해볼까?

어떤 객체를 만들때, 그 객체가 반드시 해야할 일들이 있다면, 그것은 별도의 메소드로 만들어서 그 절차를 사용자가 일일히 숙지하는것이 아니라, 그냥 생성자에 그 절차를 포함시켜버려서 객체를 사용하는 사용자가 그 절차를 숙지하는 과정을 생략하고, 반드시 해야하는 일들을 놓치지 않게 함으로 오류를 줄이고 편의성을 증대시키는것이 생성자다!!

public class myOOP {
public static void main(String[] args) {
이름이름 p1 = new 이름이름("----");
p1.A();
p1.A();
p1.B();
p1.B();
이름이름 p2 = new 이름이름("****");
p2.B();
p2.B();
p2.A();
p2.A();
 
p1.B();
p2.A();
p1.B();
p2.A();
}
}
public class 이름이름 {
public String delimiter = "";
public 이름이름(String delimiter) {
this.delimiter = delimiter;
}
public void A() {
}
public void B() {
}
}

정리 및 활용 1

객체지향(클래쓰) 도입 전
객체지향(클래쓰) 도입 후
public class AccountingApp {
…VAT 계산과는 상관 없는 1억줄
public static double valueOfSupply = 10000.0;
…VAT 계산과는 상관 없는 1억줄
public static double vatRate = 0.1;
 
public static double getVAT() {
return valueOfSupply * vatRate;
}
 
public static double getTotal() {
return valueOfSupply + getVAT();
}
 
public static void main(String[] args) {
 
 
}
 
}
class Accountings{
②public static double valueOfSupply;
public static double vatRate = 0.1;
public static double getVAT() {
return valueOfSupply * vatRate;
}
public static double getTotal() {
return valueOfSupply + getVAT();
}
}
public class AccountingApp {
public static void main(String[] args) {
Accountings.valueOfSupply = 10000.0;
 
}
 
}

왼쪽에 …VAT 계산과는 상관 없는 1억줄 때문에 정리 정돈이 필요한 상황! 정리 정돈의 핵심은 서로 비슷한 것끼리 모아 이름을 붙여주는 것. 즉,

☞①AccountingApp 클래스 안의 부가가치세와 관련된 변수, 메쏘드들을 모아서 Accounting이라는 클래쓰를 만들어주자!

☞②그 후 VAT와 연관된 코드들만 컷해서 이동시킴!

☞③이후 예컨대 valueOfSupply는 Accontings class 밑으로 들어가 있으니, Accoutings.valueOfSupply = 100000; 으로 클래스명을 명시해줌!

이렇게 하면

🍋Accountings.( )의 ( ) 부분 즉 메쏘드가 회계와 관련된 메쏘드란걸 명확하게 밝혀줌!

🍉getTotal이라는 Accountings 말고 다른 클래스에 속할 수 있으니까 getTotal이라는 이름의 메쏘드가 우리 프로그램 안에서 서로 다른 클래쓰에 소속됨을 통해 공존할 수도 있다~!

🥭즉, 이 프로그램 사용자들이 자기가 필요한 걸 훨씬 빠르게 찾을 수 있음!


정리 및 활용 2

한 번은 1만원에 대한 공급가액을 출력하고, 또 한 번은 2만원에 대한 공급가액을 출력하고, 갑자기 1만원에 대한 VAT를 출력하고, 또 갑자기 2만원에 대한 VAT를 출력하고... 즉, 클래스의 상태가 계속해서 변화하는 상황! ☞인스턴스 도입 必

if)1만원 2만원이면 덜한데.. 막 수백 수천개를 동시에 바꿔야하고, 어떤 하나의 클래스의 상태가 valueOfSupply 단 하나가 아니라 수십 수백개의 내부적인 상태로 이루어져있고 걔를 계속해서 바꿔야 함? 굉장히 높은 확률로 버그 발생.

☞왜?! 우리는 여러 상태에 어카운팅이라는 단 하나의 클래스로 돌려막기 하고 있기 때문..!

∴그러면?! 인스턴스를 도입!!(Accountings a1 = new Accountings();, Accountings a2 = new Accountings();) 이렇게 하면 어카운팅이라는 클래스를 복제별도의 인스턴스를 만들어, 걔들마다 고유한 상태를 주게 되므로 독립된 인스턴스 제어가 가능함~!

🍡a1.valueOfSupply는 인스턴스에 소속된 변수임. 그러므로 ①public static double valueOfSupply에서 static이 빠져야 함.

🍨②를 보면 getVAT라는 메쏘드는 인스턴스 변수인 valueOfSupply에 접근하는데 얘가 static이면 접근 불가. static은 클래스 소속인데, 이 클래스가 인스턴스의 valueOfSupply에 접근하려면 어떤 인스턴스(a1인지 a2인지) 모르니까. 그렇기에 ②public static double getVAT() 에서 static 제거 + ③public static double getTotal()에서도 static 제거 필요

🍮결과로 a1과 a2는 독립적인 상태를 유지하므로 우리는 단지 호출만 하면 안심하고 인스턴스를 사용 가능~!

🍱vatRate는 한국 내 어딜 가나 0.1이므로 이건 인스턴스 말고 클래스 소속으로 두는게 더 편함ㅋ 인스턴스를 만들때마다 메모리를 쓰지 않아도 되므로 컴퓨터 자원 절약도 되고! vatRate의 값을 변경한다고 하면 클래스에서만 바꿔주면 얘를 사용하는 모든 인스턴스에서 한번에 바뀌는 유지보수의 편리함도 누리고!(클래스 소속 변수 사용의 장점들)

🍣만약, 인스턴스가 생성 될 때 그 인스턴스가 내부적으로 꼭 valueOfSupply를 갖도록 생성자 레벨에서 강제하려면?

(1)public Accountings(){ }으로 어카운팅 메쏘드 구현하기. 그 때 우리는

(2)Accountings a1 = new Accountings(10000); 이렇게 인스턴스가 생성될 때 10000원이라는 인자가 public Accountings(double valueOfSupply)에 매개변수로 전달이 되면서, 인스턴스의 변수인 valueOfSupply의 값이 세팅됨.

생성자가 호출될 때 인자(10000.0)를 매개변수로 전달하고 싶기 때문에,

(1)을 다시 public Accountings(double valueOfSupply) {

this.valueOfSupply = valueOfSupply;}로 처리하기((좌변의 this.valueOfSupply는 ①public static double valueOfSupply의 valueOfSupply, 우변의 valueOfSupply는 (1)public Accountings(double valueOfSupply)의 valueOfSupply 즉 매개변수))

객체지향(인스턴스) 도입 전
객체지향(인스턴스) 도입 후
최종
class Accountings{
public static double valueOfSupply;
public static double vatRate = 0.1;
public static double getVAT() {
return valueOfSupply * vatRate;
}
public static double getTotal() {
return valueOfSupply + getVAT();
}
}
public class AccountingApp {
public static void main(String[] args) {
Accountings.valueOfSupply = 10000.0;
Accountings.valueOfSupply = 20000.0;
 
Accountings.valueOfSupply = 10000.0;
Accountings.valueOfSupply = 20000.0;
 
Accountings.valueOfSupply = 10000.0;
Accountings.valueOfSupply = 20000.0;
 
}
 
}
class Accountings{
public double valueOfSupply;
public static double vatRate = 0.1;
public double getVAT() {
return valueOfSupply * vatRate;
}
public double getTotal() {
return valueOfSupply + getVAT();
}
}
public class AccountingApp {
public static void main(String[] args) {
Accountings a1 = new Accountings();
a1.valueOfSupply = 10000.0;
 
Accountings a2 = new Accountings();
a2.valueOfSupply = 20000.0;
 
 
}
 
}
class Accountings{
public double valueOfSupply;
public static double vatRate = 0.1;(1)public Accountings(double valueOfSupply) {
this.valueOfSupply = valueOfSupply;
}
public double getVAT() {
return valueOfSupply * vatRate;
}
public double getTotal() {
return valueOfSupply + getVAT();
}
}
public class AccountingApp {
public static void main(String[] args) {
(2)Accountings a1 = new Accountings(10000.0);
 
Accountings a2 = new Accountings(20000.0);
 
 
}
 
}