1. 🔥 배열

  • 클래스를 생성할 때 기본적으로 같은 패키지 내에서는 같은이름의 클래스를 선언할 수 없다.
class MyCup{
    double radius;
    String name;

    MyCup(double a){
        this(a,"");
        // radius = a;
        // name = "";
    }
    MyCup(double a, String b){
        radius = a;
        name = b;
    }

    void print(){
        System.out.println("name : "+name+" r : "+radius);
    }
}

public class MoreClass {
    public static void main(String[] args) throws Exception {
        MyCup [] arr = new MyCup[5]; //이것의 의미는 MyCup레퍼런스 변수 5개를 배열로 만든 것 즉, 각각에 대한 객체화는 진행하지 않음
        arr[0].print(); //예외 던짐 -> nullpointexception
        arr[0] = new MyCup(10,"cup"); //instace생성..! 
        arr[0].print(); //실행 가능
    }
}

앞서 작성한 코드를 활용하여 배열에 객체를 담은 모습!

중요

MyCup [] arr = new MyCup[5]는 MyCup객체를 가르킬 수 있는 레퍼런스 변수 5개를 생성한 것이지 실제로 동적할당한 부분이 아님!

그렇다면 왜 이렇게 사용할까?

  public static void main(String[] args) throws Exception {
      MyCup [] arr = new MyCup[5];
      String a = "fkdl";
      for(int i = 0; i < arr.length ; i++,a +="a")
          arr[i] = new MyCup(i*2,a); //코드의 재사용을 줄이고 일반화가 가능
      for(MyCup elem : arr) //foreach접근!
          elem.print();
  }
}

위의 코드에서 배열을 함수의 매개변수로 넘겨주고 싶다면 다음과 같이 사용가능하다.

public class MoreClass {
    static void func(MyCup []ar){ //똑같이 받는다 그대로 해석 MyCup클래스의 []베열을 가리키는 레퍼런스 변수 ar
        for(MyCup elem : ar)
            elem.print();
    }

2. 메서드 형식

기본적으로 자바의 모든 메서드는 클래스안에 존재해야함

public int getsum(int i, int j){ //public: 접근지정자/ int: 리턴타입/ getsum: 메서드 이름/ (int i, int j) : 메서드 인자
  ~~~ //함수 바디
}

기본구성

2.1. 인자값 전달 방식

기본 작동방식은 내생각엔 c언어와 동일하다..! 레퍼런스를 포인터개념에 덮어씌우기 하면 될듯!

  1. 기본타입의 값 전달
    • 이 경우에는 값이 복사가 되어 전달되기 때문에 클래스내부를 건드리지 못한다. 즉 실인자값을 못건드림
  2. 객체 혹은 배열전달
    • 객체나 배열의 레퍼런스를 전달한다면 복사가 아닌 같은 곳을 가르키게 된다.(해당 매개변수가) 즉, 같은 레퍼런스를 공유하게 된다…

주의
String은 클래스지만 원하는대로 작동되지 않음!(다른 주차에서 내용나옴)

3. 가비지 컬렉션

public static void main(String[] args) throws Exception {
    
    MyCup a = new MyCup(1,"fkdl"); // fkdl값을 가진 객체 현재 레퍼런스 카운팅 1
    MyCup b = new MyCup(2,"4878"); // 4878값을 가진 객체 현재 레퍼런스 카운팅 2
    a = b; // fkdl값을 가진 객체 현재 레퍼런스 카운팅 0
}
  • 이렇게 a의 객체가 소실되게 되면 가비지 컬렉션이 메모리를 헤제한다..!(하지만 언제 호출될지 모르기 때문에 카운팅이 0이 된다고 해서 바로 호출되는 것이 아님)

System.gc()로 호출요청가능.. 하지만 요청한다고 해서 작동되는 것이 아님 가상머신 판단하에 이뤄어진다.

4. 접근 지정자

자바에선 필드, 메서드, 클래스 앞에 접근지정자를 쓰게 되어있다.

뒤에서 배우겠지만 자바는 기본적으로 패키지들(단순하게 같은 폴더내)로 구성된다.

  • 같은 패키지내의 클래스들은 기본적으로 서로 공유하게되는데 그것을 디폴트접근지정자라고 한다.

즉, 지금까지 실습하며 사용한 필드, 클래스, 메서드는 전부 디폴트접근지정자이다.(앞에 아무것도 쓰지 않아서)

public

그렇다면 지금까지 main앞에 있던 그 public은 어떠한 접근 지정자인가?

  • 디폴트접근지정자가 해당 패키지내에서만 공유된다면 public은 다른 패키지에서도 공유가 가능하다..!

즉, 이말은 소스코드를 하나 생성할 때 첫 클래스의 규칙에 따라 첫클래스의 이름은 소스코드의 이름과 동일하고 public으로 선언해야하기 때문에 다른 패키지내에서도 이 클래스를 볼 수 있다.(main이 존재하는 클래스)

private

같은 패키지라도 접근 못하는 강력한 접근지정자!

  • private은 캡슐화에서 정말 중요한 개념으로 같은 클래스내에서 밖에 접근하지 못하기 때문에 중요한 필드나 메서드를 숨기기에 적합하다.(private클래스는 자기 자신을 만드나 마나.. 즉 아이 보이지 않기 때문에 사용하지 않음)

protected

같은 패키지 접근 가능 그리고?

  • 다른 패키지에서는 일반적인 경로로는 접근이 불가능 하지만 해당 클래스를 상속받은 경우 protected된 메서드 사용가능!

정리

접근지정자의 종류 -> private, protected, public, 디폴트

목적 -> 캡슐화를 통해 멤버들을 보호해야하는데 전부 public으로 선언되어 있으면 특정함수가 실행되고 실행되어야하는 메서드나, 사용자가 수정하면 안되는 필드등 캡슐화의 목적에서 어긋남

데이터,메서드 은닉이 가능해짐!

클래스의 접근지정자

클래스의 접근지정자는 public, 디폴트를 선택할 수 있다.

  • public 다른 모든 클래스에게 접근 가능
  • 디폴트는 같은 패키지내에서만 접근이 가능해짐

5. static

static은 정적의 의미를 가진다.

  • 정적이란 컴파일 전에 이미 개수가 정해지거나 동작이 정해진 경우를 말한다.

즉, 어떠한 메서드가 정적(static)이라는 말은 컴파일 타임부터 존재하는 것을 의미한다.(c언어의 전역변수같은 개념)

예를 들어

public static void main(String[] args) throws Exception {
    
    MyCup a = new MyCup(10,"cup"); //동적할당!
    MyCup b;
    b.print(); //오류..! 마찬가지로 널포인트익섹션올퓨 
    a.print(); //동적할당을 해야지만 실행 가능한 함수 즉 동적함수라고 부를 수 있음!
}
  • 일반적인 멤버들은 객체 속에 각각 존재한다. 하지만 static으로 선언한 메서드, 필드는 클래스와 상관없이 독립적으로 존재한다.

그렇다면 각각의 객체들로 static(전역/정적)을 사용한다면 독립적으로 존재하니 객체의 영향을 받지 않나요?

  • 그렇다!
class MyStatic{
    static int t = 1; // 정적 변수 -> 전역변수로 인식 가능
    static void test(){
        System.out.println("Test" + t); // 마찬가지로 전역함수개념
        t++;
    }
}

public class App {
    public static void main(String[] args){
        MyStatic a = new MyStatic();
        MyStatic b = new MyStatic();

        a.test(); //Test1출력
        b.test(); //Test2출력!
    }
}
  • 이처럼 독립적으로 존재하는 것을 static즉 정적(컴파일시 이미 정해져 있음)

그렇다면 사실 상 이미 존재하는 전역의 개념이니 객체와 무관하지 않은가?

  • 그렇다..!
public class App {
    public static void main(String[] args){
        MyStatic.test(); //Test1출력
        MyStatic.test(); //Test2출력
        MyStatic.test(); //Test3출력
        MyStatic.t = 100; 
        MyStatic.test(); //Test100출력
    }
}
  • 이러한 방식으로 출력할 수 있다. 또한 이러한 방식으로 출력하는 것이 용이하다!

그 이유는 사용자가 정적메서드나 정적필드를 호출하는 모습 즉, 클래스이름자체로 호출하는 모습만 봐도 정적인줄 알 수 있기 때문에 객체로 호출해도 무관하지만 클래스이름 자체로 호출하는 것이 좋다.

주의

static은 객체와 무관하기 때문에 static에서 객체를 참조할 수 없다..!

class MyStatic{

    int itme; //MyStatic멤버 변수 즉, 객체를 동적할당으로 생성해야지 사용가능

    static int t = 1;
    static void test(){
        System.out.println("Test" + t);
        t++;
        
        item = 1; //참조 불가능하다..! 
    }
}

static함수는 다른 static함수만 호출 가능하다..즉, 객체 멤버 메서드는 호출 불가

그렇다면 main은?

내가 만든 MyApp라는 소스코드에는 MyApp라는 클래스가 존재하고 해당 클래스 내부에는 public static으로 만들어진 main메서드가 존재한다.

  • 즉, 컴파일시 이미 main이 존재하기 때문에(public)컴파일러는 MyApp.main()호출하면 전체적인 소스코드가 실행되게 된다.

  • static멤버는 클래스당 하나만 생성되며 모든 객체가 공유함

대표적인 static활용

  • 자바의 기본 API 패키지 java.lang.Math라이브러리가 대표적인 예이다.

내부에는 수학적인 static메서드만 존재하며 Math.abs(-5);와 같이 사용한다.

  • 그렇다면 Math의 객체를 만들어서 사용해도 되지 않나?

답은 만들 수 없다. 왜냐하면 내부에는 static을 제외한 멤버가 존재하지 않는데 객체를 만들면 쓸데없는 낭비이기도 하고 불필요한 작업이기 때문에 생성자로 막아놨다.

this키워드 사용불가

class MyStatic{

    private MyStatic(){ //생성자를 private으로 가려둠
    }
    
    static int t = 1; 
    static void test(){
        System.out.println("Test" + t); 
        t++;
    }
}

public class App {
    public static void main(String[] args){
        MyStatic.test();
        MyStatic a = new MyStatic(); //오류 발생 즉, 메서드 사용자에게 경고를 할 수 있음 이 메서드는 유틸함수처럼 static만 사용해줘~ 
    }
}

5.1. final

c언어의 const즉 변경불가능한 상수을 지칭할 때 쓰는 키워드이다.

  • final클래스는 클래스 상속 불가..!
    • 즉 유일한 클래스로 만들어 고정시키고 싶다.!
  • final메서드는 오버라이딩 불가..!
    • 오버라이딩을 막아둘 수 있음

final필드

클래스안에 필드를 상수로 만든다면 100개의 객체는 각각 똑같은 상수를 가진다..?

  • 이는 매우 비효율적이며 어차피 상수이기 때문에 100개의 객체가 하나의 상수를 참조하면 간단하게 해결 가능하다 따라서 위에서 사용한 static을 사용한다.
static final double PI = 3.141592; 

5.2. String 정리

public class StringTest {

    static void func(MyCup a){ //MyCup객체의 필드를 치환
        a.name = "realcup";
    }

    static void func2(String n){ //String객체의 필드를 치환?
        n = "hello";
    }

    public static void main(String[] args){
        MyCup a = new MyCup(10,"cup");
        a.print(); //cup 10출력 
        func(a);
        a.print(); //realcup 10 출력

        String n = "fkdl"; // -> String n = new String("fkdl");
        func2(n);
        System.out.println(n); //fkdl출력
    }
}

왜 String클래스는 변환이 안될까?

String n = "fkdl";은 사실 내부적으로 다른 클래스처럼 String n = new String("fkdl");으로 “fkdl”라는 문자열이 데이터 영역에 생성되고 해당 문자열을 n이 가르키는 것

  • 따라서 func2()기능 처럼 해당 메서드의 매개변수(String n)은 즉, n은 “fkdl”을 같이 가르키다(main에서 가리키던 n이랑 다른 변수임) “hello”을 참조하고 있는것

따라서 우리가 볼땐 값이 바뀌지 않은 것

즉 String은 바뀌지 않는다. 항상 새로운 문자열을 만들고 그곳을 가리키는 것!

6. 🔥 상속

상속은 A가 가진 멤버들을 가지면서 새로운 멤버들을 같는 B를 만드는 것

확장, 유지보수, 클래스 간의 관계성 확립(형제 개념), 다령성의 기본 전제

정리하자면 상속은 클래스를 간결화할 수 있으며, 관리에 용이하고, 전체적인 생산성이 향상된다.

extends 키워드

확장의 뜻을 가진 extends… 즉, B extends A A를 확장한 B를 만들겠다..!

  • 부모클래스, 슈퍼클래스
  • 자식클래스, 서브클래스
class Point{ // 이미 오래전 부터 사용해온 클래스 
    int x,y;
    Point(){
        x = 0;
        y = 0;
    }
    Point(int _x,int _y){
        x = _x;
        y = _y;
    }
    void print(){
        System.out.println("x = "+x+", y = "+y);
    }
}

class ColorPoint extends Point{ //Point를 상속 받은 새로운 클래스
    String color;
    void colorprint(){
        System.out.println("x = "+x+", y = "+y+" Color = "+color);
    }
}

public class InheritanceTest {
    public static void main(String[] args){
        ColorPoint a = new ColorPoint(); // ColorPoint 클래스 객체 생성
        a.print(); //x = 0, y = 0 출력
        a.color = "red";
        a.colorprint(); //x = 0, y = 0 Color = red 출력!
    }
}
  • 코드 처럼 상속의 가장 큰 기본개념은 확장을 가진다..!

특징

  1. 클래스의 다중 상속을 지원하지 않음
  2. 상속 횟수 무제한
  3. 상속의 최상위 조상 클래스는 Object클래스
    • 모든 클래스는 java.lang.Object를 자동으로 상속받음

6.1. 상속 : 생성자

class Point{
    Point(){ //생성자 / 디폴트 생성자 재정의
        x = 0;
        y = 0;
    }
    생략
}

class ColorPoint extends Point{
  // 생성자 없음 즉, 컴파일러가 만들어 주는 디폴트 생성자 사용
  생략
}
  • 위의 코드를 생성자 부분만 간추린 것

이때, 출력값을 다시 본다면 x = 0, y = 0이 출력되는데 왜 이럴까?

  • 부모를 상속받은 서브클래스의 디폴트 생성자는 기본적으로 부모의 생성자를 호출하기 때문

이유는 만약 슈퍼클래스를 업데이트하는 상황이 벌어진다고 가정한다면(멤버 변수하나 추가)

위에서 말한 디폴트 생성자를 사용하지 않는다면 슈퍼클래스를 상속받은 모든 클래스들의 생성자를 고쳐야한다..!

  • 그렇다면 한가지 질문이 더 생기는데 만약 부모가 디폴트 생성자를 없애고 인자를 받는 생성자를 강제한다면?

super 키워드를 사용!

super키워드는 부모의 생성자를 골라서 호출할 수 있다. (강제한 경우)

class Point{
    Point(int _x,int _y){ // 사용자가 정의한 생성자
        x = _x;
        y = _y;
    }
    생략 //디폴트 생성자는 없음 즉, 매개변수를 강제함
}

class ColorPoint extends Point{
  ColorPoint(){ // 에러출력..! 명확한 생성자를 호출해야함

  }
  //따라서 
  ColorPoint(){
    super(0,0); // 부모의 Point(int _x,int _y)생성자 호출
    color = "black"; 
  }
  생략
}
  • super키워드는 this처럼 생성자 가장 위에 존재해야 한다.

즉, 서브클래스의 생성자가 실행될 때 서브,슈퍼클래스의 생성자 모두 실행된다.(슈퍼클래스 실행 후 서브클래스 실행)

반드시 서브클래스 생성자에서 슈퍼클래스의 생성자 하나를 선택해야 한다.

미선택 시 디폴트 생성자 호출..!

6.2. 상속 : 형변환

클래스 간에 형변환은 일반적으로 불가능하지만 상속관계에선 가능하다.

업캐스팅(upcasting)

  • 자식클래스의 객체를 부모 클래스의 객체로 인식할 수 있는가?

= 언제나 가능하다!

즉, 자식형태로 만든 클래스는 언제나 부모형태로 돌릴 수가 있다..!

자식이 부모를 상속받는 순간 부터 부모의 필요조건을 전부 가지고 있기 때문..!

public class InheritanceTest {
    public static void main(String[] args){
        ColorPoint a = new ColorPoint();
        a.colorprint();
        Point b = a; //업캐스팅 즉, a의 영역에서 부모의 영역을 가리킴
        b.x = 4878; //해당 변수 치환
        a.colorprint(); // x=4878출력!
    }
}

다운캐스팅(downcasting)

  • 다운캐스팅은 부모클래스의 객체를 자식클래스의 객체로 인식할 수 있는가?

= 불가능하다!

하지만 강제적인 다운캐스팅은 가능하다.. 하지만 사용은 불가능!

  • 그렇다면 왜 존재하는 개념인가? 가능한 경우가 있기 때문!
public class InheritanceTest {
    public static void main(String[] args){
        ColorPoint a = new ColorPoint();
        a.colorprint();
        Point b = a; //업캐스팅
        b.x = 4878;
        a.colorprint();

        ColorPoint c = (ColorPoint)b; //다운캐스팅
        // 중요! b가 가리키고 있는 곳은 ColorPoint의 Point영역이다.
        // 즉 ColorPoint자체가 Point자체를 상속 받은 클래스이기 때문에 생성된 객체의 구조는 Point를 가지고 있음
        // 이때 명시적으로 업캐스팅해준 b를 다시 사용자가 알고 있다는 전제하여 다운캐스팅이 가능하다! 
        c.colorprint(); // 올바른 출력값
    }
}
  • b의 경우 부모처럼 생겼지만 그 아래에는 자식이 존재하기 때문에 명시적 다운캐스팅이 가능한 것!

강제적 형변환은 컴파일시 체크를 안하기 떄문에 책임이 사용자에게 있음

instanceof 연산자

앞서 사용한 형변환 개념이 들어가면 현재 가르키고 있는 객체의 원래 타입을 알 수 없기 때문에 instanceof를 사용하면 어떠한 클래스인지 알 수 있다.

public class InheritanceTest {

    static void func(Object o){ //모든 클래스의 최상위 부모 Object클래스
        if(o instanceof Point) // 만약 들어온 객체가 Point라면 
            System.out.println("Point");
        if(o instanceof ColorPoint)// 만약 들어온 객체가 ColorPoint라면 
            System.out.println("ColorPoint");
    }

    static void change(Point in){ //상위 부모클래스로 자식까지 전부 받을 수 있음
        in.x *= 2; // ColorPoint든 Point든 실행
        in.y *= 2;
        if(in instanceof ColorPoint){ //들어온 클래스가 원래 ColorPoint의 객체라면 실행
            ColorPoint pt = (ColorPoint) in;
            pt.color = "blue";
        }
    }

    public static void main(String[] args){
        ColorPoint a = new ColorPoint(10,20);
        Point b = a; //업캐스팅
        change(b); // 원래 ColorPoint객체 
        a.colorprint();
        Point c = new Point(60, 30);
        change(c); // Point의 객체
        c.print(); // 곱하기 연산만 수행

        func(a); //Point, ColorPoint 출력! <- ColorPoint는 Point이면서 ColorPoint이기 때문
        func(c); //Point 출력 
    }
}

중요!

public static void main(String[] args){
    
    Point [] pts = new Point[2]; // Point객체 배열 생성(레퍼런스 변수 배열)
    pts[0] = new Point(10, 20); // pts[0] 이 가르키는 곳에 Point 객체 동적 생성 
    pts[1] = new ColorPoint(30,40,"blue"); // pts[1]이 가리키는 곳에 ColorPoint 객체 동적 생성 하지만 내부의 Point부분만 가리킴

    pts[0].print(); // 10, 20 출력
    pts[1].print(); // 30, 40 출력 -> 하지만 내부 구조는 30,40,"blue"가 저장되어 있음
    ((ColorPoint)pts[1]).colorprint(); //따라서 명시적으로 형변환(다운캐스팅) 가능
}
  • 그렇다면 클래스의 최상위 클래스인 Object도 가능할까?
Object [] objs = new Object[2];
objs[0] = new Point(1, 2);
objs[1] = new ColorPoint(3,4,"vert");
((Point)objs[0]).print();
((ColorPoint)objs[1]).colorprint(); 
  • 가능하다 하지만 중요한 점은 다운캐스팅은 사용자가 형식을 정확하게 알아야 사용가능 하다는 점이다!

태그: ,

카테고리:

업데이트:

댓글남기기