클래스

반응형
반응형

클래스 정의하는 방법

클래스란 무엇일까요? 

프로그래밍에서 클래스는 설계도를 뜻합니다. 즉, 객체를 생성되기 이전에 만드는 코드라고 생각할 수 있습니다.

public class Name {}

기본적인 코드는 위와 같습니다.

처음 클래스를 공부하였을 때, 단순히 위 문장만 입력하면 된다고 생각했습니다.

이는 반은 맞고 반은 틀린 답이었습니다. 틀렸다는 것은 위 코드만 입력한다고 자바 jvm에서 코드를 만들어주지 않기 때문이죠.
그럼 어떻게 해야 할까요?

바로 파일명과 클래스명이 같아야 비로소 클래스를 정의했다고 할 수 있죠. 하지만 걱정 안 하셔도 됩니다. ide에서 파일을 만드는 것만으로도 이 모든 것을 해결할 수 있기 때문이죠. 즉, 그냥 파일만 만들면 된다는 뜻입니다.

그렇다면, 위 코드만 클래스를 만드는 방법일까요?
아닙니다.

먼저 위치에 따른 분류를 생각해 보겠습니다.

public class A {}
class B {}
public class A {
   public class B{}
}

이렇게 2가지로 나뉩니다. 

그러면 하나씩 살펴보겠습니다.

첫 번째, 이 방식은 class를 여러 개 즉, 외부 클래스를 여러 개 만드는 형태입니다. 이들의 특징은 각자 클래스로 인식되기 때문에 멤버 변수는 서로의 변수를 사용하는 것이 불가능합니다.

두 번째, 이 방식은 class안에 class가 들어가 있는 형태로, 이는 중첩 클래스라고 부르며 하위에 있는 클래스는 내부 클래스라고 부릅니다. 위 방식과 달리 이 방식은 A클래스의 변수를 B클래스에서 사용이 가능해집니다. 즉, A클래스의 모든 것은 B클래스에서 사용이 가능하다는 뜻입니다. 하지만 반대는 불가합니다.

이를 표로 그려 보겠습니다.

  1번 방식 2번 방식
(중첩 클래스)
서로의
맴버 변수 사용
불가능 가능
(반대는 불가능)
특징  자바파일을 만드는 방식과 같다.  

첫 번째 방식은 코딩 테스트 같은 특수한 경우를 제외하면, 일반적으로 자바 파일을 생성하는 것과 같습니다. public을 하나밖에 사용하지 못하는 것만 빼고요...

하지만 2번 방식은 객체를 만드는 방법에 대해 소개할 때 다시 한번 더 말씀드리겠습니다.

다른  방식으로도 나누어보겠습니다.
interface, abstract class, 일반 클래스

일반 클래스 같은 경우는 위에서 설명했기 때문에 생략하겠고, 나머지 interface와 abstract class에 대해 설명하겠습니다.

  interface abstract class
공통점 추상화를 시킬 수 있다.
차이점 추상화만 시킬 수 있다.
(이것을 어기는 방법은 존재한다.)
추상화까지 가능하다.

여기서 말하는 추상화란 매소드의 리턴, 매개 변수만 존재하는 형태로 다른 곳에서 구체화를 시켜야 되는 것을 말합니다.

전에 추상화에 대해 정리한 적이 있는데 참조하면 될 것 같습니다.

 

추상화란 무엇일까?

*주의 : 이 글은 CODE라는 책을 읽고 영감을 받아 작성한 글입니다. 일반적인 프로그래밍 추상화에 대해서는 설명하지는 않습니다. 저는 프로그래밍 추상화와 다른 추상화에 대한 미묘한 차이를

b-programmer.tistory.com

이러한 클래스들은 대게 인터페이스라고 불리는데, 이들은 2가지 방법으로 사용할 수 있습니다. 이는 추후에 객체를 만드는 방법에서 설명할 것 같습니다.

이들은 

interface {}
abstract class{}

이들의 차이점은 위에서 말한 거 말고 한 가지가 있지만, 이것은 추후 상속을 공부할 때 설명할 것 같아, 이는 생략하겠습니다.

abstract class는 interface와 달리 구현체와 추상화를 동시에 사용이 가능하며, 주로 디자인 패턴의 템플릿 메서드 패턴에서 사용됩니다.
그렇다면 class안에는 어떤 것을 넣을 수 있을까요?

변수, 메서드, 클래스등을 넣을 수 있습니다.

클래스에 작성된 변수, 메서드, 클래스는 멤버 XX라고  불립니다. (일반적으로 interface는 불가합니다.)

public class A {
  int b;
  int c;
  
  public void d() {}

}

지금까지 클래스를 정의하는 방법에 대해 알아봤습니다. 구조로 나눌 때는 외부 클래스를 여러개 정의할 수 있고,
중첩 클래스로도 사용이 가능했습니다. 또 기능적으로 나눌 때는 interface, abstract class, class로 나눌 수 있다는 것을 학습했습니다.

객체 만드는 방법 (new 키워드 이해하기)

클래스를 객체로 만들기 위해서는 new 키워드를 사용해야 합니다.

A a = new A();

다음과 같이 작성할 수 있습니다.
또한 new키워드는 힙 메모리에 저장됩니다.
다음과 같은 코드를 작성하였다.

class A {

}
public class Heap {
    public static void main(String[] args) {
            A a = new A();
            A a1 = new A();
    }
}

바이트 코드를 열어 확인해보자.

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #7                  // class A
         3: dup
         4: invokespecial #9                  // Method A."<init>":()V
         7: astore_1
         8: new           #7                  // class A
        11: dup
        12: invokespecial #9                  // Method A."<init>":()V
        15: astore_2
        16: return
      LineNumberTable:
        line 6: 0
        line 7: 8
        line 8: 16
}

 

 

Back to the Essence - Java 컴파일에서 실행까지 - (2)

Back to the Essence - Java 컴파일에서 실행까지 - (2)Java 11 JVM 스펙을 기준으로 Java 소스 코드가 어떻게 컴파일되고 실행되는지 살짝 깊게 알아보자. 이번엔 2탄 실행 편이다. 1탄 컴파일 편은 여기에..

homoefficio.github.io

바이트 코드를 확인해보니 new키워드로 실행할 때마다 새롭게 할당되는 것을 확인할 수 있습니다.
위 글을 읽어보니 new는 인자로 새 인스턴스로 할당된 메모리를 할당되고, 할당된 위치를 가리키는 참조를 오퍼랜드 스택에 쌓는다고 합니다. 이때 인스턴스 변수들이 기본값으로 초기화가 된다고 합니다.

dup가 오퍼 랜드 스택이라는 곳에 있는 정보를 복사해서 스택의 가장 위로 올려주는 역할을 한다고 합니다.

결국, new키워드를 반복적으로 입력한다고 해도, jvm에서는 새롭게 할당된다는 사실을 알 수 있습니다.
그렇다면 실행하면 어떻게 될까요?

2개 모두 주소 값이 다르게 나온다는 사실을 알 수 있습니다.

즉, new 키워드를 입력하게 되면 jvm에서는 새롭게 할당이 되기 때문에 서로 다른 객체로 나온다는 사실을 알 수 있습니다.

하지만 new키워드로만 객체를 생성하는 것은 아닙니다. 
클래스 내부에 static변수가 존재한다면 이야기는 달라집니다. 

static변수는 static영역에 생성되며, 프로그램이 종료 시까지 메모리에 할당된 상태로 존재합니다. 왜냐하면 static메모리는 GC가 처리해주지 않기 때문이죠.
쉽게 말하면, static을 사용하는 행위는 대출을 받는 행위와 비슷하다고 생각합니다.
더 정확히 말하면 대출받는 뒤 이자와 비슷합니다. 너무 많은 대출을 받게 되면 평생 이자를 갚아야 될지도 모르기 때문이죠.

static을 사용하게 되면 되면 다음과 같은 바이트코드를 확인할 수 있습니다.
제 코드는 간단히

class A {
    static int a;
}
public class Static {
    public static void main(String[] args) {
        System.out.println(A.a);
    }
}

으로 했는데 신기하게도 getstatic이라는 2개가 등장하는 것을 알 수 있습니다.
읽어보니 print도 static의 영향을 받는 것 같습니다. 혹시 몰라서 print는 하지 않는 상태에서 A.a만 실행해봤더니 그때는 getstatic이 단 1개만 등장하는 것을 확인할 수 있습니다. 

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: getstatic     #13                 // Field A.a:I
         6: invokevirtual #19                 // Method java/io/PrintStream.println:(I)V
         9: return
      LineNumberTable:
        line 6: 0
        line 7: 9
}

class에는 static을 붙일 수는 없지만, 멤버 클래스로 들어온다면 static을 붙일 수 있습니다. 
결국, 멤버 XXX인 모든 경우 사용이 가능한 것 같습니다.

이를 클래스 변수라고 부릅니다. 왜냐하면 객체로 만들지 않고 클래스에서 사용하기 때문이죠.
흔히들 알고 있듯이 static이 붙은 변수는 아무리 많이 만들어도 서로 영향을 받는 다고 알 고 있습니다. 과연 그럴까요?

2개의 값을 증가시키는 형태로 바꾼 뒤 출력해 봤습니다. 역시 예상되롭니다. 값이 증가했습니다.

결국, 설계도하나로 돌려먹기한다는 것이 증명되었습니다.

그렇다면 중첩 클래스상태일때는 어떻게 할까요?

class A {
    class B {}
}
public class Heap {
    public static void main(String[] args) {
        System.out.println(new A().new B());

    }
}

바로 마치 체인처럼 외부 클래스에서 내부 클래스를 가져오는 방식으로 되는 것 같습니다.
다시 코드를 작성해보겠습니다.

A.B b = new A().new B();
A a = new A();
A.B b = a.new B();

3가지 방법으로 코드를 작성할 수 있습니다.

이를 해석하면 A라는 설계도에서 B설계도를 찾구 그것을 b로 명명한다.라고 해석할 수 있을 것 같습니다.

이제 마지막 익명 클래스입니다. 과연 익명 클래스는 어떻게 될까요?

익명클래스는 추상클래스를 만들어야 합니다. 그리고나서 그것을 바로 사용하게 되면 그게 익명 클래스가 됩니다.
코드로 보여드리겠습니다.

interface A {}
public class B {
    public static void main(String[] args) {
        
        A a = new A() {
            
        };

    }
}

추상 클래스는 위에서 말했듯이, 구현체를 만들지 않고, 껍데기만 만드는 형태입니다. 이에 대해서는 추후에 말씀드리겠습니다.
구현체라는 곳에는 우리입맛대로 변경할 수 있습니다. 

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #7                  // class Ann$1
         3: dup
         4: invokespecial #9                  // Method Ann$1."<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 4: 0
        line 7: 8
}

흥미롭군요. 클래스를 객체로 만드는 행위와 같습니다.

결국, 객체를 만드는 방법은 2가지가 있다는것을 알 수 있었습니다.

- new 키워드를 이용한다.
- static 키워드를 이용한다.

물론 static은 객체를 만드는 방법은 아니지만, 이참에 소개하면 설명하면 좋을 것 같아 이것두 작성해봤습니다.

메서드 정의하는 방법

메서드는 방법이라는 뜻을 가지고 있습니다.
즉, 클래스에서 방법을 만들어낸다고 할 수 있습니다. 자매품으로 function이 있긴하지만... 비슷하면서도 다른것 같습니다.

아무튼 메소드는 다음과 같이 작성 됩니다.

public return값 메소드명(매개변수) {

}

또한 public과 {}를 제외한 부분을 메소드 시그니처라고 부릅니다.

그렇다면 return값과 매개변수(인자)에는 어떤것이 들어갈까요?

이 곳에 들어갈 수 있는 건 기본형,참조형이 들어 갈 수 있습니다. 기본형은

 

자바 데이터 타입, 변수 그리고 배열 

프리미티브 타입 종류와 값의 범위 그리고 기본 값 프리미티브 타입은 기본값 타입으로 불립니다. 종류는 크게 정수타입, 실수타입, 불리언타입으로 나눠져있습니다. 정수형에는 byte,short,int,long

b-programmer.tistory.com

여기서에 보이는듯이 int,boolean,double등과 같은 것들을 말합니다.
그리고 나머지를 참조변수라고 부릅니다. 

자바에서 미리 정해둔것도 있지만 
사용자가 직접 정의 할 수 도 있습니다. 바로 클래스로 말이죠.

 - 매개 변수(인자) : 이 메소드에는 이 변수가 필요합니다.
 - 리턴문 : 이 메소드에서 어떤 행위가 발생하는 지는 모르지만, 다음과 같은 변수값으로 리턴됩니다.
*변수값이라는 표현이 조금 그렇기는 하지만, 이를 표현하기에는 가장 좋다고 생각해서 이렇게 작성하였습니다.

그러면 이렇게만 하면 될까요?

public int method(int a) {

}

아닙니다. 이코드는 잘못된 코드이며 ide에서 컴파일에러가 발생할 것입니다.
그러면 어떻게 해야할까요?

매개변수와 리턴문에 맞게 작성해줘야합니다. 
즉, 어떠한 행위를 해야된다는 뜻입니다.

이 그림은 function그림이긴 한데 mehod도 이 관점에서 보면 틀린 그림은 아니기 때문에 가져왔습니다.

매개변수(인자) => input
return => output

조금 이상한 코드이긴 하지만... 다음처럼 하면 됩니다.

public int method(int a) {
  return a;
}

조건에만 맞으면 되기 때문에 위 처럼 작성해도 코드상에서는 아무런 문제가 되지는 않지만, 
저렇게는 코딩하지 않죠..;;ㅎㅎ;

그러면 매개변수도 존재하구 리턴값도 존재하는 메소드만 있을까요?
그렇지 않습니다.

public void method(int a) {
}

 

public int method() {
  return 0;
}
public void method() {
}

그런데 void값은 어떻게 동작할까요?

주로 자료구조에 값을 넣을 때 사용되는 것 같습니다. 
print로도 사용할 수 도 있구, 맴버변수에 할당할때도 사용되는 것 같습니다.

그러면 메소드를 이렇게 정의한다고 사용할 수 있을까요?
그렇지 않습니다. 
객체로 만들고 나서 메소드들을 사용해줘야합니다.

class A {
    public int hello(int num) {
        return num * 100;
    }
}
public class M {
    public static void main(String[] args) {
        System.out.println(new A().hello(20));
    }
}

근데 왜 이렇게 사용할까요? 그냥 클래스에 메소드만 작성해두고, 클래스만 객체로 만들때 자동으로 할당시켜주면 안될까요?
위에서 말했듯이 클래스는 설계도입니다.

설계도를 짜고보니 필요없는 메소드가 있을 수 도 있습니다. 만약, 위 가정처럼 된다고 했을때, 필요없는 메소드까지 메모리에 저장된다면 
쓸데없이 무거워지는게 아닐까요? 

추가적으로 메소드에 있는 변수를 지역 변수라고 합니다. 

+ 오버로딩

오버라이딩도 존재하지만, 상속을 배울때 작성하겠습니다.

정확한 뜻은 아니지만, 이부분은 제 나름대로 기억한 내용대로 작성하겠습니다.
오버로딩은 over + loading으로

over: 너머서다라는 뜻을 가지고 있고
load : 싣다라는 뜻을 가지고 있습니다. 
즉, 싣고 있는 중에 넘서선다?라고 해석 될 수 있습니다.

일반적으로 메소드는 다음과 같이 생겼습니다.

public void method() {
}

현재 매개변수(인자)에 담겨 있는 매개변수(인자)의 값은 0개입니다. 즉, 이 메소드가 싣을 수 있는 변수는 총 0개라는 뜻입니다.
이제 위에서 말한 '오버로딩'을 적용하면 어떻게 될까요?

바로 최대 갯수0개를 넘어서 게 됩니다.
즉, 1개,2개,3개.... 변수를 추가가 가능하다는 뜻이죠.

근데, 클래스에는 메소드가 0개도 존재하지만, 10이상도 존재할 수 있습니다.
그렇다면 이들과 어떻게 구별할 수 있을까요?

바로 메소드명입니다. 원래 메소드명이 같은 메소드는 1개만 사용이 가능합니다.
하지만, 이 오버로딩을 사용하게 된다면, 이 리미트를 벗어날 수 있습니다.
*다시한번 말하겠지만, 메소드명이 다르면 매개변수가 같아도 오버로딩은 아닙니다.

생성자 정의하는 방법

생성자와 메소드는 서로 비슷하게 생겼습니다. 비교하면서 이야기 해봅시다. 
생성자는 다음과 같이 생겼습니다.

public class A {
  public A() {}
}

또, 일반적인 메소드는 다음과 같이 생겼습니다.

 

생성자 메소드
클래스 자체에 어떠어떠한 정보를 넣겠다 메소드에 어떠어떠한 정보를 넣겠다.

여기서 알 수 있는 사실은 , 생성자를 클래스와 연관이 있다는 것을 알 수 있습니다.
만약에, 다음과 같이 작성했다고 생각해봅시다.

public class A {
  int a;
  public A() {
  a = 3;
  }
}

클래스는 객체로 만들어야 비로소 사용할 수 있습니다.

사용해보겠습니다.

A A = new A();

 * 원래 변수에 대문자로 시작하는 것은 별로 좋은 코드는 아니지만 여기서 중요한건 컨벤션이 아니니까요.

이제 변수를 사용해보겠습니다.

A.a = ?

그러면 어떻게 나올까요? 정답은 3입니다. 왜냐하면 생성자에서 a=3으로 정의해 두었기 때문입니다.

그렇다면 다음과 같이 작성할 수 있지 않을 까요?

public class A {
  public A(int a) {}
}

아... 메소드이기 때문에 오버로딩도 되겠죠. 

public class A {
  public A(int a,int b) {}
}

사실 우리는 생성자를 계속 써왔습니다.
자바에서는 생성자를 굳이 작성하지 않아도 자동으로 생성해줍니다. 다만, 매개변수는 생성해주지는 않습니다.

이러한 생성자를 기본 생성자라고 부릅니다.

아무튼, 제가 말하고 싶은건 생성자는 개발자가 굳이 만들지 않아도 생성이 된다는 뜻입니다.

메소드와 비교해서 생성자를 보면 이상한점이 하나 있습니다.
바로 생생자에는 메소드명이 존재하지 않습니다.

즉, 클래스명자체가 메소드명이라는 뜻입니다. 
또, 주의해야할점도 있습니다.
바로, 생성자명은 클래스명과 같아야 된다는 뜻입니다. 결국 이렇게는 사용이 불가하는 뜻이겠죠.

public class A {
  public B() {}
}

그런데, 특정 변수를 받아서 클래스자체에 넣고 싶다면 어떻게 해야할까요?

this 키워드 이해하기

바로 this을 이용하면 해결 할 수 있습니다.
그렇다면 this는 무엇을 뜻할까요?

바로 지금 사용하고 있는 클래스라는 뜻입니다.

this는 객체를 사용할 수 있는 모든곳에 사용할 수 있습니다.

다음과 같이 사용할 수 있습니다.

public class A {
  int a;
  public A(int a) {
   this.a = a;
  }


 public void method(int a) {
  this.a = a;
 }
}
public A method() {
  return this;
}

그러면 이렇게도 사용할 수 있을까요?

public this method() {
  return null;
}
public void method(this a) {
 
}

이것들은 불가능합니다.

왜냐하면 위 자리는 객체 자리가 아니라 클래스 자리이기 때문입니다.
굳이 해석하면 이 자리에 이것을 사용하겠어!라고 할 수 있겠죠. 근데 객체는 이미 사용된것이기 때문에 에러가 안난다고 한들 의미가 없습니다.(그냥 불가하다는 뜻입니다.)

그러면 이것은 어떻게 동작할까요?

위에서 생성자에 대해 학습하였고, 
변수 파트에서 '=' 를 학습하였습니다. 이 둘을 잘 조합해 봅시다.

this가 객체구, 변수를 사용한다,

this.a

그곳에 (X)를 할당한다.

예를들어

public class A {
  int a;
  public A(int a) {
   this.a = a;
  }


 public void method(int a) {
  this.a = a;
 }
}

public static void main(String[] args){
  A a = new A(3);
  
}

이렇게 사용한다고 했을때, 이 A라는 객체는 a라는 변수에 3을 할당 받는 거를 알 수 있습니다.

한번 생각해봅시다.

A a = new A(5);
A a = new A(2);
A a = new A(1);
A a = new A(4);

 어떻게 될까요?

여기서 알 수 있는 사실은 this는 현재 사용하고 있는 클래스 자체를 뜻하며, 객체로 변환을 시켜야만 가능한 곳에서만 사용이 가능합니다.

개인적으로 클래스는 객체지향 프로그래밍을 이해하는데 제일 중요한 지식중 하나라고 생각합니다. 

 

반응형

'프로그래밍 언어 > 자바' 카테고리의 다른 글

Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.2:test  (0) 2020.12.29
상속  (1) 2020.12.24
큐 구현(설명 x)  (0) 2020.12.08
스택 구현(설명x)  (0) 2020.12.07
Live-Study 대시 보드 만들기.  (0) 2020.12.06

댓글

Designed by JB FACTORY