본문 바로가기
혼자 공부하는 자바

혼자 공부하는 자바 (34강 ~ 39강)

by moca7 2024. 5. 29.

34강. 인스턴스 멤버와 정적 멤버(1) - 인스턴스 멤버와 this

35강. 인스턴스 멤버와 정적 멤버(2) - 정적 멤버와 static

36강. 인스턴스 멤버와 정적 멤버(3) - 싱글톤, final 필드와 상수

37강. 패키지와 접근 제한자(1) - 패키지 선언

38강. 패키지와 접근 제한자(2) - 접근 제한자

39강. 패키지와 접근 제한자(3) - Getter와 Setter 메소드

 

 

 

 

34강. 인스턴스 멤버와 정적 멤버(1) - 인스턴스 멤버와 this

 

 

클래스의 멤버인스턴스 멤버정적 멤버 2가지로 구분할 수 있다.

 

 

인스턴스 멤버

- 객체 마다 가지고 있는 멤버

- 인스턴스 필드: 힙 영역의 객체 마다 가지고 있는 멤버. 객체마다 다른 데이터를 저장.

- 인스턴스 메서드: 객체가 있어야 호출 가능한 메서드.

클래스 코드(메서드 영역)에 위치하지만, 이해하기 쉽도록 객체마다 가지고 있는 메서드라고 생각해도 됨.

 

= 인스턴스 멤버란 객체를 생성한 후 사용할 수 있는 필드메서드를 말함.

이들을 각각 인스턴스 필드와 인스턴스 메서드라고 함.

인스턴스 필드와 인스턴스 메서드는 객체에 소속된 멤버이기 때문에 객체 없이 사용할 수 없다.

 

 

정적 멤버

- 객체와 상관없는 멤버, 클래스 코드(메서드 영역)에 위치.

- 정적 필드 및 상수: 객체 없이 클래스만으로도 사용 가능한 필드.

- 정적 메서드: 객체 없이 클래스만으로도 호출 가능한 메서드.

 

= 정적(static) 멤버는 클래스에 소속된 멤버(필드, 메소드)를 의미한다.

간단히 말하면 static이라는 키워드가 붙은 필드(전역변수) 또는 메소드가 정적 멤버이다.

 

- 정적 필드는 클래스 하나만 존재하기 때문에 모든 객체에서 같은 값을 갖는다.

그래서 선언할 때 객체마다 다른 값을 가져야 할 필요가 있으면 인스턴스 필드로 선언하고,

객체마다 공통된 값을 갖는다면 정적 필드로 선언한다.

 

- 정적 멤버는 객체와 상관없이 사용하는 멤버이기 때문에, 클래스 이름과 함께 도트(.) 연산자로 접근합니다.

그래서 객체를 생성하지 않아도 멤버에 접근이 가능합니다. 

 

 

- 정적 필드(전역변수)는 static이라는 메모리 영역에 저장되어 프로그램이 종료될 때까지 메모리상에 남는다.

인스턴스를 여러번 생성하여도 메모리에서 한 번만 공간을 할당받게 되어 메모리를 아낄 수 있다.

 

- static을 붙이는 가장 중요한 이유는 공유를 하기 위해서이고 쉽게 호출하기 위해서이다.

공유의 의미는 여러 인스턴스들이 동일한 데이터를 사용한다는 것이고,

쉽게 호출한다는 의미는 인스턴스를 생성하지 않고 클래스 바로 접근(클래스.변수명)이 가능하다는 것이다.

 

 

ㅁ 인스턴스 멤버

- 객체를 생성한 후 사용할 수 있는 필드와 메서드.

- 객체 안에 존재하는 필드와 메서드라고 생각하셔도 됩니다.

 

ㅁ this

- 객체 내에서 인스턴스 멤버(필드, 메서드)에 접근하기 위해 사용.          //        여기서 this는 객체를 뜻함.

Car(String model) {   this.model = model;    } 

 

- 인스턴스 필드와 매개 변수의 이름이 다를 경우에는 this를 써도 되고 안 써도 된다.

String model;

Car(String m){      model = m;    }

 

String model;

Car(String m){      this.model = m;    }

 

- 인스턴스 메서드를 같은 객체의 인스턴스 메서드가 사용할 때는 this를 써도 되고 안 써도 된다.

void setSpeed(int speed) { }

void run() { setSpeed(50); }

 

void setSpeed(int speed) { }

void run() { this.setSpeed(50); }

 

 

 

 

35강. 인스턴스 멤버와 정적 멤버(2) - 정적 멤버와 static

 

 

ㅁ 정적(static) 멤버

- 클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메서드

- 정적 멤버 선언은 필드나 메서드 앞에 static 키워드를 붙여주면 됨.

 

 

ㅁ 정적 멤버 사용

- 클래스 이름과 함께 도트 연산자로 접근

클래스.필드;

클래스.메서드( 매개값, ... );

 

- 같은 패키지에 있는 다른 클래스에서 객체 생성 없이 해당클래스이름.필드 or 해당클래스이름.메서드로 사용 가능하다.

 

 

- 메서드 블록 내에서 객체 자신의 멤버(인스턴스 멤버)를 사용하려면 static을 붙이면 안 된다.

 

- 메서드 블록 내에서 인스턴스 멤버를 사용하지 않고 매개 변수나 스태틱 필드만 사용한 경우에는 static을 붙이는 것이 좋다.

반드시 붙여야 하는건 아니다. 인스턴스 메서드는 인스턴스 필드와 정적 필드 모두 접근 가능하기 때문.

 

 

ㅁ 정적 메서드 선언 시 주의점

- 정적 메서드 선언 시 그 내부에 인스턴스 필드와 인스턴스 메서드는 사용할 수 없다. 

정적 메서드는 객체 없이도 실행할 수 있어야 하기 때문에 객체가 필요한 인스턴스 필드와 인스턴스 메서드는 사용 불가능.

- 정적 메서드 선언 시 그 객체 자신 참조인 this 키워드는 사용할 수 없다.

정적 메서드는 객체 없이도 실행할 수 있어야 하기 때문에 그 메서드 안에 객체 자신을 참조할 수 없다. 

 

 

 - this 키워드는 현재 객체의 인스턴스를 참조합니다. 그러나 정적 메서드는 특정 객체의 인스턴스에 속하지 않고 클래스 자체에 속합니다. 따라서, 정적 메서드 내에서는 this 키워드를 사용할 수 없습니다.

 

- main도 정적 메서드다.

 

 

 

 

36강. 인스턴스 멤버와 정적 멤버(3) - 싱글톤, final 필드와 상수

 

 

ㅁ 싱글톤(Singleton)

- 전체 프로그램에서 단 하나의 객체만 만들도록 보장하는 코딩 기법.

 

 

ㅁ 싱글톤 작성 방법

- 클래스 외부에서 new 연산자를 통해 생성자 호출이 불가능하도록 private 접근 제한자를 사용.

- 자신의 타입인 정적 필드 선언 후 자신의 객체를 생성해 초기화.

- 외부에서 호출할 수 있는 getInstance() 선언.

- 정적 필드에서 참조하는 자신의 객체 리턴.

 

- private으로 생성자를 선언하는 것이 첫번째 조건.

- 두번째 조건이 자기 자신의 타입인 필드를 static으로 선언하고 자기 자신의 객체를 생성하고 대입.

(클래스 내부에서는 private으로 선언된 생성자를 이용해 객체를 만들 수 있다.)

- 세번째는 외부에서 호출할 수 있는 getInstance() 선언해라.

이때 리턴 타입을 자기 자신의 타입으로. 

 

- 이러면 외부에서는 클래스 변수 = 클래스.getInstance(); 이런 식으로 객체를 얻음.

(singleton이라는 클래스형 변수를 선언하고, 객체를 생성한 후 주소값을 대입한 것.)

getInstance가 static이므로 클래스.xx() 이렇게 접근함. 

외부 클래스(실행 클래스)의 main 메서드에서 객체 생성 없이 저렇게 객체를 얻는 것이 가능함.

static으로 클래스 변수가 선언되니까 프로그램 실행하자마자 객체도 자동으로 만들어짐.

 

- 클래스 변수가 아무리 많이 생성되어도 다 동일한 객체를 참조함. 

 

 

 

 

- 생성자를 private으로 선언하면, 해당 클래스 외부에서 그 생성자를 직접 호출할 수 없게 됩니다. 즉, 외부 코드에서는 new 키워드를 사용하여 해당 클래스의 인스턴스를 생성할 수 없습니다.

 

- private으로 선언된 클래스를 new 연산자를 통해 외부에서 직접 인스턴스화하려고 하면 컴파일 에러 발생.

객체 생성은 클래스 내부에서 제공하는 메서드를 통해서만 가능.

 

 

ㅁ final 필드

- final이라는 이름처럼 최종적이라는 뜻.

- 초기값이 저장되면 최종값이 되어 프로그램 실행 도중 수정이 불가능 함.

 

 

ㅁ final 필드에 초기값을 주는 방법

 

(1) 단순 값일 경우 필드 선언 시 초기화.      //          주로 정적 필드(상수)

 

- 단순 값(리터럴, 상수 등)으로 final 필드를 초기화할 때는 필드를 선언하는 시점에 초기화합니다. 주로 상수 값이나 미리 알고 있는 값을 사용할 때 이 방법을 사용합니다. 이 경우에는 주로 정적(static) 필드에 사용됩니다. 이렇게 선언된 필드는 클래스가 로드될 때 초기화됩니다.

 

 

public class Example {                  

         public static final int CONSTANT_VALUE = 100;

}

 

 

 

(2) 객체 생성 시 외부 데이터로 초기화가 필요한 경우 생성자에서 초기화.            //            주로 인스턴스 필드

- fianl 필드이긴 하지만 객체마다 다른 값을 가져야 하는 경우.   

 

- final 필드를 외부 데이터나 동적인 값으로 초기화해야 할 때는 객체가 생성될 때 생성자에서 초기화합니다. 이 방법은 주로 인스턴스 필드에 사용됩니다. 생성자에서 초기화된 final 필드는 한 번 할당되면 다시 변경할 수 없습니다.

 

 

public class Example {

       private final String name;

 

       public Example(String name) {

                   this.name = name;             // 생성자에서 외부 데이터로 초기화

       }

}

 

 

 

 

ㅁ 인스턴스 final 필드

- 객체에 한번 초기화된 데이터를 변경 불가로 만들 경우. ex) 주민등록번호

 

final 타입 필드 [ = 초기값 ];          //        생성자에서 초기화

 

 

ㅁ 정적 final 필드

- 관례적으로 모두 대문자로 작성, 서로 다른 단어가 연결되면 사이에 언더바(_) 삽입.

- 한번 값이 저장되면 바꿀 수없는 불변의 필드라고 해서 일반적으로 상수라고 부름.

- 불변의 값인 상수를 만들 경우.

- 보통 정적 final 필드를 상수라고 함. (다른 말로)

 

static final 타입 상수 = 초기값; 

 

static final double EARTH_RADIUS = 6400;

 

 

ㅁ 예제

-

package sec05.exam05;

 

public class Person {

     final String nation = "Korean";

     final String ssn;

     String name;

 

     Person(String ssn, String name) {

          this.ssn = ssn;

          this.name = name;

     }

}

 

 

package sec05.exam05;

 

public class PersonExample {

     public static void main(String[] args) {

           Person p1 = new Person("123456-1234567", "가나다");

 

           System.out.println(p1.nation);

           System.out.println(p1.ssn);

           System.out.println(p1.name);

    

           p1.nation = "NSA";                                        //              컴파일 에러

           p1.ssn = "654321-6789032";                       //              컴파일 에러

           p1.name = "라라라";

     }

}

 

 

ㅁ 정적 final 필드 예제

-

package se06.exam06;

public class Earth {
        static final double EARTH_RADIUS = 6400;
        static final double EARTH_AREA = 4 * Math.PI * EARTH_RADIUS* EARTH_RADIUS;
}

 

Math는 자바에서 제공하는 표준 클래스.

Math.PI인걸 보니 static 필드. 근데 다 대문자니 final로 선언된거.

static final로 선언되었다는 것을 알 수 있음.

 

-

package se06.exam06;

public class EarthExample {

        public static void main(String[] args) { 
               System.out.println("지구의 반지름 " + Earth.EARTH_RADIUS);
               System.out.println("지구의 표면적 " + Earth.EARTH_AREA);
        }
}

 

 

 

 

37강. 패키지와 접근 제한자(1) - 패키지 선언

 

 

ㅁ 프로젝트 개발 시 클래스를 체계적으로 관리하기 위해 패키지를 사용한다.

클래스와 클래스의 멤버를 사용 범위에 맞게 접근 제한자를 활용한다.

 

 

ㅁ 패키지

- 바이트 코드 파일을 포함하고 있는 이런 폴더를 패키지라고 볼 수 있음.

- 패키지의 물리적인 형태는 파일 시스템의 폴더

- 패키지는 클래스의 일부분으로, 클래스를 유일하게 만들어주는 식별자 역할   //     전체 이름이 달라지는 것.

- 클래스 이름이 동일하더라도 패키지가 다르면 다른 클래스로 인식

- 클래스의 전체 이름은 패키지+클래스 사용해서 다음과 같이 표현

 

상위패키지.하위패키지.클래스

com.mycompany.A              <-       A라고 하는 class의 전체 이름이 이렇다. 

com.yourcompany.B

 

 

ㅁ 패키지 선언

- 클래스 작성 시 해당 클래스(또는 인터페이스)가 어떤 패키지에 속할 것인지를 선언.

 

package 상위패키지.하위패키지;                   

public class ClassName { ... }

 

ㅁ 패키지 이름 규칙

- 숫자로 시작 불가.

- _ 및 $ 제외한 특수문자 사용 불가.

- java로 시작하는 패키지는 자바 표준 API에서만 사용하므로 사용 불가.

- 모두 소문자로 작성하는 것이관례.

 

ㅁ 예제

- sec06 패키지 만들고, sec06.exam01로 그 하위에 exam01 패키지 만듦.

이러면 실제 파일 시스템에 sec06 폴더 안에 exam01 폴더가 생성됨.

 

sec06.exam01.com

sec06.exam01.com.mycompany 

class 이름을 Car로 생성.

 

sec06.exam01.com.mycompany.Car 이게 전체 Car 클래스의 이름.

 

패키지 선언에 쓰인 패키지의 이름과 실제 폴더가 똑같이 구성이 되어야 Car.class를 쓸 수 있음.

 

- 만약 다른 곳에서 Car.class를 쓰고 싶다면 sec06.exam01.com.mycompany.Car 이거 전체를 가져가서 써야 함. 

 

- 이클립스 상단 메뉴 window - show view - navigator로 폴더 확인 가능.

 

 

ㅁ import문

- 다양한 패키지가 만들어지고 무수히 많은 클래스가 작성됨.

우리가 작성한 클래스에서 다른 패키지에 있는 클래스(또는 인터페이스)를 가져와서 써야하는 경우가 매우 많다.

그럴 경우에 import문을 사용해서 내가 어떤 패키지에 있는 클래스를 가져와서 사용하겠다고 선언해야 함. 

 

- 사용하고자 하는 클래스 또는 인터페이스가 다른 패키지에 소속된 경우에

해당 패키지 클래스 또는 인터페이스를 가져와 사용할 것임을 컴파일러에 통지.

 

- 패키지 선언과 클래스 선언 사이에 작성

 

import 상위패키지.하위패키지.클래스이름;        //    사용하고자 하는 클래스가 포함된 패키지 이름도 같이 넣어줘야 한다.

import 상위패키지.하위패키지.*;           //       사용하고자 하는 클래스가 여러개라면 패키지 이름을 적고 *

 

package com.mycompany;

 

import com.hankook.Tire;

[ 또는 import com.hankook.*; ]

 

public class Car {

  Tire tire = new Tire();     }

 

 

- 단, import문을 작성할 때 하위 패키지는 별도로 import를 해야 함.

 

import com.hankook.*;           //       이렇게 선언하면 com.hankook.project에 있는 클래스, 인터페이스는 못가져옴.

import com.hankook.project.*;

 

 

import 문을 작성할 때 상위 패키지를 포함하는 와일드카드(*)를 사용하면 해당 패키지의 클래스들만 임포트되고, 하위 패키지의 클래스들은 포함되지 않습니다.

 


import com.hankook.project.*;는 com.hankook 패키지 내의 클래스와 인터페이스를 포함하지 않습니다.

두 개의 import문은 독립적으로 동작합니다.

 

각각의 import 문은 해당 패키지 내의 클래스와 인터페이스만 임포트합니다.

import com.hankook.*;    //     com.hankook 패키지 내의 모든 클래스와 인터페이스를 포함합니다. 

import com.hankook.project.*;    //     com.hankook.project 패키지 내의 모든 클래스와 인터페이스를 포함합니다.

 

 

ㅁ  다른 패키지에 동일한 이름의 클래스가 있을 경우 import와 상관없이 클래스 전체 이름을 기술해야 한다.

 

package sec07.exam01.mycompany;

import sec07.exam01.hyundai.Engine;
import sec07.exam01.hankook.SnowTire;
import sec07.exam01.kumho.BigWidthTire;

public class Car {

      Engine engine = new Engine();
      SnowTire tire1 = new SnowTire();
      BigWidthTire tire2 = new BigWidthTire();

      sec07.exam01.hankook.Tire tire3 = new sec07.exam01.hankook.Tire();
      sec07.exam01.kumho.Tire tire4 = new sec07.exam01.kumho.Tire();

}

 

- hankook과 kumho에 각각 Tire 클래스가 있음. 그러나 여기서는 import로 선언 안 함.

- 클래스의 전체 이름을 사용하여 클래스의 패키지 경로를 포함하면 import 문 없이 특정 패키지의 클래스를 사용할 수 있습니다. 

 

 

 

 

38강. 패키지와 접근 제한자(2) - 접근 제한자

 

 

ㅁ 어떤 패키지에 클래스를 만들었을 때 그 패키지 안에서만 사용하기를 원할 때나, 모든 패키지의 다른 클래스에서 사용할 수 있게 접근 제한자를 사용함.

 

 

ㅁ 접근 제한자(access modifier)

- 클래스와 인터페이스 및 이들이 가진 멤버의 접근 제한

 

 

 

- public: 외부 클래스가 자유롭게 사용 가능           //        어떤 패키지에 있는 클래스건 다 사용 가능

- protected: 같은 패키지 또는 자식 클래스에서 사용 가능

- default: 같은 패키지에 소속된 클래스에서만 사용 가능

- private: 외부에서 사용 불가  (같은 클래스)      //           클래스 내부에서만. public의 정반대.

 

- 같은 패키지 내에서만 쓸 거면 class A {}로 선언해도 됨.

다른 패키지에서도 쓸 거면 public class A {}로 선언해야 함. (import는 해야 함)

 

- 이클립스에서 Ctrl + Shift + O를 누르면, 자동으로 파일 내에 import 되지 않은 클래스를 찾아서 모두 import 합니다. 

 

 

ㅁ 생성자의 접근 제한에 따라 생성자 호출 가능 여부 결정

 

public class ClassName{

    public ClassName(...) { ... }

 

    protected ClassName(...) { ... }

 

    ClassName(...) { ... }

 

    private ClassName(...) { ... }

}

 

- 생성자를 호출한다는 것은 new 연산자로 호출할 수 있다는 거니까 객체를 만들 수 있다는 것. 

 

- 클래스를 만들 때 생성자를 만들지 않으면 기본 생성자가 생성됨. 

클래스 앞에 public이 붙으면 기본 생성자도 public으로 만들어지고, 

클래스 앞에 public이 붙지 않으면 기본 생성자도 public으로 만들어지지 않음. (default 기본 생성자가 추가됨.)

 

- public 클래스 안에 default 생성자를 만들 수 있음.

 

package sec06.exam04.package1;

 

public class A {

    A() {}

}

 

 

package sec06.exam04.package2;

 

public class C {

    A field = new A();           //         컴파일 에러  

}

 

 

 

- A를 public으로 선언하고 C.java에서 import sec06.exam04.package1.A;를 선언하면 사용 가능.

 

package sec06.exam04.package1;

 

public class A {

   public A() {}

}

 

 

package sec06.exam04.package2;

 

import sec06.exam04.package1.A;           //         이 부분.

 

public class C {

    A field = new A();         

}

 

아니면 import를 안하고  sec07.exam02.A field = new sec07.exam02.A();  이렇게 할 수도 있다.

 

 

ㅁ default로 선언된 생성자, 필드, 메서드는 다른 패키지의 클래스에서 import해도 쓸 수 없다. 

 

 

ㅁ 필드와 메서드의 접근 제한

- default 키워드는 쓸 수 없다. 그냥 비워놓으면 default다. 

 

[ public | protected | private ] [static] 타입 필드;

 

[ public | protected | private ] [static] 리턴타입 메서드(...) { ... }

 

 

- public이어도 다른 패키지에서 생성자, 필드, 메서드에 접근하려면 import를 하거나 전체 패키지 경로를 적어줘야 한다. 

 

 

ㅁ 클래스 앞에 붙는 것은 public 하나뿐이다.

생성자, 필드, 메서드는 넷 다 가능. 

 

 

 

 

39강. 패키지와 접근 제한자(3) - Getter와 Setter 메소드

 

 

ㅁ 우리가 클래스를 작성할 때, 별다른 조건이 없다면 모든 필드는 private으로 선언을 함. 

외부에서 객체에 마음대로 접근할 경우 객체의 무결성이 깨질 수 있기 때문이다.

객체의 무결성이란 그 객체 안에 올바른 데이터가 들어가 있어야 하는데 올바르지 않은 데이터가 들어가있을 수 있다.

 

-

class Car {

    private int speed;

    private boolean stop;

}

 

Car myCar = new Car();

myCar.speed = -60;

 

speed 변수는 0 이상이어야 하는데 외부에서 이 객체에 접근해서 speed 변수에 접근해서 음수를 저장하면 객체의 무결성이 깨지게 되는 것. 

 

 

-

class Car {

    private int speed;

    private boolean stop;

 

    public int getSpeed() {

         return speed;

    }

 

    public void setSpeed(int s) {

         this.speed = s;

    }

}

 

필드를 private으로 선언하고 메서드를 2개 만들어서, 하나는 필드값을 외부로 전달하고 하나는 외부에서 값을 받아서 필드값을 바꾸도록 할 수 있다. 이와 같은 메소드들을 getter, setter 메서드라고 함. getter는 보통 get으로 시작(boolean은 is), setter는 보통 set으로 시작. 보통 뒤에는 필드의 이름이 옴. 

 

 

-

class Car {

    private int speed;

    private boolean stop;

 

    public int getSpeed() {

         return speed;

    }

 

    public void setSpeed(int s) {

         

         if(s < 0)

             { this.speed = 0;

               return; }

         else 

             { this.speed = s; }

    }

}

 

 

이전의 코드는 여전히 speed에 음수값이 전달될 수 있음. 그러지 못하도록 매개값을 검증하여 유효한 값만 필드로 저장하도록 매개값에 대한 검증코드를 작성.

 

 

ㅁ Getter 메서드

- 외부로 필드값을 저달하는 것이 목적. 

- 필드값을 가공해서 외부로 전달할 수도 있음.

 

double getSpeed() {

     double km = speed * 1.6;

     return km;

}

 

객체에 저장되어있는 속도의 단위가 마일인 경우, 외부로 속도값을 전달할 때 km 단위로 환산하여 제공.

 

- 어떤 필드에 대해 getter만 존재하고 setter는 존재하지 않으면 그 필드는 읽기 전용 필드.

 

 

ㅁ 이클립스 상단 메뉴 source - Generate Getters and Setters를 통해 자동으로 만들 수도 있음.