Programming/Java

메서드, return, 객체지향 프로그래밍, 객체, 생성자, 오버로딩 Null, 가비지컬렉터, 패키지, 상속, 메서드 오버라이딩, final

김심슨 2025. 6. 2. 12:19

메서드

특정 작업을 수행하는 코드 블록, 프로그램의 재사용성+구조화를 위해 사용됨

자바에서는 함수가 없고 메서드라고 한다.

클래스 안에 정의되며, 리턴타입 메서드 이름(매개변수) 형식으로 선언됨

리턴되는 값의 타입을 지정해줘야한다. (자바스크립트랑 다른점)

int add(int a, int b) 두 정수를 더해 결과를 반환하는 메서드

형태

리턴타입 메서드이름(매개변수들) {
	실행할 코드 
	return 값; 
}
  • 값을 반환하지 않을 경우 void 사용
  • 매개변수 : 메서드 호출 시 전달되는 값(없을 수도 있음)

반환값이 없고 매개변수도 없는 메서드

public void printHello() {
    System.out.println("안녕하세요!");
}

printHello 로만 부르면 되는 메서드

반환값이 없고 매개변수가 있는 메서드

public void printName(String name) {
    System.out.println("이름: " + name);
}

printName(’apple’) 이라고 호출함 ⇒ 이름 : apple

반환값이 있고 매개변수가 없는 메서드

public int getRandomNumber() {
    return (int)(Math.random() * 100); // 0~99 사이의 정수
}

반환값이 있고 매개변수가 있는 메서드

public int multiply(int a, int b) {
    return a * b;
}

가변 매개변수를 사용한 메서드

public int sum(int... numbers) {
    int total = 0;
    for (int n : numbers) {
        total += n;
    }
    return total;
}
  • 몇 개인지 몰라, 암튼 여러 개를 받을거야.
  • 배열로 받음. numbers라는 배열이 생기고, for문을 돌릴 수 있음
  • [10,4,9,8] → 4바퀴를 돌게됨.
  • sum(10,4,9,8)로 실행했다고 볼 수 있음

예제 : Ex01_Method

package lesson03;

public class Ex01_Method {
    public void printHello() {
        System.out.println("안녕하세요");
    }

    // 시작점을 만들어 줄게요! (main 입력)
    public static void main(String[] args) {
        // 클래스 명 써주고, 
        Ex01_Method ex01_Method = new Ex01_Method();
        // 변수명 .메서드로 접근하면 된다.
        ex01_Method.printHello();
    }
}
  • static을 써서 클래스명 쓰고~ 변수명 쓰고~ 메서드 쓰고~ 이런 절차 안해도 된다.
    • class가 힙 메모리 영역에 먼저 올라감. → 별도로 사용할 수 있는 메서드가 됨.
    • 객체를 만들지 않고 그냥 메서드를 쓰고 싶다. 할 때 사용하면 됨
<< 이어서 >>

package lesson03;

public class Ex01_Method {
    public void printHello() {
        System.out.println("안녕하세요");
    }

    public void printName(String name) {
        System.out.println("이름 : " + name);
    }

    // 매개변수 x, 리턴 값 o
    public int getRandomNumber() {
        return (int) (Math.random() * 100);
    }

    public int multiply(int a, int b) {
        return a * b;
    }

    public int sumAll(int... numbers) {
        int sum = 0;
        for (int n: numbers) {
            sum += n;
        }
        return sum;
    }

    // 시작점을 만들어 줄게요! (main 입력)
    public static void main(String[] args) {
        // 클래스 명 써주고, 변수명 . 으로 접근하면 된다.
        Ex01_Method ex01_Method = new Ex01_Method();
        ex01_Method.printHello();
        ex01_Method.printName("김사과");

        int rand  = ex01_Method.getRandomNumber();
        System.out.println("랜덤 숫자 :" + rand);

        int result = ex01_Method.multiply(4,5);
        System.out.println("곱셈 결과 :" + result);

        int total = ex01_Method.sumAll(1,2,3,4,5);
        // 배열로 받게 되고, 갯수만큼 for문을 돌면서 더해줌
        System.out.println("덧셈 결과 :" + total);
        System.out.println("합(빈 값으로 호출) :" + ex01_Method.sumAll());
    }
}

--------------------------------------------------------------
안녕하세요
이름 : 김사과
랜덤 숫자 :66
곱셈 결과 :20
덧셈 결과 :15
합(빈 값으로 호출) :0

return

메서드 실행을 끝내고, 호출한 쪽에 값을 전달할 때 사용하는 키워드

void 메서드에서의 return (생략 가능)

public void sayHello() {
    System.out.println("안녕하세요!");
    return; // 생략해도 자동으로 메서드는 끝나게 됨
}

값을 반환하는 메서드

public int add(int a, int b) {
    return a + b; // 결과를 호출한 쪽으로 돌려줌
}

조건문 안에서 return 사용 (프로그램 잘 짜는 사람들은 이런식으로 만들어용!)

에러가 나면 걸러내는 것 (잘못된 값이 들어오면 에러를 내보내야 하는 것) 들을 위에 배치해서 걸러내고 실제 계산해야하는 것은 아래에 둔다. (뭐가 아닌 것! 부터 걸러내자!)

public String checkEven(int number) {
    if (number % 2 == 0) {
        return "짝수입니다";
    }
    return "홀수입니다";
}
  • 데이터 타입이 일치해야함.
public void testReturn() {
    System.out.println("시작");
    return;
    // 아래 코드는 절대 실행되지 않음
    // System.out.println("끝");
}

[클래스와 객체] 객체지향 프로그래밍

머리로 떠올리는 모든 것들을 객체(object)라는 단위로 모델링하여 프로그램을 구성하는 방식

  • 명사(field)는 변수, 동사(method)는 함수

⇒ 코드 재사용성과 확장성을 높여주는 프로그래밍 패러다임

객체 : 데이터(속성) + 그 데이터를 처리하는 함수(메서드)를 함께 가짐 → 클래스를 통해 설계되고 인스턴스를 통해 사용됨

특징 : **캡슐화, 상속, 다형성, 추상화** ⇒ 구조를 체계화하고 유지보수와 협업을 용이하게 해용!

클래스 (class)

클래스는 변수(필드, 속성)와 메서드(함수)를 포함하여 하나의 단위로 묶으며, 이를 바탕으로 실제 사용할 수 있는 객체(인스턴스)를 생성할 수 있습니다. 예를 들어 Car라는 클래스를 만들면, 이 클래스는 자동차의 속성(예: 색상, 속도)과 동작(예: 달리기, 멈추기)을 정의하고, 이를 기반으로 여러 대의 자동차 객체를 생성하여 사용할 수 있습니다. 클래스는 객체지향 프로그래밍의 핵심 구성요소로, 코드의 재사용성과 구조화를 가능하게 합니다.

클래스를 쓴다는 것…. 객체지향 프로그래밍을 한다는 것..

<aside> 👉

"자동차"라는 개념을 프로그래밍으로 표현하고 싶다면?

  • 속성(필드): 색상, 속도, 브랜드 등
  • 동작(메서드): 달리다(run), 멈추다(stop) 등

이런 속성과 동작을 정의한 것이 클래스이고, 이 클래스를 바탕으로 만들어진 각각의 자동차는 객체(Object) 혹은 인스턴스(Instance)라고 부릅니다.

</aside>

클래스의 구성 요소

필드 : 객체가 가지는 변수, 속성 (이름, 나이 등)

메서드 : 객체가 할 수 있는 동작, 함수

생성자 : 객체가 생성될 때 호출되는 특수한 메서드

예제 : Ex02_Dog

package lesson03;

public class Ex02_Dog {
    String name; //이름을 저장하려고 함
    int age; // 나이를 저장하려고 함

    public void bark() {
        System.out.println(name + "가 멍멍 짖습니다");
    }

    public void introduce() {
        System.out.println("안녕하세요! 제 이름은 " + name + "이고, 나이는" + age + "살 입니다");
    }
}

예제 : Ex02_Main (위의 파일과 연결)

package lesson03;

public class Ex02_Main {
    public static void main(String[] args) {
        // Ex02_Dog 를 불러 쓸 겨
        Ex02_Dog dog = new Ex02_Dog(); // 객체 생성
        dog.name = "루시";
        dog.age = 18;
        dog.introduce();
        dog.bark();
        
        // 다른 객체 만들기
        Ex02_Dog dog2 = new Ex02_Dog();
        dog2.name = "뽀미";
        dog2.age = 8;
        dog2.introduce();
        dog2.bark();
    }
}

---------------------------------------------
안녕하세요! 제 이름은 루시이고, 나이는18살 입니다
루시가 멍멍 짖습니다

안녕하세요! 제 이름은 뽀미이고, 나이는8살 입니다
뽀미가 멍멍 짖습니다

객체 (Object)

클래스라는 설계도를 바탕으로 생성된 실제 사용 가능한 실체 (인스턴스)

클래스에 정의된 속성+동작을 그대로 가지고 있음

프로그램 내에서 다양한 데이터를 저장하고 기능을 수행하는 주체로 활용됨

<aside> 👉

예를 들어 Car라는 클래스가 있다면, myCar = new Car();처럼 생성된 myCar는 Car 클래스의 객체이며, 고유한 색상, 속도 등의 값을 가지며 달리기(run) 같은 동작을 수행할 수 있습니다.

</aside>

⇒ 클래스에서 정의한 구조를 바탕으로 실제 메모리에 생성되어 작동하는 실질적인 요소

객체 vs 인스턴스

정의 클래스에 의해 생성된 실체를 의미 객체가 특정 클래스의 구성 요소로 생성되었을 때를 강조하는 표현

포괄성 더 일반적인 개념 보다 구체적인 의미, 어떤 클래스의 인스턴스인지 명확히 함
예시 표현 "메모리에 존재하는 독립된 실체" "이 객체는 A라는 클래스의 인스턴스다"
관점 프로그래밍 실체 자체를 말할 때 사용 클래스와의 관계를 강조할 때 사용
비유 '강아지'라는 개체 '강아지는 동물(Animal) 클래스의 인스턴스다'

인스턴스는 객체보다 좀 더 구체적인 개념이라고 볼 수 있다.

생성자 (constructor)

클래스로부터 객체를 생성할 때 호출되는 특수한 메서드

자동으로 생성되고, 객체가 생성될 때 필요한 초기값을 설정(초기화)해주는 것

이름 클래스 이름과 동일해야 함 (반드시!)

반환형 없음 (void도 쓰지 않음)
호출 시점 new 키워드로 객체를 생성할 때 자동으로 호출됨
여러 개 가능 매개변수를 달리하여 생성자 오버로딩 가능

오버로딩 : 매개변수, 반환값이 다르다면 여러 개 만들 수 있음

매개변수가 없는 생성자

public class Person {
    String name;
    int age;

    // 기본 생성자
    public Person() {
        System.out.println("기본 생성자 호출됨!");
    }
}

Person person = new Person()

타입 객체 생성자 호출

매개변수가 있는 생성자 / 예제 : Ex03_Person

public class Person {
    String name;
    int age;

    // 생성자 (이름과 나이를 초기화)
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Person person = new Person(”apple”, 20)

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("김사과", 20);  // 생성자 호출됨
        System.out.println(p1.name + ", " + p1.age);
    }
}
  • 하나의 파일에는 하나의 public만 있어야 한다. (public 제거)
package lesson03;

public class Ex03_Person {
    String name;
    int age;

    public Ex03_Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
} => 자동으로 생성자를 호출한다. 

class Ex03_Main {
    public static void main(String[] args) {
        Ex03_Person person = new Ex03_Person("김사과", 20);
        System.out.println(person.name + ", " + person.age);
    }
}
----------------------------------
김사과, 20

생성자 오버로딩 : 같은 이름의, 시그니처가 다른 메서드를 여러 개 만드는 것

같은 이름의 생성자를 매개변수 개수나 타입이 다르게 여러 개 정의할 수 있다.

매개변수 : 괄호 안에 있는 놈

예제 : Ex04_User

package lesson03;

public class Ex04_User {
    String name;
    int age;

    // 매개변수가 없는 생성자 먼저 만들기
    public Ex04_User() {
		    this("이름없음", 0);
        System.out.println("기본 생성자 호출됨");
    }

    public Ex04_User(String name) {
        System.out.println("이름만 받는 생성자 호출됨");
    }

    public Ex04_User(String name, int age) {
        System.out.println("이름과 나이를 받는 생성자 호출됨");
        if(age < 0) {
            System.out.println("나이는 음수가 될 수 없습니다");
            this.age = 0; //이 클래스에 변수로 선언해놓은 age에 0을 세팅
        } else {
            this.age = age;
        }
        if(name == null || name.trim().isEmpty()) {
        // 함수형 프로그래밍 : 앞 뒤 공백 날리고, 값 사용하고
            this.name = "이름없음";
        } else {
        this.name = name;}
    }

    public void introduce() {
        System.out.println("안녕하세요. 제 이름은 " + name + "이고, 나이는 " + age + " 살 입니다.");
    }
    // 같은 이름의 메서드를 안에 파라미터만 다르게하면 여러 개 만들 수 있다 (오버로딩)
    public static void main(String[] args) {
        Ex04_User u1 = new Ex04_User();
        Ex04_User u2 = new Ex04_User("김사과");
        Ex04_User u3 = new Ex04_User("반하나", 25);

        u1.introduce();
        u2.introduce();
        u3.introduce();
        // String의 기본값은 null, int의 기본값은 0이다. 
        // 참조형은 기본값이 무조건 null 이다. 값을 세팅해서 누군가를 가리키는게 없기 때문.
    }
}
---------------------------------------------
기본 생성자 호출됨
이름만 받는 생성자 호출됨
이름과 나이를 받는 생성자 호출됨
안녕하세요. 제 이름은 이름없음이고, 나이는 0 살 입니다.
안녕하세요. 제 이름은 null이고, 나이는 0 살 입니다.
안녕하세요. 제 이름은 반하나이고, 나이는 25 살 입니다.

값들을 전달 받게 되면 변수에 저장을 하고 싶다. (this.name, this.age)

if(age < 0) {
            System.out.println("나이는 음수가 될 수 없습니다");
            this.age = 0; //이 클래스에 변수로 선언해놓은 age에 0을 세팅
        } else {
            this.age = age;
        }

Null

참조형 변수(객체)의 기본값 (아직 어떤 객체도 참조하지 않고 있음)

null은 기본형(int, double)에는 사용할 수 없고, 객체 타입(참조형)에만 사용할 수 있음

null pointer exception (NPE)

NULL 값을 가진 변수에서 메서드를 호출하거나 필드에 접근하려고 하면 자바는 NPE을 박생시킴

String s = null;
System.out.println(s.length());  // ❌ NPE 발생

방어코드 (주의할 점)

메서드 호출 전 NULL 체크 필수

if (s != null) {
    System.out.println(s.length());
}

equal 비교는 null 객체로 하지 말고, 상수로 시작

String name = null;

// 잘못된 방식 (null에서 메서드 호출)
if (name.equals("김사과")) { }  // ❌ NPE

// 올바른 방식 (확실한 걸 앞에 써줘라)
if ("김사과".equals(name)) { }  // ✅ 안전

래퍼 클래스는 언박싱 시 null이면 에러

자바는 C, C++을 따서 만들었기 때문에 기본 데이터 타입이 있다. int, double 등..

참조를 하지 않고, 스택에 값을 같이 저장하는 애들을 의미

스택에 저장하기 때문에 참조형이 아니라 객체가 아님. 기능이 없어요. 그냥 값 자체라고 봐야됨

⇒ 언박싱 할 수가 없음 (null을 쓸 수 없음)

Integer num = null;
int x = num;  // ❌ NPE 발생 (null → int 언박싱 불가)

// 해결 방법
if (num != null) {
    int x = num;
}
// 또는
int x = (num != null) ? num : 0;

배열 또는 리스트에서 null 요소 존재 여부 확인

String[] names = new String[3];  // [null, null, null]
// string은 기본값이 null이기 때문.

// null 값을 걸러내는 방법 
for (String n : names) {
    if (n != null) {
        System.out.println(n.length());
    }
}

Optional 사용으로 null 안전하게 처리

Optional<String> opt = Optional.ofNullable(name);

opt.ifPresent(n -> System.out.println(n.length())); // null이면 실행 안 함

String result = opt.orElse("기본값"); // null이면 기본값 사용

가비지 컬렉터

참조형임에도 불구하고 변수가 해당 객체를 가리키지 않으면 (참조가 끊어진 경우) 알아서 힙에서 삭제시켜주는 역할

개발자가 직접 메모리를 해제하지 않아도 되도록 도와주는 관리 시스템

JVM이 자동으로 수행하고, 메모리 누수를 방지하고 안정적인 프로그램 실행을 돕는다.

  • 메모리 부족 | 시스템 한가 | 명시적으로 요청 가능 (반드시 되는 건 아님) System.gc();
Dog dog = new Dog();  // Dog 객체 생성
dog = null;           // 참조 제거 → 이 객체는 더 이상 접근할 수 없음

단편화

메모리에 빈 공간이 조각조각 나뉘어 흩어져있어서, 충분한 총 공간은 있지만 연속된 공간이 부족해서 새로운 데이터를 저장할 수 없는 상태를 말함.

필요 시 남은 객체들을 앞으로 땡겨서 단편화를 줄이는 작업도 수행

패키지

관련된 클래스, 인터페이스, 열거형 등을 논리적으로 묶어주는 디렉터리 형태의 구조 (조직화, 관리 쉽게 해줌)

패키지를 사용하면 클래스 이름이 충돌하는 것을 방지할 수 있음.

서로 관련 있는 기능들을 그룹으로 묶어서 모듈화와 재사용성을 높일 수 있음.

패키지는 package 키워드로 선언. 다른 패키지의 클래스를 사용하려면 import 문을 통해 명시적으로 불러와야 함.

패키지 선언

자바소스 파일 첫 줄에 package 키워드 사용

src/
 └── com/
     └── example/
         └── utils/
             └── MathHelper.java
package com.example.utils;

public class MathHelper {
    public static int add(int a, int b) {
        return a + b;
    }
}

다른 패키지 사용

import com.example.utils.MathHelper;

public class Main {
    public static void main(String[] args) {
        int result = MathHelper.add(5, 10);
        System.out.println("결과: " + result);
    }
}

전체 패키지 임포트

import com.example.utils.*;

기본 패키지

자동으로 default package에 속함 (명시 안하면) 근데.. 웬만하면 패키지 명시 하세요.

상속

공통된 코드를 중복 없이 여러 클래스에서 사용할 수 있어 코드의 재사용성과 확장성이 높아짐

extends 키워드를 사용해 구현하며, 기존 클래스는 부모 클래스(슈퍼 클래스), 새로 정의한 클래스는 자식 클래스 (서브 클래스)라고 부름.

class 부모클래스 {
    // 공통 속성 및 동작 정의
}

class 자식클래스 extends 부모클래스 {
    // 부모의 기능을 상속받고 추가적인 기능 정의
}

예제 : Ex05_Main

package lesson03;

class Ex05_Animal {
    public void eat() {
        System.out.println("냠냠쓰");
    }
} //Animal은 eat이라는 메서드를 가지고 있다.

class Ex05_Dog extends Ex05_Animal {
    public void bark() {
        System.out.println("왕왕 짖는다.");
    }
}

public class Ex05_Main {
    public static void main(String[] args) {
        Ex05_Dog dog = new Ex05_Dog(); //객체 생성
        dog.eat(); //아무것도 안 만들었지만 기능도 상속됨
        dog.bark();
    }
}
-------
냠냠쓰
왕왕 짖는다.

메서드 오버라이딩

부모 메서드를 그대로 사용할 수도 있고 필요에 따라서 재정의하여 자신에게 맞게 바꿀 수 있다.

자바에서의 상속 제약 사항

자바는 단일 상속만 지원함. 한 클래스는 하나의 부모 클래스만 상속받을 수 있음

final 클래스는 상속할 수 없다.

생성자는 상속될 수 없음. 생성자는 자식 클래스가 직접 정의해야하며, 부모 생성자를 호출하려면 super()로 호출해야함.

예제 : Ex06_Main

package lesson03;
class Parent {
    public Parent(String msg) {
        System.out.println("부모 생성자 :" + msg);
    }
}

class Child extends Parent {
    public Child() {
        super("apple"); //부모에 넣어주는 값
        System.out.println("자식 생성자");
    }
}
public class Ex06_Main {
    public static void main(String[] args) {
        Child child = new Child();
    }
}

예제 : Ex07_Main

package lesson03;

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
    public void greet() {
        System.out.println("안녕하세요, 저는" + name + "입니다.");
    }
}
class Student extends Person {
    String school;
    public Student(String name, String school) {
        super(name); //부모한테 채워주기
        this.school = school; //자식은 나한테 채워주면 됨
    }

    @Override
    public void greet() {
        super.greet(); // 이걸 추가해줘야 부모 것도 출력됨
        System.out.println("학교는 " + school + "입니다.");
        // 오버라이딩 하면 자식이 우선 출력됨. super을 하지 않으면 자식만 출력 
    }
}

public class Ex07_Main {
    public static void main(String[] args) {
        Person person = new Person("김사과"); //부모 객체
        person.greet(); // 부모에 있는 메서드 잘 쓸 수 있음
        Student student = new Student("반하나", "코리아");
        student.greet();
    }
}

오버라이딩 : 마우스 우클릭 → 생성 → 메서드 재정의 → 부모 생성자 클릭

final

자바에서 ‘변경할 수 없음’을 의미하는 키워드 (const와 비슷한 것)

1. final 변수 : 값 변경 불가 (상수)

final을 변수에 사용하면, 초기화된 이후 값 변경이 불가능함.

선언과 동시에 값을 설정하거나, 한 번만 초기화 가능하며 관례상 대문자로 작성

final int MAX = 100;
MAX = 200; // ❌ 에러! 값을 바꿀 수 없음

2. 오버라이딩 불가

final을 메서드에 사용하면 자식클래스에서 그 메서드를 오버라이딩 할 수 없다. (보안성 유지)

class Parent {
    public final void show() {
        System.out.println("부모 클래스 메서드");
    }
}

class Child extends Parent {
    // public void show() {} // ❌ 오류: final 메서드는 재정의할 수 없음
}

상속 불가

final을 클래스에 사용하면 상속 자체를 못함

final class Animal {
    public void sound() {
        System.out.println("동물 소리");
    }
}

// class Dog extends Animal {} // ❌ 오류: final 클래스는 상속 불가

예제 : Ex08_Main

package lesson03;

class Animal {
    String name;
    public Animal(String name) {
        this.name = name; // 이름을 전달 받고 필드에 저장

    }
    public void eat() {
        System.out.println(name + "냠냠쓰");
    }
    public final void sleep() {
        System.out.println(name + "쿨쿨쓰");
    }
    public void speak() {
        System.out.println(name + "나불쓰");
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println(name + "멍멍쓰~");
    }
    public void bark() {
        System.out.println("멍멍");
    }
    public void bark(String target) {
        System.out.println(target + "을 보고 멍멍");
    }
    public void bark(int times) {
        for (int i = 0; i < times; i++) {
            System.out.println("으르르르왈르을릉ㄹ컹ㅋ엉!컼!!ㅇ르으를ㄹㄹ컼엌엉ㅇ엉!!!");
        }
    }
}

public class Ex08_Main {
    public static void main(String[] args) {
        Dog rucy = new Dog("루시");
        rucy.eat();
        rucy.sleep();
        rucy.speak(); // 부모는 원래 나불쓰, 자식이 멍멍쓰 (출력:멍멍쓰)
        rucy.bark();
        rucy.bark("택배 아저씨");
        rucy.bark(3);
    }
}

sleep은 final 로 정의되어있기 때문에 오버라이딩 불가