java

java 자바의 꽃, 객체1

#풀닢 2022. 12. 29. 17:09

객체 지향 언어(OOP)란?


현실세계에서 사물이나 개념처럼 독립되고 구분되는 각각의 객체로 이루어져있어

발생하는 모든 사건들은 객체 간의 상호작용이다.

oop (Objecy Oriented Programming) 무엇가를 지향한다.

절차지향은 – 재사용성이 어려움

 

클래스와 인스턴스 == 객체 ==멤버 같은 거라고 보면 된다.

클래스란 객체를 추상화 한 것으로 인스턴스를 생성 할 목적으로 정의 해 놓은 소스 코드 작성 단위

 

객체란 현실에 존재하는 독립적이고 하나로 취급되는 사물이나 개념

클래스에 정의된 대로 new 연산자를 통해 heap에 할당된 공간(==인스턴스)

클래스 인스턴스(멤버들)
학생
학생이 가지는 공통적인 기능과 속성을
추상화하여 클래스로 정의
김철수
김영희
현실세계에서 존재하는 고유 객체를
취급하듯 개별 인스턴스를 메모리에 할당한다.

 

이해가 됐나용 

 

아뇨...

객체. 클래스

래스는 서로 다른 자료형의 데이터들을 사용자(개발자)정의로 하나로 묶어 새로운 타입을 정의한 것.

배열과 달리 다른 자료형들을 값을 하나로 묶을 수 있음 개똑똑해

배열은 똑같은 자료형만 묶기가 가능했지만 이제 객체지향적인 클래스에서는 다르다!

 

클래스를 처음에 선언 할 때는 public class ____ {} 이렇게 먼저 선언한다.

 

클래스 내부에 메소드를 작성하지 않고 바로 변수를 선언 할 수도 있다. 이러한 것을 전역변수라고 하는데

전역변수란 == 필드 ==인스턴스 변수 == 속성 다 같은 말이다.

 

먼저 클래스를 선언해 보자면,

public class Member {
	
	String id;
	String pwd;
	String name;
	int age;
	char gender;
	String[] hobby;
	
}

이렇게 쓸 수 있을 것이다.

 

위의 클래스 안에 서로 각각 다른 자료형 참조형 타입들이 모여 있다.

이러한 멤버들을 먼저 선언하고

동일한 폴더 내에 있는 다른 클래스(파일) 를 하나 더 생성하여 main 메소드 안에 방금 적었던 

자료형,참조형 자료들을 초기화 시켜 준다.

 

public static void main(String[] args) {
		
	String id = "user01";
	String pwd = "pass01";
	String name = "홍길동";
	int age = 20;
	char gender = 'M';
	String[] hobby = {"축구", "볼링", "테니스"};

이렇게 어떤 사람의 아이디와 패스워드, 이름 그리고 나이 등을 한꺼번에 적어서 관리하려고 하는데

 각각의 변수로 관리하게 되면 단점이 있다.

1. 변수명을 다 관리해야 한다. 

2. 모든 사람 정보를 인자로 메소드 호출 시에 전달해야 하면 너무 많은 인자로 전달해야 해서 눈에 한번에 들어오지 않는다.

3. 리턴은 1개의 값만 가능하기 때문에 이렇게 사람의 정보를 묶어서 리턴 값을 사용할 수 없다.

 

위와 같은 어려움 때문에 우리는 서로 다른 자료형의 데이터를 사용자(개발자) 정의로 하나로 묶어서 새로운 타입을 정의하려고 한다.

 

사용자 정의 자료형 사용하기

사용자 정의 변수 선언 및 객체를 이용하기 위해서는 new 연산자로 heap에 할당을 해야 한다.

객체를 생성하게 되면 클래스에 정의한 필드와 메소드대로 객체(instance)가 생성이 된다.

먼저 그 전의 파일 명, 나는 Member이다.

Member member = new Member(); 이렇게 객체를 생성한다.

	Member member = new Member();
		
		System.out.println(member.id);
		System.out.println(member.pwd);
		System.out.println(member.name);
		System.out.println(member.age);
		System.out.println(member.gender);
		System.out.println(member.hobby);

 

위와 같은 필드에 접근하기 위해서는 레퍼런스명.필드명으로 접근한다.
 변수명 앞에 붙인 '.'은 참조 연산자라고 하는데, 레퍼런스 변수가 참조(가르키는) 하고 있는 주소로 접근한다는 의미를 가진다. 
 각 공간에 필드명으로 접근한다. 배열은 인덱스로 접근, 객체는 필드명으로 접근해야 한다.

 

위는

null

null

null

0

이렇게 생성이 되어진다. pwd,age는 숫자형이기 때문에 따로 나오지 않지만 hobby가 0이 나온 이유는 배열이기 때문

heap에 생성 되기 때문에 위처럼 JVM 기본 값으로 초기화 된다.

 

자 이제는 필드에 접근해서 변수로 초기화 하듯 사용 해 보자.

	member.id = "user01";
	member.pwd = "pass01";
	member.name = "홍길동";
	member.age = 20;
	member.gender = 'M';
	member.hobby = new String[] {"축구", "볼링", "테니스"};

선언은  위와 같이 하면 된다.

그럼 출력은 어떻게 될까? 먼저 유추해보자

출력

더보기

null

user01

pass01

홍길동

20

M

[축구, 볼링, 테니스]

 

위를 프린트 하려면 콘솔을 찍어야 하는데 위와 같다.

궁금하면 아래 클릭 !

더보기

System.out.println(member.id);

System.out.println(member.pwd);

System.out.println(member.name);

System.out.println(member.age);

System.out.println(member.gender);

System.out.println(member.hobby);

System.out.println(Arrays.toString(member.hobby));

하지만 필드에 직접 접근할 때 생길 수 있는 문제점이 있다.

 

문제점1

1. 필드에 올바르지 않은 값이 들어가도 통제가 불가능하다.

양수를 입력해야 하는데 -의 음수를 입력한다면 그대로 출력되어진다. 이럴경우

 

위와 같이 하나의 폴더 안에 두개의 파일을 만들어서 객체를 호출한 다음 똑같이 필드로 몬스터 이름과 체력을 저장할 공간을 선언 한 후 안에 또 다른 메소드를 작성하여

 

public void setHp(int hp) {
if(hp > 0) {
	System.out.println("양수 값이 입력 되어 몬스터의 체력을 입력한 값으로 변경합니다.");

this.hp = hp;
} else {
	System.out.println("0보다 작거나 같은 값이 입력 되어 몬스터의 체력을 0으로 변경합니다.");
	this.hp = 0;
	}

여기서 this는 ?

인스턴스가 생성 되었을 때 자신의 주소를 저장하는 레퍼런스 변수이다.
 지역변수와 전역변수의 이름이 동일한 경우 지역 변수에 우선적으로 접근하기 때문에

전역변수에 접근하기 위해 this.를 명시하여 가르키는 게 누군지 정확하게 파악해 준다.

 

	Monster monster3 = new Monster();
	monster3.name = "프랑켄슈타인";
	monster3.setHp(-100);

	System.out.println("monster3 name : " + monster3.name);
	System.out.println("monster3 hp : " + monster3.hp);

출력 한다면 

monster2 hp : -100 지만

0보다 작거나 같은 값이 입력 되어 몬스터의 체력을 0으로 변경합니다. 로 변경이 된다.

 

monster3 name : 프랑켄슈타인

monster3 hp : 0

이렇게 변경이 되는 걸 볼 수 있다. 그대로 -100이 나오지 않는다.

문제점2

위에 몬스터 name을 kinds로 필드에서 변경했기 대문에 직접 name 필드에 접근하는 코드는 모두 컴파일 에러를 발생한다. 그래서 개발자가 몬스터 클래스의 일부는 수정했지만 사용하는 곳에서 전부 함께 수정해줘야 하는 힘듦이 있다.
이러한 경우 유지보수에 악영향을 끼치게 된다.

 

필드를 name에서 kinds로 변경할 경우 몬스터 클래스의 내부 메소드는 변경해 주어야 하지만
사용자의 호출 코드는 병경하지 않아도 된다.
String kinds;
int hp;
	
public void setInfo(String info) {
	//this.name = info;
	this.kinds = info;
	}
	
public void setHp(int hp) {
	if(hp > 0) {
		this.hp = hp;
	} else {
		this.hp = 0;
		}
	}	
	public String getInfo() {
//	return "몬스터의 이름은 " + this.name + "이고, 체력은 " + this.hp + "입니다.";
	return "몬스터의 종류는 " + this.kinds + "이고, 체력은 " + this.hp + "입니다.";
	}
}

몬스터의 종류는 프랑켄슈타인이고, 체력은 -100입니다.

출력은 이렇게 되는데 이렇게 되는 이유는 아직 몬스터 클래스에

직접 접근을 막아놓지 않아 가능한 상태이다. 이제 이것들은 제한자로 막아 보겠다.

 

접근제한자 ?
 
접근제한자는 총 4개가 있다.
public
모든 패키지에 접근을 허용한다. 메소드를 통한 간접 접근
 
protected
동일 패키지에 접근을 허용한다. 단 상속 관계에 있는 경우 다른 패키지에서도 접근이 가능함.
직접적으로 내부에 접근 못하게 하도록 한다.
 
default
동일 패키지서만 딱 접근이 허용된다. 작성하지 않는 것이 default인데 같은 폴더 안에서만 가능하다.
 
private
해당 클래스 내부에서만 접근이 허용된다.
 
위의 4가지 접근 제한자는 클래스의 멤버(필드, 메소드)에 모두 사용이 가능한데
단, 클래스 선언 시 사용하는 접근 제한자는 public과 default만 사용이 가능하다.
 
그래서 우리는 private를 사용하려 한다.
필드에 접근 제한자를 붙여 직접 접근을 막게 한다.
private String name;
private int hp;

이제는 간접 접근할 수 있는 메소드를 작성한다.

public void setInfo(String info) {
this.name = info;
}
public void setInfo(String info) {
this.name = info;
}
public void setHp(int hp) {
if(hp > 0) {
this.hp = hp;
} else {
this.hp = 0;
}
}
public String getInfo() {
return "몬스터의 이름은 " + this.name + "이고, 체력은 " + this.hp + "입니다.";
}

 선언한 필드대로 공간은 생성되어 있지만 직접 접근할 수 없고public으로 접근을 허용한 메소드만 이용할 수 있도록 했다.

이러한 것을 캡슐화encapsulation라고 한다. 클래스 작성 시 특별한 목적이 아니면 캡슐화가 기본 원칙이다.

Monster monster = new Monster();
//monster.name = "프랑켄슈타인";
//monster.hp = -200;
monster.setInfo("프랑켄슈타인");
monster.setHp(-200);
System.out.println(monster.getInfo());

이렇게 콘솔에 찍으면

출력

더보기

몬스터의 이름은 프랑켄슈타인이고, 체력은 0입니다. 

출력 된다.

 

 

 

SMALL