edu day 18, 19
일반적으로 프로그램을 개발하다보면 많은 클래스파일들을 만들게 된다.
이때 서로 관련 있는 클래스 파일들은 패키지로 묶어서 효율적으로 관리할 수 있다.
- 패키지문은 반드시 한번만 사용 가능하다.
- 클래스 선언보다 먼저 선언되어야 된다.
- 패키지명은 계층구조(서브 패키지)를 가질 수 있다.
- 패키지명이 중복되면 식별이 불가능하기 때문에 안된다. 따라서 유일한 값인 도메인 형식으로 지정하는 것을 권장한다.
- 패키지가 서로 다른 클래스들끼리는 기본적으로 접근이 불가능하다.(import문으로 해결)
- JDK에서 제공해준 API도 패키지로 되어 있다. 대부분의 패키지 이름이 java 또는 javax 로 시작되는데, 사용자 지정 패키지명으로 API 패키지명은 사용이 불가능하다. 따라서 java로 시작되는 패키지명은 사용할 수 없다.
- package을 지정하지 않으면 패키지가 없는 것이 아니고 default 패키지라고 부른다.
- 하나의 어플리케이션에는 동일한 이름의 클래스를 여러 개 지정할 수 없으나 패키지가 다르면 가능하다.
기본적으로 패키지가 서로 다른 클래스 파일들은 접근할 수 없다.
하지만 import 문을 사용하게 되면 패키지가 서로 달라도 접근 가능하다.
import 문은 접근할 클래스파일의 패키지를 알려주는 용도로 사용된다.
- import 문은 여러 번 사용가능하다.
- 클래스 선언보다 먼저 사용해야 되며 패키지 선언보다는 나중에 사용한다.
- 클래스명 대신에 모든 클래스를 의미하는
*을 사용할 수도 있다. 하지만 가독성이 떨어지므로 권장하지는 않는다.- API 중에서 java.lang 패키지는 import 하지 않아도 되는 유일한 패키지이다. java.lang 패키지에는 String, Object 등 일반적으로 가장 많이 사용하는 클래스파일들의 패지키이기 때문에 명시적으로 import 하지 않아도 자동 import가 된다.
<예시>
//Student.java
package com.dto;
public class Student {}
// TestStudent.java
package com.main;
import com.dto.Student; //패키지가 서로 다르게 때문에 import 한다.
public class TestStudent {
public static void main( String [] args ) {
Student stu = new Student();
}
}
자동으로 import 문을 추가하기 위해서는 코드를 모두 입력하고 가장 나중에 단축키 Ctrl + Shift + 영문자 O를 사용한다.
만약 클래스 입력중에 Ctrl + SpaceBar를 선택해도 자동으로 import문을 추가할 수 있다.
소스코드 작성
-> 클래스파일 생성
-> 클래스파일 실행
-> 클래스 로딩 : static 키워드를 사용한 static 변수와 메서드가 메모리에 자동으로 로딩된다.
-> main 메서드 실행 : 여러 static 메서드들 중에서 시작점(starting point)기능을 하는 main 메서드가 실행된다.
-> 인스턴스 생성 : new 이용하여 인스턴스 변수와 메서드를 메모리에 로딩한다.
-> 메서드 호출 : 메서드 안에서 선언된 로컬변수가 생성된다.
-> 결과값 출력
static 변수는 '클래스'와 관계가 있으며 인스턴스 변수는 '인스턴스'와 관계가 있고 로컬변수는 '메서드'와 관계가 있다.
프로그램이 실행될 때 인스턴스가 생성되기 위한 정보를 제공해주기 위해서 클래스가 먼저 메모리로 로딩된다.
대표적인 메서드가 main 메서드이다.
- 클래스, 변수, 메서드의 지정자로 사용할 수 있다.
클래스는 Inner 클래스(중첩 클래스)에서만 사용되며 변수는 인스턴스에서 공유 데이터를 사용할 목적으로 사용하고 메서드는 객체생성 없이 메서드를 접근할 목적으로 사용한다.- static변수는 '클래스'와 관련이 있기 때문에 단 한번 생성되며 프로그램이 실행될 때 생성되고 프로그램이 종료될 때 삭제된다. 또한 자동으로 기본값으로 초기화된다.
- 프로그램이 실행될 때 생성되기 때문에 객체생성과 전혀 관련이 없다.
- 생성된 모든 인스턴스에서 클래스명으로 접근이 가능하고 공유가 가능하다.
- static 메서드는 오버라이딩이 불가능하다.
- static 메서드내에서 인스턴스 변수 접근이 불가능하다. 생성시점이 다르기 때문이다.
- static 블록을 이용하여 어플리케이션에서 필요한 초기화 작업(파일 및 DB연동)을 할 수 있다.
class Counter{
private int num;
private static int count;
public Counter() {
num++;
count++;
}
public int getNum() {
return num;
}
public int getCount() {
return count;
}
// public static int getNum2() { // static 함수 내에서는 static변수가 아닌 일반변수를 쓰면 안된다.
// return num // error.
// }
public static int getCount2() { //클래스이름.함수명, 변수명
return count; //count는 static변수이기 때문에 사용가능하다.
}
}
public class TestMain {
public static void main(String[] args) {
System.out.println(Counter.getCount2()); //0
int x = Integer.parseInt("10");
Counter c = new Counter();
System.out.println("count "+c.getCount()); //1
System.out.println(c.getNum()); //1
Counter c2 = new Counter();
System.out.println("count "+c.getCount()); //2
System.out.println((c.getNum())); //1
System.out.println(Counter.getCount2()); //2
}
}
--> 여러 인스턴스에서 공유 가능한 데이터를 저장하기 위한 누적용으로 static변수를 사용할 수 있다.

class Person {
String name;
static int age;
public void a() {
System.out.println(this.name);
System.out.println(age);
}
public static void b() {
// System.out.println(name); //사용불가
System.out.println(age);
} //인스턴스 변수 name을 사용하면 에러가 발생하는 이유는 생성시점이 다르기 때문이다.
//static메서드는 프로그램 실행시 생성되고 인스턴스 변수는 객체 생성할 때 만들어 진다.
//따라서 프로그램 실행시 생성되는 메서드에서 아직 생성되지 않은 인스턴스 변수를 인지하지 못한다.
}
public class TestMain {
public static void main(String[] args) {
System.out.println(Person.age); //null
Person.b(); //0
Person p = new Person();
System.out.println(p.name); //null
System.out.println(p.age); //0
p.a(); //null 0
Person.b(); //0
p.age = 20;
Person p2 = new Person();
System.out.println(p.age); //20
System.out.println(p2.age); //20
}
}
static 블록은 프로그램이 실행될 때 단 한번 수행되고 instance 블록은 객체생성 될 때 매번 수행되는 블록이다.
public class Ex05_20 {
public Ex05_20() {
System.out.println("Ex05_20 생성자");
}
static {
System.out.println("static 블록은 프로그램 실행 시 실행");
}
{
System.out.println("instance 블록은 객체생성시 실행");
}
public static void main(String[] args) {
System.out.println("main 메소드 실행");
Ex05_20 ex = new Ex05_20();
Ex05_20 ex2 = new Ex05_20();
}
자바의 디자인 패턴 중 일반적으로 가장 많이 알려진 패턴으로서 하나의 인스턴스만을 생성하기 위한 패턴이다.
현실세계에 존재하는 객체 중에 유일한 객체가 존재하듯이 가상세계에서도 단 하나의 인스턴스만 생성하고자 할 때 사용되는 방법이다.
다음 코드는 단 하나의 인스턴스만 생성하고자 할 때 사용되는 방법이다.
public class Bank{
private static Bank b = new Bank(); // 2)
private Bank(){} // 1)
public static Bank getBank(){ // 3)
return b;
}
public String getName(){
return "XX은행“;
}
}//end class
public class TestBank{
.... Bank b = Bank.getBank(); // 4)
String name = b.getName();
.... }
}
- 외부에서 객체생성하지 못하도록 생성접근자의 지정자를 private로 지정한다. private접근 지정자는 같은 클래스에서만 접근 가능하다.
- 단 한번은 생성해야 되기 때문에 자신의 클래스에서 객체생성 코드를 사용한다. static지정자를 사용하여 프로그램 실행시 단 한번 생성된다.
- 외부에서는 객체생성은 불가능하지만, 다른 클래스 멤버들의 접근은 허용해야 된다. 따라서 2번에서 생성한 객체의 참조변수를 static 메서드를 이용하여 외부에 돌려준다.
- 외부에서는 클래스, 메서드로 참조값을 얻어서 다른 멤버들을 접근할 수 있다.
클래스명 변수 = 클래스명.메서드();
Calendar cal = Calendar.getInstance();
사전적 용어로 '마지막' 이라는 의미이다. 자바에서는 '변경되는 것'이 마지막이라는 용도로 사용된다.
일반적으로 다음 형식으로 표현한다. public static 지정자를 사용하여 외부에서 객체생성 없이 클래스이름으로 쉽게 접근 가능하도록 구현한다.
public static final 데이터형 상수 = 값;
--> 메서드에 사용하면 오버라이딩이 불가능하다.
앞에서는 기본형 데이터를 사용한 배열 사용법을 정리했고, 이번에는 클래스인 참조데이터를 배열로 관리하게 한다.
❗객체인 클래스 또한 데이터이기 때문에 데이터로 간주해야 한다.❗
데이터이기 때문에 변수에 저장할 수도 있고 배열로 관리할 수도 있고 또한 메서드 호출시 인자값으로 전달할 수 있으며 메서드의 리턴값으로 사용할 수도 있는 것이다.
public class TestStudent {
public static void main(String[] args) {
// 1. Student 저장배열생성, new 이용
Student[] allstu = new Student[3];
allstu[0] = new Student("홍길동1", 1, "서울1");
allstu[1] = new Student("홍길동2", 2, "서울2");
allstu[2] = new Student("홍길동3", 3, "서울3");
for (int i = 0; i < allstu.length; i++) {
// Student student = allstu[i];
// System.out.println(student.getName());
System.out.println(allstu[i].getName());
}
System.out.println("===================");
for (Student s : allstu) {
System.out.println(s.getName());
}
System.out.println("===================");
//2.
Student stu = new Student("홍길동1", 1, "서울1");
Student stu2 = new Student("홍길동2", 2, "서울2");
Student stu4 = new Student("홍길동4", 3, "서울3");
Student stu5 = new Student("홍길동5", 4, "서울4");
Student stu6 = new Student("홍길동5", 19, "서울5");
Student [] yy = {stu,stu2,stu4,stu5,stu6};
for (Student s : yy) {
System.out.println(s.getName());
}
System.out.println("===================");
//3. 리터럴 이용
Student [] yy2 = {new Student("홍길동1",1,"서울1"),
new Student("홍길동2",2,"서울2"),
new Student("홍길동3",3,"서울3"),
new Student("홍길동4",4,"서울4"),
new Student("홍길동5",19,"서울5")};
for (int i = 0; i < yy2.length; i++) {
Student student = yy2[i];
System.out.println(student.getName());
}
}
}
현실 세계에 존재하는 객체들은 독자적으로 사용되지 않고 일반적으로 여러 객체들 간에 특정 관계를 맺으면서 사용하게 된다. 상사-부하직원 / 스승-제자-친구 / 부모-자식 관계 등으로 밀접한 관계를 맺으면서 존재한다.
마찬가지로 자바 클래스들도 독자적으로 사용되지 않고 일반적으로 특정 관계를 맺고 상호작용하면서 프로그래밍 되어 진다.
이 챕터에서는 여러 클래스들간에 어떤 관계가 있으며 또한 어떤 방법으로 관계를 표현하는지를 배우게 된다.
한 객체와 다른 객체가 포함관계인 경우를 의미한다.
자동차 객체 - 엔진 객체, 바퀴 객체, 핸들 객체, 라디오 객체 등
자동차는 전체(whole)와 나머지 부품은 부분(part)이기 때문에 whole-part 관계 라고도 한다.



비슷한 속성 및 동작을 가진 객체들 간의 관계이다.
예를 들어 대학생, 고등학생, 중학생, 초등학생 객체들은 모두 공통된 개념의 학생 객체들이다.
개발자, 부장, 과장, 엔지니어, 비서 등은 모두 공통된 개념의 직원 객체들이다.
대학생 is a 학생, 중학생 is a 학생
개발자 is a 직원, 엔지니어 is a 직원, 비서 is a 직원
비슷한 개념들의 객체들은 모두 공통된 속성 및 동작을 가지고 있으며 이는 결국 공통점들이 중복되어 있다는 것이다.
공통점들을 추출해서 상위개념의 객체로 만들 수 있으며 하위 객체들에게 상위 개념의 객체를 상속받아서 사용한다면 중복이 제거되고 재사용성도 향상될 수 있다.

보통 공통 객체인 직원을 부모클래스, 상위클래스라고 하며 하위 객체를 자식클래스, 하위클래스라고 한다.
객체들 간에 is a 관계가 성립되면 자바의 상속기법을 적용시킬 수 있다.
자바 상속의 특징
- 객체 간에 is a 관계가 성립되어야 한다.
- 부모클래스의 멤버(인스턴스 변수, 메서드)를 자식클래스가 선언 없이 사용 가능하다. (❗단
private로 지정한 멤버 or 생성자는 상속이 불가능)- 자바는 단일 상속 (Single Ingeritance)만 지원한다.
- 상속은 UML 표기법으로
를 이용한다.( 실선을 이용한 삼각형 )
- 상속을 자바 코드에서는
extends키워드로 표현한다.- API 및 사용자가 만든 클래스들은 모두 상속 관계인 계층구조로 되어있다. 가장 상위에 있는 클래스는 java.lang.Object 클래스이며 최상위 클래스라고 부른다. 따라서 모든 클래스는 Object 클래스를 자연스럽게 상속받는다.
UML 표기법을 이용한 상속관계 도식화

상속 표현 형식
public class 자식클래스 extends 부모클래스 { }
클래스 정의시 extends를 사용하지 않으면 자동으로 extends Object가 설정된다고 생
각하면 된다
부모가 먼저 생성되어야 부모가 가진 멤버를 자신이 사용할 수 있기 때문이다. 이러한 이유 떄문에 부모 클래스를 먼저 생성할 목적으로 자식 클래스의 생성자 첫 라인에서 부모 생성자 호출이 자동으로 발생된다. 이때 부모의 오버로딩 생성자 중에서 기본 생성자가 자동 호출되기 때문에 부모 클래스에는 반드시 기본 생성자가 존재해야 된다.
package com.test;
class Emp{
public Emp() {
System.err.println("Emp 생성자 1");
}
public Emp(String name, int salary) {
System.err.println("Emp 생성자 2");
}
}
class Man extends Emp{
public Man() {
System.err.println("Man 생성자 ");
}
}
public class Ex06_2 {
public static void main(String[] args) {
Man man = new Man();
}
}
--> 결과는 부모 클래스의 기본생성자인 EMP()를 먼저 실행하고, 자식 클래스의 기본생성자 Man()을 실행한다.
사실 부모 클래스 상위에 있는 최상위클래스 Object가 가장 먼저 생성되므로 언제든 Object 클래스를 사용할 수 있다.

this는 메모리에 올라간 자신의 인스턴스를 의미하고 super는 부모의 인스턴스를 의미한다.
super 키워드는 상속관계의 하위클래스에서 상위클래스의 구성요소를 명시적으로 호출할 때 사용되는데, 어차피 상속받기 때문에 하위클래스에서 상위클래스의 멤버를 사용할 수 있으나 특별한 경우에는 반드시 상위클래스의 멤버 및 생성자를 명시적으로 호출해야 된다.
super.부모멤버 형식으로 표현한다.
public class Person {
int age = 20;
...
}//end class
public class Man extends Person{
int age = 40;
public void getInfo(){
System.out.println( age ); //40 출력 , this.age에서 this생략
System.out.println( super.age ); // 20 출력
}
....
}//end class
--> Person 클래스의 age 변수와 Man 클래스의 age 변수명이 동일하다.
그런데 이러한 경우는 거의 사용되지 않는 형태이다.
이유는 부모에 있는 변수를 자식에서 중복 선언할 필요가 없기 때문이고 잘못된 클래스 설계인 경우이다.
super([인자값]) 형식으로 사용하며 인자값은 없거나 여러 개가 가능하다. 주의할 점은 반드시 자식 생성자의 첫 라인에서 호출해야 된다.
public class Person{
String name;
public Person(String name){
this.name =name; // Man클래스에서 제공한 name값을 초기화
}
....
}//end class
public class Man extends Person{
public Man(String name){
super(name); //반드시 첫라인에서 호출한다. }
......
}//end class
--> 하위 클래스인 Man 의 생성자 첫 라인에서 명시적으로 하나의 문자열을 저장할 수 있는 부모 생성자를 super키워드를 이용하여 호출한다. 부모 생성자는 상속이 안되기 때문에 super([값])을 통한 방법으로만 호출이 가능하다.
👍이렇게 명시적으로 부모 생성자를 호출하는 이유는 name 변수가 Man클래스가 아닌 Persom 클래스에서 선언된 변수이기 때문에 선언된 곳에서 초기화 작업을 하기 위한 목적이다.
package com.test2;
class Employee {
String name;
int salary;
public String getEmployee() {
return name + " " + salary;
}
public Employee() {
super();
}
public Employee(String name, int salary) {
super();
this.name = name;
this.salary = salary;
}
}
class Manager extends Employee {
String depart;
public String getManager() {
return super.getEmployee() + "\t" + depart;
}
public Manager(String name, int salary, String depart) {
super(name, salary);
this.depart = depart;
}
}
public class Ex06_3 {
public static void main(String[] args) {
Employee emp = new Employee("홍길동", 2000);
Manager man = new Manager("이순신", 4000, "개발");
System.out.println(emp.getEmployee()); //홍길동 2000
System.out.println(man.getManager()); //이순신 4000 개발
}
}
--> 부모 클래스 Employee의 객체인 emp는 자식 클래스 Manager의 함수에 접근할 수 없지만, 자식 클래스 Manager의 객체인 man은 extends로 인한 상속으로, 부모 클래스 Employee의 멤버변수와 함수 모두 접근이 가능하다.
name, salary 변수를 Manager 클래스에서 초기화하지 않고 그 변수가 선언된 Employee 클래스의 생성자로 super를 이용하여 전달한다. 전달된 name, salary를 Employee에서 초기화하고 Manager에서 상속받아서 사용한다.

public과 private이 거의 대부분 쓰인다.
일반적으로 상속 관계시 부모 클래스의 메서드를 하위 클래스에서는 조건 없이 사용 가능하다.
하지만 하위 클래스에서 부모 클래스의 메서드를 재정의해서 사용할 수도 있는데 이렇게 재정의된 메서드를
overriding 이라고 부른다. ( overloading 과 구별해야 한다. 오버로딩은 기존에 없던 새로운 메서드를 정의하는 것이다.)
- 상속이 전제되어야 한다.
- 메서드 이름이 반드시 동일해야 된다.
- 메서드 리턴타입이 반드시 동일해야 된다. 단 상속관계인 경우에는 작은 타입으로 재정의 가능하다.
- 메서드 인자 리스트가 반드시 동일해야 된다.
- 접근 지정하는 부모의 레벨보다 같거나 확대만 가능하다.
- 예외 클래스는 부모의 클래스보다 계층적으로 같거나 하위 클래스만 사용 가능하다.
static,final,private지정자를 가진 메서드는 오버라이딩이 불가능하다.- 오버라이딩 메서드라는 것을 확실하게 알려주기 위하여
@Override라는 어노테이션을 사용할 수 있다. (생략할 수 있으나 사용하는 것을 권장한다. 사용하면 오버라이딩 규칙에 위배될 때 컴파일 에러가 발생되기 때문에 구현하기 편리하다.)
package com.test4;
class Employee{
String name;
int salary;
public void a() {
System.out.println("emp a 함수");
}
public String getEmployee() {
return name + " " + salary;
}
public Employee() {
super();
// TODO Auto-generated constructor stub
}
public Employee(String name, int salary) {
super();
this.name = name;
this.salary = salary;
}
}//end Employee
class Manager extends Employee{
String depart;
public void b() {
System.out.println("manager b 함수");
}
@Override
public String getEmployee() {
return super.getEmployee() + " " + depart;
}
public Manager(String name, int salary, String depart) {
super(name, salary);
this.depart = depart;
}
}//end manager
public class Ex06_7 {
public static void main(String[] args) {
Employee e1 = new Employee("홍길동",2000);
System.out.println(e1.getEmployee()); //홍길동 2000
Employee m1 = new Manager("길동",100,"영업"); //Employee 클래스의 하위 클래스 Manager에 다형성 적용
System.out.println(m1.getEmployee()); //길동 100 영업
Manager m2 = (Manager)m1;
System.out.println(m2.name); //길동
System.out.println(m2.depart); //영업
System.out.println(m2.salary); //100
}
}
--> 부모클래스와 자식클래스 모두 동일한 getEmployee() 메서드를 사용할 수 있기 때문에 재사용성이 향상된다.
기본 데이터형과 마찬가지로 참조 데이터 형에서도 같은 계열이고 큰 타입이면 하나의 변수가 여러 클래스 타입의 객체를 저장할 수 있다. 같은 계열을 상속을 의미하고 큰 타입은 부모클래스를 의미한다.
즉, 상속관계의 계층구조에서 상위타입의 변수로 모든 하위타입을 참조할 수 있는 특성을 '다형성'이라고 한다.
다형성의 특징
- 반드시 상속관계가 전제되어야 된다.
- 코드는 '부모타입 = 자식 타입' 형식으로 사용된다
- 만약 부모 타입에 저장된 자식 타입의 실제 데이터 형을 알려면
instanceof연산자를 사용한다.- '자식 타입 = 부모 타입' 형식으로 사용하기 위해서는 강제 형변환 시켜야 된다.
- 서로 다른 데이터를 배열에 저장하거나 서로 다른 데이터를 하나의 메서드에 전달하는 경우에 다형성을 활용할 수 있다.
예제는 위에 있는 예제와 동일하다.
--> 실행할 당시의 객체 타입을 기준으로 실제 실행할 메서드를 호출하는 것을 동적 바인딩(Dynamic Binding)이라고한다.
Employee e = new Manager( “이순신”, 4000, “개발” );
String info = e.getEmployee(); //동적 바인딩
컴파일 시점에서의 getEmployee()는 Employee 객체의 메서드로 인식하고, 실행 시점에서는 실제 객체 생성한 Manager 객체의 getEmployee()가 실행된다.
결국 다형성을 적용하면 참조변수가 컴파일단계와 실행단계에서 인식하는 메서드가 다르다는 것을 알 수 있다.
주의할 점은 메서드에만 동적 바인딩이 발생되고 변수에서는 발생하지 않는다.
메서드의 파라미터로 넘어온 클래스타입을 식별할 수 있다.
public class Employee {
public void taxRate (Employee e) {
if(e instanceof Manager) {
Manager m = (Manager)e;
System.out.println("Manager 세금 구하기");
}else if( e instanceof Engineer ){
Engineer en = (Engineer)e;
System.out.println("Engineer 세금 구하기");
}else if( e instanceof Employee) {
System.out.println("Employee 세금 구하기");
}
}
}
class Manager extends Employee{}
class Engineer extends Employee{}
❗instanceof 연산자 사용시 주의할 점은 계층구조의 하위부터 비교해야 된다. 만약 상위타입(Employee)부터 비교하면 첫 번째 if문만 실행한다.