nathan_H

[Java] 빠르게 정리하는 자바 클래스 본문

Programming Laguage/Java

[Java] 빠르게 정리하는 자바 클래스

nathan_H 2020. 4. 28. 19:10

들어가기전

객체 개념

  • 물리적으로 존재하는 것(자동차, 책 등등)
  • 추상적인 것 중 자신의 속성과 동작을 가지는 모든 것
  • 객체는 필드과 메소드로 구성되어 자바 객체 모델링이 가능함
    • 현실 세계의 객체를 소프트웨어의 객체로 모델링 하는 과정

객체 지향 프로그래밍

  1. 캡슐화

    https://docsplayer.org/104244489-Chapter-01-html.html

    • 객체의 필드, 메소드를 하나로 묶고, 실제 구현 내용을 감추는 것
    • 외부 객체는 객체 내부 구조를 알지 못하며 객체가 노출해 제공하는 필드와 메소드만 이용 가능
      • 필드와 메소드를 캡슐화 하여 보호하는 이유는 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하기 위함
    • 자바 언어는 캡슐화된 멤버를 노출 시킬 것인지 숨길 것인지 결정하기 위해 접근 제한자(Access Modifier) 사용
  2. 상속

    • 상위(부모)객체의 필드와 메소드를 하위(자식) 객체에게 물려 주는 것
    • 하위 객체는 상위 객체를 확장해서 추가적인 필드와 메소드를 가질 수 있음
    • 상속의 효과
      1. 반복된 코드의 중복을 줄임
      2. 유지 보수의 편리성 제공
      3. 객체의 다형성 구현
  3. 다형성

    • 같은 타입이지만 실행 결과가 다양한 객체를 대입할 수 있는 성질
    • 부모 타입에는 모든 자식 객체가 대입
    • 인터페이스 타입에는 모든 구현 객체가 대입
    • 효과
      • 객체를 부품화

객체 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);

그렇다면 생성자 다양화 왜 필요할까?


  1. 객체 생성할 때 외부 값으로 객체를 초기화 할 필요
  2. 외부 값이 어떤 타입으로 몇 개가 제공될 지 모름(생성자도 다양화)
  • 이로 인해 자바는 생성자 오버로딩 기능을 제공해 생성자 다양화를 시킬 수 있음
    • 오버로딩 : 매개변수의 타입, 개수, 순서가 다른 생성자를 여러 개 선언
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도 사용 불가

싱글톤


  • 프로그램을 짤 때 가끔 단 하나의 객체만 만들도록 보장하는 경우가 존재하는데, 이 객체를 바로 싱글톤이라고 함
  • 싱글톤 만드는 방법
    1. 외부에서 new 연산자로 생성자를 호출할 수 없도록 막기
      • private 접근 제한자를 생성자 앞에 붙임
    2. 클래스 자신의 타입으로 정적 필드 선언
      • 자신의 객체를 생성해 초기화
      • private 접근 제한자 붙여 외부에서 필드 값 변경 불가능하도록
    3. 외부에서 호출될 수 있는 정적 메소드인 getInstance() 선언
      • 정적 필드에서 참조하고 있는 자신의 객체 리턴

예시 코드

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)
      • 객체마다 가지고 있지 않음
      • 메소드 영역에 클래스 별로 관리되는 불변의 정적 필드
      • 공용 데이터로서 사용
  • 상수 이름은 전부 대문자로 작성
  • 다른 단어가 결합되면 _로 연결
    • 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

접근 제한자


https://worri.tistory.com/98

  • 자바에서는 접근 제한자를 활용해 객체의 메소드 생성자, 필드들에 대해 접근을 제한할 수 있음
    • 클래스 제한 : 다른 패키지에서 클래스를 사용하지 못하도록 함
    • 생성자 제한 : 클래스로부터 객체를 생성하지 못하도록 함
    • 필드와 메소드 제한 : 특정 필드와 메소드를 숨김 처리
  • 초반에 언급한 객체 지향 프로그램에서 캡슐화를 함에 있어 위 접근 제한자를 사용해 진행
  • 종류는 위 그림과 같이 4가지 존재

마무리


  • 본 내용은 자바에서의 클래스의 "기본 개념"에 대해서 정리한 내용이다. 위 내용에서 몇 가지는 포스팅 하나 각잡고 해도 부족할 정도로 깊게 들어갈 수 있는 내용들도 많다. 또한 객체 지향 프로그램에 있어 다양한 의견과 식견이 존재하기 때문에 추후에 "객체 지향"이라는 주제로 포스팅도 해볼까 한다.

[Java] 클래스(Class) - 기본 개념

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=50563128

Comments