자료의 입출력 (Inputs and Outputs)

자료의 입력과 출력을 위한 메서드에 대해 배움

출력메서드

println()

  • println은 여러가지 자료형을 입력할 수 있다.
  • ln == line에 약자이고 new line을 뜻함
  System.out.println("String cㅜa be printed."); //문자열도 출력가능
        System.out.println('c');//문자도 출력가능
        System.out.println(40234); //정수도 출력가능
        System.out.println(1.52f); //float도 출력가능
        System.out.println(15.2434);// double도 출력가능

printf()

  • printf는 포맷문자를 사용하여 출력함
  • 자료형에 맞는 포맷문자를 사용하고 ','를 사용하여 포맷문자에 해당하는 내용을 입력함
  • %s = string / %b = boolean / %d =10진수 %x=16진수, %o=8진수 / %f = floatin number = double, flaot
    %e = 지수 / \u0 = 유니코드 / %n = new line / 등이 포맷문자가 있음
  • %(숫자)d를 통해 숫자만큼의 빈칸을 확보할 수 있음
  • 확보한 빈칸의 숫자만큼을 초과하더라도 내용은 모두 출력됨
  • 실수에 자릿수 표현은 %0.0f로 표현되고 뒤에수는 자릿수를 뜻하고 앞에수는 표현할 칸수를 뜻함
  • 이외에도 시간과 날짜를 출력하는 포맷문자도 존재한다
System.out.printf("%s is for string\n", "STRING"); //%s는 스트링자리, newline은 수동
        System.out.printf("%s %s %s\n","123","456","15234");
        System.out.printf("%b\n", true); // b=boolean
        System.out.printf("%d 0x%x 0X%X 0%o \n",15,15,15,15); //d=10진수, x= 16진수 , o=8진수
                                    //10진수와 구분위해 16진수에는 앞에0x를 붙이고 8진수에는 0을붙임
        System.out.printf("%f\n",14.23); // f는 float 이 아닌 floating number로 float,double둘다 들어갈 수 있음
        System.out.printf("%f\n",14.23f); // %f로 float형도 입력 가능
        System.out.printf("%e\n",14.423); // 지수표현
        System.out.printf("%c %c\n", 'U','\u0042'); //U B 문자, 유니코드표현가능
        System.out.printf("%n"); //%n == new line

        System.out.printf("%5d.\n", 10); //우측정렬하고 5칸확보 // .은 공간보려고 찍음
        System.out.printf("%-5d.\n", 10); // 좌측정렬하고 5칸빈칸확보
        System.out.printf("%05d\n", 234); //5칸 앞에 0을 붙이면 빈곳은 00으로 표현됨
        System.out.printf("%3d\n", 104294);//3칸은 최소한 확보, 넘처도 출력은 다한다
        System.out.println("");


        //실수 자릿수 표현
        System.out.printf("%5.4f\n", 152523.456228); //5.4에 .4는 네자리까지 표현, 5는 최소 5칸확보하고 표현
                                                     //칸은 소숫점 끝짜리에서 시작
        System.out.printf("%5.2f\n", 1.425); //2자리까지 표현하고 5칸 확보해야되니까 1앞에 빈칸하나채움
                                            // 표현된 자리부터 5칸을확보함
                                            //

        System.out.printf("%-5.2f\n", 1.425); //2자리까지 표현하고 뒤에 1칸 만듬

입력메서드

입력 메서드는 변수의 입력을 리터럴이 아닌 키보드로 입력을 뜻함

Scanner 클래스

  • Sacnner 클래스를 생성함으로 입력메서드를 사용할 수 있음
  • next() 메서드는 공백으로 구분된 String을 입력
  • 3개의 입력값중 2개만 입력하게 되면 입력을대기하게 되는데 이것을 Blocking메서드라고 함
    <-> non - blocking 메서드
  • nextInt() Integer타입의 값을 입력받는 메서드(int외에도 다른자료형으로 활용가능)
  • nextLint() \n으로 구분되는 String을 입력받음, \n은 키보드에서는 엔터를 의미함
        System.out.println("Input methods.");
        Scanner scanner = new Scanner(System.in); // System.in은 키보드 입력을 뜻함
        //자동 import되네오

//        String s = scanner.next(); // 공백으로 구분된 String을 입력 받는다. String
//        System.out.println(s);

//        // this is string을 입력하면 공백으로 3개의 다른 입력으로 인식함
//        System.out.println(scanner.next()); //"this"
//        System.out.println(scanner.next()); //"is"
//        System.out.println(scanner.next()); //"string"

        // this is 만 입력하면 "this" "is" 출력되고 기다림 그다음 string입력하면 "string"출력됨

        //next() 메소드는 입력이 있을 때까지 기다립니다.
        //Blocking 메소드라고 부른다(<-> non-blocking 메소드 - 기다리지 않고 바로넘어가는애)

        //공백으로 구분되는 정수 입력
//        System.out.println(scanner.nextInt());
//        System.out.println(scanner.nextInt());
//        System.out.println(scanner.nextInt());
//        System.out.println(scanner.nextInt());

        // 정수 4개를 입력 받는다. 다른 자료형 입력 받으면 에러발생

        System.out.println(scanner.nextDouble());
        System.out.println(scanner.nextDouble());
        System.out.println(scanner.nextDouble());


        // \n으로 구분이 되는 String을 입력 받는다.
        // \n은 키보드에서는 엔터를 의미함

//        System.out.println(scanner.nextLine());
//        System.out.println(scanner.nextLine());
//        System.out.println(scanner.nextLine());

'Java' 카테고리의 다른 글

[Java] 2_예제1  (0) 2020.07.29
[Java]2_4_연산자 (Operators)  (0) 2020.07.29
[Java] 2_2_자료형(Data Type)  (0) 2020.07.28
[Java] 2_1_변수  (0) 2020.07.28
[Java] 자바 기본 클래스 - 예제  (0) 2020.07.22

자료형(Data Type)

자료형이란

  • 변수의 종류와 타입을 나타내는 용어
  • 변수의 선언할 때 리터럴에 따라 자료형을 필수로 표기해야함
  • 자료형은 기본형(Primitive Type), 참조형 (Reference Type)으로 구분함
  • 기본형 자료형에는 정수형, 실수형, 문자형, 논리형이 있음
  • 참조형에는 String(문자형)외 여러가지가 있음

정수형

  • 정수형에는 byte, short, int, long이 있음
  • 각 자료형 별로 메로리의 크기와 그에 따른 표현범위가 다름
  • 메모리의 효율적 사용을 위해서 범위에 맞는 자료형을 사용함
  • 정수 자료형의 기본 형은 int이기에 다른 자료형을 쓸때는 별도로 표기히야함
  • 정수 자료형은 값의 변동에 따라 overflow가 발생할 수 있으니 유의해야함
  • 정수 자료형은 10진수 뿐 아니라 2진수 8진수 16진수로도 표현 가능함
        // 정수 자료형 

        System.out.println(Byte.BYTES);
        System.out.println("Byte");
        System.out.println(Byte.BYTES); //1 // Byte자료형은 1BYTE라는 뜻 = 8bit
        byte byteValue = 42;
        System.out.println(byteValue);
        System.out.println(Byte.MAX_VALUE); //2^7 -1  싸인비트를 포현해야되서 (8비트-1비트) 7승
                                            // 0을 표현 해야 되기 떄문에 -1 해준다.(정수= 음수,양수,0)
        System.out.println(Byte.MIN_VALUE); //-2^7  음수까지의 범위를 포함
        // byte byteValue1 = 128; //범위를 넘어선 정수는 에러발생
        System.out.println(""); // 줄바꿈 기능


        System.out.println("Short"); // 2byte = 16bit
        System.out.println(Short.BYTES);//2
        System.out.println(Short.MAX_VALUE);// 2^15 -1
        System.out.println(Short.MIN_VALUE);// -2^15
        System.out.println("");

        System.out.println("Int");
        System.out.println(Integer.BYTES); // 4byte = 32bit
        System.out.println(Integer.MAX_VALUE);// 2^31 -1
        System.out.println(Integer.MIN_VALUE);// -2^31
        System.out.println("");

        System.out.println("Long");
        System.out.println(Long.BYTES); // 8
        System.out.println(Long.MAX_VALUE);// 2^63 -1
        System.out.println(Long.MIN_VALUE);// -2^63
        System.out.println("");

        //왜 다 Long으로 자료형을 안하냐 - 메모리 효율성위해, 필요에 따라 사용하여야 한다.

        //정수자료형의 유의할 점

        //Overflow
        // 32676 = 2^15 - 1 ==> 0111111111111111
        // 64000 => 10011111101111011110111 (엄밀하지않은수, 랜덤숫자)
        // 64000을 short로 바꾸면서 중간에 잘라버리고 앞자리가 1로바뀌어 음수가되고 남아있는 비트만큼 숫자가잡힘
        // ==> 음수로출력 , 이런현상을 overflow현상이라고함
        short shortValue = (short) 64000; //숫자를 short형으로 바꿈
        System.out.println(shortValue); // -1536,

        // 정수형은 기본적으로 int이기 때문에 다른 유형의 정수 선언시 별도로 표기해주어야함

        // byte byteValue3 = 144; // byte지만 기본 형인 int형으로 표현됨 so 4byte자료형으로표현됨

        short shortA = 10;
        short shortB = 20;
        // short shortC = shortA + shortB; //에러, 둘다 shortA와 shortB의 값이 int이기 때문에 shortC에들어갈수가 없음
        short shortC = (short)(shortA + shortB); // 더한값을 캐스팅해줘야 short로 들어갈수가 있음
        // shortA는 인티저가 아니라 변수가 맞다. 근데 언어에서 변수의값이 Integer가 기본값이다.  
        //정확한 연산과정
        // shortA와 showrB가 int로 먼저 변환이 된다.
        // +연산이 이루어진다
        // 연산된 값이 short로 변환된다

        int bigInt = Integer.MAX_VALUE;
        int biggerInt = bigInt + 1;
        System.out.println(biggerInt); // - 2147483648 int의 범위넘어서서 값이 잘려서 음수가됨
                                        // int의경우 에러도 안뜸 기본형이라.. so 직접 overflow조심해야함

        //long veryBigInt = 100000000000; //에러발생 너무커서
        long veryBigInt = 100000000000L; // 라지 L을 붙여줘서 Long인것을 알려줘야함 왜? 앞에서 Long은 자료형이고 값에붙은 Long은 리터럴이기때문
        long veryBigint2 = 100_000_000_000L; // 숫자의 가시성을위해 언더바로 구분할 수 있다. 3개씩 아니라 다양하게 할수있지만 3개씩만하늘걸로
        System.out.println(veryBigInt);
        System.out.println("");

        //진수법- Binary(2진수), Octal(8), Decimal(10), Hexadecimal(16)

        System.out.println(0b1101); // 0b하면 2진수 입력  //13
        System.out.println(071);// 0만있으면 8진수 // 57 //10진수랑 비슷해서 해깔려서 가능하면 쓰지마세요
        System.out.println(1424); // 숫자는 그냥입력
        System.out.println(0x10); //0x를 하면 16진수입력, 0~9까지는 숫자 10~16 : a,b,c,d,e,f 까지쓰면됨
        System.out.println(0xff); //


        //리터럴은 어던 변수의 대입되지 않은 순수한 값 64000은 리터럴  대입이된 수는 리터럴이 아님

실수형

  • 실수 자료형으로는 float과 double이 있음
  • 기본형이 double이기에 연산할 때 double형으로 변환되어 연산됨
  • float 변수를 선언할때는 리터럴 끝에 f(F) 붙여줘야함
  • 실수 리터럴은 지수형으로 표현 가능
        // 실수형 float, double
        System.out.println("float");
        System.out.println(Float.BYTES); // 4
        System.out.println(Float.MAX_VALUE); // 3.4028235 * 10^38
        System.out.println(Float.MIN_VALUE); // 1.4 * 10^-45 :resoulution (가장작게 구분할수있는수)
        System.out.println("");

        System.out.println("double");
        System.out.println(Double.BYTES); // 8
        System.out.println(Double.MAX_VALUE); // 1.8 * 10^308
        System.out.println(Double.MIN_VALUE); // 4.9 * 10^-324
         //: resoulution=해상도=단위 이수보다 더 작아질수가 없다.(정수에 resoultion은 1) (가장작게 구분할수있는수)
         //0에서 resoultion만큼 간 수가 MIN_VALUE = 0보다크면서 가장작은값
        System.out.println("");               // 정밀도 증가

        //일반적으로는 double로 바뀌어서 연산되기때문에 이것도 캐스팅해줘야함

        float floatVal = 1.4234f;
        float floatval2 = (float)1.4234;

        double doubleVal = 104.424132341234;
        // 실수 리터럴은 지수형으로도 표현 가능함
        double doubleVal2 = 1.423e8; //e8 = 10^8  //10 몇승 포함해서
        double doubleVal3 = 1.423E8; // 대문자 소문자 둘다 가능

문자형

  • 문자형엔 Char 타입이 있음
  • 문자열이 아닌 문자형임으로 한개의 문자 리터럴만 대입가능
  • ''작은 따옴표를 사용함
  • Char을 int형으로 캐스팅할 수 있고 각 문자마다 숫자가 배정되어 있음
  • 문자에 배정된 숫자를 테이블로 정리한 것을 아스키코드/유니코드 라고 함
        //문자형
        System.out.println("Char");
        System.out.println(Character.BYTES);
        System.out.println((int)Character.MAX_VALUE); // 캐스팅을해줘야 정수로 값을 볼수 있음 / 2^16 - 1 싸인비트가 없어서 정직하게들어감
                                                        // 맥스 숫자에 배정된 문자가 없어서 캐스팅안하면 출력이 안됨
        System.out.println((int)Character.MIN_VALUE); // 0
        System.out.println("");

        char charVal = 'A';
        System.out.println(charVal); // A
        System.out.println((int)'A'); // 65, 숫자를 외울필요는 없지만 문자를 숫자로 찍어보면 나온다 이정도만 알면됨
                                    // 이것 확장해서 숫자와 문자열을 표로 만든 것을 아스키코드/유니코드 라고한다
        System.out.println('C'); //작은따옴표 안에 1개이상은 입력 불가,
        System.out.println('\''); //작은따옴표 넣기위해 \를 사용해야함함
        System.out.println('"'); //이것도 가능하긴해 그래도 역슬래시쓰는게조음
        System.out.println('\"');

        System.out.println('\u0041'); //유니코드 칠땐 /u을 붙이고 뒤에 숫자는 16진수를 사용함// 16진수 41 => 65 == 'A'
        System.out.println((char)65);

###논리형

  • 논리를 판단하는 자료형으로 true와 false값만 가짐
        System.out.println("boolean");
        System.out.println(Boolean.TRUE);
        System.out.println(Boolean.FALSE);

        boolean isTrue = true;
        boolean isFalse = false;

        // isTure =1; // 다른 언어에서는 1=true, 0=false로 대입이 가능하지만 Java에서는 안됨
        // isFalse = 0;
        System.out.println("");

###문자열

  • 문자열(String)은 참조형 자료형이며 큰따옴표를 사용함
  • 문자열은 덧셈(+)로 이어 붙일 수 잇음
  • 문자열에 정수혹은 실수 리터럴을 더하면 자동으로 수를 문자열로 변환하여 함게 문자열로 출력 해 줌
        System.out.println("String");
        String s = "This is a new string.";
        System.out.println(s);

        String s1 = "This" + "is" + "also" + "a String.";
        System.out.println(s1);

        int intVal10 = 20;
        String s2 = "Stirng + Integer = " +intVal10; // 자동으로 String으로 변환후 연산
        System.out.println(s2);

        String s3 = "String + Integer= " + Integer.valueOf(intVal10).toString();//수동으로 String 변환후 연결
        System.out.println(s3);
        System.out.println("");
        //s2와 s3는 똑같은 과정을 거쳐서 STring으로변환되는것이고 자바에서 평의기능을 제공한 것임

        //다른 실수형 자료형에서도 똑같이 자동변환이 일어난다

형변환 (Type Casting)

  • 변수의 자료형을 바꿔주는 것을 형변환이라고 함
  • 변수앞에 (자료형)을 넣어주어 형변환을 함
  • 범위가 작은쪽에서 큰쪽 혹은 정밀도가 낮은쪽에서 높은쪽으로 형변환하는 것을 업캐스팅이라고함
  • 반대의 경우를 다운캐스팅이라고 하고 데이터가 소실 될 수 있음으로 주의하여야 함
/* //형변환(Type Casting)
        System.out.println("Casting");
        int intValue = (int)100.9;
        System.out.println(intValue); //100 // 캐스팅 시 값이 소실이 됨


        //Upcasting
        System.out.println("Upcasting"); // 범위가 작은 쪽 -> 범위가 큰 쪽
                                        //또는 정밀도가 낮은 쪽 - > 정밀도가 큰 쪽(실수)

        byte byteVal = 10;
        int intVal = byteVal;
        System.out.println(intVal); //10 // 1byte 에서 4byte로 넘어오니 자연스럽게 소실 없이 넘어옴
        intVal = (int)byteVal;//검은색안내문 redundant하다-> 자동으로 변환되기때문에 굳이 쓸필요없다.// 하지만 필요에따라서 하기도함

        intVal = 10244;
        long longVal = intVal; //자동으로 upcasting 됨
        longVal = (long)intVal; //

        float floatVal = longVal; // float: 4byte long:8byte// byte가 long이 크지만 upcasting됨// 범위가 중요하다는 얘기
        floatVal = (float)longVal;// redundant뜨진 않지만 자동으로 되는 캐스팅
        System.out.println(floatVal);

        double doubleVal = floatVal;
        doubleVal = floatVal; // 범위, 정밀도가 double이 높아서 upcasing 됨

        */


        //DownCasting

        long longVal = 104244L;
        // int intVal = longVal; // 에러//Downcasting은 자동으로 이루어지지 않습니다.
        int intVal = (int)longVal; // 직접캐스팅해줘야함 (자료형)
        System.out.println(intVal);

        longVal = 100_000_000_000L;
        intVal = (int)longVal;
        System.out.println(intVal); //1215752192 // Downcasting 하면서 상위 비트가 소실딤 -> 하위비트만으로 수표현 -> 어떤수 나올지 예측 어려움
                                    // 문제가 생길 소지가 있기에 캐스팅을 해줘야함

        char charVal = 4123; // 큰 숫자지만 char의 범위안임.
        byte byteVal = (byte)charVal; // Char의 범위 > byte의 범위
        System.out.println(byteVal); //27 // 데이터 손실 발생

변수(Variable)

변수란

  • 변수는 변하는 수를 뜻함
  • 변수를 선언한다는 것은 변하는 값이 할당 될 수 있는 메모리의 공간을 확보하는 것

변수의 선언

int x;
//자료형 변수명 으로 변수를 선언할 수 있다.
x = 10; // 10 -> 리터럴, 순수한 값을 리터럴이라고 부름, 있는 그대로의 값
//변수에 리터럴을 대입할 수 있다.
System.out.println(x); //10

int y, z, value; //변수는 여러개를 동시에 선언가능함

int valueOne = 10; //자료형 변수명 선언과 함께 값 대입가능
int valueTwo = 20; 

int valueThree = 100, valueFour = 1000; // 같은 자료형의 변수를 옆으로 늘여서 선언

변수명 규칙

-변수를 선언할 때 변수명을 유의해서 선언해야 함

        //int int 
        int intOne; //int(자료형)는 변수명으로 사용불가 but int+@는 가능
        //int 4thword; // 숫자가 가장먼저 나올 수는 없음
        int val2ue1; // 숫자가 중간에는 가능
        int 한글_됩니다; // 한글은 쓸수있지만 사용하지말아라 강사님이 욕먹는데

        //int Three&Four; // &과 같은 특수문자 불가능
        int $power; // 그중 $는 가능하지만 특수한경우에만 사용하기에 쓰지마세요

        // Code Convention // 관습,관례

        int valueOfComputer; // 처음엔 소문자로시작하고 단어가바뀔떼마다 대문자사용 = camelCase= camelNotation
        // int PascalCase;//참고로 시작도 대문자 단어도 대문자하는것을 파스칼케이스
        int lowerCamelCase; // 카멜케이스
        int UpperCamelCase; // 파스칼케이스

        int _8thWord; //숫자먼저쓸땐 _(언더바)먼저 사용, 언더바는 특수문자 X

        //상수는 모두대문자로 STATIC_변수명(대문자), 위에 예시있음




예제

 

- 날짜를 구현한 클래스 MyDate가 있습니다.
- 날짜가 같으면 equals()메서드의 결과가 true가 되도록 구현해 보세요
- hashCode()메서드도 구현해 보세요.

 

class Mydate {
	int day;
	int month;
	int year;
	
	public Mydate (int day, int month, int year) {
		this.day=day;
		this.month=month;
		this.year=year;
	}
	
	@Override
	public int hashCode() {
		return day+month+year;
	}

	@Override
	public boolean equals(Object obj) {
		if( obj instanceof Mydate ) {
			Mydate date = (Mydate)obj;
			return (this.day == date.day);
		}
			
		return false;
	}
}

public class MydateTest {

	public static void main(String[] args) {
		
		Mydate date1 = new Mydate(22,7,2020);
		Mydate date2 = new Mydate(22,7,2020);

		System.out.println(date1.equals(date2)); //true
		
		System.out.println(date1.hashCode()); //2049
		System.out.println(date2.hashCode()); //2049
		
	}

}
  • Mydate 클래스에 날짜를 구현할 수있도록 변수와 생성자를 입력합니다.
  • equals()를 재정의하여 2개의 date가 날짜가 같으면 eqauls를 true로 반환하게 하게 재정의합니다.
  • hashCode()를 재정의하여 day+month+year의 값을 return하게 합니다.
  • Mydate 인스턴스 dat1과 dat2를 생성하고 eqauls를 하면 true가 반환되고 hascode()를 사용하면 day,month,year를 합한 값이 출력됩니다.

'Java' 카테고리의 다른 글

[Java] 2_2_자료형(Data Type)  (0) 2020.07.28
[Java] 2_1_변수  (0) 2020.07.28
[Java] 자바 기본 클래스 - Class 클래스  (0) 2020.07.22
[Java]자바 기본 클래스 - Object 클래스  (0) 2020.07.22
[Java] 인터페이스 - 예제  (0) 2020.07.21

Class 클래스

 

-자바의 모든 클래스와 인터페이스는 컴파일 후 class 파일로 생성됩니다. class파일에는 멤버변수, 메서드, 생성자 등 객체의 정보가 포함되어 있는데 Class 클래스는 이 class파일에서 객체에 정보를 가져올 수 있습니다.

 

		Class c1 = String.class;
		
		String str = new String();
		Class c2 = str.getClass();
		
		Class c3 = Class.forName("java.lang.String");

-Class 클래스는 위와 같은 방법으로 불러올 수 있고 그중에 forName을 통해 가져오는 방법이 많이 사용되고 이를 동적 로딩이라고 부릅니다.

- 동적 로딩이라고 부르는 이유는 보통 다른 클래스 파일을 불러올때는 컴파일 시 스태틱에 그 클래스파일이 같이 바인딩이 되지만 forName으로 class파일을 불러올 때는 컴파일에 바인딩이 되지않고 런타임때 불러오게 되기 때문입니다.

즉 실행시에 불러서 사용할 수 있기 때문에 동적 로딩이라고 부르게됩니다.

-단점은 클래스파일명을 직접 적게 되어 있어 만약 파일명에 오타가 나면 에러가 발생할 수 있기 때문에 주의해야합니다.

 

public static void main(String[] args) throws ClassNotFoundException {
		
		Class c1 = String.class;
		
		String str = new String();
		Class c2 = str.getClass();
		
		Class c3 = Class.forName("java.lang.String");
		
		Constructor[] cons = c3.getConstructors();
		for(Constructor con: cons) {
			System.out.println(con);
		}
		System.out.println();
		
		Method[] methods = c3.getMethods();
		for(Method method : methods) {
			System.out.println(method);
		}
		
	}

-Constructor와 Method를 모를 때 위와 같은 방식으로 모든 Constructor와 Method를 출력시킬 수 있습니다.

-하지만 보통 .을 누루면 어떤 것들이 가능한지 알 수 있기 때문에 사용할 일은 많이 없습니다.

 

Object 클래스

Object 클래스는 모든 클래스이 최상위 클래스로 모든 클래스는 Object 클래스에서 상속을 받습니다. 그렇기 때문에 모든 클래스는 Object클래스의 메서드를 사용 가능하고 그 중 일부는 재정의 하여 사용할 수 있습니다. 위치는 java.lang.Object 클래스 입니다.

 

ToString 예제

class Book{
	String title;
	String author;
	
	public Book(String title, String author) {
		this.title = title;
		this.author = author;
	}

	@Override
	public String toString() {
		return author + "," +title;
	}
	
	
}
public class ToString {
	
	public static void main(String[] args) {
		
		Book book = new Book("토지", "박경리");
		
		System.out.println(book);
		
		String str = new String("토지");
		System.out.println(str.toString());
	}
}
  • Object클래스의 메서드인 ToString를 예제로 사용해보겠습니다. ToString 클래스를 만들면 모든 클래스는 Object를 상속하기 때문에 ToString클래스 또한 Object에 메서드를 사용할 수 있습니다. 
  • 이후 Book클래스를 작성하여 인스턴스를 생성시 title과 author입력할 수 있게 한후 다시 main메서드로 돌아와 Book인스턴스를 생성하여 "토지", "박경리"를 입력합니다.
  • 그후 출력문으로 book을 출력하면 내용물이 출력되는 것이아닌 인스턴스의 주소값이 출력됩니다.
  • 하지만 String인스턴스를 생성하여 입력하여 str을 출력하면 바로 토지가 출력이되는데 그것은 String이 출력될때 자동으로 toStirng 메서드를 사용하게 되어있기 때문입니다.
  • Book에서도 자동으로 toString()을 하기 위해 Book 클래스에 toSTring메서드를 재정의하고 author + "," title순으로 출력되게 하고 다시 book을 출력해보면 "토지", "박경리'하고 출력되게 됩니다.
  • 이것을 통해서 모든클래스에서 Object클래스의 메서드를 사용할 수 있고 또한 필요에 따라 메서드를 재정의해서 사용할 수 있는 것을 알 수 있었습니다.

equals() 메서드

equals()메서드는 두 객체의 동일함을 논리적으로 재정의하는 메서드입니다. 여기서 논리적 동일함은 서로 주소는 다르지만 같은 값을 가지는 객체를 뜻하고 서로 같은 주소를 가지는 물리적 동일함과 다른 의미로 사용됩니다. 정리해보면 equals()는 물리적으로 다른 메모리에 위치한 객체라도 논리적으로 동일함을 구현하기 위해 사용되는 메서드입니다.

 

public class EqualsTest {

	public static void main(String[] args) {
		
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		System.out.println(str1==str2);//false
		System.out.println(str1.equals(str2));//true
	}
  • str1==str2 에서 ==은 주소값이 동일한지 물어보는 것임으로 물리적동일함을 묻는 것이고 두 인스턴스는 다른 주소에 있기 때문에 false가 반환됩니다.
  • stra.equals(str2)는 논리적 동일성을 묻고 있기 때문에 두인스턴의 값은 abc로 같기에 true가 반환됩니다.
class Student{
	int studentNum;
	String studentName;
	
	public Student(int studentNum, String studentName) {
		this.studentNum=studentNum;
		this.studentName=studentName;
	}

	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Student) {
			Student std = (Student)obj; //Object클래스로 업캐스팅된걸 다운캐스팅
			return (this.studentNum == std.studentNum);
		}
		return false;
	}
	
}
public class EqualsTest {

	public static void main(String[] args) {
		
	Student Lee = new Student(100, "이순신");
	Student Lee2 = Lee;
	Student shin = new Student(100, "이순신");
	
	System.out.println(Lee == Lee2); //true
	System.out.println(Lee == shin); //false
	System.out.println(Lee.equals(shin)); //재정의전//false
	System.out.println(Lee.equals(shin)); //재정의후//true
	
	}
}
  • Lee와 Lee2는 물리적으로 같은 주소를 가지고 있기 때문에 Lee=Lee2는 true가 반환됩니다.
  • Lee와 shin는 물리적으로 같지 않기 때문에  Lee == shin 는false가 반환됩니다.
  • Lee.equals(shin) equlas 재정의 전에는 비교할 항목이 지정이 안되있기 때문에 false가 발생합니다
  • Book 클래스로 돌아가서 equals 메서드를 재정의합니다. 메서드에서 사용될 때 비교되는 항목이 자동으로 Object로 업캐스팅 되기 때문에 instanceof를 사용해서 안전하게 Student클래스로 다시 다운캐스팅하고 두 항목의 studentNum을 비교하게 재정의합니다
  • 다시 Lee.equals(shin)을 하게되면 true가 반환됩니다.

hashCode() 메서드

 

hashCode() 메서드의 반환값은 10진수의 인스턴스가 저장된 가상머신의 주소입니다. 

 

	System.out.println(Lee); //object.Student@79fc0f2f
	System.out.println(Lee.hashCode()); // 2046562095
	System.out.println(shin.hashCode()); // 1342443276
  • 기본 주소값이 16진수인 것과 다르게 hashCode()를 사용하면 10진수 주소를 반환해 줍니다.
  • 두개의 서로 다른 메모리의 위치한 인스턴스가 동일하다는 것은 equals()의 반환값이 true여서 논리적인 동일함을 보여주고 또한 동일한 hashCode값을 가져야 합니다.
  • 위의 Lee와 Shin은 equals()의 반환값은 true이지만 hashCode의 값은 다른 모습을 보여주고 있는데 이는 hashCode는 재정의 하지 않았기 때문입니다. 이러한 이유로 equals()를 재정의할 때 hashCode()를 같이 재정의해주는 경우가 많습니다.
  • 참고로 hashCode가 같아 지게 재정의한다고해서 실제 hashCode 주소값이 같다는 뜻은 아닙니다.
	//Book 클래스
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Student) {
			Student std = (Student)obj; //Object클래스로 업캐스팅된걸 다운캐스팅
			return (this.studentNum == std.studentNum);
		}
		return false;
	}
    @Override
	public int hashCode() {
		return studentNum;
	}
    //main 메서드
    System.out.println(Lee.hashCode()); // 100
	System.out.println(shin.hashCode()); // 100
    
    System.out.println(System.identityHashCode(Lee)); //2046562095
	System.out.println(System.identityHashCode(shin)); //1342443276	
  • hashCode를 재정의하여 Lee와 shin에 hashCode()를 같게 만드려고 할 때 return값은 equals를 재정의 할대 썻던 멤버 변수를 사용하는게 좋습니다.
  • 그래서 return을 equals에서 기준이 되었던 studentNum을 사용하면 Lee와 shin의 hashCode()값이 100으로 같아지는 것을 확인할 수 있습니다.
  • 만약 진짜 hascode()값이 궁금하다면 System.identityHashCode()메서드를 사용하며 됩니다.

 

clone()메서드

-clone() 메서드는 객체의 복사본을 만드는 메서드로 기본틀(prototype)이 되는 객체로부터 같은 속성 값을 가진 복사본을 생성할 수 있는 메서드입니다.

-객체지향 프로그래밍의 정보은닉에 위배되는 가능성이 있으므로 복체할 객체는 clonable 인터페이스를 명시해야 합니다.

 

class Book implements Cloneable{
	String title;
	String author;
	
	public Book(String title, String author) {
		this.title = title;
		this.author = author;
	}

	@Override
	public String toString() {
		return author + "," +title;
	}
	//클론 매서드
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	
  • 클론메서드를 추가하기 위해 클래스 선언부에 복사가 가능하다는 의미로 implements Clonable을 붙여줍니다.
  • 클론 메서드를 재정의하는데 따로 수정할 것은 없고 그대로 가져다 쓰면됩니다.
public class ToString {
	
	public static void main(String[] args) throws CloneNotSupportedException {
		
		Book book = new Book("토지", "박경리");
		
		System.out.println(book); // 박경리,토지
		
		Book book2 = (Book)book.clone();
		
		System.out.println(book2); // 박경리,토지
		
	}
}
  • book의 복사본을 만들기 위해 book2에 book.clone()메서드를 사용해주고 앞에 (Book)타입을 명시해 줍니다.
  • 예외 처리를 하고 book2를 출력해보면 book과 같은 결과값을 얻을 수 있습니다.

정렬알고리즘 구현하기

다음과 같이 여러 정렬 구현 알고리즘이 입력에 따라 실행될 수 있도록 구현해 보세요.

public interface Sort {
	
	void ascending(int[] arr);
	void descending(int[] arr);
	
	default void description() {
		System.out.println("숫자를 정렬하는 알고리즘입니다");
	}

}

Sort인터페이스를 만들고 int[] arr을 매개변수로하는 asceding()과 descending()메서드를 만든 후 default메서드로 description 을 추가합니다.

 

public class BubbleSort implements Sort {
	
	
	
	public void ascending(int[] arr) {
		System.out.println("BubbleSort ascending");
	}

	public void descending(int[] arr) {
		System.out.println("BubbleSort decending");
		
	}

	@Override
	public void description() {
		Sort.super.description();
		System.out.println("BubbleSort입니다");
	}

}

인스턴스를 구현할 BubbleSort 클래스를 만들고 ascending()과 desceding() 메서드를 만들어 출력문을 출력합니다. default 클래스를 재정의하여 출력문을 추가합니다. 이후 HeapSort클래스와 QuickSort클래스를 만들어 같은 작업을 반복해 줍니다.

 

import java.io.IOException;

public class SortTest {

	public static void main(String[] args) throws IOException {
		System.out.println("정렬방식을 선택하세요.");
		System.out.println("B : BubbleSort");
		System.out.println("H : HeapSort");
		System.out.println("Q : QuickSort");
		
		int ch = System.in.read();
		
		Sort sort = null;
		
		
		if(ch == 'B' || ch == 'b') {
			sort = new BubbleSort();
		} 
		else if(ch == 'H' || ch == 'h') {
			sort = new HeapSort();
		}
		else if(ch == 'Q' || ch == 'q') {
			sort = new QuickSort();
		} 
		else { 
			System.out.println("잘못된 입력입니다.");
			return;
		}
		
		int[] arr = new int[10];
		
		sort.ascending(arr);
		sort.descending(arr);
		sort.description();

	}

}
정렬방식을 선택하세요.
B : BubbleSort
H : HeapSort
Q : QuickSort
H
HeaSort ascending
HeaSort decending
숫자를 정렬하는 알고리즘입니다
HeaSort입니다

 

Test 클래스를 만들고 출력문을 출력 후 정렬방식을 입력받기 위해 System.in.read()를 작성 후 if if-else문을 통해 입력값에 따라 다른 정렬방식의 인스턴스를 생성합니다. 그후 생성된 인스턴스에 asceding(),desceding(),description() 메서드를 실행하면 결과값같이 나오게 됩니다.

 

인터페이스의 요소

 

  • 상수 : 선언된 모든 변수는 상수로 처리됩니다.
  • 메서드 : 인터페이스에 모든 메서드는 추상 메서드입니다.
  • 디폴트 메서드 : 구현코드가 없는 인터페이스에서 공통적으로 구현되야 하는 메서드가 있는 경우 추상클래스의 구현메서드 처럼 기본적인 구현을 가지는 메서드입니다. 기본 구현을 가지고 있다고 해도 실제 구현하는 클래스에서 재정의 할 수 있습니다.
  • 정적 메서드 : static키워드가 붙는 메서드로 인스턴스 생성과 상관없이 인터페이스 타입으로 호출하는 메서드입니다. 인스턴스를 사용하기 위해 클래스를 만들고 인스턴스를 생성하는 과정을 생략하고 바로 사용할 수 있게 구현해놓은 메서드입니다.
  • private 메서드 : 인터페이스 내에서만 사용가능한 메서드이고 디폴트 메서드나 정적메서드에 사용하기 위해 작성되는 메서드 입니다. 인터페이스를 구현하는 클래스쪽에서 재정의하거나 사용할 수 없고 디폴트나 정적메서드를 통해서만 사용 가능합니다.

예제

public interface Calc {
	
	double PI = 3.14; //나중에 상수가됨
	int ERROR = -9999999;
	
	int add(int num1, int num2);
	int substract(int num1, int num2);
	int times(int num1, int num2);
	int divide(int num1, int num2);
	//디폴트 메서드
	default void description() {
		System.out.println("정수 계산기를 구현합니다.");
		myMethod(); //private 메서드 사용
	}
	//static메서드
	static int total(int[] arr) {
		int total = 0;
		
		for(int i :arr) {
			total+= i;
		}
		mystaticMethod();//private static 메서드 사용
		return total;
	}
	//private 메서드
	private void myMethod() {
			System.out.println("private method");
}
	//private static 메서드
	private static void mystaticMethod() {
		System.out.println("private static method");
	}

}
  • 기존의 Calc 메서드 안에 인터페이스의 요소들을 추가해서 실습해보도록 하겠습니다.
  • default 메서드 description()은 구현문을 가진 메서드로 implements하는 클래스들에서도 기본적으로 적용이 되는 메서드입니다.
  • static메서드(정적메서드) total()은 인스턴스의 생성없이 사용할 수 있는 메서드로 인터페이스명을 타입으로 하여 실행할 수 있습니다.
  • private 메서드 myMethod는 인터페이스 내에서만 사용가능한 메서드로 description()안에 사용되었습니다.
  • private static 메서드는 인터페이스 내에서만 사용가능하고 static 키워드가 있기에 static 메서드안에서만 사용가능하기 때문에 total에 사용되었습니다.
public class CalcTest {

	public static void main(String[] args) {
		
		Calc calc = new CompleteCalc();
		int n1 = 10;
		int n2 = 2;
		
		System.out.println(calc.add(n1, n2));
		System.out.println(calc.substract(n1, n2));
		System.out.println(calc.times(n1, n2));
		System.out.println(calc.divide(n1, n2));
		//default 메서드사용
		calc.description();
		
		int[] arr = {1,2,3,4,5};
		//static 메서드 사용
        int sum = Calc.total(arr);
		System.out.println(sum);
		
	}

}
12
8
20
5
정수 계산기를 구현합니다.
private method
private static method
15

 

  • calc는 CompleteCalc의 인스턴스이고 description();은 CompleteCalc에 구현되어있지 않지만 default 메서드이기 때문에 사용이 가능합니다. default메서드는 재정의가 가능하기 때문에 CompleteCalc에서 override한다면 다른결과 값이 나오게 됩니다.
  • static 메서드 total은 따로 인스턴스 생성없이 인터페이스명인 Calc.total로 바로 사용할 수 있습니다.
  • 마지막으로 private 메서드와 private static메서드도 각각 defalt메서드와 static 메서드안에서 사용되고 있음을 결과값에서 확인할 수 있습니다.

여러개의 인터페이스 구현하기

  • 인터페이스는 구현 코드가 없으므로 하나의 클래스가 여러 인터페이스를 구현 할 수 있습니다.
  • 하지만 다른 인터페이스의 디폴트 메서드 이름이 중복되는 경우 에러가 발생하니 재정의해줄 필요가 있습니다. 
public interface Buy {
	
	void buy();
	
	default void order() {
		System.out.println("구매 주문");
	}

}
public interface Sell {
	
	void sell();
	
	default void order() {
		System.out.println("판매 주문");
	}
}
  • 2개의 인터페이스를 각각 buy와 sell이란 이름으로 만들고 동일한 이름의 default 메서드 order를 만듭니다.
public class Customer implements Buy, Sell {

	@Override
	public void sell() {
		System.out.println("customer sell");
		
	}

	@Override
	public void buy() {
		System.out.println("customer buy");
	}
	
	public void order() {
		System.out.println("customer order");
	}
    
	public void sayHello() {
		System.out.println("hello");
	}
}

 

  • 구매와 판매를 동시에하는 고객을 구현하기 위해 Buy와 Sell 두 인터페스를 모두 implements 합니다.
  • 각각의 인터페이스에 있던 sell()과 buy()를 구현하는데는 문제가 없지만 이름이 같았던 order()는 재정의가 필요하다고 에러메세지가 떠서 재정의를 해줍니다.
  • 마지막으로 Customer에서만 사용할 수 있는 sayHello()를 추가하고 마칩니다.
public class CustomerTest {

	public static void main(String[] args) {
		
		Customer customer = new Customer();
		customer.buy();
		customer.sell();
		customer.order();
		customer.sayHello();
		
		Buy buyer = customer;
		buyer.buy();
		buyer.order();
		
		Sell seller = customer;
		seller.sell();
		seller.order();
		
	}

}
customer buy
customer sell
customer order
hello
customer buy
customer order
customer sell
customer order
  • 실행을 위해 Test클래스를 만들고 Customer 인스턴스를 생성해 buy(),sell(),order(),sayHello()메소드를 사용합니다.
  • 각각 Buy와 Sell에 인스턴스를 생성해 사용한 결과 각각의 메서드가 실행한것을 확인 할 수 있습니다.
  • 1개의 클래스만 상속이 가능한 것과는 다르게 인터페이스는 여러개도 구현이 가능하고 중복될 수 있는 default 메서드만 유의해서 사용하면 된다는것을 알 수 있습니다.

 

인터페이스의 상속

인터페이스간에도 상속이 가능합니다. 구현부가 없으므로 extends 뒤에 여러 인터페이스를 상속받을수 있습니다. 이런 인터페이스 간의 상속을 타입 상속(type inheritance)라고 합니다. 아래의 클래스 관계도를 예제로하여 더 알아보도록 하겠습니다.

 

public interface X {

	void x();
}
public interface Y {
	void y();

}

X, Y 두개의 인터페이스가 있습니다.

 

public interface MyInterface extends X, Y{
	
	void myMethod();
}

MyInterFace는 X와 Y 인터페이스를 extends하는 인터페이스입니다.

public class MyClass implements MyInterface {

	@Override
	public void x() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void y() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void myMethod() {
		// TODO Auto-generated method stub
		
	}

}

 MyClass는 MyInterface를 구현하는 클래스이고 구현해야될 메서드는 MyinterFace에 myMethod() 뿐 아니라 상속받은 x()와 y()까지 입니다. 간단한 예제이지만 인터페이스간에도 상속이 일어날 수 있음을 알 수 있습니다.

 

 

인터페이스 구현과 클래스 상속 함께 사용하기

 

인터페이스 구현과 클레스 상속을 함께 하는 경우에 대해서 예제를 통해서 알아보도록 하겠습니다. 아래의 관계도에서 BookSelf는 Shlef라는 클래스를 상속하고 Queue 인터페이스를 구현을 동시에 합니다.

import java.util.ArrayList;

public class Shelf {

	protected ArrayList<String> shelf;
	
	public Shelf() {
		shelf = new ArrayList<String>();
	}
	
	public ArrayList<String> getShelf(){
		return shelf;
	}
	
	public int getCount() {
		return shelf.size(); 
	}
}

상위 클래스인 Shelf를 생성하고 ArrayList<String>을 선언하여 입력한 책을 보관할 수 있게 합니다. Shelf 생성자 안에 ArrayList의 생성자를 두고 getShelf()와 getCount()메서드를 만듭니다. 복습으로 .size는 현재 배열안에 들어있는 값이 몇개인지 알려주는 메서드입니다.

 

public interface Queue {
	
	void enQueue(String title);
	String deQueue();
	
	int getSize();
}

Queue인터페이스를 만들고 책을 넣을 수 있는 enQueue()와 뺄 수 있는 deQueue()메서드를 생성합니다. 

 

public class Bookshelf extends Shelf implements Queue {

	public void enQueue(String title) {
		shelf.add(title);
	}

	public String deQueue() {

		return shelf.remove(0);
	}

	public int getSize() {
		return getCount();
	}

}

BookShelf 클래스를 만들고 extends Shelf와 implements Queue를 동시에 적음으로 상속과 인터페이스 구현을 함께 합니다. enQueue()에 shelf.add(title)로 책을 축가하는 기능을 추가하고 deQueue()를 통해 .remove(0)사용함으로 배열에 첫번째 부터 지워주는 기능을 하게 합니다. 마지막으로 getSize()메서드를 통해 책장에 책이 얼마나 있는지 확인하는 기능을 추가합니다.

 

public class BookshelfTest {

	public static void main(String[] args) {

		Queue bookQueue = new Bookshelf();
		bookQueue.enQueue("태백산맥1");
		bookQueue.enQueue("태백산맥2");
		bookQueue.enQueue("태백산맥3");
		
		System.out.println(bookQueue.deQueue());
		System.out.println(bookQueue.deQueue());
		System.out.println(bookQueue.deQueue());
	}

}
태백산맥1
태백산맥2
태백산맥3

Test클래스를 만들고 Bookshlf인스턴스를 생성하는데 Shelf로 해도되고 Queue로 해도 되지만 기능적인 면에서 Queue인터페이스를 타입으로하고 인스턴스를 생성합니다. 복습으로 Shlef 인스턴스를 생성 안해도 ArrayLIst<Stirng>이 생성되는 이유는 BookShelf 생성이전에 상위 클랙스인 Shelf를 자동으로 생성하기 때문입니다. 이후 .enQueue 메서드로 책을 3권 추가하고 deQueue()메서드로 책을 한권씩 빼보면 배열에 앞에서부터 하나씩 빠지는걸 확인 할 수 있습니다.

 

인터페이스에 대한 많은 내용이 있었지만 무엇보다 중요한건 인터페이스가 무엇이고 왜쓰이는지, 그것을 설계하는것은 어떤것인지를 알면 좋을 것 같습니다.

인터페이스의 역할은?

인터페이스는 클라이언트 프로그램에 어떤 메서드를 제공하는지 알려주는 명세(specification) 또는 약속입니다. 그 이유는 어떤 객체가 어떤 인터페이스 타입이라는 것은 그 메서드들을 다 구현했다는 의미이기 때문입니다. 그렇기 때문에 클라이언트 프로그램은 실제 모듈이나 클래스의 구현내용을 몰라도 인터페이스의 정의만 알면 그 객체를 사용할 수 있게 됩니다.


인터페이스와 다형성 구현하기


고객 센터에는 전화 상담을 원하는 상담원들이 있습니다. 일단 고객 센터로 전화가 오면 대기열에 저장됩니다. 상담원이  지정되기 전까지 대기 상태가 됩니다. 각 전화가 상담원에게 배분되는 정책은 다음과 같이 여러방식으로 구현될 수 있습니다.
- 상담원 순서대로 배분하기
- 대기가 잛은 상담원 먼저 배분하기
- 우선수위가 높은(숙련도가 높은) 상담원에게 먼저 배분하기

위와 같은 다양한 정책이 사용되는 경우 interface를 정의하고 다양한 정책을 구현하여 실행하세요.

 


코드를 작성하기 전에 설계단계에서 어떤 메서드가 필요한지 구상해서 그것을 인터페이스로 만드는 것이 선결과제입니다. 과제 해결을 위해 위와 같이 먼저 코드 관계도를 그려보는 단계가 필요합니다.  그 다음 관계도를 바탕으로 코드를 작성합니다.

public interface Scheduler {
	public void getNextCall();
	public void sendCallToAgent();
}
  • 인터페이스 Scheduler를 생성해서 getNextCall();과 sendCallToAgent() 메서드를 작성합니다. 간단해 보이는 코드이지만 설계단계에서 어떤 메서드들이 필요한지 미리 선언하는 것은 어려운과제가 될 수 있습니다.
public class RoundRobin implements Scheduler {

	public void getNextCall() {
		System.out.println("상담 전화를 순서대로 대기열에서 가져옵니다.");
	}

	public void sendCallToAgent() {
		System.out.println("다음 순서의 상담원에게 배분합니다.");
	}

}
public class LeastJob implements Scheduler {

	@Override
	public void getNextCall() {
		System.out.println("상담 전화를 순서대로 대기열에서 가져옵니다.");
	}

	@Override
	public void sendCallToAgent() {
		System.out.println("현재 상담업무가 없거나 상담대기가 가장 적은 상담원에게 할당합니다");
	}

}
public class PriorityAllocation implements Scheduler {

	@Override
	public void getNextCall() {
		System.out.println("고객등급이 높은 고객의 call을 먼저 가져옵니다.");
	}
	@Override
	public void sendCallToAgent() {
		System.out.println("업무 숙련도가 높은 상담원에게 먼저 배분합니다.");
	}

}
  • Scheduler 인터페이스를 각각의 방식으로 구현한 3가지 클래스를 작성합니다.
  • 다른 방식의 도입이 필요하다면 하나의 클래스를 더만들어서 implement를 통해 언제든지 확장이 가능합니다.
import java.io.IOException;

public class SchedulerTest {

	public static void main(String[] args) throws IOException {

		System.out.println("전화 상담원 할당 방식을 선택하세요");
		System.out.println("R : 한명씩 차례대로");
		System.out.println("L : 대기가 적은 상담원 우선");
		System.out.println("P : 우선순위가 높은 고객운선 숙련도 높은 상담원");

		int ch = System.in.read();
		Scheduler scheduler = null;
		
		if(ch == 'R'|| ch== 'r') {
			scheduler = new RoundRobin();
		}
		else if( ch == 'L' || ch =='l') {
			scheduler = new LeastJob();
		}
		else if( ch == 'P' || ch =='p') {
			scheduler = new PriorityAllocation();
		}
		else {
			System.out.println("지원되지 않는 기능입니다.");
			return;
		}
		
		scheduler.getNextCall();
		scheduler.sendCallToAgent();
	}	
}
  • Scheduler 객체를 실행하기 위한 Test클래스를 만듭니다. 이부분이 실제 클라이언트들이 사용하게 되는 부분입니다.
  • 먼저 어떤 입력값이 입력값이 될 수 있을지 출력문을통해 안내를 하고 System.in.read()를 통해 입력값을 받습니다. 그후 if 문을 통해 각 입력값 별로 어떤 클래스의 인스턴스를 생성해야되는지를 구현하고 마지막으로 .getNExcall()와 sendCallToaAent를 실행합니다.

간단한 예를 통해 인터페이스를 활용하여 다양한 정책이나 알고리즘을 프로그램의 큰 수정 없이 적용하고 확장할 수 있음을 배웠습니다. 반대로 하나의 정책이나 알고리즘으로 구현되는 프로그램을 여러개의 정책이나 알고리즘을 선택할 수 도있도록 인터페이스를 작성하는 경우도 있습니다.

인터페이스란?

 

  • 인터페이스는 어떤 객체에 대한 명제로 이 객체가 어떤 메서드들을 제공하고 어던 역할을 하는지에 대한 일종의 설명서로 대부분 설계단계에서 만들게 됩니다.
  • 인터페이스란 클래스의 일종으로 추상메서드로만 이루어진 클래스로 추상클래스에는 추상메서드와 일반 메서드가 동시에 있을 수 있다는 점에서 다릅니다.
  • 추상메서드의 경우와 같이 구현부가 없는 메서드로만 구성되어 있기에 new 생성자를 사용할수 없고 그로 인해 인터페이스 안에 변수를 선언하더라도 힙메모리 안에 구성이 안되기 때문에  결국 상수로 변환됩니다.
  • 구현 코드가 없는 인터페이스의 상속은 타입 상속이라고 불리기도 합니다.(하위 클래스들의 타입의 역할을 하여 다형성을 구현할 수 있게 해주기 때문에)
  • 인퍼페이스의 요소로 앞에서 언급한 추상 메서드, 상수 외에도 디폴트 메서드, 정적 메서드, private메서드가 나머지는 뒤에서 더 자세히 알아보도록 하겠습니다. 

 

인터페이스의 선언과 구현

인터페이스를 어떻게 선언하고 구현하는지에 대한 예제를 살펴보겠습니다.

	//1번
public interface Calc {
	//2번	
	double PI = 3.14; //나중에 상수가됨
	int ERROR = -9999999;
	//3번
	int add(int num1, int num2);
	int substract(int num1, int num2);
	int times(int num1, int num2);
	int divide(int num1, int num2);

}
  • 1번 : 계산을 기능을 하는 객체에 인터페이스를 작성하기 위해 Calc클래스를 만들고 클래스 선언부(public class Clac)에 class를 지우고 interface를 적으면 인터페이스로 선언됩니다.
  • 또한 클래스를 생성할 때 클래스로 생성하지 않고 바로 인터페이스를 생성하는 방법도 있습니다.
  • 2번 :  변수 PI와 ERROR은 실제 변수가 아닌 상수로 컴파일과정에서 public static final이 다 붙어서 상수화됩니다.
  • 3번 :  계산 기능을 하는 add(), substract(), times(), divde() 메서드를 만듭니다. abstract 키워드가 생략됬지만 이들은 구현부가 없는 추상메서드 입니다. 이 메서드들도 컴파일 과정에서 추상 메서드로 변환됩니다.
  • 메서드를 함수의 이름, 매개변수 반환값을 선언할 수 있다는 것은 결국 이 메서드가 어떤 기능을 대략적으로 보여주는 기능을 합니다. 인터페이스는 이렇게 객체의 기능을 간략하게 설명하는 기능을 하고 하위 클래스에서 어떻게 구현되야 하는지를 안내합니다.

  • 위의 그림같이 추상메서의 경우와 같이 이탤릭체로 표현된 Calc를 구현한 Calculator와의 관계는 점선으로 표현합니다.
  • 인터페이스의 메서드를 일부만 구현한 Calculator는 추상클래스이고 이를 실선으로 상속받은 CompleteCalc클래스에서는 Claculator에서 구현되지 않은 메서드들이 구현될 것입니다.
	//1번
public abstract class Calculator implements Calc {
	//2번
	public int add(int num1, int num2) {
		return num1+num2;
	}

	public int substract(int num1, int num2) {
		return num1-num2;
	}

}
  • 1번 : Claculator 클래스를 만들고 인터페이스 Calc를 구현한다는 뜻에서 implements를 선언부에 사용합니다. 또한 Calc의 일부 메서드들만 구현할 것이기에 class앞에 abstract을 붙여 추상 클래스라고 선언해 줍니다.
  • 2번 : add(),substarct()클래스를 각각의 기능에 맞게 작성합니다
public class CompleteCalc extends Calculator {

	public int times(int num1, int num2) {
		return num1 * num2;
	}

	public int divide(int num1, int num2) {
		if(num2 == 0)
			return ERROR;
		else
		return num1/num2;
	}
	
	public void showInfo() {
		System.out.println("모두 구현하였습니다.");
	}

}
  • 클래스 선언부에 extends를 사용하여 상속 받는다는 것을 알리고 아래의 Calculator에서 구현하지 않은 나머지 2개의 메서드를 마저 작성합니다. 
  • showInfo()메서드를 추가해서 작성합니다.
public class CalcTest {

	public static void main(String[] args) {
		CompleteCalc calc = new CompleteCalc();
		int n1 = 10;
		int n2 = 2;
		
		System.out.println(calc.add(n1, n2));
		System.out.println(calc.substract(n1, n2));
		System.out.println(calc.times(n1, n2));
		System.out.println(calc.divide(n1, n2));
		
		calc.showInfo();
	}
}

 

  • CalcTest를 만들고 유일하게 생성할 수 있는 CompleteCalc클래스를 선언합니다
  • Completecalc는 Calc나 Calculator로도 선언할 수 있지만 업캐스팅(묵시적 형변환) 되기 때문에 인터페이스의 메서드들만 사용할 수 있고 CompleCalc에서 만든 shoInfo();메서드는 사용할 수 없습니다.
  • n1,n2 변수에 값을 넣어주고 메서드들의 결과값을 출력문을 만들어 출력합니다.
  • cal.했을 때 뜨는 메서드 항목을 통해 사용자들은 이 개체에서 제공하는 것들이 무엇인지 알 수 있게되는데 그게 인터페이스의 역할중에 하나입니다.

 

 

자동차 주행과정 구현하기


Car 추상 클래스를 상속받는 Sonata, Avate, Gradeur, Genesis 클래스가 있습니다. 각 차는 주행하기 위해 다음 메서드의 순서로 움직입니다.

run(){
  start();
  drive();
  stop();
  turnoff();
}

run() 메서드를 템플릿 메서드로 구현하고 CarTest 클래스를 ArrayList를 활용하여 다형성이 구현되게 작성하시오

 

public abstract class Car {
	
	public abstract void start();
	public abstract void drive();
	public abstract void stop();
	public abstract void turnoff();
	
	final public void run() {
		start();
		drive();
		stop();
		turnoff();
		System.out.println("=================");
	}
}
  • Car 클래스를 작성하고 공통된 메서드인 start(),drive(),stop(),turnoff();를 추상메서드로 구현하고 클래에도 abstract를 선언해줍니다.
  • run()메서드를 Car 클래스의 메서드들의 기능하는 순서를 조직하는 템플릿 메서드로 작성합니다.
public class Sonata extends Car{

	@Override
	public void start() {
			System.out.println("Sonata 시동을 켭니다");
	}

	@Override
	public void drive() {
		System.out.println("Sonata 달립니다");
	}

	@Override
	public void stop() {
		System.out.println("Sonata 멈춥니다.");
		
	}

	@Override
	public void turnoff() {
		System.out.println("Sonata 시동을끕니다.");
		
		
	}

}
  • Sonata 클래스를 하부 클래스로 작성하고 추상 메서드들에 출력문을 작성합니다.
  • 나머지 3개의 하부 클래스도 이름을 바꾸 똑같이 작성합니다.(생략)
import java.util.ArrayList;

public class CarTest {

	public static void main(String[] args) {

		ArrayList<Car> carList = new ArrayList<Car>();
		
		carList.add(new Sonata());
		carList.add(new Grandeur());
		carList.add(new Avante());
		carList.add(new Genesis());
		
		for(Car car : carList) {
			car.run();
		}
	}

}
  • CarTest 클래스를 작성하고 Car를 자료형으로 하는 ArryList를 만들어 add메소드를 통해 하부 클래스들을 new하여 배열에 담습니다.
  • 향상된 for문을 사용하여 배열안에 모든 인스턴스들이 run()메서드를 사용하게 합니다.
Sonata 시동을 켭니다
Sonata 달립니다
Sonata 멈춥니다.
Sonata 시동을끕니다.
=================
Grandeur 시동을 켭니다
Grandeur 달립니다
Grandeur 멈춥니다.
Grandeur 시동을끕니다.
=================
Avante 시동을 켭니다
Avante 달립니다
Avante 멈춥니다.
Avante 시동을끕니다.
=================
Genesis 시동을 켭니다
Genesis 달립니다
Genesis 멈춥니다.
Genesis 시동을끕니다.
=================

 

템플릿 메서드 활용 예제

위의 예제는 PlayerLevel를 상위 클래스로 하여 3가지 하위 레벨 클래스를 가지고 있고 Player가 PlayerLevel을 사용하게 되는 구조를 가지고 있고 그것을 관계도로 표현하면 다음과 같습니다.

 

 

 

public abstract class PlayerLevel {

	public abstract void run();
	public abstract void jump();
	public abstract void turn();
	public abstract void showLevelMessage();
	
	final public void go(int count){
		run();
		for(int i=0; i<count;i++) {
			jump();
		}
		turn();
	}
}
  • 상위클래스인 PlayerLevel을 abstract 클래스로 선언하고 하위클래스에서 공통으로 사용될 run(),jump(),turn(),showLevelMessage();를 abstract메서드로 작성합니다.
  • PlayerLevel클래스에 메서드들을 어떤순서로 작동시킬지 정의한 템플릿 메서드 go()를 작성하고 jump는 for문을 통해 입력횟수만큼 반복되도록 합니다.
public class BeginnerLevel extends PlayerLevel{

	public void run() {
		System.out.println("천천히 달립니다");
	}

	public void jump() {
		System.out.println("Jump 못하지롱");
	}

	public void turn() {
		System.out.println("turn 못하지롱");
	}

	public void showLevelMessage() {
		System.out.println("******** 초급자 레벨입니다 ********");
	}

}

 

  • BeginnerLevel 클래스를 작성하고 abstract 메서드들에 출력문을 작성합니다. 아래의 Advanced클래스와 SuperLevel클래스도 해당 레벨에 맞게 작성해줍니다.
public class Advanced extends PlayerLevel {

	public void run() {
		System.out.println("빨리 달립니다");
		
	}

	public void jump() {
		System.out.println("높이 Jump 합니다");
		
	}

	public void turn() {
		System.out.println("turn 못하지롱");
		
	}

	public void showLevelMessage() {
		System.out.println("******** 중급자 레벨입니다 ********");
		
	}

}

 

public class SuperLevel extends PlayerLevel {
	
	public void run() {
		System.out.println("엄청 빨리 달립니다");
		
	}

	public void jump() {
		System.out.println("아주 높이 Jump 합니다");
		
	}

	public void turn() {
		System.out.println("turn 합니다");
		
	}

	public void showLevelMessage() {
		System.out.println("******** 고급자 레벨입니다 ********");
		
	}
}

 

public class Player {
	
    //1번
	private PlayerLevel level;
	
	public Player() {
		level = new BeginnerLevel();
		level.showLevelMessage();
	}
	
	public PlayerLevel getLevel() {
		return level;
	}

	public void setLevel(PlayerLevel level) {
		this.level = level;
	}
    //2번
	public void upgradeLevel(PlayerLevel level) {
		
		this.level = level;
		level.showLevelMessage();
		
	}

	public void play(int count) {
		level.go(count);
	}
	
	
	
}
  • 1번 : Player 클래스를 작성하고 PlayerLevel을 자료형으로 하는 level변수를 선언하고 Player 생성자를 만들면서 level에 New BeiginnerLevel 인스턴스를 생성해서 대입하고 shoLevelMessage()메서드를 사용해 현재 레벨을 출력 합니다.. prviate 변수인 level을 읽고 쓰기 위한 get/set 메서드도 추가합니다
  • 2번 : upgradeLevel()메서드를 작성하고 새로운 level을 입력하면 지금 있는 멤버변수 level에 대입하고 showLevelMessage()메서드를 사용해 현재 레벨을 출력합니다.
public class PlayerTest {

	public static void main(String[] args) {
		//1번
		Player player = new Player();
		player.play(1);
		//2번
		PlayerLevel aLevel = new Advanced();
		player.upgradeLevel(aLevel);
		player.play(2);
		
		PlayerLevel sLevel = new SuperLevel();
		player.upgradeLevel(sLevel);
		player.play(3);
	}

}
******** 초급자 레벨입니다 ********
천천히 달립니다
Jump 못하지롱
turn 못하지롱
******** 중급자 레벨입니다 ********
빨리 달립니다
높이 Jump 합니다
높이 Jump 합니다
turn 못하지롱
******** 고급자 레벨입니다 ********
엄청 빨리 달립니다
아주 높이 Jump 합니다
아주 높이 Jump 합니다
아주 높이 Jump 합니다
turn 합니다
  • 1번 Player 생성자를 통해 player를 생성하고 plya()메서드를 사용해 BegginerLevel에서의 플레이를 실행시봅니다.
  • 2번 레벨 업그레이드를 위해 PlayerLevel 타입으로 Advnaced 인스턴스를 생성하여 upgradeLevel()메서드를 통해 level에 대입합니다. 그후 play()메서드를 사용하면 중급자 레벨로 플레이하는 것을 볼 수 있습니다. SuperLevel도 동일하게 업그레이드하고 플레이 할 수 있습니다.

 

템플릿 메서드

  • 틀이나 견본을 의미하는 템플릿을 붙인 템플린 메서드는 추상 메서드나 구현된 메서드를 활용하여 전체의 흐름(시나리오)를 정의 해놓은 메서드를 뜻합니다. final로 선언하여 하위 클래스에서 재정의 될 수 없게 하고 그렇기에 전체의 흐름이 정의되있다고 얘기할 수 있는 것입니다.
  • 템플릿 메서드는 Java에서만 사용되는 개념이아닌 디자인 패턴의 일종으로 프레임워크에서 많이 사용되는 페턴입니다. 추상 클래스로 선언된 상위클래스에서 추상메서드를 이용하여 전체적인 흐름을 정의하고 하위 클래스에서 각 메서드에 대한 구체적인 구현을 하도록 위임하는 방식입니다. 

템플릿 메서드 구현하기 예제

 

  • 왼쪽 클래스 관계도에서 볼수 있듯이 Car라는 상위 클래스를 추상클래스로 정의하여 차의 공동적인 기능들인 startCar(), tunOff(), run()을 이미 구현해 놓았고 그외에 하위 클래스인 AICar와 ManualCar에서 개별적으로 구현해야 할 drive(), stop() 메서드를 추상메서드로 두었습니다.
  • 메서들 중에 run()메서드는 전체적으로 차의 움직임에 흐름을 조직하는 기능을 하는 메서드로 각 메서드들의 활용하는 순서가 정의되어 있고 이런 기능을 하는 메서드를 템플릿 메서드라고 합니다.
  • startCar();나 turnOff()같이 상위클래스에서 구현된 메서드들도 하위클래스에서 재정의 될 수 있습니다.
public abstract class Car {
	//1번
	public abstract void drive();
	public abstract void stop();
	//2번
	public void startCar() {
		System.out.println("시동을 켭니다.");
	}
	public void turnoff() {
	System.out.println("시동을 끕니다.");
	}
	//3번
	final public void run() {
		startCar();
		drive();
		stop();
		turnoff();
	} //템플릿메서드
}
  • 1번 : Car클래스를 생성하고 추상메서드인 drive()와 stop()을 작성하고 abstract를 각메서드와 클래스의 선언해줍니다.
  • 2번 : 상위 클래스에서 구현되어야할 stratCar()와 turnOff() 클래스를 작성합니다.
  • 3번 : 메서드들의 전체적인 흐름을 조직하는 run() 메서드를 작성하고 각 메서드들을 순서에 맞게 입력합니다. 이렇게 전체적인 흐름을 구성하는 메서드들 템플릿 메서드라고 하고 하위 클래스에서 재정의 할 수 없게 앞에 final을 붙여 줍니다.
public class AICar extends Car {

	@Override
	public void drive() {
		System.out.println("자율 주행합니다");
		System.out.println("자동차가 스스로 방향을 바꿉니다.");
	}

	@Override
	public void stop() {

		System.out.println("스스로 멈춥니다");
	}
}
  • AICar 클래스를 만들고 하위클래스에서 구체화 되어야 할 drive()와 stop()을 작성합니다.
public class MannualCar extends Car {
	@Override
	public void drive() {
		System.out.println("사람이 운전합니다.");
		System.out.println("사람이 핸들을 조작합니다.");
	}
	@Override
	public void stop() {
		System.out.println("브레이크를 밟아서 정지합니다.");
	}
}

 

  • MannualCar 클래스를 작성하고 drive()와 stop()을 작성합니다.
public class CarTest {

	public static void main(String[] args) {

		Car aiCar = new AICar();
		aiCar.run();
		System.out.println("=======================");
		Car mannualCar = new MannualCar();
		mannualCar .run();
	}
}
시동을 켭니다.
자율 주행합니다
자동차가 스스로 방향을 바꿉니다.
스스로 멈춥니다
시동을 끕니다.
=======================
시동을 켭니다.
사람이 운전합니다.
사람이 핸들을 조작합니다.
브레이크를 밟아서 정지합니다.
시동을 끕니다.
  • CarTest클래스를 만들고 AICar와 MannalCar 인스턴스를 상위클래스인 Car를 타입으로 하여 생성하고 run()메서드를 각각 사용하면 아래와 같은 결과물이 출력됩니다.
  • 이처럼 전체적인 흐름을 정해져 있지만 각 하위 클래스에 따라 달라져야 할 구체적인 내용을 추상메서드로 작성하여 구성하는 것을 템플릿 메서드라고 합니다.

훅메서드

	//1번
	public void washCar() {};
	//2번
	final public void run() {
		startCar();
		drive();
		stop();
		turnoff();
		washCar();
		
	}
  • 1번의 washCar()와 같이 구현부가 있지만 내용이 비어있는 메서드를 훅 메서드라고 합니다. 추상 메서드와는 다르게 에러가 나지 않고 하위 클래스에서 재정의 하지 않더라도 에러가 나지 않습니다.
  • 이런 훅메서드는 하위클래스에서 선택적인 오버라이딩을 할때 사용됩니다. 2번과 같이 washCar();을 run에 포함시키더라도 CarTest에 실행 결과에는 변한이 없습니다.
//AICar 클래스
	@Override
	public void washCar() {
		System.out.println("자동 세차 합니다.");
	}
    
    
시동을 켭니다.
자율 주행합니다
자동차가 스스로 방향을 바꿉니다.
스스로 멈춥니다
시동을 끕니다.
자동 세차 합니다.
  • AICar 클래스에 washCar 메서드를 재정의하고 CarTest 클래스에서 출력을 해보면 자동세차합니다가 추가된것을 알 수 있습니다. 
  • 이렇듯 훅메서드는 추상메서드와 같이 상위 클래스에서 정의되고 하위클래스에서 구현하는 역할을 하지만 훅메서드는 재정의를 하지 않아도 오류가 발생하거나 abstract를 선언해줄 필요가 없습니다.

final 예약어

  • final 변수는 값이 변할 수 없는 상수입니다. static과 함께 변하지 않는 값을 사용하고 싶을 때 많이 활용됩니다. 
  • firnal 메서드는 하위 클래스에서 재정의(overriding)할 수 없습니다. 또한 final 클래스는 상속 할 수 없습니다.

public static final 상수 값 정의 하여 사용하기

  • 프로젝트 구현시 시 여러 파일에서 공유해야 하는 상수 값은 하나의 클래스에 선언하여 사용하면 편리합니다.
  • 위의 예시 처럼 Define 이란 클래스에 public static final 을 선언하고 상수값을 입력하면 다른 클래스들에서 생성자를 생하지 않고 클래스명.상수명(Define.MIN,MAX..등)을 통해서 언제든지 상수를 불러와서 사용할 수 있게됩니다.

추상 클래스

 

  • 추상 클래스는 추상 메서드를 포함한 클래스를 말하고 추상 메서드는 구현부(body)가 없이 선언부만 있는 메서드입니다. 여기서 선언부는 반환값, 메서드 이름, 매개변수를 만합니다.
  • 추상 클래스는 abstract 예약어를 사용하고, new 생성자를 사용해 인스턴스화 할 수 없습니다. 인스턴스를 만들었을 때 메서드에 body가 없기에 해당 추상메서드를 호출해도 실행될 수 있는 부분이 없기 때문입니다.

실습

	//5번
public abstract class Computer {
	//2번
	public void testing() {};
	//3번
	public void display();
	//4번
	public abstract void typing();
		
	//1번
	public void turnOn() {
		System.out.println("컴퓨터 전원을 켭니다");
	}

	public void turnOff() {
		System.out.println("컴퓨터 전원을 끕니다");
	}

}
  • 1번 : trunOn/Off 메서드는 우리가 평소에 만들던 메서드로 선언부와 구현부가 모두 구현되어 있습니다.
  • 2번 : testing()은 중괄호'{}'안에 내용이 생략되어 있어 추상메서드라고 생각하기 쉽지만 추상메서드에는 중괄호 자체가 생각략됩니다. 그래서 추상메서드를 만들기 원한다면 중괄호를 지워줘야 합니다.
  • 3번 : display()는 선언부만 존재하는 메서드기 때문에 추상메서드의 조건을 만족하지만 에러가 발생합니다. 그때 4번 typing()과 같이 반환타입 앞에 abstract를 붙여주면 추상메서드로 인식을 하고 에러가 발생하지 않습니다.
  • 5번 : 클래스안에 추상메서드가 생성됨과 동시에 5번 클래스 선언부에도 에러가 발생하는데 추상메서드와 마찬가지로 class 앞에 abstract를 붙여줘서 추상메서드를 포함한 추상 클래스인 것을 알려주면 에러가 사라지게 됩니다.
  • 구현부가 없는 추상메서드를 작성하는 이유는 추상 클래스를 상속받는 하위 클래스에서 개별적으로 작성되야 하기 때문입니다. 위의 예제에 turnOn과 turnOff는 상속관계 전체에서 공통되게 사용될 수 있는 메서드이지만 display와 typing은 전체적으로 필요한 메서드이지만 각 개별 하위 클래스에서 작성될 내용입니다.
  • 정리해보면 추상클래스는 상위클래스 역할을 하고 상속관계내에서 전체적으로 필요하지만 개별적인 작성이 필요한 메서드를 추상메서드로 만들어 구현부를 하위클래스에서 작성하도록 하는 것 입니다.
	//1번
public class Desktop extends Computer {
	//2번
	public void display() {
		System.out.println("Desktop Display");
		
	}
	public void typing() {
		System.out.println("Desktop Typing");
	}

}
  • 1번 : Computer에 하위클래스를 만들기위해 Desktop 클래스를 작성하고 extends를 하면 Desktop에 에러가 발생합니다. 에러메세지에 커서를 올려보면 구현되지 않은 메서드를 구현하던지, 해당 클래스를 abstract로 선언하라고 메세지가 뜹니다. 만약 상속받은 2개의 추상메서드 중 하나만 구현하고 싶다면 여전히 해당 클래스에는 추상 메서드가 있다는 의미이기 때문에 abstract 클래스로 선언해야 합니다.
  • 2번 : 에러 메세지에서 메서드를 구현하기를 클릭하면 추상메서드들이 자동으로 구현됩니다. 비어있는 구현부를 위와 같이 채워줍니다.

 

  • 위의 그림은 클래스간의 관계도로 클래스는 네모로 표시하고 안에 줄을 긋고 그 클래스명과 내부 요소들을 구분합니다.
  • 추상클래스나 메서드는 이텔릭체로 표현합니다. Computer는 2개의 추상메서드를 가지고 있고 하위 클래스들에게 모두 상속됩니다
  • DeskTop처럼 모든 추상메서드를 구현한 클래스는 보통의 클래스가 되지만 NoteBook처럼 일부의 추상메서드만 구현한 클래스는 추상클래스가 됩니다.
public abstract class NoteBook extends Computer {


	@Override
	public void typing() {
		System.out.println("NoteBook Typing");
		
	}

}
  • 나머지 클래스들을 구현하기위해 NoteBook 클래스를 작성합니다. 해당 클래스에서는 typing()만 작성할 것이기 때문에 abstract 클래스를 선언해줍니다.
public class MyNoteBook extends NoteBook {

	public void display() {
		System.out.println("MyNoteBook Display");		
	}

}
  • NoteBook 클래스에 하위 클래스인 MyNoteBook 클래스를 작성합니다. 상위 클래스에서 이미 typing()메서드를 작성했기에 하위클래에서는 display()만 작성하면 abstract클래스가 아닌 온전한 클래스가 됩니다.
public class ComputerTest {

	public static void main(String[] args) {
		//1번
		//Computer computer = new Computer(); //에러발생
		//computer.display(); //표현할 내용없음
        //2번
        Computer computer = new Desktop();
		computer.display(); // DeskTop Display
		computer.turnOn();  // 컴퓨터 전원을 켭니다.
        //3번
        Computer computer2 = new MyNoteBook();
		NoteBook computer3 = new MyNoteBook();
        
        
  • 1번: 테스트를 위해 ComputerTest 클래스를 작성하고 Computer 인스턴스를 생성하려고 하면 에러가 발생합니다. .display()같이 추상메서드가 있다면 표현할 내용이 없기 때문입니다. 그래서 추상클래스는 생성을 하지 못합니다.
  • 2번: 상위 클래스인 Computer를 타입으로해서 Desktop인스턴스를 생성합니다. 그후 .display를 실행하면 당연히 Desktop클래스의 .display()가 실행됩니다. 업스케일을 하면 개별 하위 클래스에서 작성된 메서드는 상위 클래스 타입으로 생성된 인스턴스에서 사용이 불가하지만 이렇게 추상메서드로 작성해두면 상위 클래스를 자료형으로 해도 하위클래스에서 작성된 메서드를 실행할 수 있습니다.
  • 또한 TurnOn과 같이 상위 클래스에서 작성된 메서드는 당연히 상위클래스를 자료형으로 하는 인스턴스에서 사용할 수 있습니다.
  • 3번 : MyNoteBook 클래스는 두개의 상위 클래스 중 어떤 것이든 타입으로 하여 인스턴스를 생성할 수 있습니다.

정리 - 추상 클래스 사용하기

  • 추상 클래스는 주로 상속의 상위클래스로 사용되어 하위 클래스가 구현해야할 추상 메서드를 포함하고 있습니다. 
  • 이는 상위클래스를 타입으로하여 하위클래스의 인스턴스를 만들 때 상위클래스에 구현되지 않은 메서드도 사용할 수 있게 하여 여러개의 하위클래스의 인스턴스를 하나의 자료형으로 관리 할 수 있다는 장점이 있습니다.
  • 추상 클래스에서 구현된 메서드는 하위클래스에서 공통으로 사용할 수 있고 필요에 따라 하위 클래스에서 재정의 될 수 있습니다.

배열을 활용하여 구현하기

  • 고객은 현재 5명입니다. VIP 1명, GOLD2명, SILVER 2명 일 때, 각 고객이 10000원 짜리 제품을 구매한 경우 지불한 금액과 적립된 보너스 포인트를 출력해보세요.
  • ArrayList를 활용하여 구현해봅니다.
//VIPCustomer클래스
//2번
public VIPCustomer(int customerId, String custoemrName, int agentID) {
		super(customerId, custoemrName);
		salesRatio = 0.1;
		bonusRatio = 0.05;
		customerGrade = "VIP";
		this.agentID=agentID;
	}
//1번
@Override
	public String showCustomerInfo() {
		return super.showCustomerInfo() + "담당 상담자 번호는 "+agentID+"입니다";
	}

 

  • VIPCustomer 클래스에 agentID변수를 활용하기 위해서 showCustomerInfo를 오버라이딩하여 출력문을 추가합니다. 이전 return값이 출력문이기에 뒤에 +를 통해 출력문을 이어서 작성합니다.
  • VIPCustomer 인스턴스를 생성할때 agentID를 함께 입력받기 위하여 생성자를 수정합니다.
import java.util.ArrayList;

public class CustomerTest {
	
	public static void main(String[] args) {
		//1번
		Customer customerKim = new Customer(10010,"김유신");
		Customer customerLee = new Customer(10020,"이바다");
		Customer customerPark = new GoldCustomer(10030,"박유식");
		Customer customerChoi = new GoldCustomer(10040,"조은빈");
		Customer customerNa = new VIPCustomer(10050,"나안해",12345);
		//2번
		ArrayList<Customer> customerList = new ArrayList<Customer>();
		customerList.add(customerKim);
		customerList.add(customerLee);
		customerList.add(customerPark);
		customerList.add(customerChoi);
		customerList.add(customerNa);
		//3번
		System.out.println("===========고객정보출력===========");
		for(Customer customer : customerList) {
			System.out.println(customer.showCustomerInfo());
		}
		//4번
		System.out.println("===============물건 구입 후 정보 ===========");
		int price = 10000;
		for(Customer customer : customerList) {
			int afterPrice = customer.calPrice(price);
			System.out.println(customer.getCustomerName() +"님이 현재"+afterPrice+"원 지불하셨습니다.");
			System.out.println(customer.showCustomerInfo());
		}
		
	}

}
===========고객정보출력===========
김유신님의 등급은 silver이며, 적립된 보너스 포인트는 0점 입니다. 
이바다님의 등급은 silver이며, 적립된 보너스 포인트는 0점 입니다. 
박유식님의 등급은 Gold이며, 적립된 보너스 포인트는 0점 입니다. 
조은빈님의 등급은 Gold이며, 적립된 보너스 포인트는 0점 입니다. 
나안해님의 등급은 VIP이며, 적립된 보너스 포인트는 0점 입니다. 담당 상담자 번호는 12345입니다
===============물건 구입 후 정보 ===========
김유신님이 현재10000원 지불하셨습니다.
김유신님의 등급은 silver이며, 적립된 보너스 포인트는 100점 입니다. 
이바다님이 현재10000원 지불하셨습니다.
이바다님의 등급은 silver이며, 적립된 보너스 포인트는 100점 입니다. 
박유식님이 현재9000원 지불하셨습니다.
박유식님의 등급은 Gold이며, 적립된 보너스 포인트는 200점 입니다. 
조은빈님이 현재9000원 지불하셨습니다.
조은빈님의 등급은 Gold이며, 적립된 보너스 포인트는 200점 입니다. 
나안해님이 현재9000원 지불하셨습니다.
나안해님의 등급은 VIP이며, 적립된 보너스 포인트는 500점 입니다. 담당 상담자 번호는 12345입니다
  • 1번 : 테스트를 위해 CustomerTest 클래스를 생성하고 5명의 고객의 인스턴스를 생성합니다.
  • 2번 : 5명의 고객을 배열에 담기 위해 ArrayList를 생성하고 .add메서드를 통해서 배열에 담습니다.
  • 3번: 고객정보 출력을 위해 향상된for문을 사용하여 배열의 값을 하나씩불러와 .showInfo메서드를 사용해서 고객정보를 출력합니다.
  • 4번: 물건값 10000원을 price 변수에 대입하고 향상된 for문을 통해 calPrice 메서드를 사용해서 고객들의 구매과정을 처리한 후 다시한번 showInfo를 통해 고객정보를 출력합니다.

상속, 오버라이딩, 배열, 향상된for문을 통하여서 다형성을 구현하여 구매처리와 고객정보 출력을 효율적으로 할 수 있었습니다.

하위 클래스로 형 변환, 다운캐스팅

  • 묵시적으로 상위 클래스 형변환된 인스턴스가 원래 자료형(하위클래스)으로 변환되어야 할 때 다운캐스팅이라고 합니다.
  • 아래의 예와 같이 묵시적으로 이루어진 상위클래스와는 다르게 하위 클래스로의 형 변환은 명시적으로 되어야 합니다.

   예) Customer vc = new VIPCustomer(); //묵시적 
   VIPCustomer vCustomer = (VIPCustomer)vc;//명시적

 

실습

	//1번
    //Human클래스
    public void readBook() {
		System.out.println("사람이 책을 읽습니다");
	}
    //Tiger클래스
    public void move() {
		System.out.println("호랑이가 네발로 뜁니다");
	}
    //Eagle 클래스
    public void flying() {
		System.out.println("독수리가 두날개를 쭉뻗고 납니다");
	}
    	//2번
   		Animal hAnimal = new Human();
		Animal tAnimal = new Tiger();
		Animal eAnimal = new Human();
		
		Human human = (Human)hAnimal;
		human.readBook(); //사람이 책을 읽습니다
        //3번
        //Eagle eagle = (Eagle)hAnimal; //오류
       	//4번
        if(hAnimal instanceof Eagle) {
			Eagle eagle = (Eagle)hAnimal; //아무것도알이어남
		}

		if(hAnimal instanceof Human) {
			Human human = (Human)hAnimal; 
			human.readBook(); 사람이 책을 읽습니다
		}
  • 1번 : Animal 클래스에서 Human, Tiger, Eagle 클래스에 각각 readbood, hunting, flying 메서드를 추가합니다.
  • 2번:  위에서 작성한 메서드를 사용하기 위해 생성된 인스턴스 중 hAnimal을 다운캐스팅하기 위해 Human 변수를 생성하고 그후 (Human)을 캐스팅해서 hAnimal을 Animal에서 Human으로 다운캐스팅하고 Human의 메서드인 .readbook을 사용헤보니 출력이 잘되는 것을 확인할 수 있습니다.
  • 3번 : 이번엔 Human 클래스인 hAnimal에 Egale로 다운캐스팅을 하고 실행을하면 오류가 발생합니다\
  • 4번: 3번과 같은 오류를 방지하기 위해 if문에 instanceof를 사용해 해당 인스턴스가 Eagle이 맞으면 다운캐스팅을 하고 아니면 아무일도 하지 않도록 했더니 flase가 반환되 아무일도 일어나지 않았습니다. 이후 Human클래스로 바꾸어서 다시 작성하니 readBook메서드가 실행되는 것을 확인할 수 있습니다.
  • 이처럼 instanceof는 어떤 인스턴스가 해당 클래스가 맞는지 유효성 검사를 해주어 복잡한 코드에서 에러가 나는 것을 방지해주는 역할을 합니다.
	//AnimalMoving 클래스
    public void testDeownCasting(ArrayList<Animal> list) {
		//1번
		for(int i = 0 ; i <list.size(); i++) {
			Animal ani = list.get(i);
		//2번
			if(ani instanceof Human ) {
				Human human = (Human)ani;
				human.readBook();
			} else if (ani instanceof Tiger ) {
				Tiger tiger = (Tiger)ani;
				tiger.hunting();
			} else if (ani instanceof Eagle ) {
				Eagle eagle = (Eagle)ani;
				eagle.flying();
			} else {
				System.out.println("error");
			}
		}
	}
    
    public static void main(String[] args) {
		//3번
		Animal hAnimal = new Human();
		Animal tAnimal = new Tiger();
		Animal eAnimal = new Human();
		
		ArrayList<Animal> animalList = new ArrayList<Animal>();
		animalList.add(hAnimal);
		animalList.add(tAnimal);
		animalList.add(eAnimal);
		
		AnimalMove test = new AnimalMove();
		//4번
		test.testDeownCasting(animalList);
        /*사람이 책을 읽습니다
		호랑이가 사냥을 합니다
		사람이 책을 읽습니다*/
  • 테스트를 위해 AnimalMoving 클래스에 testDownCasting 메서드를 작성하고 매개변수로 Animal자료형의 ArryList를 입력받습니다
  • 1번 : 반복문을 통해 list에 있는 값을 ani변수에 하나씩 대입합니다.
  • 2번 : if문을 통해 ani의 클래스가 Human, Eagle, Tiger 중에 어떤 클래스인지 찾고 해당 클래스가 맞다면 다운캐스팅 한 이후 각자의 메서드를 사용하도록 합니다.
  • 3번 : main 메서드로 돌아와 생성된 인스턴스들을 ArrayList에 넣고 사용하기 testDownCasting메서드를 사용하기 위해 AnimalMove인스턴스를 생성합니다.
  • 4번: testDownCasting()메서드를 사용하고 매개변수로 animalList를 넣고 실행하니 아래의 결과값이 출력되는 것을 확인 할 수 있습니다.
  • 이처럼 instanceof를 활용하여 에러가 나는 것을 방지하면서 다운캐스팅을 하는 방법을 배웠습니다.

정리 해보면 상위 클래스에서 오버라이딩을 해서 여러 하위 클래스에서 동시에 사용할 수 있는 메서드의 경우는 오버라이딩을 해서 사용하는게 효율적이지만 그렇지 않은 개별적인 메서드의 경우는 안전하게 instanceof를 사용해서 다운캐스팅을 통해 사용하는게 좋다는걸 알 수 있습니다.

다형성(ploymorphism)이란?

  • 다형성은 하나의 코드가 여러 자료형으로 구현되어 실행되는 것을 뜻하며 이는 같은 코드에서 여러 실행 결과가 나올 수 있는 것을 말합니다.
  • 정보은닉, 상속과 더불어 객체지향 프로그래밍의 가장 큰 특징 중 하나인 다형성은 객체지향 프로그래밍의 유연성, 재활용성, 유지보수성에 기본이 되는 특징입니다.

실습

//1번
class Animal {
	
	public void animalMove() {
		System.out.println("동물이 움직입니다");
	}
}

class Human extends Animal {
	
	public void animalMove() {
		System.out.println("사람이 두발로 걷습니다");
	}
}
class Tiger extends Animal{
	
	public void animalMove() {
		System.out.println("호랑이가 네발로 뜁니다");
	}
}
class Eagle extends Animal{
	
	public void animalMove() {
		System.out.println("독수리가 하늘을 납니다");
	}
}

public class AnimalMove {

	public static void main(String[] args) {
		//2번
		Animal hAnimal = new Human();
		Animal tAnimal = new Tiger();
		Animal eAnimal = new Human();
		//4번
		AnimalMove test = new AnimalMove();
		test.moveAnimal(hAnimal);//사람이 두발로 걷습니다
		test.moveAnimal(tAnimal);//호랑이가 네발로 뜁니다
		test.moveAnimal(eAnimal);//사람이 두발로 걷습니다

	}
	//3번
	public void moveAnimal(Animal animal) {
		
		animal.animalMove();
	}
}
  • 다형성을 실습해보기 위해 AnimalMove라는 클래스를 생성합니다.
  • 1번 : Animal 클래스를 만들고 그안에 animalMove라는 메서드를 작성합니다. 그 후 Animal 클래스를 상속하는 3개의 클래스를 더 만들고 그 안에 각각클래스 이름에 맞게 animalMove메서드를 override하여 작성합니다.
  • 2번 : 메인 메서드로 와서 위에서 생성한 3개의 하위클래스를 Animal클래스에 업캐스팅하여 생성합니다.
  • 3번 : AnimalMove 클래스에 moveAnimal 메서드를 작성하고 매개변수를 Animal타입으로 하여 animalMove메서드를 실행하도록 합니다.
  • 4번: moveAnimal 메서드를 실행하기 위해 AnimalMove 인스턴스를 생성하고 각각의 하위 클래스를 매개변수로 하여 moveAnimal을 실행하면 결과 값처럼 같은 메서드에서 다른 결과가 나오는 것을 알 수 있습니다. 
  • 이와 같이 하나의 코드에 여러 자료형이 구현되어서 다른 실행이 이루어지는 것이 다형성입니다. 다형성을 이용해서 여러개로 작성될 코드를 하나로 묶어서 효율적으로 코드를 작성할 수 있다는 이점이 있습니다.
		ArrayList<Animal> animalList = new ArrayList<Animal>();
		animalList.add(hAnimal);
		animalList.add(tAnimal);
		animalList.add(eAnimal);
		
		for(Animal animal : animalList) {
			animal.move();
		}
		//사람이 두발로 걷습니다
		//호랑이가 네발로 뜁니다
		//사람이 두발로 걷습니다
  • ArryList를 사용하여 다형성을 구현해 보기 위해 Anmial을 자료형으로하는 animaList를 만듭니다.
  • .add 메서드로 각 클래스를 animalList에 넣고 향상된 for문을 사용해 하나씩 돌아가며 .move()메소드를 사용하게 하면 위의 예제와 같은 결과를 얻을 수 있습니다. 이때의 move는 각 인스턴스의 move가 사용된 것입니다.
  • 다형성은 다양한 개념이 결합되어 구현됩니다. 한 클래스의 기능을 구체적으로 확장한 상속, 상속관계에 의해 묵시적으로 일어나는 의한 업캐스팅, 상속된 메서드를 재정의하는 오버라이딩, 오버라이딩 된 메서드는 업캐스팅 하더라도 인스턴스의 메서드로 실행한다는 가상함수 그리고 이 모든 것을 통해 하나의 코드로 다양한 자료형을 실행할 수 있는 다형성을 한 맥락에서 이해하고 활용할수 있게 되는 것을 목표로 해야합니다.

다형성을 사용함으로써 갖는 장점

  • 다형성을 이용하여 다양한 여러 클래스를 하나의 자료형(상위 클래스)으로 선언하거나 형변환 하여 각 클래스가 동일한 메서드를 오버라이딩 한 경우, 하나의 코드가 다양한 구현을 실행 할 수 있습니다.
  • 다형성을 통해서 유사한 클래스가 추가되는 경우 유지보수에 용이하고 각 자료형 마다 다른 메서드를 호출하지 않으므로 코드에서 많은 if문이 사라지는 장점이 있습니다.
public class GoldCustomer extends Customer {

	public double salesRatio;
	
	public GoldCustomer() {
		
		bonusRatio = 0.02;
		salesRatio = 0.1;
		customerGrade = "Gold";
	}
	
	@Override
	public int calPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price - (int)(price * salesRatio);
	}
  • VipCustomer에 이어 GoldCustomer를 추가하려고 할때 상속과 오버라이딩을 통해 손쉽게 추가할 수 있습니다. 
  • 이후 Customer 클래스를 통해 하위클래스들을 쉽게 관리할 수 있다는 장점이 있습니다.

상속을 언제 사용할까?

IS-A관계(is a relationship : inheritance)

  • IS - A관계는 일반적인(general)개념과 구체적인(specific)개념과의 관계를 뜻합니다. 한 클래스와 다른 클래스가 IS-A관계 일 때 상속을 사용합니다.
  • 상위 클래스는 일반적인 개념 클래스이고(예 포유류) 하위 클래스는 구체적인 개념 클래스(예: 사람, 원숭이, 고래..)에 속합니다.
  • 클래스간에 상속관계가 만들어지면 클래스간에 관계가 타이트해져서 상위 클래스에 변화를 주면 하위클래스에 영향을 주게 됩니다. 그렇기에 단순히 코드를 재사용하는 목적으로 상속을 사용하지는 않습니다.

HAS-A관계(composition): 

  • 반면에 HAS-A관계는 한 클래스가 다른 클래스를 소유한 관계로 코드 재사용의 한 방법입니다. 
  • 만약 우리가 ArrayList클래스를 사용하고 싶다면 ArrayList extends하여 상속해서 사용 하는 것이 아닌 인스턴스를 하나 생성해서 사용하는 것 같이 필요한 코드를 재사용을 위해선 생성해서 사용하면 됩니다.

하위 클래스에서 메서드 재정의 하기

오버라이딩(overriding)

  • 상위 클래스에서 정의된 메서드의 구현 내용이 하위 클래스에서 구현할 내용과 맞지 않는 경우 하위 클래스에서 동일한 이름의 메서드를 재정의 할 수 있고 그것을 오버라이딩 이라고 합니다.
  • 예제의 Customer 클래스의 calPrice()와 VIPCustomer 의 calPrice()구현내용은 할인율과 보너스 포인트 적립내용부분의 구현이 다릅니다. 따라서 VIPCustomer 클래스는 calPrice()메서드를 재정의 할 필요가 있습니다.
  • 오버라이드를 하기 위해 오른쪽 클릭 - Source - Overrride/Implemnet Method...를 클릭하면 변수나 메서드를 선택할 수 있고 calPrice() 메서드를 선택해 생성하면 이전 클래스의 값을 return하는 메서드가 생성됩니다.
  • 메서드를 아래와 같이 수정해 Customer의 carlPrice()와 다른역할을 하는 메서드로 재정의 할 수 있습니다. 
	//VIPcustomer 클래스
    @Override
	public int calPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price - (int)(price * salesRatio);
	}
  • @Override는 에너테이션(주석)으로 재정의된 메서드라는 의미로 선언부가 기존의 메서드와 다른 경우 에러가 발생 합니다.
  • 에노테이션은 컴파일러에게 특정한 정보를 제공해주는 역할을 하고 여기서는 재정의된 메서드라는 정보를 제공합니다.

형변환과 오버라이딩 메서드 호출

Customer vc = new VIPCustomer();

vc.calPrice(10000);

  • 위의 코드에서 calPrice()메서드는 Customer클래스의 메서드가 호출될 것이라고 생각하기 쉽지만 VIPCustomer클래스의 메서드가 호출됩니다.
  • Java에서는 항상 인스턴스의 메서드(여기서는 VIPCustomer)가 호출되고 이것을 가상 메서드 기법이라고 합니다.

실습

package inheritance;

public class OverrideTest {

	public static void main(String[] args) {
		
		Customer Lee = new Customer();
		Lee.setCustomerId(10010);
		Lee.setCustomerName("이순신");
		Lee.bonusPoint= 1000;
		//1번
		int priceLee = Lee.calPrice(10000);
		System.out.println(Lee.showCustomerInfo()+"지불 금액은 "+priceLee+"원 입니다.");
//이순신님의 등급은 silver이며, 적립된 보너스 포인트는 1100점 입니다. 지불 금액은 10000원 입니다.

		
		VIPCustomer Kim = new VIPCustomer();
		Kim.setCustomerId(10020);
		Kim.setCustomerName("김유신");
		Kim.bonusPoint = 10000;
		//2번		  
		int priceKim =Kim.calPrice(10000);

		System.out.println(Kim.showCustomerInfo()+"지불 금액은 "+priceKim+"원 입니다.");
//김유신님의 등급은 VIP이며, 적립된 보너스 포인트는 10500점 입니다. 지불 금액은 9000원 입니다.
	}

}
  • OverrideTest 클래스를 만들고 CustomerTest에 내용을 복사해 옵니다.
  • 1번 : Lee가 10000원짜리 물건을 샀다고 가정하고 calPrice메서드를 사용해 priceLee에 price를 대입합니다. 그 후 결과값을 출력문으로 뽑는다면 아래의 결과문 처럼 출력이 됩니다.
  • 2번 : Kim도 같은 물건을 사고 calPrice를 출력해보면 Lee의 것과는 다르게 지불금액이 출력된 것을 확인 할 수있습니다. 이것으로 우리는 priceKim에 calPrice가 Override된것을 알 수 있습니다.
		//테스트 클래스
        Customer Na = new VIPCustomer();
		Na.setCustomerId(10030);
		Na.setCustomerName("나몰라");
		Na.bonusPoint = 10000;
		int priceNa =Na.calPrice(10000);
		System.out.println(Na.showCustomerInfo()+"지불 금액은 "+priceNa+"원 입니다.");
        //나몰라님의 등급은 VIP이며, 적립된 보너스 포인트는 10500점 입니다. 지불 금액은 9000원 입니다.
  • 위의 코드처럼 상위 메서드인 Customer를 자료형으로 해서 하위 클래스인 VIPCustomer의 인스턴스를 생성한뒤 calPrice메서드를 사용하여 결과값을 보면 Customer클래스의 calPrice가 아닌 VIPCustomer의 calPrice가 사용 된 것을 알 수있습니다. 
  • 앞에서 하위 클래스에서 상위 클래스로의 묵시적 형변환이 일어난 경우 상위 클래스의 변수와 메서드만 사용가능한 것과는 다르게 하위 클래스에서 오버라이딩된 메서드가 사용되는 것을 가상메서드라고 합니다.

가상 메서드(virtual method)

함수 혹은 메서드는 이름이 그 자체로 주소역할을 하기 때문에 같은 이름을 가진 다른 메서드가 존재할 수 없지만 override를 하는 경우 같은 이름의 다른 주소값을 가진 메서드가 재정의 되기 때문에 같은 이름의 다른기능을 하는 메서드를 가질 수 있습니다. 이처럼 override를 할 수 있는 메서드를 가상 메서드라고 합니다.

 

위의 그림과 같이 calcPrice는 이름이 같지만 재정의된 다름함수가 존재하고 그 주소값이 다릅니다. 이럴 때 실제적으로 호출되는 calPrice는 자료형(타입)기준이 아닌 생성된 인스턴스를 기준으로 호출이 되는 것을 가상 메서드 기법이라고 합니다.

 

정리해보면 Customer vd = new VIPCustomer(); 일 때 재정의가 안됬을 경우 Customer의 calPrice가 호출되지만 재정의가 됬을 경우 VIPCustoemr의 calPrice가 호출 됩니다.

하위 클래스를 생성하는 과정

 

  • 상속 관계에서 하위 클래스가 생성 될 때 상위 클래스가 먼저 생성 됩니다. 이는 하위 클래스에서 상위 클래스의 변수나 메서드를 사용기 위해서는 상위클래스를 생성할 필요하기 때문입니다. 
  • 생성되는 순서는 상위 클래스의 생성자가 호출되고 하위 클래스의 생성자가 호출됩니다. 위에서 얘기했듯이 하위 클래스의 생성자에게는 무조건 상위 클래스의 생성자가 호출되어야 합니다.
  • 아래의 예와 같이 각각의 클래스의 생성자에 클래스가 생성될 때 마다 호출문을 출력하게 해서 Vip 클래스를 생성하면 가장아래의 테스트 결과 처럼 상위클래스인 Customer 클래스가 먼저 생성되고 VIPCustomer클래스가 생성되는 것을 확인할 수 있습니다.
 //Customer 클래스
 public Customer() {
		 customerGrade = "silver";
		 bonusRatio = 0.01;
		 
		 System.out.println("Customer() 생성자 호출");
	 }
     
 //VIPcustomer 클래스
 	public VIPCustomer () {
		
		salesRatio = 0.1;
		bonusRatio = 0.05;
		customerGrade = "VIP";
		
		System.out.println("VIPCustomer() 생성자 호출");
	}
 //테스트 클래스
 	VIPCustomer Kim = new VIPCustomer();
		Kim.setCustomerId(10020);
		Kim.setCustomerName("김유신");
		Kim.bonusPoint = 10000;
		System.out.println(Kim.showCustomerInfo());
        //Customer() 생성자 호출
        //VIPCustomer() 생성자 호출
		//김유신님의 등급은 VIP이며, 적립된 보너스 포인트는 10000점 입니다
  • 하위 클래스에서 상위 클래스의 생성자를 호출하는 코드가 없는 경우 컴팡이러는 상위클래스 기본 생성자를 호출하기 위한 super()를 추가합니다. super()는 생성자를 호출하는 this()와 비슷한 역할을 하는데 자기자신의 생성자를 호출하는 역할을 합니다.
  • 위의 코드에서 상위 클래스를 호출하지 않았는데도 자동으로 호출이 된것은 컴파일을 할 때 Super()라는 문구가 Customer 클래스에 자동으로 생성되었기 때문입니다.
  • super()로 호출되는 생성자는 상위클래스의 기본생성자입니다. 만약 상위 클래스의 기본생성자가 없는 경우(매개변수가 있는 생성자만 존재 하는 경우) 하위 클래스는 명시적으로 상위 클래스의 생성자를 호출해야 합니다.
  • 다시 한번 위의 Customer클래스의 생성자를 살펴보면 매개변수가 없는 기본생성자임을 확인할 수 있고 그렇기 때문에 super()가 호출이 되었던 것 입니다.
	 //Customer 클래스     
     public Customer(int customerId, String custoemrName) {
		 this.customerId=customerId;
		 this.customerName=custoemrName;
		 customerGrade = "silver";
		 bonusRatio = 0.01;
		 
		 System.out.println("Customer() 생성자 호출");
	 }
     //VIPCustoemr 클래스
     	public VIPCustomer(int customerId, String custoemrName) {
		super(customerId, custoemrName);
		salesRatio = 0.1;
		bonusRatio = 0.05;
		customerGrade = "VIP";
		System.out.println("VIPCustomer() 생성자 호출");
	}
    //테스트 클래스
    	VIPCustomer Kim = new VIPCustomer(10020,"김유신");
		//Kim.setCustomerId(10020);
		//Kim.setCustomerName("김유신");
		Kim.bonusPoint = 10000;
		System.out.println(Kim.showCustomerInfo());
		//Customer() 생성자 호출
		//VIPCustomer() 생성자 호출
		//김유신님의 등급은 VIP이며, 적립된 보너스 포인트는 10000점 입니다
  • 위의 코드의 Customer 클래스의 생성자처럼 기본생성자가 아닌 매개변수가 존재하는 생성자만 Customer클래스의 있을 때는 아래의 VIPCustomer클래스의 생성자처럼 super()를 통해 Customer 생성자를 수동으로 생성해주어야 하위 클래스를 사용할 수 있습니다. 
  • 다시 테스크 클래스로 돌아가 customerId와 customerName을 입력하면 이전과 같은 결과를 얻을 수 있습니다.

상속에서의 메모리 상태

아래의 그림처럼 메모리 상으로도 하위 클래스를 생성했을 때 상위클래스의 인스턴스가 먼저 생성되고 하위클래스 인스턴스가 생성이 됩니다.

상위 클래스로의 묵지적 형 변환(업캐스팅)

  • 하위 클래스는 상위 클래스의 타입을 내포하고 있음으로 위의 그림과 같이 상위 클래스 형으로 변수를 선언하고 하위 클래스 인스턴스를 생성 할 수 있습니다. 
  • 이는 상위클래스의 묵시적 형변환(특별한 처리없이 형변환이 되는 것)이 일어나는 것으로 상속관계에서 모든 하위 클래스는 상위클래스로 묵시적 형 변환이 가능합니다.
  • 이때 참조변수 vc가 가리킬 수 있는 범위는 Customer클래스의 멤버 변수와 메서드로 제한됩니다. 이는 VIPCustomer의 인스턴스가 선언되어 확장된 멤버 변수와 메서드는 메모리에 잡혀있지만 Customer를 자료형으로 하기 때문에 vc에서 실제 사용할 수 있는 범위도 Customer의 변수와 메서드로 제한이 되는 것입니다.
  • 하지만 그 역인 상위 클래스가 하위클래스로 묵시적 형변환이 일어나지는 않습니다.

클래스 계층구조가 여러 단계인 경우

  • 위의 그림 처럼 계층구조가 여러개여도 하위 클래스는 상위 클래스를 변수로 선언하고 인스턴스를 생성할 수 있습니다. 즉 상속 단계의 상관없이 하위클래스에서 상위클래스로에 묵시적 형변환 일어나는 것을 알 수 있습니다.

클래스에서 상속의 의미

 

  • 상속은 객체 지향프로그래밍에 특징 중에 하나로 어떤 클래스보다 좀 더 확장된 기능을 구현하고 싶을 때 새로 클래스를 구현하는게 아니라 이미 구현된 클래스를 상속받아서 속성이나 기능 확장시킨 클래스를 구현하는 것을 말합니다.
  • 코드의 재사용의 개념이 아닌 기존의 클래스를 가져다가 좀더 확장된 기능을 만드는 것을 뜻하고 이질적인 기능을 하는 클래스간에는 상속의 개념을 사용할 수 없습니다.
  • 상속하는 클래스는 상위 클래스, parent class, base calss, super class라는 용어를 사용합니다.
  • 상속받는 클래스는 하위 클래스, child class, de rived class, sub class라는 용어를 사용합니다.
  • B가 A로부터 상속받는 경우 B class exteds A{                    }라고 표현하고 Java에서는  A의 자리의 하나의 클래스만 들어갈 수 있는 single inheritance만 가능합니다.
  • 아래 그림과 같이 박스로 많이 표현하고 화살표의 방향이 상속받는 쪽이아닌 상속하는 쪽을 향하게 해서 화살표 내부를 비워놓고 표현하는 경우가 많습니다.

상속을 사용하는 경우

 

  • 상위 클래스는 하위클래스보다 일반적인 개념과 기능을 가지고 하위클래스는 상위 클래스보다 구체적인 개념과 기능을 가집니다.
  • 포유류 <- 사람의 경우에서 포유르는 더 포괄적인 개념으로 사람뿐만아니라 침팬지, 고릴라, 팬더 등 다른 포유류들로 분화될 수 있는 것 같이 일반적인 상위클래스는 구체적인 여러 하위 클래스들로 세분화 될 수 있습니다.
  • 위의 관계를 코드로 나타내면 Class Mammal {               }  class Human extends Mammal {                  }와 같습니다.

상속을 사용하여 고객관리 프로그램 구현하기

  • 고객에 등급에 따라 차별화된 서비스를 제공할 수 있습니다.
  • 고객의 등급에 따라 할인율, 적립금이 다르게 적용됩니다.
  • 이러한 경우에 대하여 구현을 해보도록 합시다.

Custome클래스의 조건

public class Customer {
	//1번
	private String customerId;
	private String customerName;
	private String customerGrade;
	 int bonusPoint;
	 double bonusRatio;
	 //2번
	 public Customer() {
			
			customerGrade = "silver";
			bonusRatio = 0.01;
		}
	 //3번
	 public int calPrice (int price) {
		 
		 bonusPoint += price * bonusRatio;
		 
		 return price;
	 }
	 //4번
	 public String showCustomerInfo( ) {
		  return customerName + "님의 등급은 "+customerGrade+"이며, 적립된 보너스 포인트는 "+bonusPoint+"점 입니다";
	 }
	//5번
	public String getCustomerId() {
		return customerId;
	}
	public void setCustomerId(String customerId) {
		this.customerId = customerId;
	}
	public String getCustomerName() {
		return customerName;
	}
	public void setCustomerName(String customerName) {
		this.customerName = customerName;
	}
	public String getCustomerGrade() {
		return customerGrade;
	}
	public void setCustomerGrade(String customerGrade) {
		this.customerGrade = customerGrade;
	}
}
  • 1번 : Customer 클래스를 생성하고 조건에 맞는 멤버 변수들을 먼저 선언해 줍니다. 
  • 2번 : Customer 생성자를 만들면서 cutomerGrade와 bonusRatio에 초기화하는 값을 입력합니다. 보통 생성자를 통해서 초기값을 입력하게 됩니다.
  • 3번 : calPrice를 메서들 만들어 price를 입력하면 bonusPoint를 계산해서 넣고 다시 price를 retrun하도록 합니다.
  • 4번 : showCustoemrInfo를 만들어서 고객의 현재 정보를 출력할 수 있도록 합니다.
  • 5번: private 멤버변수를 읽고 쓰기 위한 getter/setter 를 만들어 줍니다.

Customer를 상속받아 구현하는 VIPCustoemr 클래스

VIPCustomer 클래스의 기능

: 제품 구매시 10% 할인

  보너스 포인트 5% 적립

  담당 상담원 배정

=> Customer 클래스와 유사하지만, 그보다 더 많은 속성과 기능을 필요

그림으로 표현하면 다음과 같다

 

//1번
public class VIPCustomer extends Customer {

	public int agentID;
	public double salesRatio;
	
	//2번
	public VIPCustomer () {
		
		salesRatio = 0.1;
		bonusRatio = 0.05;
		customerGrade = "VIP";
	}
}
  • VIPCustomer 클래스는 Customer 클래스의 멤버 변수와 메서드를 사용하면서 추가적인 기능을 하기 때문에 Custoemr class 란에 extends를 써서 Customer를 상속받도록하고 추가된 멤버 변수만 넣어 줍니다.
  • 2번 상속자에 Vip클래스에서 바뀐 초기값들을 넣어주면 Cusomer클래스에서 가져온 custoemerGrade가 오류가 나는데 접근제한자가 private으로 되어있기 때문입니다. 이때 사용할 수 있는 것이 protected 접근제한자로 외부에선 접근을 막지만 하위 클래스에선 접근이 가능하도록 해주는 역할을 하기 때문입니다.
public class Customer {
	
	protected String customerId;
	protected String customerName;
	protected String customerGrade;
  • Customer 클래스의 멤버 변수를 다음과 같이 바꾸어 주면 오류가 사라지고 사용이 가능해집니다.

접근제어자(access modifier)의 가시성

잠시 접근제어자에 대해 정리하고 넘어가자면 public > protected > 선언되지않음(default) > private순으로 공개되는 범위가 넓어지는 것을 알 수 있습니다.

 

테스트 시나리오

  • 일반고객 1명과 VIP고객 1명이 있습니다. 일반 고객의 이름은 이순신, 아이디는 10010, 보너스 포인트는 1000점입니다.
  • VIP 고객의 이름은 김유신, 아이디는 10020, 보너스 포인트는 10000점 입니다. 두 고객을 생성하고 이에 대한 고객 정보를 출력해 보세요.
public class CustomerTest {

	public static void main(String[] args) {
		//1번
		Customer Lee = new Customer();
		Lee.setCustomerId(10010);
		Lee.setCustomerName("이순신");
		Lee.bonusPoint= 1000;
		System.out.println(Lee.showCustomerInfo());
		//이순신님의 등급은 silver이며, 적립된 보너스 포인트는 1000점 입니다

		//2번
		VIPCustomer Kim = new VIPCustomer();
		Kim.setCustomerId(10020);
		Kim.setCustomerName("김유신");
		Kim.bonusPoint = 10000;
		System.out.println(Kim.showCustomerInfo());
		//김유신님의 등급은 VIP이며, 적립된 보너스 포인트는 10000점 입니다
	}
}
  • 1번 : 일반 고객 Customer Lee를 생성하고 protected 변수는 set메서드를 사용해서 갑을 입력하고 아닌것은 바로 대입해줍니다. showCustoemrInfo 메서드를 출력하면 등급이 silver로 잘나오는 것을 알 수 있습니다.
  • 2번 : VIP 고객 VIPCustomer Kim을 생성하고 똑같이 입력해 줍니다. 마찬가지로 shoCustomerInfo를 사용해서 출력하면 VIP등급의 고객이 출력되는 것을 확인할 수 있습니다.
  • 이처럼 상속받은 클래스에서 상위 클래스에 변수와 메서드를 사용할 수 있는 것을 확인해보았습니다.

ArrayList를 활용한 응용 프로그램

  • 어느학교에 학새잉 3명이 있고 각 학생마다 읽은 책을 기록하고 있습니다.
  • Student 클래스를 만들고 각 학생마다 읽은 책을 Student클래스 내에 ArrayList를 생성하여 관리하도록 합니다.
  • 다음과 같이 출력 되도록 Student, Book, StudentTest 클래스를 만들어 실행하세요

 

public class Book {
	
	public String title;
	public String author;
	
	public Book (String title,String author) {
		this.title=title;
		this.author=author;
		
	}

		public void setTitle(String title) {
		this.title = title;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public String getTitle() {
		
		return title;
	}

}
import java.util.ArrayList;

public class Student {
	
	public String studentName;
	ArrayList<Book> bookList;
	
	public Student(String studentName) {
		this.studentName=studentName;
		
		
		bookList = new ArrayList<Book>();
		
	}
	
	public void addBook(String title, String author) {
		
		Book book = new Book(title, author);
		bookList.add(book);
	}
	
	public void showInfo() {
		System.out.print(studentName+"학생이 읽은 책은 : ");
		for(Book book : bookList) {
			System.out.print(book.getTitle()+" ");
		}
		System.out.println("입니다");
	}
	
}
public class StudentTest {

	public static void main(String[] args) {
		Student Lee = new Student("Lee");
		
		Lee.addBook("태백산맥1", "조정래");
		Lee.addBook("태백산맥2", "조정래");
		
		Student Kim = new Student("Kim");
		
		Kim.addBook("토지1", "박경리");
		Kim.addBook("토지2", "박경리");
		Kim.addBook("토지3", "박경리");

		Student Cho = new Student("Cho");
		
		Cho.addBook("해리포터1", "조앤 롤링");
		Cho.addBook("해리포터2", "조앤 롤링");
		Cho.addBook("해리포터3", "조앤 롤링");
		Cho.addBook("해리포터4", "조앤 롤링");
		Cho.addBook("해리포터5", "조앤 롤링");
		Cho.addBook("해리포터6", "조앤 롤링");
		
		Lee.showInfo();
		Kim.showInfo();
		Cho.showInfo();
	}

}
  • 직전에 했던 예제와 같이 Student 인스턴스안에 Array List를 생성하고 그안에 Book 인스턴스들의  내용을 입력해서 담는 메서드를 만들고 showInfo 메서드를 통해 ArrayLIst안에 내용들을 출력하는 예제였습니다.

 

ArrayList 클래스

 

  • ArrayList는 자바에서 제공되는 객체 배열이 구현된 클래스로 객체 배열을 사용하는데 필요한 여러 메서드들이 구현되 어있어서 사용하는데 편의를 제공합니다

주요 메서드 알아보기

배열을 입력하고 값을 출력해보기.

import java.util.ArrayList;

public class ArrayListTest {

	public static void main(String[] args) {
		//1번
		ArrayList<String> list = new ArrayList<String>();
		//2번
		list.add("aaa");
		list.add("bbb");
		list.add("ccc");
		//3번
		for(int i=0; i<list.size();i++) {
			String str = list.get(i);
			System.out.println(str);
		}
		System.out.println("===========");
		//4번
		for(String s : list) {
			System.out.println(s);
		}

}
}
//결과물
aaa
bbb
ccc
===========
aaa
bbb
ccc
  • 1번 : ArrayList 클래스를 사용하기 위해 list인스턴스를 생성합니다. <>에는 자료형이 들어가는데 생략하면 자료형에 상관 없이 값을 입력할 수 있지만 형변환을 해줘야 합니다.
  • 2번 : .add 메서들 사용해 list 배열에 값을 입력합니다. String은 생성할 필요없이 바로입력이 가능하지만 다른 객체는 생성후 입력해야합니다.
  • 3번 : for문을 통해 배열 값을 하나씩 출력하는데 범위를 .size를 통해 현재배열에 크기인 3을 지정해줍니다. .length와의 차이점은 .size는 현재 배열의 크기를 뜻하고 .length는 전체 배열이 길이를 뜻합니다. 만약 list[10]이었다면 sieze에서는 입력된 값의 크기인 3이 되지만 length는 여전히 10을 유지합니다.
  • 4번 : 향상된 for문을 이용해서 값을 출력해보고 결과값을 확인하면 두개의 방법 모두 배열의 값을 출력했음을 확인할 수 있습니다.
  • 그외에 메서드는 help를 통해서 읽어보고 찾아가면서 해보시면됩니다.

<>의값을 생략했을 때

	public static void main(String[] args) {
		//1번
		ArrayList list = new ArrayList();
		//2번
		list.add("aaa");
		list.add("bbb");
		list.add("ccc");
		//3번
		for(int i=0; i<list.size();i++) {
			String str = (String)list.get(i);
			System.out.println(str);
		}
		System.out.println("===========");
		//4번
		for(Object s : list) {
			System.out.println(s);
		}

}
  • 1번 : 자료형을 지정해주는 <String>을 생략해보기 위해 지워줍니다.
  • 3번 : list.get에 자료형을 지정되지 않아 에러가 발생하기에 (String)을 입력합니다.
  • 4번 : String s : list에서 String이 아닌 모든 자료형을 포괄하는 Object로 수정해주고 실행하면 같은 결과값이 나옵니다.

예제

학생의 수강과목 학점 출력하기

  • Lee 학생은 두 과목을 수강하고, Kim 학생은 세 과목을 수강합니다.
  • Student클래스에 ArrayList 멤버변수를 하나 가지고 각 학생이 수강하는 과목을 관리하도록 합니다.
  • 각 학생의 학점과 총점을 아래와 같이 출력해봅시다.

//1번
public class Subject {
	
	public String name;
	public int score;
	//2번
	public Subject(String name, int score) {

		this.name=name;
		this.score=score;
	}
	//3번
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getScore() {
		return score;
	}
	public void setScore(int score) {
		this.score = score;
	}
}
  • 1번 : 각 과목 역할을 할 Subject 클래스를 만들고 멤버변수로 name과 score를 선언해줍니다.
  • 2번 : Subject 생성자를 만들어 name과 score를 입력할 수 있게 만듭니다.
  • 3번 : name과 score를 활용할 수 있게 get/set 메서드를 각각 만들어 줍니다.
import java.util.ArrayList;
	//1번
public class Student {
	
	public int studentId;
	public String studentName;
	ArrayList<Subject> subjectList;
	//2번
	public Student(int studentId, String studentName) {
		
		this.studentId = studentId;
		this.studentName= studentName;
		
		subjectList = new ArrayList<Subject>();
	}
	//3번
	public void addSubjcet(String name, int score) {
		
		Subject subject = new Subject(name, score);
		
		subjectList.add(subject);
	}
	//4번
	public void showInfo() {
	   int	total = 0;
		
	 for(Subject subject : subjectList) {
		 total = 0;
		 
		 total += subject.getScore();
		 System.out.println(studentName + "학생의 "+subject.getName() +"과목의 점수는 "+subject.getScore()+"입니다.");
	 }
	   System.out.println(studentName + "학생의 "+"총점은" + total+"입니다");
	}
}
  • 1번 : Student클래스를 만들고 studentId, studentName와 <Subject>를 담는 ArrayList subjectList를 멤버 변수로 선언합니다.
  • 2번 : Student 생성자를 만들어 studentId와 score를 입력할 수 있게 하고 Subject가 생성 될때 함게 ArrayList도 생성 될 수 있도록 생성자를 함께 작성합니다.
  • 3번 : 과목을 추가할 수 있는 addSubject 메서드를 만들고 name과 score를 입력하는데 그게 Subject생성자에 name과 score로 입력되도록 작성합니다. 그후 .add메서드를 통해 생성된 Subject가 ArrayList인 subjectList에 추가되도록 합니다.
  • 4번 : 정보를 출력하는 showInfo메서드를 작성합니다. 향상된 for문을 사용해 subject안에 subjectList 배열에 내용들이 하나씩 담기게 해서 total에 더해주면서 총점을 구하고 출력문을 통해 각 subject안에 name과 score을 get메서들 통해서 가져와서 출력합니다.
public class StudentTest {

	public static void main(String[] args) {
		//1번
		Student Lee = new Student(1001, "Lee");
		
		Lee.addSubjcet("국어", 100);
		Lee.addSubjcet("수학", 90);
		//2번
		Student Kim = new Student(1001, "Kim");
		
		Kim.addSubjcet("국어", 100);
		Kim.addSubjcet("수학", 90);
		Kim.addSubjcet("영어", 80);
		//3번
		Lee.showInfo();
		System.out.println("===============");
		Kim.showInfo();
	}

}

//결과물
Lee학생의 국어과목의 점수는 100입니다.
Lee학생의 수학과목의 점수는 90입니다.
Lee학생의 총점은90입니다
===============
Kim학생의 국어과목의 점수는 100입니다.
Kim학생의 수학과목의 점수는 90입니다.
Kim학생의 영어과목의 점수는 80입니다.
Kim학생의 총점은80입니다
  • 1번: Lee학생의 인스턴스를 생성하고 addSubject를 통해 각 과목의 이름과 점수를 입력합니다
  • 2번: 위와 마찬가지로 KIm학생의 인스턴스를 생성하고 과목이름과 점수를 입력합니다.
  • 3번 정보출력을 위해 showInfo메서드를 사용하면 결과물과 같은 출력문이 출력됩니다.

다차원 배열

 

  • 2차원 이상의 배열로 지도, 게임, 평면이나 공간을 구현할 때 사용나는 배열입니다.
  • 아래의 예시처럼 하나이상의 배열이 결합되어 구성되어 있습니다..

예시

다차원 배열의 length

	public static void main(String[] args) {
		//1번
		int[][] arr = {{1,2,3},{4,5,6}};
		//2번
		System.out.println(arr.length);    //2
		System.out.println(arr[0].length); //3
		System.out.println(arr[1].length); //3
		
	}
  • 1번 : new를 사용하지 않고 바로 값을 대입해 2차원 배열을 만들었습니다. 다차원 배열에서는 중괄호 안에 중괄호를 써 행과 열을 구분하여 값을 입력합니다.
  • 2번 : arr.lnegth는 값에 합인 6이 아닌 행의 갯수인 2가 출력됩니다. 즉, 몇차원 배열인지 알려주는 역할을 합니다. 그리고 각 열을 뜻하는 arr[0]과 arr[1]의 length는 값이 3개이기 때문에 3이 나오게 됩니다. 

2차원 배열의 값 출력

public static void main(String[] args) {
		//1번
		int[][] arr = {{1,2,3},{4,5,6,7}};
		//2번
		for(int i = 0; i < arr.length ; i++) {
			for(int j = 0; j < arr[i].length; j++) {
				System.out.print(arr[i][j]+" ");
				
			}
			System.out.println();
		}
	}
    //결과값
   	 1 2 3 
	4 5 6 7 
  • 1번 : 2차원 배열의 열의 length를 다르게 하기 위해 arr[1]에 7을 추가 입력합니다.
  • 2번 : 2중 for문을 사용해여 행을 반복시킬 동안 열의 길이만큼 출력될 수 있도록 합니다. i < arr.length는 행의 길이만큼 반복이 되는 것을 뜻하고 j<arr[i].length 는 해당 열의 길이만큼 반복하라는 뜻으로 arr[1]과 arr[2]의 길이가 다르더라도 올바르게 값을 출력해주는 역할을 합니다.
  • 위와 같이 다차원 배열은 for문을 통해 출력할 수 있고 차원이 많아 질수록 for문의 중첩이 늘어나게 됩니다. 바깥에 있는 for문일 수록 큰 공간을 표현한다고 이해하시면 어렵지 않게 사용할수 있을 것 입니다.

 

기본 자료형 배열과 참조 자료형 배열(객체 배열)

 

int[] arr = new int[10];

 

Book[] library = new Book[5];

 

  • 객체 배열은 자료 값을 담는 기본 자료형 배열과는 다르게 객체를 담는 배열이다. 여기서 객체는 생성자를 통해 만드는 인스턴스를 뜻한다. 위의 식은Book이란 클래스의 생성된 인스턴스를 담는 역할을 하는 객체 배열 library를 뜻한다.
  • 또한 기본 자료형이 직접 값을 메모리에 담는것과는 다르게 객체배열은 직접 인스턴스를 메모리에 기억하는 것이 아닌 주소값을 담는다. 객체 배열을 사용해 인스턴스를 담는다고해도 인스터는 여전히 힙메모리에 저장이되고 그 메모리의 주소만이 각 객체배열의 null값을 채우는 것이다.(null은 비었있다는 뜻이다)

예제

public class Book {
	
	private String title;
	private String author;
	
	public Book () {}

	public Book (String title, String author) {
		
		this.title = title;
		this.author = author;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}
	
	public void showInfo() {
		System.out.println(title+","+author);
	}

 

  • 예제를 위해 Book 인스턴스를 만들고 변수, 생성자, get/set메서드, showInfo 메서드를 작성한다

 

 

public class BookArrayTest {

	public static void main(String[] args) {
		//1번
		Book[] library = new Book[5]; //책이 5권생긴게 아니다, address만가지고있다.
		//3번
		library[0] = new Book("태백산맥1","조정래"); 
		library[1] = new Book("태백산맥2","조정래"); 
		library[2] = new Book("태백산맥3","조정래"); 
		library[3] = new Book("태백산맥4","조정래"); 
		library[4] = new Book("태백산맥5","조정래"); 
		//2번
		for(int i=0; i<library.length; i++) {
			System.out.println(library[i]);
			library[i].showInfo();
		}
		/*결과값
		array.Book@5305068a
		태백산맥1,조정래
		array.Book@1f32e575
		태백산맥2,조정래
		array.Book@279f2327
		태백산맥3,조정래
		array.Book@2ff4acd0
		태백산맥4,조정래
		array.Book@54bedef2
		태백산맥5,조정래 */
		
	}

 

1번 : BookArrayTest 클래스를 만들고 객체배열 library를 생성한다.(여기서 new는 배열생성의 new이지 인스턴스 생성자가 아니다)

2번 : for문을 통해 객체배열을 출력하면 배열에 담겨있는 인스턴스의 주소값이 출력되는데 아직 배열을 입력하기 전이라 null값이 5개 출력된다.

3번 : 객체 배열 library를 채우기 위해 생성자를 통해 Book의 인스턴스를 5개 선택한다. 그 후 2번의 출력문을 다시 출력하면 각 인스턴스의 주소값과 showInfo메서드를 통해 title과 author가 출력된다.

 

ArrayCopy

 

기본 자료형의 ArryCopy

public static void main(String[] args) {
		//1번
		int[] arry1 = {10,20,30,40,50};
		int[] arry2 = {1, 2, 3, 4, 5};
		//2번
		System.arraycopy(arry1, 0, arry2, 1, 3);
		//3번
		for(int i=0; i<arry2.length; i++) {
			System.out.println(arry2[i]);
		}
		/*결과물
		1
		10
		20
		30
		5 
		*/
	}
  • 1번 : 배열을 복사해주는 ArrayCopy를 실습 해 보기 위해 2개의 int배열을 만듭니다.
  • 2번 : System.arraycopy를 사용하면 (src, srcPos, dest, destPos, length)라고 뜨는데 앞에서부터 (복사할 배열, 복사를 시작할 위치, 붙여넣기할 배열, 붙여넣기할 위치, 복사하는 길이)를 뜻합니다. 2번에선 arry1의 0부터 3개를 복사해 arry2에 1번 부터 붙여넣기하라고 입력했습니다.
  • 3번 : for문을 통해 배열 arry2를 출력하면 결과물같이 1~3번에 내용이 복사되었으면 알 수 있습니다.

 

객체 배열의 ArryCopy

public static void main(String[] args) {
		//1번
		Book[] library = new Book[5]; 
		Book[] copyLibrary = new Book[5];
		//2번
		library[0] = new Book("태백산맥1","조정래"); 
		library[1] = new Book("태백산맥2","조정래"); 
		library[2] = new Book("태백산맥3","조정래"); 
		library[3] = new Book("태백산맥4","조정래"); 
		library[4] = new Book("태백산맥5","조정래"); 
		//3번
		System.arraycopy(library, 0, copyLibrary, 0, 5);
		//4번
		for(Book book : copyLibrary) {
			book.showInfo();
		}
        
      //결과물
      태백산맥1,조정래
	태백산맥2,조정래
	태백산맥3,조정래
	태백산맥4,조정래
	태백산맥5,조정래		
	}
  • 1번 : BookArrayTest에서 library 와 2번에 생성된 클래스들을 복사하고 library내용을 복사할 copyLibrary 배열을 생성합니다.
  • 3번 : 기본자료형 배열과 동일한 방법으로 arrycopy를 사용해서 library에 내용을 copyLibrary로 복사합니다.
  • 4번 : for(자료형, 변수명: 배열) 하면 배열을 처음부터 끝까지 돌면서 해당 변수에 값을 하나씩 대입한다는 뜻이 됩니다. 그래서 showinfo()메서드를 사용해서 객체 배열에 내용을 출력하면 결과물 같이 모두 복사된것을 확인할 수 있습니다.

ArrayCopy : 얕은복사

		//1번
		library[0].setTitle("나목");
		library[1].setTitle("박완서");
		//2번
		for(Book book : library) {
			book.showInfo();		}
		System.out.println("=============");
		for(Book book : copyLibrary) {
			book.showInfo();		}
            
           //결과물
           	박완서,조정래
			태백산맥2,조정래
			태백산맥3,조정래
			태백산맥4,조정래
			태백산맥5,조정래
			=============
			박완서,조정래
			태백산맥2,조정래
			태백산맥3,조정래
			태백산맥4,조정래
			태백산맥5,조정래
				
  • 1번 : ArrayCopy에 특성을 알아보기 위해 setTitle과 setAuthor 메서들 사용해 libarary[0]에 값을 바꿔줍니다.
  • 2번 : 비교를 위해 library와 copyLibrary의 내용을 출력하면 arry[0]에 내용이 둘다 바뀐 것을 알 수 있습니다. 이는 ArryCopy에서 복사는 얕은 복사로 주소값만을 복사하기 때문에 원본 내용이 바뀌면 주소값을 복사한 카피본의 내용도 바뀌는 것입니다. 참고로 내용을 직접 수정하여 복사하여 원본을 수정해도 카피본이 수정되지 않는 복사를 깊은복사 라고 합니다.

ArrayCopy : 깊은복사

 

		//1번
		copyLibrary[0] = new Book();
		copyLibrary[1] = new Book();
		copyLibrary[2] = new Book();
		copyLibrary[3] = new Book();
		copyLibrary[4] = new Book();
		//2번
		for ( int i= 0; i<library.length; i++) {
			copyLibrary[i].setAuthor(library[i].getAuthor());
			copyLibrary[i].setTitle(library[i].getTitle());
		}
		//3번		
		library[0].setTitle("나목");
		library[0].setTitle("박완서");
				
		for(Book book : library) {
			book.showInfo();		}
		System.out.println("=============");
		for(Book book : copyLibrary) {
			book.showInfo();		}
  • 1번 : 직접 값을 대입하기 위해 copyLibrary에 인스턴스를 생성합니다.
  • 2번 : for문을 통해 copyLibarary에  author에 library의 Author를 대입하고 Title도 대입합니다.
  • 3번 : 얕은 예제와 같이 library[0]에 내용을 바꾸고 두 배열을 출력하면 이번엔 library만 바뀌고 카피본은 안바뀐 것을 확인할 수 있습니다. 이처럼 주소가 아닌 원보값을 카피본에 직접대입하는 복사를 깊은복사라고하고 필요에 따라 얕은복사와 깊은 복사 중 선택하여 사용할 수 있습니다.

향상된 for문

위의 예제에서 사용된 for문으로 배열의 요소의 처음부터 끝까지 모든 요소를 참조 할 때 편리한 반복문으로 구조는 다음과 같습니다.

 

for(변수 : 배열){

    반복 실행문

}

배열이란?

  • 배열은 자료들을 모아놓는 자료구조의 일종으로 동일한 자료형을 순차적으로 관리하는 기능을 합니다.
  • 배열을 선언하기 위해선 '자료형[] 변수명 = new 자료형[인덱스 크기]'으로 사용하고 예를 들어int[] arr= new int[10];와 같이 선언합니다.
  • 배열의 메모리 구조 아래 그림과 같이 자료형 크기의 맞는 메모리가 1열로 선언한 갯수만큼 생성됩니다. 그래서 배열은 물리적인 위치와 논리적인 위치가 동일하다고 합니다.
  • 배열은 0번 부터 시작합니다. int[10]이면 위치가 1~10가 아닌 0~9입니다. 정리하면 int[] arr = new int[n]라면 인덱스 번호는 0~n-1까지 입니다. 

예제

		int[] arr1 = new int[] {1,2,3}; 
		
		int[] arr2 = {1,2,3}; 
		
		int[] arr3;
		arr2 = new int[]{1,2,3}; 
        
        int[] arr4 = new int[3];
        arr4[0] = 1;
        arr4[1] = 2;
        arr4[2] = 3;
  • 위와 같은 방식으로 배열을 입력할 수 있습니다. 배열은 자료형 뒤에 대괄호[]가 붙고 값을 중괄호{}안에 콤마','로 구분하여 넣습니다. 
public static void main(String[] args) {
		//1번
		int[] arr = new int[10];
        //2번
	   	for(int i = 0, num =1; i <arr.length ; i++,num++) {
		  arr[i] = num;
		}
		//3번
		int sum = 0;
		for(int i = 0; i<arr.length ; i++) {
			sum += arr[i];			
		}
		System.out.println(sum); //55
        }
  • 배열을 통하여서 1~10까지 합을 구하는 예제입니다.
  • 1번 : Int arr 배열을 선언하고 10개의 인덱스를 입력합니다.
  • 2번 : for문을 통해 배열 arr에 0~9자리에 1~10까지 대입하는 반복문을 작성합니다
  • 3번 : sum 변수를 만들고 arr배열에 대입한 1~10을 더해주는 반복문을 작성하고 마지막에 출력문을 통해 sum을 출력합니다.
		double[] dArr = new double[5];
		
	    dArr[0] = 1.1;
	    dArr[1] = 2.1;
	    dArr[2] = 3.1;
	    
	    double mtotal = 1;
	    
	    for(int i = 0; i<dArr.length; i++) {
	    	mtotal *= dArr[i];
	    }
		
		System.out.println(mtotal);//0
  • 다음예제는 유의사항으로 다음과 같이 5개의 배열 중 3개만 입력한 상황에서 length만큼 반복해서 곱셈을 하였더니 결과값이 0.0이 나왔습니다. 이는 값을 입력하지 않은 dArr[3],dArr[4]가 초기값인 0.0로 잡혀있기 때문입니다.
  • 이 문제를 해결하기 위해 아래와 같이 count 변수를 선언하고 배열에 변수를 대입할 때 마다 conut++을 해주고 조건에 dArr.length대신 count를 넣어주면 결과값을 얻을 수 있습니다.
double[] dArr = new double[5];
		
		int count = 0;
		
	    dArr[0] = 1.1; count++;	
	    dArr[1] = 2.1; count++;
	    dArr[2] = 3.1; count++;
	    
	    double mtotal = 1;
	    
	    for(int i = 0; i<count; i++) {
	    	mtotal *= dArr[i];
	    }
		
		System.out.println(mtotal);//7.161
     

 

Q. 배열문제

  • 문자 배열을 생성하고 출력해 보세요.
  • 대문자를 A-Z까지 배열에 저장하고 이를 다시 출력하는 프로그램을 만들어 보세요.
public static void main(String[] args) {
		//1번
		char[] alpahbets = new char[26];
		char ch = 'A';
		//2번		
		for(int i = 0; i < alpahbets.length ; i++) {
		   alpahbets[i] += ch++;
		}
		//3번
		for(int i = 0; i <  alpahbets.length; i++) {
			System.out.println(alpahbets[i]+","+(int)alpahbets[i]);
		}
		
	}
  • 1번 : char alpahbets 배열을 먼저 만들어주고 시작 값인 A를 ch 변수로 선언합니다.
  • 2번 : for문을 만들고 length까지 반복되게 범위를 지정하고 alpabets배열에 A부터++해서 더합니다.
  • 3번 : for문을 만들고 출력문안에 alpabets[i] 와 (int)alpabets[i]를 출력하게합니다. 그러면 A,65 B,66 .....Z,90까지 출력이 됩니다.

static과 singleton pattern

 

  • 카드 회사가 있습니다. 카드회사는 유일한 객체이고, 이 회사에서는 카드를 발급하면 항상 고유번호가 자동으로 생성됩니다.
  • 10001부터 시작하여 카드가 생성될 때 마다 10002, 10003식으로 증가 됩니다.
  • 다음 코드가 수행 되도록 Card클래스와 CardComapny 클래스를 구현하세요.
public class Card {
	//1번
	private static int serialNum = 10000;
	private int cardNum;
	//2번
	public Card() {
		serialNum++;
		cardNum=serialNum;
	};
	//3번
	public int getCardNum() {
		
		return cardNum;
	}
}
  • 1번 : Card 클래스를 만들고 cardNum과 cardNum을 카운트하기위한 serialNum을 private으로 선언하고 serialNum은 static으로 선언하여 여러 인스턴스에서 접근가능하도록 합니다.
  • 2번 : Card 생성자를 만들고 인스턴스가 생길때마다 serialNum이 증가하고 증가한 수가 carNum이 되도록 작성합니다
  • 3번 : private인 cardNum을 읽기위한 getCardNum 메서드를 작성합니다.
public class Company {

	//2번
	private static Company instance = new Company();
	//1번
	private Company () {}
	//3번
	public static Company getInstance() {
		
		return instance;
		
	}
	//4번
	public static Card createCard() {
		
		Card card = new Card();
		return card;
	}
}
  • 1번 : Company 클래스를 만들고 Company 생성자를 private으로 만들어 외부에서 인스턴스를 생성할수 없게 만듭니다.
  • 2번 : private으로 클래스 내부에서 i단 하나의 인스턴스를 생성합니다
  • 3번 : 외부에서 Comapny instance 를 읽기 위해 사용할 getInstance메서드를 작성하고 static을 통해 인스턴스를 생성하지 않고도 메서드를 사용할 수 있게 합니다.
  • 4번 : Card 인스턴스를 생성할 createCard메서드를 만들고 역시 인스턴스 생성없이 사용할수 있게 static을 붙여줍니다.
public class CardTest {

	public static void main(String[] args) {
		//1번
		Company company = Company.getInstance();
		//2번
		Card myCard = company.createCard();
		Card yourCard = company.createCard();
		//3번
		System.out.println(myCard.getCardNum()); //10001
		System.out.println(yourCard.getCardNum()); //1002
		
	}

}
  • 1번 : Company 인스턴스를 불러오기 위해 Company.getInstance 메서드를 사용합니다.
  • 2번 :  Card 인스턴스를 사용하기 위해 company.createCard()메서드를 사용합니다.
  • 3번 :  cardNum을 확인하기 위해 출력문에 getCardNum 메서드를 사용합니다.

단하나의 존재하는 인스턴스 - singleton pattern

 

  • 학교와 학생의 클래스가 있다면 학생은 여러명일 수 있으나 학교는 하나여야 합니다. 이렇듯 한 클래스에 인스턴스가 하나만 존재하게 구현 하는 것을 singleton pattern이라고 합니다.
  • sigleton patter에서는 생성자를 prviate으로 만들고 클래스 내부에 인스턴스를 생성합니다. 그리고 외부에서 해당 인스턴스를 사용할 수 있게 호출해주는 static 메서드를 제공합니다.

예제

public class Company {
	//2번
	private static Company instance = new Company();
	//1번
	private Company(){}
	//3번
	public static Company getInsatance() {
		if(instance == null) {
			instance = new Company();
		}
		return instance;
	}
}
  • 1번 : Company 클래스를 만들고 외부에서 생성자를 사용할 수 없게 prviate Company 생성자를 만듭니다.
  • 2번 : 지금까지 클래스 외부에서 인스턴스를 만들었지만 하나의 클래스만을 만들기 위해 클래스 내부에 private을 붙여주고 여러곳에서도 인스턴스 외부에서도 사용할 수 있게 static을 붙여 인스턴스를 생성합니다. 
  • 3번 :  instance를 사용하는 곳에서 호출하기 위한 getInsatace 메서드를 작성하되 외부에서 사용할수 있게 public을 붙여주고 인스턴스를 생성하지 않아도 사용할수 있는 형태를 만들기 위해 static을 붙여줍니다. 조건문을 통해 혹시라도 하나의 인스턴스도 생성되지 않았다면 생성할 수 있게 해주고 인스턴스가 있다면 instance를 return해줍닏.ㅏ
import java.util.Calendar;

public class CompanyTest {

	public static void main(String[] args) {
		//1번
		Company company1 = Company.getInsatance();
		Company company2 = Company.getInsatance();
		//2번
		System.out.println(company1);//staticex.Company@5305068a
		System.out.println(company2);//staticex.Company@5305068a
		//3번
		Calendar calendar1 = new Calendar(); // 생성이안됨 
		Calendar calendar2 = Calendar.getInstance();
		
	}

}
  • 1번: 테스트를 위해 CompanyTest 클래스를만들고 company1 인스턴스를 생성하는것이 아닌 Compnay.getInstance()를 통해 불러옵니다. 테스트를 위해company2를 통해 한번더 불러 옵니다.
  • 2번 : 출력문을 통해 두 인스턴스에 주소값을 출력해 본 결과 두개의 주소가 같음을 볼 수 있고 인스턴스를 생성한 것이 아닌 한곳에 인스턴스를 불러 온 것을 알 수 있습니다.
  • 3번 : 자바에서 제공하는 Calendar 클래스의 경우 clendar1처럼 java.until에 있는 Calendar 클래스를 생성하려고 하면 오류가 생기지만 calendar2처럼 불러와서 사용할 수 있습니다. 이처럼 자바 내부에서 제공하는 클래스나 또 단하나의 인스턴스만 생성하는 경우 singleton patter을 사용하는 것을 알수 있습니다.

static 변수

  • 클래스 하나에서 생성되는 여러개의 인스턴스가 하나의 값을 공유할 필요가 있을 때 사용하는 변수로 하나의 변수
  • 인스턴스가 힙메모리에 생성되는 것과는 다르게 다음 그림과 같이 데이터 영역 메모리에 생성되며 인스턴스의 생성과 상관없이 사용할 수 있습니다.

  • 클래스 이름으로 참조되며 Student 클래스에 serialNum이라는 스태틱 변수를 참조한다면 Student.serialNum으로 사용할 수 있습니다.

예제, Student클래스에 학생 인스턴스가 새로 생성될 때 마다 Student ID를 부여하시오

 

public class Student {
	//1번
	private static int serialNum = 1000;
	private int studentID;
	public String studentName;
	//2번
	public Student(String name) {
		
		studentName = name;
		serialNum++;
           studentID = serialNum;
	}
    //3번
    public int getStudentID() {
	    		    	
	   	return studentID;
    }    
    public static int getSerialNum() {
		
		return serialNum;
	}
 
    }
  • 1번 : 인스턴스가 생성될 때 마다 Student ID를 부여하기 위해서는 기준점이 되는 하나의 값이 필요한데 그것을 static변수로 선언하기 위해 serialNum 변수를 선언하고 초기값을 1000 대입합니다. 
  • 2번 : 인스턴스가 생성될 때마다 serialNum이 1씩증가해야 하기 때문에 2개의 Student생성자에 serialNum++을 해줍니다.  그리고 증가된 serailNum을 생성된 인스턴스의 StudentID 값의 대입해줍니다. 
  • 3번 : prviate으로 선언한 studentID와 serialNum을 읽기위한 get메서드를 각각 작성해주되 SerailNum은 static변수이니 static 메서드로 작성합니다.
public class StudentIdTest {

	public static void main(String[] args) {
		//1번
		Student studentLee = new Student("Lee");
		System.out.println(Student.getSerialNum());//1001
		Student studentSon = new Student("Son");
		System.out.println(Student.getSerialNum());//1002
		//2번
		System.out.println(studentSon.getStudentID());//1002
		System.out.println(studentLee.getStudentID());//1001
		
	}
  • 1번 : 테스트 하기 위해 Student 인스턴스를 2개만들고 static 변수인 serialNum을 출력하기 위해 클래스명.메서드를 사용하여 출력하였더니 인스턴스가 생성될 때 마다 serailNum이 증가하는 것을 확인 할 수 있었습니다. 
  • 2번: studentId를 출력해 보았더니 인스턴스가 생성될때마다 증가된 seriaNum이 잘 대입된 것을 확인할 수 있습니다.

  • 예제의 static변수인 serialNum과 참조변수, 각 인스턴스의 관계는 위의 그림과 같습니다. 그리고 증가된 serailNum이 대입되는 studentID변수는 각각의 인스턴스에 담기게 됩니다.

static 메서드

  • static 변수를 위한 기능을 제공하는 메서드로 static메서드와 마찬가지로 클래스명.으로 참조해서 사용합니다.
	public static int getSerialNum() {
		int i = 0; // 지역변수는 사용가능
		studentName = "Lee"; //에러 - 멤버변수는 사용불가
		return serialNum;
	}

 

  • 위의 예제와 같이 static 메서드안에서 int i =0과 같은 지역변수는 함수안에서만 사용하고 소멸되는 변수이기에 사용가능합니다. 하지만 studentName같은 멤버변수는 사용할 수 없는데 그 이유는 static메서드는 인스턴스의 생성여부와 상관없이 사용할수 있는 메서드이지만 멤버변수는 인스턴스의 생성이 필수이기 때문입니다. static메서드를 사용하는 순간에 인스턴스가 없다면 오류가 날수 밖에 없는 변수이기 때문에 사용이 불가능합니다.

프로그램에서 변수의 유형

 

  • 변수유형 별 어디에서 어떤 변수를 사용해야 하는지를 배워야 메모리 사용을 최적화 하고 에러를 줄이는 프로그래밍을 할 수 있기에 잘 이해해야 합니다.

Q1. 정보은닉 - 날짜의 유효성을 검증하는 프로그램을 구현해 보세요

다음과 같은 MyDate 클래스가 있습니다. 

-day, month, year변수는 private으로 선언합니다.
-각 변수의 getter, setter를 public으로 구현합니다.
-MyDate(int day, int month, int year) 생성자를 만듭니다.
-public boolean isVaild() 메서드를 만들어 날짜가 유효한지 확인합니다.
-MyDateTest 클래스에서 생성한 MyDate 날짜가 유효한지 확인합니다.

 

public class MyDate {
	//1 번
	private int day;
	private int month;
	private int year;	
	private boolean isValid = true;
	//2번
	public MyDate(int day, int month, int year) {
		setYear(year);
		setMonth(month);
		setDay(day);						
	}	
	//3번
	public int getDay() {
		return day;
	}
    public void setDay(int day) {
		
		this.day = day;
		
		switch(month) {
		case 1: case 3: case 5: case 7: case 8: case 10: case 12:
			if (day >31 || day < 0) {
				isValid = false;
			} 
			break;
		case 4: case 6: case 9: case 11:
			if (day > 30 || day < 0) {
				isValid = false;
			} 
			break;
		case 2:
			if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0 )//윤년여부
            {
				if (day <0 || day >29) {
					isValid = false;
				} 
			} else {
				if (day <0 || day >28) {
					isValid = false;
				} 
			}
			break;
			default : isValid = false;
		} 
		
	}
    //4번
    public int getMonth() {
		return month;
	}
	public void setMonth(int month) {
		this.month = month;	
		
		if(month > 12 || month < 1) {
			isValid = false;
		} 
	}
	//5번
    public int getYear() {
		return year;
	}

	public void setYear(int year) {
		this.year = year;
		
		if( year < 1 ) {
			isValid = false;
		}
	}
	//6번
	public void isValid() {
	
		if(isValid) {
			System.out.println( "유효한 날짜입니다.");
		} else {
			System.out.println( "유효하지 않은 날짜입니다.");
		}		
	}
  • 1번 : MyDate 클래스를 만들고 day, month, year를 멤버 변수로 선언 해주고 유효성을 검사하는데 사용할 isVaild변수 또한 boolean형으로 선언해 줍니다.
  • 2번 : MyDate 생성자에 매개변수로 int day, int month, int year을 잡아주고 구현문에 뒤에 입력메서드로 만들  set(~)메서드를 넣어줍니다. 이 때 뒤에 조건문을 사용하기 위해 year - month - day 순으로 작성해주어야 합니다.
  • 3번 : day getter setter 문을 작성 해 줍니다. setDay 메서드에 switch-case문을 사용하여 각 월별 최대일수의 범위를 지정해주고 2월은 윤년여부를 체크하는 조건문을 하나 더 만들어서 넣어줍니다.
  • 4번 : month getter setter 문을 작성해 줍니다. setMonth 메서드에 조건문을 통해 month의 범위를 지정해줍니다.
  • 5번 : year getter setter 문을 작성해 줍니다. setYear 메서드에 조건문을 통해 year의 범위를 지정해줍니다.
  • 6번: isValid 메서드를 만들고 조건문을 작성하여 set(~)메서드에서 범위가 넘어간게 있다면 유효하지 않는 날짜입니다가 출력되도록 합니다.

 

public class MyDateTest {

	public static void main(String[] args) {

		
		MyDate date1 = new MyDate(29,2,2020);
		date1.isValid(); // 유효한 날짜입니다.
		
		MyDate date2 = new MyDate(29,2,2019);
		date2.isValid(); // 유효하지 않은 날짜입니다.
		
	}

}
  • 테스트를 위해 MyDataTest 클래스를 만들고 date1과 date2 인스턴스를 만들어 1년차이나는 2월 29일을 입력했을 때 윤년여부에 따라 유효성 테스트가 되는 것을 확인할 수 있습니다.

 

Q2. 객체 간 협력 - 출근길 커피 사기

아침 출근길에 김 졸려씨는 4000원을 내고 별다방에서 아메리카노를 사 마셨습니다.
이 피곤 씨는 콩다방에서 4500원을 내고 라떼를 사 마셨습니다.
객체 간의 협력 강의를 참고하여 객체 지향 방법으로 구현해 보세요.

public class BeanCoffee {
	//1
	public int price;
	public int income;
	//2
	public BeanCoffee() {
				}
	//3
	public String sellBeanCoffee(int price) {
		income+=(price);
				
		if (price == 4200) {
		   return "콩 다방 아메리카노를 구입했습니다.";
		} else if (price == 4500) {
			return  "콩 다방 라떼를 구입했습니다.";
		} else {
		return null;
		}
		
	}
}
  • 1번 : BeanCoffee 클래스를 만들고 필요한 멤버변수를 선언해 줍니다.
  • 2번 : BeanCoffee 생성자를 만드는데 내용이 빈 기본생성자이니 생략해도 됩니다.
  • 3번 : BeanCoffee 를 팔때 해줘야 할 작업들을 sellBeanCoffee 메서드를 작성합니다. inome을 price만큼 중가시켜주고 가격에 따라 아메리카노 혹은 라떼를 구입했다는 String 값을 return합니다.
public class StarCoffee {
	
	public String shopName;	
	public int price;
	public int income;
	
	public StarCoffee() {
	
		shopName = "별 다방";
	}
	
	public String sellStarCoffee(int price) {
		income+=(price);
				
		if (price == 4000) {
		   return "별 다방 아메리카노를 구입했습니다.";
		} else if (price == 4300) {
			return  "별 다방 라떼를 구입했습니다.";
		} else {
		return null;
		}
		
	}
	
  • StarCoffe 클래스를 만들고 BeanCoffee와 동일한 역할을 하는 변수와 메서드를 작성해줍니다.
public class Person {
	//1번
	public String name;
	public int money;
	//2번
	public Person(String name) {
		this.name = name;
		money = 10000;
	}
    //3번	
	public void buyStarCoffee(StarCoffee starcoffee, int price) {
		String message = starcoffee.sellStarCoffee(price);
		if(message != null) {
			money -= price;
			System.out.println(name+" 님이 "+price+"으로 " + message );
		}
	}
	//4번	
	public void buyBeanCoffee(BeanCoffee beancoffee, int price) {
		String message = beancoffee.sellBeanCoffee(price);
		if(message != null) {
			money -= price;
			System.out.println(name+" 님이 "+price+"으로 " + message );
		}
	}
  • 1번 : 커피를 살 사람을 대표하는 Person 클래스를 만들고 멤버 변수로 name과 money를 선언합니다
  • 2번 : Person 생성자를 만드는데 name을 입력하게 하고 money는 동일하게 10000원씩 동일하게 소지함으로 입력받지 않고 바로 값을 대입해줍니다.
  • 3번 : buyStarCoffee 메서드를 만들고 매개변수로 starcoffee와 price를 잡아줍니다. 그후 sellStarCoffee메서드를 사용해 price에 따라 나오는 return값을 String message에 대입해주고 조건문을 통하여 message가 null이 아닌경우 money에서 price만큼 가격이 빠져나가고  상황에 맞는 메세지가 출력되도록 합니다.
public class CoffeTest {

	public static void main(String[] args) {
		//1번
		Person Lee = new Person("Lee");
		Person Kim = new Person("Kim");
		//2번
		StarCoffee starcoffee = new StarCoffee();
		BeanCoffee beancoffee = new BeanCoffee();
		//3번
		Lee.buyStarCoffee(starcoffee, 4000);
		//Lee 님이 4000으로 별 다방 아메리카노를 구입했습니다.
		Kim.buyBeanCoffee(beancoffee, 4500);
		//Kim 님이 4500으로 콩 다방 라떼를 구입했습니다.
	}

 

  • 1번 : CoffeeTest 클래스를 만들고 Person 인스턴스를 2개 만들어 줍니다.
  • 2번 : 각각 커피의 메서드를 사용하기 위해 Coffee 인스턴스도 각각 만들어 줍니다.
  • 3번 : person 클래스의 buyCoffee 를 사용하고 매개변수로 각coffee와 가격을 넣어주면 결과값이 나오는 것을 확인할 수 있습니다.

 

객체 지향 프로그래밍은 객체를 정의하고 협력 한다고 배웠는데 예제를 통해서 객체간 협력이 어떻게 구현되는지 알아보도록 하겠습니다.

 

예제

다음과 그림과 같이 학생이 버스나 지하철을 탔을 경우 학생이 가진돈과 교통수단의 수입이 어떻게 변화하지는지 구현해보시오.

 

 

public class Bus {
	//1번
	int busNum;
	int passengerCount;
	int income;
	//2번
	public Bus(int busNum) {
		this.busNum=busNum;
	}
	//3번
	public void take(int income) {
		this.income += income;
		passengerCount++;
	}
	//4번
	public void busInfo() {
		System.out.println(busNum+"번 버스의 승객 수는"+passengerCount+"이고 수입은 "+income+"입니다.");
	}

}

 

  • 1번 : Bus 클래스를 생성하고 필요한 3가지 멤버 변수를 선언합니다.
  • 2번:  busNum을 매개변수로 하는 Bus 클래스의 생성자를 생성합니다.
  • 3번:  Student가 bus를 탔을 때 매개변수 income만큼 멤버변수 income을 증가시키면서 passegerCount도 1시키는 take메서드를  작성합니다
  • 4번: busNum의 passegerCount와 incom을 출력하는 busInfo를 작성합니다. 
public class Subway {
	
	int lineNum;
	int passengerCount;
	int income;
	
	public Subway(int lineNum) {
		this.lineNum=lineNum;
		
	}
	
	public void take(int income) {
		this.income += income;
		passengerCount++;
	}
	
	public void subwayInfo() {
		System.out.println(lineNum+"번 지하철의 승객 수는"+passengerCount+"이고 수입은 "+income+"입니다.");
	}
}
  • Buts 클래스와 동일한 기능을 하는 Subway 클래스를 생성하여 작성해줍니다. 

 

public class Student {
	//1번
	String studentName;
	int grade;
	int money;
	//2번
	public Student(String studentName, int money) {
		
		this.studentName = studentName;
		this.money = money;
	}
	//3번
	public void takeBus(Bus bus) {
		bus.take(1000);
		money -= 1000;
	}
	//4번
	public void takeSubway(Subway subway) {
		subway.take(1200);
		money -= 1200;
	}
	//5번
	public void showInfo() {
		System.out.println(studentName + "의 남은 돈은 "+money+"입니다.");
	}
}
  • 1번 : Student 클래스를 생성하고 3가지 멤버 변수를 생성합니다.
  • 2번 : Student 생성자를 생성합니다. 매개변수는 studentName 과 money를 사용합니다
  • 3번 : takeBus메서드를 만들어 학생이 버스를 탔을 때 Bus Class에서 만든 take메서드를 통해 bus의 income에 1000원이 증가하고 passengerCount가 1증가하며 Student에 money 변수에서는 1000원이 빠져나가게 합니다.
  • 4번 : 3번과 같은역할을 Subway클래스를 통해서 하는 takeSubway 메서드를 작성합니다.
  • 5번 :  studentName과 money를  출력하는 showInfo메서드를 작성합니다.
  • 3번과 4번 과정이 우리가 알고 싶어한 객체간의 협력이 일어나는 부분으로 Student의 메서드의 매개변수로 Bus가 참조 변수로 들어가 있습니다. 또한 구현문에서 Bus클래스의 메서드인 .take가 사용되어 있어 Student메서드 안에 다른 클래스의 메서드와 멤버 변수가 협력하고 있는 것을 확인할 수 있습니다.
public class TakeTransTest {

	public static void main(String[] args) {
		//1번
		Student studentJ = new Student("James",5000);
		Student studentT = new Student("Tomas",10000);
		//2번
		Bus bus100 = new Bus(100);
		Bus bus500 = new Bus(500);
        //3번
		Subway subwayGreen = new Subway(2);
		//4번		
		studentJ.takeBus(bus100);
		studentT.takeSubway(subwayGreen);
		//5번
		studentJ.showInfo();//James의 남은 돈은 4000입니다.
		studentT.showInfo();//Tomas의 남은 돈은 8800입니다.
		//6번
		bus100.busInfo();//100번 버스의 승객 수는1이고 수입은 1000입니다.
		bus500.busInfo();//500번 버스의 승객 수는0이고 수입은 0입니다.
		//7번
		subwayGreen.subwayInfo();//2번 지하철의 승객 수는1이고 수입은 1200입니다.
	}
}
  • 1번 : TakeTransTest 클래스를 만들고 Student 인스턴스를 2개 만듭니다.
  • 2번: : Bus의 인스턴스 2개를 만듭니다.
  • 3번:  Subway의 인스턴스를 만듭니다.
  • 4번 : Student인스턴스들이 Bus와 Subway를 탔다고 가정하여 각각 takeBus, takeSubway 메서드를 사용합니다.
  • 5번 : showInfo메서드를 사용해 Student 인스턴스들의 현재 남은 money를 출력합니다.
  • 6번 : BusInfo 메서드를 사용해 Bus 인스턴스들의 passengerCount와 Income을 출력합니다.
  • 7번 : Subwayinfo 메서드를 사용해 subwayGreen의 passengerCount와 Income을 출력합니다
  • 테스트의 결과값을 통해 3개의 클래스에 협력이 잘 이루어진 것을 확인 할 수 있었습니다.

 

추가예제

Edward가 늦잠을 자서 학교에 택시를 타고 10,000원을 지불한 상황을 추가 코딩해 보시오.

public class Taxi {
	
	int taxiNum;
	int passengerCount;
	int income;
	
	public Taxi(int taxiNum) {
		this.taxiNum=taxiNum;
		
	}
	
	public void take(int income) {
		this.income += income;
		passengerCount++;
	}
	
	public void busInfo() {
		System.out.println(taxiNum+"의 승객 수는"+passengerCount+"이고 수입은 "+income+"입니다.");
	}
}
  • Taxi 클래스를 만들고 Bus, Subway 클래스들과 동일한 기능을 하는 코드를 작성합니다
public void takeTaxi(Taxi taxi1) {
		taxi1.take(10000);
		money -= 10000;
	}
  • Student 클래스에 takeTaxi 메서드를 추가해줍니다.
        Student studentE = new Student("Edward", 10000);
		Taxi taxi1 = new Taxi(1);
		studentE.takeTaxi(taxi1);
		studentE.showInfo(); //Edward의 남은 돈은 0입니다.
		taxi1.taxiInfo(); //택시1의 승객 수는1이고 수입은 10000입니다.
  • TakeTransTest 클래스에 위와 같은 코드를 입력하여 작동하는지를 테스트 합니다.
  • 테스트 결과 Taxi 클래스도 Student 클래스와 잘 협력하고 있음을 확인할 수 있습니다.

+ Recent posts