关于java:cs61b-week5-Object-Methods

10次阅读

共计 4264 个字符,预计需要花费 11 分钟才能阅读完成。

(在 2020 后的版本把 2018 写的 ArrayMap 改成了 ArraySet,因而上面的案例基于 ArraySet)
所有的类都隐式地继承了 Object,且蕴含 Object 的以下办法:

  • String toString()
  • boolean equals(Object obj)
  • Class <?> getClass()
  • int hashCode()
  • protected Objectclone()
  • protected void finalize()
  • void notify()
  • void notifyAll()
  • void wait()
  • void wait(long timeout)
  • void wait(long timeout, int nanos)

本次课咱们重点介绍前两个办法

1.toString()

当你每次调用 System.out.println(x)时,无论 x 本来的类型是什么,都会产生隐式地转换,System.out.println(Object x) calls x.toString()
(If you’re curious: println calls String.valueOf which calls toString)

String x = x.toString();
System.out.println(x);

默认的 Object 的 toString()办法转换的格局为:

classname @ hex_memory
类名 @十六进制内存地址

因而如果打印咱们本人写的 ArraySet,其后果是:

$ java ArraySetPrintDemo
ArraySet@75412c2f

然而 Java.util 内置的 List,Set,Map 均 Override 了 Object 原来的 toString()办法,因而如果咱们应用内置的 List:

    public static void main(String[] args ) {List<Integer> a = new ArrayList<>();
        a.add(1);
        a.add(2);
        a.add(3);
        System.out.println(a);
    }

就会打印出赏心悦目的后果:

[1, 2, 3]

咱们的指标是,让咱们本人写的 ArraySet 也打印出这种丑陋的后果,也就是咱们须要 Override 本来的 toString()
先回顾一下咱们的 ArraySet:

public class ArraySet<T> implements Iterable<T> {private T[] items;
    private int size; // the next item to be added will be at position size

    public ArraySet() {items = (T[]) new Object[100];
        size = 0;
    }

    /* Returns true if this map contains a mapping for the specified key.
     */
    public boolean contains(T x) {for (int i = 0; i < size; i += 1) {if (items[i].equals(x)) {return true;}
        }
        return false;
    }

    /* Associates the specified value with the specified key in this map.
       Throws an IllegalArgumentException if the key is null. */
    public void add(T x) {if (x == null) {throw new IllegalArgumentException("can't add null");
        }
        if (contains(x)) {return;}
        items[size] = x;
        size += 1;
    }

    /* Returns the number of key-value mappings in this map. */
    public int size() {return size;}
    

    @Override
    public String toString() {/* hmmm */}


    @Override
    public boolean equals(Object other) {/* hmmm */}

    public static void main(String[] args) {ArraySet<Integer> aset = new ArraySet<>();
        aset.add(5);
        aset.add(23);
        aset.add(42);
        
        //toString
        System.out.println(aset);
        
    }

解决方案:

@Override
public String toString() {
    String returnString = "{";
    for (int i = 0; i < size; i += 1) {returnString += keys[i];
        returnString += ",";
    }
    returnString += "}";
    return returnString;
}

这样的 toString()尽管能实现咱们的指标,然而效率上非常低,这是因为 Java 中,String 是不变量(还记得不变量吗?回顾一下以前的笔记), 因而当每次执行字符串拼接时:

returnString += keys[i];

Java 都是从头创立一个新的正本,而后再将字符拼接,最初返回该正本,因而,假如拼接一个字符须要 1s,对字符串 ”{1 2 3}” 进行拼接,则须要

  • {(1s)
  • {+ 1 = { 1 (2s)
  • {+ 1 + 2 = { 1 2 (3s)
  • {+ 1 + 2 + 3 = {1 2 3 (4s)
  • {+ 1 + 3 +} = {1 2 3} (5s)

可见每次拼接都是从头开始,上述过程的总耗时为 1 + 2 + 3 + 4 + 5 = 15s
如果一个字符串的长度为 n,则以下面的算法拼接字符串的工夫复杂度是 O(1 + 2 + 3 + 4 +……+ n-1 + n) = O(n(n-1)/2),即 O(n²)
如何让字符串拼接变得更加高效?

为了解决这个问题,Java 有一个非凡的类,称为 StringBuilder. 它创立一个可变的字符串对象,因而您能够持续追加到同一个字符串对象上,而不是每次都创立一个新对象。
应用 StringBuilder 重写 toString()办法:

public String toString() {StringBuilder returnSB = new StringBuilder("{");
        for (int i = 0; i < size - 1; i += 1) {returnSB.append(items[i].toString());
            returnSB.append(",");
        }
        returnSB.append(items[size - 1]);
        returnSB.append("}");
        return returnSB.toString();}

此算法的工夫复杂度是线性的,O(n)


2.equals() vs. ==

后面咱们有提到 Object.equals(Object o)办法是比拟两个 Object 的值,而 Java 中 == 则是比拟二者的内存地址是否雷同,或者说是否指向同一 Object
举例:


因而当前如果要比拟两个 Object 通常意义下的相等,请应用.equals()
然而事实上,Java 的 Object.equals()办法默认是与 == 等效的 ,在内置的 Java.util 的 Set,Map,List 均 Override 了本来的 equals(),因而咱们应用起来才没问题
如果咱们一成不变地应用默认的 equals()办法,同样会相当于应用 == 判断两个内存地址不同的 Object,从而返回 false:

因而咱们的指标是 Override 默认的 equals()办法,使之实用于咱们的 ArraySet
请留神 List 是有序元素汇合,Set 是无序元素汇合。因而,如果咱们须要重写自定义的 ArrayList 的 equals(),须要逐项比拟两个 List 中的元素是否程序雷同且元素值相等
而 Set 是惟一元素的无序汇合。因而,要将两个汇合视为相等,您只须要查看它们之间是否互相蕴含雷同的元素。
解决方案:

@Override
public boolean equals(Object other) {if (this == other) {return true;}
        if (other == null) {return false;}
        if (other.getClass() != this.getClass()) {return false;}
        ArraySet<T> o = (ArraySet<T>) other;
        if (o.size() != this.size()) {return false;}
        for (T item : this) {if (!o.contains(item)) {return false;}
        }
        return true;
    }

getClass()办法是返回以后 Object 的 class type,例如 this.getClass() = ArraySet.class
总结:


3.Even Better toString() and ArraySet.of()

String.join()

实现字符串拼接还有一种办法,应用 String.join()

@Override
public String toString() {List<String> lisOfItems = new ArrayList<>();
    for(T x : this) {lisOfItems.add(x.toString());
    }
    return "{" + String.join(",", lisOfItems) + "}";
}

.Of()

回顾一下以前做过的 lab,咱们以 .Of()形式创立一个 List,或者 Set,例如:

Set<Integer> javaset = Set.of(5, 23, 42);

就能创立一个 Set,其内容为 {5,23,42},十分的不便
因而,咱们想实现属于本人的.Of()办法,使之可能达到下面的成果
解决方案:

public static <Glerp> ArraySet<Glerp> of(Glerp... stuff) {ArraySet<Glerp> returnSet = new ArraySet<>();
    for (Glerp x : stuff) {returnSet.add(x);
    }
    return returnSet;
}

语法点:

  • 泛型办法,在办法返回类型前加 <T>,后面笔记有介绍
  • Varargs: 可变长度参数

在 Java 5 中提供了变长参数,容许在调用办法时传入不定长度的参数。变长参数是 Java 的一个语法糖,实质上还是基于数组的实现,Java 的可变参数,会被编译器转型为一个数组

void foo(String... args);
void foo(String[] args);

更多可见 this link

正文完
 0