Java의 Null

박건희·2021년 8월 17일
0

Java

목록 보기
2/2

Null이란

  • 아무 것도 없는 것을 나타내는 자바의 keyword
  • 참조형 타입(객체와 배열의 참조변수)의 기본값

참조변수가 가리키는 객체가 없을 때, 주로 초기값으로 null값을 가진다. 참조변수가 지역변수로 선언된 경우 선언과 동시에 초기화되어야 하기 때문에, 선언할 때 참조변수가 가리킬 객체의 주소가 결정되지 않았다면 null로 초기화 하는 것 같다.

참조변수의 값이 null일 때는 어떤 일이 일어날까?

class Computer{
    private int cpu;
    private int memory;

    Computer(int cpu, int memory) {
        this.cpu = cpu;
        this.memory = memory;
    }

    public void boot(){}
}

public class Null {
    public static void main(String[] args) {
        Computer myCom[] = new Computer[10];  //(1)   
        System.out.println(myCom);            //(2)   패키지명.클래스명.;@2f4d3709
        System.out.println(myCom[0]);         //(3)   null
        myCom[0].boot();                      //(4)   NPE!!!
    }
}

헷갈림을 방지하기 위해 복합적인 예제를 작성해보았다.

위 예제를 설명하자면
(1) : Computer type의 객체 배열의 참조변수 myCom을 선언한다.
(2) : 배열 참조변수 myCom의 값을 출력 -> 메모리 주소값
(3) : myCom[0] 의 값 출력 -> null
(4) : Exception 발생!! 발생하는 Exception은 java.lang.NullPointerException로, myCom[0] 의 값이 null이기 때문이다.

객체 배열에서 각 원소가 reference하는 객체가 없기 때문에 null로 초기화 되어있다(일반 참조변수를 지역변수로 선언하고 사용하면 초기화후 사용하라는 error가 발생한다). 위와같이 null값을 가지는 객체의 인스턴스 변수나 인스턴스 메소드를 접근하면 NullPointerException이 발생한다.

NullPointerException 해결 방법

1) 참조변수로 객체에 접근하기 전에 null값을 가지는지 확인한다.

class Computer{
    private int cpu;
    private int memory;

    Computer(int cpu, int memory) {
        this.cpu = cpu;
        this.memory = memory;
    }

    public void boot(){
        System.out.println("boot합니다.");
    }
}

public class Null {
    static void bootComputer(Computer computer){
        if(computer!=null) computer.boot();
        else System.out.println("computer가 null입니다.");
    }
    
    public static void main(String[] args){
        Computer myCom1 = null;
        Computer myCom2 = new Computer(1,1);

        bootComputer(myCom1);  // computer가 null입니다.
        bootComputer(myCom2);  // boot합니다.

    }
}

참조변수로 객체를 접근하는 쪽(Null 클래스의 main method)에서 Computer 참조변수의 값이 null인지 확인하는 bootComputer() 메소드를 작성했다.

그런데, null 체크 방식은 깊이가 깊은 클래스(클래스의 필드로 객체를 갖는 경우)일 수록 실수를 범할 확률이 높은 방식이다.

class Cpu{
    private int core;

    Cpu(int core) {
        this.core = core;
    }

    
    public int numCore(){
    	return core>0 ? core : -1;
    }
}

class Computer{
    private Cpu cpu;     // 멤버변수로 참조타입의 변수 -> null 가능성
    private int memory;

    Computer(Cpu cpu, int memory) {
        this.cpu = cpu;
        this.memory = memory;
    }
    public Cpu getCpu(){
        if(cpu != null) {
            return cpu;
        }
        return null;
    }
    
}

public class Null {
    static int getNumCpuCore(Computer com){
        if(com==null) return -1;
        if(com.getCpu()==null)  return -1;
        return com.getCpu().numCore();
    }
    public static void main(String[] args){
        Cpu ncpu = null;
        Computer myCom1 = new Computer(ncpu, 1);
        Computer myCom2 = new Computer(new Cpu(2),1);
        
        getnumCpuCore(myCom1);                // (1) -1
        getnumCpuCore(myCom2);                // (2) 2
        System.out.println(myCom1.getCpu());  // (3) null
        myCom1.getCpu().numCore();            // (3) NPE!!!
    }
}

(1) : getNumCpuCore()에서 cpu가 null임을 확인해서 NPE가 발생하지 않는다.
(2) : Computer와 그의 멤버인 Cpu가 null이 아니므로 Exepction이 발생하지 않는다.
(3) : myCom1이 가리키는 Computer의 Cpu = null 이지만, Cpu 객체에 접근하지 않는 이상 NPE가 발생하지 않음.
(4) : myCom1 != null이라 getCpu()는 OK, myCom1이 가리키는 Computer의 Cpu가 null이라 setCore(2)에서 NPE 발생!!

위 예제보다 복잡한 클래스에서는 null 체크를 위한 코드가 매우 복잡해지고 자칫하면 NPE가 발생할 수 있다. 또, null체크를 완벽히 한다해도, method 바디가 매우 길어져 method의 핵심 로직을 파악하기 어려울 수 있다.

2) Java8의 Optional

Java8에서 등장한 개념인 Optional을 이용하면 NPE 방지를 보다 쉽게 할 수 있다. Optional은 Generic type의 객체를 감싸는 wrapper class이다. 이 글에서는 Optional을 사용하는 예제만 다루려고 한다.


import java.util.Optional;

class Cpu{
    private int core;

    Cpu(int core) {
        this.core = core;
    }

    public int numCore(){
        return core>0 ? core : -1;
    }
}

class Computer{
    private Optional<Cpu> cpu;     // 멤버변수로 참조타입의 변수 -> null 가능성
    private int memory;

    Computer(Optional<Cpu> cpu, int memory) {
        this.cpu = cpu;
        this.memory = memory;
    }
    public Optional<Cpu> getCpu(){
        if(cpu.isEmpty()) {
            return Optional.empty();
        }
        return cpu;
    }

}

public class Null {
    static int getNumCpuCore(Optional<Computer> com){
        if(com.isEmpty()) return -1;
        if(com.flatMap(Computer::getCpu).isEmpty()) return -1;
        return com.flatMap(Computer::getCpu).get().numCore();
    }
    public static void main(String[] args){
        Cpu ncpu = null;
        Optional<Computer> myCom1 = Optional.ofNullable(new Computer(Optional.ofNullable(ncpu), 1));
        Optional<Computer> myCom2 = Optional.ofNullable(new Computer((Optional.of(new Cpu(2))), 1));
        
        //Computer myCom2 = new Computer(new Cpu(1),1);
        
        
        System.out.println(getNumCpuCore(myCom1));                  // -1
        System.out.println(getNumCpuCore(myCom2));                  //  2

        System.out.println(myCom2.flatMap(Computer::getCpu));       //  Optional[Cpu@79fc0f2f]
        System.out.println(myCom2.flatMap(Computer::getCpu).get()); // Cpu@79fc0f2f
        System.out.println(myCom2.map(Computer::getCpu));           // Optional[Optional[Cpu@79fc0f2f]]
        System.out.println(myCom1.flatMap(Computer::getCpu));       // Optional.empty
        
        
        // Optional.empty 인 경우에 대체할 행위 
        System.out.println(myCom1.flatMap(Computer::getCpu).orElse());
        System.out.println(myCom1.flatMap(Computer::getCpu).orElseGet());
        
        
        
        
        
        //System.out.println(myCom2.flatMap(Computer::getCpu));
        //getNumCpuCore(Optional<Computer> com);                // (1) cpu가 null입니다.
        //getNumCpuCore(myCom2);                // (2) cpu core는 1개입니다.
        //System.out.println(myCom1.getCpu()); // (3) null
        //myCom1.getCpu().numCore();          // (3) NPE!!!
    }
}

이 밖에도

3) String의 null

  • @NotNull
  • String.valueOf()
    등의 방법이 있는데, 나중에 알아볼 계획이다.

참고

Optional을 잘 쓰는 방법을 포스팅한 블로그
Optional 바르게 쓰기
Optional 제대로 활용하기
[자바 8] CH10 - null 대신 Optional

0개의 댓글