包装器类型比较是否相等为什么用equals?

1,239 阅读4分钟

1、==和equals的区别

我们都知道,==是直接比较两边的值是否相等,换句话说,如果是基本数据类型,比较的是数值是否相等;如果是引用类型,比较的是引用指向的地址是否相等;而equals内部,实际上是调用了==

区别在于:我们可以对equals方法进行重写,让它比较对象的某个数据域是否相等即可。典型的有String类的equals方法。

2、问题引入

今天在刷一道题的时候,需要比较栈A的弹出元素是否等于栈B的栈顶元素,相等则把栈B的栈顶元素也弹出。

    if(A.pop()==B.peek()){
        B.pop();
    }

但是发现这两行代码并没有起到预期的效果,于是想着到IDEA调试一下。我一把代码拷贝到IDEA,就弹出提示:包装器类型的相等应该用equals,而不是'=='

image.png

对啊,Integer也是一个类,怎么能直接用==呢?既然如此,Integer类肯定对equals方法进行了重写了。

image.png

3、享元模式

享元模式(Flyweight Pattern)是池技术的重要实现方式,可以降低大量重复的、细粒度的类在内存中的开销。 享元对象能做到共享的关键是区分内部状态(Internal State)和外部状态(External State)

  • 内部状态是存储在享元对象内部的、可以共享的信息,并且不会随环境改变而改变。

  • 外部状态是随环境改变而改变且不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。

image.png

4个角色:

  • 抽象享元(Flyweight):该角色对享元类进行抽象,需要外部状态的操作可以通过参数的形式将外部状态传入
  • 具体享元(ConcreteFlyweight)角色:该角色实现抽象享元定义的业务,注意享元对象的内部状态必须与环境无关,从而使得享元对象可以在系统内共享
  • 享元工厂(FlyweightFactory)角色:该角色构造一个池容器,负责创建和管理享元角色,并提供从池容器中获得对象的方法,保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象时,享元工厂会检查系统中是否已经有一个复合要求的享元对象。如果已经有了,享元工厂提供这个已有对象;否则创建一个合适的享元对象。
  • 客户端:需要自行存储所有享元对象的外部状态。

框架实现

public interface FlyWeight {
    /**
     * 业务方法
     * @param externalState 享元对象的外部状态
     */
    public abstract void operation(String externalState);
}
public class ConcreteFlyweight implements FlyWeight{
    // 内部状态
    private String internalState;

    // 通过构造函数中的参数对内部状态进行赋值
    public ConcreteFlyweight(String internalState){
        this.internalState = internalState;
    }

    @Override
    public void operation(String externalState) {
        System.out.println("外部状态:"+externalState);
        System.out.println("内部状态:"+internalState);
    }
}
public class FlyweightFactory {
    // 静态的Map集合享元对象-->池容器
    private static Map<String,FlyWeight> pool =
            new HashMap<String,FlyWeight>();

    // 私有构造方法
    private FlyweightFactory(){

    }

    /**
     * 根据内部状态值获取享元对象
     * @param internalState
     * @return
     */
    public static FlyWeight getFlyweight(String internalState){
        // 内部状态不可改变,可以作为主键,根据它的值从池容器获取享元对象
        FlyWeight flyweight = pool.get(internalState);
        // 如果容器中没有对应的享元对象,则创建一个新的享元对象并保存到池容器中
        if(flyweight == null){
            flyweight = new ConcreteFlyweight(internalState);
            pool.put(internalState,flyweight);
        }
        return flyweight;
    }
}

使用实例

image.png

/**
 * 棋子接口作为抽象享元
 */
public interface Chesspiece {
    /**
     * 落子,用x,y对其进行定位
     * @param x
     * @param y
     */
    void put(int x, int y);
}
/**
 * 具体享元实现棋子接口Chesspiece
 */
public class ChesspieceFlyweight implements Chesspiece{
    // 内部状态
    private String color;

    // 构造函数传入内部状态
    public ChesspieceFlyweight(String color){
        this.color = color;
    }
    @Override
    public void put(int x, int y) {
        System.out.println("在("+x+","+y+")位置放了一个"+color+"子");
    }
}
public class ChesspieceFactory {
    static final Map<String,Chesspiece> pool
            = new HashMap<>();

    public static Chesspiece getChesspiece(String color){
        Chesspiece chesspiece = pool.get(color);
        if(chesspiece == null){
            chesspiece = new ChesspieceFlyweight(color);
            pool.put(color,chesspiece);
        }
        return chesspiece;
    }
}
public class Test {
    public static void main(String[] args) {
        Chesspiece p1 = ChesspieceFactory.getChesspiece("黑");
        p1.put(1,1);
        Chesspiece p2 = ChesspieceFactory.getChesspiece("白");
        p2.put(2,2);
        Chesspiece p3 = ChesspieceFactory.getChesspiece("黑");
        p3.put(3,3);
        Chesspiece p4 = ChesspieceFactory.getChesspiece("白");
        p4.put(4,4);

    }
}

享元模式的优缺点和应用

  • 优点:大幅度减少内存中对象的数量,降低程序内存的占用,提高性能
  • 缺点:1、享元模式增加了系统的复杂性,需要区分外部状态和内部状态,而且内部状态具有固化特性,不应该随着外部状态而改变,使得程序的逻辑复杂化;2、享元模式将享元对象的状态外部化,而读取外部状态使得运行时间变长。
  • 使用场景
    • 系统中有大量相似的对象,这些对象耗费大量的内存
    • 细粒度的对象都具备接近的外部状态,而且内部状态与环境无关,即对象没有特定身份
    • 需要缓冲池的场景
    • 实际应用:String、Integer、Boolean、Character等类都通过享元模式提供了内部的池化机制(例如:boolean、byte、char≤127,介于-128~127之间的short和int被包装到固定的对象中)

回到Integer

通过上文可知,Integer的值在-128~127之间都放在固定的对象中,看看源码实现。


public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

// IntegerCache类中存储了[-127,127]之间的Integer对象

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        // 把cache区间内的值的Integer对象存储在cache数组中
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

也就是说,如果Integer的值在[-128,127]之间,可以使用==比较是否相等,否则就要使用equals比较。所以,如果没有特殊情况的话,还是都用equals好了。