Notice
Recent Posts
Recent Comments
Link
nathan_H
[Java] 빠르게 정리하는 자바 클래스 본문
들어가기전
객체 개념
- 물리적으로 존재하는 것(자동차, 책 등등)
- 추상적인 것 중 자신의 속성과 동작을 가지는 모든 것
- 객체는 필드과 메소드로 구성되어 자바 객체 모델링이 가능함
- 현실 세계의 객체를 소프트웨어의 객체로 모델링 하는 과정
객체 지향 프로그래밍
-
캡슐화
https://docsplayer.org/104244489-Chapter-01-html.html
- 객체의 필드, 메소드를 하나로 묶고, 실제 구현 내용을 감추는 것
- 외부 객체는 객체 내부 구조를 알지 못하며 객체가 노출해 제공하는 필드와 메소드만 이용 가능
- 필드와 메소드를 캡슐화 하여 보호하는 이유는 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하기 위함
- 자바 언어는 캡슐화된 멤버를 노출 시킬 것인지 숨길 것인지 결정하기 위해 접근 제한자(Access Modifier) 사용
-
상속
- 상위(부모)객체의 필드와 메소드를 하위(자식) 객체에게 물려 주는 것
- 하위 객체는 상위 객체를 확장해서 추가적인 필드와 메소드를 가질 수 있음
- 상속의 효과
- 반복된 코드의 중복을 줄임
- 유지 보수의 편리성 제공
- 객체의 다형성 구현
-
다형성
- 같은 타입이지만 실행 결과가 다양한 객체를 대입할 수 있는 성질
- 부모 타입에는 모든 자식 객체가 대입
- 인터페이스 타입에는 모든 구현 객체가 대입
- 효과
- 객체를 부품화
객체 vs 클래스
- 현실세계 : 설계도 → 객체
- 자바 : 클래스 → 객체
- 자바에서의 객체는 개발자가 클래스를 통해 설계를 진행한 후 클래스를 인스턴스화 시켜 실제 사용이 이루어짐
- 하나의 객체는 여러 개의 인스턴스를 만들 수 있음
자바 클래스 선언
- 소스 파일당 하나의 클래스를 선언하는 것이 관례
- 두 개 이상의 클래스도 선언 가능하지만 파일당 하나가 관례
- 소스 파일 이름과 동일한 클래스만 public으로 선언 가능
// Nathan.java
public Nathan {
String name;
int age;
}
클래스 생성
- new 연산자와 함께 힙 메모리에 생성이 됨
- new 연산자는 객체를 생성한 후, 객체 생성 번지를 넘겨줌
Nathan n1 = new Nathan();
- 그리고 객체를 생성하고 저장되는 변수의 값은 객체의 주소를 저장하기 때문에 변수는 객체를 "참조"하게 됨
https://programmer-seva.tistory.com/76
스택 영역
- 함수가 호출될 때 메모리에 할당, 지역 변수, 매개변수
public class StackMemoryTest {
public static void m1(int a) {
m2(++a);
System.out.printf("m1() : %d \n", a);
}
public static void m2(int a) {
m3(++a);
System.out.printf("m2() : %d \n", a);
}
public static void m3(int a) {
++a;
System.out.printf("m3() : %d \n", a);
}
public static void main(String[] args) {
int a = 20;
m1(a);
System.out.printf("main() : %d \n", a);
}
}
결과
m3() : 23
m2() : 22
m1() : 21
main() : 20
- m1을 호출 한 후 main함수에 a 값을 찍으면 힙 영역으로 인해 값이 바뀌지 않고 그대로 출력이 됨
- m1, m2, m3도 마찬가지로 매개변수로 받은 값이 그대로 찍힘
힙 영역
- 함수 호출이 끝나도 유지, 참조 형태의 변수, 인스턴스 변수, 객체, 배열
public class HeapMemoryTest {
public static int[] m1(int a) {
int[] arr = m2(a+1);
arr[2] = a;
return arr;
}
private static int[] m2(int a) {
int[] arr = m3(a+1);
arr[1] = a;
return arr;
}
private static int[] m3(int a) {
int[] arr = new int[3];
arr[0] = a;
return arr;
}
public static void main(String[] args) {
int[] arr = m1(100);
for (int i = 0; i < arr.length; i++) {
System.out.printf("arr[%d] = %d\n", i, arr[i]);
}
}
}
출력
arr[0] = 102
arr[1] = 101
arr[2] = 100
- 위 예시는 배열을 참조해서 실행이 되기 때문에 arr의 값이 m1, m2, m3로 인해 저장된 값으로 출력이 이루어짐
클래스 구성요소
필드
- 객체의 "데이터"가 저장되는 곳
- 객체의 "상태"라고도 표현
필드의 내용
- 객체의 고유 데이터
- 객체가 가져야 할 부품 객체
- 객체의 현재 상태 데이터
public class Car {
String company; // 고유 데이터
int rmp; // 상태
Engine engine; // 부품 객체
}
- 위 코드 예시를 들면 차라는 객체에는 회사라는 고유 데이터 값과 rmp이라는 상태, Engine이라는 부품 객체를 포함해 데이터가 저장이 이루어질 수 있음
사용 예시
public class Car {
String company = "tesla";
String model = "ModelS";
String color = "black";
int maxSpeed = 350;
public static void main(String[] args) {
Car nathanCar = new Car();
System.out.println("Company : " + nathanCar.company);
System.out.println("Model Name : " + nathanCar.model);
System.out.println("Color : " + nathanCar.color);
System.out.println("Max speed : " + nathanCar.maxSpeed);
}
}
결과
Company : tesla
Model Name : ModelS
Color : black
Max speed : 350
필드의 기본 초기값
https://m.blog.naver.com/PostView.nhn?blogId=heartflow89&logNo=220956313502
public class FieldInitValue {
//필드
byte byteField; short shortField; int intField; long longField;
boolean booleanField;
char charField;
float floatField;
double doubleField;
int[] arrField;
String referenceField;
public static void main(String[] args) {
FieldInitValue fiv = new FieldInitValue();
System.out.println("ByteField : " + fiv.byteField);
System.out.println("ShortField : " + fiv.shortField);
System.out.println("IntField : " + fiv.intField);
System.out.println("LongField : " + fiv.longField);
System.out.println("BooleanField : " + fiv.booleanField);
System.out.printf("charField: \\u%04X%n", (int)fiv.charField);
System.out.println("floatField: " + fiv.floatField);
System.out.println("doubleField: " + fiv.doubleField);
System.out.println("arrField: " + fiv.arrField);
System.out.println("referenceField: " + fiv.referenceField);
}
}
출력
ByteField : 0
ShortField : 0
IntField : 0
LongField : 0
BooleanField : false
charField: \u0000
floatField: 0.0
doubleField: 0.0
arrField: null
referenceField: null
생성자
- new 연산자에 의해 호출되어 객체의 초기화 담당
- 필드의 값 설정
- 메소드 호출해 객체를 사용할 수 있도록 준비하는 역할 수행
생성자 종류
기본 생성자
- 모든 클래스는 생성자가 반드시 존재하며 하나 이상 가질 수 있음
- 생성자 선언을 생략하면 컴파일러는 다음과 같은 기본 생성자 추가
생성자 선언
- 개발자가 직접 생성하는 생성자로 컴파일러는 컴파일 시 선언된 생성자가 있으면 기본 생성자를 선언하지 않음
- 초기화 값 없이 선언된 필드는 객체를 생성될 때 기본값으로 자동 설정되는데, 특정 값으로 필드를 초기화 할 수도 있음
- 매개 변수와 필드명이 같은 경우 this 사용
People p1 = new People("nathan", 23);
People p2 = new People("홍길동", 24);
그렇다면 생성자 다양화 왜 필요할까?
- 객체 생성할 때 외부 값으로 객체를 초기화 할 필요
- 외부 값이 어떤 타입으로 몇 개가 제공될 지 모름(생성자도 다양화)
- 이로 인해 자바는 생성자 오버로딩 기능을 제공해 생성자 다양화를 시킬 수 있음
- 오버로딩 : 매개변수의 타입, 개수, 순서가 다른 생성자를 여러 개 선언
package object.field;
public class Car {
String company = "tesla";
String model = "ModelS";
String color = "black";
int maxSpeed = 350;
public Car(String company) {
this.company = company;
}
public Car(String model, String color) {
this.model = model;
this.color = color;
}
public Car(String model, String color, int maxSpeed) {
this.model = model;
this.color = color;
this.maxSpeed = maxSpeed;
}
}
메소드
- 객체의 동작을 하는 역할
- 객체의 "행위"라고도 표현
메소드 선언
리턴타입 메소드이름 (매개변수, ..) {
// 실행 코드 작성
}
public class Calculator {
void powerOn() {
System.out.println("Power On!");
}
int plus(int x, int y) {
return x + y;
}
double divide(int x, int y) {
if (y == 0) {
throw new IllegalArgumentException("0으로 나눌 수 없음.");
}
return x / y;
}
void powerOff() {
System.out.println("Power Off..");
}
public static void main(String[] args) {
Calculator calculator = new Calculator();
calculator.powerOn();;
System.out.println("result 1 : " + calculator.plus(5 ,6));
byte x = 10;
byte y = 4;
System.out.println("result 2 : " + calculator.divide(x, y));
calculator.powerOff();
}
}
결과
Power On!
result 1 : 11
result 2 : 2.0
Power Off..
메소드 오버로딩
- 클래스 내에 같은 이름의 메소드를 여러 개 선언하는 것
- 하나의 메소드 이름으로 다양한 매개변수 값을 받기 위해 사용
- 오버로딩 조건 : 매개변수의 타입, 개수, 순서가 달라야 함
class Nathan {
void printNathan() {
}
void printNathan(int weight) {
}
void printNathan(int age) {
}
void printNathan(int age, int weight) {
}
}
인스턴스 멤버와 this
- 인스턴스 멤버
- 객체마다 가지고 있는 필드와 메소드
- 인스턴스 멤버는 객체 소속 멤버이기 때문에 객체 없이 사용 불가능
this
키워드- 객체(인스턴스) 자신의 참조(번지)를 가지고 있는 키워드
- 객체 내부에서 인스턴스 멤버임을 명확히 하기 위해 this 사용
- 매개변수와 필드명이 동일할 때 인스턴스 필드임을 명확히 하기 위해 사용
예시 코드
class Nathan {
int age;
int height;
Nathan(int age, int height) {
this.age = age;
this.height = height;
}
정적 멤버와 static
정적 멤버
- 클래스에 고정된 필드와 메소드
- 정적 필드, 정적 메서드
- 정적 멤버는 클래스에 소속된 멤버
- 객체 내부에 존재하지 않고, 메소드 영역에 존재
- 정적 멤버는 객체를 생성하지 않고 클래스로 바로 접근해 사용
public class Calculator {
static double pi = 3.14159;
static int plus(int x, int y) {
return x + y;
}
static int minus(int x, int y) {
return x - y;
}
public static void main(String[] args) {
double result1 = 10 * 10 * Calculator.pi;
int result2 = Calculator.plus(10, 5);
int result3 = Calculator.minus(10, 5);
System.out.println("result 1 : " + result1);
System.out.println("result 2 : " + result2);
System.out.println("result 3 : " + result3);
}
}
인스턴스 멤버 선언 vs 정적 멤버 선언
-
필드
-
인스턴스 필드 : "객체 마다" 가지고 있어야 할 데이터
-
정적 필드 : 공용적인 데이터
public class Calculator { static double pi = 3.14159; // 계산기에 사용되는 pi 값은 항상 동일 String color; // 계산기별 색은 상이할 수 있음 }
-
정적 필드의 경우 공용적으로 사용되는 데이터이기 때문에 외부에서 해당 정적 필드를 사용 시 주의를 해야함
- 정적변수 사용 시
final
을 붙여 변경 불가능하게 해줄 수도 있음
- 정적변수 사용 시
-
-
메소드
- 인스턴스 메소드 : 인스턴스 필드로 작업해야 할 메소드
- 정적 메소드 : 인스턴스 필드로 작업하지 않는 메소드
- 정적 메소드의 경우 객체 내부에 존재하는 것이 아니기 때문에 인스턴스 필드에 접근하지 못함!
정적 초기화 블록
- 클래스가 메소드 영역에서 로딩될 때 자동으로 실행되는 블록
- 정적 필드의 복잡한 초기화 작업과 정적 메소드 호출 가능
- 클래스 내부에 여러 개가 선언되면 선언된 순서대로 실행
- 정적 초기화가 왜 필요한가?
- 정적 필드에 초기화 작업이 필요한 경우가 존재할 수 있음
- 정적 필드의 경우 객체 생성 없이도 사용할 수 있어야 하므로 생성장에서 초기화 작업을 할 수 없음
- 생성자는 객체 생성 시에만 실행되기 때문에
- 그래서 자바는 정적 블록을 통해 초기화 작업 기능을 제공
public class Car {
static String company = "tesla";
static String model = "ModelS";
static String info;
static {
info = company + " - " + model;
}
}
정적 메소드와 정적 블록 사용시 주의할 점
- 정적 메소드와 블록은 객체가 없이도 실행이 가능하기 때문에 블록 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없음
- 당연히 객체 자신을 참조하는 this도 사용 불가
싱글톤
- 프로그램을 짤 때 가끔 단 하나의 객체만 만들도록 보장하는 경우가 존재하는데, 이 객체를 바로 싱글톤이라고 함
- 싱글톤 만드는 방법
- 외부에서 new 연산자로 생성자를 호출할 수 없도록 막기
private
접근 제한자를 생성자 앞에 붙임
- 클래스 자신의 타입으로 정적 필드 선언
- 자신의 객체를 생성해 초기화
private
접근 제한자 붙여 외부에서 필드 값 변경 불가능하도록
- 외부에서 호출될 수 있는 정적 메소드인 getInstance() 선언
- 정적 필드에서 참조하고 있는 자신의 객체 리턴
- 외부에서 new 연산자로 생성자를 호출할 수 없도록 막기
예시 코드
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() { }
static Singleton getInstance() {
return singleton;
}
public static void main(String[] args) {
// Singleton obj1 = new Singleton(); // 컴파일 에러
// Singleton obj2 = new Singleton(); // 컴파일 에러
Singleton obj1 = Singleton.getInstance();
Singleton obj2 = Singleton.getInstance();
if (obj1 == obj2) {
System.out.println("같은 Singleton 객체");
} else {
System.out.println("다른 Singleton 객체");
}
}
}
출력
같은 Singleton 객체
final 필드와 상수
final 필드
- 최종적인 값을 가지고 있는 필드로써 값을 변경할 수 없는 필드
- final 필드의 딱 한 번의 초기값 지정 방법
- 필드 선언 시
- 생성자
- 생성자는 final 필드의 최종 초기화를 마쳐야 하는데, 만약 초기화 되지 않는 final 필드를 남겨두면 컴파일 에러가 남
성공
public class Person {
final String nation = "Korea";
final String ssn;
String name;
int age = 25;
public Person(String ssn, String name) {
this.ssn = ssn;
this.name = name;
}
}
컴파일 에러
package object.staticfinal;
public class Person {
final String nation = "Korea";
final String ssn;
String name;
int age = 25;
public Person(String name) {
this.name = name;
}
}
상수 (static final)
- 상수 : 불변의 값 (원주율 파이, 지구의 무게 및 둘레 등등)
- final
- 객체마다 가지는 불변의 인스턴스 필드
- final을 상수라고 부르지 않는 이유
- 불변의 값은 객체마다 저장할 필요가 없는 공용성을 띄고 있으며, 여러 가지 값으로 초기화될 수 없기 때문
- final 필드는 객체마다 저장되고, 생성자의 매개값을 통해서 여러 가지 값을 가질 수 있기 때문에 상수가 될 수 없음
- 상수 (static final)
- 객체마다 가지고 있지 않음
- 메소드 영역에 클래스 별로 관리되는 불변의 정적 필드
- 공용 데이터로서 사용
- final
- 상수 이름은 전부 대문자로 작성
- 다른 단어가 결합되면 _로 연결
enum
이 나오기 전에static final
로 상수를 표현함- enum이란?
예제 코드
public class Earth {
static final double EARTH_RADIUS = 6400;
static final double EARTH_SURFACE_AREA;
static {
EARTH_SURFACE_AREA = 4 * Math.PI * EARTH_RADIUS * EARTH_RADIUS;
}
public static void main(String[] args) {
System.out.println("지구의 반지름 : " + Earth.EARTH_RADIUS + " km");
System.out.println("지구의 표면적 : " + Earth.EARTH_SURFACE_AREA + " km^2");
}
}
출력
지구의 반지름 : 6400.0 km
지구의 표면적 : 5.147185403641517E8 km^2
접근 제한자
- 자바에서는 접근 제한자를 활용해 객체의 메소드 생성자, 필드들에 대해 접근을 제한할 수 있음
- 클래스 제한 : 다른 패키지에서 클래스를 사용하지 못하도록 함
- 생성자 제한 : 클래스로부터 객체를 생성하지 못하도록 함
- 필드와 메소드 제한 : 특정 필드와 메소드를 숨김 처리
- 초반에 언급한 객체 지향 프로그램에서 캡슐화를 함에 있어 위 접근 제한자를 사용해 진행
- 종류는 위 그림과 같이 4가지 존재
마무리
- 본 내용은 자바에서의 클래스의 "기본 개념"에 대해서 정리한 내용이다. 위 내용에서 몇 가지는 포스팅 하나 각잡고 해도 부족할 정도로 깊게 들어갈 수 있는 내용들도 많다. 또한 객체 지향 프로그램에 있어 다양한 의견과 식견이 존재하기 때문에 추후에 "객체 지향"이라는 주제로 포스팅도 해볼까 한다.
'Programming Laguage > Java' 카테고리의 다른 글
[Java] Java 애플리케이션에서 JVM 구조와 실행 과정 (0) | 2020.05.05 |
---|---|
[Java] 상속 핵심 개념과 추상 클래스 (0) | 2020.05.01 |
[Java] Call By Value와 Call By Reference (0) | 2020.04.27 |
[Java] Enum을 잡아보자 (0) | 2020.04.25 |
Class member value encapsulation. (0) | 2019.05.21 |
Comments