(in the version after 2020, the ArrayMap written in 2018 is changed to ArraySet, so the following case is based on ArraySet)
All classes implicitly inherit Object and contain the following methods of 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)
In this lesson, we will focus on the first two methods
1.toString()
Every time you call system out. When println (x), no matter what the original type of X is, it will be implicitly converted, 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);
The default toString() method of Object converts the following format:
classname @ hex_memory Class name@Hexadecimal memory address
Therefore, if we print the ArraySet written by ourselves, the result is:
$ java ArraySetPrintDemo ArraySet@75412c2f
But Java Util's built-in List, set and map Override the original toString() method of Object. Therefore, if we use the built-in 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); }
It will print out pleasing results:
[1, 2, 3]
Our goal is to make our own ArraySet print this beautiful result, that is, we need to Override the original toString()
Let's review our 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); }
Solution:
@Override public String toString() { String returnString = "{"; for (int i = 0; i < size; i += 1) { returnString += keys[i]; returnString += ", "; } returnString += "}"; return returnString; }
Although such toString() can achieve our goal, it is very inefficient. This is because String is an invariant in Java (remember the invariant? Review the previous notes). Therefore, when String splicing is performed each time:
returnString += keys[i];
Java creates a new copy from scratch, then splices the characters, and finally returns the copy. Therefore, assuming that it takes 1s to splice a character, it needs 1s to splice the string "{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)
It can be seen that each splicing starts from scratch, and the total time of the above process is 1 + 2 + 3 + 4 + 5 = 15s
If the length of a string is n, the time complexity of splicing strings with the above algorithm is O (1 + 2 + 3 + 4 +... + n-1 + n) = O(n (n-1) / 2), that is, O(n) ²)
How to make string splicing more efficient?
To solve this problem, Java has a special class called StringBuilder It creates a variable string object, so you can continue to append to the same string object instead of creating a new object every time.
Override the toString() method with StringBuilder:
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(); }
The time complexity of this algorithm is linear, O(n)
2.equals() vs. ==
We mentioned Object The equals (Object o) method is used to compare the values of two objects, while in Java = = is used to compare whether their memory addresses are the same or point to the same Object
give an example:
Therefore, if you want to compare the equality of two objects in the normal sense in the future, use equals()
But in fact, Java's object By default, the equals () method is equivalent to = = in the built-in Java Util's set, map and list Override the original equals(), so we have no problem using them
If we use the default equals() method intact, it will also be equivalent to using = = to judge two objects with different memory addresses, thus returning false:
Therefore, our goal is to Override the default equals() method and make it applicable to our ArraySet
Note that List is an ordered Set of elements and Set is an unordered Set of elements. Therefore, if we need to override the equals() of the custom ArrayList, we need to compare whether the elements in the two lists are in the same order and have the same element values one by one
Set is an unordered collection of unique elements. Therefore, to treat two collections as equal, you only need to check whether they contain the same elements as each other.
Solution:
@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; }
The getClass () method returns the class type of the current Object, such as this getClass() = ArraySet. class
Summary:
3.Even Better toString() and ArraySet.of()
String.join()
There is another way to realize string splicing, using String.join()
@Override public String toString() { List<String> lisOfItems = new ArrayList<>(); for(T x : this) { lisOfItems.add(x.toString()); } return "{" + String.join(",", lisOfItems) + "}"; }
.Of()
Recall the lab we have done before. Let's take it as an example Create a List or Set in the Of() method, for example:
Set<Integer> javaset = Set.of(5, 23, 42);
You can create a Set with {5,23,42}, which is very convenient
Therefore, we want to realize our own Of() method to achieve the above effect
Solution:
public static <Glerp> ArraySet<Glerp> of(Glerp... stuff) { ArraySet<Glerp> returnSet = new ArraySet<>(); for (Glerp x : stuff) { returnSet.add(x); } return returnSet; }
Syntax points:
- For generic methods, add < T > before the return type of the method, as described in the previous notes
- Varargs: ... Variable length parameter
Variable length parameters are provided in Java 5, which allows variable length parameters to be passed in when calling methods. Variable length parameter is a syntax sugar of Java. It is essentially an array based implementation. Java variable parameters will be transformed into an array by the compiler
void foo(String... args); void foo(String[] args);
More visible this link