JAVA 클래스와 메서드

금송·2024년 9월 7일
0

이론

목록 보기
14/17
post-thumbnail

클래스와 객체

클래스객체
제품 설계도실제 제품
붕어빵 틀붕어빵
자동차 설계도실제 자동차

클래스란 객체의 특징(속성, 기능)을 정해놓은 ‘제품 설계도’

객체는 실제로 존재하는 ‘제품’

객체를 생성할 때 클래스가 필요

속성은 필드에 기능은 메서드에 저장.

클래스 이름 지을때 식별자 작성 규칙

작성 규칙예시
하나 이상의 문자로 이루어져야 한다.Car, SportsCar
첫 번째 글자는 숫자가 올 수 없다.3Car (x)
$ _ 외의 특수문자는 사용할 수 없다.$Car, _Car, @Car (x), #Car (x)
자바 키워드는 사용할 수 없다. (예약어)int (x), for (x)

💡 클래스에 의해서 만들어진 객체를 ‘인스턴스’라고도 합니다.

자바 자동완성 껐다 켜기 확인 경로

package chap06;

//클래스 바꿔 적는 방법 shift + F6
public class Car {      // convention : 첫글자 대문자, 카멜케이스
    public Car() {
        System.out.println("Car 클래스의 생성자 호출 되나?");
    }

    public static void main(String[] args) {
        // 자동완성 : 세팅 - 라이브 템플릿 - 자바
        Car car = new Car(); // Car 클래스 객체 생성

    }
}

클래스 구성 멤버 살피기

필드

객체의 고유 데이터가 저장되는 곳

선언 형태는 변수와 비슷하고 클래스 전역에서 사용될 수 있으며 객체가 소멸되지 않는 한 객체와 함께 존재하는 변수.

//필드 접근법
객체.필드
public class Car {
	String company; // 필드, 멤버 변수
	
	public static void main(String[] args){
		Car car = new Car(); // 해당 변수와 String company의 생명주기를 함께함
		System.out.println(car.company); // null
	}
}

클래스에 의해 생성되는 것은 객체(인스턴스), 그리고 그 클래스에 선언된 변수는 멤버 변수 → 필드

생성자

생성자는 new 연산자와 같이 사용되어 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당.

접근제어자로 public 혹은 private 모두 사용 가능

생성자는 객체가 생성될 때 호출되는데 new 키워드가 사용될 때 호출

생성자의 규칙

  • 클래스명과 메서드명이 같다.
  • 리턴 타입은 정의하지 않는다. (void도 사용하지 않는다)
package chap06;

//클래스 바꿔 적는 방법 shift + F6
public class Car {      // convention : 첫글자 대문자, 카멜케이스
    String company;

    public Car() {
        // 멤버변수의 대한 초기화를 해주어야함.
        // 위에서 바로 초기화 할 수도 있지만 생성자에서 초기화 하는 형태를 추천. - 가독성을 위해서라도.
        company = "현다이자동차";
    }

    public static void main(String[] args) {
        // 자동완성 : 세팅 - 라이브 템플릿 - 자바
        Car car = new Car(); // Car 클래스 객체 생성. new 클래스명(입력인수, ...)
        System.out.println(car.company);
    }
}

디폴트 생성자

생성자의 입력 항목이 없고 생성자 내부에 아무런 내용이 없는 생성자를 디폴트 생성자

만약 클래스에 생성자가 하나도 없다면, 컴파일러는 자동으로 디폴트 생성자를 추가한다.

하지만 사용자가 작성한 생성자가 하나라도 구현되어 있다면 컴파일러는 디폴트 생성자를 추가하지 않는다.

package chap06;

public class DefaultConstructor {
    String field;
    
    // 입력값이 없는 생성자를 사용하기 위해서 사용된 생성자.
    DefaultConstructor (){
		    this.field = "특정값으로 초기화"
    }
    
    DefaultConstructor(String field){
        this.field = field;
    }
    
    public static void main(String[] args){
        // 입력값이 있는 생성자
        DefaultConstructor constructorTest = new DefaultConstructor("특정값");
				
				// 입력값이 없는 생성자
				DefaultConstructor constructorTest2 = new DefaultConstructor(); 
        // 상단에 생성자를 만들어서 디폴트 생성자를 자동으로 생성하지 않기 때문에 오류가 난다.
        // 만약에 해당 객체의 생성자를 쓰고싶다면 상단에 하나 더 만들면 사용 가능
    }
}

생성자 오버로딩

오버로딩이란 클래스 내에 같은 이름의 함수를 여러개 선언하는 것을 말함.

하나의 메소드(혹은 생성자) 이름으로 여러 기능을 담는다 하여 붙여진 이름

여러개의 생성자를 선언하는 방식

이때 메서드에서도 사용할 수 있는데 이때는 매개변수를 여러개 선언하는 방식.

package chap06;

//클래스 바꿔 적는 방법 shift + F6
public class Car {      // convention : 첫글자 대문자, 카멜케이스
    String company;
    String model;
    int maxSpeed;
    int price;

    Car() {             // 입력받는 값이(매개변수) 없는 생성자
        company = "현대자동차";
    }
    
    Car(String company){ // 입력받는 값이 있는 생성자
        this.company= company; //용어가 겹칠 때 this를 쓰는데 이때 this는 현재의 값을 말하는데 이때는 위에 멤버변수를 말함.
    }
    // 오버로딩 : 과적. 즉 매개변수를 여러개 선언하는 방식, 이름이 같은 메서드(생성자)에 다양한 매개변수를 정의
    Car(String company,String model, int maxSpeed, int price) {
        this.company = company;
        this.model = model;
        this.maxSpeed = maxSpeed;
        this.price = price;
    }

    String printField() {
        return "company : " + company + ", model : " + model + ", maxSpeed : " + maxSpeed + ", price : " + price;
    }

    public static void main(String[] args) {
        Car car = new Car(); // Car 클래스 객체 생성
        System.out.println(car.company);
        System.out.println(car.printField());
        
        Car car2 = new Car("기아자동차");
        System.out.println(car2.company);
        System.out.println(car2.printField()); //설정값이 없는 부분은 null로 노출

        Car car3 = new Car("현대자동차", "santafe", 200, 50000000);
        System.out.println(car3);   //객체의 주소값이 나옴.
        System.out.println(car3.company);
        System.out.println(car3.model);
        System.out.println(car3.maxSpeed);
        System.out.println(car3.price);   //객체 각각의 값으로 가져오기
        System.out.println(car3.printField()); // 객체를 메서드로 값을 전부 가져오기

        Car car4 = new Car("페라리", "ferrari", 400, 50000000);
        System.out.println(car4);
        System.out.println(car4.printField());
    }
}

메소드

다른 프로그램 언어에는 함수가 별도 존재하지만, 자바에서는 클래스 내에 함수를 메서드라고 한다.

//input이 없는 메서드
String printField(){ // 인풋이 없을 땐 괄호를 빈칸으로 해주면 된다
	return "~~";
}

//output이 없는 메서드
void method2(input){ // 아웃풋이 없을 땐 void를 적어 리턴값이 없다는걸 명시.
	//실행문
}
// input, output이 없는 메서드
void method(){ // void, 매개변수 빈칸을 적으면 둘다 없는 메서드
	//실행문
}

    Car(String company,String model, int maxSpeed, int price) {
        this.company = company;
        this.model = model;
        this.maxSpeed = maxSpeed;
        this.price = price;
    }

		// 메서드 생성
    String printField() {
        return "company : " + company + ", model : " + model + ", maxSpeed : " + maxSpeed + ", price : " + price;
    }
    
    // 메서드 호출
    public static void main(String[] args) {        
        Car car2 = new Car("기아자동차");
        System.out.println(car2.company);
        String printResult = car2.printField(); // 위에서 String으로 만들어졌기 때문에 String으로 쓴다.
        System.out.println(printResult); //설정값이 없는 부분은 null로 노출
    }
  • output이 없는 메서드 생성 후 호출
// model 값을 넣어주는 메서드
void setModel(String model){
    // 실행문
    // 예시1. if(model == ""){}
    this.model = model;
}

// maxSpeed 값을 넣어주는 메서드
void setMaxSpeed(int maxSpeed){
    // 메서드명 컨벤션 : 동사 + 동작하는 행위
    this.maxSpeed = maxSpeed;
}
public static void main(String[] args) {
        Car car = new Car(); // Car 클래스 객체 생성
        System.out.println(car.company);
        car.setMaxSpeed(100);
        car.setModel("sonata");
        System.out.println(car.printField());

        Car car4 = new Car("페라리", "ferrari", 400, 50000000);
        System.out.println(car4);
        System.out.println(car4.printField());

        //int testSpeed = car4.setMaxSpeed(800); // output이 없기 때문에 변수로 설정 못하고 호출만 가능
        car4.setMaxSpeed(800);
    }

메서드 선언

리턴타입 메소드이름([매개변수 선언, ...]) { //선언부

	// 실행블럭
	
}

리턴문

메서드 선언부의 맨 첫번째에 위치한 리턴타입은 메서드가 실행 후 리턴하는 값의 타입을 말함.

메서드는 리턴값이 있을 수도 없을 수도 있는데 메서드가 실행된 후 결과를 호출한 곳에 넘겨줄 경우엔 리턴값이 있어야함.

void powerOn() { 
	System.out.println("전원을 켭니다");      // void 리턴타입: 리턴타입이 없을경우
}

double divide(int x, int y) { 
	return x / y;	 // return (double) x / y 자동으로 캐스팅이 되서 리턴됨.
}

/* divide 메서드는 입력값으로 두개의 값(int 자료형 x, int 자료형 y)을 받으며 
   리턴값은 두 개의 입력값을 나눈 값(double 자료형)이다.
*/

retrun은 메서드의 결과값을 돌려주는 명령어, 즉 return 뒤에 실행문을 적으면 오류 남.

    double divide(int x, int y){
        if (x / y == 4.5) {
            return x / y;
        } else { // else 빼고 return 310.5만 적어도 문제없음.
            return 310.5;
        }   
    }
    
    // 위 두 메소드 호출
    powerOn();
    double result = divide(10,20); // == divice(10, 20)

매개변수와 인수

매개변수 : 메소드에 전달된 입력값을 저장하는 변수

인수 : 메서드를 호출할 때 전달하는 입력값

public class Calculator {
  double divide(int x, int y) {     // x, y는 매개변수
		return x / y;	
  }

  public static void main(String[] args) {
	  Calculator calculator = new Calculator();
		calculator.divide(10, 20);      // 10, 20은 인수
  }
}

call by value.. call by reference

  • 참고) 메서드 내에서 선언된 변수의 효력 범위

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

    메소드 안에서 사용하는 변수의 이름을 메소드 밖에서 사용한 이름과 동일하게 사용한다면 어떻게 될까요? 다음 예제를 풀어봅시다.
    public class Calculator {
        void postfixOperator(int a) {
            a++;
        }
    
        public static void main(String[] args) {
            int a = 1;
            Calculator calculator = new Calculator();
            calculator.postfixOperator(a);
            System.out.println(a); // 1
        }
    }
    a값을 출력했을 때 어떤 값이 나오는지 풀어보셨나요? 그럼 풀이를 해봅시다. 이 예제의 postfixOperator 메소드는 입력으로 들어온 int 자료형의 값을 1만큼 증가시키는 역할을 합니다. main 메소드를 순서대로 분석해 보면
    1. main 메소드에서 a라는 int 자료형의 변수를 생성하고 a를 1로 초기화.

    2. postfixOperator 메소드의 인자값으로 a를 주고 호출.

    3. a의 값을 출력.

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

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

      그렇다면 postfixOperator 메소드를 이용해서 main 메소드 외부의 a의 값을 1만큼 증가시킬 수 있는 방법은 없을까요? 다음과 같이 postfixOperator 메서드와 main 메소드를 변경해 봅시다.

      public class Calculator {
          int postfixOperator(int a) {
              a++;
      				return a;
          }
      
          public static void main(String[] args) {
              int a = 1;
              Calculator calculator = new Calculator();
              a = calculator.postfixOperator(a);
              System.out.println(a); // 2
          }
      }

      결과가 어떻게 나올지 예상하셨나요?

      a의 값을 postfixOperator 메소드를 이용하여 1만큼 증가시켰고, 증가시킨 값을 리턴해서 main메소드에서 받아와 출력했습니다. 이전 코드와는 다르게 값이 증가되었겠죠.

      2

      해법은 이 예시와같이 postfixOperator 메소드에 return 문을 이용하는 방법입니다. postfixOperator 메소드는 입력으로 들어온 값을 1만큼 증가시켜 리턴하고, 따라서 a = calculator.postfixOperator(a)처럼 작성하면 a의 값은 다시 postfixOperator 메소드의 리턴값으로 대입되는 것이죠.(즉, 1만큼 증가된 값으로 a의 값이 변경).

      이번에는 postfixOperator 입력값이 int 자료형이 아닌 객체인 경우를 살펴보겠습니다. 메소드에 값을 전달하는 것과 객체를 전달하는 것에는 큰 차이가 있는데, 메소드의 입력으로 객체를 넘기고 메소드가 객체의 속성값(필드 값)을 변경한다면 메소드 수행 이후에도 객체는 변경된 속성값을 유지합니다. 다음 코드를 살펴보시죠

      public class Calculator {
      		int a;      // 필드(객체변수) a
      
          void postfixOperator(Calculator cal) {
              cal.a++;
          }
      
          public static void main(String[] args) {
              Calculator cal = new Calculator();
              cal.postfixOperator(cal);
              System.out.println(cal.a);
          }
      }
      2

      이번에는 Calculator 클래스의 필드로 a를 선언했습니다. 그리고 postfixOperator 메소드에서 Calculator 클래스의 객체를 매개변수로 입력받도록 하고, 해당 객체를 1만큼 증가시켰습니다. 그리고 main 메서드에서는 postfixOperator 메소드 호출 시 Calculator 클래스의 객체인 cal을 전달하도록 수정했습니다. 이렇게 수정하고 프로그램을 실행해보면 cal 객체의 필드 a의 값이 원래는 1이었는데 postfixOperator 메소드 실행 후 1만큼 증가되어 2가 출력되는 것을 확인할 수 있습니다.

      여기서 주목해야 하는 부분은 postfixOperator 메소드의 입력 파라미터가 값이 아닌 Calculator 클래스의 객체 라는것이죠. 이렇게 메소드가 파라미터로 객체를 전달받으면, 메소드 내의 객체는 전달받은 객체 그 자체로 수행됩니다. 따라서 입력으로 전달받은 cal 객체의 필드 a의 값이 증가하게 되는 것입니다.

this

객체를 이용하여 메소드를 호출할 경우 굳이 객체를 전달할 필요가 없다.

이때 this라는 키워드를 이용하여 객체에 접근할 수 있기 때문.

public class Calculator {

    int a;  // 필드(객체변수) a

    void postfixOperator() {
        this.a++;     // 본인 객체 접근시 this 사용 == this.a = ++a;
    }

    public static void main(String[] args) {
        Calculator cal = new Calculator();
        cal.a = 1;
        cal.postfixOperator();     // Before) cal.postfixOperator(cal);
        System.out.println(cal.a);
    }
}

this는 주로 생성자와 메소드의 매개변수 이름이 필드와 동일할 경우, 혹은 인스턴스의 필드임을 명시하고자 할 때 사용.

final 필드와 상수

final 필드

최종적이라는 뜻을 가지고 있음.

즉 final 필드가 초기값 지정이 되면 이것이 최종적인 값이 되어 프로그램 실행 도중에 수정 할 수 없음.

final 타입 필드명 [= 초기값];

final 필드의 초기값을 줄 수 있는 방법

  1. 필드 선언시
  2. 생성자에서 선언 가능

초기화되지 않은 final을 남겨두면 컴파일 오류가 난다.

package chap06;

public class Person {
    // 필드 선언시 final 초기값 설정
    // final String nation = "Korea";
    final String nation;
    String name;

    Person(String name){
        // 생성자 안에서 final 초기값 설정
        this.nation = "korea";
        this.name = name;
    }
}
public class PersonExample {
	public static void main(String[] args) {
		Person person = new Person("계백");

		System.out.println(person.nation);
		System.out.println(person.name);
		
	  person.nation = "을지문덕";   // Error: 컴파일 오류 발생. final 필드는 값 수정 불가
	}
}

final은 프로그램을 수행하면서 그 값이 바뀌면 안 될 때 사용.

상수

수학에서 사용되는 원주율 파이, 지구의 무게 및 둘레 등 불변의 값을 저장하는 필드를 자바에서는 상수라고 함.

final 필드는 한 번만 초기화되면 수정할 수 없는 필드지만 이떄 final 필드를 상수라곤 하지 않음.

→불변의 값은 객체 마다 저장할 필요가 없는 공용성을 띄는 게 특징이고 여러 가지 값으로 초기화 될 수 없기 때문.

static final 타입 상수 [= 초기값];
// public static final 타입 상수 [= 초기값]; 이처럼 쓸수도 있음.

객체마다 저장할 필요가 없이 공용으로 선언하여 사용하기 때문에 static 이면서 final로 선언이 됨.

package chap06;

// static final 타입 상수;
// static {
// 	 상수 = 초기값;
// }

public class Earth {
    static final double EARTH_RADIUS = 6400;
    static final double EARTH_SURFACE_AREA;

    static {
        // static 안에서도 초기값 설정 가능
        EARTH_SURFACE_AREA = Math.PI * EARTH_RADIUS * EARTH_RADIUS;
    }
}

package chap06;

public class EarthExample {
    public static void main(String[] args){
        //Earth , 클래스 변수는 다른 클래스에서 호출 가능. 호출방법은 클래스.변수
        System.out.println("지구의 반지름은 " + Earth.EARTH_RADIUS + "km");
        System.out.println("지구의 표면적은 " + Earth.EARTH_SURFACE_AREA + "km^2");
    }
}

접근 제어자

접근 제어자(access modifier)를 사용하여 변수나 메소드의 사용 권한을 설정할 수 있다.

설정하지 않았다면 default 접근 제어로 된다. 같은 패키지 내에서만 접근 가능.

  • private - 해당 클래스 내에서만 접근 가능
  • default - 같은 패키지 내에서만 접근 가능
  • protected - 상속 받은 클래스까지 접근 가능
  • public - 클래스간 접근 가능

private

private이 붙은 변수나 메서드는 해당 클래스 안에서만 접근이 가능

하나의 클래스 파일당 하나의 public 클래스를 하나 가져야 한다.

public class Secret {
	private String name;

	private String getName() {
		return this.name;
	}
}

default

접근 제어자를 별도로 설정하지 않는다면 변수나 메소드는 default 접근 제어자가 자동으로 설정됨

동일한 패키지 내에서 접근이 가능.

package chap06.house;

public class HouseKim {
    String lastname = "Kim";
}
package chap06;

import chap06.house.HouseKim;

public class HouseMain {
    public static void main(String[] args){
        HouseKim Kim = new HouseKim();
        // 다른 패키지에 있는 lastname 접근 불가 
        //'lastname' is not public in 'chap06.house.HouseKim'. Cannot be accessed from outside packag
				Kim.lastname; 
				
    }
}
package chap06.house;

public class HouseJang {
    String lastname = "Jang";

    public static void main(String[] args) {
        HouseKim kim = new HouseKim();
        System.out.println(kim.lastname);  // kim. HouseKim 클래스의 lastname 변수를 사용할 수 있다.
    }
}

protected

접근 제어자가 protected로 설정되어있다면 protected가 붙은 변수나 메서드는 동일 패키지의 클래스 또는 해당 클래스를 상속받은 클래스에서만 접근 가능

extends라는 키워드를 사용하여 상속 받을 수 있다.

아래의 코드를 간단하게 예를 들자면

Sonata가 자식 클래스 Car가 부모 클래스.

extends라는 키워드로 상속 받음.


public class Sonata extends Car {    // Car를 상속
	public static void main(String[] args) {
		Sonata sonata = new Sonata();
		System.out.println(sonata.company);   // 상속한 클래스의 protected변수는 접근 가능
	}
}

package chap06.car;

public class Car {
    protected String company = "테슬라";
}
package chap06.car.example; // Car.java와 패키지가 서로 다름

import chap06.car.Car;

public class SportsCar extends Car {  // Car를 상속
    public static void main(String[] args){
        SportsCar sportsCar = new SportsCar();
        System.out.println(sportsCar.company); // 상속한 클래스의 protected 변수는 접근 가능
    }
}

public

접근 제어자가 public으로 설정되어 있으면 public 접근 제어자가 붙은 변수나 메소드는 어떤 클래스에서도 접근 가능.

package chap06.car;

public class Car {
    protected String company = "테슬라";
  	public String info = "this is public message.";
}
package chap06.car.example; // Car.java와 패키지가 서로 다름

import chap06.car.Car;

public class SportsCar extends Car {  // Car를 상속
    public static void main(String[] args){
        SportsCar sportsCar = new SportsCar();
        System.out.println(sportsCar.company); // 상속한 클래스의 protected 변수는 접근 가능
        System.out.println(sportsCar.info); // this is public message.
    }
}

Getter, Setter 메서드

Setter 메서드

set 해주는 메서드.

객체 지향 프로그램밍에서 일반적으로 객체의 데이터는 외부에서 직접적으로 접근하는 것을 막음.

이때 수정 할 경우 객체의 무결성(결점이 없는 성질)이 깨질 수 있기 때문.

car.speed = -100; // 속도가 음수가 되어 객체의 무결성이 깨짐

이런 문제점을 해결하기 위해 메소드를 통해 데이터를 변경하는 방법을 선호한다.

데이터는 외부에서 접근할 수 없도록 막고, 메소드는 공개해서 외부에서 메소드를 통해 데이터에 접근하도록 유도한다. 이윤 메소드는 매개값을 검증해서 유효한 값만 데이터로 저장할 수 있기 때문.

이때 그 역할을 하는 메소드가 Setter 메소드이고 아래의 코드는 예시이다.

public class Sonata {
		private int speed;
		
		public void setSpeed(int speed) { // Setter 메서드
				if (speed < 0) {
						this.speed = 0;
						return;
				} else {
						this.speed = speed;
				}
		}
}

Getter 메서드

외부에서 객체의 데이터를 읽을 때도 메소드를 사용하는 것이 좋은데 객체 외부에서 객체의 필드 값을 사용하기 부적절한 경우도 있다. 이런 경우에는 메소드를 필드값을 가공한 후 외부로 전달한다.

public class Sonata {
		private int speed;
		
		public double getSpeed() { // Getter 메서드
				double km = speed * 1.6;
				return km;
		}
}

private 타입 fieldName;

// Getter
public 리턴타입 getFieldName() {
	return fieldName;
}

// Setter
public void setFieldName(타입 fieldName) {
	this.fieldName = fieldName;
}

// 만약 필드 타입이 boolean일 경우 관례상 get이 아닌 is로 시작
private boolean stop;

// Getter
public boolean **is**Stop() {
	return stop;
}

// Setter
public void setStop(boolean stop) {
	this.stop = stop;
}
public class Sonata {
	private int speed;
	private boolean stop;

	void setSpeed(int speed) {
		if (speed < 0) {
			this.speed = 0;
			return;
		} else {
			this.speed = speed;
		}
	}

	public int getSpeed() {
		return speed;
	}

	public boolean isStop() {
		return stop;
	}

	public void setStop(boolean stop) {
		this.stop = stop;
	}

	public static void main(String[] args) {
		Sonata sonata = new Sonata();

		// 잘못된 속도 변경
		sonata.setSpeed(-50);
		System.out.println("현재 속도: " + sonata.getSpeed()); // 0

		// 올바른 속도 변경
		sonata.setSpeed(60);
		System.out.println("변경 후 속도: " + sonata.getSpeed()); // 60

		// 멈춤
		if (!sonata.isStop()) {
			sonata.setStop(true);
		}

		System.out.println("멈춤 후 속도: " + sonata.getSpeed()); // 60
		// 이때 멈춘 후 속도가 60이 아닌 0으로 수정되려면 sonata.setSpeed(0);을 if (!sonata.isStop()) {sonata.setStop(true);} 내에 적어주면 됨.
	}
}
profile
goldsong

0개의 댓글