중간고사 대비 java 6단원 정리

목차

혼자 공부하는 자바 6단원, 클래스 관련 내용 정리

1. 객체 지향 프로그래밍

1.1 객체

객체는 속성과 동작으로 구성되어 있다. 이때 속성을 필드, 동작을 메소드라고 보통 부른다. 객체 모델링이란 현실의 객체의 동작을 추려내서 소프트웨어 객체의 필드, 메소드로 정의하는 것이다. 또한 현실 속의 현상들을 객체 간의 상호작용(메시지를 통해서 일어난다)으로 표현하는 것이다.

1.2 클래스

객체를 만들기 위해서는 틀이 필요하다. 자바에서는 이 틀이 클래스이다. 클래스에는 객체를 생성하기 위한 필드, 메서드가 정의되어 있고 이 클래스를 통해 만들어진 객체를 인스턴스라 한다.

이때 클래스는 한 파일 내에 여러 개 선언할 수 있는데 이를 컴파일할 시 바이트코드 파일은 선언된 클래스의 개수만큼 생긴다.

주의할 점은 클래스 선언시 public으로 선언된 클래스의 이름은 파일명과 같아야 된다는 점이다. 클래스를 생성할 때는 new 연산자를 사용한다.

// Student.java
package study;

public class Student {
  String name;
}

// Hello.java
package study;

public class Hello {

  public static void main(String[] args){
    Student s1=new Student();
    s1.name="김성현";
    System.out.println(s1.name);
  }
}
// Student.java
package study;

public class Student {
  String name;
}

// Hello.java
package study;

public class Hello {

  public static void main(String[] args){
    Student s1=new Student();
    s1.name="김성현";
    System.out.println(s1.name);
  }
}
// Student.java
package study;

public class Student {
  String name;
}

// Hello.java
package study;

public class Hello {

  public static void main(String[] args){
    Student s1=new Student();
    s1.name="김성현";
    System.out.println(s1.name);
  }
}
// Student.java
package study;

public class Student {
  String name;
}

// Hello.java
package study;

public class Hello {

  public static void main(String[] args){
    Student s1=new Student();
    s1.name="김성현";
    System.out.println(s1.name);
  }
}

1.3 클래스의 구성 요소

클래스의 구성 요소는 필드, 생성자, 메소드가 있다. 이때 생성자는 클래스 이름과 같은 이름을 가지며 리턴 타입이 없는 메소드이다. 생성자는 객체를 생성할 때 호출되며 객체가 생성될 때 필드의 초기화를 담당한다.

2. 필드

필드는 객체의 속성을 나타내며 생성자 선언과 메소드 선언의 앞, 뒤 어디서든 선언할 수 있다. 또한 기본 타입과 참조 타입 모두 될 수 있다. 초기값은 필드 선언 시 주어질 수도 있고 생략될 수도 있다. 만약 생략되면 기본값이 주어지는데 기본 타입은 0, 참조 타입은 null이다.

3. 생성자

생성자는 new연산자로 클래스에서 객체를 생성할 때 호출되어 객체를 초기화한다. 생성자가 실행되어 객체가 초기화되면 객체는 힙 영역 메모리에 생성된다.

만약 사용자가 생성자를 선언하지 않았다면 컴파일시 기본 생성자가 자동으로 추가된다. 하지만 클래스에 명시적으로 선언한 생성자가 하나라도 있다면 기본 생성자는 추가되지 않는다. 이 말은 반대로 말하면 클래스에 생성자가 명시적으로 선언되어 있다면 반드시 선언된 생성자를 호출해서 객체를 생성해야 한다는 뜻이다.

3.1 필드 초기화

클래스 필드 초기화는 클래스에서 필드 선언 시 초기값을 주거나 생성자에서 초기값을 주는 2가지 방법이 있다.

이때 생성자는 여러 가지의 인수를 받는 다양한 형태로 오버로딩될 수 있다. new로 객체를 생성할 때 생성자에 전달하는 인수의 형태에 따라 호출되는 생성자가 달라진다. 이를 생성자 오버로딩이라 한다.

이때 생성자 오버로딩이 많아질 경우 생성자 간 중복 코드가 많아지게 된다. 따라서 생성자 오버로딩을 사용할 때는 중복 코드를 줄이기 위해 this()를 사용하는 것이 좋다. this(매개변수) 는 생성자 내에서 클래스의 다른 생성자를 호출하는 것이다. 주의할 점은 this()는 생성자의 첫 줄에서만 허용된다는 것이다.

4. 메소드

클래스 메소드는 선언부와 실행 블록으로 구성되며 함수의 선언과 비슷하다. 메소드 선언부를 메소드 시그니처라고 한다.

메소드 시그니처는 메소드 이름, 매개변수 목록, 리턴 타입으로 구성된다. 메소드 시그니처는 메소드를 호출할 때 사용되며 메소드 이름과 매개변수 목록이 일치해야 호출할 수 있다. 리턴 타입은 메소드의 실행 결과를 반환할 때 사용된다.

4.1 매개변수 목록

어떤 상황에서는 메소드를 선언할 때 매개변수 개수가 미정일 수도 있다. 이런 경우를 해결하는 방법은 2가지가 있다.

  1. 매개변수를 배열 타입으로 선언
  2. ...을 이용해서 매개변수를 가변인자로 선언

2번 방식을 이용할 경우 메소드를 호출하기 전에 굳이 배열을 생성하지 않아도 되는 이점이 생긴다. 즉 가변인자는 배열 타입의 매개변수보다 메소드를 호출할 때 편리하다.

package study;

public class Computer {
  int sum1(int[] values){
    int sum=0;
    for(int i=0;i<values.length;i++){
      sum+=values[i];
    }
    return sum;
  }

  int sum2(int ...values){
    int sum=0;
    for(int i=0;i<values.length;i++){
      sum+=values[i];
    }
    return sum;
  }
}
package study;

public class Computer {
  int sum1(int[] values){
    int sum=0;
    for(int i=0;i<values.length;i++){
      sum+=values[i];
    }
    return sum;
  }

  int sum2(int ...values){
    int sum=0;
    for(int i=0;i<values.length;i++){
      sum+=values[i];
    }
    return sum;
  }
}
package study;

public class Computer {
  int sum1(int[] values){
    int sum=0;
    for(int i=0;i<values.length;i++){
      sum+=values[i];
    }
    return sum;
  }

  int sum2(int ...values){
    int sum=0;
    for(int i=0;i<values.length;i++){
      sum+=values[i];
    }
    return sum;
  }
}
package study;

public class Computer {
  int sum1(int[] values){
    int sum=0;
    for(int i=0;i<values.length;i++){
      sum+=values[i];
    }
    return sum;
  }

  int sum2(int ...values){
    int sum=0;
    for(int i=0;i<values.length;i++){
      sum+=values[i];
    }
    return sum;
  }
}

4.2 메소드 오버로딩

클래스 내에 같은 이름의 메소드를 여러 개 선언하는 걸 메소드 오버로딩이라 한다. 이때 이름이 같은 메소드는 매개변수 타입, 개수, 순서 중 하나 이상이 달라야 한다. 리턴 타입만 다른 것, 그리고 매개변수의 이름이 다른 것은 같은 메소드로 취급되는 것에 주의한다.

5. 인스턴스 멤버와 정적 멤버

클래스의 필드와 메소드는 당연히 모든 클래스 인스턴스에 포함되어 있어야 한다. 하지만 모든 클래스가 공유하는 어떤 필드나 메소드가 필요할 수도 있다. 이런 경우에는 클래스의 인스턴스 멤버가 아닌 정적 멤버로 선언한다. 인스턴스 멤버는 객체마다 가지고 있는 멤버를 말하며 정적 멤버는 클래스에 포함되어 모든 인스턴스가 공유하는 멤버를 말한다.

이때 객체 내부에서 인스턴스 멤버에 접근하게 위해서는 this라는 키워드를 사용한다. 반면 정적 멤버에 접근할 때는 클래스 이름을 사용한다.

5.1 정적 멤버와 static

정적 멤버는 클래스에 고정된 멤버로 객체를 생성하지 않고도 사용할 수 있는 필드와 메소드이다. 정적 필드와 정적 메소드를 선언하기 위해서는 static 키워드를 사용한다.

정적 필드, 정적 메소드는 클래스에 고정된 멤버이므로 클래스가 메모리에 로딩될 때 생성된다. 따라서 클래스 로딩이 끝나면 바로 사용할 수 있다.

인스턴스마다 다르게 가지고 있어야 하는 값은 인스턴스 필드로 선언하고, 모든 인스턴스가 공유해야 하는 값은 정적 필드로 선언한다. 메소드를 어떻게 선언할지의 기준은 인스턴스 필드를 가지고 있는 메소드인지에 따라 달라진다. 인스턴스 필드를 포함한다면 인스턴스 메소드로, 인스턴스 필드를 포함하지 않는다면 정적 메소드로 선언한다.

5.2 정적 멤버 사용

정적 멤버는 클래스가 메모리에 로딩되면 바로 사용할 수 있다. 클래스 이름을 통해 접근한다.

// Calculator.java
package study;

public class Calculator {
  static double pi=3.141592;
  static int plus(int x, int y){
    return x+y;
  }
  static int minus(int x, int y){
    return x-y;
  }
}
// Hello.java
package study;

public class Hello {

  public static void main(String[] args){
    int res=Calculator.plus(10,20);
    System.out.println(Calculator.pi);
    System.out.println(res);
  }
}
// Calculator.java
package study;

public class Calculator {
  static double pi=3.141592;
  static int plus(int x, int y){
    return x+y;
  }
  static int minus(int x, int y){
    return x-y;
  }
}
// Hello.java
package study;

public class Hello {

  public static void main(String[] args){
    int res=Calculator.plus(10,20);
    System.out.println(Calculator.pi);
    System.out.println(res);
  }
}
// Calculator.java
package study;

public class Calculator {
  static double pi=3.141592;
  static int plus(int x, int y){
    return x+y;
  }
  static int minus(int x, int y){
    return x-y;
  }
}
// Hello.java
package study;

public class Hello {

  public static void main(String[] args){
    int res=Calculator.plus(10,20);
    System.out.println(Calculator.pi);
    System.out.println(res);
  }
}
// Calculator.java
package study;

public class Calculator {
  static double pi=3.141592;
  static int plus(int x, int y){
    return x+y;
  }
  static int minus(int x, int y){
    return x-y;
  }
}
// Hello.java
package study;

public class Hello {

  public static void main(String[] args){
    int res=Calculator.plus(10,20);
    System.out.println(Calculator.pi);
    System.out.println(res);
  }
}

정적 필드, 정적 메소드는 클래스 이름을 통해 접근해야 하지만, 인스턴스를 통해서도 접근 가능하다. 물론 원칙적으로는 클래스 이름을 통한 접근이 좋다.

주의할 점은 정적 멤버는 객체가 없어도 실행되기 때문에 정적 메소드 선언시 정적 메소드 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. 객체 자신을 참조하는 this도 사용할 수 없다. 정적 메소드 내부에서 인스턴스 필드나 인스턴스 메소드를 사용하려면 객체를 먼저 생성해야 한다.

이는 main메소드에서도 마찬가지다. main도 정적 메소드이기 때문에 인스턴스 필드, 인스턴스 메소드를 main에서 바로 사용할 수 없다.

package study;

public class Hello {
  int temp;
  public static void main(String[] args){
    // 정적 메소드 main에서 인스턴스 변수 temp에 접근 불가 - 에러
    temp=1;
  }
}
package study;

public class Hello {
  int temp;
  public static void main(String[] args){
    // 정적 메소드 main에서 인스턴스 변수 temp에 접근 불가 - 에러
    temp=1;
  }
}
package study;

public class Hello {
  int temp;
  public static void main(String[] args){
    // 정적 메소드 main에서 인스턴스 변수 temp에 접근 불가 - 에러
    temp=1;
  }
}
package study;

public class Hello {
  int temp;
  public static void main(String[] args){
    // 정적 메소드 main에서 인스턴스 변수 temp에 접근 불가 - 에러
    temp=1;
  }
}

5.3 final 필드와 상수

final 필드는 최종적인 필드라는 뜻이다. 즉 한 번 값을 할당하면 변경할 수 없는 필드이다. 프로그램 실행 도중에 더 수정할 수 없다. 따라서 이 final 필드는 필드 선언 시에 주거나 생성자에서 초기화해야 한다. 만약 생성자까지 실행되고도 초기화되지 않은 final 필드가 있다면 컴파일 에러가 발생한다.

하지만 final 필드는 생성자를 통해 초기화될 수도 있으므로 절대 바뀌지 않는 상수라고 하기에는 부족한 감이 있다. static final 필드가 되어야 상수라고 한다.

6. 패키지와 접근 제한자

패키지의 물리적인 형태는 파일 시스템의 폴더이다. 하지만 패키지는 폴더 기능뿐 아니라 클래스의 일부분의 기능도 한다. 클래스를 유일하게 만들어주는 식별자 역할을 하는 것이다. 클래스 이름이 같아도 패키지가 다르면 다른 클래스로 인식한다. 패키지가 클래스 식별에도 역할을 하는 것이다.

6.1 패키지 가져오기

사용하고자 하는 패키지의 클래스나 인터페이스가 다른 패키지에 소속되어 있다면 패키지를 가져와야 한다. 패키지를 가져오는 방법은 import문을 사용하는 것이다. import문은 패키지 선언문과 클래스 선언문 사이에 작성한다. import문은 패키지 이름을 포함한 클래스 이름을 작성한다.

import 상위패키지.하위패키지.클래스명;
import 상위패키지.하위패키지.클래스명;
import 상위패키지.하위패키지.클래스명;
import 상위패키지.하위패키지.클래스명;

주의할 점은 상위 패키지를 import했다고 하위 패키지까지 전부 import되는 건 아니라는 것이다.

또한 서로 다른 패키지에 동일한 이름의 클래스가 존재하고 두 클래스를 모두 사용하고 싶다면 클래스 이름 앞에 패키지 이름을 붙여서 사용하면 된다.

6.2 접근 제한자

접근 제한자는 클래스, 필드, 메소드, 생성자의 접근 범위를 제한하는 역할을 한다. 접근 제한자는 public, protected, default, private가 있다.

  • public : 접근 제한이 전혀 없다. 어디서든 접근이 가능하다.
  • protected : 같은 패키지 또는 자식 클래스에서 사용이 가능하다.
  • private : 같은 클래스 내에서만 사용이 가능하다.
  • default : 같은 패키지 내에서만 사용이 가능하고 다른 패키지에선 접근할 수 없다. 위 3가지 접근 제한자를 제외한 나머지는 default 접근 제한자이다.

이 접근 제한자는 생성자에도 사용 가능하다. 그런데 클래스에 생성자를 선언하지 않았을 때 컴파일러에서 추가하는 기본 생성자가 있다. 이 기본 생성자의 접근 제한은 클래스의 접근 제한과 동일하다. 예를 들어 클래스의 접근 제한이 public이면 기본 생성자의 접근 제한도 public이다.

6.3 getter와 setter

oop에서는 객체 필드를 객체 외부에서 직접 접근하는 것을 막는 경우가 많다. 객체 필드를 외부에서 마음대로 변경할 경우 객체의 무결성이 깨질 수 있기 때문이다.

그래서 객체 필드를 외부에서 직접 접근할 수는 없도록 막고 외부에서 접근할 수 있는 통로로서 getter와 setter메소드를 제공한다. 메소드는 매개변수를 검증하여 유효한 값만 객체 필드로 저장할 수 있기 때문이다.

따라서 클래스를 선언할 때 필드는 private로 선언해서 외부 직접 접근을 막고 getter와 setter 메소드를 public으로 선언하여 제공하는 게 좋다.