【ing】CS61B Java 00 | Reading & Lecture

1. Intro

HelloWorld

public class HelloWorld {
	public static void main(String[] args) {
		System.out.println("Hello world");
	}
}

该程序由类声明构成,使用public class关键字声明,Java中所有的代码都在类中。
变量有声明的类型,也称为"静态类型 static type",变量必须声明后方能使用
Static Typing. 静态类型 使程序更易理解和推理代码

命令行编译和执行 javac用于编译程序。java用于执行程序。执行以前必须编译
客户端程序和main方法 Java程序若没有main方法则无法用java命令启动。程序中的方法也可以被其他类中的main方法调用,使用另一类的类1被称为类的客户端client

Objects

类声明 Java类包含方法和/或变量,称之类的成员。成员分为两种:(1)实例成员instance members、(2)静态成员static members
类实例化 通常使用new关键字实例化类,Java中实例化的类也称为对象“Object”
点符号 我们使用点符号访=访问类成员,类成员可以被同一类或其他类访问
一旦对象被实例化,就可以将它分配给对应声明类型的变量。

构造函数 告诉Java当程序创造类实例时应当做些什么。在面向对象的语言中,我们使用构造函数创建对象。new对象时就会调用构造函数。
数组实例化 数组也用new关键字实例化,e.g. int[] arr = new int[10]。如果我们有一个数组对象,e.g. Dog[] dogarray,那么数组中的每一个元素都必须单独实例化

Dog[] dogs = new Dog[2]; 
dogs[0] = new Dog(8);
dogs[1] = new Dog(20);
dogs[0].makeNoise();

静态方法 vs. 实例方法instantiate methods / 非静态方法 non-static methods 实例方法只能被对象使用,e.g. d.bark(),而静态方法由类本身执行的,由类名调用 e.g. Math.sqrt()。静态方法也可以被对象调用,为避免混淆,建议不用。
静态变量 一般由类名通过点符号访问
void方法 方法没有返回应设定void类型
this关键字 在方法中,我们用this关键字引用当前实例,相当于Python中的self。如在类的非静态方法(需要实例调用的实例方法中使用)

public static void main(String[] args) public: 目前我们所有的方法都是以public关键字开头,static:这是个静态方法,不与任何特定实例关联
命令行参数 操作系统可以将参数通过命令行参数提供给程序,并且可以通过main方法中的args参数访问。例如我们通过以下命令调用程序java ArgsDemo these are command line arguments, ArgsDemo类的main方法将获取包含一些列字符串的array数组参数
Using Libraries 使用库,如Java内置库

2 Lists

8个基本数据类型/基元 Primitives 4类:整型、浮点型、布尔型、字符型,byte, short, int, long, float, double, boolean, and char. 每个数据类型由一定数量的比特表示,例如ints 4字节 32比特,bytes 1字节 8比特
声明基元 i.e. int x; 我们流出足够的内存空间保存位, 这个32位
创建对象 使用new关键字创建类实例时,Java为每个字段field创建比特框,大小取决于字段类型,并设置初始默认值如0.构造函数。构造函数来将这些值填充到合适的值,构造函数返回值将返回盒子所在的内存地址,地址存储在引用类型变量中

2.1 Mystery of the Walrus

Exercises in online textbook 2. Lists > 2.1 Mystery of the Walrus
Predict what happens when we run the code below.
Does the change to b affect a? Yes, a & b store addresses.

Walrus a = new Walrus(1000, 8.3);
Walrus b;
b = a;
b.weight = 5;
System.out.println(a);
System.out.println(b);

Does the change to x affect y? No, x & y store objects.

int x = 5;
int y;
y = x;
x = 2;
System.out.println("x is: " + x);
System.out.println("y is: " + y);

引用类型 reference type 不是基本数据类型的变量,包括arrays。当声明对象变量时,使用引用类型变量存储对象所在的内存位置。引用类型始终是64位的框,构造函数返回引用类型变量,引用类型变量并不直接存储整个对象本身。
平等黄金准则GRoE(Golden Rule of Equals) i.e.int y = x 复制基元数据框和引用类型数据框
参数传递 遵循GRoE, 当我们调用方法并传递参数时,我们从变量复制比特到参数变量中
函数有自己的作用域,用参数名标记,参数按值传递“pass by value”,当函数改变了参数,不会影响参数传递前的变量。
Exercise 2.1.1: Call to doStuff have an effect on walrus and x.

Array数组实例化
Array Instantiation. 数组Arrays也是对象,使用new关键字实例化,创建数组array,并返回在内存中的位置。int[] x = new int[]{0, 1, 2, 3, 4}; 我们将新创建的数组array位置为变量x,属于引用变量。注意,数组array的大小是创建时指定的,不能修改。Python中的List可以改变,但Java中没有内置的List类型,我们创建自己的list。

IntLists

数组IntLists cannot be changed!

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 = new IntList(15, null);
// or
IntList L =  new IntList(15, null);
L = new IntList(10, L);
L = new IntList(5, L);

IntLists. 使用引用references定义,可以改变大小,用递归或迭代写size,可以存储任意大小的整数

// Return the size of the list using... recursion!
public int size() {
	if (rest == null) {
		return 1;
	}
	return 1 + this.rest.size();
}

递归的关键是你需要一个基本案例base case(rest is null,size is 1)。

// Return the size of the list using no recursion!
public int iterativeSize() {
	IntList p = this;
	int totalSize = 0;
	while (p != null) {
		totalSize += 1;
		p = p.rest;
	}
	return totalSize;
}

问题 i值越大,依次访问花费线性时间,费时越长

public int get(int i) {
	if (i == 0) {
		return first;
	}
	return rest.get(i - 1);
}

2.2 SLLists (Single Linked List)

Improvement #1: Rebranding

重新命名并丢弃辅助方法helper method

public class IntNode {
	public int item;
	public IntNode next;
	public IntNode(int i, IntNode n) {
		item = i;
		next = n;
}

Improvement #2: Bureaucracy

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 i) {
	first = new SLList(x, first);
	}

	public int getfist(int i) {
		return first.item;
	}
}
	pulic static void main(String[] args) {
	SLList L = new SLList(15);
	// IntList L1 = new IntList(5, null);
	L.addfirst(10);
	L.addfirst(5);
	System.out.println(L.getfirst(0));

在这里插入图片描述

Improvement #3: Public vs. Private

SLList 可以被绕过,直接访问原始裸数据结构,如

L.first.next.next = L.first.next;

这会导致有无限循环的畸形列表。为了解决这个问题,我们将SLList类中的first变量声明称private

public class SLList {
	private IntNode first;
...

private 变量和方法只能被同一.java文件中的代码访问。它所做的唯一一件事就是打断原本正在编译的程序。因此,在大型工程项目中,private代表了这段代码可以被终端用户所忽略,不用理解。同样,public表明一段代码一直可用。

Improvement #4: Nested Classes 嵌套类

把支持角色类嵌套入主类中。
如果被嵌套的类不需要使用主类的任何实例方法或变量,则声明为static,意味着静态类中的方法不能被主类中的成员访问。即,如果你不使用主类的任何实例,则被嵌套的类使用static静态声明

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;
	public SLList(int x) {
		first = new IntNode(x, null);
	}

通过循环构建addLast。缺点:很慢,添加一位需要遍历整个列表

public void addLast(int x) {
	IntNode p = first;
	while (p.next != null) {
		p = p.next;
	}
	p.next = IntNode(x, null);
}

通过递归构建size。缺点:慢,遍历整个列表

// 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);
}

我们称具有相同名称但不同签名的两个方法被重载overloaded

Improvement #5: Caching

size()花费时间是线性的,重写使无论大小多少,时间恒定。
Caching缓存 保存重要数据以加快检索

public class SLList {
	... // InNode declaration ommitted.
	private IntNode first;
	private int size;
	public SLList(int x) {
		first = new IntNode(x, null);
	}
	public int size() {
		return size;
	}
	... 

Improvement #6: The Empty List

SLList可以轻松创建空列表构造函数

public SLList() {
	first = null;
	size = 0;
}

但是,向空指针addLast会导致空指针异常
Sentinel Nodes 哨兵节点
在这里插入图片描述

public class SLList<LochNess> {
    private class StuffNode { //嵌套类表明节点
        public LochNess item;
        public StuffNode next;

        public StuffNode(LochNess i, StuffNode n) {
        item = i;
        next = n;
        }
    }
    
    private StuffNode first;
    private int size;

    private SLList(LochNexx x) { // 结构函数创建空列表或第一个节点
        first = new StuffNode(x, null);
        size = 1;
    }

    public void addFirst(LochNess x) {
        first = new StuffNode(x, first);
        size += 1;
    }
    
    public void addLast(LochNess x) {
        size += 1;
        StuffNode p fist;
        while (p.next != null) {
            p = p.next;
        }
        p.next = new StuffNode(x, null);
    }
    
    public int size() {
    	return size;
    }
}

2.3 DLLists (Doubly Linked List)

SLLists的缺点,慢。添加一个last变量以提高效率

public class SLList {
	private IntNode sentinel;
	private IntNode last;
	private int size;

	public void addLast(int x) {
	last.next = new IntNode(x, null);
	last = last.next;
	size += 1;
	}
	...
}

至此,addLast和getLast是快的,但removeLast仍然慢,因为为移除了最后一个节点,我们仍需遍历至倒数第二个节点,将其next指针设为null,然后更新last指针。

Improvement #7: Looking Back

解决之道:设置一个向前的指针

public class IntNode {
	public IntNode prev;
	public int item;
	public IntNode next;
}

在这里插入图片描述

Improvement #8:Sentinel Upgrade

为避免空链表的特殊性
方法一:更新哨兵节点
在这里插入图片描述
方法二(recommand):循环链表,前后共用一个哨兵节点
在这里插入图片描述

Generic DLLists

DLList的限制:仅能存储整形值
2004年,java语言添加泛型功能,允许创建可存储引用类型的数据结构
语法:类名后添加尖括弧,存放任意占位符即所需类型,在需要使用任意类型的地方,使用任意占位符

public class DLList<Example> {
	public ItemNode sentinel;
	public int size;

	public class ItemNode {
		public ItemNode prev;
		public Example item;
		public ItemNode next;
		...
		public ItemNode(Item i, ItemNode n){
		    item = i;
		    next = n
		}
	}
	...
	private ItemNode first;
	private int size;

    public DLList(Item x);
        first = new ItemNode(x, null)
        size = 1;
}
// e.g.
DLList<String> d2 = new DLList("hello");
d2.addLast("world")'

泛型仅适用于引用类型,我们不能将基本元素如intdouble放在尖括号里,改为使用基本元素的引用版本,如Integer

DLList<Integer> d1 = new DLList<>(5);
d1.insertFront(10);

一些经验法则 rules of thumb

  • 仅在类名后java文件顶部指定一次泛型名以实现数据结构
  • 在其他使用创建的数据结构的java文件中,声明时制定所需类型,实例化时使用空间括弧
  • 基本元素对应的引用版本:Integer Double Character Boolean Long Short Byte Float
    实例化时在间括弧中声明变量不是必须的,只要在同一行生命过即可。如下面的代码是有效的,即使后半部分的声明是多余的
DLList<Integer> d1 = new DLList<Integer>(5);

2.4 Arrays

lecture7 | Lists 4 Study Guide
Lists vs. Arrays
DLList 有一个缺点,获取第i项很慢,我们必须扫描列表中的每一项,从开头到结尾,知道我们找到第i项。
AList 可以使用括号概念快速获取第i项, A[i]AList 相比 DLList具有相同的接口,此外,它也具有size变量跟踪大小。
实现 AList每个 AList 有一个 int[] 被称为 items.🌟定位
对于 getLast, 我们简单地返回 items[size-1].
对于 removeLast, 我们只是简单地缩减 size 无须改变items). 因此,如果下一步调用addLast ,它会被旧值覆盖。 删除对象时最好将其置空,可以节省内存空间,尤其是费int类型。
阵列调整Array Resizing 当阵列满时,我们可以调整阵列。已知阵列大小不可改变,解决方案是创造一个新的更大size地阵列,将旧值复制到新阵列中。
提高调整大小的性能(速度)创建一个具有“size*FACTOR”的新数组,而不是通过一个额外的框进行添加。
另外:代码分解Aside: Breaking Code Up 有时,我们编写的大型方法可以做很多事情。更好的方法是我们可以分部分测试代码。
Generic AList 我们可以为 AList 做一些类似 generic DLList的事情.。不过,Java中不允许使用泛型数组。我们通过如下方法实现

items = new Item[100];

改为:

iitems = (Item[]) new Object[100];
This is called a cast.

Array Basics

此前,我们学习了通过变量声明和类实例化来获取内存盒 memory boxes。 如:
int x;声明变量x为int类型,给了我们一个存储int的32位内存盒
Walrus w1;实例化类Walurus为w1,给了我们一个存储Walrus引用的64位内存盒
Walrus w2 = new Walrus(30, 5.6); 实例化Walurus为w2,并初始化new创建,给了我们3个内存盒,一个存储引用的64位,一个存储int的32位,一个存储double的64位。
数组Arrays是一种特殊的对象,由一系列带有编号的内存框组成,我们通过括号获取对应编号内存狂存储的元素A[i]。不同于类,类具有命名的内存框盒。Arrays的特点:固定的整数长度N;一系列相同类型的内存框,从0编号至N-1.

Array Creation

x = new int[3]; //填充int默认值0 (String[3]内存盒中默认值null)
y = new int[]{1, 2, 3, 4, 5};
int[] z = {9, 10, 11, 12, 13}; // 结合变量声明盒创建z,省略new

Array Access and Modification

public class ClassNameHere {
   public static void main(String[] args) {
      int[] z = null; // z = null
      int[] x, y; //未获取内存盒

      x = new int[]{1, 2, 3, 4, 5}; //创建数组
      y = x; //同一引用
      x = new int[]{-1, 2, 5, 4, 99};
      y = new int[3];
      z = new int[0]; // 空数组,有引用,没有有编号的内存框盒
      int xL = x.length;

      String[] s = new String[6];
      s[4] = "ketchup";
      s[x[3] - x[1]] = "muffins";

      int[] b = {9, 10, 11};
      System.arraycopy(b, 0, x, 3, 2);
   } //相当于Python中的 x[3:5] = y [0:2]
}

另一种方法实现arraycopy:通过loop

int[] x = {9, 10, 11, 12, 13};
int[] y = new int[2];
int i = 0;
while (i < x.length) {
    y[i] = x[i];
    i += 1;
}

由于Java数组尽在运行时检查边界,所以上述代码编译时没有问题,运行时会报错数组越界java.lang.ArrayIndexOutOfBoundsException

2D Arrays in Java

An array of arrays 数组阵列的数组阵列
int[][] bamboozle = new int[4][]

int[][] pascalsTriangle;
pascalsTriangle = new int[4][]; //创建4个内存框盒,每个可以指向一个整数数组阵列,默认为null
int[] rowZero = pascalsTriangle[0]; //第一个 默认为null

pascalsTriangle[0] = new int[]{1};
pascalsTriangle[1] = new int[]{1, 1};
pascalsTriangle[2] = new int[]{1, 2, 1};
pascalsTriangle[3] = new int[]{1, 3, 3, 1};
int[] rowTwo = pascalsTriangle[2];
rowTwo[1] = -5;

int[][] matrix;
matrix = new int[4][];
matrix = new int[4][4];

int[][] pascalAgain = new int[][]{{1}, {1, 1},
                                 {1, 2, 1}, {1, 3, 3, 1}};

Arrays vs. Classes

相同点:内存框数量固定。array长度不能改变,类的字段不能添加或删除
不同点:array框通过[]符号符号编码盒访问,class通过.dot符号命名和访问;array框须是相同的类型,class框可以是不同的类型。==>数组[]表示表示法允许我们在运行时指定索引,但是类运行的时候不能指定字段

2.5 The AList

DLList 链表获取索引item仍需大量便利,导致很慢。

Our First Attempt: The Naive Array Based List

获取array的第I个元素时间恒定

public class AList<Item> {
    private Item[] = items;
    private int size;
    /** Creates an empty list. */
    public AList() {
		items = (Item[]) new Object[100]; //java创建泛型的语法
		size = 0;
    }
    /** Resizes the underlying array to the target size. */
    private void resize(int capacity) {
    	Item[] a =Item[]) new Object[capacity];
    	System.arraycopy(items, 0, a, 0, size);
    	items = a;
    }

    /** Inserts X into the back of the list. */
    public void addLast(Item x) {
    	if (size == items.length) {
    		resize(size * factor)
    		}
    	items[size] = x;
    	size = size + 1;
    }

    /** Returns the item from the back of the list. */
    public Item getLast() {
        return items[size - 1];        
    }
    /** Gets the ith item in the list (0 is the front). */
    public Item get(int i) {
        return items[1];        
    }

    /** Returns the number of items in the list. */
    public int size() {
        return size;        
    }

    /** Deletes item from back of the list and
      * returns deleted item. */
    public Item removeLast() {
    	Item x = getLast();
    	items[size - 1] = null; // 若是int型可以保留不覆盖,若是泛型的通常覆盖
    	size = size - 1;
        return x;
    }
} 

Geometic Resizing 几何调整

通过乘法而不是加法以增加array的大小,以此修复性能问题。
原通过添加添加调整因子数量的内存框,而是通过将框的大小乘以因子以调整array大小

Generic ALLists

Java不允许我们创建泛型对象数组,因此使用如下语法

Glorp[] items = (Glorp[]) new Object[8];

11 Subtype Polymorphism, Comparators, Comparable 子类型多态性,比较器,比较接口

-回顾动态方法选择
-定义子类型的多态性,并将其与高阶函数进行对比
-子类型多态性在Comparators和Comparables方面的应用

11.1 回顾动态方法选择

extend扩展:运行子类的方法,还是父类的方法,还是运行报错?

  • 编译器允许内存盒存储它本身的任何子类
  • 编译器允许静态类型的调用
  • 重写的非静态方法在运行时基于动态类型进行选择,其他一切都是基于静态类型的,包括重载overload方法。

静态类型 vs. 动态类型
变量具有“编译时类型”,又称“静态类型”,声明时指定,不会改变。
变量也具有“运行时类型”,又称“动态类型”,实例化(使用new)时指定,等于所指向对象的类型。

Object o2 = new ShowDog("Mortimer", "Corgi", 25, 512.2); // o2的动态类型是ShowDog,o2的静态类型是Object(创建一个ShowDog,存储在Object内存盒中)
ShowDog sdx = ((ShowDog) o2); //sdx的静态类型是ShowDog
sdx.bark(); // sdx运行ShowDog.bark()
Dog dx = ((Dog) o2); // dx的静态类型是Dog(类型转换只是将o2暂时视为Dog,不会改变o2的静态类型)
dx.bark(); // dx运行ShowDog.bark()
((Dog) o2).bark(); // 运行ShowDog.bark()
Object o3 = (Dog) o2; // o2的静态类型视为Dog,o3的静态类型是Object
o3.bark(); // 运行失败!o3的静态类型Object没有bark()方法,编译器错误

11.2 子类多态性 vs 显示高阶函数

多态性:为具有不同类型的实体提供单一接口

// 显示高阶函数:使用常见的回调函数
def print_larger(x, y, compare, stringify):
    if compare(x, y):
        return stringify(x)
    return stringify(y)

// 子类多态性方法:对象本身进行选择(代码更干净)
def print_larger(x, y):
    if x.largerThan(y):
        return x.str()
    return y.str()

11.3 Comparables – 子类多态性的大规模应用

Object无法直接通过>比较,我们在Dog类中编写一个maxDog方法。
但如果写在Dog类中,则无法应用到其他类如Cat class,所以更好的方式是创建可比较接口,不同的类可以继承同一接口。

public interface Comparable<T> {
    public int compareTo(T obj){
        ...
    }
}
常见大小比较,一条语句替代(如果>=<0三种情况)
java
return this.size - uddDog.size;

if (this.size < uddaDog.size){
    return -1;
} else if (this.size == uddaDog.size) {
    return 0;
}
return 1;

11.4 Comparators 比较器

Natural order自然顺序:compareTo中暗含的顺序
Comparator:比较器可实现自然顺序以外的实现

public interaface Comparator<T> {
    int compare(T o1, T o2);
}
NameComparator
java
import java.util.Comparator;

public class Dog implements Comparable<Dog> {
    ...
    public int compareTo(Dog uddaDog) {
        return this.size - uddaDog.size;
    }

    private static class NameComparator implements Comparator<Dog> { // 用不到任何实例化Dog及其实例变量,因此可设为static
        public int compare(Dog a, Dog b) {
            return a.name.compareTo(b.name);
        }
    }

    public static Comparator<Dog> getNameComparator() {
        return new NameComparator();
    }
}

Comparable and Comparator 总结
接口提供回调的功能。有时函数需要还没有写的辅助函数,如:max需要compareTo。辅助函数被称为回调。
有些语言通过显示高阶函数传递(参数/辅助函数)处理此问题,如python。Java中,我们通过在在接口中封装所需的函数来实现这一点(例如,Arrays.sort需要compare,它位于比较器接口中)
Comparable可比性比较自身和另一对象,Comparator比较器比较任意两个对象

11.5 章节总结

键入规则
编译器允许内存框容纳任何子类型。
编译器允许基于静态类型的调用。
重写的非静态方法是在运行时根据动态类型选择的。
对于重载方法,在编译时选择该方法。

子类多态性:考虑一个静态型Deque的变量。调用deque.method()的行为取决于动态类型。因此,我们可以有许多子类来实现Deque接口,所有这些子类都可以调用Deque.method()。
子类多态性案例:写一个函数max(),它返回任何数组的max,而不考虑类型,适用于所有可比较对象的“一个真正的最大方法”。思考(1)如果我们写一个方法max(Object[] items),但有些Object不能使用’>'运算符来比较;思考(2)在Dog类中编写一个max()函数,但必须为要比较的每个类编写一个max()函数!
解决方案:OurComparable接口包含compareTo(Object)方法,max()方法可以采用OurComparable[]参数,由于任何扩展接口的对象都具有接口内的所有方法,因此始终能够调用compareTo方法,并且该方法将正确地返回对象的某些排序。因此,我们不需要在每个类中重新实现max逻辑,而只需要在给定两个对象的情况下实现用于选择对象顺序的逻辑。
更好的方法:Java有一个内置可比较接口Comparable,它使用泛型,已经适用于Integer、Character和String等,还实现了max、min等方法。
Comparator比较器:术语“自然顺序”是指Comparable的compareTo方法所隐含的顺序。如果想按自然顺序以外的方式排序,我们将传入一个Comparator<T>接口,该接口需要compare()方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值