참조 자료형은 번지를 통해 객체를 참조하는 방식으로 동작한다. 기본 자료형은 변수에 실제 값을 저장하지만, 참조 자료형은 메모리의 번지(주소)를 값으로 갖고, 이 번지를 통해 객체를 참조한다. 이로 인해 참조 자료형의 연산 시간은 기본 자료형보다 더 소요되며, 연산 효율도 더 낮다.
byte, char, short, int, long, float, double, booleanArray, Enum, Class기본 자료형의 변수는 스택(Stack) 영역에 실제 값 그대로 저장되지, 참조 자료형 변수는 메모리 번지(주소)를 저장하고, 이를 통해 힙(Heap) 영역에 있는 객체나 배열을 참조한다.
// [기본 타입 변수]
int count = 5;
double price = 10000;
// [참조 타입 변수]
String name = "Jo sung yeon";
String hobby = "조깅";
메모리 사용 영역은 변수의 선언 위치에 따라 달라지며, 크게 다음과 같이 구분된다.
static 키워드로 선언된 변수로, 프로그램이 시작될 때 메서드(Method) 영역에 저장// 저장되는 메모리 영역이 다르기 때문에 접근 방법도 다르다.
// 다른 파일에 있는 Class명 접근도 가능하다.
public class Sample {
// 클래스 변수 (전역 변수)
static int value = 45;
// 인스턴스 변수
int value2 = 45;
public static void main(String[] args) {
// 지역 변수
int localVariable = 10;
// 클래스 변수 접근 : 클래스명.인덱스
System.out.println(Sample.value);
// 인스턴스 변수 접근 : 인스턴스 변수는 객체를 선언해야 접근이 가능하다.
Sample sample = new Sample();
System.out.println(sample.value2);
}
}
클래스 변수는 클래스명으로 접근 가능하며, 인스턴스 변수는 객체를 생성한 후 접근해야 한다. 이와 같이 메모리 영역에 따라 변수의 접근 방식이 달라진다.
int a = 10; // 기본 자료형 - 스택에 값이 저장됨
String str = "hello"; // 참조 자료형 - 스택에 참조값이 저장되고, 실제 데이터는 힙에 저장됨
메서드 영역은 프로그램의 시작과 함께 생성되며, 클래스가 로드될 때 그 클래스에 대한 정보들이 저장된다.
메모리 공간의 효율적 사용과 GC에 의한 메모리 관리는 Java 프로그램 성능의 중요한 요소
==, != 연산참조 타입 변수를 비교할 때 == 연산자는 두 객체가 동일한 메모리 주소를 참조하고 있는지 여부를 확인한다. 참조 변수의 값은 객체의 주소값이므로, 같은 객체를 참조하지 않는다면 == 연산자는 false를 반환하지만, 객체의 실제 값(내용)을 비교할 때는 equals() 메서드를 사용해야 한다.
public static void main(String[] args) {
OutterClass outter = new OutterClass();
OutterClass outter2 = new OutterClass();
System.out.println(outter == outter2);
String str1 = new String("문자1");
String str2 = new String("문자1");
System.out.println(str1 == str2); // false
String str3 = "문자1";
System.out.println(str1 == str3); // false
String str4 = "문자1";
System.out.println(str3 == str4); // true
System.out.println(str1.equals(str2)); // true
System.out.println(str1.equals(str3)); // true
System.out.println(str1.equals(str4)); // true
}
==) outter1과 outter2는 각각 다른 객체이므로, == 연산자는 false를 반환한다. 이는 두 객체가 다른 메모리 주소를 참조하고 있기 때문이다.new 키워드로 생성된 문자열 비교 str1과 str2는 new String("문자1")으로 생성된 서로 다른 객체이므로, == 연산자는 false를 반환한다. 이는 두 문자열이 동일한 값을 가지고 있더라도, 다른 메모리 주소를 참조하고 있음을 의미한다.str3과 str4는 "문자1" 리터럴로 생성되었다. 자바에서는 동일한 문자열 리터럴은 하나의 객체로 취급되어 String Constant Pool이라는 메모리 영역에 저장되기 때문에, str3 == str4는 true를 반환한다.equals()를 이용한 값 비교 equals() 메서드는 두 객체의 실제 값을 비교한다. str1.equals(str2), str1.equals(str3), str1.equals(str4) 모두 true를 반환한다. 1, 2, 3 모두 힙 메모리 영역에 객체를 참조하고 있지만, 3처럼 리터럴로 선언된 문자열은 힙 메모리의 String Constant Pool에 저장된다. 이 때문에 동등성 비교(==)를 할 때 다른 객체로 인식될 수 있다.str4를 "문자1"로 다시 선언하면, str3과 동일한 값을 참조하게 된다. 즉, 같은 리터럴 값이라면 같은 객체를 참조한다. 그러나 문자열 값을 비교할 때는 주소값이 아닌 문자열 자체의 값을 비교하기 위해 equals() 메서드를 사용하는 것이 좋다.== 연산자는 객체의 참조(주소)를 비교equals() 메서드는 객체의 값을 비교equals() 메서드를 사용하는 것이 좋다.null과 NullPointerException (NPE)null이란?null은 참조 타입 변수에서, 변수가 힙 영역의 어떤 객체도 참조하지 않는 상태를 의미. 참조 타입 변수는 null 값으로 초기화될 수 있으며, 이 경우 해당 변수는 스택 영역에 생성된다.
String str = null; // str은 어떤 객체도 참조하지 않음
참조타입 변수가 null값을 갖는지 확인하기 위해서 동등비교를 통해 확인이 가능하다.
refValue1 == null //결과: false
refValue1 != null //결과: true
refValue2 == null //결과: true
refValue2 != null //결과: false
if(refValue1 != null){
// refValue1의 null 체크
refValue1.length();
}
NullPointerException (NPE)참조하고 있는 객체가 없는 상태에서 참조 변수를 사용하려 할 때 발생. 예를 들어, null 값인 변수를 사용하여 메서드를 호출하거나 배열에 접근하려 하면 NPE가 발생한다.
int[] intArray = null;
intArray[0] = 10; // NullPointerException 발생
위 코드에서 intArray는 null로 초기화된 상태이므로 배열의 첫 번째 요소에 접근하려는 시도에서 NPE가 발생한다.
null로 초기화하는 습관을 피해야 한다.String str = " "; // null 대신 빈 문자열로 초기화
System.out.println("총 문자수: " + str.length()); // 예외 발생하지 않음
또한, 객체가 생성되지 않은 상태에서 참조 변수를 사용하는 것을 피하려면, 객체를 생성자로 초기화하는 것이 좋다.
프론트에서 오는 필수 파라미터가 아닌 값을 받을 때 발생할 수도 있다.
그러다 .toString()을 쓰면 발생 (대신 String.valueOf()을 사용해 방지할 수 있다.)
null로 초기화하는 습관을 피하는 것이 NPE를 방지하는 좋은 방법null 대신 빈 값으로 변수를 초기화하면 NPE를 예방할 수 있다.equals()를 이용한 값 비교 시, NPE를 피하려면 null인지 여부를 먼저 확인하거나 값을 미리 초기화하는 습관을 들이는 것이 좋다.intern() 메서드intern() 메서드는 문자열이 이미 String Constant Pool에 있는지 확인하고, 있다면 해당 문자열의 참조를 반환한다. intern()을 사용하면 메모리 절약과 성능 최적화를 기대할 수 있다.
참고: String intern()
문자열은 문자로 구성된 문장을 의미하며, Java에서는 String 클래스를 사용하여 문자열을 다룬다. String은 불변 객체(immutable)로, 한 번 생성된 문자열 객체는 변경할 수 없다.
String str = "a";
str = str + "b";
위 코드에서 "a"에 "b"를 더했을 때, 기존 문자열이 변경되는 것이 아니라 새로운 문자열 객체가 생성되기 때문에 , str 변수는 새로운 메모리 공간을 참조하게 된다. 이러한 특징 때문에, 문자열을 반복적으로 변경하는 작업에서는 성능 저하가 발생할 수 있다.
Java String 클래스는 다양한 내장 메서드를 제공하여 문자열을 쉽게 다룰 수 있다.
equals() 문자열 두 개가 같은지 비교하여 결과를 리턴indexOf(문자) 특정 문자열이 시작되는 위치(인덱스 값)를 리턴String hello = "Hello JAVA";
System.out.println(hello.indexOf("J")); // 출력: 6contains(특정 문자열) 문자열이 특정 문자열을 포함하고 있는지 여부를 리턴. (결과값: true 또는 false)charAt(특정 위치 인덱스) 문자열에서 특정 위치의 문자를 반환System.out.println(hello.charAt(hello.indexOf("J"))); // 출력: J
replaceAll(문자열, 문자열) 문자열에서 특정 문자열을 다른 문자열로 교체할 때 사용String hello2 = "Hello Java Java";
String replaceStr = hello2.replaceAll("Java", "everyone");
System.out.println(replaceStr); // 출력: Hello everyone everyonereplace(정규 표현식, 문자열) 특정 패턴(정규 표현식)에 맞는 문자열을 교체할 때 사용substring(시작 인덱스, 끝 인덱스) 문자열의 특정 부분을 추출할 때 사용되며, 끝 인덱스의 문자는 포함되지 않는다.String hello3 = "Hello everyone everyone";
System.out.println(hello3.substring(8, 13)); // 출력: every
System.out.println(hello3.substring(13)); // 출력: one everyonetoUpperCase() 문자열의 모든 문자를 대문자로 변환toLowerCase() 문자열의 모든 문자를 소문자로 변환String hello4 = "Hello World";
System.out.println(hello4.toUpperCase()); // 출력: HELLO WORLD
System.out.println(hello4.toLowerCase()); // 출력: hello worldsplit(구분자) 문자열을 특정 구분자로 나누어 문자열 배열로 반환concat(문자열) 문자열을 합치는 메서드로, 결과값은 합쳐진 문자열을 반환trim() 문자열의 앞뒤 공백을 제거System.out.println(" Hello ".trim()); // 출력: Hello
public class StringInnerMethod {
// 문자열 내장 메서드 실습
public static void main(String[] args) {
// indexOf / "Hello Java" J의 위치 출력
String hello = "Hello JAVA";
System.out.println(hello.indexOf("J")); // 6
// charAt / 위치 찾은 것을 문자로 출력
System.out.println(hello.charAt(hello.indexOf("J"))); // J
// replace()
String hello2 = "Hello Java Java";
String replaceStr = hello2.replaceAll("Java","everyone");
System.out.println(replaceStr); // Hello everyone everyone
// substring
String hello3 = "Hello everyone everyone";
System.out.println(hello3.substring(8, 13)); // eryon
System.out.println(hello3.substring(13)); // e everyone
// toUpperCase, toLowerCase()
String hello4 = "Hello World";
System.out.println(hello4.toUpperCase());
System.out.println(hello4.toLowerCase());
//trim
System.out.println(" hello4.trim(): ".trim());
// "aaaaddccd" 문자열 한 글자씩 출력하기
String str = "aaaabbccd";
for(int i = 0; i < str.length(); i++){
System.out.println(str.charAt(i));
}
// 단어 순서 뒤집기
String str2 = "Hello Welcom Java";
String[] arr = str2.split(" ");
for (int i = arr.length - 1; i >= 0; i--) {
System.out.print(arr[i] + " ");
}
System.out.println("-------------");
// 공백 제외해서 하나의 문자로 출력
String str3 = " Hello Welcome Java ";
System.out.println(str3.replace(" ",""));
}Java에서 문자열 내의 특정 값을 동적으로 변경할 수 있도록 하는 방법 중 하나가 문자열 포맷팅이다. String.format() 메서드를 사용하면 문자열 안에 값을 삽입할 수 있다.
%d : 10진수 숫자%s : 문자열%f : 실수형 숫자// 문자열 포맷(%s, %d, %f)
public static void main(String[] args) {
String str = "..........%s......특정문자%s값";
System.out.println(String.format(str,"치환값1", "치환값2"));
System.out.println(String.format(str, 4, 5));
String str2 = "치환값 : %d ...........치환값2 : %d";
System.out.println(String.format(str2, 7, 8));
String str3 = "치환값1 : %f ...... 치환값2 : %f";
// System.out.println(String.format(str3, "문자", "문자2?")); 에러
System.out.println(String.format(str3, 1.1, 2.2));
}
포맷팅을 사용할 때, 선언된 치환값 개수와 실제로 전달되는 치환값 개수가 동일해야 한다.
또한, 모든 치환값을 %s로 대체하여 사용할 수도 있다. 주로 템플릿 기반 문자열을 생성할 때 사용되며, 특정 값만 동적으로 치환할 수 있다.
ArrayList<Customer> customerInfo = xxx.getInfo();
for (Customer customer : customerInfo) {
System.out.println(String.format("%s님의 고객정보 취급 약관...", customer.getName()));
}
String은 불변 객체로, 값이 변경되면 새로운 객체가 생성된다.equals(), indexOf(), replaceAll(), substring(), split(), concat() 등이 있다.String.format()은 문자열 내부에서 특정 값을 동적으로 변경할 수 있게 해준다.StringBuffer와 StringBuilder는 String 클래스의 불변성(immutable)을 보완하기 위한 클래스들로, 문자열을 추가하거나 변경하는 작업이 빈번할 때 주로 사용된다. String 객체는 불변이기 때문에 문자열을 조작할 때마다 새로운 객체가 생성되지만, StringBuffer와 StringBuilder는 동일 객체 내에서 문자열을 수정할 수 있고, 이를 통해 불필요한 객체 생성을 줄이고 성능을 향상시킬 수 있다.
StringBuffer buffer = new StringBuffer();
buffer.append("문자열 추가");
멀티스레드 환경에서는 여러 스레드가 동시에 하나의 자원에 접근할 수 있기 때문에 동기화(synchronized)가 필요하다. 동기화는 하나의 스레드가 작업을 완료할 때까지 다른 스레드의 접근을 차단하는 방식으로 이루어지며, 이는 안정성을 보장하는 반면, 성능을 저하시킬 수 있다.
StringBuffer는 동기화를 지원하는 반면, StringBuilder는 동기화가 적용되지 않아 성능이 더 뛰어나지만, 멀티스레드 환경에서는 안전하지 않을 수 있다.
StringBuffer buffer = new StringBuffer();
buffer.append("스레드 안전한 사용");
문자열을 추가하는 메서드,
이 메서드를 사용하면 새로운 객체를 생성하지 않고 기존 문자열에 이어붙힌다.
StringBuffer buffer = new StringBuffer("Hello");
buffer.append(" World!");
System.out.println(buffer); // 출력: Hello World!
특정 위치에 원하는 문자열을 삽입할 수 있다.
문자열의 길이를 초과하는 인덱스를 지정하면 에러가 발생한다. 문자열의 끝에 붙이고 싶을 경우 buffer.length()를 이용할 수 있다.
StringBuilder builder = new StringBuilder("Hello World!");
System.out.println(builder.insert(6, "Java ")); // 출력: Hello Java World!
System.out.println(builder.insert(builder.length(), " 마지막에 추가")); // 출력: Hello Java World! 마지막에 추가
String의 substring() 메서드와 동일하게 동작
시작 위치에서 끝 위치까지의 문자열을 추출한다. 끝 인덱스는 포함되지 않는다.
StringBuffer buffer = new StringBuffer("Hello Java World!");
String substringResult = buffer.substring(6); // 6번 인덱스부터 끝까지 추출
String substringResult2 = buffer.substring(6, 10); // 6번 인덱스부터 9번 인덱스까지 추출
System.out.println(substringResult); // 출력: Java World!
System.out.println(substringResult2); // 출력: Java
// StringBuffer/StringBuilder
public static void main(String[] args) {
String result = "";
result += "Hello ";
result += "Java ";
result += "World!";
System.out.println(result);
StringBuffer buffer = new StringBuffer();
buffer.append("Hello ");
buffer.append("Java ");
buffer.append("World! ");
System.out.println(buffer);
System.out.println(buffer.toString());
StringBuilder builer = new StringBuilder();
builer.append("Hello ");
builer.append("Java ");
builer.append("World! ");
System.out.println(builer);
// insert(), substring()
System.out.println(builer.insert(0, "첫번째"));
// System.out.println(builer.insert(50, "오십번째다")); // 문자열 길이를 초과하면 에러
System.out.println(builer.insert(builer.length(),"마지막에 붙히기"));
// substring();
String substringResult = buffer.substring(6);
String substringResult2 = buffer.substring(6, 7);
System.out.println(substringResult); // Java World!
System.out.println(substringResult2); // J
// 연속된 문자의 반복 횟수로 문자열 압축해보기
String str = "aaaabbccd";
for(int i = 0; i < str.length(); i++){
}
}StringBuilder를 사용하는 것이 좋다.일반적으로, 멀티스레드 환경에서는 StringBuffer를 사용하고, 단일 스레드나 동기화가 필요하지 않은 경우에는 StringBuilder를 사용하는 것이 좋다.
StringBuffer safeBuffer = new StringBuffer(); // 멀티스레드 환경에서 사용
StringBuilder fastBuilder = new StringBuilder(); // 성능이 우선시되는 경우 사용
StringBuffer 또는 StringBuilder를 사용하여 성능을 최적화할 수 있음append(), insert(), substring() 등이 있으며, 이들을 통해 문자열을 동적으로 조작할 수 있음객체지향프로그래밍(Object-Oriented Programming, OOP)은 부품들을 조립하여 프로그램을 완성하는 개발 기법. 이를 이해하기 위해서는 객체와 객체 간의 상호작용에 대한 개념이 중요하다.
객체지향 프로그래밍에서 객체는 클래스에 의해 정의된 실체로, 속성(필드)과 기능(메서드)을 가지고 있다.
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)); // 출력: 5
System.out.println(Calculator.add(4)); // 출력: 9
}
}
위 코드에서 Calculator 클래스는 정적(static) 멤버를 가지고 있어서 계산기를 여러 대 생성할 수 없다. 만약 다른 계산기로 독립적인 계산을 하고 싶다면, 새로운 인스턴스를 생성해야 한다.
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)); // 출력: 5
System.out.println(cal1.add(4)); // 출력: 9
System.out.println(cal2.add(1)); // 출력: 1
System.out.println(cal2.add(10)); // 출력: 11
}
}
이 코드에서는 각각의 계산기 객체를 생성하여 독립적으로 계산을 수행한다.클래스 정의만 만들어 놓고 필요할 때마다 객체를 생성하여 사용한다.
캡슐화는 클래스 내부의 데이터와 메서드를 외부로부터 보호하는 개념
외부에서는 객체의 내부 구조를 알 필요 없이, 정해진 메서드를 통해서만 객체와 상호작용한다.
이를 통해 데이터 보호 및 데이터 은닉을 실현할 수 있다.
다형성은 같은 타입의 객체가 여러 형태로 동작할 수 있는 성질을 의미.
상위 클래스 타입의 참조 변수를 사용하여 여러 하위 클래스 객체를 참조할 수 있는 능력으로.
주로 인터페이스와 관련이 있으며, 상황에 맞게 객체가 다르게 행동할 수 있도록 한다.
클래스는 객체의 속성과 기능을 정의하는 청사진. 설계도라고 볼 수 있으며, 객체는 이 설계도를 기반으로 만들어진 실체이다.. 객체를 생성하려면 클래스 정의가 필요하다.
객체는 클래스를 바탕으로 생성된 실제 실체로, 속성(필드)과 기능(메서드)을 가진다.
(참조타입) (변수) = new (클래스명);
참조타입에 클래스가 올 경우, 변수는 해당 클래스의 객체가 된다.
class Calculator {
int result = 0; // 인스턴스 변수 (필드)
int add(int num) {
result += num;
return result;
}
int multiple(int num) {
result *= num;
return result;
}
}
위 코드에서:
result는 계산 결과를 저장하는 속성add()와 multiple()은 계산기를 통해 수행할 기능을 정의