[ OOP란? ]
Object Oriented Programming, 즉 객체 지향 설계를 뜻한다.
[ OOP의 4요소 ]
1. Encapsulation(정보 은닉): 한 클래스에 담겨 있는 데이터와 메소드 중 중요한 것은 클래스 내부에서만 접근 가능하고, 외부에서 필요한 기능만을 공개한다.
2. Inheritance(상속): 객체를 정의할 때 기존에 존재하는 객체를 상속받도록 하면 부모의 데이터와 메소드를 재사용하 수 있다.
3. Polymorphism(다형성): 같은 타입, 같은 메소드를 다양한 방법으로 실행 가능하다.
4. Abstraction(추상화): 현실세계의 여러 객체들의 공통적이고 중요한 특징들을 뽑아내는 것.
OOP의 3요소는 여기서 정보 은닉을 제외한 상속, 다형성, 추상화이다.
[ Class vs Object ]
Object - 현실 세계에 존재, 구체적, '객체'라고도 불린다. // 예) 김철수 학생, 김영희 학생, 갤럭시 폰, 아이폰
Class - 프로그래밍 언어로 구현, 추상적(다양한 Object의 특징들을 추상화 -> 분류) //예) 학생 클래스, 휴대전화 클래스
[ Class 분류 후 해야 할 일 - 속성과 메소드 결정 ]
사물들을 추상화 해서 공통점을 모았다면, 그 공통점들의 정적인 특성과 동적인 특성으로 다시 나누어야 한다.
정적인 특성은 클래스의 속성(데이터, 필드)가 되고 동적인 특성은 함수형태로 표현되어 클래스의 메소드가 된다.
이런 클래스의 멤버들은 객체가 생성되어 소멸될 때 까지 메모리에 상주한다.
예시) 김영희 학생, 김철수 학생의 공통적인 특성(학번, 이름, 학년이 존재하고 공부라는 작업을 수행함)을 추상화하여 만든 Student 클래스 +)클래스 명은 대문자로 시작하는 것이 관례이다.
public class Student{
/*정적인 특성 - attribute, field, data, 멤버변수, 상태 변수, 전역 변수*/
public int studentId;
public String name;
public int grade;
/*동적인 특성 - 멤버 메소드*/
public void study(){
System.out.println(name+"는 JAVA 공부 중!");
}
}
이 클래스를 Object로 객체화 해서 생성하는 작업은 static 메소드인 main메소드에서 실행하는데, Student 클래스 내부에서 선언해도 되지만, 클래스의 고유한 특성('학생')을 잃지 않도록 따로 클래스를 만들어, 그 곳에서 main메소드를 실행하는 게 일반적이다.(그런 클래스들의 보통 이름은 ~Test 로 짓는다.)
[ 멤버 변수 vs 지역 변수 ]
이클립스 IDE에서 멤버 변수는 파란색, 지역 변수는 갈색으로 표기된다.
멤버 변수 | 지역 변수 | |
선언 위치 | 클래스 중괄호 내부 | 클래스 메소드 내부 |
수명 | 객체 생성 부터 소멸까지 | 메소드 호출시 생성, 호출이 끝나면 사라짐 |
초기화 | 0 혹은 그와 유사한 값 자동 초기화 (null, 0.0 등) | 사용자가 초기화 필요 |
+) 멤버 변수는 같은 객체 내부의 멤버 메소드에서 자유롭게 호출 가능, 메소드의 매개변수도 지역변수에 포함됨.
++) 그렇기 때문에 메소드 내부의 지역변수는 private로 선언할 필요가 없다. 메소드 시행이 끝나면 바로 사라지기 때문에..
[ 멤버 변수 == 전역(global) 변수 ]
Class의 멤버변수는 전역변수라고도 하는데, 클래스의 여러 메소드들에서 호출및 변경이 가능하며, 선언했을 때 0 혹은 그와 유사한 값으로(문자열 등 객체일 경우 null, boolean 혀의 경우 false) 자동으로 초기화되기 때문이다.
[ Class를 Object로 만드는 과정 ]
public class StudentTest {
public static void main(String[] args) {
Student cheolsu = new Student(); //객체 생성
chelsu.study(); //철수는 JAVA 공부 중!
}
}
Student 라는 클래스의 철수 Object를 만드는 작업이다.
new 라는 키워드 + 생성자인 Student();를 보면 컴퓨터는 그 생성자를 보고 그에 맞는 메모리 공간을 확보, 철수에게 공간 할당을 실행한다. 이 때, 지역 변수 (주로 객체의 주소)는 stack, 객체의 멤버 변수(static 변수 제외)는 heap, static 변수와 메소드(static이든 아니든)는 Class area 영역에 할당된다. stack엔 메소드에서 선언한 여러 변수들이 저장되는데, main 메소드도 메소드 중 하나이기 때문이다.
<문자열 선언 시 JVM 동작 과정>
public class Test{
public void main(String[] args){
String str;
str = new String("apple");
}
}
1. Class Area에 Test 클래스에 대한 메모리를 올린다.
2. String str; 을 보고 stack 에 str에 대한 참조 변수(주소를 담는 공간)을 할당한다
-> str은 힙에 String이 올 것으로 기대한다. (아직 힙은 비어있는 상태, stack엔 null 저장되어있음)
3. str = new String("apple")을 보고, heap 에 "apple" 문자열 객체를 할당한 후 stack 의 str에 apple의 힙 주소를 저장한다
[ 생성자가 뭔데? ]
방금 철수 객체를 생성할 때 Student(); 라는 함수를 호출했다. 하지만 이 함수는 따로 Student 클래스에서 정의한 적이 없는 메소드이다. Class 이름과 같은 이름의 생성자는 사용자가 따로 정의하지 않는다면, 컴파일러가 이를 보고 매개변수가 없는 형태의 기본 생성자를 만들어준다.
생성자는 매개변수가 없는 모양의 기본생성자 이외에도 따로 매개변수를 주고, 멤버변수를 매개변수로 초기화 하는 방식으로 따로 생성자를 정의해줄 수도 있다. 이 경우, 컴파일러는 생성자가 정의되어 있는 것을 보고 따로 기본생성자를 만들지 않기 때문에, 만약 기본생성자를 호출할 일이 있다면 이전과 달리 따로 기본생성자도 정의를 해주어야 한다.
생성자 메소드의 접근지정자(access modifier)는 보통 public이다. 그래야지 다른 클래스(StudentTest)에서도 이를 호출하여 객체 생성이 가능하기 때문이다. 예외적으로 싱글톤 디자인패턴에선 생성자의 modifier을 private로 두고, 같은 클래스 내부에서만 객체 생성이 가능하도록 한다.
/*Student 클래스 내부에 정의된 다양한 생성자*/
public Student(){}
public Student(int studentId, String name, int grade){
this.studentId = studentId;
this.name = name;
this.grade = grade;
}
여기서 this는 생성되는 객체의 주소를 의미하며, 생성자의 매개변수와 객체의 멤버 변수간의 구분을 위해 달린 것이다. 상속받았을 경우 부모와 자신을 구별하기 위해 사용하기도 한다.
생성자는 직접 만들기 귀찮으면 이클립스 ide에서 Alt + Shift + s > Generate Constructor using Fields 를 클릭하면 원하는 필드값을 선택하면 생성자를 알아서 만들어준다.
+) 생성자는 반환형이 없다. 만약 반환형이 있다면 그건 생성자가 아니라 멤버 메소드로, 객체 생성 시 호출되는 게 아니라 객체가 생긴 후 거기서 호출할 때 사용되는 것이다. 예) public void Student()
++) this는 메소드가 아니라 자신의 참조 변수이다. 종종 다른 메소드에서 자신 클래스의 생성자 메소드를 호출하기 위해 this()와 같이 사용하기도 한다.(주로 오버로딩 했을 때) 기본적으로 heap 공간에 있다.
[ 다양한 생성자로 객체 생성하기 ]
public class StudentTest {
public static void main(String[] args) {
Student cheolsu = new Student(); //객체 생성
chelsu.study(); //철수는 JAVA 공부 중!
Student yeonghee = new Student(2017142328, "김영희", 4);
yeonghee.study(); //영희는 JAVA 공부 중!
System.out.println("이름: "+yeonghee.name + ", 학번: "+yeonghee.studentId+", 학년: "+yeonghee.grade);
//이름: 김영희, 학번: 2017142328, 학년: 4
}
}
[ Encapsulation과 private ]
현재, Student 클래스의 멤버변수들은 public으로 선언된 상태이므로, Stdent 클래스가 아니라 외부 클래스인 StudentTest에서 'yeonghee.name' 등으로 직접 접근하여 값을 조회, 또는 수정할 수 있다. 규모가 큰 시스템의 경우, 외부에서 직접 클래스에 접근하여 변수를 바꿀 경우 적절히 필터링되지 못하여 시스템 전체에 문제가 생길 수 있다. 이런 문제를 막기 위해 멤버변수를 private로 선언해, 클래스 내부에서 직접 관리할 수 있도록 하고, public 메소드로 getter와 setter를 두어, 외부에서 허용된 범위 내에서 조회 또는 수정할 수 있도록 해야한다. 멤버 변수는 private, 메소드는 public으로 선언하는 것이 객체지향 프로그래밍에서의 일반적인 멤버 선언 방법이다.
setter는 값을 지정하는 게 목적이므로 매개변수가 필요하고, 대부분 반환형이 void 이며, getter는 값을 가져오는 게 목적이기 때문에 반환형이 필수적이고 매개변수는 없을 때가 많다.
public class Student{
/*private - 외부에서 직접 접근 방지*/
private int studentId;
private String name;
private int grade;
/*getter, setter - 간접 접근 허용*/
public void setStudentId(int studentId){
this.studentId = studentId;
}
public void setName(String name){
this.name = name;
}
public void setGrade(int grade){
this.grade = grade;
}
public int getStudentId(int accessId){
if(accessId == 1111)
return studentId;
else
return 0;
//main - System.out.println("학번은 관리자 외엔 접근이 불가합니다.");
}
public String getStudentName(){
return name;
}
public int getGrade(){
return grade;
}
}
getter, setter들은 직접 만들기 귀찮으면 이클립스 ide에서 Alt + Shift + s > Generate Getters and Setters 를 클릭, 원하는 getter와 setter를 선택하면 알아서 만들어준다.
[ private를 사용하는 이유 정리! ]
1. 외부의 비정상적인 접근으로 멤버 변수가 손상, 오작동 되는 것을 방지
2. 객체끼리 서로 의존하여 커플링이 증가하는 것 방지
3. 모듈 간 영향력을 줄이고 모듈의 독립성을 높일 수 있다.
[ Encapsulation = '캡슐화' 의 이중적인 의미 ]
1. 변수와 메소드가 하나에 뭉쳐져 있다.
2. 딱딱한 껍데기에 둘러쌓여 내부 정보가 잘 보이지 않는다, (내부가 보이면 객체가 비정상적인 접근에 의해 망가지기 쉽다.)
[ package ]
Java Class 들은 모듈별(파일)로 관리되고 이 파일들은 비슷한 유형에 속한 것끼리 또 묶여서 계층적 구초를 이루게 된다. 파일을 묶은 것이 package 이다.
package는 도메인의 역순 구조 (www.naver.com -> com > naver 순)로 주로 구성된다. 넓은 속성 > 좁은 속성 이라는 점이 공통되는 것 같다.
다른 패키지의 클래스를 사용하고자 할 땐 import라는 키워드를 사용해야 한다. import 패키지.클래스명; 과 같이 사용하는데, 끝에 *을 붙여주면 그 패키지 하위의 모든 모듈을 불러오는 것과 같다. java.lang 패키지는 디폴트로 사용자가 따로 지정해주지 않아도 알아서 import 된다.
[ 기타 소소한 정보들 ]
- 메소드의 반환형은 없거나 하나만 가진다. -> 여러개를 가질 순 없다.
- 중괄호 내부의 글들을 수행코드라고 한다.
- C언어는 물리적인 리얼 메모리를 가리키는 반면, Java는 OS 와 프로그램 사이 JVM이 존재하기 때문에 이것의 가상 메모리를 카리킨다. -> stack, class area, heap 이것들 다 가상 메모리라는 뜻
- 메소드가 없고 멤버 변수만 있는 클래스는 데이터 저장용으로 사용되며 DTO(Data Transfer Object), VO(Value Object)라고 불린다.
- 반대로 메소드만 가지고 있는 클래스도 있다.
- 배열도 객체 이다. > 배열 자체의 주소값을 한번 할당한 후, 객체로 이루어진 배열의 경우 배열의 각 원소들에 대해서도 new로 공간을 각각 할당해 주는 과정이 필요하다. 안하면 원소가 가리키는 공간이 없으므로 NullPointerException이 일어남.
- String도 객체이다.
- 리터럴로 선언했을 때, 상수라서 Constant pool 에 저장되는데, 이건 JVM에 따라 힙일 때도 있고, 다른 공간일 때도 있고.. 그때 그때 다르며 다른 객체가 같은 상수를 선언하면 같은 주소를 가리키게 된다. 즉, 재사용된다.
- GC(Garbage Collector)은 사용자 입장에선 편하지만 시스템 엔지니어 입장에선 메모리가 튈 때(?)가 있기 때문에 불필요한 객체 생성은 알아서 지양해야 한다.
- 배열에선 리터럴 타입으로 선언과 동시에 초기화만 경우 값 수정이 불가능하지만 문자열에선 가능하다. 이건 그냥하나의 약속임
- 문자열 + 연산은 편하긴 하지만 두 문자열 씩 더해서 그 결과값을 저장하는 공간을 힙에 계속해서 생성하기 때문에 + 연산을 여러번 시행하면 메모리에 부하가 걸리고 속도가 느리다. StringBuilder의 append 연산은 그렇지 않아 빠르다. 또한 문자열 + 연산도 일종의 오버로딩이다.
- toString() 메소드 (Object 클래스 - 모든 클래스의 조상/부모 클래스의 메소드로 객체 정보 출력.)의 디폴트는 객체 주소 정보로, 해시 코드를 이용해서 위치를 관리한다. 멤버 변수를 출력하려면 이를 오버라이딩 해야 함.
- 객체는 call by reference, 지역 변수는 call by value
- 주소 공간을 담는 크기는 가리키는 객체에 상관 없이 똑같다.(C언어의 포인터도 항상 4바이트)
- modifier가 선언되지 않은 디폴트 멤버를 패키지멤버라고 부른다.(같은 패키지 내에서만 허용)
- Java 8버전부터 switch 내부에 String이 오 수 있다 > int, char, String 가능.
'컴맹탈출기 > Java를 자바' 카테고리의 다른 글
Java Data type - 까먹기 쉬운 문법 정리(C와의 차이점 위주) (0) | 2021.01.25 |
---|---|
[1주차] JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가. (0) | 2020.11.21 |