Java 3일차

진창호·2023년 1월 18일

Java

목록 보기
3/9

Java는 상속을 지원하는 객체지향 언어이다.

프로그래밍에서 상속의 개념은 아래와 같다.

기존 클래스의 멤버를 물려 받아 자식 클래스에서 재사용하는 것이다.
코드를 재사용하여 유지보수가 쉽고 중복이 적다는 장점이 있다.
is a 관계로 정의된다.

Java에서 상속을 사용하는 방법은 아래와 같다.

extends 키워드로 상속을 사용한다.
단, 부모의 생성자와 초기화 블록은 상속하지 않는다.

Java에서 상속의 특징은 아래와 같다.

  1. 모든 클래스는 Object 클래스를 상속받는다.
  2. 단일 상속만 지원한다.

※ 단일 상속은 다양한 기능을 지원하지 못한다.
따라서 이를 극복하기 위해 Java는 인터페이스와 has a 관계를 지원한다.
has a 관계란 객체가 객체 안에 객체를 생성하여 해당 객체의 멤버를 사용하는 관계이다.


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

조상 클래스에 정의된 메서드를 자식 클래스에서 적합하게 수정하는 걸 메서드 오버라이딩이라 한다.

메서드 오버라이딩의 조건은 아래와 같다.

  1. 메서드 이름이 같아야 한다.
  2. 매개 변수의 개수, 타입, 순서가 같아야 한다.
  3. 리턴 타입이 같아야 한다.
  4. 접근 제한자는 부모보다 범위가 넓거나 같아야 한다.
  5. 조상보다 더 큰 예외를 던질 수 없다.

Java는 Annotation을 지원한다.

컴파일러에게 알려주는 주석을 Annotation이라 한다.

Annotation 예시는 아래와 같다.

  1. @Deprecated : 컴파일러에게 해당 메서드가 오래되서 향후 없어질 기능임을 걸 알려준다.
  2. @Override : 컴파일러에게 해당 메서드가 오버라이드한 메서드임을 알려준다.
    이때 해당 메서드가 조상 클래스에 없을 시 오류가 발생한다.
  3. @SuppressWarnings : 컴파일러에게 Warning은 신경 쓰지 말라고 알려준다.

Java의 모든 클래스는 Object 클래스를 상속한다.

따라서 Object 클래스에는 꼭 필요한 메서드들이 정의되어 있다.

  1. toString() : 객체의 정보를 출력하는 메서드
  2. equals() : 두 객체의 내용을 비교하는 메서드

위 함수들은 자식 객체에서 적절하게 오버라이딩해서 사용한다.

toString()의 예시는 아래와 같다.

class Sam {
	int num;
	String name;
	
	@Override
	public String toString() {
		return "Sam [num=" + num + ", name=" + name + ", toString()=" + super.toString() + "]";
	}
}
public class RTest {
	public RTest() {
		Sam a = new Sam();
		a.num = 1;
		a.name = "dddd";
		System.out.println(a.toString());
	}
	
	public static void main(String[] args) {
		new RTest();
	
	}
}

toString()을 재정의하여 a 객체의 정보를 출력할 수 있다.

equals()의 예시는 아래와 같다.

class PData {
	int a;
	PData(int a) {
		this.a = a;
	}
	
	@Override
	public boolean equals(Object obj) {
		return a == ((PData)obj).a;
	}
}
public class QTest {
	public QTest() {
		String s1 = new String("samsung");
		String s2 = new String("samsung");
		
		System.out.println(s1 == s2);
		System.out.println(s1.equals(s2));
		
		String s3 = "samsung";
		String s4 = "samsung";
		
		System.out.println(s3 == s4);
		System.out.println(s3.equals(s4));
		
		PData p1 = new PData(1);
		PData p2 = new PData(1);
		
		System.out.println(p1 == p2);
		System.out.println(p1.equals(p2));
	}

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

출력 결과는 아래와 같다.

false
true
true
true
false
true

equals()를 재정의하여 p1과 p2 객체의 내용을 비교할 수 있다.

※ == 은 두 객체의 주소를 비교한다.


Java는 super 키워드를 지원한다.

super 키워드는 자식 객체에서 조상 객체의 멤버를 접근하거나 적절한 조상 생성자를 호출하는 데 사용한다.

첫번째로 자식 객체에서 조상 객체의 멤버를 접근하는 경우이다.

public class Person
...


void jump() {
		System.out.println("폴짝");
	}
     
...
public class SpiderMan extends Person
...

@Override
	void jump() {
		if (isSpider) {
			spider.jump();
		} else {
			super.jump();
		}
	} 
...

else 문이 수행되면 super 키워드를 통해 Person의 jump() 를 수행할 수 있다.

두번째로 자식 객체에서 조상 객체의 생성자를 호출하는 경우이다.

public class Person
...
public Person() {
		
	}
	
	public Person(String name) {
		this.name = name;
	}
...
public class SpiderMan extends Person
...
public SpiderMan(String name, boolean isSpider) {
		super(name);
		this.isSpider = isSpider;
		spider = new Spider();
	}
...

SpiderMan(String name, boolean isSpider) 생성자가 실행되면 super 키워드를 통해
Person(String name) 생성자가 실행된다.

여기서 생각할 점이 몇 가지 있다.

첫번째로 변수의 범위이다. 아래의 코드를 살펴보자.

class Parent {
	String x = "parent";
}
class Child extends Parent {
	String x = "child";

	void method() {
		String x = "method";
		System.out.print("x : " + x);
		System.out.print(" this.x : " + this.x);
		System.out.print(" super.x : " + super.x);
	}
}

출력 결과는 x : method this.x : child super.x : parent 이다.

...
void method() {
// String x = "method";
...

이 경우 출력 결과는 x : child this.x : child, super.x : parent 이다.

...
class Child extends Parent {
	// String x = "child";
...

이 경우 출력 결과는 x : method this.x : parent, super.x : parent 이다.

...
class Child extends Parent {
	// String x = "child";

	void method() {
		// String x = "method";
...

이 경우 출력 결과는 x : parent this.x : parent, super.x : parent 이다.

두번째로 컴파일러의 처리이다.
super로 생성자를 부를 시 주의해야 할 점은 아래와 같다.

  1. super()는 생성자 코드의 가장 위어야 한다.
    따라서 this()와 super()는 같은 생성자 내부에서 동시에 사용할 수 없다.
  2. 만약 생성자 안에 this(), super() 둘 다 없으면 컴파일러가 super()를 자동으로 삽입한다.

※ static 메서드는 오버라이딩을 지원하지 않는다.


Java는 Package 라는 저장 단위로 클래스를 관리한다.

Package의 특징은 아래와 같다.

  1. . 별로 계층적으로 관리된다. ((ex) a.b.c)
  2. 코드 중 최상위에 있어야 하며 클래스 별로 단 하나만 존재한다.
    그래서 클래스의 Full name은 Package name + Class name 으로 말한다.
  3. Package가 없으면 Default Package로 들어가는데, 권장하지 않는다.

Package의 작명 규칙은 아래와 같다.

  1. for, if 같은 예약 키워드 & java, vs 같은 몇몇 확장자는 이름으로 사용하지 못한다.
  2. com.회사명.계열명.부서명 ... 같은 형식을 권장한다.

Java는 import 키워드로 다른 Package의 클래스를 가져온다.

import의 선언 방법은 아래와 같다.

  1. import 패키지명.클래스명;
  2. import 패키지명.*; (하위 패키지는 import 하지 않는다.)

Package 사용 시 알아야 할 점이 있다. 아래 예시를 살펴보자.

package gumi.util;

public class Sam {
	int num;
	void pr() {
		
	}
}
package gumi.util.class4;

import java.awt.*; // java.awt.Button 권장
import java.util.*; // java.util.List 권장

import gumi.util.Sam;

public class ImportTest {
	
	public ImportTest() {
		Sam s = new Sam();
		// List list; 오류 발생
		Button btn;		
		
		java.awt.List btn2 = new java.awt.List();
        
        System.out.println(Math.random());
		System.out.println(Math.PI);
	}
}
  1. 왠만하면 *을 권장하지 않는다. List 처럼 충돌이 일어날 수 있다.
  2. import를 하지 않고도 전체 경로를 넣어 객체를 생성할 수 있다.
    단, new 뒤에도 전체 경로를 넣어 객체를 생성해야 한다.
  3. import java.lang.* 은 컴파일러가 자동으로 입력한다.

import static 사용시 static 클래스의 멤버에 접근할 수 있다.


Java는 final로 상수를 만들 수 있다.

final은 제한자 중 하나이다. 제한자의 예는 아래와 같다.

  1. public, private 같은 접근 제한자
  2. static 같은 클래스 멤버를 만들 수 있는 제한자
  3. abstract 같은 추상 멤버를 만들 수 있는 제한자
  4. final 같은 요소를 더 이상 수정할 수 없게 하는 제한자

Java에서 바뀔 수 없는 값인 상수를 만들 때 final을 사용한다. 사용 예시는 아래와 같다.

  1. final class : 더 이상 확장할 수 없음 / 상속 금지
  2. final method : 더 이상 재정의할 수 없음 / 오버라이딩 금지
  3. final variable : 더 이상 값을 바꿀 수 없음 / 전체 대문자, 두 단어 사이엔 _ 작명 권장
    / 선언할 때 값을 바로 할당 / 멤버 변수
  4. blank final : 선언 후 단 한번만 값을 할당할 수 있음
    / 매개 변수가 변하지 않도록 보장하기 위해 자주 사용 / 지역 변수
  5. static final : debug 시 사용

final variable과 blank final의 차이는 아래와 같다.

public class FinalTest {

	final int a = 99; // final variable
	
	public FinalTest() {
		final int b; // blank final
		b = 99; 
	}
	
	public static void main(String[] args) {
		new FinalTest();
	}
}

Java는 접근 제한자를 통해 외부 접근을 제어할 수 있다.

접근 제한자의 종류는 아래와 같다.

  1. public : 같은 클래스, 같은 패키지, 다른 클래스의 자손 클래스, 전체 접근 가능
  2. protected : 같은 클래스, 같은 패키지, 다른 클래스의 자손 클래스 접근 가능
  3. default (기본) : 같은 클래스, 같은 패키지 접근 가능
  4. private : 같은 클래스 접근 가능

메서드 오버라이딩 시 부모 클래스와 접근 제한자 범위가 같거나 넓어야만 가능하다.


Java는 접근 제한자로 데이터 은닉과 보호가 가능하다.

아래 예시를 살펴보자.

class Person {
	private int age;
	private String name;
	
	public int getAge () {
		return age;
	}
	
	public void setAge (int num) {
		// 멤버 변수에 접근할 수 있는 조건
		
		this.age = num;
	}
	
	public String getName () {
		return name;
	}
	
	public void setName (String input) {
		// 멤버 변수에 접근할 수 있는 조건
		
		this.name = input;
	}
}

public class EncTest {

	public EncTest() {
		Person p = new Person();
		int age;
		
		p.setAge(12);
		age = p.getAge();
		
		System.out.println(age);
	}

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

멤버 변수는 private, 멤버 메소드는 public 으로 설정함으로써
멤버 변수에 접근할 수 있는 조건을 설정할 수 있다.


Java는 객체 생성을 제한하기 위해 Singleton 디자인 패턴을 사용한다.

여러 개의 객체가 필요 없는 객체를 stateless한 객체라고 한다.
객체를 하나 만들어놓고, 이를 사용만 할 수 있도록 할 때 Singleton 디자인 패턴을 활용한다.

Singleton 디자인 패턴의 조건은 아래와 같다.

  1. 생성자의 접근 제한자가 private : 외부에서 생성자에 접근할 수 없다.
  2. 객체를 저장한 멤버 변수의 접근 제한자가 private : 외부에서 멤버 변수를 바꿀 수 없다.
  3. 객체를 저장한 멤버 변수를 외부에 줄 getter 메서드가 public :
    외부에서 객체를 저장한 멤버 변수를 가져올 수 있다.
  4. 객체를 저장한 멤버 변수와 getter 메서드가 static :
    외부에서 객체를 생성하지 않고 getter 메서드로 객체를 저장한 멤버 변수를 가져올 수 있다.

Singleton 디자인 패턴 구현은 아래와 같다.

package gumi.util;

public class Manager {
	private static Manager manager;
	
	private Manager() {
	}
	
	public static Manager getInstance() {
		// private에 바로 선언할 수도 있지만 
        // static 메서드가 불렸을 때 불필요하게 manager에 instance를 생성하는 과정을 막자
		if (manager == null) {
			manager = new Manager();
		}
		
		return manager;
	}
	
	public static void pr() {
		
	}
	
	public void add() {
		
	}
	
	public void select() {
		
	}
}
package gumi.util;

public class SingleTest {

	public static void main(String[] args) {
		Manager.pr();
		
		Manager manager1 = Manager.getInstance();
		manager1.add();
		manager1.select();
		
		Manager manager2 = Manager.getInstance();
		Manager manager3 = Manager.getInstance();
		
		System.out.println(manager2 == manager3);
	}
}

출력 결과는 true 이다.

※ import 할 수 있는 java 내부 class 중 메서드가 getInstance() 라고
무조건 Singleton 패턴을 보장하진 않는다.

profile
백엔드 개발자

0개의 댓글