Walrus a = new Walrus(1000, 8.3);
Walrus b;
b = a;
b.weight = 5;
System.out.println(a);
System.out.println(b);
对b的改变会影响a吗?–会。
因为new Walrus(1000, 8.3)新建了一个对象,将其赋值给a即是令a的内存空间指向该对象。而b=a则是将a的内存空间的内容复制给b的内存空间,也就是在b的内存空间中填充指向Walrus的地址。当使用b.weight=5改变该对象的实例变量后,a还指向该对象,因此也会改变a.weight的值。
Java的八种原始类型: byte, short, int, long, float, double, boolean, char。当声明某个类型的变量时,Java将找到一个连续的块,该块的位数恰好足以容纳该类型的东西。例如,如果声明一个int,则会得到一个32位的块。如果声明一个字节,则会得到一个8位的块。块内存储数据本身。
其他所有内容(包括数组、链表、对象等)都不是原始类型,而是reference type。在声明这些reference type时,声明的变量存储的是指向这些对象的地址。无论对象是什么类型,Java都会分配一个64位的框,用来存地址。
String s
:声明变量,为s分配64位存储地址的空间。(不写入内容,也不创建对象)
s = new String;
:创建对象,为一个空字符串分配空间;对s进行初始化。
String s = new String;
:声明;创建对象;初始化
注意: 每个变量都必须声明类型和初始化。
可以将new视为返回创建的新对象的地址。
当我们使用new 关键字创建类的实例时,Java为每个字段创建位框,其中每个框的大小由每个字段的类型定义。例如,如果一个Walrus对象具有一个 int变量和一个double变量,则Java将分配两个框,总计96位(32 + 64)以容纳两个变量。这些将设置为默认值,例如0。然后,构造函数进入并将这些位填充为适当的值。构造函数的返回值将返回盒子所在的内存位置,通常是64位地址。然后可以将该地址存储在类型为“reference type”的变量中。
Ps.如果丢失与地址对应的bits,则对象可能会丢失。例如,如果特定对象的地址的唯一副本存储在中x,对x进行重新赋值时,如x = null,则将导致永久丢失该对象。这不一定是一件坏事,因为经常会决定对一个对象已完成操作,因此简单地丢弃地址是安全的。
方法传参时,就是copy bits,也就是把主函数中要传的参数bits完全复制给方法参数。(copy bits就是pass by value,在Java中只pass by value)
练习:
public class PassByValueFigure {
public static void main(String[] args) {
Walrus walrus = new Walrus(3500, 10.5);
int x = 9;
doStuff(walrus, x);
System.out.println(walrus);
System.out.println(x);
}
public static void doStuff(Walrus W, int x) {
W.weight = W.weight - 100;
x = x - 5;
}
}
调用的函数doStuff是否会对walrus和x产生影响?
—会改变walrus的值,但不会影响x。
y = x copies all the bits from x to y.
int[] x = new int[]{0, 1, 2, 95, 4};
int[] x
为声明,new int[]{0, 1, 2, 95, 4}
为初始化
注意:数组的大小是在创建数组时指定的,不能更改!
这一节实现了链表的基本构建。(最初级的方法)
事实证明,实现一个非常基本的链表很简单,如下所示:
public class IntList {
public int first;
public IntList rest;
/** 构造函数 */
public IntList(int f, IntList r) {
first = f;
rest = r;
}
}
这样的列表很难使用。例如,如果我们要列出数字5、10和15,则可以执行以下操作:
IntList L = new IntList(5, null);
L.rest = new IntList(10, null);
L.rest.rest = new IntList(15, null);
或者,我们可以向后构建列表,从而产生稍微更好但更难理解的代码:
IntList L = new IntList(15, null);
L = new IntList(10, L);
L = new IntList(5, L);
注意: 代码是从右往左执行的,所以将L赋值给new IntList的rest的操作 在 改变L存储的地址之前。
虽然原则上可以使用IntList来存储任何整数列表,但是生成的代码非常难看并且容易出错。
两种方法:递归(recursion)和迭代(iteration)
迭代:需要指针p
/** Return the size of the list using iteration! */
public int iterativeSize() {
IntList p = this;
int totalSize = 0;
while (p != null) {
totalSize += 1;
p = p.rest;
}
return totalSize;
}
递归:需要basecase,即无法进行迭代的情况
/** Return the size of the list using... recursion! */
public int size() {
//basecase
if (rest == null) {
return 1;
}
return 1 + this.rest.size();
}
练习:您可能想知道为什么我们不做类似的事情if (this == null) return 0;。为什么不起作用?
答案:想想当call size() 时会发生什么。您正在一个对象上调用它,例如L.size() 。如果L为null,则将出现NullPointer错误!
为什么???
递归:
public int get(int i){
//basecase
if(i==0) return first;
return this.rest.get(i-1);
}
这一节改进、实现了单向链表的构建。
IntList L = new IntList(5, null);
,不能隐藏null等IntList的实现细节。不好看且不好用。—改进1、改进2剩下的改进4、5、6是在完成前三个改进的基础上进行的改进。
原IntList构建方法:
public class IntList {
public int first;
public IntList rest;
public IntList(int f, IntList r) {
first = f;
rest = r;
}
...
重命名并丢弃辅助方法。现IntNode:
public class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
直接调用naked list不好看也不好用,给naked list穿一个衣服:用SLList当做main函数与IntNode之间的中间人。
public class SLList {
public IntNode first;
public SLList(int x) {
first = new IntNode(x, null);
}
}
/** Adds an item to the front of the list. */
public void addFirst(int x) {
first = new IntNode(x, first);
}
/** Retrieves the front item from the list. */
public int getFirst() {
return first.item;
}
SLList L = new SLList(15);
L.addFirst(10);
L.addFirst(5);
int x = L.getFirst();
IntList L = new IntList(15, null);
L = new IntList(10, L);
L = new IntList(5, L);
int x = L.first;
1 隐藏了Null指针、next指针等细节
2 更方便实用,不需要理解递归
private:
private变量和方法只能由本.java文件中的代码访问;其他类不能访问
public:
其他类可以使用。因此,必须让public属性的方法和变量永远保持可用和正常工作。否则其他类在使用时会出错。
SLList L = new SLList(15);
L.addFirst(10);
L.first.next.next = L.first.next;
嵌套类:将一个类放入另一个类中。如果一个类明显是另一个类的附属,可以作为嵌套类。
将嵌套类声明为static意味着静态类内部的方法无法也不必访问外部类的任何成员(方法与变量)。在这种情况下,这意味着在IntNode不能够访问first,addFirst或getFirst。
public class SLList {
public static class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
private IntNode first;
...
使用嵌套类能够节省内存,且使代码易读。
SLList不是递归的。因为它没有一个类型为SLList的属性。为了递归实现size(),需要一个private(因为只有size()方法会用到该辅助方法,外部类不需要访问)辅助方法来与naked recursive data structure(即IntNode)进行交互。
/** Returns the size of the list starting at IntNode p. */
private static int size(IntNode p) {
if (p.next == null) {
return 1;
}
return 1 + size(p.next);
}
public int size() {
return size(first);
}
ps. 名字一样但参数不同的方法称为overloaded。
之前的递归size()方法所消耗的时间与链表的数据长度成正比。改进size(),使其与链表的数据长度无关:
public class SLList {
... /* IntNode declaration omitted. */
private IntNode first;
private int size;
public SLList(int x) {
first = new IntNode(x, null);
size = 1;
}
public void addFirst(int x) {
first = new IntNode(x, first);
size += 1;
}
public int size() {
return size;
}
...
}
这种保存重要数据以加快检索速度的做法有时称为缓存。
重写构造函数:
public SLList() {
first = null;
size = 0;
}
使用迭代方法:
public void addLast(int x) {
size += 1;
IntNode p = first;
while (p.next != null) {
p = p.next;
}
p.next = new IntNode(x, null);
}
但是,当创建空链表时,因为p.next无法访问,addLast()会出现空指针错误。
if (first == null) {
first = new IntNode(x, null);
return;
}
但是,应该尽量避免使用特殊情况。虽然这里特殊情况很少,但是在其他数据结构中,有可能有很多特殊情况。
public class SLList {
public static class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
private IntNode sentinel;
private int size;
public SLList() {
sentinel = new (63, null)
size = 0;
}
public SLList(int x) {
sentinel = new IntNode(63, null);
sentinel.next = new IntNode(x, null);
size = 1;
}
public void addFirst(int x) {
sentinel.next = new IntNode(x, sentinel.next);
size += 1;
}
public int getFirst() {
return sentinel.next.item;
}
public int size() {
return size;
}
public void addLast(int x) {
size += 1;
IntNode p = sentinel;
while (p.next != null) {
p = p.next;
}
p.next = new IntNode(x, null);
}
}
这一节改进单向链表为双向链表。
解决方案2见改进7.
public class IntNode {
public IntNode prev;
public int item;
public IntNode next;
}
为了去掉DLList的特殊情况:
去掉了DLList的特殊情况—DLList的Last属性有时指向哨兵节点,有时指向真正的节点。
使链表中不仅可以存储整数,还可以是任意类型的数据。
泛型使用的法则:
public class DLList<T> {
private IntNode sentinel;
private int size;
public class IntNode {
public IntNode prev;
public T item;
public IntNode next;
...
}
...
}
DLList<String> d2 = new DLList<>("hello");
d2.addLast("world");
ps.DLList
也可以,但是实例化中的Integer是完全多余的。