Video of CS61B on oil pipe
Learning code
Follow up
- Java value transfer assignment
- For recursion, the most important are base case and neighbor
- For the recursion of list, try to judge the list itself. List iterates several times instead of its list rest
- For the iteration of List, try to use iterators instead of this keyword
- Static methods can only change static variables because they follow the golden rule.
- java.util.Arrays.equals compares whether the contents of all items in two arrays are equal.
- For an SLList, the address of the SLList is the address of sentinel, which is used to operate the whole packaging list.
Hello World
- All code in Java must be written in Class
- . Java - > compile (javac) - > Class - > interpreter - > run code
Defining and Using Classes
- When the DogLauncher is manually compiled, java will automatically compile the Dog, because the DogLauncher needs to have a Dog dependency to continue running
- Declaration only, not instantiation
- Only instantiation but no object assignment cannot be used. It will be directly garbage collected by java
- Assign an instantiated dog to a declared variable
References, Recursion, and Lists
-
205.75 is stored in 32 digits. The first 16 digits are before and after the decimal point, and the last 16 digits are after the decimal point
-
-
Create objects for declaration and new
Java List linked list representation
Original List creation
- IntList memory visualization
- Create a linked list from the beginning to the end.
List creation with constructor
- IntList memory visualization
- The reverse list is created from the end, which is easier to write
Get the length of the List (l.size) (recursive method)
-
- Recursive visualization of size method
- Find the null s in IntList through multiple recursions, and stack them in turn when returning
Get the length of the List (l.size) (iterative method)
-
In iterative size, p can be obtained directly through the pointer this of l. I haven't seen this writing method for a long time
Summary exercise
- Iteration is better in terms of efficiency, but recursive code is more concise
-Memory diagram of recursive writing method of get
- Pay attention to learn how to write recursion. Pay attention to base case and neighbor. Just think of beighor as base case.
exercises
- Visualization of two functions
- Mainly the use of iteration in the List
- The iteration should aim at the current list instead of the list rest
- Do not use this keyword in the iteration of List
Wrapper class for SLList
- The purpose of designing LIst wrapper class is to hide the object, and the corresponding storage content can be found only through index.
addFirst() and getFirst()
- A very important point is to use addFirst and getFirst to fill the list and get the first content of the list
- Visualization of the above two methods
Safety and sealing
- The security of SLList is improved by embedding IntNode into SLList for packaging and setting it private and static to deny external direct access
public class MySLList { // When the outside world does not need attention and needs protection, it needs to be set as private // Nested classes can be set to static when the outside world does not need to operate on them // Think of static as never looks forwards private static class MyIntNode { public int item; public MyIntNode next; public MyIntNode(int i, MyIntNode n) { item = i; next = n; } } private MyIntNode first; public MySLList(int x) { first = new MyIntNode(x, null); } /** * Returns the first item in the list */ public int getFirst(){ return first.item; } /** * Add an int to the top of the list */ public void addFirst(int x){ first = new MyIntNode(x, first); } public static void main(String[] args) { MySLList L = new MySLList(10); L.addFirst(15); } }
addLast() and size()
- The visual size iteration method of the two methods is also in it
- If the wrapped class wants to realize recursion, it needs to establish a private and non-public method to replace the original method for recursive operation, and use its input parameters for recursive operation
- Personally, I think it will be easier to iterate after packaging, but we should understand the idea that recursion needs to establish private static classes.
Cache
- The complexity of using recursion to obtain size is too high, so the size member variable is introduced to cache. Each time you add, you only need to operate size, and only need to return this when you get Size can greatly reduce the consumption of time
Empty List
- The main problem to be solved is that after the empty structure exists, the object of the first node may be null, and additional if statements are needed for conditional judgment. As shown below, the sentinel node is introduced.
- Memory visualization
/** * @description: * @program: CS61B * @author: paleatta * @date: 2021-05-18 09:38 * @version: jdk1.8 **/ public class MySLList { // When the outside world does not need attention and needs protection, it needs to be set as private // Nested classes can be set to static when the outside world does not need to operate on them // Think of static as never looks forwards private static class MyIntNode { public int item; public MyIntNode next; public MyIntNode(int i, MyIntNode n) { item = i; next = n; } } private MyIntNode first; private int size; public MySLList() { first = null; size = 0; } public MySLList(int x) { first = new MyIntNode(x, null); size = 0; } /** * Add an int to the top of the list */ public void addFirst(int x) { first = new MyIntNode(x, first); size++; } /** * iteration * Add an int to the end of the list */ public void addLast(int x) { if (first == null){ first = new MyIntNode(x, null); return ; } MyIntNode p = first; while (p.next != null) { p = p.next; } p.next = new MyIntNode(x, null); size++; } /** * Reduce time consumption through caching */ public int cacheSize() { return size; } public static void main(String[] args) { MySLList L1 = new MySLList(); L1.addFirst(10); L1.addFirst(5); L1.addFirst(20); int size = L1.cacheSize(); System.out.println(size); } }
Sentinel Node
- For the SLList, the sentinel node is used as the node of the first List. It is used in the null structure to set the next of the sentinel node to null instead of directly setting the first to null, so as to avoid the problem of starting the null pointer. It should also be one of the first useless pointers in the List at present.
- Visualization with sentinel nodes
/** * @description: * @program: CS61B * @author: paleatta * @date: 2021-05-18 09:38 * @version: jdk1.8 **/ public class MySLList { // When the outside world does not need attention and needs protection, it needs to be set as private // Nested classes can be set to static when the outside world does not need to operate on them // Think of static as never looks forwards private static class MyIntNode { public int item; public MyIntNode next; public MyIntNode(int i, MyIntNode n) { item = i; next = n; } } private MyIntNode sentinel; private int size; public MySLList() { //The - 1 here is optional. You can take it freely because you can't use it sentinel = new MyIntNode(-1, null); size = 0; } public MySLList(int x) { sentinel = new MyIntNode(x, null); size = 1; } /** * Returns the first item in the list */ public int getFirst() { return sentinel.next.item; } /** * Add an int to the top of the list */ public void addFirst(int x) { sentinel.next = new MyIntNode(x, sentinel.next); size++; } /** * iteration * Add an int to the end of the list */ public void addLast(int x) { MyIntNode p = sentinel.next; while (p.next != null) { p = p.next; } p.next = new MyIntNode(x, null); size++; } /** * Reduce time consumption through caching */ public int cacheSize() { return size; } /** * Teacher's version * It is mainly caused by the fact that SLList itself is not a MyIntNode class * Unable to enter recursion, so you wrote a private function that is not open to the outside world * Recursion with the of its parameters */ public int size() { return size(sentinel.next); } private static int size(MyIntNode p) { if (p.next == null) return 1; return size(p.next) + 1; } /** * recursion * Returns the length of the SLList */ public int MySize() { int size = 0; MyIntNode p = sentinel.next; while (p != null) { size++; p = p.next; } return size; } public static void main(String[] args) { // MySLList L = new MySLList(15); // L.addFirst(10); // L.addFirst(5); // L.addFirst(20); MySLList L1 = new MySLList(); L1.addFirst(10); L1.addFirst(5); L1.addFirst(20); int size = L1.size(); System.out.println(size); } }
- Note in the sentry node:
- In the original SLList, first is used as the first node of the linked list, but the first node here is not sentinal, but sentinal Next, the sentinel node itself is not a member of the linked list, but just the beginning. It is generated when null appears in the placement, and its item has no effect
- As long as an SLList is generated, its sentinel address will not change, so it can be set to final
Invariants
DLList
introduce
- For the original one-way list, it is complex to iterate the whole linked list when adding, updating or deleting the Last bit or a bit in the list. When updating or adding the Last node, although it can be accelerated by adding the Last cache, other operations still cannot be optimized.
private final MyIntNode sentinel; private MyIntNode last; private int size;
- Therefore, the bidirectional linked list DLList is introduced
Doubly Linked Lists
- Because it is a two-way linked list, at the beginning, the next at the beginning and end of the list may be null or sentinel node. To solve this problem:
- - When introducing a two-way linked list, we should pay attention to the problem of the last node. There are the above two schemes
- Like the previous sentinel node, add a sentinel node at the end of the linked list.
- The next and sentinel of the last sentinel node Next is connected and becomes a circular linked list.
Generic (generic)
- In the past, the list we built can only be used for int types, but there are many data types, so we introduce generics to make it more general
public class MySLList<Generic> { // When the outside world does not need attention and needs protection, it needs to be set as private // Nested classes can be set to static when the outside world does not need to operate on them // Think of static as never looks forwards private class MyGenericNode { public Generic item; public MyGenericNode next; public MyGenericNode(Generic i, MyGenericNode n) { item = i; next = n; } } private final MyGenericNode sentinel; private MyGenericNode last; private int size; }
array Overview
- The array has a length, but the array has no method. It has only one reference when it is created.
- 2D Arrays
Memory display of two-dimensional array
- array vs class
- For array, it is only a continuous storage space, and it needs serial number to find the storage content
- For class, it is stored after a strong encoding without reflection. It can only operate on attributes, not indexes
They are all reference types, and their storage type in the stack is a 32-bit reference address.
- java vs other language
- Compared with arrays in other languages, Java arrays:
There is no special syntax for "slicing" (for example, in Python).
Cannot shrink or expand (for example, in Ruby).
There are no member methods (such as Javascript).
Must contain only values of the same type (unlike Python).
AList
Why Use Array
- It is difficult to find the corresponding objects in the list by index. One of the solutions is to implement it with the help of the structure of array.
Alist
- The most basic version of Alist
public class MyAList { private int[] items; private int size; public MyAList() { items = new int[100]; size = 0; } //When added to the end, the default capacity expansion is 100 public void addLast(int item) { items[size] = item; size++; } public int getLast() { return items[size - 1]; } public int get(int index) { return items[index]; } public int size() { return size; } }
- removeLast deletes an item
- In alias, the length of the array is determined by Size. Although the last item has not been modified, it has been ignored during use and will be overwritten the next time addLast is used. Therefore, it can be used interchangeably. It can also design a logical deletion bit like in the comment, but it is not necessary.
/** * removeLast() */ public int removeLast() { int x = items[size]; //items[size - 1] = 0; size--; return x; }
- Capacity expansion of Alist
- The length of the array itself is fixed in memory. It seems that the length is changing. We treat size as the length of alias.
- When the length of the array is insufficient, it needs to be expanded and supplemented.
- I think there is a code style that writes two overloaded functions, one with parameters and one without parameters. The default configuration is used for those without parameters, and other calls with complete parameters. Like this, I don't know if this is a good habit.
- I added an array myself and haven't tested it yet.
/** * Default capacity expansion 100 */ private void resize() { resize(size + 100); } /** * Adjust capacity to target capacity * @param capacity */ private void resize(int capacity) { int[] temp = new int[capacity]; System.arraycopy(items, 0, temp, 0, size); items = temp; } //When added to the end, the default capacity expansion is 100 public void addLast(int item) { if (size >= items.length) resize(); items[size] = item; size++; } //Add array public void addLast(int[] is) { if (size + is.length >= items.length) resize(size + is.length); // items[size] = item; System.arraycopy(is, 0, items, size, is.length); size += is.length; }
The rate of resize
-
Each time the array size limit is reached, the array size is adjusted. This is very slow because copying the array to the new array means that we have to copy each item. Worst of all, since we have only resized one extra box, if we choose to add another item, we must do it again each time we add it to the array.
-
Compared with LinkedList, AList needs to expand and copy every time, which takes more time. For LinkedList, more time is spent on operating pointers, which is less complex and takes less time.
-
Instead of adding an additional box, we can create a new array containing the size * FACTOR item, where the FACTOR can be any number, such as 2. We will discuss why this is done quickly later in this course.
-
The original expansion is that + size is linear. Now * is geometric growth, which greatly reduces the time
private void resize(int capacity) { int[] temp = new int[capacity]; System.arraycopy(items, 0, temp, 0, size); items = temp; }
Generic alias
- Convert the intelligent storage integer type into an array that can store general types.
Pay attention to the following points
- java does not allow the creation of Generic array objects, so you can only create object [] and force its type into Generic []
- removeLast() does not operate on the bit of size that we think is exceeded when the int type is the only one. However, in Generic, null the exceeded position allows the GC to recycle the garbage of its reference object in the heap and save space
public class MyAList<Generic> { private Generic[] items; private int size; public MyAList() { items = (Generic[]) new Object[100]; size = 0; } /** * Default capacity expansion 100 */ private void resize() { resize(size * 2); } /** * Adjust capacity to target capacity * * @param capacity */ private void resize(int capacity) { Generic[] temp = (Generic[]) new Object[capacity]; System.arraycopy(items, 0, temp, 0, size); items = temp; } //When added to the end, the default capacity expansion is 100 public void addLast(Generic item) { if (size >= items.length) resize(); items[size] = item; size++; } //Add array public void add(Generic[] is) { if (size + is.length >= items.length) resize(size + is.length); // items[size] = item; System.arraycopy(is, 0, items, size, is.length); size += is.length; } public Generic getLast() { return items[size - 1]; } public Generic get(int index) { return items[index]; } public int size() { return size; } /** * removeLast() */ public Generic removeLast() { Generic returnItem = items[size]; items[size] = null; size--; return returnItem; } }
Testing
JUnit
import static org.junit.Assert.*; //Assertion judgment on two arrays //If the two arrays are the same, then //If not, an exception is returned assertArrayEquals(expected, input);
- The effect is the same as below
Selection Sort
- The steps of writing and testing for sorting are as follows
public class MySort { /** * There are three steps to realize selection sorting * 1 Find the smallest item * 2 Move it to the front * 3 Repeat steps 1 and 2 (using recursion?) * @param x */ //The external sorting interface is actually implemented by calling the sort overloaded method public static void sort(String[] x ){ sort(x,0); } private static void sort(String[] x ,int start) { if (start == x.length) return; /*1. Find the smallest project*/ int smallest = findSmallest(x , start); /*2. Move it to the front*/ swap(x,start,smallest); /*3. Recursively sort the rest*/ sort(x,start+1); } /*1. Find the smallest project, starting with start*/ private static int findSmallest(String[] x , int start) { int smallestIndex = start; for (int i = start; i < x.length; i++) { int flag = x[smallestIndex].compareTo(x[i]); if (flag > 0) smallestIndex = i; } return smallestIndex; } /*2. Swap two specified item s*/ private static void swap(String[] x, int a, int b) { String temp = x[a]; x[a] = x[b]; x[b] = temp; } }
Inheritance, Implements
Inheritance and overloading
- Both have their own uses
- For inheritance, it is easier to use when there is a parent-child relationship. For example, List and SLList in the class implement the method in List (one of inheritance), which can reduce the code of writing overloaded functions. It looks more beautiful and logically more comfortable.
- Overloading is a method with the same function name that distinguishes between two methods according to different signatures. Overloading mainly emphasizes different parameters, while inheritance mainly emphasizes the parent-child relationship.
- Inheritance is generally a subclass of implementation or inheritance. After creating an object in a subclass, override the function of the parent class by calling a method.
//Although List is the parent class used in the declaration, it is actually an object that calls the subclass. Therefore, the declaration of the parent class is used, but the method of the subclass is used. // Here, you can associate it with the knowledge of java stack, which is easier to understand. List<String> someSLList = new SLList<>(); List<String> someAList = new AList<>();
default Methods and override default method
- You can rewrite the interface downward through default, which means that the print method of the interface will be called in both the list object and the SLList object
public interface List<Item> { default public void print(){ for (int i = 0; i < size(); i++) { System.out.print(get(i)); } System.out.println(); } } public static void main(String[] args) { List<String> someSLList = new SLList<>(); someSLList.addFirst("elk1"); someSLList.addLast("dwell"); someSLList.addLast("on"); someSLList.addLast("existential"); someSLList.addLast("crises"); someSLList.print(); List<String> someAList = new AList<>(); someAList.addFirst("elk2"); someAList.addLast("dwell2"); someAList.addLast("on"); someAList.addLast("existential"); someAList.addLast("crises"); someAList.print(); }
- The output is like this (don't pay attention elk2 no, this is a small problem)
- Since this writing is inefficient for SLList print, it is necessary to rewrite the print method in SLList.
@Override public void print() { System.out.println("THIS IS THE OVERRIDDEN VERSION."); Node p = sentinel.next; while (p != null) { System.out.print(p.item + " "); p = p.next; } }
public static void main(String[] args) { List<String> someSLList = new SLList<>(); someSLList.addFirst("elk1"); someSLList.addLast("dwell"); someSLList.addLast("on"); someSLList.addLast("existential"); someSLList.addLast("crises"); someSLList.print(); List<String> someAList = new AList<>(); someAList.addFirst("elk2"); someAList.addLast("dwell"); someAList.addLast("on"); someAList.addLast("existential"); someAList.addLast("crises"); someAList.print(); }
- The print method is not written in list, so the statement will not be printed in list
- Therefore, I think that after the print method is written in the SLList, the default keyword will become invalid. Although it is declared in the List, it does not take the method of the parent class, and only depends on the method of the inherited object.
Dynamic Methods Selection and Overrided
- In the java language, it includes static types and dynamic types. Static type is a type of life, which cannot be changed after its declaration. The dynamic type is created at the time of new. It is a dynamic type and can receive types inherited from it. When calling later, it is to call the object (dynamic) method. A dynamic type is a runtime type.
- greet exists only in the parent class. Sniff exists in the parent class, but because the child class also has sniff and the signature is the same, it is overridden in the Dog class. In the flag method, because its signature is different, it is not rewritten and can be treated as an overloaded function. The input parameter of the parent class is Animal type and the input parameter of Dog is Dog type.
Conclusion
- For inheritance, the most important thing is to realize the relationship between classes, not what methods a class uses. However, the interface can also facilitate the expansion of functions.
Extends
//Constructors can also be written like this private final SLList<Item> lostItems = new SLList<>(); //========================================= //You can not write Super. When compiling, the parameterless construction of the parent class will be automatically used. Do not write super(); all one to. public VengefulSLList() { // super(); lostItems = new SLList<>(); }
Inheritance breaking
- This. Is called when entering the bark method The method of barkMany, because the dynamic type is a subclass, will go through the rewritten method, so it will enter the rewritten barkMany method again, so it will enter the dead loop.
Conservative compilation and cast
- The compiler will be very conservative to use only declared static types for compilation judgment. Therefore, the dynamic type can pass through, but the compilation fails because this method does not exist in the static type. And during type conversion, the object of the parent class cannot be converted to the declared subclass type.
- Cast is a way for programmers to paralyze the compiler. Sometimes cast is from parent class to child class, which can be converted, but sometimes two unrelated classes cannot be converted, which is very dangerous. Therefore, casts need to be used with caution.
Method transfer
-
Method passing in Java usually uses calling object methods to obtain.
-
Call methods of subclasses and object methods by implementing interfaces. That is what we often call polymorphism.
Conlusion
Compiler rules
Compiler allows the memory box to hold any subtype.
Compiler allows calls based on static type.
Overriden non-static methods are selected at runtime based on dynamic type.
For overloaded methods, the method is selected at compile time.
- Should not appear in code
- Superclass and subclass have the same variable name.
- Superclass and subclass have the same static method with the same previous name. This is because override does not take effect, because override only takes effect for member functions.
Polymorphism of interface
- The essence of this is that although the declared static type has changed, its dynamic type (runtime type) has not changed, and it has always been a Dog type. Therefore, although its declaration type changes in the process of code, it can be converted back to Dog type.
- It feels more like that after Dog implements the Comparable interface, it can compare the objects of Dog class through the Comparable type.
Comparable benefits
- It is mainly to solve the problem of forced conversion of types and the error caused by the forced conversion of types that cannot be converted.
- You can reduce casts by adding generics
Comparator comparator
- The difference between Comparator and comparable is that Comparator generates a Comparator object to compare two objects. Comparable is to provide an object and compare it with itself.
- After the comparator is implemented, the comparison between two objects only needs to pass through the comparator. The comparison is realized by calling the method of the comparator.
- Generally speaking, the comparator is private, and most of them only obtain a comparator through the static method of the class.
public class MyDog implements Comparable<MyDog> { public String name; private int size; public MyDog(String name, int size) { this.name = name; this.size = size; } // @Override public int MyCompare(Object item) { MyDog dog = (MyDog) item; return size - dog.size; } @Override public int compareTo(MyDog dog) { return size - dog.size; } private static class NameComparator implements Comparator<MyDog> { @Override public int compare(MyDog o1, MyDog o2) { // compareTo here is not the above method // This is the built-in method after the Comparator is implemented return o1.name.compareTo(o2.name); } } public static Comparator<MyDog> getNameComparator(){ return new NameComparator(); } }
public class MyMaximizer { public static Comparable max(Comparable[] items){ int maxDex = 0; for (int i = 0; i < items.length; i += 1) { int cmp = items[i].compareTo(items[maxDex]); if (cmp > 0) { maxDex = i; } } return items[maxDex]; } }
public class MyDogLauncher { public static void main(String[] args) { MyDog d1 = new MyDog("Elyse", 3); MyDog d2 = new MyDog("Sture", 9); MyDog d3 = new MyDog("Benjamin", 15); MyDog[] myDogs = new MyDog[]{d1, d2, d3}; MyDog d = (MyDog) MyMaximizer.max(myDogs); System.out.println(d.name); Comparator<MyDog> nc = MyDog.getNameComparator(); int flag = nc.compare(d1, d2); System.out.println(flag); if (flag > 0) { System.out.println(d1.name); return; } System.out.println(d2.name); } }
Clonclusion
- There is no relationship between comparator and Comparable. The comparison can be realized by implementing Comparable. Comparator means that the special comparison between two objects can be realized.
- The function of the interface is equivalent to creating a callback function
List and Set in Java
Basic ArraySet
- Github has code in it. I won't copy here.
- For adding null at the beginning, it is a feasible strategy to throw it as an exception or store it as an object, as long as the document is well prepared.
iterable and iterator
- There are two necessary conditions for a class to implement an enhanced for loop
- This class implements Iterable, rewrites its related methods, and returns the iterator.
- The next method and hasnext method are rewritten in the iterator and made effective
- If you don't inherit Iterable but complete all the subsequent steps, you can use the iterator iterator for ugly iterations.
MyArraySet<Integer> aset = new MyArraySet<>(); .................................................................. Iterator<Integer> iterator = aset.iterator(); while (iterator.hasNext()) System.out.println(iterator.next());
- If you inherit Iterable, you can enhance the for loop
MyArraySet<Integer> aset = new MyArraySet<>(); .................................................................. //iteration for (int i : aset) { System.out.println(i); }
- If you hit the breakpoint, you will find that the start enhanced for loop is also a walking iterator. stay
for (int i : aset) { //First, call public iterator < T > iterator() {} to get the iterator //Call the hasNext() method to check whether it is true //If true, call iterator Next(), move the index to i+1 and return the ith item //false jumps out of the loop System.out.println(i); }
- In fact, iterable and iterator are similar to Comparable and Comparator.
- Privately obtain the comparator or iterator, and carry out the operation related to itself by implementing the method of able. Especially in writing code, it has high similarity.
toString
- String memory waste serious memory
- StringBuffer memory is not wasted, thread safety and efficiency are low
- StringBuilder does not waste memory, thread is unsafe and efficient
- Generally, it is recommended to use StringBuilder and use StringBuffer when thread safety is required. Reduce the direct use of String and try to convert from StringBuilder or StringBuffer.
equals and==
- The default way of writing equals is to return a Boolean value only by judging "= =".
public boolean equals(Object obj) { return (this == obj); }
- Note that the types of T and this are different. Why can they succeed
- Because the iterator will enter the iterator's hasNext() method to determine whether to continue execution. And its own T is independent of the type of this
//Code for equals @Override public boolean equals(Object other) { if (other == this) return true; if (other == null) return false; if (other.getClass() != this.getClass()) return false; MyArraySet<T> o = (MyArraySet<T>) other; if (o.size != this.size) return false; //Note that the types of T and this are different here. Why can they succeed for (T item : this) { if (!o.contains(item)) return false; } return true; }
Do better about String
- Check it, string Join is to apply for all memory at one time, so the efficiency will be much higher than "+".
@Override public String toString() { List<String> stringItems = new ArrayList<>(); for (T x : this) { stringItems.add(x.toString()); } return "{"+String.join(",", stringItems)+"}"; }