본문 바로가기

학원/복기

0407 - This, Static, Runtime Class, 싱글톤 클래스(Singleton Class)

This


this 키워드 : 메소드에 숨겨져 있는 키워드 


this 키워드는 메소드 내부에 숨겨져 있는 키워드로 메소드를 호출한 객체의 메모리 주소(HashCode)를 자동으로 전달받아 저장하는 키워드이다. 
메소드 내부의 명령에서 객체의 필드 또는 메소드를 사용하기 위해 this 키워드를 사용한다. this 키워드를 사용해 메소드 내부에서 객체를 대신 표현한다. 
 
평상시에는 this 키워드를 사용하지 않아도 자동으로 객체의 필드 또는 메소드에 접근하여 사용된다.

public void display() {

		//this를 쓰지 않아도 JVM에서 알아서 this를 처리해준다.

		//System.out.println("아이디 = " + this.id); 
		System.out.println("아이디 = " + id); 
		System.out.println("이름 =  " + name); 
		System.out.println("이메일 = " + email); 	
		}

 

메소드에 this 키워드가 존재해야하는 이유

 
필드는 객체마다 메모리(HeapArea)에 따로 독립적으로 생성되지만 메소드는 객체와 상관없이 메모리(MethodArea)에 하나만 생성된다. >> 프로토타입 클래스(Prototype Class)
메소드에서 필드를 사용 할 때, this 키워드로 객체를 명확히 구분하여 필드에 접근하도록 사용한다. 
 
 

this 키워드를 사용하는 경우


1. 메소드에서 매개변수의 이름을 필드의 이름과 동일하게 작성한 경우 필드를 표현하기 위해 this 키워드 사용 - 생성자 및 Setter 메소드 

-> 굳이 매개변수와 필드의 이름을 똑같이 만들어 주는 이유는 메소드 사용자가 필드에 명확하게 초기값을 줄 수 있도록 하기 위해서이다.
(편리성을 위해)

 
ex. 클래스 필드에서 선언한 변수 a 와 Num 메소드의 매개변수 a 이름이 같은경우

public class Num {

	//필드 선언 
	private int a,b;
	
	//생성자 선언
	public Num() {
	}

	public Num(int a, int b) {
		this.a = a;  //객체가 가지고 있는 'a필드'에 메소드 'a'를 저장해주세요 (즉, this를 쓰면 '필드'를 가르키는 것임)
		this.b = b;  // => this.a는 클래스 필드의 변수, a는 Num의 메소드 매개변수 
	}
	
	//메소드 선언 
	public void display() {     //() 내부에 Num.this 존재 
	  System.out.println("a =" + a);
	  System.out.println("b =" + b);	
	}
}

 
 

2. 생성자에서 다른 생성자를 호출하여 초기화 작업을 하기 위해서 this 키워드를 사용한다.

 
this (값, 값, ...) : 생성자에서 this 키워드로 다른 생성자를 호출하는 명령

public class Member {
	//기본 생성자 선언
    public Member() {

        //this 키워드로 생성자를 호출하는 명령은 첫번째 명령으로 작성
        //생성자를 호출하는 명령 전에 다른 명령이 먼저 실행될 경우 에러 발생
        this("NoId", "NoName", "NoEmail");
        
        
        //매개변수가 있는 생성자 선언 
        public Member(String id) {
            super(); //super은 추후에 배울 예정 
            this.id = id;
        }

        public Member(String id, String name) {
            super();
            this.id = id;
            this.name = name;
        }

	
	public Member(String id, String name, String email) {
		super();
		this.id = id;
		this.name = name;
		this.email = email;
	}
    }
}
//실행 
public class MemberApp {
    public static void main(String[] args) {

            Member member1 = new Member();

            //Getter 메소드 호출하여 Member 객체의 필드값을 반환받아 출력
            System.out.println("아이디 = " + member1.getId()); 
            System.out.println("이름 =  " + member1.getName()); 
            System.out.println("이메일 = " + member1.getEmail());
    /*
    아이디 = NoId
    이름 =  NoName
    이메일 = NoEmail
    */
}	
}

 
하지만 이 방법은 자바에서는 많이 쓰지 않는다.
객체 생성시 객체 필드에 초기값으로 자동 저장될 기본값 변경하는 것이 더 편리하다.

private String id = "NoId";
private String name = "NoName";
private String email = "NoEmail";

 
 
3. 이벤트 처리 프로그램 또는 다중 스레드 프로그램의 메소드에서 객체 자체를 표현하기 위해 this 키워드를 사용
 
 
 
 
 

Static


Static 키워드 : 객체가 아닌 클래스로 접근하기 위한 기능을 제공하는 제한자
→ 클래스(내부 클래스), 필드, 메소드 작성시 사용 (일반 클래스에서 static 붙이면 에러 발생) 
 
• 제한자(Modifier) : 특별한 기능을 제공(제한)하기 위해 사용하는 키워드
 => Access Modifier(private, package, protected, public), static, final, abstract
 

ex) 학생정보(학번, 이름, 국어, 영어, 총점)를 저장하기 위한 VO 클래스 생성

 
• 인스턴트 필드(Instance Field) : 객체가 생성될 때 메모리(HeapArea)에 생성되는 필드
• 정적 필드(Static Field) : 클래스를 읽어 메모리(MethodArea)에 저장될 때 생성되는 필드 → 클래스가 생성되어지면 만들어지는 필드
: 똑같은 클래스를 수백번 써도 메소드 영역에는 클래스가 딱 하나만 저장된다. 따라서 정적 필드는 객체와 상관없이 객체가 생성되기 전 메모리에 하나만 생성된다. (메소드 영역에 클래스 생성된 후, 객체와 상관없이 하나만 생성정적 필드는 생성자를 이용하지 않는다. 생성자에 초기화 처리하지 않고 직접 초기값을 필드에 저장한다. 클래스로 생성된 모든 객체가 정적 필드 사용이 가능하다. → 공유값 : 메모리 절약 및 필드값 변경 용이 
클래스 외부에서는 객체가 아닌 클래스를 사용하여 접근 가능하다. (객체를 가지고 접근할 수 있긴 하지만, 권장하지 않는다. 객체를 통해 접근하는 것 보다 direct로 클래스로 접근하는 것이 더 효율적)
 
 

필드선언 

public class Student {
    //인스턴스 필드(Instance Field) 선언
    
    private int num;//학번
    //계산을 목적으로 하는 것이 아니라면 '문자형'으로 주는 것이 맞지만, 검색 속도를 위해 '숫자형'으로 주기도 한다.
    private String name; //이름
    private int kor, eng, tot; //국어, 영어, 총점


    //정적 필드(Static Field) 선언
    
    private static int total; //기본값을 초기값으로 사용할 경우 값 초기값 저장 생략 가능
    //기본값이 아닌 초기값을 저장하고 싶으면 직접 초기값을 저장하면 된다.
}

 
 

생성자 선언

//생성자(Constructor) : 객체를 생성하면서 인스턴스 필드에 원하는 초기값을 저장하기 위해 작성
public Student() {
    // TODO Auto-generated constructor stub
}


//매개변수 있는 생성자 선언
public Student(int num, String name, int kor, int eng) {
    super();
    this.num = num;
    this.name = name;
    this.kor = kor;
    this.eng = eng;

    //총점을 계산하여 필드에 저장하는 명령 작성 가능
    //tot = kor + eng;

    //명령 작성 대신, 메소드 호출 가능
    // => 코드의 중복성을 최소화하여 프로그램의 생산성 및 유지보수의 효율성 증가
    //따라서, 똑같은 코드일 경우 메소드를 호출 하는 것을 권장
    calcTot();
}

 

메소드 선언

 
• 인스턴스 메소드(Instance Method) : this 키워드가 제공되는 메소드
: this 키워드를 이용하여 인스턴스 필드 및 메소드 접근이 가능하다. 
→ 클래스를 사용하여 정적 필드 및 메소드 접근 가능  (클래스는 생략 가능)

//Getter 메소드와 Setter 메소드 생성
public int getNum() {
    return num;
}

public void setNum(int num) {
    this.num = num;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getKor() {
    return kor;
}

public void setKor(int kor) {
    this.kor = kor;
    calcTot(); //점수 변경된 후, 계산이 새롭게 잘 될 수 있도록 메소드 호출
}

public int getEng() {
    return eng;
}

public void setEng(int eng) {
    this.eng = eng;
    calcTot(); //점수 변경된 후, 계산이 새롭게 잘 될 수 있도록 메소드 호출
}

public int getTot() {
    return tot;
}

public void setTot(int tot) {
    this.tot = tot;
}

//은닉화 선언된 메소드 - 클래스 내부에서만 호출하여 사용하는 메소드
    // => 단순히 코드의 중복성을 최소화 하기 위한 기능을 제공하는 메소드
    //(외부에서 쓸 필요 없기 때문에 private로 만들어 준 것임)
    private void calcTot() { //총점을 계산하는 메소드
        tot = kor + eng;
    }

    //원래 VO클래스에서 만들지 않지만, 필드값 확인하기 위한 목적으로 생성 (이후 상속에서 다른 방법 배울 예정)
    public void display() {
        System.out.print("[" + name + "]님의 성적 >> ");
        System.out.println("국어 = " + kor + ", 영어 = " + eng + ", 총점 = " + tot);
    }

 
• 정적 메소드(Static Method) : this 키워드를 제공하지 않는 메소드
: this 키워드가 없으므로 인스턴스 필드 및 메소드 접근이 불가능하다.
→ 클래스를 사용하여 정적 필드 및 메소드 접근 가능  (클래스는 생략 가능)

 

public static int getTotal() {
    return total; //return Class.total 과 같지만 Class 생략 가능
}

public static void setTotal(int total) {
    Student.total = total;
}

 

실행 클래스 생성

 
1. 객체배열 생성해 메소드 호출하는 방식

public class StudentApp {
    public static void main(String[] args) {
                //객체를 저장할 수 있는 참조요소가 5개인 배열 생성
                //배열의 참조요소에는 기본적으로 [null]을 초기값으로 저장
                Student[] students = new Student[5];

                //객체를 생성하여 배열의 참조요소에 객체의 메모리 주소 저장 - 객체 배열
                students [0] = new Student(1000, "홍길동", 90, 90);
                students [1] = new Student(2000, "임꺽정", 94, 98);
                students [2] = new Student(3000, "전우치", 91, 80);
                students [3] = new Student(4000, "일지매", 76, 82);
                students [4] = new Student(5000, "장길산", 84, 86); 
        
        
              //반복문을 사용하여 배열의 참조요소에 저장된 객체의 메소드를 일괄적으로 호출하여 처리
              for(int i = 0; i < students.length; i++) {
		      students[i].display();
		      }
    }
}

 
 
만약 참조변수(배열의 참조요소)에 [null]이 저장된 상태에서 참조변수로 객체의 메소드를 호출할 경우, NullPointerException이 발생한다. - 예외가 발생된 지점에서 프로그램 종료

students [0] = new Student(1000, "홍길동", 90, 90);
students [1] = new Student(2000, "임꺽정", 94, 98);
students [2] = new Student(3000, "전우치", 91, 80);
students [3] = new Student(4000, "일지매", 76, 82);
//students [4] = new Student(5000, "장길산", 84, 86);  => null 저장

 
NullPointerException 방지하는 법

for(int i = 0; i < students.length; i++) {
   //참조변수에 [null]이 저장되어 있지 않은 경우 메소드 호출
   // => NullPointException을 방지할 수 있는 선택문
   if(students[i] != null) {
      students[i].display();
   }
}

 
 
2. 참조요소에 null이 아니라, 객체를 직접 만들어 저장하는 방법 

public class StudentApp {
    public static void main(String[] args) {
        Student[] students = {new Student(1000, "홍길동", 90, 90)
            , new Student(2000, "임꺽정", 94, 98), new Student(3000, "전우치", 91, 80)
            , new Student(4000, "일지매", 76, 82), new Student(5000, "장길산", 84, 86) };
 
 
//배열의 참조요소에 저장된 객체의 메모리 주소를 차례대로 제공받아 변수에 저장하여 처리하는 향상된 for 구문을 사용하여 일괄 처리
        for (Student student : students) {
            student.display();
            /*
            [홍길동]님의 성적 >> 국어 = 90, 영어 = 90, 총점 = 180
            [임꺽정]님의 성적 >> 국어 = 94, 영어 = 98, 총점 = 192
            [전우치]님의 성적 >> 국어 = 91, 영어 = 80, 총점 = 171
            [일지매]님의 성적 >> 국어 = 76, 영어 = 82, 총점 = 158
            [장길산]님의 성적 >> 국어 = 84, 영어 = 86, 총점 = 170
            */

           //정적 필드가 public 접근 제한자로 설정된 경우 클래스를 이용하여 접근해 사용이 가능하다
           //=> 객체로 접근 가능하지만 경고 발생
           //Student.total += student.getTot(); //학생 총점을 반환받아 총합계 변수에 누적하여 저장

           //정적 필드가 private 접근 제한자로 설정된 경우 메소드를 이용하여 접근 가능
           //=> 정적 메소드는 객체가 아닌 클래스로 접근하여 호출 가능
           Student.setTotal(Student.getTotal()+student.getTot());
           
           //모든 학생들의 점수들의 합계를 계산하여 출력
		   //System.out.println("총합계 = " + Student.total);    

		   //정적 필드인 경우
           System.out.println("총합계 = " + Student.getTotal()); //총합계 = 871
    
     }
  }
}

 
 
 

Runtime Class


• Runtime 클래스 : Java 프로그램에서 운영체제(Operation System) 관련 정보를 제공하는 기능의 메소드가 선언된 클래스이다. 생성자가 은닉화 선언되어 있는 new 연산자로 객체 생성이 불가능하다.
Runtime 클래스는 프로그램에 객체를 하나만 제공하기 위한 클래스이다. → 싱글톤 클래스 (객체를 여러개 생성할 수 없다)                                       
• Runtime.getRuntime() : Runtime 객체를 반환하는 메소드
=> 정적 메소드이므로 클래스를 이용하여 호출 가능 / getRuntime() 메소드를 여러번 호출해도 같은 객체를 반환
 
• Runtime.totalMemory() : JVM이 사용 가능한 전체 메모리의 크기를 반환하는 메소드
• Runtime.freeMemory() : JVM이 사용 가능한 여유 메모리의 크기를 반환하는 메소드
• Runtime.gc() : 메모리를 정리하는 프로그램(Garbage Collector)을 실행하는 메소드
• Runtime.exec(String command) : 운영체제에 명령을 전달하여 실행하는 메소드
 

public class RuntimeApp {
    public static void main(String[] args) throws IOException {
   
        //Runtime runtime = new Runtime();  (불가능)
        
        Runtime runtime1 = Runtime.getRuntime(); //Runtime 객체를 반환
        Runtime runtime2 = Runtime.getRuntime();

        System.out.println("runtime1 = " + runtime1); //runtime1 = java.lang.Runtime@6a5fc7f7
        System.out.println("runtime2 = " + runtime2); //runtime2 = java.lang.Runtime@6a5fc7f7
		
        =======================================================================================
        
        System.out.print("메모리를 정리하기 전 JVM 사용 메모리의 크기 >> ");
        
        System.out.println((runtime1.totalMemory()-runtime1.freeMemory()) + "Byte");

        runtime1.gc(); //System.gc(); >> 정적 메소드 (클래스로 호출 가능해서 사용하기 더 좋음)

        System.out.println("메모리를 정리한 후 JVM 사용 메모리의 크기 >> ");
        System.out.println((runtime1.totalMemory()-runtime1.freeMemory()) + "Byte");
        
        runtime2.exec("calc.exe"); //계산기출력
        System.out.println("================================================================");
    }
}

 
 

싱글톤 클래스(Singleton Class)


• 싱글톤 클래스(Singleton Class) : 싱글톤 디자인 패턴을 적용하여 작성된 클래스
프로그램에 객체를 하나만 제공하기 위한 목적의 클래스를 작성하기 위해 사용
▶  프로그램에 불필요한 객체가 여러개 생성되는 것을 방지하기 위한 디자인 패턴
 


싱글톤 클래스 생성하기

 
1. 클래스의 객체(메모리 주소)를 저장하기 위한 필드 선언

static 제한자를 사용하여 정적 필드 선언 >> 객체의 갯수에 상관없이 필드는 1개만 만들어 진다.

* 시스템 필드 : 클래스 내부에서만 사용하기 위한 필드
 Getter 메소드와 Setter 메소드를 선언하지 않는다.
일반적인 필드와 구분하기 위해 필드명을 _로 시작되도록 작성하는 것을 권장
 

public class Singleton {
	//정적 필드 선언 
    private static Singleton _instance;
}

 
2. 생성자를 은닉화 선언 - 클래스 외부에서 생성자에 접근하지 못하도록 설정
 객체 생성 불가능

//생성자를 은닉화 선언 
public class Singleton {
    private Singleton() {
        // TODO Auto-generated constructor stub
        }
}

 
3. 정적 영역 생성
 
• 정적 영역(Static Block) : 클래스를 읽어 메모리(MethodArea)에 저장된 후 자동으로 실행될 명령을 작성하기 위한 영역
 프로그램에서 한번만 실행되는 명령이다.
객체가 만들어지기 전에 실행되기 때문에, 객체 메소드와 객체 필드를 사용할 수 없다. 따라서,정적 필드 또는 정적 메소드만 사용 가능하다.

public class Singleton {
    static {
        //클래스로 객체를 생성하여 시스템 필드에 저장
        // => 프로그램에서 객체를 하나만 생성하여 필드에 저장
        _instance = new Singleton(); //생성자로 객체 생성  (클래스 내부이기 때문에 생성자와 객체 생성 가능)
    }
}

 
4.시스템 필드에 저장된 객체를 반환하는 메소드 생성

public class Singleton {
    public static Singleton getInstance() {
        return _instance;
    }
}

 
5. 인스턴스 메소드 

public class Singleton {
    public void display() {
            System.out.println("Singleton 클래스의 display() 메소드 호출");
        }
}

 

싱글톤 클래스 실행하기

싱글톤 클래스는 생성자가 은닉화 선언되어 있어 new 연산자로 생성자를 호출하여 객체 생성이 불가하다. 
대신, 객체를 반환하는 정적 메소드를 호출하여 참조변수에 반환받은 객체를 저장하여 사용할 수 있다.

public class SingletonApp {
    public static void main(String[] args) {
         Singleton singleton1 = Singleton.getInstance();
                Singleton singleton2 = Singleton.getInstance();

                System.out.println("singleton1 = " + singleton1);
                System.out.println("singleton2 = " + singleton2);

//singleton1 = oop.Singleton@75b84c92
//singleton2 = oop.Singleton@75b84c92
//=> 참조변수에 저장된 메모리 주소가 같다 (단 하나의 객체만 반환받아 사용하기 때문에)

                singleton1.display();
                singleton2.display();
//Singleton 클래스의 display() 메소드 호출
//Singleton 클래스의 display() 메소드 호출
//=> 얻을 수 있는 결과가 같다
    }
}

 
싱글톤 클래스는 참조변수에 반환받은 객체를 저장하여 메소드를 호출하지 않고 반환받은 객체로 직접 메소드를 호출하여 사용하는 것을 권장한다. 이유:  불필요한 변수를 만들지 않아도 되어서 (식별자는 많을 수록 좋지 않다)
 

public class SingletonApp {
	public static void main(String[] args) {
        Singleton.getInstance().display();
        Singleton.getInstance().display();
        
        //Singleton 클래스의 display() 메소드 호출
	//Singleton 클래스의 display() 메소드 호출
    }
}