본문 바로가기
Java/basic

자바의 클래스, 인스턴스, 메서드 관련 개념

by Ellery 2022. 10. 30.

목차
1. 자바의 클래스 정의, 클래스 구성요소
2. 인스턴스(객체) 선언 - new keyword
3. 메서드 정의
4. 메서드 오버로딩, 메서드 오버라이딩
5. 객체의 생성자
6. this keyword
7. Reflexion API

1. 자바의 클래스 정의, 클래스 구성요소

클래스는 개체를 정의하는 틀이다. (객체의 속성과 기능을 하나로 묶어 놓은 틀)
혹은 데이터와 함수의 결합, 사용자 정의 타입 등으로 정의할 수 있다.

클래스의 구성요소로는 멤버변수, 생성자, 메서드, 초기화 블록, 접근 지정자(access modifier), 그외의 modifier로 분류할 수 있다.

1. 멤버변수(property, member variable, attribute, field, state)

- 객체지향에서 속성에 해당하며 멤버 변수라고도 한다. 여기서 값을 초기화하는 것을 명시적 초기화라고 함

  • 인스턴스 변수: 인스턴스 생성시 메모리를 할당받는다. heap 메모리 영역에 할당되고 GC에 의해 관리됨.
  • 지역변수: 클래스 영역 이외의 영역에서 선언됨(메서드 생성자, 초기화 블럭 내부, 변수 선언문이 실행될 때 메모리를 할당받음)
  • 클래스 변수: static 키워드가 붙으면 JVM의 method area(클래스 영역)에 저장된다. 이미 클래스를 사용하기 이전에 메모리를 할당 받음
  • static 키워드 정리
    1. static 멤버변수 및 멤버함수는 클래스에 속함(같은 이름으로 딱 1개만 존재), static 아닌 것은 개체에 속함(따라서 개체 수만큼 존재)
    2. 객체보다 상위 개념인 클래스에 종속된 메서드, 변수가 필요할 때 static 메서드, static 변수를 사용하자
    3. 클래스 메서드는 인스턴스 변수를 사용할 수 없음. 인스턴스가 생성되기 전에 클래스 메서드가 이를 사용할 수 있기 때문에 사용할 수 없다
    4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면 static을 붙이는 것을 고려해보자.
    5. 클래스의 멤버 변수 중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있으면 static을 붙이자
    6. 작성한 메서드 중에 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 static를 붙이는 것을 고려해보자
    7. 자바에서의 static 사용 ex)
      1. Math 클래스 관련 메서드와 같이 단순 계산 로직을 개체로 만들고 싶지 않을 때.
      ex) 예시로 들 수 있는 Math 클래스의 함수들, java.lang.System 패키지의 out.print() 메서드
      2. 개체 단위가 아니라 클래스 단위에서 뭔가를 하고 싶을 때
      이 클래스에서 총 몇 개의 개체를 만들었는지 카운트할 수 있다)
  • 개체 생성시 멤버변수는 0에 준하는 값으로 초기화해 준다 ↔ C는 쓰레기값을 넣어줌
  • Java의 모든 개체는 포인터형이라서 구분 없이 . 연산자 사용 ↔ C에서 멤버변수 접근시 . 연산자는 구조체의 멤버변수에 접근, → 연산자는 포인터로 참조 중인 구조체의 멤버변수에 접근
  • Java는 포인터 역참조용 *연산자가 없으므로 주소를 직접 읽을 방법 자체가 없음. 따라서 언제나 그 주소에 저장된 값을 읽어옴, 그 주소에 포인터 연산도 불가능
  • 인스턴스 변수 초기화 순서: default, 명시적 초기화, 인스턴스 초기화 블럭, 생성자
  • 클래스변수 초기화 순서: default, 명시적 초기화, 클래스 초기화 블럭
Human adam = new Human(); // human_t* adam = (human_t*)malloc(sizeof(human_t));
// adam.name: null
// adam.age: 0
// adam.sex: null

ada.name = Sex.MALE; // adam->sex = MALE; 
adam.name = "Adam"; // adam->name = "Adam"
adam.age = 20; // adam->age = 20;

adam.walk(); // ++age
adam.eat(); // --age

2. 생성자

생성자는 개체 생성 시에 자동으로 호출되는 특수한 함수이다. 반환형은 없음(void가 아님). 함수명은 클래스명과 동일함.

  • 생성자로 초기화를 하는 것이 대체로 바람직함(개체 생성 후, 변경되는 것은 바람직 하지 않음) .
    생성자도 함수이므로 선조건, 후조건이 적용됨. 실수 방지를 위해 이를 지키는 것이 좋다. (스프링에서 빈의 의존성 주입시에 생성자 주입을 권장하는 이유)
    • **선조건 : (메소드)연산의 시작 전에 참으로 가정되는 조건. (연산이 시작할 때의 상황을 기술) 보통 데이터 무결성 준수도 함께 이루어 진다.
    • **후조건 : (메소드)연산의 종료 후 참인 것이 보장되는 조건. (연산이 끝난 후의 상황을 기술)
  • 생성자 초기화를 하지 않는다면 객체 생성 시점에 멤버변수의 값이 주입되지 않은 채로 서비스에서 이용될 수도 있고, 초기화를 위한 로직이 중복될 수 있고 개발 과정에서 실수가 생길 수도 있다. 외부 라이브러리를 사용할 경우에 바이트코드만 제공되기 떄문에 클래스 선언이 있는 소스파일을 열어볼 수 없어 해당 클래스를 이용하기가 힘들 수 있다.
    • 함수는 블랙박스이다. 호출자와 함수의 분명한 책임의 분리가 필요하다. 책임의 분리는 함수 시그니처, 선조건, 후조건 등으로 확실히 정리되어야 한다.
    public class Human {
        public String name;
        public int age;
        public Sex sex;
    
        public Human(String name, Sex sex) {
            this.name = name;
            this.sex = sex; // 중복코드
        }
    
        public Human(String name, int age, sex sex) { // 생성자 오버로딩. 같은 이름이지만 파라미터 형식이 다른 메서드를 여러개 정의
            this.name = name;
            this.age = age;
            this.sex = sex; // 중복코드
        }
    
    }
    
    Human adam = new Human("adam, 20, Sex.MALE);
    Human jenny = new Human(); // 컴파일 에러. 매개변수를 받지 않는 생성자가 없음. 아예 생성자 코드가 없다면 컴파일러가 기본 생성자를 만들어줌. 
    // 프로그래머가 생성자를 작성하면 기본 생성자는 생성되지 않음.
  • 생성자 오버로딩 시에 코드 중복을 피하는 코드 작성 요령:
    매개변수 수가 적은 생성자에서 매개변수 수가 많은 생성자를 호출하면 됨( this()를 이용하면 다른 생성자 호출가능)
  • public Human(String name, Sex sex) { this(name, sex == Sex.MALE ? 1 : 5, sex); }

3. 메서드

객체지향에서 기능(행위)에 해당되며, 클래스를 사용하여 메서드 내에 정의된 행위를 실행하는 역할을 한다

  • 인스터스 메서드: 인스턴스 변수와 연관된 작업을 함. 인스턴스를 통해서만 호출이 가능하다
  • 클래스 메서드: 인스턴스와 관계없는 정적 메서드이다.→ 클래스 설계시 모든 인스턴스에서 공통으로 사용하는 멤버변수는 static을 붙인다. (모든 인스턴스에서 같은 값이 유지되려면)
  • → 메서드 내에서 인스턴스 변수를 사용하지 않으면 static을 붙인다. (메서드 호출시간이 짧아짐)
  • → 클래스 메서드는 인스턴스 변수를 사용할 수 없다.(클래스 메서드가 호출될 때는 인스턴스가 존재하지 않을 수 있음)

4. 초기화 블록

블록 내에서 명시적 초기화에서는 불가능한 초기화를 수행할 수 있다. 

//접근지정자 class(키워드) 클래스이름 { }
public class Person {
	//필드, 멤버변수
	private String name;
	private String age;// 인스턴스 변수
	static String var; // 클래스변수
	
	//default 생성자, 생략이 가능하지만 파라미터를 가진 생성자가 있을시 반드시 명시해야한다.
	public Person(){
		
	}
 
	static { // 클래스 초기화 블록
		classVariable = "hello";
	}

	{ // 인스턴스 초기화 블록
		instanceVariable = "hi";	
	}

	//파라미터를 가진 생성자, 파라미터를 가지고 변수를 초기화한다.
	public Person(String name, String age){
		this.name = name;
		this.age = age;
	}
	
	//메소드, 이름을 가져오는 행위를 한다.
	public String getName(){
		//메소드 내부 기능
		return name;
	}
}

5. 접근지정자 access modifier

  • public : 누구나 접근 가능
  • protected : 자식들만 접근 가능
  • 생략 or default : 같은 패키지에 속한 클래스들만 접근 가능→ public이 아닌 내포클래스를 최상위 클래스로 바꿀 때 쓸 것
    • 내포 클래스는 가독성 문제가 생길 수 있음. 따라서 별도의 클래스로 분리시키는 것이 트렌드
    • 이 떄 접근 권한을 패키지 내로 제한하는 것이 public보다 나음
  • → public 대신 패키지 접근제어자를 사용할 수 있다면 그리할 것(어떤 패키지 안에서만 사용되는 클래스)
  • private 외부 접근 금지, 클래스 내부에서만 실행 가능

6. 그 외의 modifier

  • static: 클래스에 속한 정적 변수, 정적 메서드가 됨
  • final: 클래스 앞에 붙으면 해당 클래스는 상속할 수 없음. 변수, 메서드 앞에 붙으면 수정되거나 오버라이딩 불가
  • abstract: 클래스 앞에 붙으면 추상 클래스가 됨. 접근하려면 상속받아야 됨.
  • transient: 변수 또는 메서드가 포함된 객체를 직렬화할 때 해당 내용은 무시됨.
  • synchronized: 메서드는 한 번에 하나의 쓰레드에 의해서만 접근 가능함
  • volatile: 해당 변수의 조작에 CPU 캐시가 쓰이지 않고 항상 메인 메모리로부터 읽힘.

2. 인스턴스(객체) 선언 - new keyword

  • 클래스를 정의 - 클래스로부터 객체를 만들어 사용. 이러한 과정을 클래스의 인스턴스화라 정의한다.
  • 객체는 new 키워드를 이용해 생성이 가능하다. new 키워드를 이용하면 heap영역에 데이터를 저장할 영역을 할당 받은 후 해당 영역의 주소를 객체에게 반환하여 객체를 사용할수 있도록 만들어준다.
  • 객체를 생성하기 위해 사용하는 것이 클래스의 구조 중 하나였던 생성자이다.
Person person = new Person();  // 기본 생성자
Person person1 = new Person("MCM",30); 
person1.getName();  //

Map<String, Integer> map = new HashMap<>();

3. 클래스의 메서드 정의

클래스 내부의 메소드는 access modifier, return type, method name, <parameter...>로 구성된 정의부와 메소드의 기능을 호출하는 호출부로 구성된다.

// 접근지정자 리턴타입 메소드명(파라미터)
public String getName() {... 호출부 ...}
public void setName(String name) {..}
  • getter, setter: 캡슐화를 위해서 private 멤버변수에 대한 접근, 변경을 메소드로 하도록 함
    • getter, setter 같은 함수를 통한 데이터 접근의 객관적인 장점
      1. 멤버변수를 저장하지 않고 필요할 때마다 getter에서 계산 가능 ex) 질량, 중력 멤버변수로부터 무게 계산
      2. setter에서 추가적인 로직을 실행할 수 있음 ex) 음수의 나이가 인자로 들어올 경우 무시
      3. 상속을 통한 다형성 구현 가능
    • best practice
      1. 멤버변수는 private(information hiding)
      2. 새 개체는 유효하도록 한다 - 개체는 살아있는 동안 언제나 유효한 상태여야 이상적
        • 처음 생성될 때부터 소멸될 때까지 유효해야 실수를 막을 수 있음
        • 생성자를 통해 이를 강제할 수 있음 - 매개변수가 틀릴 경우 컴파일 자체가 안되도록
      3. getter는 자유롭게 추가
        • 사용자가 알 필요 없는 정보는 보여주지 않는게 정석
        • 그러나 보여줘도 큰 문제 없으면 getter는 보통 자유롭게
        • → 어떤 개체의 레퍼런스를 반환할 때는 문제될 수도 있음
        • getXXX, isXXX(boolean)
      4. setter는 고민 후에 추가
        • 이상적인 개체의 상태 수정법
          • 그 개체의 사용자가 어떤 동작을 지시
          • 그 동작의 결과로 개체 안에 어떤 상태가 바뀜(개체 스스로 상태를 변경)
          • setter는 데이터를 직접 바꾸므로 가능한 피하는 게 좋음
          • 개체가 불확실한 상태로 되는 경우를 최대한 막자
    // 이상적인 개체의 상태 수정법 예시
    public class Classroom {
    	private int[] scores;
    	private float mean;
    	
    	public boolean setScore(int idx, int score){
    		score[idx] = score; // idx검사 생략
    		**updateMean();**
    		
    		return true;
    	}
    	private void updateMean(){
    		this.mean = 계산결과;
    	}
    }
    // main.java
    classroom.setScore(1, 100); // 입력될 때마다 업데이트됨
    classroom.setScore(13, 20);
    

4. 메서드 오버로딩, 메서드 오버라이딩

  • OOP의 다형성을 이용해서 코드의 변경과 확장을 편리하게 해줌
    1. 메소드 오버로딩(Method Overloading)
      • 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것을 뜻함(메서드 이름은 같고, 매개변수의 갯수와 타입은 다른 경우)
      • 다양한 인자를 받지만, 같은 기능의 함수들의 이름을 통일할 수 있어 이름만으로 메소드를 기억하기가 쉬워진다.
      ↔ (매개변수는 동일하고 리턴타입이 다른 경우에는 메소드 오버로딩이 성립하지 않는다)
      //메소드 오버로딩
      public String getName() {// act(defaultAge)}
      public String getName(int age) {// act(age)}
      
      // 가변인자 사용시 항상 마지막 매개변수여야 함. 
      // 가변인자를 사용한 메서드는 오버로딩 하지 않는 것이 좋음
      public printStream printf(String format, Object... args) {...}
      
      String concatenate(String... str) {...}
      --> 
      System.out.println(concatenate("a", "b"));
      System.out.println(concatenate(new String[] {"a","b"}));
      
      String concatenate(String[] str) {...}
      String result = concatenate(new String[0]); //
      String result = concatenate(null); // 인자를 반드시 넣어줘야함.
      
    2. ex) System.out.println(String), System.out.println(int), System.out.println(char)...
    2. 메소드 오버라이딩(Method Overriding)
    • 상위 클래스가 정의한 메소드를 하위 클래스가 가져와 변경하거나 확장하는 기법 → 하위 클래스에서 메소드를 재정의하는 기법
    class Person {
    	public void info() {
    		System.out.println("사람");
    	}
    }
    
    class Adult extends Person {
    	@Override
    	public void info() {
    		System.out.println("어른");
    	}
    }
    
    class Child extends Person {
    	@Override
    	public void info() {
    		System.out.println("어린이");
    	}
    }
    
    Person person = new Person();
    Adult adult = new Adult();
    Child child = new Child();
    
    person.info();     //사람입니다.
    adult.info();      //어른입니다.
    child.info();      //어린이입니다.
    

5. 객체의 생성자

  • 생성자 : 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드
  • 생성자의 종류 구분 - 기본 생성자, 묵시적 생성자, 명시적 생성자
    1. 기본 생성자 : 클래스 내부에 선언된 생성자가 없는 경우 객체 생성 시에 컴파일러가 자동으로 추가해주는 생성자이다. (명시적으로 작성 안하면 자동으로 추가)
    2. 묵시적 생성자 : 파라미터 값을 가지지 않는 생성자이다.
    3. 명시적 생성자 : 파라미터 값이 있는 생성자이다.
    public class Init {
    		private int number;
    		private String name;
    
    		public Init(int number) {
    			// super() 기본생성자로 숨겨져있음
    			this(); // this() 호출 시에는 오버로딩하는 생성자 맨 첫 라인에 작성
    			this.number = number;
    		}
    		
    		public Init() {
    			this.name = "M";
    		}
    
    		public static void main(String[] args) {
    			Init init = new Init(20);
    			System.out.println(init.number); // 20
    			System.out.println(init.name); // M
    		}
    }
    
  • 생성자는 리턴 타입을 가지지 않는다.
  • 생성자는 클래스 이름과 동일하다.
  • 모든 클래스는 생성자가 반드시 존재하고, 한개 이상의 생성자를 가진다.
  • 클래스 내부에 생성자를 선언하지 않으면 컴파일러가 기본 생성자를 선언해 사용한다.
  • 명시적 생성자만 선언되있는 경우 파라미터가 없는 생성자를 사용하고 싶다면 묵시적 생성자를 선언해주어야한다. (생성자가 클래스 내부에 선언되어 있기 때문에 기본 생성자가 생성되지 않는다.)

6. this keyword

  • this: 인스턴스 자신을 가리키는 참조변수. 인스턴스의 주소가 저장되어 있음. 모든 인스턴스 메서드에 지역변수로 숨겨져있다. 즉, 자기 자신을 나타내는 키워드.
    1. 클래스 내부의 필드 이름과 메소드를 통해 넘어온 파라미터의 변수명이 동일한 경우 this키워드를 이용해 클래스 내부의 필드이름과 파라미터를 구분해준다.
    2. 오버로딩된 다른 생성자를 호출 할 때 → this(parameter)
    3. 객체 자신의 참조값을 전달하고 싶을 때
    class Person {
    	private String name;
    	public Person(String name) {
    		this.name = name;   // 클래스 필드 name = 파라미터 name // 1. 클래스의 속성과 매개변수의 이름이 같을 때 사용
    	}
    
    	public Person getPerson() { // 3. 객체 자신의 참조값을 전달
    		return this;
    	}
    }
    
  • this(), this(parameter): 자기 자신의 생성자. 해당 클래스의 생성자를 재사용 하는 데 쓰인다(생성자 체이닝)
    class Person {
    	private String name;
    	public Person(String name) {
    		this.name = name;   // 클래스 필드 name = 파라미터 name
    	}
    	
    	public Person(String name)
    		this(name+"입니다");
    	}
    }
    
  • this()의 경우 호출하는 곳의 첫 번째 문장에서 호출되어야 한다. 생성자가 파라미터가 있는 경우 this()안에 생성자 파라미터 타입에 맞게 직접 입력하여 사용할 수 있다.
  • super: 자식클래스가 부모 클래스로부터 상속받은 멤버들을 사용하고자 할 때 사용, super(): 자식클래스에서 부모클래스의 생성자를 호출
  • class Parent { int a = 10; } class Child extends Parent { int a = 20; void childMethod() { System.out.println(a); // 20 System.out.println(this.a); // 20; System.out.prinlnt(super.a); // 10; } }

7. reflexion API


- 백기선님의 자바 온라인 스터디 https://github.com/whiteship/live-study 주제를 정리한 내용입니다.

참고
- 자바의 정석 2판
- https://www.javatpoint.com/java-reflection