Java의 정석 - 객체

원태연·2022년 5월 30일
0

Java의 정석

목록 보기
6/19
post-thumbnail

객체

기존의 프로그래밍 언어에 몇 가지 새로운 규칙을 추가하여 객체지향에 알맞는 설계를 하는 것.

코드의 재사용성, 코드의 관리, 신뢰성의 증가를 기대할 수 있다.

객체에 대한 내용인 클래스, 그 클래스로 부터 만들어진 객체인 인터페이스가 있다.

인스턴스의 생성과 사용

클래스로 정의된 객체를 인스턴스화 하여 새로운 객체를 생성한다.

이때 생성된 객체는 클래스의 객체를 참조하는 참조변수이다.

클래스 변수 = new 클래스 ,Person me = new Person()

Person이라는 클래스를 참조하여 새로운 객체를 만들고(메모리에 할당하고), 그 객체를 me라는 참조변수를 통해 참조한다는 뜻

package playground;

public class playGround {
	static class Person{
    String name;
    int age;
    void upAge(){ ++age; }
    void changeName(String newName){ name = newName; }
  }
  public static void play(){
    Person me = new Person();
    me.name = "Royce";
    me.age = 23;
    System.out.println(me.name); // Royce
    System.out.println(me.age); //  23
    me.upAge();
    me.changeName("TaeYeon");
    System.out.println(me.name); // TaeYeon
    System.out.println(me.age); //  24
  }
}

위 코드 처럼, Person 이라는 class를 통해 객체에 대한 정보를 선언한다. Person 이라는 객체는 name, age, upAge(), changeName() 라는 변수와 메소드를 가지고 있다.

Person() 객체를 만들고, me 라는 참조변수를 선언하여 생성된 객체를 참조한다. me 는 그 객체를 다룰 수 있는 리모콘 같은 역할을 하여 제어할 수 있다.

Person me2 = me; // 참조변수 me를 복사
me2.changeName("Name Changed");
System.out.println(me2.name); //Name Changed
System.out.println(me.name);  //Name Changed

me 는 참조변수이기 때문에 me2 에는 me가 참조하는 값이 저장되고, me2me 는 같은 Person() 을 참조하고 있기 때문에, 위와 같은 결과가 나오는 것이다.

참조변수는 인스턴스와 같은 타입이어야 한다.

Person a = new Person()

변수의 범위

class playGround{
  int iv;
  static int cv;
  void method(){
    int lv;
  }
}
변수의 종류선언 위치생성시기
클래스 변수 (cv)클래스 영역클래스가 메모리에 올라갈 때
인스턴스 변수 (iv)클래스 영역인스턴스가 생성되었을 때
지역변수 (lv)1 - 클래스 영역변수 선언문이 수행되었을 때

같은 영역이지만 클래스 변수와 인스턴스 변수는 구분을 한다.

그 이유는 생성 시기에 따라 다르기 때문인데, 인스턴스 변수는 인스턴스가 생성될 때 마다 각각 다른 값들이 유지 되지만, 클래스 변수는 인스턴스의 생성과는 상관 없이 생성되기 때문에, 여러 인스턴스들이 생성되어도 같은 cv의 값을 참조한다. static(고정된)이라고 이해해도 괜찮을 것 같다.

package playground;

public class playGround {
	static class Planet{
    String planetName;
    static int DAY_OF_YEAR = 365;
  }
  public static void play(){
    System.out.println(Planet.DAY_OF_YEAR); //365 --1
    Planet earth = new Planet();
    System.out.println(earth.DAY_OF_YEAR);  //365
    Planet mars = new Planet();
    mars.DAY_OF_YEAR = 687;

    System.out.println(earth.DAY_OF_YEAR);  //687 --2

  }
}

--1을 실행하면 Planet 이라는 객체가 생성되지도 않았는데 DAY_OF_YEAR를 참조할 수 있다.

또, --2 를 보면, earthmars 는 다른 객체를 참조하고 있지만, 이미 생성된 DAY_OF_YEAR는 공유하고 있다는 것을 알 수 있다. 그래서 DAY_OF_YEAR 와 같은 cv(클래스 변수)를 참조할 경우는 참조변수가 아닌 --1 처럼 클래스이름를 통해 접근하는 것이 권장된다.

메서드

구문
반환타입 메서드이름 (타입 변수명, 타입 변수명, ...) { 수행될 코드 }

int add(int x, int y){
	int answer = x + y;
  return answer;
}

return 타입과 선언부에서 선언한 반환타입이 일치하여야 한다. 다르다면, 반환타입으로 형변환이 이루어져 반환 될 것이다.

package playground;

public class playGround {
  static class MathPersonal{
          int add(int x, int y){
              int answer = x + y;
              return answer;
          };
      }
  public static void play(){
    MathPersonal math = new MathPersonal();
    int A = math.add(97, 2); //99
    int B = math.add('a', 2); //99 char 'a'가 int x로 변환되어 들어감
    System.out.println(A);
    System.out.println(B);
  }
}

반환 타입이 void 인 메소드는 return 이 없어야 한다.

메소드들은 Call Stack 이라는 공간에 호출된 메서드를 쌓는다. 그리고 그 메서드들에게 필요한 메모리 공간을 제공하고, 그 메소드가 종료되면 GC(garbage collector)를 통해 메모리가 반환되어 비어진다. 이때, 메소드들은 스택으로 쌓이다 보니 메소드들간의 관계 파악이 쉽다.

메소드의 매개변수

기본형 매개변수와 참조형 매개변수의 차이에 대해 알아 둘 필요가 있다.

기본형 매개변수는 읽기 전용이고,

참조형은 읽고 쓰기가 가능하다.

package playground;

public class playGround {
    static class Data { int x; }
    static void changeDataRef(Data xRef){
        xRef.x = 1000;
        System.out.println(xRef.x);
    }
    static void changeDataValue(int xVal){
        xVal = 1000;
        System.out.println(xVal);
    }
    public static void play(){
        Data xRef = new Data();
        xRef.x = 10;
        System.out.println(xRef.x); // 10
        changeDataValue(xRef.x);    // 1000
        System.out.println(xRef.x); // 10    --1
        changeDataRef(xRef); 				// 1000
        System.out.println(xRef.x); // 1000  --2
    }
}

위 설명이 가능한 이유는 참조변수를 전해주면, 그 참조변수가 참조하고 있는 객체의 값을 변경하기 때문이다.

package playground;

public class playGround {
    static  class Data { int x ;}
    static Data copy(Data d){
        Data tmp = new Data();
        tmp.x = d.x;
        return tmp;
    }
    public static void play(){
        Data d = new Data();
        d.x = 10;
        Data d2 = copy(d);
        System.out.println(d.x);  // 10
        System.out.println(d2.x); // 10
    }
  /*
  메소드에서 tmp가 참조하는 객체가 생성이 되고,
  그 객체를 참조하는 참조변수 tmp를 반환하여 d2에 저장하였다.
  이제 d2가 tmp가 참조했던 객체를 참조한다.
  */
}

참조변수를 반환하는 메소드 또한 Obj val = new Obj()의 원리를 잘 이해하면 쉽게 이해할 수 있다.

Static

Static을 사용하여 클래스 내의 변수를 클래스 변수로 선언할 수 있다. 메소드에도 가능하다.

Static method() 또한 Static 변수(cv)처럼 인스턴스화 되지 않아도 클래스가 생성될 때 생성이 된다.

참조되지 않아도 메소드가 생성되기 때문에, 인스턴스 변수를 보장받지 못하기 때문에, 메소드에서는 인스턴스 변수를 사용할 수 없다.

package playground;

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

public class playGround {
    public static void play(){
	      System.out.println(Add.add(20,20)); // 40
        Add example = new  Add();
        example.a = 10;
        example.b = 20;
        System.out.println(example.add()); // 30
        System.out.println(example.add(20,20)); // 40
    }
}

위 코드에서 static int addint add() 와는 달리 매개변수를 받는다. 인스턴스 변수를 사용 할 수 없기 때문이다.

Static이라는 키워드는 클래스가 선언될 때 생성이 되기 때문에 다음과 같은 경우에서 사용되는 것이 권장된다.

  • 모든 인스턴스가 동일한 값을 사용할 때
  • 인스턴스 변수나 메서드를 사용하지 않을 때 (성능 향상)

오버로딩

자주 사용되는 println() 이라는 메소드는 어떤 타입의 변수가 들어가도 우리가 원하는 결과를 얻는다는 것을 알 수 있다. 한 클래스 내에서 같은 이름의 메소드를 여러 개 정의하여 어떤 타입의 매개변수가 들어와도 처리가 가능한 것 이다. 이걸 오버로딩이라 하며, 멤버변수의 이름을 아끼며 원하는 결과를 얻을 수 있다.

package playground;

class Add{
    int add(int a, int b) { return 1; };
    int add(int a, long b) { return 2; };
    int add(long a, int b) { return 3; };
    int add(long a, long b) { return 4; };
}

public class playGround {
    public static void play(){
        Add example = new  Add();
        int intNum = 1;
        long longNum = 1l;
        System.out.println("Case : " + example.add(intNum,intNum));
      //Case : 1 
        System.out.println("Case : " + example.add(intNum, longNum));
      //Case : 2
        System.out.println("Case : " + example.add(longNum, intNum));
      //Case : 3
        System.out.println("Case : " + example.add(longNum, longNum));
      //Case : 4
    }
}

add() 라는 메소드는 여러번 정의 되었지만, 매개변수의 개수나 타입이 달라 다른 코드를 실행 하였다.

생성자

인스턴스 초기화 메소드

생성자가 존재하지 않아도 기본 생성자가 존재한다.

생성자는 클래스의 이름과 같아야 하며, 반환하는 값이 없어야 한다.

package playground;

class Person{
    String name;
    int age;
    boolean isChanged;

    Person() {
        name = "Royce";
        age = 23;
        isChanged = false;
    }
  
  //overloading
    Person(int a) {
        birthyear = a;
    }
}
class PersonSimple{
    String name;
    int age;
    int birthYear;

    PersonSimple(String name, int age, int birthYear) {
        this.name = name;
        this.age = age;
        this.birthYear = birthYear;
    }
}

public class playGround {
    public static void play(){
        Person me = new Person();
        System.out.println(me.name);      //Royce
        System.out.println(me.isChanged); //flase
      
        Person me2 = new Person();
        System.out.println(me2.name);     //Royce
        System.out.println(me2.isChanged);//flase
      
     		Person me3 = new Person(1999);    //Person(int a) a = 1999
     		System.out.println(me3.name);     //null --Person(int a)생성자
        System.out.println(me3.birthyear);// 1999
      
      	PersonSimple me4 = new PersonSimple("Taeyeon", 24, 1999);
      	//생성자를 활용하여 간결하게 인스턴스 변수 초기화 하기
    }
}

this()

this() 는 생성자들 간의 호출에서 사용된다. 반드시 생성자의 첫 줄에서 사용되어야 한다.

package playground;

class Juice {
    String fruit;
    int price;
    String callBy;
    Juice(){
        this("apple", 1000, "No Selected"); //this -> constructor()
    }
    Juice(String callBy){
        this("apple", 1000,  "Name Select");//this -> constructor()
    }
    Juice(int callBy){
        this("apple", 1000,  "Price Select");//this -> constructor()
    }
    Juice(String fruit, int price, String callBy) {   //-- constructor
        this.fruit = fruit;
        this.price = price;
        this.callBy = callBy;
    }
}

public class playGround {
    public static void play(){
        Juice apple = new Juice();
        System.out.println(apple.callBy); //No Selected

        Juice lemon = new Juice("Lemon"); 
        System.out.println(lemon.callBy); //Name Select

        Juice orange = new Juice(3000);
        System.out.println(orange.callBy); //Price Select
    }
}

this

this()와는 다르다. this 는 참조변수로 인스턴스 자신을 가리킨다. 위 코드에서 this.fruit은 Juice라는 클래스의 fruit 을 의미한다. 이 역시 인스턴스를 가리키기 때문에, static메서드에서는 사용이 불가하다.

Import

import를 통해 현재 소스파일 이외의 클래스나 패키지에 접근이 가능하다.

import를 하지 않고도 직접 명명하며 사용할 수 있지만, 매번 그렇게 사용한다면 불편할 것이다.

// main.java

import pkg1.importExClass;

public class main {

    public static void main(String[] args){
        importExClass ex = new importExClass();
        pkg1.importExClass ex2 = new pkg1.importExClass(); //import가 없다면,
        System.out.println(ex.x);  //10
        System.out.println(ex2.x); //10
    }
}
// importExClass.java
package pkg1;
public class importExClass {
    public int x = 10;
    public class importExClass2 {
        public void printMessage(){
            System.out.println("Here is importExClass");
        }
    }
}

import를 통해 다른 패키지에 존재하는 클래스를 보다 간결하고 명확하게 접근할 수 있게 되었다.

import를 선언하는 방법에 따라 그 표현을 더욱 명확하게 할 수 있다.

  • import package.class : package라는 패키지에 class라는 클래스를 사용

  • import package.*: package라는 패키지에 모든 클래스를 사용

  • static import : static멤버를 호출할 때 클래스 이름을 생략 할 수 있다.

    import static java.lang.System.out;
    //System.out.println("Hello world");
    out.println("Hello world");

소스파일을 만들때, 선언한적 없은 클래스(String(), Systme.out.println()) 를 사용할 수 있는 이유는, 기본적으로 import java.lang.*이 되기 때문

패키지는 클래스들의 묶음이다.

프로젝트내의 디텍토리 같은 역할을 한다.

' . '을 통해 계층구조를 나타낼 수 있고, 하나의 소스파일엔 한 번만 패키지 선언이 가능하다

profile
앞으로 넘어지기

0개의 댓글