80년대 초 소프트웨어의 위기 - 빠른 변화를 못쫓아감
해결책으로 객체지향 언어를 도입(절차적 -> 객체지향)
c++ -> Java(1996)
코드의 재사용성이 높고 유지보수가 용이, 중복 코드를 제거해줍니다.
객체지향 언어 = 기존 프로그래밍 언어 + 객체지향개념(규칙)
구분 | 정의 | 용도 |
---|---|---|
클래스 | 클래스란 객체를 정의해 놓는것(설계도) | 클래스는 객체를 생성하는데 사용 |
객체 | 실제로 존재하는것, 사물 또는 개념(실제 제품) | 객체가 가지고 있는 기능과 속성에 따라 다름 |
속성과 기능
실제 사는 세상을 구현하다가 등장한 개념입니다.
객체 = 속성(변수) + 기능(메서드)
tv로 예를 들자면..
속성: 크기, 길이, 높이, 색상, 볼륨, 채널
기능: 켜기, 끄기, 볼륨높이기, 볼륨낮추기
세세한 차이가 있으나 거의 같은 의미입니다.
클래스가 왜 필요한가?
객체를 생성하기 위해(TV 설계도)
객체가 왜 필요한가?
객체를 사용하기 위해(TV 제품)
객체를 사용한다는 의미는?
객체가 가진 속성과 기능을 사용하려한다.
public 클래스가 있는 경우 소스파일의 이름은 반드시
public class이름과 일치해야 합니다.
//Hello2.java
public class Hello2 { }
class Hello3 { }
public class가 하나도 없는 경우, 소스파일의 이름은
'Hello2.java', 'Hello3.java'둘 다 가능합니다.
class Hello2 { }
class Hello3 { }
하나의 소스파일 안에 둘이상의 public class가 있는경우
public class Hello2 { }
public class Hello3 { }
소스파일의 이름이 public class와 일치하지 않는경우
//Hello2.java
public class Hello1 { }
class Hello2 { }
소스파일의 이름과 클래스의 이름의 대소문자를 구분하지 않은경우
//hello1.java
public class Hello1 { }
main함수가 있는 클래스로 소스파일의 이름을 생성해줘야합니다.
하나의 소스파일에는 하나의 클래스만 작성하는 것이 바람직합니다!
클래스명 변수명; // 클래스의 객체를 참고하기 위한 참조변수를 선언
변수명 = new 클래스명(); // 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장
예제)
//클래스 생성
class Tv {
String color;
boolean power;
int channel;
void power() { power = !power; }
void channelUP() { channel++; }
void channelDown() { channel--; }
}
//객체의 생성
Tv t; // Tv클래스 타입의 참조변수 t를 선언
t = new Tv(); // Tv 인스턴스를 생성한 후, 생성된 Tv인스턴스의 주소(메모리주소)를 t(리모콘같은 존재)에 저장
//객체의 사용
t.channel = 7; // Tv인스턴스의 멤버변수 channel의 값을 7로 한다.
t.channelDown(); // Tv인스턴스의 메서드 channelDown()을 호출한다.
System.out.println("현재 채널은 " + t.channel + " 입니다."); // "현재 채널은 6 입니다."
//객체를 2개생성하기
Tv t1 = new Tv();
Tv t2 = new Tv();
t1.channel = 7;
t2.channel = 10;
System.out.println("t1.channel : "+t1.channel+", t2.channel : "+t2.channel); // "t1.channel : 7, t2.channel : 10"
//프로그램을 종료하면 가비지 컬렉터가 객체의 참조변수들을 메모리에서 정리해준다.
객체배열 == 참조변수 배열
//객체배열 생성
Tv[] tvArr = new Tv[3];
//객체를 생성해서 배열의 각 요소에 저장
tvArr[0] = new Tv();
tvArr[1] = new Tv();
tvArr[2] = new Tv();
//아래와 같이 작성할 수도 있다.
Tv[] tvArr = {new Tv(), new Tv(), new Tv()};
데이터의 발전과정
1. 변수 - 하나의 데이터를 저장할 수 있는 공간
2. 배열 - 같은 타입의 여러 데이터를 하나로 저장할 수 있는 공간
3. 구조체 - 서로 관련된 여러 타입의 데이터를 하나로 저장할 수 있는 공간
4. 클래스 - 데이터와 함수의 결합(구조체 + 함수들)
사용자 정의 타입이란?
원하는 타입을 직접 만들 수 있습니다.
연관성 높은 변수나 함수를 묶어서 클래스를 만들어서 원하는 타입으로 참조변수를 생성할 수 있습니다.
예제)
//객체가 등장하기전
int hour = 12;
int minute = 34;
int second = 56;
//변수로 생성하는 것보다 클래스로 묶어서 생성하는 것이 관리에 용이하다
class Time {
int hour;
int minute;
int second;
}
Time t = new Time();
t.hour = 12;
t.minute = 34;
t.second = 56;
변수의 종류 | 선언위치 | 생성시기 |
---|---|---|
클래스 변수 | 클래스 영역 | 클래스가 메모리에 올라갈 때 |
인스턴스 변수 | 클래스 영역 | 인스턴스가 생성되었을 때 |
지역 변수 | 클래스 영역 이외의 영역 | 변수 선언문이 수행 되었을 때 |
class Variables { //클래스영역
//클래스 영역에서는 선언문만 가능하다!
static int cv; // 클래스 변수
int iv; //인스턴스변수
void method() { //메서드 영역
int lv = 0; // 지역변수
//메서드 종료시 자동제거된다.
}
}
인스턴스변수
클래스 변수
예제)
class Card {
String kind; //무늬
int number; //숫자
static int width = 100; //폭
static int height = 250; //높이
}
Card c = new Card(); //객체 생성
c.kind = "HEART";
c.number = 5;
//객체를 이용해서 클래스변수를 조작할 수 있다.
//그러나 권장되는 방법은 아니다.
//다른 공동 개발자들이 해당 변수가 인스턴스 변수로 착각할 수도 있다.
c.width = 200;
c.height = 300;
//클래스 변수는 클래스로 접근해서 조작하는 것을 권장한다.
Card.width = 200;
Card.height = 300;
예제)
public class MethodTest {
//배열을 만드는 함수
static int[] makeArr() {
int[] numArr = new int[10];
for(int i=0; i<10; i++) {
numArr[i] = (int)(Math.random() * 10);
}
return numArr;
}
//배열을 출력하는 함수
static void printArr(int[] num Arr){
for(int i=0; i<10; i++) {
System.out.printf("%d", numArr[i]);
System.out.println();
}
}
public static void main(String args[]) {
int[] numArr1 = makeArr(); //배열생성
printArr(numArr1); //배열 출력
int[] numArr2 = makeArr(); //배열생성
printArr(numArr2); //배열 출력
//......
}
}
메서드 = 선언부 + 구현부
반환타입(출력) 메서드이름(타입 변수명1, 타입 변수명2, ....)//선언부 {
//메서드 호출시 수행될 코드
return 반환값(반환타입과 동일해야함)
} // 구현부
//예시
int add(int x, int y) {
int result = x + y;
return result;
}
//x, y, result 전부 지역변수이다.
//위 변수들은 함수 블럭안에서만 사용할 수 있다.
메서드의 장점
메서드의 작성방법
매개변수가 있는 경우
메서드의 매개변수(파라미터)는 메서드가 선언한 갯수와 타입에 맞게 사용해야 합니다.
return값이 없는경우
메서드이름(값1, 값2, .....); // 메서드를 호출하는 방법
return값이 있는경우
변수 = 메서드이름(값1, 값2, .....); // 메서드를 호출하는 방법
예제)
public class Ex6_4 {
public static void main(String[] args) {
MyMath mm = new MyMath();
long result1 = mm.add(5L, 3L);
long result2 = mm.subtract(5L, 3L);
long result3 = mm.multiply(5L, 3L);
long result4 = mm.divide(5L, 3L);
System.out.println("add(5L, 3L) = "+result1);
System.out.println("subtract(5L, 3L) = "+result2);
System.out.println("multiply(5L, 3L) = "+result3);
System.out.println(" mm.divide(5L, 3L) = "+result4);
}
}
class MyMath {
long add(long a, long b) {
return a + b;
}
long subtract(long a,long b) {
return a - b;
}
long multiply(long a, long b) {
return a * b;
}
long divide(long a, long b) {
return a / b;
}
}
//결과
//add(5L, 3L) = 8
//subtract(5L, 3L) = 2
//multiply(5L, 3L) = 15
//mm.divide(5L, 3L) = 1
메서드의 실행흐름
실행 중인 메서드를 종료하고 호출한 곳을 되돌아갑니다.
void printGugudan(int dan) {
if(!(2<= dan && dan <= 9)) {
return; // dan의 값이 2~9가 아닌경우, 호출한 곳으로 되돌아간다.
}
for(int i=1; i<=9; i++) {
System.out.printf("%d * %d = %d\n", dan, i, dan * i);
}
return; 반환타입이 void이므로 생략가능, 컴파일러가 자동추가
}
반환타입이 void가 아닌 경우, 반드시 return문이 필요합니다.
//조건문의 경우 반드시 모든 조건 상황에 return문을 작성하거나 조건문 밖에 작성해야한다.
int max(int a, int b) {
if(a > b) {
return a;
} else {
return b;
}
}
반환값
return에 들어가는 값은 반드시 함수를 선언할때 작성한 반환타입과 일치해야합니다.
스택(stack)
밑이 막힌 상자. 위에 차곡차곡 쌓인다.
호출스택(call stack)
메서드 수행에 필요한 메모리가 제공되는 공간
메서드가 호출되면 호출스택에 메모리 할당, 종료되면 해제
아래 있는 메서드가 위의 메서드를 호출한 순서대로 쌓인다.
맨 위의 메서드 하나만 실행중, 나머지는 대기중
기본형 매개변수(기본형 8가지타입) - 변수의 값을 읽기만 할 수 있습니다.
예제)
class Data { int x; }
class Ex6_6 {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
System.out.println("main() : x = "+d.x);
change(d.x);
System.out.println("After change(d.x)");
System.out.println("main() : x = "+d.x);
}
static void change(int x) { //기본형 매개변수
x = 1000;
System.out.println("change() : x = "+x);
}
}
결과)
main() : x = 10
change() : x = 1000
After change(d.x)
main() : x = 10
변수의 값을 읽고 변경할 수 있습니다.
예제)
class Data { int x; }
class Ex6_7 {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
System.out.println("main() : x = "+d.x);
change(d);
System.out.println("After change(d.x)");
System.out.println("main() : x = "+d.x);
}
static void change(Data d) { //참조형 매개변수
d.x = 1000;
System.out.println("change() : x = "+d.x);
}
}
결과)
main() : x = 10
change() : x = 1000
After change(d.x)
main() : x = 1000
예제)
class Ex6_8 {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
Data d2 = copy(d);
System.out.println("d.x = "+d.x);
System.out.println("d2.x = "+d2.x);
}
static Data copy(Data d) { //참조형 매개변수
Data tmp = new Data(); // 새로운 객체 tmp를 생성한다.
tmp.x = d.x; // d.x의 값을 tmp.x에 복사한다.
return tmp; // 복사한 객체의 주소를 반환한다.
}
}
결과)
d.x = 10
d2.x = 10
인스턴스 메서드
static메서드(클래스메서드)
예제)
class MyMath2 {
long a, b; // 인스턴스 변수
long add() { // 인스턴스 메서드
return a + b; // 인스턴스 메서드는 인스턴스 변수사용가능
}
static long add(long a, long b) { // 클래스메서드(static메서드)
return a + b;
}
}
//사용법
MyMath2.add(200L, 100L); //클래스메서드 호출
MyMath2 mm = new MyMath2(); //인스턴스(참조변수)를 먼저 생성해야한다.
mm.a = 200L; // 인스턴스 변수 대입
mm.b = 100L;
mm.add(); // 인스턴스메서드 호출
static변수는 속성(멤버 변수)중에서 공통 속성에 붙인다.
static메서드는 인스턴스변수를 사용하지 않는 메서드에 붙인다.
static메서드는 인스턴스 변수를 사용할 수 없습니다.
예제)
class TestClass {
int iv; // 인스턴스 변수
static int cv; // static 변수
void instanceMethod() {}
static void staticMethod() {]
void instanceMethod2() { // 인스턴스 메서드
System.out.println(iv); //인스턴스 변수를 사용할 수 있다.
System.out.println(cv); //클래스 변수를 사용할 수 있다.
instanceMethod(); // 인스턴스 메서드를 호출할 수 있다.
staticMethod(); // static 메서드를 호출할 수 있다.
}
static void staticMethod2() { // static메서드
//System.out.println(iv); //에러!! 인스턴스 변수를 사용할 수 없다.(객체생성이 필요)
System.out.println(cv); //클래스 변수는 사용할 수 있다.
//instanceMethod(); //에러!! 인스턴스 메서드를 호출할 수 없다.
staticMethod(); // static 메서드를 호출할 수 있다.
}
}
한 클래스 안에 같은 이름의 메서드를 여러 개 정의하는 것을 의미합니다.(중복정의)
대표적인 예로 println이 있다.
void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(float x)
void println(int x)
void println(long x)
void println(Object x)
void println(String x)
오버로딩이 성립하기 위한 조건 3가지 조건
//다음과 같은 케이스는 모호하여 에러가 발생한다.(ambiguous)
long add(int a, long b) { return a+b; }
long add(long a, int b) { return a+b; }
add(3, 3);//에러발생!
오버로딩의 올바른 예
매개변수는 다르지만 같은 의미의 기능수행할 때 사용합니다.
예제)
class MyMath {
int add(int a, int b) {
System.out.println("int add(int a, int b)");
return a + b;
}
long add(long a, long b) {
System.out.println("long add(long a, long b)");
return a + b;
}
int add(int[] a) {
System.out.println("int add(int[] a)");
int result = 0;
for(int i : a) {
result += i;
}
return result;
}
}
인스턴스가 생성될 때마다 호출되는 '인스턴스 초기화 메서드'
인스턴스 생성시 수행할 작업(iv 초기화에 사용)
Time t = new Time();
t.hour = 12;
t.minute = 34;
t.second = 56;
//생성자를 이용하면 아래와 같이 개선할 수 있다.
Time t = new Time(12, 34, 56);
생성자(construnct) 생성규칙
클래스이름(타입 변수명, 타입 변수명) {
//인스턴스 생성 시 수행될 코드
//주로 인스턴스 변수의 초기화 코드를 적는다.
}
기본 생성자(default constructor)
클래스이름() { }
Point() {} //Point라는 클래스의 기본 생성자
매개변수가 있는 생성자
예제)
class Car {
String color;
String gearType;
int door;
Car() {} //기본 생성자
Car(String c, String g, int d) { // 매개변수가 있는 생성자
color = c;
gearType = g;
door = d;
}
}
Car c = new Car("white, "auto", 4);
생성자에서 다른 생성자 호출할 때 사용합니다.
다른 생성자 호출시 첫 줄에서만 사용가능합니다.
코드의 중복을 제거할 때 사용합니다.
예제)
class Car2 {
String color;
String gearType;
int door;
Car2() { //Car2(String color, String gearType, int door)호출
this("white", "auto", 4);
}
Car2(String color) { //Car2(String color, String gearType, int door) 호출
this(color, "auto", 4);
}
Car2(String color, String gearType, int door) {
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
인스턴스 자신을 가리키는 참조변수를 의미합니다.
인스턴트 메서드(생성자포함)에서 사용가능합니다.
지역변수(lv)와 인스턴스변수(iv)를 구별할 때 사용합니다.
this()와 전혀 관계없습니다.
예제)
Car2(String color, String gearType, int door) {
this.color = color; // 인스턴스 변수 = 지역 변수
this.gearType = gearType;
this.door = door;
}
생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.
지역변수(lv)는 수동 초기화 해야합니다.(사용전에 꼭!)
멤버변수(iv, cv)는 자동 초기화됩니다.
class InitTest {
int x; // 인스턴스 변수
int y = x; // 인스턴스 변수
void method1() {
int i; // 지역변수
int j = i; // 에러!! 지역변수를 초기화하지 않고 사용
}
}
class Car {
int door = 4; // 기본형(primitive type) 변수의 초기화
Engine e = new Engine(); // 참조형(reference type) 변수의 초기화
2.초기화 블럭
복잡한 초기화에 사용한다.
class StaticBlockTest {
static int[] arr = new int[10]; // 명시적 초기화
static { // 클래스 초기화 블럭 - 배열 arr을 난수로 채운다.
for(int i=0; i<arr.length; i++) {
arr[i] = (int)(Math.random() * 10) +1;
}
}
}
클래스 변수의 초기화 시점 : 클래스가 처음 로딩될 때 단 한번
인스턴스 변수의 초기화 시점 : 인스턴스가 생성될 때 마다
예제)
class InitTest {
static int cv = 1; // 명시적 초기화
int iv = 1; // 명시적 초기화
static { cv = 2; } // 클래스 초기화 블럭
{ iv = 2; } // 인스턴스 초기화 블럭
InitTest () { //생성자
iv = 3;
}
}
초기화 순서
1. 클래스변수 다음 인스턴스 변수가 초기화
2. 자동 -> 간단 -> 복잡