본문 바로가기

WINK-(Web & App)/JAVA 스터디

[2024-2 Java 스터디] 김지수 #3주차

반응형

객체 지향 프로그래밍이란?

계산기의 더하기 기능을 구현한 코드는 다음과 같다.

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(3));
System.out.println(Calculator.add(4));
}
}
3
7

add 메서드는 매개 변수 num으로 받은 값을 이전에 계산한 결괏값에 더한 후 돌려주는 메서드이다. 이전에 계산한 결괏값을 유지하기 위해서 result 전역 변수(static 변수)를 사용했다.

 

그런데 만일 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(3));
System.out.println(Calculator1.add(4));

System.out.println(Calculator2.add(3));
System.out.println(Calculator2.add(7));
}
}
3
7
3
10

 

Calculator1의 결괏값이 Calculator2에 아무 영향을 끼치지 않음을 확인할 수 있다. 하지만 계산기가 3개, 5개, 10개로 점점 더 많이 필요해진다면 어떻게 해야 할까? 아직 객체를 자세히 배우지 않았지만 이와 같은 문제를 해결하기 위해 객체를 사용하면 다음과 같이 간단하게 해결할 수 있다.

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(3));
System.out.println(cal1.add(4));

System.out.println(cal2.add(3));
System.out.println(cal2.add(7));
}
}
3
7
3
10

Calculator 클래스로 만든 별개의 계산기 cal1, cal2(이것을 바로 객체라고 부른다)가 각각의 역할을 수행한다. 그리고 계산기(cal1, cal2)의 결괏값 역시 다른 계산기의 결괏값과 상관없이 독립적인 값을 유지한다

 

만약 빼기 기능을 추가하려면 Calculator 클래스에 다음처럼 sub 메서드를 추가하면 된다.

class Calculator {
int result = 0;

int add(int num) {
result += num;
return result;
}

int sub(int num) {
result -= num;
return result;
}
}

 


클래스

클래스와 객체

객체가 무엇인지를 알기 위해 먼저 Animal 클래스를 다음과 같이 Sample.java 파일에 작성하자

class Animal {
}

public class Sample {
public static void main(String[] args) {
}
}

Animal 클래스는 가장 간단한 형태의 클래스이다. 클래스의 선언만 있지 내용이 없는 껍데기 뿐인 클래스이다. 하지만 이 껍데기뿐인 클래스도 아주 중요한 기능을 가지고 있다. 바로 객체(object)를 만드는 기능이다. 

class Animal {
}

public class Sample {
public static void main(String[] args) {
Animal cat = new Animal();
}
}

new는 객체를 생성할 때 사용하는 키워드이다. 이렇게 하면 Animal 클래스의 인스턴스(instance)인 cat, 즉 Animal의 객체가 만들어진다.

 

다음 그림을 통해 클래스를 이해해 보자. 과자 모양을 찍어 내는 과자 틀은 클래스에 비유할 수 있고, 과자 틀에 의해 만들어진 과자들은 객체에 비유할 수 있다.

다음과 같이 무수히 많은 동물 객체(여기서는 cat, dog, horse, …)들을 Animal 클래스로 만들 수 있다.

Animal cat = new Animal();
Animal dog = new Animal();
Animal horse = new Animal();
...

 

객체 변수란?

Animal이라는 ‘껍데기’ 클래스를 조금 더 발전시켜 보자. Animal 클래스에 의해 만들어진 동물 객체의 이름을 지어 보자.

class Animal {
String name;
}

public class Sample {
public static void main(String[] args) {
Animal cat = new Animal();
}
}

Animal 클래스에 name이라는 String 변수를 추가했다. 이렇게 클래스에 선언된 변수를 객체 변수(instance variable)라고 한다. 

 

객체 변수는 다음과 같이 도트 연산자(.)를 이용하여 접근할 수 있다.

객체.객체변수

 

체 변수 name에는 다음과 같이 접근할 수 있다.

cat.name // 객체: cat, 객체변수: name

 

객체 변수에 어떤 값이 대입되어 있는지 다음과 같이 작성하여 출력해 보자.

class Animal {
String name;
}

public class Sample {
public static void main(String[] args) {
Animal cat = new Animal();
System.out.println(cat.name);
}
}
null

null은 값이 할당되어 있지 않은 상태를 말한다. 객체 변수로 name을 선언했지만 아무런 값도 대입하지 않았기 때문에 null이라는 값이 출력된 것이다.

 

메서드란?

 클래스에는 객체 변수와 함께 메서드가 있다. 메서드(method)는 클래스 내에 구현된 함수를 말한다.

 

이제 메서드를 이용하여 Animal 클래스의 객체 변수인 name에 값을 대입해 보자. 다음과 같이 setName 메서드를 추가해 보자.

class Animal {
String name;

public void setName(String name) {
this.name = name;
}
}

public class Sample {
public static void main(String[] args) {
Animal cat = new Animal();
System.out.println(cat.name);
}
}

Animal 클래스에 추가된 setName 메서드는 다음과 같은 형태의 메서드이다.

 


메서드 더 살펴보기

다른 프로그래밍 언어에는 함수가 별도로 존재한다. 하지만 자바는 클래스를 떠나 존재하는 것은 있을 수 없기 때문에 자바의 함수는 따로 존재하지 않고 클래스 내에 존재한다. 자바는 이러한 클래스 내의 함수를 메서드(method)라고 부른다.

 

먼저 믹서를 떠올려 보자. 우리는 주스를 만들기 위해 믹서에 과일을 넣는다. 그리고 믹서를 이용해서 과일을 갈아 주스를 만들어 낸다. 우리가 믹서에 넣는 과일은 입력에 비유할 수 있고, 만들어진 주스는 출력(리턴값)에 비유할 수 있다. 그렇다면 믹서는 무엇에 비유할 수 있을까?

믹서가 바로 우리가 여기서 알고자 하는 메서드이다. 입력값을 가지고 어떤 일을 수행한 다음에 결과값을 내어놓는 것이 바로 메서드가 하는 일이다!

 

메서드를 사용하는 이유

프로그래밍을 하다 보면 똑같은 내용을 반복해서 적을 때가 있다. 이럴 때 바로 메서드가 필요하다. 여러 번 반복해서 사용된다는 것은 또다시 사용할 만한 가치가 있는 부분이라는 뜻이다. 즉, 이러한 경우 이것을 한 뭉치로 묶어서 ‘어떤 입력값을 주었을 때 어떤 리턴값을 돌려준다’라는 식의 메서드를 작성하는 것이 현명한 방법이다. 가장 간단하지만 많은 것을 설명해 주는 다음의 메서드를 살펴보자.

int sum(int a, int b) {
return a+b;
}

이 메서드는 다음과 같이 정의된다.

“sum 메서드는 입력값으로 두개의 값(int 자료형 a, int 자료형 b)을 받으며 리턴값은 두 개의 입력값을 더한 값(int 자료형)이다.”

 return은 메서드의 결괏값을 돌려주는 명령어이다.

 

앞서 설명한 메서드를 직접 작성하고 사용해 보자. 다음 예제는 sum 메서드에 3, 4라는 입력값을 전달하여 7이라는 값을 리턴받는다.

public class Sample {
int sum(int a, int b) {
return a + b;
}

public static void main(String[] args) {
int a = 3;
int b = 4;

Sample sample = new Sample();
int c = sample.sum(a, b);

System.out.println(c);
}
}
7

 

매개 변수와 인수

매개 변수(parameter)와 인수(arguments)는 혼용되는 헷갈리는 용어이므로 잘 기억해 두자. 매개 변수는 메서드에 전달된 입력값을 저장하는 변수를 의미하고, 인수는 메서드를 호출할 때 전달하는 입력값을 의미한다.

public class Sample {
int sum(int a, int b) { // a, b 는 매개변수
return a+b;
}

public static void main(String[] args) {
Sample sample = new Sample();
int c = sample.sum(3, 4); // 3, 4는 인수

System.out.println(c);
}
}

 

 

메서드의 입력값과 리턴값

메서드는 입력값을 가지고 어떤 처리를 하여 적절한 리턴값을 돌려주는 블랙박스와 같다. 왜냐하면 메서드의 내부 동작이 복잡하더라도 외부에서는 그저 입력값을 넣으면 리턴값이 나오는 과정만 관심을 가지기 때문이다.

입력값 ---> 메서드(블랙박스와 같은 역할을 한다.) ----> 리턴값

 

메서드의 구조

리턴자료형 메서드명(입력자료형1 매개변수1, 입력자료형2 매개변수2, ...) {
...
return 리턴값; // 리턴자료형이 void 인 경우에는 return 문이 필요없다.
}

 

메서드는 입출력 유무에 따라 다음과 같이 4가지로 분류할 수 있다.

  • 입력과 출력이 모두 있는 메서드
  • 입력과 출력이 모두 없는 메서드
  • 입력은 없고 출력은 있는 메서드
  • 입력은 있고 출력은 없는 메서드

 

입력값과 리턴값이 모두 있는 메서드

int sum(int a, int b) {
return a+b;
}

 

  • 입력값 : int 자료형 a, int 자료형 b
  • 리턴값 : int 자료형

 

sum 메서드는 두 개의 입력값을 받아서 서로 더한 결괏값을 돌려주는 메서드이다. 입력값과 리턴값이 있는 메서드는 다음과 같이 호출할 수 있다.

리턴값_받을_변수 = 객체.메서드명(입력인수1, 입력인수2, ...)

 

입력값이 없는 메서드

String say() {
return "Hi";
}

 

say 메서드의 입출력 자료형은 다음과 같다.

  • 입력값 : 없음
  • 리턴값 : String 자료형
public class Sample {
String say() {
return "Hi";
}

public static void main(String[] args) {
Sample sample = new Sample();
String a = sample.say();
System.out.println(a); // "Hi" 출력
}
}
H1

say 메서드를 쓰기 위해서는 say()처럼 괄호 안에 아무런 값도 넣지 않고 써야 한다. say 메서드는 입력값은 없지만 리턴값으로 Hi라는 문자열을 리턴한다. 따라서 String a = sample.say()의 a에는 Hi라는 문자열이 대입될 것이다. 정리하자면 입력값이 없고 리턴값만 있는 메서드는 다음과 같이 호출할 수 있다.

 

리턴값_받을_변수 = 객체.메서드명()

 

리턴값이 없는 메서드

void sum(int a, int b) {
System.out.println(a+"과 "+b+"의 합은 "+(a+b)+"입니다.");
}

 

sum 메서드의 입출력 자료형은 다음과 같다.

  • 입력값 : int 자료형 a, int 자료형 b
  • 리턴값 : void (리턴값 없음)

리턴값이 없는 메서드는 명시적으로 리턴 자료형 부분에 void라고 표기한다. 

 

리턴값이 없는 메서드는 다음과 같이 호출할 수 있다.

객체.메서드명(입력인수1, 입력인수2, ...)

 

입력값과 리턴값이 모두 없는 메서드

void say() {
System.out.println("Hi");
}

 

say 메서드의 입출력 자료형은 다음과 같다.

  • 입력값 : 없음
  • 리턴값 : void

 

입력값도, 리턴값도 없는 메서드는 다음과 같이 호출할 수 있다.

객체.메서드명()

 

return의 또 다른 쓰임

특별한 경우 메서드를 빠져나가고 싶다면 return을 단독으로 사용하여 메서드를 즉시 빠져나갈 수 있다.

public class Sample {
void sayNick(String nick) {
if ("바보".equals(nick)) {
return;
}
System.out.println("나의 별명은 "+nick+" 입니다.");
}

public static void main(String[] args) {
Sample sample = new Sample();
sample.sayNick("야호");
sample.sayNick("바보"); // 출력되지 않는다.
}
}
나의 별명은 야호 입니다.

sayNick 메서드는 입력으로 받은 문자열을 포함한 문장을 출력한다. 이 메서드 역시 리턴값은 없다. 문자열을 출력한다는 것과 리턴값이 있다는 것은 전혀 다른 말이다. 여기서 쓰인 return은 메서드를 호출한 곳에 리턴값을 돌려주는 역할이 아니라 메서드를 즉시 빠져나가는 역할을 한다. 이 메서드는 입력값으로 ‘바보’라는 값이 들어오면 문자열을 출력하지 않고 메서드를 즉시 빠져나간다.

 

메서드 내에서 선언된 변수의 효력 범위

public class Sample {
void varTest(int a) {
a++;
}

public static void main(String[] args) {
int a = 1;
Sample sample = new Sample();
sample.varTest(a);
System.out.println(a);
}
}
1

 

  1. main 메서드에서 a라는 int 자료형의 변수를 생성하고 a에 1을 대입했다.
  2. varTest 메서드를 입력값으로 a를 주어 호출했다.
  3. a의 값을 출력하게 했다.

varTest 메서드에서 a의 값을 1만큼 증가시켰으니 2가 출력되어야 할 것 같지만 막상 프로그램을 실행해 보면 1이라는 결괏값이 나온다. 왜 그럴까? 그 이유는 메서드에서 사용한 매개변수는 메서드 안에서만 쓰이는 변수이기 때문이다. 즉 void varTest(int a)라는 문장에서 매개 변수 a는 메서드 안에서만 쓰이는 변수이지 메서드 밖의 변수 a가 아니라는 말이다.

 

앞서 매개 변수 이름을 a로 사용한 varTest 메서드는 다음처럼 매개 변수 이름을 b로 사용한 varTest와 기능적으로 완전히 동일하다.

public void varTest(int b) {
b++;
}

다시 말해 메서드에서 쓰이는 매개 변수의 이름과 메서드 밖의 변수 이름이 같더라도 서로 전혀 영향을 주지 않는다.

 


값에 의한 호출과 객체에 의한 호출

메서드에 값(원시 자료형)을 전달하는 것과 객체를 전달하는 것에는 큰 차이가 있다. 이것은 매우 중요하기 때문에 이전에 잠깐 언급했지만 다시 한번 자세히 알아보자. 결론부터 얘기한다면 메서드에 객체를 전달할 경우 메서드에서 객체 변수의 값을 변경할 수 있다.

class Updater {
void update(int count) {
count++;
}
}

class Counter {
int count = 0; // 객체변수
}

public class Sample {
public static void main(String[] args) {
Counter myCounter = new Counter();
System.out.println("before update:"+myCounter.count);
Updater myUpdater = new Updater(); myUpdater.update(myCounter.count);
System.out.println("after update:"+myCounter.count);
}
}
before update:0
after update:0

Updater 클래스는 전달받은 숫자를 1만큼 증가시키는 update라는 메서드를 가지고 있다.

 

class Updater {
void update(Counter counter) {
counter.count++;
}
}

class Counter {
int count = 0; // 객체변수
}

public class Sample {
public static void main(String[] args) {
Counter myCounter = new Counter();
System.out.println("before update:"+myCounter.count);
Updater myUpdater = new Updater();
myUpdater.update(myCounter);
System.out.println("after update:"+myCounter.count);
}
}

이전 예제와의 차이점은 update 메서드의 입력 항목에 있다. 이전에는 int count와 같이 값을 전달받았다면 지금은 Counter counter와 같이 객체를 전달받도록 변경한 것이다.

 

update 메서드를 호출하는 부분도 다음처럼 바뀌었다.

myUpdater.update(myCounter);

 

이제 변경된 클래스를 실행해 보면 다음과 같은 결과가 출력된다.

before update:0 after update:1

myCounter 객체의 count값이 1만큼 증가되었다. 이렇게 메서드의 입력으로 객체를 전달하면 메서드가 입력받은 객체를 그대로 사용하기 때문에 메서드가 객체의 속성값을 변경하면 메서드 수행 후에도 객체의 변경된 속성값이 유지된다.

 


퀴즈

객체지향 프로그래밍(OOP)의 4대 특징이 아닌 것은? (정답:d)

  • a) 상속
  • b) 다형성
  • c) 추상화
  • d) 복사

클래스에서 필드와 메서드의 차이점은 무엇인가? (정답:a)

  • a) 필드는 객체의 상태를 나타내고, 메서드는 동작을 정의한다.
  • b) 필드는 동작을 정의하고, 메서드는 객체의 상태를 나타낸다.
  • c) 필드와 메서드는 동일하다.
  • d) 필드만 객체에 속한다.

다음 중 this 키워드의 역할은 무엇인가? (정답:b)

  • a) 상속된 메서드를 호출한다.
  • b) 현재 객체를 참조한다.
  • c) 부모 객체를 참조한다.
  • d) 외부 클래스의 필드를 참조한다.

클래스에서 this 키워드를 사용하는 이유는 무엇인가? (정답:a)

  • a) 같은 이름의 지역 변수와 필드를 구분하기 위해
  • b) 상속받은 필드를 참조하기 위해
  • c) 메서드 오버라이딩을 위해
  • d) 클래스를 상속하기 위해

메서드의 리턴 타입이 void인 경우, 메서드가 반환하는 값은? (정답:c)

  • a) null
  • b) 0
  • c) 아무 값도 반환하지 않는다.
  • d) -1

아래 코드에서 출력값은? (정답:b)

public class Example {
int a = 5;
public void setA(int a) {
this.a = a;
}
public static void main(String[] args) {
Example ex = new Example();
ex.setA(10);
System.out.println(ex.a);
}
}
  • a) 5
  • b) 10
  • c) 0
  • d) 컴파일 에러

매개변수(parameter)와 인자(argument)의 차이는 무엇인가? (정답:a)

  • a) 매개변수는 메서드 정의 시 사용되고, 인자는 메서드 호출 시 전달된다.
  • b) 인자는 메서드 정의 시 사용되고, 매개변수는 메서드 호출 시 전달된다.
  • c) 매개변수와 인자는 동일한 의미이다.
  • d) 매개변수는 클래스 정의 시 사용되고, 인자는 메서드 정의 시 사용된다.

메서드가 값을 반환하는 데 사용하는 키워드는? (정답:a)

  • a) return
  • b) break
  • c) exit
  • d) continue

메서드 내에서 선언된 변수의 효력 범위(scope)는? (정답:d)

  • a) 클래스 전체
  • b) 메서드 전체
  • c) 메서드가 호출된 후에도 유지된다.
  • d) 메서드 내에서만 유효하다.

자바에서 call by value의 의미는? (정답:c)

  • a) 참조 값을 전달한다.
  • b) 객체를 직접 전달한다.
  • c) 값의 복사본을 전달한다.
  • d) 값의 참조를 전달한다.

객체를 생성할 때 사용하는 키워드는? (정답:c)

  • a) object
  • b) this
  • c) new
  • d) create

다음 중 클래스 멤버에 해당하지 않는 것은? (정답:d)

  • a) 필드
  • b) 메서드
  • c) 생성자
  • d) 지역 변수

다음 중 지역 변수와 필드를 구분하는 올바른 방법은? (정답:)

  • a) 지역 변수는 this 키워드로 접근한다.
  • b) 필드는 클래스 외부에서 사용할 수 없다.
  • c) 지역 변수는 메서드 내에서만 사용된다.
  • d) 필드는 메서드 내에서만 사용된다.

자바에서 기본 데이터 타입을 객체로 감싸는 클래스는? (정답:c)

  • a) Wrapper 클래스
  • b) Interface 클래스
  • c) Collection 클래스
  • d) Abstract 클래스

객체의 생성자를 호출하는 역할을 하는 것은? (정답:d)

  • a) 메서드
  • b) 필드
  • c) 클래스
  • d) new 연산자

return 키워드는 언제 사용되는가? (중요) (정답:a)

  • a) 메서드의 실행을 종료하고 값을 반환할 때
  • b) 메서드의 실행을 반복할 때
  • c) 메서드 실행을 일시 중지할 때
  • d) 메서드 내부에서만 사용될 때

call by reference는 무엇을 의미하는가? (정답:b)

  • a) 값을 복사하여 전달한다.
  • b) 객체의 참조를 전달한다.
  • c) 기본 데이터 타입을 전달한다.
  • d) 메서드를 복사한다.

 


서술형 문제

  • 클래스와 객체의 차이점?

클래스는 객체를 만들기 위한 설계도이고 객체는 클래스를 바탕으로 생성된 실체이다.

  • this 키워드를 사용하는 이유?

동일한 이름의 변수를 구분하게 해준다.

  • 매개변수와 인자의 차이점?

매개변수는 변수이고 전달 인자는 값이다.

  • 필드와 지역 변수의 차이점을 설명하고, this 키워드가 사용되는 이유를 함께 설명하기.
 차이점은 생성되는 메모리의 위치로 필드는 힙 메모리에 지역 변구는 스택 메모리에 생성된다. 

this 키워드는필드와 지역 변수의 이름이 같을 때 구분하기 위해 사용된다.

  • 자바에서 call by value와 call by reference의 차이?

call  by value는 값의 복사이고 call by reference는 주소값의 복사이다.

  • 메서드 내에서 선언된 변수의 효력 범위(scope)란?

메서드 내에서 선언된 변수는 해당 메서드 내에서만 유효하며 메서드가 종료되면 사라진다.

  • 자바에서 생성자(constructor)의 역할?

자바에서 생성자는 객체를 생성하는 역할을 하는 클래스의 구성 요소이다.

  • 클래스에서 final 키워드를 사용하는 이유와 그 효과?

final 키워드는 다른 클래스에서 상속하여 재정의를 할 수 없는 것이다. 그러므로 final 키워드를 붙여 메소드를 정의한다면 서브 클래스에서 재정의를 제한하게 돼 불변성을 지키게 된다.

  • 메서드가 return을 사용하는 이유와 반환되는 값을 처리하는 방법?

return은 메서드의 실행을 종료하고 호출한 곳에 값을 반환하는 데 사용되고 반환되는 값은 호출한 메서드에서 다른 변수에 저장하거나 출력하는 등 다양한 방식으로 처리할 수 있다.

  • 객체를 생성하는 방법과 그 과정에서 new 연산자의 역할?

객체를 생성하는 방법은 new 키워드를 통해 생성을 할 수 있고 인스턴스를 생성할 때 사용하는 코드이며 객체 변수가 실제 데이터가 아닌 참조 값을 갖는다.

  • 클래스와 인터페이스의 차이점?

클래스는 인스턴스화가 될 수 있지만 인터페이스는 인스턴스화가 될 수 없다.

  • 다음 코드에서 this 키워드가 어떻게 동작하는지 설명하기
public class Test {
int value;
public Test(int value) {
this.value = value;
}
}

this 키워드를 통해 클래스의 필드 value를 명확하게 참조하여 구분한다.

 

  • 지역 변수와 전역 변수의 차이점?

지역변수는 특정 지역에서 선언되어, 선언된 지역에서만 사용 가능한 변수이고 전역변수는 어디서든 사용 가능한 변수이다.


코딩테스트 문제

  • 같은 숫자는 싫어

  • 1번 변형 문제 (반드시 1번을 먼저 해결한 후, 풀어주세요!)
    • 아래 코드는 1번 문제를 해결하기 위한 코드이다. 주어진 코드에서 class를 완성시켜 프로그램을 실행했을 때, 올바른 결과가 출력되도록 만드시오.

출력 결과: [1, 3, 0, 1]

import java.util.*;

class NumberFilter {
    private List<Integer> numbers;

    public NumberFilter(int[] numbers) {
        this.numbers = new ArrayList<>();
        for (int number : numbers) {
            this.numbers.add(number);  
        }
    }
    
    public List<Integer> removeConsecutiveDuplicates() {
        List<Integer> result = new ArrayList<>();  

        for (int i = 0; i < numbers.size(); i++) {
            if (i == 0 || !numbers.get(i).equals(numbers.get(i - 1))) {
                result.add(numbers.get(i));
            }
        }
        return result; 
    }
}

public class Main {
    public static void main(String[] args) {
        int[] arr = {1, 1, 3, 3, 0, 1, 1};

        NumberFilter numberFilter = new NumberFilter(arr);
        List<Integer> filteredNumbers = numberFilter.removeConsecutiveDuplicates();
        System.out.println(filteredNumbers);  // [1, 3, 0, 1]
    }
}

 

반응형