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")'
泛型仅适用于引用类型,我们不能将基本元素如int
或double
放在尖括号里,改为使用基本元素的引用版本,如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()方法。