{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 01 Hello Java" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "hello world" ] } ], "source": [ "System.out.print(\"hello world\");" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "helloworld" ] } ], "source": [ "System.out.print(\"hello\");\n", "System.out.print(\"world\");" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "hello\n", "world\n" ] } ], "source": [ "System.out.println(\"hello\");\n", "System.out.println(\"world\");" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "hello\n", "world" ] } ], "source": [ "System.out.print(\"hello\\n\");\n", "System.out.print(\"world\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "기본적인 조건문(if, if ... else, switch)나 반복문(while, for, do ... while)은 1학년 때 C를 배웠으면 스스로 찾아보면 금방 익숙해질 수 있을 만큼 비슷하므로 스스로 학습할 것.\n", "\n", "Do it! Java ... 교재의 첫째 마당 그러니까 4장까지 내용을 익숙하지 않은 사람은 한번 빠르게 훑어볼 것." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Primitives vs. Objects\n", "(원시타입 vs. 오브젝트)\n", "\n", "Object를 *객체*라고 옮기기도 하지만 그냥 *오브젝트*라 부르겠다.\n", "\n", "Java에는 오브젝트가 아닌 `int`, `double`, `boolean`, `char` 등의 원시타입이 있다. (1학년 때 C를 배웠으니 대략 이게 어떤 건지는 감이 올거다)\n", "\n", "오브젝트와 원시타입의 값을 함께 섞어 쓰는 경우 일괄적으로 오브젝트로 처리하는 것이 훨씬 편한 경우가 많다. (자바는 객체지향언어니까 ...)\n", "\n", "그래서 자바 표준라이브러리에는 이런 원시타입을 오브젝트로 포장해주기 위한 `Integer`, `Double`, `Boolean`, `Char`같은 클래스(`class`)가 제공된다.\n", "이렇게 원시타입을 오브젝트로 포장해주는 클래스들를 wrapper class라고 부른다. (Do it! 자바 ... 11-3 참고)\n", "\n", "참고로 객체지향언어 중에는 모든 값을 다 오브젝트로 취급하도록 설계해 개념적인 통일성을 추구하는 경우도 있으나, 아쉽게도 자바는 그렇게 설계되지 않았다." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "int i1 = 1;\n", "double d1 = 1.1;" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "ename": "CompilationException", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1m\u001b[30m| \u001b[1m\u001b[30m\u001b[0m\u001b[1m\u001b[30m\u001b[41mi1.getClass\u001b[0m\u001b[1m\u001b[30m() // 오브젝트가 아니라서 안됨\u001b[0m", "\u001b[1m\u001b[31mint cannot be dereferenced\u001b[0m", "" ] } ], "source": [ "i1.getClass() // 오브젝트가 아니라서 안됨" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "ename": "CompilationException", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1m\u001b[30m| \u001b[1m\u001b[30m\u001b[0m\u001b[1m\u001b[30m\u001b[41md1.getClass\u001b[0m\u001b[1m\u001b[30m() // 오브젝트가 아니라서 안됨\u001b[0m", "\u001b[1m\u001b[31mdouble cannot be dereferenced\u001b[0m", "" ] } ], "source": [ "d1.getClass() // 오브젝트가 아니라서 안됨" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "Integer i2 = new Integer(2);\n", "Double d2 = new Double(2.2);" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "class java.lang.Integer" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "i2.getClass()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "class java.lang.Double" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d2.getClass()" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "class java.lang.Integer" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "((Object) i1).getClass()" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "((Object) i1) instanceof Integer" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "((Object) i1) instanceof Double" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "class java.lang.Integer" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "new Integer(i1).getClass()" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "new Integer(i1) instanceof Integer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Classes and Instances\n", "(클래스와 인스턴스)\n", "\n", "일반적으로 프로그래밍 언어에서 어떤 타입의 값, 예를 들어 `int`의 값 `1`이나 또다른 값 `2`를 생각해 보라.\n", "`int`를 집합으로 보자면 `1`이나 `2`는 그 원소이다.\n", "이와 같이 일반적으로 어떤 타입의 값을 해당 타입의 *인스턴스*라고 부르기도 한다.\n", "\n", "OOPL에서는 다양한 종류의 오브젝트를 직접 설계하여 정의할 수 있는 기능을 제공한다.\n", "예를 들면 *직각삼각형*이라는 종류를 설계한다거나 *직사각형*이라는 종류를 설계할 수 있다.\n", "그리고 각 종류에 속하는 다양한 오브젝트를 생각해 볼 수 있다.\n", "예를 들어 직각으로 만나는 변의 길이가 모두 1로 같은 직각삼각형 오브젝트,\n", "직각으로 만나는 변의 길이가 3과 4인 직각삼각형 오브젝트를 생각해 볼 수 있다.\n", "이런 각각의 삼각형 오브젝트는 *직각삼각형*이라는 종류의 **인스턴스**라고 한다.\n", "그러니까 세 변의 길이가 모두 1인 오브젝트도 *삼각형*의 인스턴스이고\n", "변의 길이가 3,4,5인 직각삼각형도 삼각형 이라는 종류의 인스턴스이다.\n", "\n", "Java의 경우 어떤 종류(class)의 오브젝트를 만들지 설계하기 위해 `class`라는 키워드를 사용한다." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "class RightTri {\n", " // 인스턴스 변수 (양의 정수값으로만 설정된다고 가정)\n", " int width;\n", " int height;\n", " \n", " // 직각삼각형의 넓이를 계산하는 인스턴스 메소드\n", " double area() {\n", " return width * height / 2;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$32$RightTri@8f8ee12" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RightTri tri1 = new RightTri();\n", "tri1.width = 10;\n", "tri1.height = 10;\n", "\n", "tri1" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri1.width" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri1.height" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "50.0" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri1.area()" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$32$RightTri@1c66d2a7" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RightTri tri2 = new RightTri();\n", "tri2.width = 30;\n", "tri2.height = 40;\n", "\n", "tri2" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "30" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri2.width" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "40" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri2.height" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "600.0" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri2.area()" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri1 instanceof RightTri" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri1 instanceof Object" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(Object) tri1 instanceof System // Object로 형변환해서 instancof하는 것이 바람직" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"abc\" instanceof Object // 참고로 문자열(String)은 원시 타입이 아닌 오브젝트" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"abc\" instanceof String" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Constructors\n", "(생성자)\n", "\n", "생성자는 객체가 처음 생성될 때 해야 할 일을 처리하는 특별한 메소드며 클래스의 이름과 같은 이름으로 메소드를 정의하면 생성자가 된다." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "class RightTri {\n", " int width;\n", " int height;\n", " \n", " RightTri(int w, int h) {\n", " width = w;\n", " height = h;\n", " }\n", " \n", " // 직각삼각형의 넓이를 계산하는 메소드\n", " double area() {\n", " return width * height / 2;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "600.0" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RightTri tri3 = new RightTri(30,40); // 한줄로 width와 height 초기화\n", "\n", "tri3.area()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overriding the `toString` method" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$32B$RightTri@49d9ec4" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri3 // 그다지 유용한 정보가 출력되지 않는다" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$32B$RightTri@49d9ec4" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri3.toString() // toString() 메소드의 결과를 보여주는 것임" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "helloworld" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"hello\" + \"world\" // String에 적용되는 +연산자는 두 문자열을 이어붙임" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "width=30, height=40" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"width=\" + tri3.width + \", height=\" + tri3.height // 이런 유용한 정보가 출력되게 할 수는 없나?" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "width=30, height=40" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "String.format(\"width=%d, height=%d\", tri3.width, tri3.height)" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "class REPL.$JShell$32B$RightTri" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri3.getClass()" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$32B.RightTri" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri3.getClass().getCanonicalName()" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "77438660" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri3.hashCode()" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "49d9ec4" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "String.format(\"%x\", tri3.hashCode())" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$32B.RightTri@49d9ec4" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// 오브젝트에 기본적으로 제공되는 toString() 메소드가 하는 일\n", "tri3.getClass().getCanonicalName() + \"@\" + String.format(\"%x\", tri3.hashCode())" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "class RightTri {\n", " int width;\n", " int height;\n", " \n", " RightTri(int w, int h) {\n", " width = w;\n", " height = h;\n", " }\n", " \n", " @Override // 써도 되고 안써도 되지만 써 주는게 좋다\n", " public String toString() {\n", " return String.format(\"RightTri( width=%d, height=%d )\", width, height);\n", " }\n", "\n", " // 직각삼각형의 넓이를 계산하는 메소드\n", " double area() {\n", " return width * height / 2;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "RightTri tri4 = new RightTri(40,60)" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "RightTri( width=40, height=60 )" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri4.toString()" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "RightTri( width=40, height=60 )" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `this` and `super`\n", "\n", "`this`는 클래스 내부에서 (즉, 인스턴스 메소드 안에서) *인스턴스 스스로를 언급*하는 키워드.\n", "\n", " - 자기 자신을 가리키는 키워드라고 설명하기도 함\n", " - 인스턴스 메소드 안에서 사용할 수 있으며, 클래스 메소드 안에서는 사용 못함\n", " - 대부분의 경우는 생략되어 있는 거라고 볼 수 있다\n", "\n", "`super`는 인스턴스 스스로를 *상위 클래스의 인스턴스로서* 언급하는 키워드.\n", "\n", " - 오버라이딩한 인스턴스 메소드가 아닌, 상위 클래스에서 정의된 원래의 인스턴스 메소드를 호출하려 할 때 주로 사용\n", "\n", "\n", "그리고 `this`와 `super`는 자기 자신 클래스나 부모 클래스 생성자를 호출하기 위한 용도로도 활용된다." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "class RightTri {\n", " int width;\n", " int height;\n", " \n", " RightTri(int width, int height) { // 이름이 겹쳐서 위에 있는 인스턴스 변수가 가려진다. (shadowing)\n", " this.width = width; // this를 쓰면 함수 바깥의 인스턴스 변수에 접근 가능\n", " this.height = height; // this를 쓰면 함수 바깥의 인스턴스 변수에 접근 가능\n", " }\n", " \n", " @Override // 써도 되고 안써도 되지만 써 주는게 좋다\n", " public String toString() {\n", " return super.toString() + String.format(\"( width=%d, height=%d )\", width, height);\n", " }\n", "\n", " // 직각삼각형의 넓이를 계산하는 메소드\n", " double area() {\n", " // return width * height / 2;\n", " return this.width * this.height / 2; // 위와 같은 의미\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$32F$RightTri@356efc2( width=20, height=40 )" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RightTri tri4 = new RightTri(20,40);\n", "\n", "tri4" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$32F$RightTri@356efc2( width=20, height=40 )" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tri4.toString()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inheritance\n", "(상속)\n", "\n", "Java와 같은 객체지향언어에서는 상속을 통한 클래스의 계층구조(hierarchy)를 표현할 수 있다.\n", "\n", "지금까지는 직각삼각형 클래스를 설계해 보았는데 이제 직사각형 클래스도 비슷하게 설계해 보자." ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": [ "class Rectangle {\n", " int width;\n", " int height;\n", " \n", " Rectangle(int width, int height) {\n", " this.width = width;\n", " this.height = height;\n", " }\n", " \n", " @Override\n", " public String toString() {\n", " return super.toString() + String.format(\"( width=%d, height=%d )\", width, height);\n", " }\n", "\n", " // 직사각형의 넓이를 계산하는 메소드\n", " double area() {\n", " return width * height;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$69$Rectangle@6d4f6a79( width=30, height=40 )" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Rectangle rect1 = new Rectangle(30,40);\n", "\n", "rect1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "다양한 종류의 도형이 있을 수 있지만 간단히 예시를 작성하기 위해 편의상 우리는 직각삼각형과 직사각형만을 고려하기로 하자.\n", "\n", "`RightTri`과 `Rectangle` 클래스는 상당히 공통점이 많다.\n", "\n", "이렇게 공통되거나 중복되는 성질을 모아 직각삼각형과 직사각형을 모두 아우르는\n", "*도형*의 개념을 나타내는 상위 클래스인 `Shape`을 두고, 기존에 따로 있던\n", "`RightTri`과 `Rectangle` 클래스가 `Shape`을 상속받아 각각 특징적으로\n", "다르게 동작하는 부분만 추가로 작성하는 식으로 계층구조를 이루게 설계할 수 있다." ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "abstract class Shape { // abstract class는 직접 인스턴스를 생성할 수 없다\n", " int width;\n", " int height;\n", " \n", " Shape(int width, int height) {\n", " this.width = width;\n", " this.height = height;\n", " }\n", " \n", " @Override\n", " public String toString() {\n", " return super.toString() + String.format(\"( width=%d, height=%d )\", width, height);\n", " }\n", "\n", " abstract double area(); // abstract method - 하위 클래스에서 오버라이드 해야만 구체적인 클래스로 정의된다\n", "}\n", "\n", "class RightTri extends Shape {\n", " RightTri(int width, int height) { super(width, height); } // 상위 클래스인 Shape의 생성자 호출\n", " \n", " @Override\n", " double area() { return width * height / 2; } // 삼각형 넓이공식에 맞게\n", "}\n", "\n", "class Rectangle extends Shape {\n", " Rectangle(int width, int height) { super(width, height); } // 상위 클래스인 Shape의 생성자 호출\n", " \n", " @Override\n", " double area() { return width * height; } // 직사각형 넓이공식에 맞게\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "클래스 계층 구조를 그림으로 나타내면 다음과 같다.\n", "```\n", " Object\n", " |\n", " .\n", " .\n", " .\n", " |\n", " Shape\n", " / \\ \n", "RightTri Rectangle\n", "```\n", "\n", "`instanceof`는 클래스 계층 구조를 따라 오르내릴 수 있는지를 검사한다.\n", "\n", "예를 들어서 어떤 객체 `o`가\n", " - `o instanceof Rectangle`을 만족한다면 (즉, `true` 이면)\n", " - `o instanceof Shape`도 항상 만족하고 (즉, 항상 `true`)\n", " - `o instanceof Object`도 항상 만족한다 (즉, 항상 `true`)\n", " - `o instanceof RightTri`는 에러 발생!!! `true`나 `false`같은 정상적인 값이 나오는 게 아님!!!\n", " - `o instanceof String`는 에러 발생!!! `true`나 `false`같은 정상적인 값이 나오는 게 아님!!!\n", "\n", "예를 들어서 어떤 객체 `o`가\n", " - `o instanceof Shape`을 만족한다면 (즉, `true` 이면)\n", " - `o instanceof Object`도 항상 만족한다 (즉, 항상 `true`)\n", " - `o instanceof RightTri`은 만족할 수도 있고 아닐 수도 (즉, `true`가 `false` 중 하나)\n", " - `o instanceof Rectangle`은 만족할 수도 있고 아닐 수도 (즉, `true`가 `false` 중 하나)\n", " - `o instanceof String`는 에러 발생!!! `true`나 `false`같은 정상적인 값이 나오는 게 아님!!!\n", "\n", "\n", "\n", "참고로 `toString`은 `Object`에 기본 동작이 정의된 것을 `Shape`에서 오버라이딩한 것이다.\n", "\n", "`area`와 같이 기본 동작이 정의되지 않는 메소드의 경우는 추상 메소드(abstract)로서 선언하며,\n", "추상 메소드를 하나라도 포함하는 클래스는 추상 클래스(abstrct class)로 정의해야 한다.\n", "\n", "추상 클래스는 직접 인스턴스를 만들 수 없으며 상속받은 구체적인 하위 클래스를 통해 인스턴스를 만들 수 있다.\n", "\n", "참고로 이렇게 프로그래밍 언어에서 자녀(하위) 클래스(타입) 오브젝트를\n", "부모(상위) 클래스(타입) 오브젝트로 취급할 수 있도록 하는 것을\n", "*하위타입다형성*(subtype polymorphism)이라고 한다." ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "ename": "CompilationException", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1m\u001b[30m| \u001b[1m\u001b[30m\u001b[0m\u001b[1m\u001b[30m\u001b[41mnew Shape(30,40)\u001b[0m\u001b[1m\u001b[30m; // 추상 클래스는 new로 인스턴스 만들 수 없음!!!\u001b[0m", "\u001b[1m\u001b[31mShape is abstract; cannot be instantiated\u001b[0m", "" ] } ], "source": [ "new Shape(30,40); // 추상 클래스는 new로 인스턴스 만들 수 없음!!!" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "ename": "CompilationException", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1m\u001b[30m| \u001b[1m\u001b[30mShape shape = \u001b[0m\u001b[1m\u001b[30m\u001b[41mnew Shape(30,40)\u001b[0m\u001b[1m\u001b[30m; // 추상 클래스는 new로 인스턴스 만들 수 없음!!!\u001b[0m", "\u001b[1m\u001b[31mShape is abstract; cannot be instantiated\u001b[0m", "" ] } ], "source": [ "Shape shape = new Shape(30,40); // 추상 클래스는 new로 인스턴스 만들 수 없음!!!" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [], "source": [ "Shape shape1 = new RightTri(30,40); // 자녀 클래스 인스턴스를 부모 클래스 타입의 변수에 지정\n", "Shape shape2 = new Rectangle(30,40); // 자녀 클래스 인스턴스를 부모 클래스 타입의 변수에 지정" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$32H$RightTri@719660d6( width=30, height=40 )" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape1" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape1 instanceof Object" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$69C$Rectangle@9fb31bc( width=30, height=40 )" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape2" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "600.0" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape1.area()" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1200.0" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape2.area()" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1800.0" ] }, "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape1.area() + shape2.area()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Object equailty (`==`) vs. `equals` method" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "int x1 = 3;\n", "int x2 = 3;\n", "x1 == x2 // 기본 타입에 대한 equality" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": [ "Rectangle rect3 = new Rectangle(30,40);\n", "Rectangle rect4 = new Rectangle(30,40);\n", "Shape shape3 = rect3;\n", "Shape shape4 = rect4;\n", "Shape shape5 = rect3;" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$69C$Rectangle@418f9cbc( width=30, height=40 )" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape3" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$69C$Rectangle@61fab81d( width=30, height=40 )" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape4" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$69C$Rectangle@418f9cbc( width=30, height=40 )" ] }, "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape5" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape3 == shape4 // 오브젝트끼리 == 비교는 내용이 아니라" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape3 == shape5 // 오브젝트끼리 == 비교는 new로 만든 바로 같은 그 물건인지를 검사" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "참고로 내용이 같은지 검사하는 함수를 제공하고 싶다면 `Object`의 `equals`를 **잘** 오버라이드하면 된다. 그런데 이걸 **잘**하는 것은 생각보다 좀 귀찮은 일이 될 수 있다.\n", "왜냐하면 `hashCode`도 함께 오버라이드해줘야 되기 때문이다. `hashCode`도 같이 오버라이드해야 하는 이유 등 더 자세한 내용은 스스로 Effective Java같은 책을 찾거나 해서 조사해 보라. (`toString`이나 `equals`같은 `Object`의 메소드에 대한 내용은 두잇 자바 교재 11장의 내용도 참고.)\n", "\n", "기본적으로는 `Object`의 `equals`는 Object equality 즉 `==` 연산자와 똑같이 동작하므로\n", "클래스를 만들 때 필요에 따라 오버라이딩해야 한다." ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [], "source": [ "abstract class Shape {\n", " int width;\n", " int height;\n", " \n", " Shape(int width, int height) {\n", " this.width = width;\n", " this.height = height;\n", " }\n", "\n", " abstract double area(); // abstract method\n", " \n", " @Override\n", " public String toString() {\n", " return super.toString() + String.format(\"( width=%d, height=%d )\", width, height);\n", " }\n", " \n", " @Override\n", " public int hashCode() { // equals를 오버라이드하려면 hashCode도\n", " return width + 31873 * height; // hashCode가 다르면 equals가 false가 되도록 정의해야 된다\n", " }\n", "}\n", "\n", "class RightTri extends Shape {\n", " RightTri(int width, int height) { super(width, height); } // 상위 클래스인 Shape의 생성자 호출\n", " \n", " @Override\n", " double area() { return width * height / 2; } // 삼각형 넓이공식에 맞게\n", " \n", " @Override\n", " public boolean equals(Object o) {\n", " if (this == o) return true; // 같은 오브젝트이면 내용을 볼것도 없이 같음\n", " \n", " if (o instanceof RightTri) { // 직각삼각형 인스턴스라면\n", " RightTri that = (RightTri) o; // 직각삼각형으로 형변환해\n", " return this.width == that.width // 실제 내용을 비교\n", " && this.height == that.height;\n", " } else\n", " return false; // 직각삼각형이 아니면 당연히 같지 않다고 판단\n", " }\n", "}\n", "\n", "class Rectangle extends Shape {\n", " Rectangle(int width, int height) { super(width, height); } // 상위 클래스인 Shape의 생성자 호출\n", " \n", " @Override\n", " double area() { return width * height; } // 직사각형 넓이공식에 맞게\n", "\n", " @Override\n", " public boolean equals(Object o) {\n", " if (this == o) return true; // 같은 오브젝트이면 내용을 볼것도 없이 같음\n", " \n", " if (o instanceof Rectangle) { // 직사각형 인스턴스라면\n", " Rectangle that = (Rectangle) o; // 직사각형으로 형변환해\n", " return this.width == that.width // 실제 내용을 비교\n", " && this.height == that.height;\n", " } else\n", " return false; // 직사각형이 아니면 당연히 같지 않다고 판단\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [], "source": [ "Rectangle rect6 = new Rectangle(30,40);\n", "Rectangle rect7 = new Rectangle(30,40);\n", "Shape shape6 = rect6;\n", "Shape shape7 = rect7;\n", "Shape shape8 = rect6;" ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$69G$Rectangle@137446( width=30, height=40 )" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape6" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$69G$Rectangle@137446( width=30, height=40 )" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape7" ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$69G$Rectangle@137446( width=30, height=40 )" ] }, "execution_count": 72, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape8" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape6 == shape7" ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape6 == shape8" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 75, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape6.equals(shape7) // 별개의 오브젝트지만 내용이 같음" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shape6.equals(shape8) // 같은 오브젝트니까 당연히 이렇게 나와야" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Some graphics in Jupyter\n", "주피터 환경은 웹기반이므로 그림을 출력하기에 편하다. 자바 표준라이브러리는 아니지만 주피터 환경의 IJava 커널에서 제공하는 기능으로 웹에서 표현가능한 형식이면 된다." ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ "String html1 = \"\";\n", "\n", "render(html1, \"text/html\");" ] }, { "cell_type": "code", "execution_count": 78, "metadata": {}, "outputs": [], "source": [ "var dispdata1 = render(html1, \"text/html\");" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 79, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dispdata1" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "class io.github.spencerpark.jupyter.kernel.display.DisplayData" ] }, "execution_count": 80, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dispdata1.getClass()" ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 81, "metadata": {}, "output_type": "execute_result" } ], "source": [ "String html2 = \"\";\n", "\n", "render(html2, \"text/html\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "사실 Java에서는 클래스 내부에 정의된 메소드가 아닌 그냥 함수라는 것이 없어서 클래스 안이 아닌 곳에서 저렇게 `render(...)`를 따로 부를 수가 없는데 이건 IJava 커널에서 별도로 제공하는 기능이라고 보면 된다. 좀더 순수하게 Java 문법만으로 하자면 다음과 같이 저 `render`가 실제로 Display라는 클래스의 클래스 메소드로 정의되어 있는 패키지를 가져와서(import) 클래스 메소드 호출을 해야 한다. 클래스 메소드가 무엇인지는 또 다음 기회에 설명하기로 하겠다." ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 82, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.github.spencerpark.ijava.runtime.*;\n", "\n", "Display.render(html1,\"text/html\");" ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 83, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Display.render(html2,\"text/html\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "두 개 이상을 마크업된 내용이나 이미지 등을 한꺼번에 연달아 출력하려면 `render` 대신 `display`를 쓰라." ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 84, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Display.render(html1,\"text/html\");\n", "Display.render(html2,\"text/html\"); // 셀 전체의 값은 마지막 문장의 값으로 취급되어 이것만 출력됨" ] }, { "cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "372311e0-2842-41cb-a781-368147b24386" ] }, "execution_count": 85, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Display.display(html1,\"text/html\");\n", "Display.display(html2,\"text/html\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## IJava kernel caveats\n", "\n", "보통의 Java 컴파일러와는 달리 메소드 내용에서 정의되지 않은 함수를 활용하더라도 에러가 나지 않는다.\n", "\n", "보통의 Java 컴파일러는 D클래스 및 f2 메소드를 포함하는 모듈이 불러올 수 없으면 C클래스의 f1 메소드 정의에서 에러가 난다.\n", "하지만 IJava 커멀에서는 당장 에러가 나지는 않고 f1 메소드가 호출된 다음에야 에러가 난다.\n", "왜냐하면 IJava 커널이 실행되는 주피터 환경에서는 프로그램을 부분부분 끊어 작성할 수 있다는 점을 고려하여,\n", "이후의 셀에서 D클래스와 f2 메수드를 정의하고 난 다음 C클래스의 f1 메소드를 호출하면 정상 작동하게 해 주려고 하기 때문이다.\n", "이런 IJava의 유연함 때문에 여러 셀에 걸쳐 상호 재귀적인 클래스 및 메소드를 작성할 수 있다는 것은 장점이기는 하지만,\n", "실수로 오타를 내서 메소드나 클래스 이름을 잘못 작성했음에도 즉시 에러가 나지 않아 오히려 문제 파악에 방해가 되기도 하니 주의하자." ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [], "source": [ "class C {\n", " int f1() {\n", " return new D().f2(); // error does not occur right away\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [ { "ename": "UnresolvedReferenceException", "evalue": "Attempt to use definition snippet with unresolved references in Snippet:ClassKey(C)#122-class C {\n int f1() {\n return new D().f2(); // error does not occur right away\n }\n}", "output_type": "error", "traceback": [ "\u001b[1m\u001b[30m| \u001b[1m\u001b[30mclass C {\u001b[0m", "\u001b[1m\u001b[30m| \u001b[1m\u001b[30m int f1() {\u001b[0m", "\u001b[1m\u001b[30m| \u001b[1m\u001b[30m return new D().f2(); // error does not occur right away\u001b[0m", "\u001b[1m\u001b[30m| \u001b[1m\u001b[30m }\u001b[0m", "\u001b[1m\u001b[30m| \u001b[1m\u001b[30m}\u001b[0m", "\u001b[1m\u001b[31mUnresolved dependencies:\u001b[0m", "\u001b[1m\u001b[31m - class D\u001b[0m" ] } ], "source": [ "new C().f1()" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [], "source": [ "class D {\n", " int f2() {\n", " return 999;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "999" ] }, "execution_count": 89, "metadata": {}, "output_type": "execute_result" } ], "source": [ "new C().f1()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "## 두잇 자바 책에서 좀 개념적으로 바람직하지 않은 표현\n", "\n", " * 어떤 클래스를 생성한다 (x) <=== 책에서 이런 표현을 반복적으로 쓰고 있음\n", " * 어떤 클래스의 인스턴스를 생성한다 (o)\n", " * 어떤 클래스의 오브젝트를 생성한다 (o)\n", " \n", "## 이 노트의 예제에서 다루지 않은 부분들도 있으니 책을 읽어보세요\n", "\n", "예를 들면 `this`를 생성자를 호출하는 용도로 활용하는 예제는 이 노트에는 포함되어 있지 않습니다.\n", "`super`를 부모 클래스 생성자를 호출하는 용도로 활용하는 내용은 위에서 다뤘는데\n", "`this`로 현재 클래스의 생성자를 호출하는 예제는 다루지 못했으니 책의 예제를 보고 학습하시면 됩니다." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Java", "language": "java", "name": "java" }, "language_info": { "name": "java" } }, "nbformat": 4, "nbformat_minor": 4 }