Blog

It's a Wonderful Life

java中如何避免类的私有域被篡改

2017-03-13 22:51 Posted in Learn with Java

来看下面一段代码:

public class test 
{
    public static void main(String[] args) 
    {
        Employee leo = new Employee("leo", 1_000_000_000, 2017, 4, 15);
        Date d = leo.getHireday();
        d.setTime(d.getTime() - 1000 * 60 * 60 * 24);
        System.out.printf("Someone changed my hiredate to %tF", d);
        System.out.printf(" It should be %tF!", leo.getHireday());
    }
}

class Employee 
{
    private final String name;
    private final double salary;
    private final Date hireday;
    
    public Employee(String n, double s, int year, int month, int day)
    {
        this.name = n;
        this.salary = s;
        GregorianCalendar date = new GregorianCalendar(year, month - 1, day);
        this.hireday = date.getTime();
    }
    
    public Date getHireday()
    {
        return this.hireday;
    }
    
    public String getName()
    {
        return this.name;
    }
    
    public double getSalary()
    {
        return this.salary;
    }
}

它的输出类似于:

Someone changed my hiredate to 2017-04-14 It should be 2017-04-15!

出现这种问题的原因是Date是一个可变对象,语句Date d = leo.getHireday();拿到的Date对象实际上和类leo中的私有域hireday指向同一块内存区域,所以任何对d的修改都会导致employee类的私有域变化,进而破坏了类的封装性。

要修复这一问题也是很方便的,只要留意对返回可变对象的函数调用clone()方法就可以了,以下是修改后的代码:

public class test 
{
    public static void main(String[] args) 
    {
        Employee leo = new Employee("leo", 1_000_000_000, 2017, 4, 15);
        Date d = leo.getHireday();
        d.setTime(d.getTime() - 1000 * 60 * 60 * 24);
        System.out.printf("You can change it to %tF", d);
        System.out.printf(" But I still get hired at %tF", leo.getHireday());
    }
}

class Employee 
{
    private final String name;
    private final double salary;
    private final Date hireday;
    
    public Employee(String n, double s, int year, int month, int day)
    {
        this.name = n;
        this.salary = s;
        GregorianCalendar date = new GregorianCalendar(year, month - 1, day);
        this.hireday = date.getTime();
    }
    
    public Date getHireday()
    {
        return (Date)this.hireday.clone();
    }
    
    public String getName()
    {
        return this.name;
    }
    
    public double getSalary()
    {
        return this.salary;
    }
}

它的输出类似于:

You can change it to 2017-04-14 But I still get hired at 2017-04-15