객체지향언어의 특징
1.코드의 재사용성이 높다
-새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다
2.코드의 관리가 용이하다
-코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다
3.신뢰성이 높은 프로그래밍을 가능하게 한다
-제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며, 코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다
객체지향언어의 가장 큰 장점은 '코드의 재사용성이 높고 유지보수가 용이하다'는 것이다
상속, 다형성과 같은 객체지향개념을 학습할 때 재사용성과 유지보수 그리고 중복된 코드의 제거, 이 세 가지 관점에서 보면 쉽게 이해할 수 있을 것이다
클래스의 정의 : 클래스란 객체를 정의해 놓은 것이다
클래스의 용도 : 클래스는 객체를 생성하는데 사용된다
프로그래밍에서의 객체는 클래스에 정의된 내용대로 메모리에 생성된 것을 의미한다
객체의 정의 : 실제로 존재하는 것. 사물 또는 개념
객체의 용도 : 객체가 가지고 있는 기능과 속성에 따라 다름
유형의 객체 : 책상, 의자, 자동차, TV와 같은 사물
무형의 객체 : 수학공식, 프로그램 에러와 같은 논리나 개념
클래스 | 객체 |
---|---|
제품 설계도 | 제품 |
TV 설계도 | TV |
붕어빵 기계 | 붕어빵 |
클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate)라고 하며, 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라고 한다
객체는 속성과 기능, 두 종류의 구성요소로 이루어져 있으며, 일반적으로 객체는 다수의 속성과 다수의 기능을 갖는다.
속성(property) : 멤버변수(member variable), 특성(attribute), 필드(field), 상태(state)
기능(function) : 메서드(method), 함수(function), 행위(behavior)
클래스명 변수명; // 클래스의 객체를 참조하기 위한 참조변수를 선언
변수명 = new 클래스명();//클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장
TV t;//Tv클래스 타입의 참조변수 t를 선언
t= new TV();//T인스턴스 생성한 후, 생성된 Tv인스턴스의 주소를 t에 저장
class Tv{
String color;
boolean power;
int channel;
void power(){power=!power;}
void channelUp(){++channel;}
void channelDown(){--channel;}
}
public class TvTest {
public static void main(String args[]){
Tv t;
t= new Tv();
t.channel = 7;
t.channelDown();
System.out.println("현재 채널은 "+t.channel+" 입니다.");
}
}
인스턴스는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입은 인스턴스의 타입과 일치해야한다
객체 역시 배열로 다르는 것이 가능하며, 이를 '객체 배열'아록 한다.
객체 배열 안에 객체가 저장되는 것은 아니고, 객체의 주소가 저장된다.
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()};
class Tv{
String color;
boolean power;
int channel;
void power(){power = !power;}
void channelUp(){++channel;}
void channelDown(){--channel;}
}
public class TvTest4 {
public static void main(String[] args){
Tv[] tvArr= new Tv[3];
for(int i=0;i<tvArr.length;i++){
tvArr[i] = new Tv();
tvArr[i].channel = i+10;
}
for(int i=0; i<tvArr.length;i++){
tvArr[i].channelUp();
System.out.printf("tvArr[%d].channel=%d%n",i,tvArr[i].channel);
}
}
}
클래스는 '객체를 생성하기 위한 틀'이며 '클래스는 속성과 기능으로 정의되어있다'고 했다.
1.클래스-데이터와 함수의 결합
1. 변수 : 하나의 데이터를 저장할 수 있는 공간
2. 배열 : 같은 종류의 여러 데이터를 하나의 집합으로 저장할 수 있는 공간
3. 구조체 : 서로 관련된 여러 데이터를 종류에 관계없이 하나의 집합으로 저장할 수 있는 공간
4. 클래스 : 데이터와 함수의 결합(구조체 + 함수)
2.클래스 - 사용자정의 타입(user-defined type)
프로그래밍어너에서 제공하는 자료형(primitive type)외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 사용자정의 타입(user-defined type)이라고 한다.
변수는 클래스변수, 인스턴스변수, 지역변수 모두 세 종료가 있다..
변수의 종류를 결정짓는 중요한 요소는 '변수의 선언된 위치'이므로 변수의 종류를 파악하기 위해서는 변수가 어느 영역에 선언되었는지를 확인하는 것이 중요하다.
멤버변수를 제외한 나머지 변수들은 모두 지역변수이며, 멤버변수 중 static이 붙은 것은 클래스변수, 붙지 않은 것은 인스턴스변수이다.
class Variables{
int iv; //인스턴스 변수
static int cv;//클래스변수(static변수, 공유변수)
void method()
{
int lv = 0; //지역변수
}
}
변수의 종류 | 선언위치 | 생성시기 |
---|---|---|
클래스변수 | 클래스 영역 | 클래스가 메모리에 올라갈 때 |
인스턴스변수 | 클래스 영역 | 인스턴스가 생성되었을 때 |
지역변수 | 클래스 영역 이외의 영역 | 변수 선언문이 수행되었을 때 |
인스턴스변수는 인스턴스가 생성될 때 마다 생성되므로 인ㄴ스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.
메서드는 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것이다.
메서드를 사용하는 이유
1. 높은 재사용성(reusability)
2. 중복된 코드의 제거
-반복되는 문장들을 묶어서 하나의 메서드로 작성해 놓으면, 반보고디는 문장들 대신 메서드를 호출하는 한 문장으로 대체할 수 있다.
3. 프로그램의 구조화
메서드는 크게 두 부분, '선언부(header.머리)'와 '구현부(body, 몸통)'로 이루어져 있다.
int add(int a,int b)//선언부
{//구현부
int result = a + b;
return result;
}//구현부
메서드 선언부는 '메서드의 이름'과 '매개변수 선언', 그리고 '반환타입'으로 구성되어 있으며, 메서드가 작업을 수행하기 위해 어떤 값들을 필요로 하고 작업의 결과로 어떤 타입의 값을 반환하는지에 대한 정보를 제공한다.
메서드를 호출해야만 구현부{ }의 문장들이 수행된다.
return문은 현재 실행중인 메서드를 종료하고 호출한 메서드로 되돌아간다. 지금까지 반환값이 있을 때만 return문을 썼지만, 원래는 반환값의 유무에 관계없이 모든 메서드에는 적어도 하나의 return문이 있어야 한다.
void printShow(){
System.out.println("Show");
//return; 생략가능
}
호출스택 특징
-메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다
-메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다
-호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드이다
-아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드이다
public class CallStackTest {
public static void main(String[] args){
firstMethod();
}
static void firstMethod(){
secondMethod();
}
static void secondMethod(){
System.out.println("secondMethod()");
}
}
객체를 생성하지 않고도 메서드를 호출할 수 있으려면, 메서드 앞에 'static'을 붙여야 한다
public class CallStackTest2 {
public static void main(String[] args){
System.out.println("main이 시작됨");
firstMethod();
System.out.println("main이 끝남");
}
static void firstMethod(){
System.out.println("firstMethod가 시작됨");
secondMethod();
System.out.println("firstMethod가 끝남");
}
static void secondMethod(){
System.out.println("secondMethod가 시작됨");
System.out.println("secondMethod가 끝남");
}
}
각 메서드의 시작과 종료의 순서를 확인하는 예제이다.
기본형 매개변수 : 변수의 값을 읽기만 할 수 있다(read only)
참조형 매개변수 : 변수의 값을 읽고 변경할 수 있다(read & write)
매개변수뿐만 아니라 반환타입도 참조형이 될 수 있다.
class Data {int x;}
public class ReferenceReturnEx {
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.x=d.x;
return tmp;
}
}
반환하는 값이 Data객체의 주소이므로 반환 타입이 'Data'인 것이다.
반환타입이 '참조형'이라는 것은 메서드가 '객체의 주소'를 반환한다는 것을 의미한다.
메서드의 내부에서 메서드 자신을 다시 호출하는 것을 '재귀호출(recursive call)'이라 하고, 재귀호출을 하는 메서드를 '재귀 메서드'라 한다.
void method(){
method();// 재귀호출. 메서드 자신을 호출한다
}
호출된 메서드는 '값에 의한 호출(call by value)'을 통해, 원래의 값이 아닌 복사된 값으로 작업하기 때문에 호출한 메서드와 관계없이 독립적인 작업수행이 가능하다
public class FactorialTest {
public static void main(String[] args){
int result = factorial(5);
System.out.println(result);
}
static int factorial(int x){
int result=0;
if(x==1){
return result=1;
}else{
result = x*factorial(x-1);
}
return result;
}
}
재귀호출의 대표적인 예시인 팩토리얼이다.
재귀호출은 무한루프가 될 수 있기 때문에 조건문이 필수적으로 따라다닌다.
public class FactorialTest2 {
static long factorial(int n){
if(n<=0 || n>=20){
return -1;
}
if(n<=1){
return 1;
}
return n*factorial(n-1);
}
public static void main(String[] args){
int n=21;
long result = 0;
for(int i=1;i<=n;i++){
result = factorial(i);
if(result==-1){
System.out.printf("유효하지 않은 값입니다. (0<n<=20) : %d%n",n);
break;
}
System.out.printf("%2d!=%20d%n",i,result);
}
}
}
메서드 앞에 static이 부터 있으면 클래스메서드이고 붙어 있지 않으면 인스턴스 메서드이다.
인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다.
- 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
- 생성된 각 인스턴스는 서로 독립적이기 때문에 각 인스턴스 변수(iv)는 서로 다른 값을 유지한다. 그러나 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스변수로 정의해야 한다.
- 클래스 변수(static변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.
- static이 붙은 변수(클래스변수)는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다
- 클래스 메서드(static메서드)는 인스턴스 변수를 사용할 수 없다
-인스턴스변수는 인스턴스가 반드시 존재해야만 사용할 수 있는데, 클래스메서드(static이 붙은 메서드)는 인스턴스 생성 없이 호출가능하므로 클래스 메서드가 호출되었을 때 인스턴스가 존재하지 않을 수도 있다.- 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다
-메서드의 작업내용 중에서 인스턴스변수를 필요로 한다면, static을 붙일 수 없다.
-클래스의 멤버변수 중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있는지 살펴보고 있으면, static을 붙여준다.
-작성한 메서드 중에서 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 static을 붙일 것을 고려한다
class MyMath2 {
long a,b;
//인스턴스변수 a,b만을 이용해서 작업하므로 매개변수가 필요없다.
long add(){return a+b;}//a,b는 인스턴스변수
long subtract(){return a-b;}
long multiply(){return a*b;}
long divide(){return a/b;}
//인스턴스변수와 관계없이 매개변수만으로 작업이 가능하다
static long add(long a,long b){return a+b;}
static long subtract(long a, long b){return a-b;}
static long multiply(long a, long b){return a*b;}
static long divide(long a, long b){return a/b;}
}
public class MyMathTest2{
public static void main(String[] args){
//클래스메서드 호출. 인스턴스 생성없이 호출가능
System.out.println(MyMath2.add(200L,300L));
System.out.println(MyMath2.subtract(200L,300L));
System.out.println(MyMath2.multiply(200L,300L));
System.out.println(MyMath2.divide(200L,300L));
MyMath2 mm = new MyMath2();//인스턴스를 생성
mm.a=200L;
mm.b=300L;
//인스턴스메서드는 객체생성 후에만 호출이 가능함
System.out.println(mm.add());
System.out.println(mm.subtract());
System.out.println(mm.multiply());
System.out.println(mm.divide());
}
}
같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다.
단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
class TestClass{
void instanceMethod(){}//인스턴스메서드
static void staticMethod(){}//static 메서드
void instanceMethod2(){//인스턴스메서드
instanceMethod();//다른 인스턴스메서드를 호출한다
staticMethod();//static메서드를 호출한다
}
static void staticMethod2(){//static메서드
instanceMethod();//에러!!! 인스턴스메서드를 호출할 수 없다
staticMethod();//static메서드는 호출 할 수 있다
}
}
클래스멤버(클래스변수와 클래스메서드)는 언제나 참조 또는 호출이 가능하기 때문에 인스턴스멤버가 클래스멤버를 사용하는 것은 아무런 문제가 안된다. 클래스멤버간의 참조 또는 호출 역시 아무런 문제가 없다.
그러나, 인스턴스멤버는 반드시 객체를 생성한 후에만 참조 또는 호출이 가능하기 대문에 클래스멤버가 인스턴스멤버를 참조, 호출하기 위해서는 객체를 생성해야 한다.
한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것을 '메서드 오버로딩(method overloading)' 또는 간단히 '오버로딩(overloading)'이라 한다.
- 메서드 이름이 같아야 한다
- 매개변수의 개수 또는 타입이 달라야 한다
반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다
long add(int a, long b){return a+b;}
long add(long a, int b){return a+b;}
오버로딩으로 간주함
메서드의 이름만 보고도 '이 메서드들은 이름이 같으니, 같은 기능을 하겠구나.'라고 쉽게 예측할 수 있다.
메서드의 이름을 절약할 수 있다는 것이다.
public class OverloadingTest {
public static void main(String args[]){
MyMath3 mm = new MyMath3();
System.out.println(mm.add(3,3));
System.out.println(mm.add(3L,3));
System.out.println(mm.add(3,3L));
System.out.println(mm.add(3L,3L));
int[] a={100,200,300};
System.out.println(mm.add(a));
}
}
class MyMath3{
int add(int a,int b){
return a+b;
}
long add(int a, long b){
return a+b;
}
long add(long a, int b){
return a+b;
}
long add(long a, long b){
return a+b;
}
int add(int[] a){
int result = 0;
for(int i=0;i<a.length;i++){
result+=a[i];
}
return result;
}
}
기존에는 메서드의 매개변수 개수가 고정적이었으나 JDK1.5부터 도적으로 지정해 줄 수 있게 되었으며, 이 기능을 '가변인자(variable arguments)'라고 한다.
가변인자는 '타입... 변수명'과 같은 형식으로 선언한다.
생성자는 인스턴스가 생성될 떄 호출되는 '인스턴스 초기화 메서드'이다. 따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서도 사용된다.
- 생성자의 이름은 클래스의 이름과 같아야 한다
- 생성자는 리턴 값이 없다
class Card{
Card(){ //매개변수가 ㅇ벗는 생성자
...
}
Card(String k, int num){// 매개변수가 있는 생성자
...
}
...
}
연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다.
Card c = new Card();
1. 연산자 new에 의해서 메모리(heap)에 Card클래스의 인스턴스가 생성된다.
2. 생성자 Card()가 호출되어 수행된다.
3. 연산자 new의 결과로, 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.
기본 생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐이다
class Car{
String color;
String gearType;
int door;
Car(){}//생성자
Car(String c, String g, int d){//생성자
color=c;
gearType=g;
door=d;
}
}
같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능하려면 다음의 두 조건을 만족시켜야 한다.
-생성자의 이름으로 클래스이름 대신 this를 사용한다.
-한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
class Car{
String color;
String gearType;
int door;
Car(){
this("white","auto",4);
}
Car(String color){
this(color,"auto",4);
}
Car(String color, String gearType, int door){
this.color= color;
this.gearType=gearType;
this.door=door;
}
}
public class CarTest2 {
public static void main(String[] args){
Car c1 = new Car();
Car c2 = new Car("blue");
System.out.println(c1.color+c1.gearType+c1.door);
System.out.println(c2.color+c2.gearType+c2.door);
}
}
this를 사용하여 호출이 가능하다
this : 인스턴스 자시을 가리키ㅣ는 참조변수, 인스턴스의 주소가 저장되어 있다. 모든 인스턴스메서드에 지역변수로 숨겨진 채로 존재한다.
this(), this(매개변수) : 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.
class Car{
String color;
String gearType;
int door;
Car(){
this("white","auto",4);
}
Car(Car c){
color = c.color;
gearType = c.gearType;
door = c.door;
}
Car(String color, String gearType, int door){
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
인스턴스를 생성할 때는 다음의 2가지 사항을 결정해야한다
1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
2. 생성자 - 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?
변수를 선언하고 처음으로 값을 저장하는 것을 '변수의 초기화'라고 한다.
멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지므로 초기화하지 않고 사용해도 되지만, 지역변수는 사용하기 전에 반드시 초기화해야 한다.
class InitTest{
int x; //인스턴스변수
int y=x;//인스턴스변수
void method1(){
int i;//지역변수
int j = i;//에러. 지역변수를 초기화하지 않고 사용
}
}
멤버변수(클래스변수와 인스턴스변수)와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수적이다.
멤버변수의 초기화 방법
1. 명시적 초기화(explicit initialization)
2. 생성자(constructor)
3. 초기화 블럭(initialization block)
-인스턴스 초기화 블럭 : 인스턴스변수를 초기화 하는데 사용
-클래스 초기화 블럭 : 클래스변수를 초기화 하는데 사용
변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다.
class Car{
int door = 4; //기본형 변수의 초기화
Engine e = new Engine();//참조형 변수의 초기화
}
초기화 블럭에는 '클래스 초기화 블럭'과 '인스턴스 초기화 블럭' 두 가지 종류가 있다.
클래스 초기화 블럭 : 클래스변수의 복잡한 초기화에 사용된다
인스턴스 초기화 블럭 : 인스턴스변수의 복잡한 초기화에 사용된다
class InitBlock{
static { /* 클래스 초기화블럭 입니다. */}
{/* 인스턴스 초기화블럭 입니다. */}
//...
}
public class BlockTest {
static{//클래스 초기화 블럭
System.out.println("static { }");
}
{//인스턴스 초기화 블럭
System.out.println("{ }");
}
public BlockTest(){
System.out.println("생성자");
}
public static void main(String[] args){
System.out.println("BlockTest bt = new BlockTest(); ");
BlockTest bt = new BlockTest();
System.out.println("BlockTest bt2 = new BlockTest(); ");
BlockTest bt2 = new BlockTest();
}
}
클래스변수의 초기화시점 : 클래스가 처음 로딩될 때 단 한번 초기화 된다
인스턴스변수의 초기화시점 : 인스턴스가 생성될 때마다 각 인스턴스별로 초기화가 이루어진다
클래스변수의 초기화순서 : 기본값->명시적초기화->클래스 초기화 블럭
인스턴스변수의 초기화순서 : 기본값->명시적초기화->인스턴스 초기화 블럭->생성자