Java 2일차

진창호·2023년 1월 17일

Java

목록 보기
2/9

Java는 객체지향 프로그래밍 언어이다.

객체지향 프로그래밍 : 주변의 많은 것들을 객체화해서 프로그래밍하는 것

객체지향 프로그래밍의 장점
1. 객체가 검증됐기 때문에 신뢰성 높은 프로그래밍이 가능하다.
2. 추가 / 수정 / 삭제가 용이하다.
3. 재 사용성이 높다.

객체지향 프로그래밍의 특징 (A PIE)
Abstraction (추상화) : 현실의 객체를 추상화해서 클래스를 구성한다.
Polymorphism (다형성) : 하나의 객체를 여러 가지 타입으로 참조할 수 있다.
Inheritance (상속) : 부모 클래스의 자산을 물려받아 자식을 정의함으로 코드의 재사용이 가능하다.
Encapsulation (데이터 은닉과 보호) : 데이터를 외부에 직접 노출시키지 않고 메서드를 이용해 보호할 수 있다.

클래스와 객체
객체를 추상화하여 클래스가 만들어지면 해당 클래스엔 상태, 속성을 보여주는 멤버변수
기능, 행위를 보여주는 멤버메서드가 존재한다.
이를 구체화하면 특이한 속성을 가진 객체가 된다.
즉, 현실의 객체(Object)가 갖는 속성과 기능은 추상화되어 클래스(Class)에 정의되고,
클래스는 구체화되어 프로그램의 객체(Instance)가 된다.

클래스와 객체의 메모리 사용
class area : 클래스 원형과 클래스 변수가 생성된다.
method stack : 메서드 호출 시 프레임이 생성되고, 이 프레임에 로컬변수가 쌓인다.
쓰레드 별로 생성된다.
heap : 객체를 저장하기 위한 영역, 쓰레드에 의해 공유된다.
메모리 사용


Java의 변수는 2가지 기준으로 분류된다.

타입에 따라 기본 타입과 참조 타입으로 나뉜다.
선언 위치에 따라 멤버 변수(클래스, 인스턴스), 지역 변수(지역, 파라미터)로 나뉜다.

멤버 변수에 대해 자세히 알아보자.

  1. 클래스 멤버 변수
    1) 선언 위치 : 클래스 영역에 선언되며 static 키워드를 붙인다.
    2) 생성 : 클래스가 최초로 로드될 때 메모리에 생성된다.
    3) 초기화 : 변수 타입 별로 default 초기화가 이뤄진다.
    4) 접근 : Person 클래스의 p 객체가 있다고 가정할 때, Person.~, p.~ 둘 다 접근 가능하다.
    5) 소멸 : 프로그램 종료 시 소멸된다.
  2. 인스턴스 멤버 변수
    1) 선언 위치 : 클래스 영역에 선언된다.
    2) 생성 : 객체가 만들어질 때 heap 영역에 객체 별로 생성된다.
    3) 초기화 : 변수 타입 별로 default 초기화가 이뤄진다.
    4) 접근 : Person 클래스의 p 객체가 있다고 가정할 때, p.~ 로 접근 가능하다.
    5) 소멸 : Garbage Collector에 의해 객체가 없어질 때 소멸된다.

지역 변수에 대해 자세히 알아보자.

1) 선언 위치 : 클래스 영역 외의 모든 중괄호 안에 선언된다.
2) 생성 : 선언된 라인이 실행될 때 생성된다.
3) 초기화 : 사용하기 전 명시적 초기화가 필요하다.
4) 접근 : 외부에서는 접근이 불가하다.
5) 소멸 : 선언 영역을 벗어날 때 소멸된다.


Java는 함수가 없고 메서드만 존재한다.

메서드의 장점은 아래와 같다.

코드의 중복을 방지하고, 코드의 양을 줄이고 유지 보수가 용이해진다.

자바에선 가변 파라미터 전달이 가능하다.

public void variableArgs (int ... params) {}
public void variableArgs (int n, int ... params) {}
public void variableArgs (int ... params, int n) {}

위 코드 중 1, 2번 코드는 선언 가능하지만, 3번 코드는 선언이 불가능하다.

메서드를 접근하기 위해 주의해야 하는 점은 아래와 같다.

메서드는 메모리에 있어야 접근 가능하다.
따라서 클래스 메서드는 클래스가 실행되야 사용 가능하고,
인스턴스 메서드는 객체가 생성되야 사용 가능하다.

※ 인스턴스 메서드는 인스턴스 변수와 달리 메모리에 한번만 생성된다. 그리고 쓰레드가 필요 시 공유한다.


Java는 메서드 오버로딩을 지원한다.

메서드 오버로딩이란 동일한 기능을 수행하는 메서드의 추가 작성을 의미한다.
따라서 사용자가 기억해야 할 메서드가 줄고, 중복 코드에 대한 효율적 관리가 가능해진다.

메서드 오버로딩 시 주의할 점은 아래와 같다.

  1. 같은 클래스 안에 있는 메서드여야 한다.
  2. 메서드 이름이 동일해야 한다.
  3. 파라미터의 개수 혹은 순서, 타입이 달라야 한다.
  4. 리턴 타입은 의미없다.

메서드 오버로딩의 예시는 아래와 같다.

static int add(int a, int b) {
		return a + b;
	}
	
	static int add(int a, int b, int c) {
		return a + b + c;
	}
	
	static int add(int a, int b, int c, int d) {
		return a + b + c + d;
	}
	
	static int add(int... args) {
		int sum = 0;
		
		for (int n : args) {
			sum += n;
		}
		
		return sum;
	}
	
	public static void main(String[] args) {
		int res = 0;
		res = add(1, 2);
		System.out.println(res);
	}

main에서 실행되는 add 함수는 add(int... args) 가 아닌 add(a, b) 이다.


Java는 생성자라는 특별한 메서드를 지원한다.

이름은 클래스랑 같지만 리턴 타입이 없는 메서드를 생성자라고 한다.

아래 코드를 살펴보자.

static class Gumi {
		Gumi() {	
		}
        Gumi(int a) {
        }
		void Gumi() {
		}
	}
	
	public static void main(String[] args) {
		Gumi d = new Gumi();
		d.Gumi();
	}
  1. 첫번째 메서드 : 기본 생성자 형태의 생성자이다.
    따로 생성자를 정의하지 않으면 컴파일러가 자동으로 기본 생성자를 생성한다.
  2. 두번째 메서드 : 파라미터를 가진 생성자이다.
    만약 파라미터를 가진 생성자를 정의하면 컴파일러는 자동으로 기본 생성자를 생성하지 않는다.
  3. 세번째 메서드 : 클래스와 이름이 같은 일반 메서드이다. 정상 실행된다.
    따라서 클래스 이름과 메서드 이름이 같다고 실행에 문제가 생기진 않는다.

Java는 this라는 키워드를 지원한다.

this를 사용하는 경우는 아래와 같다.

1) 인스턴스 변수에 값 주기

Java는 지역 변수와 인스턴스 변수의 이름이 같을 시 지역 변수 값을 우선적으로 접근한다.
따라서 인스턴스 변수를 우선적으로 접근하고 싶을 때 this 키워드를 사용한다.

class Person {
		int age;
		int num;
		String name;
		
		Person(int age, int num, String name) {
			this.age = age;
			this.num = num;
			this.name = name;
		}
		
		void printInfo() {
			System.out.println(String.format("age : %d, num : %d, name : %s", age, num, name));
		}
	}
	
	public static void main(String[] args) {
		Person person = new Person(22, 15, "진창호");
		person.printInfo();
	}

static 안에서는 this를 사용할 수 없다.

2) 자신을 다른 함수의 매개변수로 전달하기

class Algo {
	void m1(N n) {
		n.num += 10;
		System.out.println("m1");
	}
}

public class N {
	int num;
	
	public N() {
		Algo algo = new Algo();
		algo.m1(this);
		System.out.println("num : " + this.num);
	}
	
	public static void main(String[] args) {
		new N();
	}
}

3) 생성자 안에서 다른 생성자 호출하기

public class M {
	public M() {
		Person p1 = new Person(25, "samsung", "gumi");
	}
	
	class Person {
		int age;
		String name;
		String location;
		
		public Person(int age, String name, String location) {
			this.age = age;
			this.name = name;
			this.location = location;
		}

		public Person(int age, String name) {
			this(age, name, "gumi");
		}

		public Person(int age) {
			this(age, "guest");
		}
		
		public Person() {
			this(27);
		}
	}

	public static void main(String[] args) {
		new M();
	}
}

생성자 코드 중 this는 가장 위어야 한다.

※ 생성자가 아닌 일반 메서드 오버로딩에서는 똑같은 함수 이름에 매개변수를 넣어서
호출하면 된다.

public class M {
	public static void f1(int n) {
    	System.out.print(n);
    }
    
    public static void f1() {
    	f1(1);
    }

	public static void main(String[] args) {
		f1();
	}
}

Java는 instance, static 블럭을 지원한다.

instance 블럭은 클래스 내부에 {} 를 만들어 안에 명령어를 넣어둔다.
따라서 생성자가 생성될 때 반드시 실행되어야 하는 명령어를 모을 수 있다.

사용 예시는 아래와 같다.

class Data {
	int a, b, c;
	{
		System.out.println("무조건 실행해야 하는 메소드");
	}
	
	public Data() {
	}
	
	public Data(int a) {
		this.a = a;
	}

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

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

public class O {

	public O() {
		Data d1 = new Data();
		System.out.println("end");
	}
	
	public static void main(String[] args) {
		new O();
	}
}

위 코드의 출력 결과는 아래와 같다.

무조건 실행해야 하는 메서드
end

static 블럭은 클래스 내부에 static {} 를 만들어 안에 명령어를 넣어둔다.
따라서 클래스가 최초로 로드될 때 반드시 실행되어야 하는 명령어를 모을 수 있다.

class Data {
	int a, b, c;
	static {
		System.out.println("로딩 시점에 실행됨");
		pr();
	}
	
	static void pr() {
		System.out.println("출력이 됩니다.");
	}
	
	public Data() {
	}
	
	public Data(int a) {
		this.a = a;
	}

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

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

public class O {

	public O() {
		Data d1 = new Data();
        Data d2; // 실제 클래스 로딩 하는 게 아니라고 컴파일러가 판단
		System.out.println("end");
	}
	
	public static void main(String[] args) {
		new O();
	}
}

위 코드의 출력 결과는 아래와 같다.

로딩 시점에 실행됨
출력이 됩니다.
end

Data d2; 에서 출력이 일어나지 않는다.
그 이유는 컴파일러가 실제 클래스를 로딩하는 게 아니라고 판단하기 때문이다.

※ 클래스 외부에 클래스를 만들 때는 static을 안 붙여도 static 블럭을 사용할 수 있다.
하지만 클래스 내부에 클래스를 만들 때는 static을 붙어야 static 블럭을 사용할 수 있다.

profile
백엔드 개발자

0개의 댓글