객체 지향 프로그래밍
- 1960년대 하드웨어 발전 속도에 비해 소프트웨어의 발전 속도는 느렸다.
- 하드웨어처럼 모듈화를 못했기 때문이었다. 이를 위해 소프트웨어에 객체지향 패러다임이 등장
- 객체와 객체 간 자유로운 데이터 이동이 가능
- 절차지향프로그래밍 : 위에서 부터 아래로 순차적으로 진행되는 형태
- 프로그램 재사용시 기존에 만들어진 코드를 복사 붙여넣기 하는 방식
- 모듈을 만들어 공유 데이터로 공유하는 방식으로 구현되었지만, 유기적으로 연결되어야하는 단점이 있다.
객체
- 현실 세계에 존재하는 모든 것을 객체로 표현
- 정적인 요소 : 변수 - 자동차 (이름, 속도, 제조사)
- 동적인 요소 : 메서드 - 자동차 (시동켠다(), 가속한다(), 시동끈다())
클래스
- 현실 세계의 객체를 컴퓨터 메모리에서 생성할 수 있는 일종의 템플릿 역할을 수행한다.
- 이러한 클래스를 통해 생성된 객체들을 인스턴스(Instance)라고 부른다. (객체 == 인스턴스)
상속 (Inheritance)
- 기존의 클래스를 이용해 새로운 클래스를 만드는 것을 의미 (상속 받으면 해당 메서드,변수를 모두 사용 가능 - private, 부모와 같은 변수명이 존재하는 경우 제외) -> extends로 사용
- 공통으로된 부분을 따로 모아 코드의 재사용성을 높이는 객체지향의 핵심 기술이다.
- 또한, 객체들 사이의 계층 구조를 이룰 수 있어 자동차 -> 승용차, 버스 -> 관광버스, 시내 버스 등 위로 갈수록 일반화, 밑으로 갈수록 특수화,개별화 구조를 이룰 수 있다. (is a~ 관계 - "자식 클래스는 부모클래스다." ex) 도트 프린터는 프린트다라는 논리가 맞아야한다. )
- Inheritance 가 아닌 extends를 사용하는 이유는 상속은 확장한다는 의미가 중요하기 때문
class Employee { // 사원 클래스 (사원 안에 관리자도 존재하는 경우)
String name;
int number;
}
class Manager extends Employee { // 관리자 클래스
int managerNumber; // Employee의 정보를 상속 받아 추가적인 멤버 변수만 선언
}
단일상속
- 개발의 편의성과 가독성을 위해 문법적으로 단일상속만을 허용
class Printer {
int price;
}
class Camera {
int price;
}
class PolaloidCamera extends Camera, Printer { // Printer와 Camara 두 개를 상속한 클래스
}
- 다음과 같이 다중 상속 받은 경우 상속받은 변수명이 일치할 때 문제 발생.
상속과 생성자
- 자식 클래스의 객체를 생성하면 부모 클래스의 객체를 생성한 후 (생성자 호출) , 자식 클래스 객체를 생성한다.
- 부모 클래스의 생성자가 오버로딩된 경우에는 super(매개변수)를 통해 구분지어야 한다. 단, super()를 사용할 때는 항상 자식클래스의 생성자보다 먼저 수행되어야한다.
class SuperClass {
int num1;
public SuperClass() {
System.out.println("SuperClass 객체 생성");
num1 = 100;
}
}
class SubClass extends SuperClass {
int num2;
public SubClass() {
System.out.println("SubClass 객체 생성");
num2 = 10000;
}
}
public class ConstructorTest {
public static void main(String[] args) {
SubClass sub = new SubClass();
System.out.println(sub.num1);
System.out.println(sub.num2);
}
}
[출력 결과]
SuperClass 객체 생성
SubClass 객체 생성
100
10000
다형성
- 하나의 인터페이스를 이용해 서로 다른 구현을 제공 (모든 티비를 하나의 리모콘으로 조종 - 동일한 인터페이스)
- 메서드 오버로딩, 오버라이딩을 통해 지원
- 오버로딩 : 같은 이름의 메서드를 여러 개 정의 (절차지향언어는 한 개밖에 선언 못함)
- 오버라이딩 : 상속 관계에 있는 하위 클래스가 상위 클래스의 메서드를 재정의 하는 것
추상화
- 현실 세계에 존재하는 다양한 객체들의 공통된 특성을 모아 일반화해 놓는 것
- 운송 수단(추상화) - 비행기, 자동차, 배 등
캡슐화
- 변수와 메서드를 하나의 추상화된 클래스로 묶는 과정 - 변수와 메서드를 하나로 묶어 독립적으로 동작하지 않게 함
정보 은닉
- 접근 제한자를 통해 정보은닉을 구현할 수 있다.
- 캡슐화된 변수나 메서드를 공개하거나 숨길 수 있음(은닉) - 접근제어자 private : 은닉, public : 공개
메세지
- 객체 간 서로 통신하는 방법 - 여러 객체는 동일한 프로세스를 가질 필요가 없다.
Modifier
static
- 멤버 변수와 메서드 앞에 붙일 수 있는 제어자로 활용 방법을 제어
- static X : 인스턴스 변수 - 객체가 생성될 때마다 인스턴스 변수들이 생성
- static O : 클래스 변수 - 별도의 메모리에 생성, 메모리에 하나의 변수만 존재
final
- 단 한 번 초기화가 가능하고, 이후에 값을 변경할 수 없는 상수이다.
- 부모 클래스에서도 final 을 설정하면 하위 클래스에서 재정의를 제한할 수 있다.
- 클래스 변수의 값을 변경하지 못하게 할 경우 static final을 동시에 사용한다.
abstract
- 메서드의 시그니처 (리턴 타입, 메서드명, 매개변수)만 정의되고 구체적인 행위 ( {}부분은 정의 되지 않는 메서드 )
- 추상 메서드를 사용하는 이유 : 의미가 없는 메서드지만, 자식 클래스에서 오버라이딩 했을 때 상속을 통해 의미를 가지게 되는 메서드이다. -> 유지보수의 편의성을 높이기 위해 사용 (추상 클래스를 상속받은 모든 클래스들이 강제로 동일한 메서드를 사용하게 된다)
- 추상 클래스는 객체를 생성할 수 없다. (아무런 기능이 없은 추상 메서드를 호출한다는 것이 논리적으로 이상하기 때문)
returnType([argType argName, ...]) {...} // 일반 메서드
abstract returnType name([argType argName, ...]); // 추상 메서드
- 추상 메서드를 포함하고 있는 클래스는 반드시 추상 클래스로 선언되어야 한다.
abstract class Abstract { // 반드시 추상 클래스로 선언
public abstract void method() {...}; // 추상 메서드가 존재하므로 추상 클래스여야 한다.
}
추상 클래스와 상속이 결합되어야 정확하게 추상 클래스를 이해할 수 있다.
abstract class SuperClass {
public void methodA() {
System.out.println(" ");
}
public abstract void methodB();
}
class SubClass extends SuperClass { // SuperClass 상속
}
- 부모 클래스를 상속 받은 자식 클래스는 구현되지 않은 methodB()가 추상 메서드로 있는 것과 동일
자식 클래스가 추상 클래스로 선언되어야 하지만 선언하면 객체를 생성할 수 없는 클래스가 된다.
abstract class SuperClass {
public void methodA() {
System.out.println(" ");
}
public abstract void methodB();
}
class SubClass extends SuperClass { // SuperClass 상속
public void methodB() {
System.out.println("Overriding!");
}
}
- 추상 메서드를 자식클래스에서 재정의해서 해결한다.
내부 클래스
- 내부 클래스가 생성되기 위해선 외부 클래스의 객체가 반드시 필요
- 정의되는 위치에 따라서 멤버 클래스와 지역 클래스로 나뉜다.
- 멤버 클래스 : static, instance로 구분
- 지역 클래스 : 메서드 내에 존재하며 지역 클래스, 무명 클래스로 구분 (메서드 내부에서만 사용 가능)
class Outside {
public class Inside {
}
}
public class InnerClassTest {
public static void main(String[] args) {
Outside outer = new Outside();
// 내부 클래스 객체 생성을 위해 외부 클래스 객체 생성
Outside.Inside inner = outer.new Inside();
}
}
생성자
- 생성자는 클래스와 같은 이름을 가진 특별한 메서드로 객체 생성 시 멤버 변수의 초기화를 담당
- 매개변수가 없는 기본 생성자가 존재하며, 추가로 생성자가 하나라도 있는 경우에는 기본 생성자는 제공되지 않는다. (기본 생성자는 객체 생성 및 초기화 역할을 한다.)
class Employee {
String name;
int number;
int age;
public Employee(String name, int number, int age) {
this.name = name; // 멤버 변수와 매개 변수가 같을 땐 this로 구분 (this.멤버 = 매개)
this.number = number;
this.age = age;
}
}
가변적 매개변수
int sum (int... num) {
// 매개변수가 여러 개일 경우 가변 매개변수는 마지막 위치에서 딱 한번 사용 가능
}
sum(1);
sum(1,2);
sum(1,3);
형변환
- 객체 참조변수의 경우에도 형변환이 이루어진다. 객체의 형변환을 이용하면 유지보수가 편해진다.
- leftObjectReference = rightObjectReference
- 왼쪽 객체가 상위 클래스인 경우에만 묵시적 형변환이 일어나며, 반대의 경우 명시적 형변환을 해야 한다. (instanceof 연산자로 생성된 객체가 class와 관계있는 타입인지 확인 필요)
class Employee {
}
class Manager extends Employee {
}
public class InstanceOfTest {
public static void main(String[] args) {
Manager m = new Manager();
Employee e = new Employee();
System.out.println(m instanceof Manager); // true
System.out.println(m instanceof Employee); // true
System.out.println(e instanceof Manager); // false
}
}
class Parent {
int num = 10;
void print() {
System.out.println(num);
}
}
class Child extends Parent {
int num = 20;
void print() {
System.out.println(num);
}
}
public class ObjectCastTest {
public static void main(String[] args) {
Parent p = new Child(); // 왼쪽이 상위 클래스이므로 객체간 묵시적 형변환 발생
p.print(); // Child 객체에 오버라이딩 된 메서드 호출
System.out.println(p.num);
}
}
[출력]
20
10
- 묵시적 형변환을 하는경우 : 변수에 대한 접근은 객체의 유형에 의해 결정됨, 메서드 호출은 할당되는 인스턴스에 의해 결정됨.
- 즉, 객체 참조 변수가 메서드나 메서드를 참조하는 경우, 참조 관계를 결정하는 시간이 다르기 때문에 나타나는 차이이다.
// 추상클래스 TV 정의 : 메서드를 일치시키기 위해 추상클래스로 구현
abstract class TV {
public abstract void powerOn();
public abstract void powerOff();
}
class S_TV extends TV {
@Override
public void powerOn() {
System.out.println("S_TV 전원 켠다.");
}
@Override
public void powerOff() {
System.out.println("S_TV 전원 끈다.");
}
}
class L_TV extends TV{
@Override
public void powerOn() {
System.out.println("L_TV 전원 켠다.");
}
@Override
public void powerOff() {
System.out.println("L_TV 전원 끈다.");
}
}
class TVFactory { // TV 객체를 생성해서 리턴하는 클래스
public TV getTV(String tvName) {
if(tvName.equals("A")) return new S_TV();
else if (tvName.equals("B")) return new L_TV();
return null;
}
}
public class AbstractTVUser {
public static void main(String[] args) {
TVFactory factory = new TVFactory();
TV tv = factory.getTV("A"); // A사인 S_TV 클래스 호출
tv.powerOn();
tv.powerOff();
}
}
인터페이스
- 추상 클래스보다 추상성이 더욱 심화된 개념
- 멤버 변수는 상수형, 메서드는 모두 추상 메서드로 선언
- 인터페이스는 상수와 추상 머세드 외에 다른 멤버를 갖지 못하게해서 완벽한 추상화를 제공
인터페이스를 사용하는 이유?
자바에서 상속인 경우는 'is a ~' 관계가 성립해야된다. (ex : 갤럭시 is a 핸드폰)
논리적으로 'is a ~'관계가 성립하지 않거나, 다중상속을 받고 싶은 경우에 인터페이스를 활용한다. 예를 들어 폴라로이드 카메라는 사진을 찍고 바로 인화를 할 수 있으므로 프린터, 카메라 클래스를 다중 상속을 받을 수 있다. 하지만 상속 관계에서 '폴라로이드 카메라는 프린터이다'가 논리적으로 성립되지 않으므로 상속이 아닌 인터페이스를 활용하여 구현할 수 있다.
왜 상속에서는 다중 상속을 금지했지만 인터페이스는 가능할까?
이유는 간단하다. 인터페이스는 변수를 가질 수 없는 특수한 클래스이므로 동일한 이름의 변수가 중복되는 경우가 없으므로 가능하다.
인터페이스 사용법
public interface 인터페이스명 [extends 부모 인터페이스명, ...] {
/*
인터페이스가 다른 인터페이스를 상속 받는 경우에는 extends를 사용하여 정의한다.
상수 : final를 이용한 멤버 변수 선언, 인터페이는 객체 생성할 수 없으므로 static을 붙여 선언
메서드 : 반드시 추상 메서드(abstract)를 사용해야 한다.
단, abstract, static final 은 생략 가능하다.
*/
public static final int PENSIL = 1;
public static final int SHARP = 2;
public abstract void write(); // 추상 메서드이므로 {} 존재 X
public abstract void remove(int x, int y);
}
인터페이스 동작 과정
인터페이스는 추상 클래스처럼 직접 객체화 되지 못한다. 인터페이스 상속(implements) -> 자식 클래스 생성 - > 자식 클래스 객체 생성 -> 프로그램 사용의 과정이 된다. 인터페이스를 상속 받은 자식 클래스에서 객체를 생성하면 된다는 의미.
class 클래스명 [extends 부모 클래스] implements 인터페이스1, 인터페이스2 {
...
}
- 인터페이스를 상속하는 클래스는 인터페이스에 정의된 추상 메서드들을 오버라이딩 해야 한다. 추상 메서드를 하나라도 오버라이딩하지 않으면 해당 클래스는 추상 클래스(abstract class)로 선언된다.
인터페이스 활용
interface Drawable {
public int P_PEN = 1; //static final 생략
public int B_PEN = 2;
public void draw(); // abstract 생략
public void move(int x, int y);
}
class Shape {
int x;
int y;
Shape(int x, int y) {
this.x = x;
this.y = y;
}
}
class Circle extends Shape implements Drawable {
int radius;
Circle(int x, int y, int radius) {
super(x, y);
this.radius = radius;
}
@Override
public void draw() {
System.out.println(x+" "+y+" radius = " +radius);
}
@Override
public void move(int x, int y) {
System.out.println(this.x+x +" "+this.y+y+" radius= " +radius);
}
}
class Rectangle extends Shape implements Drawable {
int width;
int height;
public Rectangle(int x, int y, int width, int height) {
super(x, y);
this.width = width;
this.height = height;
}
@Override
public void draw() {
System.out.println(x +" "+y+" width = "+width +" height = "+height);
}
@Override
public void move(int x, int y) {
System.out.println(x+this.x+" "+(y+this.y)+" width = "+width +" height = "+height);
}
}
public class InterfaceTest1 {
public static void main(String[] args) {
Circle c = new Circle(10, 10, 100);
c.draw();
c.move(5,5);
Rectangle r = new Rectangle(20,20,50,50);
r.draw();
r.move(5, 10);
}
}
인터페이스 형변환
class Circle extends Shape implements Drawable {}
Shape s = new Circle();
Drawable d = new Circle();
- 인터페이스도 상속 받은 부모클래스와 같이 동일한 지위를 가지므로 위의 예시와 같이 묵시적 형변환(부모 = new 자식)이 가능하다.
- 인터페이스 유형의 객체 참조 변수는 인터페이스의 선언된 요소에만 접근이 가능하다. (즉, d는 Drawable 인터페이스가 가지고 있는 상수와 메서드만 접근이 가능)
인터페이스 상속
interface Paintable {
public void paint();
}
// Printable이 extends를 통해 인터페이스 두 개를 상속 받음.
interface Printable extends Paintable, Drawable {
public void print();
}
class Circle2 implements Printable{
// Printable 이 상속 받은 Drawable 인터페이스
@Override
public void draw() {
System.out.println("원 그리기");
}
@Override
public void move(int x, int y) {
System.out.println(x+" "+y +"원을 이동");
}
// Printable 이 상속 받은 Paintable
@Override
public void paint() {
System.out.println("원을 색칠");
}
// Circle2가 상속 받은 Printable
@Override
public void print() {
System.out.println("원을 출력");
}
}
public class InterfaceTest2 {
public static void main(String[] args) {
Circle2 c = new Circle2();
c.draw();
c.move(5,5);
c.paint();
c.print();
}
}
예외 처리
- 에러 : 프로그램으로 처리할 수 없는 상황으로 프로그램이 중단됨. (랜선이 끊어지는 경우 등)
- 예외 : 프로그램 실행 중에 발생할 수 있는 가벼운 사건 (프로그램적으로 처리할 수 있는 것)
자바는 객체지향 언어이므로 예외도 객체로 처리한다. 즉, 예외를 처리하기 위해선 객체를 생성할 클래스가 필요하게 된다.
예외 처리 과정은 JVM이 예외 객체를 (throw) 프로그램에 전달하고 예외 발생 시 다시 JVM이 받아와 예외 처리 로직을 수행하고 프로그램을 계속 진행한다. 예외 처리 로직을 구현하지 않는 경우 기본 예외 처리 핸들러가 동작하며 단순히 에러 메시지를 콘솔에 출력하고 실행을 중지시킨다.
모든 예외는 java.lang.Throwable 클래스의 자식 클래스인 java.lang.Exception 클래스의 자식 클래스로 만들어진다.
예외 처리 방법
- 예외가 발생한 메서드 내에서 직접 처리하는 방식
- try ~ catch 구문, 다중 catch 블록, 상속을 이용한 예외 처리, finally 블록
- 해당 메서드를 호출한 곳으로 예외 처리를 넘기는 방식
- throws 예약어
try ~ catch
try { // try 블록은 최소한 하나의 catch 블록이 있어야 한다.
예외 발생 가능이 있는 소스코드;
} catch (예외 타입 매개변수명) {
예외 타입의 예외가 발생할 경우 수행될 소스코드;
} finally {
예외 발생과 무관하게 반드시 실행되는 코드;
}
- 예외 객체 발생 -> try 블록의 나머지 문장 수행 X -> JVM은 발생한 예외 객체를 확인 -> 예외 타입에 맞는 catch 블록 수행
public class ExceptionTest {
public static void main(String[] args) {
try {
int num = 9;
int num2 = 0;
System.out.println(num/num2);
} catch (ArithmeticException e) {
System.out.println("정수를 0으로 나눌 수 없습니다.");
}
}
}
- 숫자를 0으로 나눌 경우 ArithmeticException 예외가 발생한다. 이 부분을 catch에서 잡아준다. 모든 예외처리는 Exception의 하위 클래스이다. 예외 경우를 모를 때 catch에서 Exception e를 사용해주면 된다.
throws
- try ~ catch : 해당 메서드 안에서 처리, throws : 호출된 메서드에서 처리
private static int score (int[] arr) throws ArrayIndexOutOfBoundsException{
... // 배열 인덱스 초과 에러 코드;
}
커스텀 예외 처리
API에서 제공되지 않는 경우 개발자가 직접 예외 처리를 해야 한다. 이럴 경우 모든 예외 클래스의 최상위 클래스인 java.lang.Exception 클래스를 상속받아 정의한다.
class 클래스명 extends Exception { }
커스텀 예외처리를 해야될 경우가 언제있을까?
예를 들면, 고객의 인출 관리 프로그램은 고객의 잔고보다 인출금액이 클 수 없다. A 고객 잔고는 100만원인데 150만원을 인출하려고 시도할 경우 산술적으로는 -50(정상)이지만 프로그램적으로는 비정상적이므로 인출이 되지 않게 처리해야 된다.
throws
- JVM이 프로그램에서 예외가 발생하면 자동으로 catch 블록을 사용하지만, throws를 통해 사용자가 직접 예외를 생성할 수 있다. (단, throw를 이용한 예외 발생시에도 try~catch 구문을 이용하여 예외 처리를 해야 한다.)
// 단 해당 방법은 API에서 제공하는 예외 클래스를 사용할 경우에만 가능(JVM이 try~catch 처리)
class Account {
String name;
int currentMoney;
public void withdraw(int money) {
try {
if (currentMoney < money) throw new IllegalArgumentException();
currentMoney = currentMoney - money;
} catch (IllegalArgumentException e) {
System.out.println("잔액이 부족합니다.");
}
}
}
- 방법 1
// 커스텀 예외처리를 throws를 사용하여 처리
class Account {
String name;
int currentMoney;
public void withdraw(int money) throws IllegalArgumentException{
if(currentMoney < money) throw new IllegalArgumentException();
currentMoney = currentMoney - money;
}
}
- 방법 2
예시
public class CustomExceptionTest {
public static void main(String[] args) {
try {
Account kim = new Account("김",100);
kim.withdraw(150);
System.out.println(kim.toString());
} catch (BadBankingException e) {
System.out.println(e.getMessage());
}
}
}
class BadBankingException extends Exception { // 커스텀 예외처리
public BadBankingException(String s) {
super(s);
}
}
class Account {
String name;
int currentMoney;
public Account(String name, int currentMoney) {
this.name = name;
this.currentMoney = currentMoney;
}
public void withdraw(int money) throws BadBankingException{
if(currentMoney < money) throw new BadBankingException("잔액이 부족합니다.");
currentMoney = currentMoney - money;
}
public String toString() {
return "name = " +name +" 현재 금액 = "+currentMoney;
}
}
String 클래스
- 자바에서 문자열 객체를 생성하고 처리하기 위해서 String 클래스를 사용해야한다.
String name = new String("자바");
String name2 = "자바마트";
// new 연산자를 사용하지 않는 경우 컴파일 시점에서 메모리를 할당 받고 같은 문자열이면 주소값을 재사용
- String 객체 생성 방법 (new 연산자 사용, 사용x 2가지 방법)
- String은 '+'연산자와 결합 하면 다른 한쪽이 다른 타입의 데이터여도 모두 String 타입으로 변환되어 두 개의 문자열을 결합한다. 예를 들어 25 + "abc" = 25abc 가 된다.
문자열 생성
public class Dummy {
public static void main(String[] args) {
//String 클래스는 문자열이 사용될 때마다 문자의 길이를 다시 계산할 필요가 없어서 편리
char[] charArr = {'J','A','V','A'};
byte[] byteArr = {65,66,67,68,69};
String str1 = new String(charArr); // JAVA - char 배열
String str2 = new String(charArr, 0, 2); // JA
String str3 = new String(byteArr, 0,2); //AB - byte 배열
}
}
String 메서드
public class StringTest {
public static void main(String[] args) {
String comp = "Java JDJ";
System.out.println(comp.concat(" Program")); // 문자열 결합
System.out.println(comp.toLowerCase()); // 소문자
System.out.println(comp.toUpperCase()); // 대문자
System.out.println(comp.startsWith("Java")); // 문자열 시작확인
System.out.println(comp.endsWith("J")); // 문자열 끝 확인
System.out.println(comp.equalsIgnoreCase("java jdj")); // 대소문자 무시후 동일한지 확인
System.out.println(comp.indexOf("JDJ")); // 시작 인덱스 위치 확인
System.out.println(comp.lastIndexOf("va")); // 인덱스 반환 단 뒤에서부터 계산
System.out.println(comp.substring(5));
System.out.println(comp.replace("J", "K"));
}
}
[출력]
Java JDJ Program
java jdj
JAVA JDJ
true
true
true
5
2
JDJ
Kava KDK
StringBuffer vs String vs StringBuilder
public class StringBufferTest {
public static void main(String[] args) {
String str = "회사 : ";
str.concat("전자");
System.out.println(str); // 회사명 :
StringBuffer sb = new StringBuffer("회사 : ");
sb.append("전자");
System.out.println(sb); // 회사명 : 전자
}
}
위의 예제를 보면 String 클래스에서 concat으로 문자열 결합을 했지만, 결합되지 않는다. 그 이유는 String 클래스는 문자열 상수로, 모든 메서드의 실행 결과로 또 다른 문자열 객체를 생성하여 리턴하기 때문이다. (변경 불가) 즉, str의 메모리 공간에 값을 변경하는 것이 아닌 새로운 공간에 새로운 객체를 생성하여 저장하기 때문이다.
반면 StringBuffer 클래스의 객체는 문자열을 직접 조작한다. 크기가 동적 (기본 16개 문자 저장할 수 있는 버퍼 공간)이라 변하는 문자열을 다룰 때 사용한다. 하나의 StringBuffer 객체만 유지하기 때문에 속도, 메모리 면에서 모두 효율적이다.
StringBuffer 랑 StringBuilder 차이 ?
자바 알고리즘을 풀어봤다면 대부분 StringBuilder를 사용해서 문자열 처리를 했을 것이다. 그 이유는 동일한 API 지만 동기화의 유무에서 차이가 있다. String과 StringBuffer 는 멀티쓰레드 환경에서 안전성을 갖고 있고, 단일쓰레드에서 성능은 떨어지는 편이지만 StringBuilder 는 멀티쓰레드 환경에서 불안전하지만, 단일쓰레드 성능은 뛰어나다. 그렇기 때문에 동시성 이슈를 고려하지 않기 때문에 알고리즘을 풀 때 StringBuilder를 대부분 사용하는 것이다.
public class StringBufferTest {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("java");
System.out.println(sb.capacity()); // 저장 공간 : 20
System.out.println(sb.length()); // 길이 : 4
sb.append(" language");
System.out.println(sb.capacity()); //20
System.out.println(sb.length()); // 13
sb.insert(5,"program "); //5번 인덱스에 삽입
System.out.println(sb);
}
}