6. 클래스

6.10 정적멤버

자바는 클래스 로더를 이용해서 클래스를 메소드영역에 저장하고 사용한다.
정적멤버란 메소드 영역의 클래스레 고정적으로 위치하는 멤버를 말한다.
정적멤버는 객체를 생성할 필요없이 클래스를 통해 바로 사용이 가능하다.

6.10.1 정적멤버선언

필드와 메소드는 모두 정적 멤버가 될 수 있다. 정적 필드와 정적메소드로 선언하면 static키워드를 추가하면된다.
static 타입 필드 [=초기값];
대부분의 값은 객체에 소속되는데 예를들어 파이나 korean클래스의 국적 등 바뀌지 않은거 객체마다 작성하는것은 비효율적임.
정적메소드 선언
static 리턴타입 메소드(매개변수){}

Calculator 클래스가 있다고 가정할때
String color; // 계산기별로 색이 다를 수 있음.
static double pi = 3.14159; // 계산기에서 사용하는 파이값은 동일하다.
void setColor(String color) {this.color = color}; // 인스턴스 멤버를 사용하면 인스턴스 메소드로 작성해야함.
static int plus(int x, int y) { return x + y}; // 인스턴스멤버 사용하나도 안함. 그러면 static붙이는게 좋다.

6.10.2 정적 멤버 사용

클래스가 메모리로 로딩되면 정적멤버를 바로 사용할 수 있는데 클래스이름.필드or메소드()해서 사용할 수 있다.
예를들어 위 클래스를 사용하면 Calculator.pi; Calculator.plus이런식으로 사용할 수 있다.
color필드와 setColor메소드를 사용하려면 객체를 호출하고 참조변수로 사용해야함.
정적멤버를 객체 참조변수로 접근하면 경고표시를 낸다.

6.10.3 정적블록

클래스가 로딩할때 자동적으로 실행하는 블록을 말한다.?
static double pi =
필드인데 계산작업을 하고나서 계산작업결과를 초기값으로 넣고 싶을때 정적블록 사용.
static { ... } 클래스가 메모리로 로딩될때 자동으로 실행된다. 메소드영역에 바이트코드가 저장될때
왜있어야하냐? 인스턴스 필드는 인스턴스할때나 생성자에서 값을 줌. 그러나 정적필드는 생성자를 사용하지 못함. 그래서 정적블록이 필요하다.

static String company = "MyCompany";
static String model = "LCD";
static String info;

static{
    info = company + "-" + model; // 여기는 단순하지만 매우 복잡한게 필요하게되면 필요함.
}

보통 정적필드는 값을 제공해놓는데 연산식이 주고싶다. = 정적블록 사용
정적 블록은 클래스가 메모리로 로딩될때 자동으로 실행된다.
정적블록이 클래스 내부에서 여러개가 선언되있을 경우에는 선언된 순서대로 실행된다.
바이트코드될때 이미 계산이 실행되어 저장 .이를 실행하면됨.
System.out.pirntln(Television_info);하면 MyCompnay-LCD이 출력이됨.
정적필드는 생성자에서 초기화작업을 하지 않는다. 인스턴스필드만 생성자에서 초기화 필요

6.10.4 인스턴스 멤버 사용불가

정적메소드와 정적블록은 객체가 없어도 실행된다는 특징때문에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. 또한 객체 자신의 참조인 this도 사용할 수 없다.
인스턴스는 객체를 불러와야 가능한데 정적은 객체가 없어도 가능하기때문에 상식적으로 안된다.
예를들어 인스턴스 필드와 메소드로 field1,method1()가 각각잇고 정적필드와 메소드로 field2 method2()가 있을때

static void method3{
    this.field1 = 10; -> 컴파일에러 사용불가
    this.method1(); -> 컴파일에러 사용불가
    field2 = 10; -> 사용가능 
    method2(); -> 사용가능
}

//정적메소드와 정적블록에서 인스턴스 멤버를 사용하고 싶다면 객체를 먼저 생성하고 참조변수로 접근해야한다.
static void method3{
    //객체 생성
    ClassName obj = new ClassName();
    obj.field1 = 10; -> 사용가능해짐
    obj.method1(); -> 사용가능해짐
}

main()메소드도 동일한 규칙이 적용된다. main()메소드도 정적 메소드이므로 객체 생성없이 인스턴스 필드와 인스턴스 메소드를 바로 사용할 수 없어서 객체를 부르고 참조변수로 사용해야한다.

6.11 final필드와 상수

인스턴스 필드와 정적필드는 언제든지 값을 변경할 수 있다 그러나 경우에 따라서는 값을 변경하는 것을 막고 읽기만 허용해야 할때가 있다.

6.11.1 final필드선언

final 타입 필드 [=초기값];

final필드에 초기값을 줄수 있는 방법은 다음 두가지 밖에없다.
1.필드선언시에 초기값대입 2.생성자에서 초기값대입
복잡한 초기화 코드가 필요하거나 객체 생성시 외부에서 전달된 값으로 초기화한다면 생성자에서 초기화하고 보통은 필드에서 값주는게 좋다.

package ch06ver2.sec11;
public class KoreanExample {
public static void main(String[] args) {
    // 객체생성 시 주민등록번호와 이름 전달
    Korean k1 = new Korean("123456-1234567","감자바");

    //필드값읽기
    System.out.println(k1.nation);
    System.out.println(k1.ssn);
    System.out.println(k1.name);

    //final필드는 값을 변경할 수 없음.
    //k1.nation = "USA"
    //k1.ssn = "123-12-134";

    //비final필드는 값 변경가능
    k1.name = "김자바";
}}

6.11.2 상수선언

우리주변에는 불변의 값이 있다. 불변의 값은 수학에서 사용되는 원주율 파이나 지구의 무게 및 둘레등이 해당된다.
이런 불변의 값을 저장하는 필드를 자바에서는 상수라고 한다.
상수는 객체마다 저장할 필요가 없고 여러개의 값을 가져도 안되기때문에 static 이면서 final인 특성을 가져야한다.
static final 타입 상수 [= 초기값];
복잡한 초기화가 필요한 경우 정적블록안에서 초기화할 수도 있다.
static final double PI = 3.14159;
또한 상수는 정적필드이므로 클래스명.상수로 호출할수잇다.

// 상수 선언및 초기화
static final double EARTH_RADIUS = 6400;

// 상수 선언
static final double EARTH_SURFACE_AREA;

//정적블록에서 상수 초기화
static {
    EARTH_SURFACE_AREA = 4 * Math.PI(자바에서 제공하는 상수) * EARTH_RADIUS * EARTH_RADIUS
}
// 상수읽기
System.out.println("지구의 반지름:" + Earth.EARTH_RADIUS + "km"); //6400.0km
System.out.println("지구의 표면적:" + Earth.EARTH_SURFACE_AREA + "km^2"); //5.147185403641517E8km^2

6.12 패키지

자바의 패키지는 단순히 디렉토리만을 의미하지 앟는다. 패키지는 클래스의 일부분이며 클래스를 식별하는 용도로 사용된다.
예를들어 현재 작성하는 패키지는 ch06.sec12.패키지이름 인거임.
클래스명만으로는 구분할 수 없어서 패키지이름으로 구분하는것. 클래스를 식별하기위해 사용
회사 도메인 역순으로 만든다. mycompany.com 이면 com.mycompany.클래스

패키지는 상위 패키지와 하위패키지를 도트(.)으로 구분한다.
패키지에 속한 바이트 코드 파일(~.class)는 따로 떼어내서 다른 디렉토리로 이동할 수 없다.
예를들어 Car클래스가 com.mycompany패키지에 소속되어있다면
다른디렉토리에 Car.class를 옮겨 저장할 경우 car클래스를 사용할 수 없게된다.

6.12.1 패키지 선언

package 상위패키지.하위패키지; 항상 소스파일 최상단에 위치해야한다.
패키지 이름은 모두 소문자로 작성하는 것이 관례이고 중복ㅈ되지않도록 회사 도메인이름의 역순으로 작성하고 마지막에는 프로젝트 이름을 붙여주는 것이 일반적임.
소스파일(java)가 저장되면 이클립스는 자동으로 컴파일해서 bin디렉토리에 패키지 디렉토리와 함께 바이트코드파일(class)를 생성한다.
만약 패키지 선언이 없다면 default package에 포함시킨다. 프로젝트는 패키지 없이 클래스를 만드는 경우가 드물다.

6.12.2 import문

현재 개발중인 패키지에서 다른 패키지에 있는 libray클래스를 가져와서 쓰고싶다. 다른패키지에서 어떤거를 쓸거냐?
import문을 사용하면된다.
만약 동일한 패키지에 포함된 다수의 클래스를 사용해야한다면 클래스 이름을 생략하고 *을 사용할수있다.
import com.hankook.*; hankook 바로 밑에있는 클래스만 사용가능
import com.hankook.project.*; 하위패키지에 있는 클래스를 쓰고싶으면 하위패키지까지 표현해줘야한다.

만약 두가지를 불러와서 사용하는데 같은 클래스가 있다면
import com.hankook.*;
import com.kumho.*;
com.hankook.Tire tire = new com.hankook.Tire();으로 사용해야함.

6.13 접근제한자

객체외부에서 사용하려고 할때 공개된 것만 사용가능
왜 공개? 외부에서 잘못사용하면 무결성이 깨지기때문
예를들어 나이 음수를 넣을수 없는데 음수값을 넣으면 안됨

캡슐화 시켜서 노출필드와 노출메소드가 있고 숨긴필드와 숨긴메소드가 있다.
접근제한자를 사용한다.
public protected default(안건거) private가 있다. -> 갈수록 접근제한이 강화
public 클래스 필드 생성자 메소드 제한범위 없음.
protected 필드 생성자 메소드 같은 패키지이거나 자식 객체만 사용가능(상속에서 자세히)
default 클래스 필드 생성자 매소드 같은 패키지
private 필드 생성자 메소드 객체 내부에서만 사용가능하게

6.13.1 클래스의 접근제한

그냥 class 클래스 {...} -> 같은 패키지안에서는 사용가능한데 다른데선 사용불가능함.
public class 클래스 {...} -> public붙은 클래스는 어떤 패키지에서든 사용가능.
다른패키지안에 class A와 public class B로 되있을 경우 다른패키지 class C에서는 다음과 같다.

public class C {
    A a;
    B b;
}

다른패키지 클래스 C에서 public이걸린 B는 참조가능한데 A는 불가능함.

6.13.2 생성자의 접근제한

public, 생략, private
public 모든패키지에서 생성자 호출가능 = 모든 패키지에서 객체 생성가능
생략(default) 같은 패키지 안에서만 생성자 호출 가능 == 같은 패키지에서만 객체 생성가능
private 클래스 내부에서만 생성자 호출가능 = 클래스 내부에서만 객체 생성 가능

public class A {
//필드선언
A a1 = new A(true);
A a2 = new A(1);
A a3 = new A("문자열");

//생성자 선언 같은 클래스안에서는 어떤 패키지든 사용가능
public A(boolean b){
}

A(int i){        
}

private A(String s){
    }

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

필드와 메소드를 선언할때 접근제한
[public | private] 타입 필드;
[public | private] 리턴 타입 메소드(...) {...};
public 모든패키지에서 필드 메소드 호출가능
생략(default) 같은 패키지 안에서만 필드 메소드 호출 가능
private 클래스 내부에서만 필드 메소드 호출가능

6.14 Getter 와 Setter

Getter 값을 가져오는자 , Setter 값을 세팅하는 자
둘다 메소드임. 값가져오는 메소드는 Getter 값바꾸는 메소드는 Setter
Car myCar = new Car(); mycar.speed = -100;
-> 차속도는 음수가 될수 없기때문에 무결성이 깨짐

이렇게 필드를 직접 변경하면 문제가 생길 수도 있다.
이런 문제점때문에 객체 지행 프로그래밍에서는 직접적인 외부에서의 필드 접근을 막고 대신 메소드를 통해 필드에 접근하는 것을 선호한다.
필드를 private를 걸고 메소드로 접근해서 필드값을 변경하게 만듬.

외부의 값을 검사해서 올바른 값만 받게 만들기 = setter임 set필드이름(대문자시작) 메소드
필드의 타입과 동일한 타입으로 매개변수 선언하고 외부에서 접근할수있도록 public선언

private double speed;
public void setSpeed(double speed)
{
    if (speed < 0) //외부에서 음수를 받으면 0으로 만들어버림.
    {
        this.Speed = 0;
        return;
    }
    else
    {
        this.speed = speed;
    }
}

필드가 프라이빗이라 외부에서 스피드 값 읽지 못함.
외부에서 이를 읽고싶게 하기
get필드이름 메소드 필드값을 읽고 리턴해주는 코드 or 필드값을 가공해서 리턴하고 싶다면 가공된값 넣기

private double speed;
 public double getSpeed()
 {
     double km = speed *1.6;
     return km;
 } 

private 타입 fieldName;

public 타입 getFieldName(){return fieldName;}
public void setFiledName(타입 filedName){this.fieldName = fieldName;}
중요!! 필드타입이 boolean일경우
private boolean stop;
public boolean isStop(){return stop;} // getter를 is로 시작하는 것이관례

getter와setter는 디폴트로 준거로 사용해도 되는데 더 내용을 넣고 싶으면 넣어도됨.
이 필드들의 값을 읽거나 사용하려면 getter와setter을 사용해야함.

package ch06ver2.sec14;
public class Car {
//필드선언
private int speed;
private boolean stop;

//speed필드의 Getter/Setter 선언
//둘다 안만들고 만들고 싶은거만 만들어도됨.
public int getSpeed() {
    return speed;
}
public void setSpeed(int speed) {
    if(speed<0) {
        this.speed = 0;
        return;
    } else {
        this.speed = speed;
    }

}
public boolean isStop() {
    return stop;
}
public void setStop(boolean stop) {
    this.stop = stop;
    if(stop == true ) {
        this.speed = 0;
    }
}}

Car myCar = new Car();
// myCar.speed = 1; private라 불가능
myCar.setSpeed(-50);
System.out.println("현재 속도: " + myCar.getSpeed()); //현재속도: 0 

myCar.setSpeed(60);
System.out.println("현재 속도: " + myCar.getSpeed()); //현재속도: 60

if(!myCar.isStop()) //메소드호출 
{
    myCar.setStop(true);
    System.out.println("현재 속도: " + myCar.getSpeed()); //현재속도: 0 
}

6.15 싱글톤 패턴

new 연산자로 객체를 만든다.
보통 한클래스에 한 객체만 만들어서 사용함. 애플리케이션 전체에서 단 한개의 객체만 생성해서 사용하고 싶다면? 싱글톤 패턴을 적용할수잇다.
프로젝트를 개발할때 객체를 하나만 만들고 이것을 공유해서 사용해야하는 경우가 존재한다. 싱글톤이란 객체하나만 만들게하는 것임.
생성자앞에 private를 붙이는 거임.
인스턴스를 여러개x 한클래스에 한객체만
싱글톤이란 객체를 하나만 만드는 것.
생성자를 private로만들면 다른데서 못만듬.
참조 : https://gyoogle.dev/blog/design-pattern/Singleton%20Pattern.html
왜 쓰나요?
먼저, 객체를 생성할 때마다 메모리 영역을 할당받아야 한다. 하지만 한번의 new를 통해 객체를 생성한다면 메모리 낭비를 방지할 수 있다.
또한 싱글톤으로 구현한 인스턴스는 '전역'이므로, 다른 클래스의 인스턴스들이 데이터를 공유하는 것이 가능한 장점이 있다.

많이 사용하는 경우가 언제인가요?
주로 공통된 객체를 여러개 생성해서 사용해야하는 상황

데이터베이스에서 커넥션풀, 스레드풀, 캐시, 로그 기록 객체 등
안드로이드 앱 : 각 액티비티 들이나, 클래스마다 주요 클래스들을 하나하나 전달하는게 번거롭기 때문에 싱글톤 클래스를 만들어 어디서든 접근하도록 설계

또한 인스턴스가 절대적으로 한 개만 존재하는 것을 보증하고 싶을 때 사용

단점
객체 지향 설계 원칙 중에 개방-폐쇄 원칙이란 것이 존재한다.
만약 싱글톤 인스턴스가 혼자 너무 많은 일을 하거나 많은 데이터를 공유시키면 다른 클래스들 간의 결합도가 높아지게 되는데 이때 개방-폐쇄 원칙이 위배된다.
결합도가 높아지게 되면 유지보수가 힘들고 테스트도 원활하게 진행할 수 없는 문제점이 발생한다.
멀티 스레드 환경에서 동기화 처리를 하지 않았을 때, 인스턴스가 2개가 생성되는 문제도 발생할 수 있다.
따라서 반드시 싱글톤이 필요한 상황이 아니면 쓰지말자. (설계 자체에서 싱글톤 활용을 원활하게 할 자신이 있으면 괜찮음)

private 클래스() {}
public class 클래스{

public 접근 권한을 갖는 정적필드 선언과 초기화
private static 클래스 singleton = new 클래스(); //-----------1
예를들어 private static Tire singleton = new Tire();를 해서 내부에서 객체를 생성해서 초기화함.
외부에서는 new 생성자로 호출 못하게하고 메소드로 호출해서 리턴하는 값을 받는거임.

private접근 권한을 갖는 생성자 선언
private 클래스() {}

public 접근 권한을 갖는 정적 메소드 선언
public static 클래스 getInstance() //--------------2 이름상관없음
{ return singleton; }

1에서는 자신의 타입으로 정적 필드를 선언하고 미리 객체를 생성해서 초기화시킨다.
그리고 private 접근 제한자를 붙여 정적필드값을 변경하지 못하도록 막는다.

2.정적필드값을 리턴하는 get이름() 정적 메소드를 public으로 선언한다.

클래스 변수 1 = 클래스.getInstance();
클래스 변수 2 = 클래스.getInstance();
리턴하는 객체가 singleton을 계속 불러오는거라 번지는 같음 외부에서는 private를 붙여서 사용불가능하나 메소드를 호출해서 리턴값을 받고자함.
결국 같은 번지를 얻어오는 거임.

public class Singleton {
    //private 접근 권한을 갖는 정적 필드 선언과 초기화
    private static Singleton singleton = new Singleton();

    //private 접근 권한을 갖는 생성자 선언
    private Singleton() {    
    }

    //public 접근 권한을 갖는 메소드 선언
    public static Singleton getInstance()
    {
        return singleton;
    }}

// Singleton obj1 = new Singleton(); 컴파일에러
// Singleton obj2 = new Singleton(); 컴파일에러
Singleton obj1 = Singleton.getInstance();
Singleton obj2 = Singleton.getInstance();

//동일 한 객체를 참조하는 지 확인
if (obj1 == obj2)
{
    System.out.println("같은 Singleton 객체입니다.");
}
else
{
    System.out.println("다른 Singleton 객체입니다.");
}

2022.12.14 리뷰

java복습을 시작하였다.
처음 공부를 시작했을 때 클래스에서 멘탈이 터졌던 기억이 있다.
그러나 다시 돌아와서 보니 정말 단순한 이론적인 내용이고 어렵지 않는 내용이었다는 것을 새삼 깨닫게 되었다.
작성을 하면서 놀란것이 자바공부 시작한지 딱 1달이 지났다는 것이다.
어느정도 복습을 하고 다음 단계로 넘어가고자 한다.
중요한 것은 꺾이지 않는 마음!!

+ Recent posts