(在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
发表回复