[항해99] 주특기언어 3일차 TIL - 왜 벌써 객체지향이지

LIHA·2023년 1월 20일
0

항해99

목록 보기
19/54
post-custom-banner

어제 건호님이 늦게까지 자바 과제를 도와주셨는데, 들어는 봤는데 알수 없었던 말이 나왔다.

나: 클래스 변수는 멤버변수라고도 하던데 뭐가 뭔지 모르겠어요ㅠㅠ 지역변수면 그 this. 쓰는건가요?
건호님: 아뇨 그건 인스턴스 변수에요. 자기 자신을 불러오는거죠.
나: ???????🤯🤯🤯🤯🤯🤯🤯
건호님: 껄껄 나중에 객체지향 하면 알게 되실거에요. 지금은 너무 어렵게 생각하지 마세요

그런데 자고 일어나니 객체지향 하는 날이 되었다. 살려주세요 건호님. 그건 나중이 아니었어요.😭

(아니, 이렇게 쓰면 저같은 뉴비는 어떻게 알아요!)

클래스 변수와 인스턴스 변수는 둘다 클래스에 위치한건 맞지만, 클래스 변수는 아무데서나 그냥 접두어 없이 불러올 수 있다.

static이 붙은 변수는 클래스 변수. static이 나 클래스 변수야! 하는 일종의 딱지. (Sonny의 주장 완장같은 느낌)

또한, 클래스 변수는 객체 생성해서 참조변수로 쓰는 형태는 권장하지 않는다. 가능은 하고 동작도 하지만, 권장은 안함. 어지간하면 클래스 변수는 클래스 이름.변수이름 으로 사용해주자.

반면 인스턴스 변수는 그 변수가 위치한 클래스 타입의 인스턴스를 선언해줘야 사용할 수 있다고. -> 아니 이게 뭔 소리에요?

-> 이를테면 아래의 예시의 경우, int mintchoco를 메인메소드에서 갖다가 쓰려면, Mycookie cookie = new Mycookie(); 라는 식으로 선언해주고 밑에서도 cookie.mintchoco 라고 써야 불러와서 사용할 수 있단 얘기임. 이때 저 cookie는 참조변수 라고 한다.
클래스 변수가 아니면 일정한 절차를 거쳐야만 가져다 쓸수 있다는것!

class Mycookie {
  static int raisin;
  int mintchoco;

	void method()
    int raspberry;
    
//내 쿠키 클래스에 쿠키가 있는데, 건포도는 싫으니까 아무나 가져가라지! (클래스 변수)
//민초는 좋으니 함부로 못 가져가게 (인스턴스 변수)로 설정해놨다.
//라즈베리는 짱 맛있으니까 보물상자에 넣어둬야지. 지역변수로만 쓰자.
}

public class Main {
public static void main(String[] args) {
	
    Mycookie.raisin = 10;
    // 음 좋아. 내가 싫어하는 레이즌은 아무나 잘 가져가 쓰는군.
	Mycookie cookie = new Mycookie();
    cookie.mintchoco = 15;
    // 이런, 내가 좋아하는 민초쿠키도 누군가 참조변수를 통해서 쓰고있군.
    cookie.method();
    raspberry = 30;
   
   // 아니!! 가장 구석인 보물상자에 접근했어! 지역변수까지 쓰다니! 
   // 매개변수 쓰고 메소드 호출하면 지역변수 쓸수 있는건 어떻게 안거지!?
}
}

으아아 도저히 모르겠어요!! 객체랑 인스턴스랑 뭐가 다른거에요!?🤯🤯🤯

객체 : 모든 인스턴스를 대표하는 일반적인 용어 (Tv 객체)
인스턴스 : 특정 클래스로부터 생성된 객체 (Tv 클래스를 사용해서 만든 Tv인스턴스)
인스턴스화 : 클래스 => 인스턴스(객체)

라고 한다. 뭔가 잘 안 와닿는데?
🤔 다른 설명도 들어보자
또다른 설명도 들어보자

클래스 : 붕어빵 틀
객체 : 붕어빵(인스턴스의 총칭)
인스턴스 : 각각의 붕어빵 그 낱개를 가리킴(팥붕, 슈붕, 초코붕 등등)
매개변수: 괄호 안에 넣는것. 네?! 그게 뭔데요?!
Cookie(shape,name,color){
this.shape = shape;
this.name = name;
this.color = color;
여기서 Cookie() 안에 들어가는 shape, name, color가 매개변수!

C-style array가 뭔 배열이야? 🤔

여윾시 없는게 없는 스택 오버플로우. 믿고 있었다굿
스택 오버플로우 좌의 대답은 다음과 같다.

A C-Style array is just a "naked" array - that is, an array that's not wrapped in a class.
아, 이거 C언어 스타일이라서 C-style이라고 부르는구나! C 스택 같은거군.

They are semantically identical. The int array[] syntax was only added to help C programmers get used to java.
int[] array is much preferable, and less confusing.

  • 근데 C-style array는 왜?
    -> 과제 중에 int[] arr[] 라는 형태가 있었는데, 얘를 입력해봤더니 intelliJ가 'C-style array declaration of local variable' 라고 하더라. 그래서 아 틀렸나보다? 했었지.

웬걸. JAVA에서 C 문법을 써도 틀리지 않는게 이차원 배열 부분이구나.

->C언어는 int[] arr[] 이런식으로 선언하나보다.
C에서 넘어온 JAVA 유저들을 위한 기능이었다.

원래 C언어 프로그래머를 위한거였구나! 고마워요 스택 오버플로우 웨건!

n번째와 n번 인덱스는 무덤에서도 헷갈릴 셈인가

다음과 같은 배열이 있을 때, arr[3].length의 값은?
int[][]arr ={
    {5,5,5,5,5},
    {10,10,10},
    {20,20,20,20},
    {30,30}
};

여기서 아주 당연하게 4라고 썼는데 sout 해보니 2가 나왔다.
인덱스 값 3번이면 0 1 2 3 이므로 {30, 30} 이므로 2가 맞는데🙄

length-1에 익숙해졌더니 length를 써야 하는 곳에서 틀렸다.

        int[] arr = {10, 20, 30, 40, 50};
        int sum = 0;
        int i;
        for (i = 0; i < arr.length ; i++) {
            sum += arr[i];
        }
        System.out.println(sum);
  • 이거 왜 length - 1이 아니라 length를 써야 할까?
    -> i++가 후위연산자라서 length-1까지만 하면 참조만 되고 i++ 한게 sout에 반영되지 않는다. 이거 length -1 까지만 하면 sout에 100 찍힘.

뙇.

2차원 배열에서 가로세로 길이는 어떻게 구해요?

int[][] arr ={
    {5,5,5,5,5}, //index 0번의 array
    {10,10,10,10,10}, //index 1번
    {20,20,20,20,20}, //index 2번
    {30,30,30,30,30} //index 3번
};

예를들어 이런 배열이 있다고 가정할때, row=4고 column=5임. 배열 당 요소 갯수가 다 같다는 전제 하에,
세로 길이(= row의 갯수. 곶감 꿰미 수) = arr.length
가로 길이 (= column의 갯수. 꿰미 당 곶감 수) = arr[i].length (전체의 가로길이 아님! i번째 index의 가로길이(column 수) 묻는 것임.
예를 들면 {5, 5, 5, 5, 5} 의 column 갯수를 알고 싶다 = arr[0].length 하면 된다.

가변길이 배열은 뭔데요?

-> 2차원 배열의 길이가 다 다른것을 말합니다. 첫번째 배열은 3, 두번째는 5, 세번째는 4 이런 식인 배열. Jagged Array 라고 함. 가변 2차원 배열에 대한 참고글!
예를 들어봅시다.

int[][] arr = {
	{1, 7, 2, 9, 4}
    {3, 1}
    {9, 7, 6}
	{5, 3, 6, 8}
}
  • 얘네의 길이는 어떻게 구해요?
    ->배열이름[i].length 하시면 됨. 그게 길이이자 column의 갯수임.
  • column이요? column이 뭐여 대체?
    -> 첫번째 줄의 경우 row index는 0번, 요소가 5개니 column도 5개.
    index로는 4번까지 있는것. (0, 0 ~ 0, 4)
  • 그럼 이런 배열에서 그냥 배열이름.length 하면 무슨 값인가요?
    -> row의 갯수임. row 하나를 곶감 꿰어있는 궤짝(?)이라고 치면,
    arr.length 하면 너 곶감 궤짝 총 몇개 갖고있냐는거임. 여기선 4 나올 것.

평균치를 소숫점까지 알고싶은데 변수가 int네. 어쩌지?

-> 중간에 자료형을 float로 바꿔치기 합시다. 풀었던 예시를 들고와보자.


        int total = 0;
        float average = 0;
        
        int i;
        for (i=0; i< arr.length; i++){
            int j;
            for(j=0; j< arr[0].length; j++){
                total += arr[i][j];
                float f = total;
                average = f / (arr.length * arr[0].length);
            }
        }

여기서 average가 float로 선언되어 있어도 total이 int로 선언되어 있어서,
이대로 나누면 평균값은 int가 되어버린다. (결과 찍으니 그렇더라.)
좌항을 float로 선언해도 우항이 int / int이니 소수점은 찍히지 않았다.

  • ->그렇다면 나눠지는 수를 float으로 바꿔주자! (float f = total;)
    이 후에 average 값을 찍었더니 무사히 소수점 아래까지 결과를 얻었다. 😋

웰컴! 여기부터 객체지향으로 넘어갑니다.


Class를 굳이 선언하고 그 안에 함수를 쓰는 이유가 뭘까?

  • 중복코딩을 방지하고 편하게 관리하기 위해서, 그리고 재사용도 가능해서.

교과서같은 답변 말고요! 뭔소린지 모르겠다고요 ㅠㅠ

예를들면 가위 칼 이런건 한군데에 미리 정해놓고 갖다 쓰는게 편하다. 필요할때마다 도처에 구매하면 비용(여기선 메모리일 것) 도 너무 많이 들고, 물건(여기선 코드일 것) 도 너무 많아진다!

int x + y 하나 하는데 내내 복붙복붙 할 수는 없으니까, Math 라는 class를 선언하고 그 안에 add 라는 이름의 메서드를 선언해서, 덧셈만 하는 애를 만들고 필요할때마다 불러와서 쓰는게 좋다는 것!

-> 이걸 게임에 적용해보면, 캐릭터 생성 과정에서 매번 creatchar() 라는 함수를 만들어주고 복잡한 코드를 다 써줄 수는 없으므로, 어디 한군데에 만들어놓고 버튼 누르면 호출만 하게 하자는 것.

  • 그리고 이렇게 클래스, 객체, 메서드 개념을 도입해서 별도의 단위와 기능으로 묶어주는 것을, 객체지향의 핵심인 캡슐화(encapsulation) 라고 한다. 목적은 정보의 은닉에 있음.

네? 은닉이요? 갑자기 뭔소리야? 😫
-> 이런식으로 코드를 다루는 이유는, 객체 내부의 코드를 숨겨서 정해진 메소드에만 호출되어 쓰이게 하기 위함. 필요한 기능이 있으면 불러오게 할 뿐, 그 기능의 코드를 보여주진 않음.
OOP - 캡슐화와 은닉에 대해

지금은 잘 모를 수 있지만, 객체지향에서는 변수나 메서드같은 객체들을 너무 개방해도 너무 은닉해도 올바르진 않다. 적절한 개방과 은닉을 통해 적절한 모듈로 작동하게 만들어야 한다.

잠깐, 모듈은 또 뭐에요? 들어는 봤는데... 🤔

클래스와 모듈의 차이점
패키지와 모듈의 차이점
클래스는 내가 변수와 메서드를 선언해서 쓸수 있고, 모듈은 남이 이미 만들어놔서 내가 변수나 메서드를 새로 선언하거나 바꿀 수 없는 클래스들의 묶음을 말한다. 그냥 갖고와서 쓸수만 있다. import 해서 끌어오는게 모듈임.

변수 앞에 붙은 static과 함수 앞에 붙은 static은 뭐가 다른가요?

C언어에 대한 얘기지만 명쾌한 답변이 적혀있는 블로그
JAVA에 대해 설명해주는 블로그

일단 이 이미지를 보고 갑시다.

  • static 붙은 변수 = 클래스 변수에요. 인스턴스 없이도 존재하고, 어디서나 호출해서 쓸 수 있어요.
    예를 들어보자면...
	
    class Cookiebox {
    static int raisin; // 건포도는 싫으니까 아무데서나 호출해서 가져가게 만듭시다.
    int mintchoco; // 민초는 맛있어요. 아무나 가져가게 둘순 없지. 인스턴스 변수로 만들어야징.
    
    void delicookie(){
    int raspberry = 30;
    // 짱맛 라즈베리는 '맛난 쿠키박스'라는 메서드에 따로 넣어놔야지. 
    // 지역번수는 아무나 호출할수 없으니 쉽게 털리진 않을거야. 
    // 그리고 초기화 해줘야 쓸수 있으니 수량도 적어두자. 누가 가져가면 안돼.
    	
        } //메서드인 delicookie의 종료지점
    } //class인 Cookiebox의 종료지점
  • static 붙은 함수 = 여기서만 쓸거에요. 컴파일 단위 내에서만 호출하고, 외부로 노출하지 않아요.
    이것도 예를 들어보자면...
	class Main {
    public static void main(String[] args){
    raisin = 10; //좋아. 내가 싫어하는 건포도는 잘 나가고 있어.
    
    Cookiebox box = new Cookiebox();
    box.mintchoco = 5; // 이런! 누가 인스턴스를 생성했잖아! 
    				// 내 인스턴스 변수인 민초쿠키를 호출해다 념념 먹고있어!
    
    Cookiebox.delicookie();
    raspberry = 10;
    //젠장! 누가 메서드를 호출해서 지역변수인 라즈베리까지 가져다 쓰고있잖아!!
    
    	}
    }

그래서 static은 뭔 뜻이에요? - static 메모리 영역에 들어간다는 얘기

참고가 된 블로그

  • JAVA에서 메모리는 크게 두 영역이 있어요. 가비지 컬렉터(GC)가 관여하는 Heap 이라는 메모리 영역과, 관여하지 않는 static 이라는 메모리 영역.
    가비지 컬렉터가 관여하는 영역은 수시로 가서 불필요한 메모리를 슥슥 청소해줘요. 메모리를 반환시켜서 다시 써야하기 때문에.

  • 관여하지 않는다뇨? -> static 영역의 데이터는 그 application이 종료될때까지 지워지지 않아요.
    -> static은 원래 CG의 관할구역이 아니랍니다. (static 영역의 특성이에요. 원래 그렇게 디자인 됨.)

  • 그렇기 때문에, static 변수나 함수를 너무 많이 선언하면 메모리에 문제가 생길 수 있어요. 이게 게임이나 어플이라면 속도가 느려진다는 얘기.
    -> 그러니 진짜 자주 쓰이는 변수나 함수에만 static을 한정적으로 선언해줍시다.
    (예를 들면 메인메소드 같은 애들처럼 안 불릴 일이 없는 요소들은 static을 달고 있는게 당연!)

함수랑 메서드랑 다른건가요? 너무 헷갈려요 ㅠㅅㅠ

그럼 void를 쓰는 이유는 뭔가요?

-> 함수나 메서드에 void 안 쓰면 그 함수 안에 반드시 return (무언가); 라고 써줘야 해요.
void를 쓰는 이유는 함수 안에 return (무언가);를 안 써도 되게 만들려고 하는 것!

  • 그럼 void로 선언하면 뭐가 다른가요?
    -> 컴파일러가 컴파일할때 알아서 자동으로 return; 을 붙여서 처리해줍니다.
  • reuturn; 이 작동하면 실행중인 메서드를 종료하고 호출된 곳으로 돌아가용.

아니 기본형 매개변수랑 참조형 매개변수는 또 뭔데?!

-> 메서드의 괄호안에 써주는게 매개변수임. 기본형은 int, byte, char 이런 8가지의 정형 타입들을 말하고, 참조형은 기본형이 아닌 것. String이 대표적인 참조형임.

  • 메서드의 괄호 안에 int나 double 같은 기본형을 가진 매개변수를 써주면 그 값을 변경할 수 없고, Cookiebox box 이런 참조형 넣어주면 값을 바꿀 수 있다는 것임!

아니 그전에, 매개변수는 대체 뭔가요 🤯🤯🤯

-> 매개변수는 메서드의 괄호 안에 써주는 것이고, 어떤 입력값을 받아올지 그 형태를 써주는 것임. 쿠키코드를 들고와보자.

    class Cookiestore {
    Cookiebox cbox = new Cookiebox();
    }
    
   class Cookiebox {
    static int raisin; 
    int mintchoco; 
    
    void delicookie(){
    int raspberry = 30;
    
        } //메서드인 delicookie의 종료지점
    } //class인 Cookiebox의 종료지점
    
    class Main {
    public static void main(String[] args){
    
  static void changeParameter(Cookiebox cbox) 
  // 여기서 메서드의 괄호 안에 들어간 Cookibox cbox가 매개변수. 파라미터 라고도 한다.
  //이 매개변수가 기본형(int, double, char 등) 이면 기본형 매개변수고,
  // String 등 참조형이면 참조형 매개변수라고 부르는 것!
  

오버로딩? 같은 클래스, 같은 이름의 메서드, 다른 타입!

-> 한 클래스, 같은 이름의 메서드. But 개수나 타입이 달라야 한다!

  • 반환 타입은 영향을 받지 않는다!
    ->예를 들어
int add (int a, int b) { 
	System.out.println("int add(int a, int b)");
    return a + b;}
    // 이런 식으로 선언된 메서드가 있다면,
    
 void add(int a, int b) {
 System.out.println("void add(int a, int b)");
 }
 
 //이런 메서드를 쓴다고 다 오버로딩이 아니다! 얘네는 int a, b로 타입이 같기 때문!
  • 이거 왜 하는거에요? -> 입력 값이 다르지만 같은 기능을 수행할 때 필요!
    그런 경우가 있어요? -> 예를들면 성적 입력할때. 학생 번호와 성적만 입력하는 경우 둘다 저장해야 하지만 타입이 다를 것. 번호는 int, 성적은 float.

    생성자? 전에도 어려웠는데 이거 대체 뭐더라? 😵

  • 인스턴스가 생성될 때마다 호출되는 '인스턴스 초기화 메서드'
    =즉, 함수! 이름() 형태로 작성될것.

※이거 왜해요? 꼭 해야돼요? 초기화가 뭔데요?
▶네 꼭 해야돼요. 자바에서는 변수 사용하려면 반드시 초기화 해야 쓸 수 있으므로 필요함.
초기화 = 데이터를 변수(공간)에 집어넣는 과정.

  • 인스턴스는 클래스에 존재하지만 static이 붙지 않은 변수니까, 어찌됐건 변수라서 초기화 해줘야 쓸 수 있음. 그래서 생성자가 필요함.
  • 어려우면, 인스턴스 변수를 생성해줄때 꼭 거쳐가야 하는 곳이 생성자라고 생각하세용.

[선언법]
IntelliJ에서 Alt+Insert 입력 > Generator > Contructor 엔터하면 자동으로 생성해줌.


class Tv9_1 {
    // 속성 : 변수 선언
    boolean power; // 전원상태
    int channel;  // 채널
    String color; // 색깔
    long price; // 가격

    // 위 속성에서 필수로 초기값이 필요한 값들을 초기화 해주는 기본 생성자
    public Tv9_1() {
        power = false;
        channel = 1;
    }
  
  public Tv9_1(String color, long price) {
        power = false; 
        // this.power, power 둘다 지금 상황에서는 인스턴스 변수를 
       // 명확하게 판단 할 수 있기 때문에 어떤걸 사용해도 상관 없습니다.
        channel = 1;
        this.color = color;
        this.price = price;
    }
    
  • 생성자의 특징
    ▶생성자 이름은 class 이름과 똑같이 지어줘야 함
    ▶생성자는 return값이 없음(아예 무존재)
    ▶생성자 앞에는 접근제어자만 올수 있음(public 같은 것)
    ▶return값이 없으므로 void나 자료형(int 등)도 작성할수 없음! 뭐가 있어야 쓰지...
    (method는 void나 자료형 작성 가능! ex)메인메소드. public static void main)
    ▶overloading 할수 있음. (이름 같고 타입 다르면 여러개 쓸수 있어요)

클래스, 객체, 인스턴스. 아무리 봐도 헷갈린다?! 여기 설명이 잘 돼있어요

  • 잠깐 - 기억 나나요? 클래스를 생성하면 생성자는 사실 반드시 필요한데,
    우리는 그냥 클래스만 선언해도 문제없이 그냥 만들어졌다. 이유가 뭘까?

->클래스에 생성자가 한개도 없는 경우라면 컴파일러가 기본 생성자를 자동으로 추가해준다! 우리 눈에 안 보일 뿐.

잠깐, this. 은 뭐에요? this()는 또 뭐고?;;

-> 건호님이 말씀하신 인스턴스 변수 드디어 나왔네!

  • this. -> 인스턴스 자신(혹은 클래스 자신)을 가리키는 참조변수.
    ▶생성자를 포함한 인스턴스 메서드에서 사용할 수 있어요!
    ▶지역 변수와 인스턴스 변수를 구분할때도 사용해요.
    this.(변수명)이면 그 변수는 인스턴스 변수인거죠.

  • this() 는 생성자에서 다른 생성자를 호출할 때 사용!

▶아니, 그런 경우도 있어요?;;;;
-> 그런 경우 있어요! 오버로딩한 생성자에서 기본 생성자 불러올때.
같은 내용 또 쓰기 싫으니까, this(); 만 써주면 기본 생성자 내용 다 불러온게 됨!

▶아니, 그냥 기본 생성자에 모든걸 다 초기화 하고 기본 생성자만 호출하면 되잖아요?;;;
-> 기본 생성자에 반드시 초기값이 필요한 것만 초기화 해놓고 나머지는 그때그때 초기화해서 쓰는 상황 일 때는 이런 기능이 필요해요!
이 경우, 나머지 값들은 초기에 정할 수 없고 그때 가서 바꿔야만 하는 상황이니까 이렇게 하는걸거에요.

변수의 초기화 - JAVA와 C의 차이요? 🤔

  • 지역 변수는 수동으로 초기화 해야합니다.
    지역 변수가 동작하는 스택 메모리는 재사용이 빈번하기 때문에 매번 초기화 해주면 성능이 떨어집니다.
  • 그래서 그냥 해당 메모리에 있는 값으로 덮어 씌웁니다.
  • 근데 해당 주소에 어떠한 값이 있는지 모르기 때문에 Java는 개발자에게 수동으로 초기화 하라고 요구합니다.
  • C 언어에서는 이를 garbage value 라 부릅니다.(C언어와 Java의 차이를 비교하면 이해가 쉽습니다.)

▶여기서 'C언어와 Java의 차이를 비교하면 이해가 쉽습니다.' 라고 써있는데 둘이 뭐가 다른가요? 전 C언어를 몰라요ㅠ

-> C에는 '쓰레기값'이라는 개념이 있는데 JAVA에는 없습니다. 그래서 JAVA만 배우면 당연히 모를수밖에 없어요.

-> C는 속도를 중시하는 언어라서, 변수를 선언만 하면 자동으로 초기화 해주지 않아요. 그래서 아무 값이나 들어가 있답니다. print 찍어보면 이상한 값이 나와요. 이게 garbage value임.

-> 하지만 JAVA는 C에 비해 편의를 중시하는 언어라, 클래스 변수나 인스턴스 변수라면 초기화를 자기가 알아서 해줘요.**
(지역변수는 메모리 문제로 니알아서 초기화해라 하고 초기화 안해줌!)

print 찍어보면 (숫자형이라면) 보통 0이 나옵니다.


그 외의 얘기들

  • 다들 contains를 도전하셨는데 replace를 쓰는게 더 낫다고.

  • 브루트 포스? 처음부터 끝까지 무식하게 for문 돌리는 것.

profile
갑자기 왜 춤춰?
post-custom-banner

0개의 댓글