java 클래스(2)

개미Coder
|2024. 4. 22. 16:01
반응형

# 인스턴스 멤버와 this


-우리가 전에 사용하던 필드와 메서드는 인스턴스 멤버라고 불린다.


인스턴스(instance) 멤버란 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드를 말하는데,
이들을 각각 인스턴스 필드, 인스턴스 메소드라고 부른다. 
우리가 지금까지 작성한 모든 필드와 메소드는 인스턴스 멤버들이었다. 
인스턴스 필드와 메소드는 객체에 소속된 멤버이기 때문에 객체 없이는 사용할 수 없다. 


Car클래스에 gas필드와 setSpeed() 메소드가 다음과 같이 선언되어 있다고 가정해보자

public class Car {
int gas; // 필드

void setSpeed(int speed){ // 메서드
}
}




gas필드와 setSpeed() 메소드는 인스턴스 멤버이기 때문에 외부 클래스에서 시용하기 위해서는 
우선 Car객체(인스턴스)를 생성하고 참조 변수 myCar 또는 yourCar로 접근해야 한다.

Car myCar = new Car( ); 
myCar.gas = 10; 
myCar.setSpeed(60);


Car yourCar = new Car( ); 
yourCar.gas = 20; 
yourCar.setSpeed(100);




위 코드가 실행된 후 메모리 상태를 그림으로 표현하면 다음 그림과 같다. 
인스턴스 필드 gas는 객체마다 따로 존재하고, 인스턴스 메소드 setSpeed()는 
객체마다 존재하지 않고 메소드 영역에 저장되고 공유된다.


// 필드
String model;
int speed;

// 생성자(Constructor)
Car (String model) {
    this.model = model;
}

// 메서드
// setSpeed (필드에 있는 speed를 바꿀거야)
// getSpeed (외부에서 speed의 값을 가져올거야)
void setSpeed (int speed) {
    this.speed = speed;
}

void run() {
    for(int i = 10; i <= 50; i += 10) {
        this.setSpeed(i);
        System.out.println(this.model +"가 달립니다. 속도는 시속" + this.speed + "km 입니다.");
    }
Car myCar = new Car("벤츠");
Car yourCar = new Car("BMW");


// 이 둘은 서로 다른 메모리에 있기 때문에 다른 객체이다.
myCar.run();
yourCar.run();


myCar.speed = 100;
System.out.println("myCar.speed = " + myCar.speed);

yourCar.speed = 200;
System.out.println("yourCar.speed = " + yourCar.speed);

 

 

 

# 정적 멤버와 static

 

-static은 인스턴스 멤버인 인스턴스 필드와 인스턴스 메서드를 사용할 수 없다.

-이는 static을 사용하면, 객체를 생성하지 않고 사용할 수 있기 때문이다.

 

다른 필드나 메소드는 객체가 생성이 되어야 메모리에 올라가는데,

 정적 멤버는 클래스 영역에 들어가 있어서 (힙 x, 스택 x) 객체가 없어도 고정적으로 클래스가 컴파일 되면 메모리에 바로 등록이 된다. (즉, 객체 없이 사용이 가능하다)

 

-정적 멤버란?

-클래스에 고정된 필드와 메소드 - 정적 필드, 정적 메소드

 

-정적 멤버는 클래스에 소속된 멤버

-객체 내부에 존재하지 않고, 메소드 영역에 존재

-정적 멤버는 객체를 생성하지 않고 클래스로 바로 접근해 사용

 

-정적 멤버 선언

 

 

 

 

-인스턴스 필드, 메서드와 정적 메서드, 정적 필드의 차이

// 인스턴스 필드
double bi = 3.14159;

// 정적 필드
static double pi = 3.14159;

// 인스턴스 메서드
int plus(int x, int y) {
    int result = x + y;
    return result;
}	
// 정적 메서드
static int plus2(int x, int y) {
    int result = x + y;
    return result;
// 객체 생성 없이 (new) 바로 사용이 가능하다.
int result = Calculator.plus2(10, 320);
System.out.println("result = " + result);

double result2 = 10 * 10 * Calculator.pi;
System.out.println(result2); // 314.159

// 이렇게 객체를 생성을 해야 인스턴스 멤버를 사용할 수 있다.
Calculator cal = new Calculator();
cal.bi = 5.14;
System.out.println("cal.bi = " + cal.bi); // 5.14

 

 

 

 

-static 정적 멤버의 대표적인 예시

-클래스를 만들지 않고도 사용이 바로 가능하다.

int result3 = (int) (Math.random());

 

 


# 인스턴스 멤버 선언 vs 정적 멤버 선언의 기준

원의 넓이나 둘레를 구할 때 필요한 파이(π)는 Calculator 객체마다 가지고 있을 필요가 없는 
변하지 않는 공용적인 데이터이므로 정적 필드로 선언하는 것이 좋다. 
그러나 객체별로 색깔이 다르다면 색깔은 인스턴스 필드로 선언해야 한다.

pub1ic  c1ass  Ca1cu1ator  { 
String co1or; // 계산기별로 색깔이 다를 수 있다
static doub1e pi = 3.1 4159; // 계산기에서 사용하는 파이(π) 값은 동일하다
}



메소드의 경우, 인스턴스 메소드로 선언할 것인가, 아니면 정적 메소드로 선언할 것인가의 판단 기준은 
인스턴스 필드를 이용해서 실행해야 한다면 인스턴스 메소드로 선언하고, 
인스턴스 필드를 이용하지 않는다면 정적 메소드로 선언한다. 

예를 들어 Calculator 클래스의 덧셈, 뺄셈 기능은 인스턴스 필드를 이용하기보다는 
외부에서 주어진 매개값들을 가지고 덧셈과 뺑셈을 수행하므로 정적메소드로 선언하는 것이 좋다 
그러나 인스턴스 필드인 색깔을 변경하는 메소드는 인스턴스 메소드로 선언해야한다.


pub1ic c1ass Ca1cu1ator { 
String co1or;
static doub1e  pi = 3.14159;
}




메소드의 경우, 인스턴스 메소드로 선언할 것인가, 아니면 정적 메소드로 선언할 것인가의 판단 기준은 
인스턴스 필드를 이용해서 실행해야 한다면 인스턴스 메소드로 선언하고, 
인스턴스 필드를 이용하지 않는다면 정적 메소드로 선언한다. 
예를 들어 Calculator 클래스의 덧셈, 뺄셈 기능은 인스턴스 필드를 이용하기보다는 
외부에서 주어진 매개값들을 가지고 덧셈과 뺄셈을 수행하므로 정적메소드로 선언히는 것이 좋다 
그러나 인스턴스 필드인 색깔을 변경하는 메소드는 인스턴스 메소드로 선언해야한다.

pub1ic  Ca1cu1ator  { 
String color; // 인스턴스 필드
void  setColor (String color) { // 인스턴스 메소드 (this는 객체에서 사용하므로 static을 
this.color = co1or; // static을 붙이게되면 컴파일 에러가 발생한다.)
}
static int p1us (int x,  int y)  { // 정적 메소드
return x  + y;  
} 
static int minus (int x,  int y)  {  // 정적 메소드
return X   -    y; 
} 
}

 

 

 

-색같은 것은 정적 멤버가 아닌 인스턴스 멤버를 활용한다!

Calculator cal1 = new Calculator();
cal1.setColor("검정색");

Calculator cal2 = new Calculator();
cal2.setColor("빨간색");

 

 

 

 

-생성자의 역할은 필드 초기화이다.

-static은 정적 초기화 블럭이라는 것을 지원해준다. (복잡한 계산을 하기 위해서)

 

 

# 정적 초기화 블록 (정적에서의 생성자 같은 역할, static은 생성자를 사용할 수 없기 때문이다)

-정적 필드는 다음과 같이 필드 선언과 동시에 초기값을 주는 것이 보통이다.

static double  pi = 3.14159;

그러나 계산이 필요한 초기화 작업이 있을 수 있다. 

-인스턴스 필드는 생성자에서 초기화하지만, 
 정적 필드는 객체 생성 없이도 사용해야 하므로 생성자에서 초기화 작업을 할 수 없다.
(생성자는 객체 생성시에만 실행되기 때문이다. )
그렇다면 정적 필드를 위한 초기화 작업은 어디에서 해야 할까? 
자바는 정적 필드의 복잡한 초기화 작업을 위해서 정적 블록(static block) 을 제공한다. 

다음은 정적블록의 형태를보여준다.

static {
//....
}

정적 블록은 클래스가 메모리로 로딩될 때 자동적으로 실행된다. 
정적 블록은 클래스 내부에 여러개가 선언되어도 상관없다. 
클래스가 메모리로 로딩될 때 선언된 순서대로 실행된다. 

 

 

 

 

-this는 객체를 의미하기에 static 블록에서는 this를 사용할 수 없다.

-조건문, 반복문등의 모든 자바의 기능을 적용할 수 있다.

 

 

-static 블럭 사용 예제

public class Television {

	static String company = "samsung";
	static String model = "LCD";
	static String info;
	static int from1To10Sum;
	
	static {
		// this는 객체를 의미하기에 static 블록에서는 this를 사용할 수 없다.
		info = company + "-" + model; // 조건문, 반복문등의 모든 자바의 기능을 적용할 수 있다.
		
		int sum = 0;
		for(int i = 0; i <= 10; i++) {
			sum += i;
		}
		from1To10Sum = sum;
	}
	
}
System.out.println(Television.info); // samsung-LCD

String result = Television.info;
System.out.println(result); // samsung-LCD

System.out.println(Television.from1To10Sum); // 55

int result2 = Television.from1To10Sum;
System.out.println(result2); // 55

 

 

 

-인스턴스 멤버와 정적 멤버에 대한 이해를 돕기 위한 예제

public class StaticExample01 {

	// 인스턴스 필드, 인스턴스 메서드
	int field1;
	void method1() {
		System.out.println("인스턴스 메서드를 호출합니다.");
	}
	
	// 정적 필드, 정적 메서드
	static int field2;
	static void method2() {
		System.out.println("인스턴스 메서드를 호출합니다.");
	}
	
	// static block
	static {
		field1 = 10; // 객체가 있어야 사용가능한 인스턴스 필드이기에 사용 불가능
		method1(); // 객체가 있어야 사용가능한 인스턴스 메서드이기에 사용 불가능
		field2 = 20; // 정적 필드값 초기화
		method2(); // 정적 메서드 호출
	}
	
	// 정적 메서드
	static void method3() {
		StaticExample01 se = new StaticExample01();
		se.field1 = 10; // 객체 생성 후 인스턴스 필드값 초기화
		se.method1(); // 객체 생성 후 인스턴스 메서드 호출
		field2 = 100; // 정적 필드값 초기화
		method2(); // 정적 메서드 호출
	}
	
	
}

 

 

메인 메서드 위에 인스턴스 필드와 메서드를 만들어 호출해도 사용이 안된다. (main 메서드에 static이 있기 때문이다)
이렇게 객체를 만들어주면 정상 사용 가능!!

 

 

 

 

 


 

 

 

# 싱글톤(Singleton)

 

-한 가지 객체만 무조건적으로 사용해야 할 때 사용한다.

-이걸 사용하는 이유는 객체를 단 하나밖에 만들 수 없게 해야할 때 (new 연산자 사용 불가능해짐) 사용한다.

 

 

가끔 전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우가 있다.  
단 하나만 생성된다고 해서 이 객체를 싱글톤(Singleton) 이라고 한다. 

싱글톤을 만들려면 클래스외부에서 new 연산자로 생성자를 호출할 수 없도록 막아야 한다. 
생성자를 호출한 만큼 객체가 생성되기 때문이다.

생성자를 외부에서 호출할 수 없도록 하려면 생성자 앞에 private 접근 제한지를 붙여주면 된다. 
접근 제한자는 나중에 자세히 설명하기로 하고, 
여기서는 외부에서 생성자 호출을 막기 위해 private를 붙여준다는 것만 알아두자.

그리고 자신의 타입인 정적 필드를 하나 선언하고 자신의 객체를 생성해 초기화한다. 
참고로 클래스 내부에서는 new 연산자로 생성자 호출이 가능하다. 
정적 필드도 private 접근 제한자를 붙여 외부에서 필드값을 변경하지 못하도록 막는다. 
대신 외부에서 호출할 수 있는 정적 메소드인 getInstance()를 선언하고 
정적 필드에서 참조하고 있는 자신의 객체를 리턴해준다.

다음은 싱글톤을 만드는 코드이다

public  class 클래스 {
// 정적 필드
private static 클래스 singleton = new 클래스() ;

// 생성자
private 클래스() {}
static 클래스 getInstance()  {
return singleton;
}
}

 



외부에서 객체를 얻는 유일한 방법은 getInstance 0 메소드를 호출하는 방법이다. 
getInstance() 메소드는 단 하나의 객체만 리턴하기 때문에 아래 코드에서 변수1과 변수2는 동일한 객체를 참조한다.

클래스 변수1 = 클래스 getInstance();
클래스 변수2 = 클래스 getInstance();

 

 

 

-싱글톤이라는 아이는 new로 새로운 객체 생성을 하지 않고, 클래스 내부에서 자체적으로 생성한다.

 

public class Singleton {

	private static Singleton sgt = new Singleton(); // 클래스 내부에서 객체 생성
	
	private Singleton() { // 생성자의 접근제한자를 private로 변경하여 new를 사용한 객체 생성을 차단
	}
	
	// 아래 타입은 참조 타입이다.
	static Singleton getIncetance() {
		return sgt;
	}
	
}
Singleton sgt1 = new Singleton(); // 생성자를 private으로 제한했기 때문에 새로운 객체를 생성할 수 없다.
Singleton sgt2 = new Singleton(); // 생성자를 private으로 제한했기 때문에 새로운 객체를 생성할 수 없다.


// sgt1과 sgt2는 같은 객체이다.
Singleton sgt1 = Singleton.getIncetance();
Singleton sgt2 = Singleton.getIncetance();

if(sgt1 == sgt2) {
    System.out.println("같은 객체입니다.");
} else {
    System.out.println("다른 객체입니다.");
}

if(sgt1.equals(sgt2)) {
    System.out.println("같은 객체입니다.");
} else {
    System.out.println("다른 객체입니다.");
}

 

 

 

 

 


 

 

# final 필드



final 의 의미는 최종적이란 뜻을 가지고 있다. 
final 필드는 초기값이 저장되면 이것이 최종적인 값이 되어서 프로그램 실행 도중에 수정 할 수 없다는 것이다. 

final 필드는 다음과 같이 선언한다.

final 타입 필드 = 초기값;


final 필드의 초기값을 줄 수 있는 방법은 딱 두 가지 밖에 없다. 
첫 번째는 필드 선언시에 주는 방법이고, 두 번째는 생성자에서 주는 방법이다. 
단순 값이라면 필드 선언 시에 주는 것이 제일 간단하다. 
하지만 복잡한 초기화 코드가 필요하거나 객체 생성시에 외부 데이터로 초기화해야 한다면 
생성자에서 초기값을 지정해야 한다.  

 

// final 값 지정 방법 비결 1번째 : 필드에서 지정해준다.
final String nation = "Korea";
final String ssn;
String name;

// final 값 지정 방법 비결 2번째 : 생성자에서 직접 지정해준다.
public Person(String ssn, String name) {
    this.ssn = ssn;
    this.name = name;
}

final로 지정한 필드 값은 전부 오류가 뜬다.

 

 

 

# 상수 (static final)

 

-상수는 대문자로 표기한다.

일반적으로 불변의 값을 상수라고 부른다. 
불변의 값은 수학에서 사용되는 원주율 파이나, 지구의 무게 및 둘레 등이 해당된다. 
이런 불변의 값을 저장하는 필드를 자바에서는 상수(constant) 라고 한다. 


final 필드는 한 번 초기화되면 수정할 수 없는 필드라고 했다. 
그렇다면 final 필드를 상수 라고 불러도 되지 않을까? 
하지만 final 필드를 상수라고 부르진 않는다. 
왜냐하면 불변의 값은 객체마다 저장할 필요가 없는 공용성을 띠고 있으며 
여러 가지 값으로 초기화될 수 없기 때문이다.

final 필드는 객체마다 저장되고 생성자의 매개값을 통해서 
여러 가지 값을 가질 수 있기 때문에 상수가 될 수 없다.

 

상수 이름은 모두 대문자로 작성하는 것이 관례이다. 
만약 서로 다른 단어가 혼합된 이름이라면 언더바(_)로 단어들을 연결해준다. 
다음은 상수 필드를 올바르게 선언한 예를 보여준다.

static final double PI = 3.14159;
static final double EARTH_SURFACE_AREA;

 

 

 

 

 


 

 

# 패키지

 

-클래스를 기능별로 묶어서 그룹 이름을 붙여 놓은 것

파일들을 관리하기 위해 사용하는 폴더(디렉토리)와 비슷한 개념

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

 

-클래스를 유일하게 만들어주는 식별자

전체 클래스 이름 = 상위패키지.하위패키지.클래스

클래스명이 같아도 패키지명이 다르면 다른 클래스로 취급

 

-패키지명은 소문자여야 한다.

 

# 패키지

프로젝트를 개발하다 보면 적게는 수십 개, 많게는 수백 개의 클래스를 작성해야 한다.   
클래스를 체계적으로 관리하지 않으면 클래스 간의 관계가 뒤엉켜서 
복잡하고 난해한 프로그램이 되어 결국 유지 보수가 어렵게 된다. 
자바에서는 클래스를 체계적으로 관리하기 위해 패키지(package )를 사용한다. 

우리가 폴더를 만들어 파일을 저장 관리하듯이 패키지를 만들어 클래스를 저장 관리한다. 
패키지의 물리적인 형태는 파일 시스템의 폴더이다.
패키지는 단순히 파일 시스템의 폴더 기능만 히는 것이 아니라 클래스의 일부분이다. 
패키지는 클래스를 유일하게 만들어주는 식별자 역할을 한다. 
클래스 이름이 동일하더라도 패키지가 다르면 다른 클래스로 인식한다.
클래스의 전체 이름은 “패키지명+클래스명”인데 패키지가 
상·하위로 구분되어 있다면 도트(.)를사용해서 다음과 같이 표현한다.


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


예를 들어 Car 클래스가 com.mycompany 패키지에 속해 있다면 
Car 클래스의 전체 이름은 cohttp://m.mycompany.Car가 되고 
실제 파일 시스랩에서는 com\mycompany폴더에 Car. class가 위치한다. 

패키지가 중요한 이유는 클래스만 따로 복사해서 다른 곳으로 이동하면 클래스는 사용할 수 없기 때문이다. 
예를 들어 Car 클래스가 com.mycompany 패키지에 소속되어 있을 경우, 
파일 시스템 com\yourcampnay폴더에 Car.class를 저장하면 Car 클래스를 사용할 수 없다. 
클래스를 이동할 경우에는 패키지 전체를 이동시켜야 한다.


 




# 패키지선언

패커지는 클래스를 컴파일하는 과정에서 자동적으로 생성되는 폴더이다. 
컴파일러는 클래스에 포함되어 있는 패키지 선언을 보고 파일 시스템의 폴더로 자동 생성시킨다. 

다음은 패키지를 선언하는 방법이다.

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

public class ClassName {
}


패키지 이름은 개발자가 임의대로 지어주면 되지만 여기에도 지켜야 할 몇 가지 규칙이 있다.
•숫자로 시작해서는 안 되고 _ , $를 제외한 특수 문자를 시용해서는 안 된다.
•java로 시작하는 패키지는 자바표준 API에서만 사용하므로 사용해서는 안 된다,
•모두 소문자로 작성하는 것이 관례이다.

여러 개발 회사가 함께 참여하는 대규모 프로젝트나, 다른 회사의 패키지를 이용해서 개발할 경우,
패키지 이름이 중복될 가능성이 있다. 
그래서 회사들 간에 패키지가 서로 중복되지 않도록 흔히 회사의 도메인 이름으로 패키지를 만든다. 

도메인은 등록 기관에서 유일한 이름이 되도록 검증되었기 때문에, 
도메인 이름으로 패키지를 만들면 다른 회사의 패키지와 중복되는 경우가 발생하지 않는다.

도메인 이름으로 패키지 이름을 만들 경우, 도메인 이름 역순으로 패키지 이름을 지어주는데,
그 이유는 포괄적인 이름이 상위 패키지가 되도록 하기 위해서이다. 
그리고 마지막에는 프로젝트 이름을 붙여주는 것이 관례이다.


com.samsung.projectname
com.hyndai.projectname
com.lg.projectname 
org.apache.projectname


 




# import문



같은 패키지에 속하는 클래스들은 아무런 조건 없이 다른 클래스를 사용할 수 있지만 
다른 패키지에 속히는 클래스를 사용하려면 두 가지 방법 중 하나를 선택해야 한다. 


첫 번째 방법은 패키지와 클래스를 모두 기술하는 것이다. 
패키지 이름이 짧을 경우에는 불편함이 없겠지만, 패키지 이름이 길거나 
이렇게 사용해야 할 클래스 수가 많다면 패커지 이름을 붙인다는 것은 전체 코드를 난잡해 보이게 할 수 있다



두 번째 방법인 import분을 주로 사용한다.   
사용하고자 하는 패키지를 import문으로 선언하고, 클래스를 사용할 때에는 패키지를 생략하는 것이다.


import문이 작성되는 위치는 패키지 선언과 클래스 선언 사이다. 
패키지에 포함된 다수의 클래스를 사용해야 한다면 클래스별로 import문을 작성할 필요 없이 
클래스 이름을 생략하고 대신 *를 사용해서 import문을 한 번 작성하면 된다.
(*는 패키지에 속하는 모든 클래스들을 의미한다.)

import문의 개수는 제한이 없고 필요한 패키지가 있다면 얼마든지 추가할 수 있다. 
주의할 점은 import문으로 지정된 패키지의 하위 패키지는 import 대상이 아니다. 
만약 하위 패키지에 있는 클래스들도 사용하고 싶다면 import문을 하나 더 작성해야 한다. 

예를 들어 com.mycompany 패키지에 있는 클래스도 사용해야 하고. 
com.mycompany.project 패키지에 있는 클래스도 사용해야 한다면 다음과 같이 두 개의 import문이 필요하다.

 

 

 

 

-보편적인 방법이 아님

 

 

com.company.hankook.SnowTire st = new com.company.hankook.SnowTire();

 

 

-이것은 보편적인 방법으로 import문을 불러오는 방법이다. (자동완성 기능 활용해보자)

 

package com.company.mycompany;

import com.company.hankook.SnowTire;
import com.company.kumho.RainTire;

public class Car {

	public static void main(String[] args) {

		SnowTire st = new SnowTire();
		RainTire rt = new RainTire();

 

 

 

-클래스명이 중복되면은 맨 위에 방법으로 보편적으로 쓰지 않는 방법을 사용해야 한다. 물론, 자동완성 기능 쓰면 자동으로 만들어주긴 함

 

Tire t1 = new Tire();
com.company.kumho.Tire t2 = new com.company.kumho.Tire();

 

 

 

# 접근제한자

default 접근 제한은 같은 package는 제한하지 않지만, 다른 package는 제한시킨다.

 

 

main() 메소드를 가지지 않는 대부분의 클래스는 외부클래스에서 이용할 목적으로 설계된 리어브러리 클래스이다. 
라이브러리 클래스를 설계할 때에는 외부 클래스에서 접근할 수 있는 멤버와 
접근할 수 없는 멤버로 구분해서 필드, 생성자, 메소드를 설계하는 것이 바람직하다. 

객체 생성을 막기 위해 생성자를 호출하지 못하게 하거나 
객체의 특정 데이터를 보호하기 위해 해당 필드에 접근하지못하도록 막아야 한다. 
그리고 특정 메소드를 호출할 수 없도록 제한할 필요가 있다. 
자바는 이러한 기능을 구현하기 위해 접근 제한자(Access Modifier)를 제공하고 있다.

접근 제한지는 public, protected, default, private과 같이 네 가지 종류가 있다. 
public 접근 제한자는 단어의 뜻 그대로, 공개한다는 의미를 가지고 있다. 
public 접근 제한자는 외부 클래스가 자유롭게 사용할 수 있는 공개 멤버를 만든다. 


protected 접근 제한자는 같은 패키지 또는 자식 클래스에서 시용할 수 있는 멤버를 만든다. 


private 접근 제한자는 단어의 뭇 그대로 개인적인 것이라 외부에 노출되지 않는 멤버를 만든다. 

private 접근 제한자는 같은 클래스내에서만 접근할 수 있다.


위 세 가지 접근 제한자가 적용되지 않은 멤버는 default 접근 제한을 가진다. 
default 접근 제한자는 같은 패키지에 소속된 클래스에서만 사용할 수 있는 멤버를 만든다. 



 

# 클래스의 접근 제한


-클래스를 선언할 때 고려해야 할 사항은 같은 패키지 내에서만 사용할 것인지, 
 아니면 다른 패키지에서도 사용할 수 있도록 할 것인지를 결정해야 한다.   
-클래스에 적용할 수 있는 접근 제한은 public과 default 단 두 가지인데, 다음과 같은 형식으로 작성한다.


// default 접근 제한 
class 클래스 {
}

// public 접근 제한 
public class 클래스 {
}

 

# default 접근제한
-클래스를 선언할 때 public을 생략했다면 클래스는 default 접근 제한을 가진다. 
-클래스가 default 접근 제한을 가지게 되면 같은 패키지에서는 아무런 제한 없이 사용할 수 있지만 
 다른 패키지에서는 사용할 수 없도록 제한된다.




# public 접근제한
클래스를 선언할 때 public 접근 제한지를 붙였다면 클래스는 public 접근 제한을 가진다. 
-클래스가 public 접근 제한을 가지게 되면 같은 패키지뿐만 아니라 
 다른 패키지에서도 아무런 제한 없이 사용할 수 있다. 
-클래스를 다른 개발자가 사용할 수 있도록 라이브러리 클래스로 개발되어야 한다면, 
 반드시 public 접근 제한을 갖도록 해야 한다. 
-인터넷으로 배포되는 리어브러리 클래스들도 모두 public 접근 제한을 가지고 있다.

 

 

 

# 생성자의 접근 제한

-객체를 생성하기 위해서는 new 연산자로 생성자를 호출해야 한다.  
 하지만 생성자를 어디에서나 호출할 수 있는 것은 아니다.   
-생성자가 어떤 접근 제한을 갖느냐에 따라 호출 가능 여부가 결정된다.
-생성자는 다음과 같이 public , protected, default, private 접근 제한을 가질 수 있다.


public class ClassName  {

// public 접근 제한 
public ClassName (...) {  ...  } 

// protected 접근 제한
protected ClassName (...) {  ...  } 

// default  접근 제한
ClassName (...) {  ...  } 

// private  접근 제한
private ClassName (...) {  ...  } 
}


-클래스에 생성자를 선언하지 않으면 컴파일러에 의해 자동적으로 기본 생성자가 추가된다.   
-자동으로 생성되는 기본 생성자의 접근 제한은 클래스의 접근 제한과 동일하다. 
-클래스가 default 접근 제한을 가지면 기본 생성자도 default 접근 제한을 가지고, 
 클래스가 public 접근 제한을 가지면 기본 생성자도 public 접근 제한을 가진다.



  # public (누구든 호출 가능)
public 접근 제한은 모든 패키지에서 아무런 제한 없이 생성자를 호출할 수 있도록 한다. 
생성자가 public 접근 제한을 가진다면 클래스도 public 접근 제한을 가지는 것이 정상적이다. 
클래스가 default 접근 제한을 가진다면 클래스 사용이 같은 패키지로 한정되므로 
비록 생성자가 public 접근 제한을 가지더라도 같은 패키지에서만 생성자를 호출할 수 있다.

  # protected 
protected 접근 제한은 default 접근 제한과 마찬가지로 
같은 패키지에 속하는 클래스에서 생성자를 호출할 수 있도록 한다. 
차이점은 다른 패키지에 속한 클래스가 해당 클래스의 자식(child) 클래스라면 생성자를 호출할 수 있다


  # default (같은 패키지에서는 호출 가능, 다른 패키지 호출 불가능)
생성자를 선언할 때 public 또는 private를 생략했다먼 생성자는 defauIt 접근 제한을 가진다. 
default 접근 제한은 같은 패키에서는 아무런 제한 없이 생성자를 호출할 수 있으나,
다른 패키지에서는 생성자를 호출할 수 없도록 한다.


  # private (같은 클래스 내에서만 객체를 만들 수 있게 해준다)
private 접근 제한은 동일 패키지이건 다른 패키지이건 상관없이 생성자를 호출하지 못하도록 제한한다.
따라서 클래스 외부에서 new 연산지로 객체를 만들 수 없다. 
오로지 클래스 내부에서만 생성자를 호출할 수 있고 객체를 만들 수 있다.

 

 

 

 

-접근 제한자 총평

-public 인싸, default 반친구들이랑만 친함, private 의문의 사나이

 

# 필드와 메소드의 접근 제한

필드와 메소드를 선언할 때 고려해야 할 사항은 클래스 내부에서만 사용할 것인지,
패키지 내에서만 사용할 것인지, 아니면 다른 패키지에서도 사용할 수 있도록 할 것인지를 결정해야 한다. 
이것은 필드와 메소드가 어떤 접근 제한을 갖느냐에 따라 결정된다. 
필드와 메소드는 다음과 같이 public , protected, default, private 접근 제한을 가질 수 있다.


//필드 선언
[ public || protected  || private ] [static]  타입 필드;
//메소드 선언
[ public || protected  || private ] [static]   리턴 타입 메소드( ..  )  { ... }


  # public
public 접근 제한은 모든 패키지에서 아무런 제한 없이 필드와 메소드를 시용할 수 있도록 해준다. 
필드와 메소드가 public 접근 제한을 가질 경우 클래스도 public 접근 제한을 가져야 한다.
클래스가 default 접근 제한을 가지게 되면 같은 패키지 안에서만 클래스가 사용되기 때문이다.

  # protected 
protected 접근 제한은 default 접근 제한과 마찬가지로 같은 패키지에 속하는 클래스에서 
필드와 메소드를 시용할 수 있도록 한다. 
차이점은 다른 패키지에 속한 클래스가 해당클래스의 지식 클래스라면 필드와 메소드를 사용할 수 있다

  # default
필드와 메소드를 선언할 때 public 또는 private를 생략했다면 default 접근 제한을 가진다.
default 접근 제한은 같은 패키지에서는 아무런 제한 없이 필드와 메소드를 사용할수 있으나,
다른 패키지에서는 필드와 메소드를 사용할수 없도록 한다.

  # private
private 접근 제한은 동일 패키지이건 다른 패키지이건 상관없이 필드와 메소드를 사용하지 못하도록 제한한다. 
오로지 클래스 내부에서만 사용할 수 있다.

package ex3;

public class Class1 {
	
	// 필드
	public int field1;		// pubilc 필드
	int field2;					// default 필드
	private int field3;	// private 필드
	
	// 메서드
	public void method1() {			// public 메서드
		System.out.println("method1() 호출");
	}
	
	void method2() {							// default 메서드
		System.out.println("method2() 호출");
	}
	
	private void method3() {			// private 메서드
		System.out.println("method3() 호출");
	}
	
	// 생성자 (Constructor)
	public Class1() {
		field1 = 1;		// public 필드값 변경
		field2 = 2;		// default 필드값 변경
		field3 = 3;		// private 필드값 변경
		
		method1();		// 접근 제한자가 public인 메서드 호출
		method2();	// 접근 제한자가 default인 메서드 호출
		method3();	// 접근 제한자가 private인 메서드 호출
	}

}
package ex3;

public class Class2 {

	
	// 생성자
	public Class2() {
		Class1 ca = new Class1();
		ca.field1 = 10;
		ca.field2 = 20;
		ca.field3 = 30;		// 접근 제한자 private은 같은 클래스에서만 접근 가능
		
		ca.method1();
		ca.method2();
		ca.method3();		// 접근 제한자 private은 같은 클래스에서만 접근 가능
	}
	
}
package ex4;

import ex3.Class1;

public class Class3 {

	
	// 생성자
	Class3() {
		Class1 ca = new Class1();
		ca.field1 = 100;
		ca.field2 = 200;	// 접근 제한자 default는 같은 패키지에서만 접근 가능
		ca.field3 = 300;	// 접근 제한자 private은 같은 클래스에서만 접근 가능
		
		ca.method1();
		ca.method2();		// 접근 제한자 default는 같은 패키지에서만 접근 가능
		ca.method3();		// 접근 제한자 private은 같은 클래스에서만 접근 가능
		
	}
}

 

## Getter, Setter

 

-필드의 값을 읽게 할 때는 get, 필드의 값을 변경 할 때는 set

 


-일반적으로 객체 지향 프로그래밍에서 객체의 데이터는 객체 외부에서 직접적으로 접근하는 것을 막는다.
-그 이유는 객체의 데이터를 외부에서 마음대로 읽고 변경할 경우 
 객체의 무결성 (결점이 없는 성질)이 깨어질 수 있기 때문이다. 
 예를 들어 지동차의 속도는 음수가 될 수 없는데, 외부에서 음수로 변경하면 객체의 무결성이 깨진다. 
-실제로 다음 코드는 Car 객체의 speed 필드값을 -100으로 변경시킨다.


myCar.speed  =  -100;


이러한 문제점을 해결하기 위해 객체 지향 프로그래밍에서는 메소드를 통해서 데이터를 변경하는 방법을 선호한다. 
데이터는 외부에서 접근할 수 없도록 막고 메소드는 공개해서 외부에서 메소드를 통해 데이터에 접근하도록 유도한다. 
그 이유는 메소드는 매개값을 검증해서 유효한 값만 데이터로 저장할 수 있기 때문이다. 
이러한 역할을 하는 메소드가 Setter이다. 

 

-set : 해당 필드 값을 변경하겠다.
예를 틀어 자동차의 속도를 setSpeed() 메소드로 변경할 경우 다음과 같이 검증 코드를 작성할 수 있다.

void setSpeed(double speed)  { 
    if(speed < 0) {
        this .speed = 0;   // 매개값이 음수일 경우 speed필드에 0으로 저장 후 메서드 종료
        return;
    } else {
    this.speed = speed;
    }
}



외부에서 객체의 데이터를 읽을 때도 메소드를 사용하는 것이 좋다. 
객체 외부에서 객체의 필드값을 사용하기에 부적절한 경우도 있다. 
이런 경우에는 메소드로 필드값을 가공한 후 외부로 전달하면 된다. 
이런 메소드가 바로 Getter이다. 예를 들어 자동차의 속도를 마일에서 km 단위로 환산해서 
외 부로 리턴해주는 getSpeed() 메소드를 다음과 같이 작성할 수 있다.

double getSpeed() {
    double km = speed*1 .6; // 필드의 값에 1.6을 곱해서 마일을 km로 변경후 리턴
    return km;
}




클래스를 선언할 때 가능하다면 필드를 private로 선언해서 외부로부터 보호하고, 
필드에 대한 Setter와 Getter 메소드를 작성해서 필드값을 안전하게 변경 및 사용하는 것이 좋다. 



다음은 Setter와 Getter 메소드를 선언하는 방법을 보여준다. 검증 코드나 변환코드는 필요에 따라 추가해야 한다.

private 타입 fieldName;  // 필드 접근 제한자 private

public 리턴타입 getFieldName() { // get메서드 접근 제한자 public
    실행문;
    return fieldName;
}

public 리턴타입 setFieldName(매개변수) { // set메서드 접근 제한자 public
    실행문;
    this.fieldName = 매개변수;
}





필드 타입이 boolean일 경우에는 Getter는 get으로 시작하지 않고 is로 시작하는 것이 관례이다.
예를 들어 stop 필드의 Getter와 Setter는 다음과 같이 작성할 수 있다.

private 타입 stop;  // 필드 접근 제한자 private

public 리턴타입 isStop() { // is메서드 접근 제한자 public
    실행문;
    return stop;
}

public 리턴타입 setStop(매개변수) { // set메서드 접근 제한자 public
    실행문;
    this.stop;  = 매개변수;
}




만약 외부에서 필드값을 읽을 수만 있고 변경하지 못하도록 하려면(읽기 전용) Getter 메소드만 선 언해도 좋고,
아니면 Setter 메소드를 private 접근 제한을 갖도록 선언해도 좋다.
이클립스는 클래스에 선언된 필드에 대해 자동적으로 Getter와 Setter 메소드를 생성시키는 기능이 있다. 

 

 

 

 

-여러개 있으면 source 항목을 이용해서 쉽게 만들 수 있다.

 

	public int getHour() {
		return hour;
	}
	
	public void setHour(int hour) {
		if (hour >= 0 && hour <= 23) {
			this.hour = hour;
		}
	}
	
	public int getMinute() {
		return minute;
	}
	
	public void setMinute(int minute) {
		if (minute >= 0 && minute <= 59) {
			this.minute = minute;
		}
	}
	
	public int getSecond() {
		return second;
	}
	public void setSecond(int second) {
		if (second >=0 && second <= 59) {
			this.second = second;
		}
	}

 

 

 

-필드의 값을 setter로 변경이 가능하고, getter로 가져올 수가 있다. (내가 원하는 범위에서만)

-오직 내가 가지고 있는 메서드의 범위 내에서 값을 넣을 수 있다.

-이것을 캡슐화라고 한다.

 

 

 

 

-private과 getter, setter 등등 메서드를 활용한 예제

 

클래스 연습 문제)

1) Tv 클래스와 TvMain 클래스를 생성 ok

2) Tv 클래스에서는 객체를  직접 생성할 수 없으며 하나의 객체 리턴받아 사용할 수 있어야 한다.

3) 외부에서 Tv 클래스의 필드로 직접 접속할 수 없으며 메서드를 사용해서만 필드값을 변경할 수 있어야 한다.

 

4) setPower()메서드 호출시 전원이 켜져야 하며 한번 더 호출시 전원은 꺼져야 한다.

4-1) 채널은 1 ~ 100까지만 변경이 가능하며 100이상 채널을 변경하면 1부터 시작되어야 한다.
4-2) 채널은 1 ~ 100까지만 변경이 가능하며 1이하로 채널을 변경하면 100부터 시작되어야 한다.
4-3) 채널 직접 입력은 1 ~ 100까지만 변경이 가능하며 해당 범위가 아닌경우 "입력하신 X 번은 없는 채널입니다."가 출력되어야 한다.

5-1) 볼륨은 0 ~ 50까지만 조절이 가능하며 0이하로 내려갈 수 없으며 50이상 올라갈 수 없어야 한다.
5-2) 음소거를 누르면 볼륨이 한번에 0으로 변경되어야 하며 음소거를 해제하면 음소거를 해제하기전의 볼륨으로 변경되어야 한다.

6) show() 메서드를 호출하면 전원 on/off상황 , 음소거 on/off상황, 현재 볼륨, 현재 채널 정보를 한번에 확인 가능해야 한다.

package example0422;

public class Tv {

	private static Tv sgt = new Tv();	// 클래스 내부에서 객체 생성
	private Tv() {	// 생성자의 접근제한을 private로 변경하며 new를 사용한 객체 생성을 차단
	}
	
	static Tv getIncetance() {
		return sgt;
	}
	
	private int channel;
	private int volume;
	private int backVolume;
	private boolean volumeOnOff;
	private boolean power;
	
	
	// tv 전원
	
	public boolean tvOnOff() {
		this.power = !power;
		if(power) {
			System.out.println("tv가 켜졌습니다.");
		} else {
			System.out.println("tv가 꺼졌습니다.");
		}
		return power;
	}
	
	
	//	채널 zone
	
	public int getChannel () {
		return channel;
	}
	
	public void setChannel(int channel) {
		if (1<= channel && channel <= 100) {
			this.channel = channel;
		} else {
			System.out.println("입력하신 " + channel + "번은 없는 채널입니다.");
		}
		 if (channel < 1) {
			this.channel = 100;
			return;
		} else if (channel > 100) {
			this.channel = 1;
			return;
		} 
	}
	
	//	 볼륨 zone
	
	public int getVolume() {
		return volume;
	}
	
	public void setVolume(int volume) {
		if (volume >= 0 && volume <= 50) {
			this.volume = volume;
			this.backVolume = volume;
		}
	}
	
	public boolean mute() {
		this.volumeOnOff = !volumeOnOff;
		if (volumeOnOff) {
			this.volume = 0;
			System.out.println("음소거 되었습니다. 현재 tv의 볼륨은 " + this.volume + " 입니다.");
		} else {
			this. volume = this. backVolume;
			System.out.println("음소거가 해제 되었습니다.현재 tv의 볼륨은 " + this.volume + " 입니다.");
		}
		return volumeOnOff;
	}
	
	// tv 현재 정보 한번에 보기
	
	void show() {
		if (power) {
			System.out.print("현재 tv는 켜져있으며,");
		} else {
			System.out.print("현재 tv는 꺼져있으며,");
		} if (volumeOnOff) {
			System.out.print("음소거 상태이며,");
		} else {
			System.out.print("음소거가 해제된 상태이며,");
		}
		System.out.print("현재 볼륨의 크기는 " + this.volume + " 이며, 현재 채널 번호는 " + this.channel + "입니다.");
	}

}
package example0422;

public class TvMain {

	public static void main(String[] args) {

		
		Tv tv =Tv.getIncetance();
		
		tv.tvOnOff();
		
		tv.setChannel(15);
		System.out.println(tv.getChannel());
		
		tv.setVolume(50);
		System.out.println(tv.getVolume());
		
		tv.mute();
		tv.mute();
		
		tv.show();
		
	}

}

 

 

-다른 답안

package example0423;

public class Tv {


	private static Tv tv = new Tv();		// tv 객체 생성 (객체는 이제 이 객체밖에 사용하지 못한다)
	
	private Tv() { // 생성자의 접근 제어자를 private으로 변경 (객체 생성 X)
	}

	static Tv getInceTance( ) {
	return tv;	// getInceTance() 메서드 호출시 Tv의 객체를 리턴한다.	
	}
	
	private boolean power;	// power의 기본값은 false
	private boolean mute;
	private int channel = 1;
	private int vol = 10;
	private int nowVol;

	public static Tv getTv() {
		return tv;
	}

	public static void setTv(Tv tv) {
		Tv.tv = tv;
	}

	public boolean isPower() {
		return power;
	}

	// setPower을 호출하면, 현재 power 값의 반대 값이 저장된다.
	public void setPower() {
		this.power = !power;
		if (power) {
			System.out.println("전원이 켜졌습니다.");
		} else {
			System.out.println("전원이 꺼졌습니다.");
		}
	}

	public boolean isMute() {
		return mute;
	}

	public void setMute() {
		this.mute = !mute;
		if (mute) {
			nowVol = vol;
			vol = 0;
			System.out.println("음소거 되었습니다. ");
		} else {
			vol = nowVol;
			System.out.println("음소거가 해제 되었습니다. 현재 볼륨은 " + vol + "입니다. ");
		}
	}

	public int getChannel() {
		return channel;
	}

	public void setChannelUp() {
		if(channel > 0 && channel < 100) {
			channel++;
		} else {
			channel = 1;
		}
		System.out.println("현재 채널은 " + channel + "번 입니다.");
	}
	
	public void setChannelDown() {
		if(channel > 1 && channel < 101) {
			channel--;
		} else {
			channel = 100;
		}
		 System.out.println("현재 채널은 " + channel + "번 입니다.");
	}

	public void setChannelInput (int channel) {
		if(0 < channel && channel < 101) {
			this.channel = channel;
		} else {
			System.out.println("입력하신 " + channel + "번은 없는 채널입니다.");
		}
		// 아래 문구에 this를 안붙이면, 잘못 입력한 채널 번호가 출력이 된다. (필드쪽은 무조건 this를 붙이자)
		System.out.println("현재 채널은 " + this.channel + "번 입니다.");
	}

	public int getVol() {
		return vol;
	}

	public void setVol(int vol) {
		this.vol = vol;
	}
	
	
	
}
package example0423;

public class TvMain {

	public static void main(String[] args) {

		
		// 이름이 달라도 우린 모두 하나야~~
		Tv tv = Tv.getInceTance();
		Tv tv2 = Tv.getInceTance();
		Tv tv3 = Tv.getInceTance();

		System.out.println(tv);		// example0423.Tv@2133c8f8
		System.out.println(tv2);	// example0423.Tv@2133c8f8
		System.out.println(tv3);	// example0423.Tv@2133c8f8
		
		tv.setPower();	// 전원이 켜졌습니다.
		tv.setPower();	// 전원이 꺼졌습니다.
		
		tv.setChannelUp();	// 현재 채널은 2번 입니다.
		tv.setChannelUp();
		tv.setChannelUp();
		
		tv.setChannelDown();	// 현재 채널은 3번 입니다.
		tv.setChannelDown();
		tv.setChannelDown();
		tv.setChannelDown();
		tv.setChannelDown();
		
		tv.setChannelInput(1257);	// 입력하신 1257번은 없는 채널입니다.
		
		tv.setMute();	// 음소거 되었습니다.
		tv.setMute();	// 현재 볼륨은 10입니다.
		
		
	}
}

반응형

'java(2)↗' 카테고리의 다른 글

java 다형성  (0) 2024.04.23
java 상속  (0) 2024.04.23
java 클래스  (0) 2024.04.19
java 데이터 참조 타입  (0) 2024.04.18
java 제어문(if, switch, for, while)  (0) 2024.04.16