[JAVA]객체지향개념(클래스와 객체, 변수와 메소드, 메소드 오버로딩, 생성자)

Ming·2022년 9월 26일
1

자바

목록 보기
2/14
post-thumbnail

객체지향프로그래밍


객체지향프로그래밍이란?

객체지향프로그래밍(OOP) 기본 개념은 프로그램들이 길어지고 복잡해지면 변수도 많아지게 되고, 코드 또한 길어져서 해석을 할 때 시간이 오래 걸린다.

그래서 동일한 목적이나 기능을 하는 변수와 함수들을 각각 하나로 묶어서 객체를 만들고, 그 객체들끼리 상호 통신을 하면서 프로그램 전체가 잘 돌아갈 수 있도록 코드를 구성하는데 이것을 바로 객체지향 프로그래밍이다. 다시 말해 “객체를 각각의 기능으로 나눠서 필요할 때 객체를 생성하여 사용한다.” 라고 보면 된다.

객체지향 프로그래밍 VS 절차지향프로그래밍

절차지향프로그래밍은 수차적인 처리를 중요시 하며, 프로그램 전체가 유기적으로 연결이 되도록 만든 프로그래밍이다. 절차적 프로그래밍 언어는 시간에 흐름에 따라 코드를 작성한다.

차이점을 보자 하면, 객체지향은 말 그대로 객체 중심으로 프로그래밍 하고, 절차지향은 순차적인 중점인 프로그래밍인 것이라고 할 수 있다.

  • 객체지향언어 C++, JAVA, PYTHON 등
  • 절자지향언어 C, FORTRAN, BASIC 등

객체지향프로그래밍(OOP)의 특징

  • 캡슐화는 코드의 재사용성을 높이고 유지보수를 원할하게 할 수 있다.
  • 상속은 부모클래스의 속성과 기능을 자식클래스에 전달하는 기능으로, 코드의 재사용성을 높이고 중복코드를 줄임으로써 메모리 효율 향상을 위한 장점이 있다.
  • 다형성은 상속 관계에서 자식클래스의 객체가 부모클래스의 인스턴스가 될 수 있도록 하는 기능 이며, 객관리를 용이하게 하고 코드를 줄일 수 있다는 장점이 있다.
  • 추상화는 상속을 위한 부모클래스를 만드는 데 사용되는 개념이며, 각 클래스의 공통된 특성을 추출하여 추상적 개념 클래스를 만드는 것이다. 상속을 발현하기 위한 보조적 특징이라 볼 수 있다.
  • 정보은닉은 클래스 내부의 속성은 숨기고 공개 인터페이스로만 접근하게 하여 정보보안을 높일 수 있고 또한 사이드 이펙트를 최소화 할 수 있다.

클래스와 객체


클래스와 객체

  • 클래스 : 객체를 정의해 놓은 것
  • 객체 : 실제로 존재하는 것. 사물 또는 개념
class Car{
	String name;
	String color;
	int speed;

	public void speedUp(){
		speed++;
	}
	public void speedDown(){
		speed--;
	}
}

pubilc class CarTest{
	public static void main(String[] args){
		Car car=new Car();
	}
}	

❗️파일이름과 동일한 이름의 클래스선언에만 public 접근 제한자를 붙일 수 있다.

클래스

데이터와 함수의 결합

  • 변수 : 하나의 데이터를 저장할 수 있는 공간
  • 배열 : 같은 타입의 여러 데이터를 저장할 수 있는 공간
  • 구조체 : 타입에 관계없이 서로 관련된 데이터들을 저장할 수 있는 공간
  • 클래스 : 데이터와 함수의 결합➡️ 구조체 + 함수

사용자 정의 타입(User-defined type)

프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새롭게 정의하는 것을 의미한다.

  • 예시
class Time{
	int hour;
	int minute;
	int second;
}

위와 같은 Time 클래스가 있다.

int hour1, hour2, hour3;
int minute1, minute2, minute3;
int second1, second2, second3;

Time time1=new Time();
Time time2=new Time();
Time time3=new Time();

3개의 시간을 다뤄야할 때 시간, 분, 초 변수를 각각 3개씩 총 9개의 변수를 사용하지 않고 Time 클래스를 이용하면 3개의 참조형 변수만 사용하면 된다.

객체(Object)와 인스턴스(Instance)

클래스에 의해 만들어진 객체를 인스턴스라고 한다.

Car car = new Car();
  • car은 인스턴스이다.
  • car은 객체이다.
  • car은 Car의 인스턴스이다.
  • car은 Car의 객체이다.

객체의 구성요소

객체는 속성과 기능으로 구성된다. 속성과 기능을 객체의 멤버(member)라고 한다.

클래스를 정의할 때 객체의 속성은 변수(variable), 기능은 메서드(method)로 정의한다.

class Car{
	String name;
	String color;
	int speed;

	public void speedUp(){
		speed++;
	}
	public void speedDown(){
		speed--;
	}
}

➡️ 속성은 name, color, speed이고 기능은 speedUp과 speedDown이다.

인스턴스의 생성과 사용

인스턴스 생성방법

클래스명 참조변수명; //객체를 다루기 위한 참조변수 선언
참조변수명 = new 클래스명(); //객체 생성 후, 생성된 객체의 주소를 참조변수에 저장
  • 예시 1
Car car;
car = new Car();
  • 예시 2
Car car=new Car();

➡️ new에 의해 Car 클래스의 인스턴스가 메모리의 빈 공간에 생성된다. 이 때, 멤버변수는 각 자료형의 해당하는 기본값으로 초기화된다. 참조변수 car를 이용해 Car 인스턴스에 접근할 수 있다.

❗️참조형 변수

  • 기본형 변수의 경우 Stack 영역에 공간을 할당 받는다.
  • 반면 참조형 변수의 경우 기본형 변수처럼 Stack영역에 참조변수명을 갖는 공간이 생성되고 이때 생성되는 변수의 크기는 4byte의 고정된 크기만 생성된다.
  • new 라는 명령어가 Heap영역에 새로운 저장공간을 생성하게 된다. 공간의 크기는 우리가 넣으려는 값(실제 값)의 크기만큼 생성하고 그 공간의 메모리 주소값을 할당받게 된다. 이때 할당 받은 주소값을 Stack영역에 저장한다.

인스턴스 사용

  • 예시1
Car car=new Car();
car.name="자동차"; //참조변수 car에 저장된 주소에 있는 인스턴스의 멤버변수 name에 자동차 저장한다.
car.speed=60; //참조변수 car에 저장된 주소에 있는 인스턴스의 멤버변수 speed에 60을 저장한다.
car.speedUp(); //참조변수 car가 참조하고 있는 인스턴스의 speedUp 메소드 호출한다.

  • 예시2
Car car1=new Car();
Car car2=new Car();
car2=car1; //car1의 주소를 car2에 저장
car1.name="자동차";
car1.speed=60;
System.out.println("car2의 speed는 "+car2.speed);
System.out.println("car1의 speed는 "+car1.speed);
car1.speedUp();
System.out.println("car2의 speed는 "+car2.speed);
System.out.println("car1의 speed는 "+car1.speed);
car2.speedDown();
System.out.println("car2의 speed는 "+car2.speed);
System.out.println("car1의 speed는 "+car1.speed);

[출력]
car2의 speed는 60
car1의 speed는 60
car2의 speed는 61
car1의 speed는 61
car2의 speed는 60
car1의 speed는 60

  • Car car1=new Car()과 Car car2=new Car() car1은 0x100 주소 할당받고 car2에는 0x200 주소를 할당받는다.
  • car2=car1 car1의 주소를 car2에 저장해주기 때문에 car2도 0x100 주소를 가리키게 된다.
  • car1.speedUp() 참조변수 car1에 저장된 주소에 있는 speedUp 메소드가 호출 되었기 때문에 car1의 speed가 1증가한다. car2도 car1의 주소가 저장되어있기 때문에 car2.speed를 출력하면 car1의 speed와 동일한 것을 확인할 수 있다.

변수와 메서드


메서드

메서드란, 작업을 수행하기 위한 명령문의 집합. 어떤 값을 입력받아서 처리하고 그 결과를 반환한다. 경우에 따라서는 입력받는 값이 없을 수도 있으며 결과를 반환하지 않을 수도 있다.

반환타입  메서드이름  (타입 변수명, 타입 변수명, ... )       //선언부
{                                                 //구현부
		// 메서드 호출 시 수행될 코드
}
int add (int a, int b)         //선언부
{                              //구현부
	int result = a + b;
	return result;  //호출한 메서드로 결과를 반환한다.

메서드의 선언부

메서드의 선언부는 메서드의 이름매개변수 선언 , 그리고 반환타입 으로 구성되어 있다.

매개변수 선언

  • 매개변수는 메서드가 작업을 수행하는데 필요한 값들(입력)을 제공받기 위한 것
  • 필요한 값의 개수만큼 변수를 선언하며 각 변수간의 구분은 쉼표 , 를 사용한다.
  • (주의) 일반적인 변수선언과 달리 두 변수의 타입이 같아도 변수의 타입을 생략할 수 없다.
	int add(int x, int y { ... }
	int add(int x, y).   { ... } //에러. 매개변수 y의 타입이 없다.
  • 선언할 수 있는 매개변수의 개수는 제한이 없지만, 입력해야할 값의 개수가 많은 경우에는 배열이나 참조변수를 사용하면 된다. 값을 전혀 입력받을 필요가 없다면 괄호 ( ) 안에 아무것도 적지 않는다.

반환타입

  • 메서드의 작업 수행 결과(출력)인 반환값(return value) 의 타입을 적는다. 단, 반환값이 없는 경우 반환타입으로 void 를 적어야 한다.

메서드의 구현부

메서드의 선언부 다음에 오는 괄호 { } 를 ‘메서드의 구현부’라고 하는데, 여기에 메서드를 호출했을 때 수행될 문장들을 넣는다.

return문

현재 실행 중인 메서드를 종료하고 호출한 메서드로 되돌아간다

  • 메서드의 반환타입이 ‘void’가 아닌 경우, 구현부 { } 안에 ‘return 반환값;’이 반드시 포함되어 있어야 한다.
  • 이 문장은 작업을 수행한 결과인 반환값을 호출한 메서드로 전달하는데, 이 값의 타입은 반환타입과 일치하거나 적어도 자동 형변환이 가능한 것이어야 한다.
  • return문은 단 하나의 값만 반환할 수 있다. 메서드로의 입력(매개변수)은 여러 개 일 수 있어도 출력(반환값)은 최대 하나만 허용한다.

반환값이 있는 메서드는 모든 경우에 return문이 있어야 한다

return문의 개수는 최소화시키는 것이 좋다

지역변수

메서드 내에 선언된 변수

  • 메서드 내에 선언된 변수들은 그 메서드 내에서만 사용할 수 있으므로 서로 다른 메서드라면 같은 이름의 변수를 선언해도 된다.
  • 매개변수도 메서드 내에 선언된 것으로 간주되므로 지역변수이다.
int add (int x, int y) {
	int result = x + y;
	return result;
}

int multiply (int x, int y) {
	int result = x * y;
	return result;
}
  • 위에 정의된 메서드 add와 multiply에 각기 선언된 변수, x, y, result는 이름만 같을 뿐 서로 다른 변수.

메서드의 호출

메서드를 구현 해도 호출되지 않으면 아무 일도 일어나지 않는다. 메서드를 호출해야만 구현부 { } 의 문장들이 수행된다. (main 메서드는 프로그램 실행 시 os에 의해 자동적으로 호출됨)

  • 메서드의 호출 방법
참조변수 . 메서드 이름 ();                  // 메서드에 선언된 매개변수가 없는 경우
참조변수 . 메서드 이름 (1,2, ... );     // 메서드에 선언된 매개변수가 있는 경우
print99danA11();        // void print99danA11()을 호출
int result = add(3, 5); // int add(int x, int y)를 호출하고, 결과를 result에 저장
  • 첫번째 코드를 호출할때는 메서드이름()을 써주면 된다. 반환타입은 void로 출력이 없다. 99단 전체를 화면에 출력하고 끝나기에 메서드가 return할 값이 없기 때문이다.
  • 두번째 코드는 add 메서드를 호출하는 것인데, add 메서드 같은 경우 덧셈을 할 때 2개의 입력값이 필요하다. 그리고 입력되어 나온 값을 int값으로 저장한다. 아래는 add 메서드를 호출했을 때의 처리과정이다.
  • add 메서드를 호출하고 3과 5를 입력값으로 주면, 3+5의 결과값을 int타입의 result변수에 저장하여 반환한다.
  • 첫번째 코드의 경우에는 작업 결과를 따로 저장할 필요가 없기에 최종 결과의 타입으로 선언한 변수에 저장할 필요가 없지만
  • add (3, 5) 메서드를 실행하면 add 메서드를 실행해서 반환한 결과값이 존재하기 때문에 해당 결과값을 저장할 int 타입의 변수 result를 선언하여 저장하는 것이다.

인수와 매개변수

  • 인수는 메서드를 호출할 때 괄호 ( ) 안에 지정해준 값들을 의미한다. 인수의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야 한다.
  • 인수는 메서드가 호출되면서 매개변수에 대입되므로, 인수의 타입은 매개변수의 타입과 일치하거나 자동 형변환이 가능해야 한다.
int result = add(1, 2, 3);   // 컴파일 에러. 메서드에 선언된 매개변수의 개수가 다름
int result = add(1.0, 2.0);  // 메서드에 선언된 매개변수의 타입이 다름

메서드의 실행 흐름

아래는 두 개의 값을 매개변수로 받아서 사칙연산을 수행하는 4개의 메서드를 가진 MyMath 클래스를 정의한 것이다

class MyMath {
	long add(long a, long b) {
		long result = a + b;
		return result;
// return a + b; // 위의 두줄을 이와 같이 한 줄로 간단하게 할 수 있다.
	}
	long subtract(long a, long b)   { return a - b; }
	long multiply(long a, long b)   { return a * b; }
	long divide(double a, double b) { retrun a / b; }
}
  • MyMath클래스의 ‘add(long a, long b)’를 호출하기 위해서는 먼저 ‘MyMath mm = new MyMath();’와 같이 해서, MyMath클래스의 인스턴스를 생성한 다음 참조변수 mm을 통해야 한다. 스크린샷 2022-09-22 오후 9.32.48.png

메서드 오버로딩


메소드 오버로딩이란

  • 메소드 오버로딩(overloading)이란 하나의 클래스에 같은 이름의 메소드를 중복하여 정의하는 것을 의미한다.
  • 원래 자바에서는 한 클래스 내에 같은 이름의 메소드를 2개 이상 가질 수 없다.
  • 함수의 다형성을 지원한다.

가장 쉬운 예로 자바에는 System.out.println() 이라는 출력 메소드가 존재한다.

➡️ 위 메소드에 int 타입을 매개변수로 주든, String 타입을 매개변수로 주든 모두 동일하게 System.out.println() 이라는 메소드를 호출


메소드 오버로딩의 조건

  • 예시 소스코드
    public class Example {
    
    	public static void main(String[] args) {
    		
    		Example ex = new Example();
    		
    		ex.overloadingEx();
    		ex.overloadingEx(10);
    		ex.overloadingEx(10, 20);
    		ex.overloadingEx(10, 3.14);
    		
    	}
    	void overloadingEx() {
    		System.out.println("컬러풀 라이언");
    	}
    
    	void overloadingEx(int i) {
    		System.out.println(i);
    	}
    
    	void overloadingEx(int x, int y) {
    	        System.out.println(x+y);
    	}
    
    	void overloadingEx(int i, double d) {
    	        System.out.println(i+d);
    	}
    
    }
  1. 메소드의 이름이 서로 같아야 한다.
  • 위의 예시 소스코드에는 overloadingEx() 라는 이름의 메소드가 4개를 사용한다.
  1. 매개변수의 개수, 순서, 혹은 타입이 달라야 한다.
  • 위에서부터 순서대로 메소드의 매개변수가 0개, 1개, 2개로 서로 다르다.
  • 3번째 메소드와 4번째 메소드는 매개변수의 수는 같지만 타입이 다르기 때문에 오버로딩이 가능하다.
  1. 매개변수의 개수와 타입은 같지만 리턴타입이 다른 경우에는 오버로딩이 불가능하다.
  • 메소드를 호출하는 부분에서는 호출 할 메소드의 이름과 매개변수 개수, 매개변수 타입으로 구분짓기 때문에 return 타입이 다르다고 해서 오버로딩이 성립되지 않는다(return 타입은 오버로딩 구현에 아무런 영향도 끼치지 못한다).

메소드 오버로딩을 사용하는 이유

  1. 같은 기능을 하는 메소드를 하나의 이름으로 사용할 수 있다.
  2. 메소드의 이름을 절약할 수 있다.
💡 위의 예시에 println() 메소드는 int형을 출력하든, char형을 출력하든 모두 동일한 기능을 제공한다. 하지만 타입에 따른 호출명이 다르다면 그 형태마다 메소드의 이름을 모두 알아야하고 비효율적이게 된다. 따라서 개발자의 편의를 고려하고 함수의 다형성을 지원하기 위해 고안된 개념이라고 볼 수 있다.

생성자


생성자란

  • 인스턴스(객체)가 생성될 때, 필드(변수)에 초기값을 제공하고 초기화 절차를 실행하는 메소드입니다.
  • 몇 가지 조건을 제외하고는 메소드와 같습니다.
  • 모든 클래스에는 반드시 하나 이상의 생성자가 있어야 합니다.

생성자의 조건

  • 생성자의 이름은 클래스의 이름과 같아야 합니다.
  • 생성자는 리턴값이 없습니다. (void를 작성하진 않습니다.)
//생성자 예제
class Pizza{
	int size;
	String type;
	public Pizza() {
		size = 12;
		type = "슈퍼슈프림";
	}
	public Pizza(int s, String t) {
		size = s;
		type = t;
	}
}

public class PizzaTest {
	public static void main(String[] args) {
		Pizza obj1 = new Pizza();
		System.out.println("("+obj1.type+", " + obj1.size+ ",)");
		
		Pizza obj2 = new Pizza(24, "포테이토");
		System.out.println("("+obj2.type+", " + obj2.size+ ",)");
	}
}

생성자에서 다른 생성자 호출

  • this()를 사용하면 생성자, 같은 클래스의 다른 생성자를 호출할 수 있습니다.
  • 다른 생성자의 호출은 생성자의 첫 문장세서만 가능합니다.
 class Car{
	String color;
	String gearType;
	int door;
	
	Car(){
		this("white", "auto", 4); // 순서대로 각 변수의 값을 입력하는 것과 같습니다.
	}
	Car(String c, String g, int d){
		color = c;
		gearType = g;
		door = d;
	}
}

기본 생성자

  • 기본 생성자는 매개변수가 없습니다.
  • 클래스에 정의된 생성자가 없으면 컴파일러가 자동으로 기본 생성자를 추가합니다
class BankAccount{
	int balance;
	public BankAccount() { // 컴파일러에 의해 자동 삽입되는 디폴트 생성자입니다.
		//empty
	}
	
	public int deposit(int amount) {...}
	public int withdraw(int amount) {...}
	public int checkMyBalance(int amount) {...}
}

참조변수 this

  • 인스턴스 자신을 가리키는 참조변수입니다.
  • 인스턴스의 주소가 저장되어있습니다.
  • 모든 인스턴스 메소드에 지역변수로 숨겨진 상태로 존재합니다.
class Car{
	String color;
	String gearType;
	int door;
	
	Car() {
		this("white", "auto", 4);
	}
	
	Car(String color, String gearType, int door){
		this.color = color; // 인스턴스 변수와 지역변수를 구별하기 위해 참조변수 this를 사용합니다.
		this.gearType = gearType;
		this.door = door;
	}
}

변수의 초기화


변수의 초기화

  • 변수를 선언하고 처음으로 값을 저장하는 것
  • 멤버변수 (인스턴스 변수, 클래스 변수)와 배열은 각 변수의 자료형의 기본값으로 자동초기화가 되기 때문에 초기화 생략 가능!
  • 지역변수는 자동초기화가 안되기 때문에 초기화 필수!

멤버변수 vs 지역변수
변수는 선언 위치에 따라 나누면 지역변수와 멤버변수로 구분할 수 있다.

‣ **멤버변수** : 클래스 영역에 선언
‣ **지역변수** : 메소드나 생성자 내부 선언, 메소드가 종료되면 자동으로 소멸

**멤버변수**는 다시 **클래스 변수**와 **인스턴스 변수**로 나눌 수 있다. 
‣ **클래스 변수** : 모든 객체가 공통적으로 똑같은 속성을 가질 때 사용한다. 
‣ **인스턴스 변수** : 각각의 객체(=인스턴스)마다 개별적인 속성을 가져야 할 때 사용한다. 
 class InitClasee{
    int x;     //**멤버변수**-인스턴스 변수
    int y = x;   //**멤버변수**-인스턴스 변수
    
    void method(){
    	int i;    //**지역변수**
    	int j = i;  //**컴파일 에러!** -> 지역변수가 초기화되지 않음!
    }
 }

멤버변수의 초기화

1. 명시적 초기화 (explicit initialization)

class Car {
		int door = 4;  //기본형 변수의 초기화
		Engine e = new Engine();  //참조형 변수의 초기화
}

2. 생성자 (constructor)

Car(String color, String gearType, int door){
		this.color = color;
		this.gearType = gearType;
		this.door = door;
}

3. 초기화 블럭 (initialization block)

class InitBlock {
		static {/* **클래스 초기화블럭** */}
		
		{/* **인스턴스 초기화블럭** */}
}
  • 클래스 초기화 블럭 명시적 초기화만으로 처리할 수 없는 클래스 변수의 복잡한 초기화에 사용되고 클래스가 로딩될 때 한 번 실행된다.
    class StaticBlockTest {
    		static int[] arr = new int[10]; // **명시적 초기화**
    		
    		static { // **클래스 초기화블럭**
    				for (int i=0; i<arr.length; i++){
    						arr[i] = (int)(Math.random() * 10) + 1;
    				}
    		}
    }
  • 인스턴스 초기화 블럭 생성자에서 공통적으로 수행되는 작업에 사용되며 인스턴스가 생성될 때마다 (생성자보다 먼저) 실행된다. 인스턴스 변수의 복잡한 초기화는 생성자를 이용하면 되므로 거의 사용되지 않고, 각 생성자에서 공통적으로 수행해야하는 작업을 따로 뽑아서 처리하는데 사용된다.

[출처]
자바의 정석-https://www.youtube.com/playlist?list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp

0개의 댓글

관련 채용 정보