티스토리 뷰

JAVA/whiteship-livestudy

클래스

최진영 2021. 3. 24. 10:09

클래스 정의하는 방법

객체 지향 프로그래밍(OOP, Object-Oriented Programming)

 모든 데이터를 객체(Object)로 취급하며 이 객체를 중심으로 돌아가는 프로그래밍을 말한다.

 객체란 단순히 우리가 인식할 수 있는 사물을 말한다.

Class(클래스)

 클래스란 "객체를 정의해놓은 것", "객체의 설계도 또는 틀"로 정의할 수 있다. 자바는 클래스를 가지고 필요한 객체를 생성해서 사용한다.

 자바에서 클래스는 객체의 상태를 나타내는 필드(field)와 객체의 행동을 나타내는 메소드(method), 생성자(constructor)로 구성된다. 즉 필드에는 클래스에 포함된 변수가 이에 해당하고, 클래스 내에 있는 모든 명령어들이 메소드에 해당된다.

package Week5;

public class Exam1 {    //클래스
    String name;        //필드(변수)
    static int age;            //필드(변수)
    boolean student;    //필드(변수)

    public Exam1(String name, boolean student) {    //생성자
        this.name = name;
        this.student = student;
    }

    static void userAgeUp(){   //메소드
        age++;
    }
    void userAgeDown(){ //메소드
        age--;
    }
    void userNameChange(String name){ //메소드
        this.name = name;
    }
}

 

 이때 static이 붙었는지 붙지않은지에 따라서 변수와 메소드가 달라진다.

 static이 작성된 경우 클래스 변수, 클래스 메소드가 되며 둘 다 클래스가 로드되는 시점에서 클래스와 함께 Method Area에 저장된다. static이 붙지 않은 경우 각각 인스턴스 변수, 인스턴스 메소드가 되고 객체화가 되었을 때 Heap Area에 생성이 된다.

 

 무슨 차이가 있는가? JVM 메모리에 저장되는 시점에 따라서 사용하는 영역과 사용 방식이 완전 달라진다. Method Area는 클래스가 사용되었을 때 클래스가 저장되는 영역이고 Heap Area는 인스턴스화를 했을 때 인스턴스가 저장되는 영역이다. 따라서 static이 붙은 변수와 메서드는 클래스가 선언되었을 때 같이 메모리에 저장되기 때문에 굳이 객체화를 하지 않아도 사용할 수 있다. 하지만 인스턴스 변수와 메소드는 객체화를 했을 때 메모리에 저장되기 때문에 객체화를 하지 않았다면 사용을 할 수 없다.

 

 static과 관련된 규칙 몇가지가 있으니 이를 지켜주어야한다.

  • 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
  • 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있다.
  • 클래스 메소드는 인스턴스 변수를 사용할 수 없다.
  • 메소드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.

 

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

 자바에서는 모든 객체를 생성할 때 new라는 키워드를 사용한다.

 new를 사용한다는 것은 앞서 말한 Heap Area에 사용하게 되는 데이터를 저장할 공간을 할당하고 그 주소를 반환한다는 것이다. 항상 새로운 객체를 생성할 때는 클래스와 더불어 new 키워드를 붙여서 객체를 생성하여야 한다.

 단, 위에서도 말했다시피 static으로 할당된 변수, 메소드의 경우 굳이 객체화를 하지 않아도 사용할 수 있다. 다음을 보자.

package Week5;

public class Exam1 {    //클래스
    String name;        //필드(변수)
    static int age = 10;            //필드(변수)
    boolean student;    //필드(변수)

    public Exam1(String name, boolean student) {    //생성자
        this.name = name;
        this.student = student;
    }

    static void userAgeUp(){   //메소드
        age++;
    }
    static void userAgeDown(){ //메소드
        age--;
    }
    void userNameChange(String name){ //메소드
        this.name = name;
    }

    public static void main(String[] args) {
        System.out.println("Exam1.age : " + Exam1.age);
        Exam1.userAgeUp();
        System.out.println("Exam1.age : " + Exam1.age);
//        System.out.println("Exam1.name : " + Exam1.name);

        Exam1 exam1 = new Exam1("라이언", true);
        System.out.println("exam1 : " + exam1);
        System.out.println("exam1.name : " + exam1.name);
        System.out.println("exam1.age : " + exam1.age);

        Exam1 exam2 = new Exam1("라이언", true);
        System.out.println("exam2 : " + exam2);
        System.out.println("exam2.name : " + exam2.name);
        System.out.println("exam2.age : " + exam2.age);
    }
}
//    [출력]
//    Exam1.age : 10
//    Exam1.age : 11
//    exam1 : Week5.Exam1@75b84c92
//    exam1.name : 라이언
//    exam1.age : 11
//    exam2 : Week5.Exam1@6bc7c054
//    exam2.name : 라이언
//    exam2.age : 11

 메인 메소드 안에 있는 영역을 보면 static 변수로 만들어진 ageuserAgeUp()는 객체화를 하지 않아도 이미 Method Area에 할당되었기 때문에 사용할 수 있음을 확인할 수 있다. 하지만 name은 인스턴스 변수이기 때문에 인스턴스화를 하기 전에는 사용할 수 없다.

 따라서 exam1과 exam2처럼 new 키워드를 사용해서 인스턴스화를 했을 때 인스턴스 변수를 사용할 수 있는 것이다.

 new 키워드를 사용하여 객체화를 할 때 생성자에 변수를 똑같은 것을 넣어도 둘은 같은 객체가 아니다. new로 객체화를 할 때마다 새로운 stack 메모리 영역에 그 주소를 넣고 새로운 heap 메모리 영역에 데이터를 저장하기 때문에 exam1의 주소와 exam2의 주소가 다른 것처럼 같은 주소를 가지지는않는다.

 

메소드 정의하는 방법

 메소드(Method)는 어떤 특정 작업을 수행하기 위한 명령문이 모인 집합으로 한마디로 말해서 클래스에서 "행위"를 나타낸다고 보면된다. 메소드를 쓰는 이유는 크게 3가지로 구분할 수 있다.

  • 높은 재사용성
  • 중복된 코드 제거
  • 프로그램의 구조화

 

 생략하거나 추가할 수 있지만 메소드의 기본 형태는 다음과 같다.

접근제어자 반환타입 메소드이름 (매개변수) {	//선언부
	//구현부
}

접근제어자

 접근 제어자는 메소드를 사용할 때 해당 메소드에 접근할 수 있는 범위를 정해준다.

 public, protected, private, default 총 4가지가 있다.

 public - 어디서나 아무나 접근 가능

 protected - 상속 관계일 경우, 같은 폴더/패키지 내에 있는 경우 접근 가능

 private - 같은 클래스 내에서만 접근 가능

 default(접근제어자 생략시) - 같은 폴더/패키지 내에서 접근가능

반환타입

 반환 타입에 제시된 타입에 따라 최종 return 값에 대한 반환 타입이 바뀜

 void의 경우 리턴값이 없으며 반환 타입에 지정된 타입을 리턴하여야함

메소드이름

 메소드 이름은 네이밍 컨벤션이 있기 때문에 그에 맞게 네이밍을 지어주어야 한다

매개변수

 매개변수는 필요한 input값이 있다면 얼마든지 넣을 수 있지만 꼭 사용하여야 하는 값만 넣는 것이 좋다.

 

class ClassTest {
    String name;
    int age;
    static int score = 10;

    public void ageUp(){
        age++;
        System.out.println(age);
    }
    private void ageDown(){
        age--;
        System.out.println(age);
    }

    void ageTest(){
        this.ageUp();
        this.ageDown();
    }

    public String getName(){
        return name;
    }
    public static void getScore(){
        System.out.println(score);
    }
    public int ageUpByInput(int input) {
        return age + input;
    }
}
package Week5;

public class Exam2 {
    public static void main(String[] args) {
//        ClassTest.ageUp();
        ClassTest.getScore();

        ClassTest classTest = new ClassTest();
        classTest.ageUp();
//        classTest.ageDown();
        System.out.println(classTest.ageUpByInput(22));
    }
}
//    [출력]
//    10
//    1
//    23

 예제 코드를 보면 static으로 선언된 메소드는 객체화를 하지 않아도 사용할 수 있다.

 또한 private 접근자를 사용한 메소드는 객체화를 하여도 사용할 수 없는것에 비해서 public 접근자를 사용한 메소드는 객체화 후 사용할 수 있다. 단, ageTest()와 같이 클래스 안에서 선언하였을 경우 private 메소드도 사용할 수 있다.

 

 

생성자 정의하는 방법

 클래스에 메소드까지 오면서 우리는 클래스의 변수를 객체화 후 메소드를 통해서건, 메인메소드에서건 변경할 수 있다는 것을 확인했다.

 그럼 객체화하면서 동시에 인스턴스를 초기화하면 어떨까? 하는 관점에서 나온게 생성자이다. 클래스를 통해 객체화를 진행하면 생성된 객체의 인스턴스 변수들은 자동으로 default 값으로 초기화된다. (ex int - 0, double - 0.0 ...) 하지만 객체를 생성할 때 원하는 인스턴스 변수로 초기화하고 싶으면 생성자를 사용하면 된다.

 중요한 것은 생성자는 기본적으로 public 접근자를 사용해주어야 한다는 것이다. 당연하다. 클래스의 메소드를 사용해야하는데 우리는 어디에서 사용하든 간에 초기화시에 생성자라는 메소드를 사용할 수 있어야한다. 그것이 생성자의 존재 이유이기 때문이다. private로 생성자를 만들었다면 ㅎㅎ... 만들어 놓고도 메인메소드에서 해당 클래스의 생성자를 쓰지 못하는 경우가 발생한다.

package Week5;

public class Exam3 {
    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = new Person("무디", 12);
        Person person3 = new Person("라이언", 10, "남", "010-1234-5678");
    }
}
class Person {
    String name;
    int age;
    String gender;
    String phone;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name, int age, String gender, String phone) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.phone = phone;
    }
}

 생성자에는 여러가지 사용방법이 있는데 일단은 3가지만 예시로 사용하였다. 알아보기 전에 생성자 생성에 대한 몇가지 규칙을 숙지하고 가자.

  • 생성자는 return 값을 가지지 않는다.
  • 생성자는 클래스 이름과 동일하다.
  • 모든 클래스는 생성자를 반드시 한 개 이상의 생성자를 가진다.

 

기본생성자

public Person() {
}

 매개변수도 없고 아무런 구현도 없는 기본 생성자이다.

 시작하기 전에 조건으로 모든 클래스는 생성자르라 반드시 한 개 이상의 생성자를 가진다고 했다. 자바 컴파일러는 컴파일 동작 시에 class에 대해서 사용자가 생성자를 생성하지 않았다면 자동으로 생성자를 만든다. 따라서 생성자 생성을 하지 않았더라도 자바 컴파일러가 기본 생성자를 만들어주기 때문에 그 규칙을 만족하는 것이다.

 단, 다른 생성자를 만들었다면 자바 컴파일러는 컴파일 시 기본생성자를 만들어주지 않는다.

 아무 동작도 하지않는다. 습관적으로 사용하는 Person person = new Person(); 역시 이 기본생성자로 인해서 만들어진 객체이다.

묵시적 생성자(매개변수가 없는)

public Person() {
    this.name = "default";
    this.age = 5;
}

 매개변수는 없는데 구현부가 있는 생성자이다. 기본 생성자와는 다르게 변수들에 대해서 내가 원하는 default 초기화값이 있을 때 사용한다.

 

명시적 생성자(매개변수가 있는)

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}
//Person person2 = new Person("무디", 12);

public Person(String name, int age, String gender, String phone) {
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.phone = phone;
}
//Person person3 = new Person("라이언", 10, "남", "010-1234-5678");

 매개변수가 있는 생성자는 사용자가 만들어내는 생성자로 원하는 매개변수를 받아서 객체 생성시에 해당 변수를 초기화시킬 수 있다.

 

this 키워드 이해하기

 지금까지 습관적으로 클래스에서 사용하였던 this 이다. 한가지 메소드를 보자

class Person {
    String name;
    Person(String name){
        this.name = name;
    }
}

 Person에 인스턴스 변수로 name이 있는데 생성자의 매개변수도 변수명이 name이다. 이때 인스턴스 변수 초기화를 사용해야하는데 name = name으로는 그 둘이 구분이 되지 않아 명시적으로 인스턴스 변수에 대한 표현을 위해서 사용을 하는 것이 this 키워드이다.

 물론 인스턴스변수가 다른 변수, 매개변수와 이름이 같지 않다면 this를 쓰지 않아도 인스턴스 변수로 사용이 되기는 한다.

package Week5;

public class Exam3 {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println("생성시 이름 : " + person.name);
        person.changeName();
        System.out.println("변경후 이름 : " + person.name);
    }
}
class Person {
    String name;
    void changeName(){
        name = "라이언";
    }
}

 하지만 생성자에서 name = name과 같이 변수명에 혼동이 생기고 원하는 인스턴스 변수를 특정하기 위해서 this 라는 키워드를 사용하게 된다.

 그럼 this가 뜻하는 바가 무엇이냐?

클래스가 인스턴스화 하였을 때 인스턴스 자기 자신의 메모리 주소를 가르킨다.

 우리가 Personperson으로 객체화를 했을 때 객체화된 person이 가르키는 변수는 객체가 담긴 "주소"이다. 객체가 생성되고 난 후에야 그 객체명을 통해서 주소를 사용할 수 있다만 클래스 안에서는 불가능하다. 그걸 해결해주는게 this 키워드이다.

 간단하게 예제로 확인할 수 있다.

package Week5;

public class Exam3 {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println("메인메소드에서 person주소 : " + person);
        System.out.print("class내에서 person 주소 : ");
        person.testThis();
    }
}
class Person {
    String name;
    void testThis(){
        System.out.println(this);
    }
}
//    [출력]
//    메인메소드에서 person주소 : Week5.Person@75b84c92
//    class내에서 person 주소 : Week5.Person@75b84c92

 this가 정확하게 내가 메인메소드에서 생성한 person의 주소와 동일한 것을 확인할 수 있다.

 

 변수 뿐만 아니라 클래스 내부에서 같이 존재하는 메소드끼리도 this를 붙여 사용할 수 있다.

package Week5;

public class Exam3 {
    public static void main(String[] args) {
        Person person = new Person("라이언", 10);
        person.printName();
    }
}
class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    void printName() {
        System.out.println(name);
        this.printAge();
    }
    void printAge() {
        System.out.println(age);
    }
}
//    [출력]
//    라이언
//    10

 

 this 키워드가 그냥 들어간거야 그 객체의 주소라고 생각하면 되는데 this()로 사용을 할 경우 생성자를 사용하는 것으로 정의한다. 단 아무곳이나 막 들어가는 것이 아니라 생성자 내부에서 다른 생성자를 사용할 때 이용된다. 예시 코드를 보자.

class Person {
    String name;
    int age;
    String gender;

    public Person() {

    }

    public Person(String name, int age) {
        this();
        this.name = name;
        this.age = age;
//        this();
    }

    public Person(String name, int age, String gender) {
        this("무지", 20);
        this.name = name;
//        this("무지", 20);
        this.age = age;
//        this("무지", 20);
        this.gender = gender;
//        this("무지", 20);
    }
}

 this()는 항상 사용에 제한된 위치가 있다.

  • this()는 항상 생성자 정의 코드 위에 존재하여야 한다.
  • this()와 생성자 정의 코드 사이에 어떠한 코드도 올 수 없다.

 위 정의된 위치를 지키지 않으면 컴파일 에러가 발생하니 주의하면서 사용하도록 하여야 한다.

'JAVA > whiteship-livestudy' 카테고리의 다른 글

패키지  (0) 2021.04.09
상속  (0) 2021.03.28
제어문  (0) 2021.03.17
연산자  (0) 2021.03.16
자바의 데이터 타입, 변수 그리고 배열  (0) 2021.03.15
댓글
최근에 올라온 글
Total
Today
Yesterday