JAVA 참조자료형, 클래스

금송·2024년 9월 5일
0

이론

목록 보기
13/17
post-thumbnail

데이터 타입 분류

기본 타입으로 연산을 하는게 처리 속도가 빠름

참조 변수 타입으로 연산했을 땐 저장공간은 넓지만 처리 속도가 기본타입으로 연산하는 것 보단 느림.

기본 자료형을 이용해서 선언된 변수는 실제 값을 변수 안에 저장

참조 자료형을 이용해서 선언된 변수는 메모리의 번지(주소)를 값을 가져옴. 즉 번지를 통해 객체를 참조한다

// [기본 타입 변수]
int count = 5;
double price = 10000;

// [참조 타입 변수]
String name = "Jo sung yeon";
String hobby = "조깅";

메모리 영역 (개념 정리 중요!)

  • 인스턴스 변수 (전역변수) : 작성법 int xxx = xx; — Heap 영역에 저장
  • 클래스 변수 (전역변수) : 작성법 static int xxx = xx; 앞에 스태틱을 붙여서 사용 — Method 영역에 저장
  • 지역 변수 : 해당 클래스 내에서만 동작 — stack 영역에 저장
  • 매개 변수 (Parameters) : 해당 클래스 내에서만 동작 — stack 영역에 저장
// 클래스 변수 외부에서 접근 방식
Sample.value; // 클래스 변수를 접근할 때

Sample sample = new Sample();
// 해당 영역에 선언한 변수들 -> 전역변수
// 해당 부분에 선언한 변수들은 대체로 절대 변하지 않는 값으로 설정함. 상수! final을 붙이기도함.
sample.value; // 인스턴스 변수를 접근할 때

public class Sample {
	static int value = 45; // 클래스 변수 선언법
	
	int value2 = 45; // 인스턴스 변수 선언법
	
	main(){
	...; //해당 부분에 선언한 변수들 -> 지역변수
	}
}
  • 전역변수 접근 방법
    package chap05;
    
    public class ClassInstanceVariable {
        // 클래스 변수 (전역변수)
        static int index = 23;
    
        // 인스턴스 변수 (전역변수)
        int index2 = 40;
    }
    package chap05;
    
    public class OutterClass {
        public static void main(String[] args) {
            System.out.println("ClassInstanceVariable.index = " + ClassInstanceVariable.index); // 클래스 변수 접근
    
            // ClassInstanceVariable.index2 // 해당 방법으론 인스턴스 변수를 호출할 수 없고 객체를 한번 더 선언 후 가져올 수 있음
            ClassInstanceVariable object = new ClassInstanceVariable();
            System.out.println("object.index2 = " + object.index2); // 인스턴스 변수 접근
        }
    }

Heap(힙) 영역

힙 영역은 객체 혹은 배열이 생성되는 영역

해당 영역에 생성된 객체 혹은 배열은 JVM 스택 영역의 변수나 다른 객체에 의해 참조

만약 참조하는 변수가 없다면 의미없는 변수로 취급하여 쓰레기 수집기(Garbage Collector) 즉, GC를 실행시켜 자동으로 제거함.

GC작업이 자동으로 이루어져 개발자가 따로 객체를 제거하기 위한 코드는 작성할 필요가 없음.

JAVA의 큰 특징 중 하나.

GC 에도 Minor GC, Major GC가 있는데 Minor GC는 조금 잊혀진 객체.. Major GC는 완전 잊혀진 객체를 넣어두는데 Major GC에는 잘 안들어가게 코드를 치는게 좋다. 왜냐하면 한번씩 돌때 해당 부분에 양이 많아지면 붕떠서 진행되어야 하는 작업이 진행 딜레이가 되기 때문에.

직접 영역을 건들 수 없어서 Heap 메모리 옵션으로 메모리 영역을 조정함

JVM 스택(Stack) 영역

자료구조 중에서도 스택이라는 자료구조가 존재

내부 동작은 선입후출(LIFO)이다.

JVM 스택은 메소드를 호출할 때마다 처리해야할 작업(Frame)을 추가(push)하고 메소드가 종료되면 해당 작업을 제거(Pop)하는 동작을 한다. 즉 먼저 작성한 코드는 제일 마지막에 제거된다.

  • Queue - 선입선출 (FIFO)

메소드(Method 혹은 Static) 영역

전역변수 - 프로그램을 실행하는 순간부터 선언되는 변수. 즉 메모리를 많이 차지함. 클래스 변수를 과하게 선언하면 자칫 잘못하면 프로그램이 뻗음..

지역변수 - 해당 클래스를 실행 시킬 때에만 선언되는 변수

Java 프로그램이 실행될 때, 클래스와 관련된 정보가 저장되는 메모리 영역.

메소드 영역에는 코드에서 사용되는 클래스(~.class)들을 Class Loader로 읽어서, 클래스별로 런타임 상수풀(runtime constant pool), 필드(field) 데이터, 메소드(method) 데이터, 메소드 코드, 생성자(Constructor) 코드 등을 분류해서 저장.

메소드 영역은 JVM이 시작할 때 생성되고 모든 스레드가 공유하는 영역입니다.

Java 8 이전에는 "Permanent Generation"이라고 불리기도 했으나, 그 이후부터는 메소드 영역의 개념이 Metaspace(메타스페이스)로 대체

Metaspace는 메소드 영역의 기능을 확장하면서 더 유연하게 메모리를 관리할 수 있도록 개선됨

참조 변수의 ==, ≠ 연산

기본 변수는 해당 값을 비교

참조타입 변수에서는 주소값을 비교

refValue1 == refValue2  //결과: false
refValue1 != refValue2  //결과: true

refValue2 == refValue3  //결과: true
refValue2 != refValue3  //결과: false
  • 실습
    package chap05;
    
    public class CompareReferenceType {
        public static void main(String[] args) {
            OutterClass outter = new OutterClass();
            OutterClass outter2 = new OutterClass();
    
            System.out.println(outter == outter2); // false
    
            String str1 = new String("문자1");
            String str2 = new String("문자1");
            System.out.println(str1 == str2); // false
            System.out.println(str1.equals(str2)); // true
    
            String str3 = "문자1";
            System.out.println(str1 == str3); // false
            String str4 = "문자1";
            System.out.println(str3 == str4); // true
            System.out.println(str1.equals(str3)); // true
        }
    }
    
    // 문자열 비교는 equals메서드를 꼭 사용하자 :-)

null, NPE

null

빈값.

참조 타입 변수는 힙 영역의 객체를 참조하지 않는다는 뜻

ull값도 초기화 값으로 사용할 수 있기 때문에 null로 초기화된 참조변수는 스택 영역에 생성됨

refValue1 == null  //결과: false
refValue1 != null  //결과: true

refValue2 == null  //결과: true
refValue2 != null  //결과: false

// 특정 변수가 null인지 체크하는 방법 if(refValue1 == null){}

NPE 오류

자바는 프로그램 실행 도중 발생하는 오류를 예외라고 부름.

참조 자료형 변수를 잘못 사용하면 발생, 즉 참조할 객체가 없으면 발생하는 오류!

//예시
int[] intArray = null; 
intArray[0] = 10; // NullPointerException 발생. 예외 또는 오류라고 함.

해당 오류 방지하는 방법

참조 자료형 변수를 선언할 때 null로 초기화하는 습관 보다는 생성자로 선언하는 습관을 들여야 함

위 코드에선 int[] intArray = new int[4]; 이런 식으로 생성자로 선언 해주는 게 좋음.

이때 null이 괜찮을 경우

null값으로 선언한 후 바로 특정 값을 바로 할당할 경우. 그렇지만 웬만하면 null로 초기화하지 말자.

// 예시
package chap05;

public class StringNullPointerTest {
    public static void main(String[] args) {
        String str = null;
        System.out.println(str.equals("비교값"));
    }
}
// 결과
C:\Users\ty796\.jdks\corretto-21.0.4\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA\lib\idea_rt.jar=62320:C:\Program Files\JetBrains\IntelliJ IDEA\bin" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath C:\Users\ty796\IdeaProjects\java-project\out\production\java-project chap05.StringNullPointerTest
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "str" is null
	at chap05.StringNullPointerTest.main(StringNullPointerTest.java:6)

Process finished with exit code 1

문자열 타입

문자열

문자로 구성된 문장을 문자열이라고 함

// String → 불변객체(immutable)

String str = "a";
str + "b"; // 이때 str 자체는 변경 시킬 수 없음. 새로운 문자열로 객체로 탄생.

문자열 내장 메서드

메서드명역할
equals(비교 문자열)두개의 문자열이 같은지 비교하여 결과를 true/false로 반환
indexOf(문자)인자로 받은 문자가 시작되는 위치(인덱스)를 반환
contains(특정 문자열)특정 문자열이 포함되어있는지를 true/false로 반환
charAt(특정위치 인덱스)인자로 받은 특정 위치(인덱스)의 문자를 반환
replaceAll(문자열, 문자열)문자열에서 특정 문자열을 다른 문자열로 바꿀 때 사용
replace(regex, 문자열)인자로 정규식(regex)을 받아서 해당 형태로 문자열을 변경할 때 사용
substring(시작인덱스, 끝인덱스)문자열에서 특정 문자열을 뽑아낼 때 사용
toUpperCase()문자열을 모두 대문자로 변경할 때 사용
toLowerCase()문자열을 모두 소문자로 변경할 때 사용
split(구분자)문자열을 특정한 구분자로 나누어 문자열 배열로 반환
concat(”합치고자 하는 문자열”)인자로 받은 문자열을 이전 문자열과 합쳐서 반환
trim()공백을 제거 해줄 때 사용
package chap05;

// 문자열 내장 메서드 연습 (강사님 코드 확인하기/ 확인했음.)

public class StringInnerMethod {
    public static void main(String[] args) {
        String hello = "Hello Java";

        int index = hello.indexOf("J");        // 6
        char character = hello.charAt(index);  // J

        hello.charAt(hello.indexOf("J"));      //J
        System.out.println(hello.charAt(hello.indexOf("J")));

        // replace()
        String hello2 = "Hello Java Java";
        String replaceStr = hello2.replaceAll("Java", "everyone");

        System.out.println(replaceStr);

        //substring()
        String hello3 = "Hello everyone everyone";
        hello3.substring(8, 13); // eryon
        hello3.substring(8); // eryone everyone

        //toUpperCase()m, toLowerCase()
        String hello4 = "Hello World!";
        System.out.println(hello4.toUpperCase());
        System.out.println(hello4.toLowerCase());

        // trim()
        System.out.println("                hello4.             trim(): ".trim()); // 앞 뒤 공백 없애고 중간 공백은 살려둠.

        // 문제 "aaaabbccd" 문자열 한 글짜씩 출력하기
        String str = "aaaabbccd";

        for(int i = 0; i<str.length();i++) {
            System.out.println(str.charAt(i));
        }

        // 단어 순서 뒤집기 "Hello Welcome Java" -> "Java Welcome Hello"
        // String str2 = "Hello Welcome Java";
        // String str3 = str2.split();

        String str2 = "Hello Welcome Java";
        String[] result2 = str2.split(" ");
        for (int i = result2.length - 1; i >=0; i--) {
            System.out.print(result2[i] + " ");
        }
        // 공백 제외해서 하나의 문자로 출력
        // "    Hello Welcome Java    " -> "HelloWelcomeJava"
        String str3 = "    Hello Welcome Java    ";
        String str3Trim = str3.trim();
        String[] result3 = str3Trim.split(" ");
        for (int i = 0; i < result3.length; i++) {
            System.out.print(result3[i]);
        }
    }
}

문자열 포맷팅 메서드 - format

문자열 안의 특정한 값을 바꿀 수 있게 해주는 메서드

String.format("... **%s**.. **%s**..", 치환값1, 치환값2);

package chap05;

public class StringFormat {
    public static void main(String[] args) {
        /*
        * 문자열 포맷 (%s, %d, %f)
        * */
        // %s - 문자열, 정수, 실수 (활용도가 높음)
        String str = "...........%s.....특정문자%s사이값";
        String.format(str, "치환값1","치환값2");
        System.out.println(String.format(str, "치환값1","치환값2"));
        System.out.println(String.format(str, 4, 20));
        System.out.println(String.format(str, 7.5, 8.5));

        // %d - 정수
        String str2 = "치환값1: %d........ 치환값2: %d";
        String.format(str2, 7, 8);
        System.out.println(String.format(str2, 7, 8));

        // %f - 실수
        String str3 = "치환값1: %f........ 치환값2: %f";
        String.format(str3, 7.5, 8.5);
        System.out.println(String.format(str3, 7.5, 8.5));
    }
}
  • 이런 식으로 대략 쓸 수 있음

참고

[자바의 정석]MessageFormat

StringBuffer/StringBuilder

StringBuffer와 StringBuilder는 문자열을 추가하거나 변경할 때(연산) 주로 사용하는 자료형

물론 String 자료형으로 +연산이나 concat() 메소드로 문자열을 이어 붙일 수 있지만, 이때 String 특성상 새로운 객체를 생성하고 String 공간을 할당하기 때문에 공간의 낭비 뿐만 아니라 속도면에서도 비효율적이라는 단점이 있음

멀티 스레드 환경에서

StringBuffer → 스레드 안전 높음(sychronized), 성능 낮음

StringBuilder → 성능 우수, 스레드 안정성 낮음(스레드에서 동시 접근이 가능하다.)

sychronized가 들어가 있다는 의미

스레드(main, 하나의 작은 작업 단위) 안정성이 높다는 의미

구동중인 스레드가 끝나야 사용 가능

싱글턴 패턴 참고

sychronized

insert(), substring()

insert 메서드는 특정 위치에 원하는 문자열을 삽입할 수 있는 메서드

substring 메서드는 String 자료형의 subString과 같은 역할

// insert()
System.out.println(builder.insert(0, "첫번째")); // 문자열 맨 앞에 추가
System.out.println(builder.insert(builder.length(), "오십번째지롱")); // 문자열의 마지막 갯수 + 1 까지 가능

// substring()
// subString(시작 인덱스, 끝 인덱스)
String substringResult = buffer.substring(6); // 6번째부터 끝까지 출력
String substringResult2 = buffer.substring(6, 8); // 6번째부터 8번째 전까지 출력
String substringResult3 = buffer.substring(3, 10); // 6번째부터 8번째 전까지 출력
System.out.println(substringResult);
System.out.println(substringResult2);
System.out.println(substringResult3);

클래스

객체지향언어

// 객체지향프로그래밍
(Class) name = new (Class);
name.method(); //기능
  • 계산기 예시 여러분이 계산기를 이용하여 연산을 한다고 생각해봅시다. 계산기에 숫자 5를 입력하고 + 기호를 입력한 후 4를 입력하면 결과값으로 9을 보여 주겠죠. 다시 한번 + 기호를 입력한 후 3을 입력하면 기존 결과값 9에 3을 더해 12를 보여줄겁니다. 즉 계산기는 이전에 계산한 결과값을 항상 메모리 어딘가에 저장하고 있어야 합니다. 이 내용을 자바로 구현해보겠습니다.
    class Calculator {
        static int result = 0;
    
        static int add(int num) {
            result += num;
            return result;
        }
    }
    
    public class Sample {
        public static void main(String[] args) {
            System.out.println(Calculator.add(5));
            System.out.println(Calculator.add(4));
        }
    }
    다음과 같은 결과가 나오겠죠
    5
    9
    그런데 만일 Sample 클래스에서 2대의 계산기가 필요한 상황이 발생하면? Calculator 클래스 각각의 계산기는 각각의 결과값 만을 갖기 때문에, 클래스 하나만으로는 결과값을 따로 유지할 수 없습니다. 이런 상황을 해결하려면 다음과 같이 클래스를 각각 따로 만들어야겠죠.
    class Calculator1 {
        static int result = 0;
    
        static int add(int num) {
            result += num;
            return result;
        }
    }
    
    class Calculator2 {
        static int result = 0;
    
        static int add(int num) {
            result += num;
            return result;
        }
    }
    
    public class Sample {
        public static void main(String[] args) {
            System.out.println(Calculator1.add(5));
            System.out.println(Calculator1.add(4));
    
            System.out.println(Calculator2.add(1));
            System.out.println(Calculator2.add(10));
        }
    }
    5
    9
    1
    11
    Calculator1과 Calculator2 모두 원하는 결과를 출력해주었습니다. 하지만, 매번 이렇게 Calculator가 필요할 때 마다 똑같은 코드(클래스)를 작성할 수는 없습니다. 또한, 지금은 add 덧셈 기능만 있지만 빼기, 나누기, 곱하기 등의 다양한 연산이 필요할 수도 있겠죠. 이럴 경우에는 중복되는 코드가 더 늘어나게 됩니다. 이와 같은 문제를 해결하기 위해 객체를 사용한다면, 다음과 같이 해결할 수 있습니다.
    class Calculator {
        int result = 0;
    
        int add(int num) {
            result += num;
            return result;
        }
    }
    
    public class Sample {
        public static void main(String[] args) {
            Calculator cal1 = new Calculator();  // 계산기1 객체를 생성한다.
            Calculator cal2 = new Calculator();  // 계산기2 객체를 생성한다.
    
            System.out.println(cal1.add(5));
            System.out.println(cal1.add(4));
    
            System.out.println(cal2.add(1));
            System.out.println(cal2.add(10));
        }
    }
    5
    9
    1
    11
    하나의 Calculator클래스 안에서 클래스 2개를 사용했을 때와 같은 결과를 출력합니다. Calculator 클래스로 만든 별도의 계산기 2개 (cal1, cal2)를 바로 객체라고 부릅니다. cal1, cal2 객체가 각각의 역할을 수행합니다. 그리고 계산기의 결과값 역시 다른 계산기의 결과값과 상관없이 독립적인 값을 유지합니다. 객체를 사용하면 계산기 수가 늘어나더라도 객체를 생성만 하면 되기 때문에 앞의 경우와는 달리 매우 간단해집니다. 만약 곱셈 기능을 추가하려면 다음과 같이 구현할 수 있습니다. 계산기 객체의 행위, 즉 ‘곱셈’이라는 행위를 메소드로 정의할 수 있죠.
    class Calculator {
        int result = 0;
    
        int add(int num) {
            result += num;
            return result;
        }
    
        int multiple(int num) {
            result *= num;
            return result;
        }
    }

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

캡슐화

캡슐화란 클래스 안에 서로 연관있는 속성과 기능들을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것.

캡슐화를 하는 이유

  • 데이터 보호 - 외부로부터 클래스에 정의된 속성과 기능들을 보호
  • 데이터 은닉 - 내부의 동작을 감추고 외부에는 필요한 부분만 노출

출처 : https://www.codestates.com/blog/content/객체-지향-프로그래밍-특징

캡슐화를 구현하기 위한 방법 중에 하나는 접근제어자를 활용하는 것

다형성

다형성이란 어떤 객체의 속성이나 기능이 상황에 따라 여러가지 형태를 가질 수 있는 성질

비유적으로 표현하자면, 어떤 중년 남성이 있다고 가정했을 때 그 남성의 역할이 아내에게는 남편, 회사에서는 회사원, 부모님에게는 자식 등 상황과 환경에 따라 달라지는 것과 비슷

예 ) 이동수단은 자동차가 될수도, 오토바이가 될 수도 있죠. 다르게 표현하면 ‘자동차는 자동차다’ , ‘자동차는 이동 수단이다’ 라는 명제는 모두 참입니다. 이동수단이라는 범위 안에 자동차와 오토바이를 하나로 묶을 수 있게 됩니다.

객체지향 프로그래밍에서 다형성이란 앞서 설명한 이동수단과 같은 넓은 범위의 타입

즉 상위 클래스 타입의 참조 변수로 그것과 관계있는 하위 클래스들을 참조할 수 있는 능력

클래스와 객체

클래스객체
제품 설계도실제 제품
붕어빵 틀붕어빵
자동차 설계도실제 자동차
// (참조타입) (변수 == 객체) = new ;
ex) Class (객체) = new Class();

필드 == 속성 (result) : 객체의 데이터가 저장되는 곳, 인스턴스 변수

생성자 : 객체가 생성 됐을 때 해당 객체 초기화하는 역할로 호출되는 아이

메소드 == 기능 (add) : 객체의 동작에 해당하는 곳

Quiz

객체와 클래스에 대한 설명으로 틀린 것은?

  1. 클래스는 객체를 생성하기 위한 설계도(청사진)와 같은 것이다.
  2. new 연산자로 클래스의 생성자를 호출함으로써 객체가 생성된다.
  3. 하나의 클래스로 하나의 객체만 생성할 수 있다.
  4. 객체는 클래스의 인스턴스이다.

정답 : 3. 하나의 클래스로 수많은 객체를 생성할 수 있다.

profile
goldsong

0개의 댓글