[Java] 객체지향

2024. 1. 21. 22:13공부 중/Java

1. 객체

 

  • 객체 : 주체가 아닌 것, 주체가 활용하는 것.

 

  • 장점
    • 신뢰성이 높은 프로그래밍이 가능하다.
    • 추가/수정/삭제가 용이하다.
    • 재사용성이 높다.
  • 단점
    • 실행속도가 느린 점
    • 메모리 사용량이 높음.

 


2. Class vs Object

 

현실의 객체가 갖는 속성과 기능은 추상화(abstraction) 되어 클래스에 정의된다.

 

클래스는 구체화되어 프로그램의 객체(instance, object)가 된다.

 

  • 클래스
    • 객체를 정의해 놓은 것, 객체의 설계도.
    • 데이터 타입
  • 객체
    • 클래스를 데이터 타입으로 메모리에 생성되어 실제로 동작하는 것
    • 메모리에 생성된 데이터

 


3. 객체 생성과 메모리

 

public class Person {
    // 객체의 속성, 데이터
    String name;
    int age;
    boolean isHungry;

    public void eat() {
        isHungry = false;
    }

    public void work() {
        isHungry = true;
    }
}
public class PersonTest {

    public static void main(String[] args) {

        int a = 10;
        Person p1 = new Person();
        p1.name = "홍길동";
        p1.isHungry = true;
        System.out.println(p1.name + " : " + p1.isHungry);
        p1.eat();
        System.out.println(p1.name + " : " + p1.isHungry);

        Person p2 = new Person();
        p2.name = "임꺽정";
        System.out.println(p2.name + " : " + p2.age + " : " + p2.isHungry);

    }

}

 


가. JVM의 메모리 구조

 

JVM의 메모리 구조는 크게 3가지로 나누어짐.

 

  • meta-space
    • 클래스 정보 처리
    • 타입 정보
    • Feild 정보
    • Method 정보
  • stack
    • 메서드들의 실행 공간
    • thread 별로 별도 관리
    • 메서드 호출 시마다 메서드 프레임 적층
    • 메서드 프레임에 로컬변수도 쌓이는 구조
  • heap
    • 객체를 저장하기 위한 영역
    • thread에 의해 공유
    • 객체가 생성되고 가비지 컬렉터?에 의해 정리됨.
    • 상수 풀

 

PersonTest.main() 메서드부터 실행.

 

metaspace에서 정보를 가져와 stack 영역에 main 메서드와 필요한 로컬 변수 등을 적재.

 

생성된 객체 p1은 heap 영역에 저장됨.

 

p1.eat() 호출 시 stack에 추가. heap 영역에 접근하여 객체의 데이터를 수정.

 

p1.eat() 종료 후 스택에서 제거.

 

p2 생성. heap 영역에 추가.

 


4. 변수 종류

 

선언 위치에 따른 종류

 

변수종류 선언 위치 종류
클래스 멤버 변수 클래스 영역 멤버 변수
인스턴스 멤버 변수 클래스 영역 멤버 변수
지역 변수 함수 내부 지역 변수
파라미터 변수 함수 선언부 지역 변수

 

  • 클래스 멤버 변수
    • 위치 : 클래스 영역 { } 안에 선언되며 static 키워드 사용
    • 생성 : 클래스 로더에 의해 클래스가 로딩될 때 heap에 클래스 별로 생성.
      • 개별 객체의 생성과 무관하며 모든 객체가 공유하게 됨(공유 변수)
    • 접근 : 클래스 이름으로 접근
    • 초기화 : 타입 별로 기본값으로 초기화
    • 소멸 : 클래스가 언로드 될 때 Gargage Collector가 소멸

 

  • 인스턴스 멤버 변수
    • 위치 : 클래스 영역 { } 안에 선언됨.
    • 생성 : 객체가 만들어질 때 heap에 객체 별로 생성됨.
    • 초기화 : 타입 별로 기본값으로 초기화됨.
    • 접근 : 객체 생성 후 객체 이름(소속)으로 접근
      • 객체를 만들 때마다 객체 별로 생성 → 객체마다 고유한 상태(변수 값) 유지.
    • 소멸 : Gargage Collector에 의해 객체가 없어질 때 소멸

 

  • 지역 변수 & 파라미터 변수
    • 위치 : 클래스 영역 { } 이외 모든 중괄호 안에 선언되는 변수들
    • 생성 : 선언된 라인이 실행될 때
    • 초기화 : 명시적 초기화 필요
    • 접근 : 외부에서는 접근이 불가능하므로 소속 불필요. 내부에서는 이름으로 접근
    • 소멸 : 선언된 영역인 { }을 벗어날 때

 


5. 메서드

 

  • 선언된 파라미터의 개수는 반드시 동일. (당연한 거 아니냐고요? JavaScript은 아니에요.)
  • 파라미터 전달 시 묵시적 형변환 적용.
  • 리턴 타입이 void인 경우 return 생략 가능.
  • static member는 클래스이름.메서드이름()으로 호출.

 


가. Variable arguments

 

메서드 선언 시 동일 타입의 인자가 몇 개 들어올지 예상할 수 없을 때.

  1. 배열 타입 선언
  2. ...을 이용해 파라미터 선언

 

... 을 사용하면 넘겨준 값의 개수에 따라 자동으로 배열 생성 및 초기화.

public static void main(String[] args){
    VariableTest vt = new VariableTest();
    vt.addAll(1,2,3);
    vt.addAll(1,2,3,4,5);
    vt.allAll(1,2);
}

public void addAll(int... params){
    int sum = 0;
    for(int i : params){
        sum+=i;
    }
    System.out.println(sum);
}

줄여서 Varargs라고 하기도 한다.

 


나. call by value

 

java는 엄연히 call by value만 존재한다.

 

메서드 호출 시 파라미터로 입력된 값을 복사해서 전달한다.

 

public class CallByTest{
    int memberVar = 10;
    static void change1(int var){
        var += 10;
        System.out.printf("change1 : %d%n", var);
    }
    static void change2(CallByTest cbtl){
        cbtl.memberVar += 100;
        System.out.printf("change2 : %d%n", cbtl.memberVar);
    }

    public static void main(String[] args){
        CallByTest cbt = new CallByTest();
        cbt.memberVar = 5;
        System.out.printf("change1 호출 전 memberVar: %d%n", cbt.memberVar);
        change1(cbt.memberVar);
        System.out.printf("change1 호출 후 memberVar: %d%n", cbt.memberVar);
        change2(cbt);
        System.out.printf("change2 호출 후 memberVar: %d%n", cbt.memberVar);
    }
}
// 5
// 5
// 105

 

  • change2(cbt);는 변수 cbt에 저장된 값(객체의 주소)을 복사해서 전달한다.

 

결과적으로는 primitive type도 referenece type도 값이 복사된다.

 

단, reference type의 경우 주소값이 저장되어 있다.

 

이에 객체 자체를 복사하는 것은 아니고 주소값을 복사했기 때문에 원본 객체에 접근할 수 있다.

 


다. 오버로딩

public class OverloadingExample {
    public int add(int a, int b) {
        return a + b;
    }

    public int add(int a, int b, int c) {
        return a + b + c;
    }

    public double add(double a, double b) {
        return a + b;
    }

    public static void main(String[] args) {
        OverloadingExample example = new OverloadingExample();
        int sum1 = example.add(5, 10);
        int sum2 = example.add(5, 10, 15);
        double sum3 = example.add(2.5, 3.7);
        System.out.println("Sum 1: " + sum1);
        System.out.println("Sum 2: " + sum2);
        System.out.println("Sum 3: " + sum3);
    }
}

동일한 기능을 수행하는 메서드의 추가 작성.

 

  • 리턴 타입만 다른 경우는 안됨. 리턴 타입이 다른 것은 의미가 없음.

 

최근에는 타입이 다르지 않은 이상 ...(Varagrs)을 많이 사용함.

 


6. 생성자

 

new 키워드와 함께 호출하는 것.

 

일반 멤버 변수의 초기화나 객체 생성 시 실행돼야 하는 작업들.

 

  • 이름은 클래스 이름과 같아야 함.
  • 리턴 타입은 없다.
    (void 아님)
    (
    리턴 타입을 넣는 순간 일반 메서드로 취급. 이때 컴파일 에러도 없으니 특히 조심.)

 

접근제한자 클래스이름(){
    //작업
}

특별히 생성자를 정의하지 않으면 컴파일러가 default 생성자를 추가함.

 

반대로 말하면 생성자를 하나라도 추가하면 컴파일러는 default 생성자를 추가해주지 않음.

 

//default constructor
클래스의_접근제한자 클래스이름(){}

 


가. this

 

  • 용도
    • this : 객체 (인스턴스 변수)
    • this. : 객체 멤버
    • this() : 동일 클래스의 생성자
public class CTest {
    int a, b, c;
    public CTest() {

    }
    public CTest(int n) {
        a = n;
    }
    public CTest(int n, int x) {
        a = n;
        b = x;
    }
    public CTest(int n, int x, int y) {
        a = n;
        b = x;
        c = y;
    }
}

이것보다는 아래 방법을 추천한다.

public class CTest {
    int a, b, c;
    public CTest() {

    }
    public CTest(int a) {
        this.a = a;
    }
    public CTest(int a, int b) {
        this.a = a;
        this.b = b;
    }
    public CTest(int a, int b, int c) {
        this.a = c;
        this.b = b;
        this.c = c;
    }
}

또 이것보다는 아래 방법을 추천한다.

public class CTest {
    int a, b, c;
    public CTest() {

    }
    public CTest(int a) {
        this.a = a;
    }
    public CTest(int a, int b) {
        this(a);
        this.b = b;
    }
    public CTest(int a, int b, int c) {
        this(a, b);
        this.c = c;
    }
}
  • this() : 같은 클래스 내 다른 생성자를 뜻함. this()보다 앞에 그 어떤 명령문이 올 수 없다.

 


나. 초기화 블록

 

생성자처럼 멤버 변수의 초기화를 목적으로 함.

 

이름이 없으므로 별도로 호출할 수 없으며 파라미터를 받을 수도 없음.

 

1) 인스턴스 초기화 블록

  • 인스턴스 멤버 변수의 초기화에 사용
  • 생성자 코드 실행보다 우선해서 실행 (객체 생성 → 인스턴스 초기화 블록 → 생성자 코드)
  • 객체 생성 시 1회 호출 (new 키워드 사용 시)
public class Sam {
    int a;
    int b;

    {
        System.out.println("모든 생성자에서 중복되는 코드");
    }
    Sam(){
        //System.out.println("모든 생성자에서 중복되는 코드");
        a=10;
        b=20;
    }
    Sam(int a){
        //System.out.println("모든 생성자에서 중복되는 코드");
        this.a=a;
        b=20;
    }
    Sam(int a, int b){
        //System.out.println("모든 생성자에서 중복되는 코드");
        this.a=a;
        this.b=b;
    }
}

생성자들 사이에 공통되는 부분을 모아둔다.

 

2) 클래스 초기화 블록

public class Sam {
    int a;
    int b;
    static{
        System.out.println("로딩시 1번만 실행");
    }
    {
        System.out.println("모든 생성자에서 중복되는 코드");
    }
    Sam(){
//        System.out.println("모든 생성자에서 중복되는 코드");
        a=10;
        b=20;
    }
    Sam(int a){
//        System.out.println("모든 생성자에서 중복되는 코드");
        this.a=a;
        b=20;
    }
    Sam(int a, int b){
//        System.out.println("모든 생성자에서 중복되는 코드");
        this.a=a;
        this.b=b;
    }
}
public class SamTest {

    public static void main(String[] args) {

        Sam s;
        s = new Sam(1,2);
        s = new Sam(1,2);
        s = new Sam(1,2);
        s = new Sam(1,2);
    }
}
/*
로딩시 1번만 실행
모든 생성자에서 중복되는 코드
모든 생성자에서 중복되는 코드
모든 생성자에서 중복되는 코드
모든 생성자에서 중복되는 코드
*/
  • 클래스 멤버 변수의 초기화에 사용
  • 클래스 로딩 시 1회 호출 (최초로 클래스 언급 시 1회. 여기선 Sam s; 부분)
  • 단, 컴파일러 단에서 비어있는(null) 레퍼런스 변수는 제거할 수 있으므로 주의할 것.

 


'공부 중 > Java' 카테고리의 다른 글

[Java] 다형성  (0) 2024.01.22
[Java] 상속  (0) 2024.01.21
[Java] 배열  (0) 2024.01.21
[Java] 조건문, 반복문  (0) 2024.01.21
[Java] 자료형과 연산자  (0) 2024.01.21