前言

Not finished

浙大翁恺老师的 OO 入门课

Refs:翁恺 & 黑马程序员

第一周 类与对象

用类制造对象

前置知识:对象变量是对象的管理者而非所有者,所以涉及到赋值、函数参数传递和比较(如果两个对象变量相等说明管理的是同一内容,判断管理内容是否相同应用equal判断)都与普通变量有所不同。

现在,我们要来尝试自己定义类,然后用自己定义的类来创建对象

定义类

观察这类事物的特点,用一些成员和函数表示这个类

  • private是对类而非对象,所以同一类的不同对象之间可以访问彼此的成员
  • 如果不声明默认friendly,可以在同一个包的文件下共用
  • protected见继承中的project

成员变量和成员函数

this 这个特殊的本地变量起着至关重要的作用

  • 注意:如果成员函数的参数与成员变量同名,那么会取作用域更小的,也即取参数作为这个变量名对应的变量
1
2
3
4
5
6
7
public class New {
int price;

void setPrice(int price) {
this.price = price;
}
}

成员函数内部调用其他成员函数也可不加this.

区别于定义在函数内部的本地变量,成员变量的生存期是对象的生存期,作用域是类内部的成员函数(也即类内的成员函数可以使用成员变量,成员变量初始化...)

对象初始化

成员变量在对象初始化时,如果未赋值,会安排一个初始值,当然也可以手动初始化(手动直接赋值、调用函数赋值)

变量类型 初始值
boolean false
int 0
object(某个对象) null

构造函数(课上讲过不少了,我摸了(bushi))

第二周 对象交互

对象的识别

感觉和定义类是一回事,两件事情可以在定义类的时候同时完成。

对象的交互

课上讲过一嘴,说到可以用static修饰的“类全局变量”

也可以通过将一个类的成员变量设置为其他类的对象来达到目的

image-20220913223756886

图1 以钟表为例来看对象的交互

访问属性

image-20220913223824851

图2 用鸡蛋图表示封装思想

封装思想:把数据和对数据的操作放在一起

public:同一个编译单元(一个java文件)里只能有一个class为public,且public class的名字必须与编译单元同名

如果不加public,则不能在这个package外面访问

一个编译单元最多有一个public class,但也可以一个都没有

文件结构:clock(project)->src->clock(package)->clock.java

import:用到的类不在同一个package里,就需要import

1
2
import display.Display
import java.util.Scanner;

包的名字.包的类

image-20220911164023484

如果不用import,也可以像这样给出全名

也可以万能import(引用display所有内容,但是可能冲突,推荐import具体的类)

image-20220911164335992

包的名字里面的点实际上表达文件夹层次

Java通过这样的手段管理文件系统(

static:类变量,不属于任何一个对象,属于这个类,任何一个对象都拥有这个变量,但是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test() {
public static void f() {

}

public static void main() {
f();
}
}

// Wrong usage
public class Test() {
private int value = 0;

public static void f() {
value++; // cannot compile
}

public static void main() {
f();
}
}

static函数只能调用static函数,只能访问static成员变量(这回理解了)

static函数和变量可以通过类访问,也可以通过对象访问,只是不能获得对象的具体信息

static变量的初始化只执行一次,和对象的创建过程无关

这就不得不提static的四种用法了(课上讲过,我摸了,略记一下)

第三周 对象容器

记事本的例子

  • 确定功能
  • 接口设计

每一个类都写main,方便做调试

泛型容器

1
private ArrayList<String> notes = new ArrayList<String>(); // ArrayList of String,用来存放String的ArrayList

ArrayList是一个类,notes本身是对象管理者

容器类型、元素类型

  • 对象数组中的每个元素都是对象的管理者而非对象本身

set:set相当于数学中的集合,元素不重复,且没有排序的概念

toString函数直接输出这个对象,会输出toString()函数返回的字符串结果

可以先写一个“空”的返回值,比如返回int类型的,先写个return 0,后面再改;

返回String的先写个return "";

HashMap:想写出“聪明”的代码,和具体数据无关

容器里的类型必须是对象,不能是基本元素,所以需要Integer这样的包裹类型

包裹类型的变量可以直接接受所对应的基础类型变量

所以HashMap的返回值也是对象,就是一个管理者

如果存在会返回有效值,不存在返回null

key唯一,后面的会覆盖前面的

遍历没有特别简单的方法,可以考虑采取for-each遍历entrySet或者valueSet的方法

1
2
3
4
for(Integet k : name.keySet()) {
String s = name.get(k);
System.out.println(s);
}

要写构造器constructor

第四周 继承与多态

一周后看:当时看觉得难是正常的,因为现在也觉得难()

继承得到了父类的所有东西,item里面的所有都得到了,都是可能可以用的(至于为什么是可能,会涉及访问权限的问题,我们后面说)

如果不在子类里定义title,那么就会报“The field Item.title is not visible”(此时没有super())

父类的private成员真的只有父类自己才能用

虽然继承给了子类,但是子类不能直接用

本来我们应该做的事情是让Item给Item的title赋值,所以应该由Item来做初始化

image-20220914222324033

可以看到,虽然CD没有private的title,但是确实产生了title

  • 第一个解决方案:把标识符改成protected(自己可以访问,同一个包内的其他类可以访问,子类可以访问)这是一种没办法的办法

对item做一个constructor

  • cd的构造器得拿到title,用super(title);把参数传给父类,父类构造器通过参数构造;
  • 如果用super(),找不到这个没有参数的构造器
  • 如果不通过super()传递参数,父类会用不带参数的构造器进行构造

因为

  • 先做了父类的初始化(定义初始化和构造器),然后才来做子类自己定义的初始化(我理解这样方便覆盖),再进构造函数做其他事情
  • 需要保证父类的成员变量得到恰当的初始化——定义初始化、构造器(原来在二者兼备时优先定义初始化,然后构造器),总体的顺序是父类->子类,也即父类定义初始化、构造器->子类初始化、构造器,与是否采用super()传递参数无关
  • 同时存在两个成员,也是可以的,父类的被隐藏
    • 计算机的基本原则:优先选择范围小的,也即title应该是DVD自己的title
    • 回到父类对父类成员变量操作时,操作的是父类自己的成员变量
  • 总结一下
    • 父类的所有内容都继承给子类
      • 但是如果是private的,子类不能访问
      • 在谁的函数里面,所指的成员变量就是谁的
        • 子类父类出现同名成员变量,在子类里面的就是子类自己的,在父类里面的就是父类自己的,之间没有任何联系

要用父类的print(),需要加一个super,比如下面这段代码:

1
2
3
4
public void print() {
System.out.print("DVD:");
super.print();
}

类定义了类型

子类定义了子类型

子类的对象可以被当作父类的对象来使用

  • 赋值给父类的对象
  • 传递给需要父类对象的函数
  • 放进存放父类对象的容器里

成员函数的绑定都是动态绑定

image-20220912171422963

image-20220912172044907

记得这里有一个强制类型转换

以及函数声明必须完全相同(函数名、参数表)

image-20220912173013633

可扩展性:不需要修改就能添加新的内容

可维护性:需要修改才能添加新的内容

image-20220912173133898

第五周 设计原则

城堡游戏

open declaration:进入对应的声明

String类的操作系统开销大,可以用StringBuffer类

image-20220912174838925

image-20220912175117169

可扩展性:用HashMap避免“硬编码”(即不需要写出每一对对应的具体键值和值,只需要每次扩展时添加新的即可)

第六周 抽象与接口

6.1 抽象

抽象就是为了防止被制造实体

抽象类的作用仅仅是表达接口,而不是具体的实现细节。抽象类中可以存在抽象方法。抽象方法也是使用abstract关键字来修饰。抽象的方法是不完全的,它只是一个方法签名而完全没有方法体

但是为了表达类之间的关系,还需要这样一个类来表达。如第一章的例子,如果去掉 Shape 类,那么 CircleRectangle 类就没有关系了

抽象函数(不能有括号):表达概念而无法实现具体代码的函数,目的是为了告诉子类需要写 draw() 方法

只要有一个函数是抽象的,类就是抽象的。(这很容易理解,否则便无法调用这个抽象方法)

  • 有抽象类
1
2
public abstract void draw(Graphics g);
// 注意不能有大括号,即不能定义函数体

抽象类(不能产生对象,但是可以定义变量):表达概念而无法构造出实体的类

1
Shape s = new Rectangle(a, b);
  • 任何继承了抽象类的非抽象子类的对象可以赋值给这个变量(也即任何Shape类子类的对象都可以由s来管理)

实现抽象函数

  • 继承自抽象类的子类必须覆盖父类中的抽象函数,这种覆盖叫做实现
  • 否则这个类自己就会成为抽象类

计算机中的两种抽象

  • 与具体相对
    • 表达一种概念而非实体
  • 与细节相对
    • 表示在一定程度上忽略细节而着眼大局

6.2 数据与表现分离

两种看程序的方法:

  • 看main函数,挨个展开需要看的子类代码(”自顶向下“方法)
  • 看最小的类,追到父类,从看懂小部件开始看懂整个代码(”自底向上“方法,联想HuffmanCode)

JFrame、View(窗口)

重绘这个图工作量太大了,先摸为敬

image-20220912194352676

数据与表现分离

  • 程序的业务逻辑与表现无关
    • 表现可以是图形的也可以是文本的
    • 表现可以是当地的也可以是远程的

(强调了一种“各司其职”的思想,写业务逻辑的程序员不需要关心表现是怎样的,不需要关心是通过网页输入字符串还是控制台输入还是blabla)

image-20220912194831143

责任驱动设计

  • 将程序要实现的功能分配到合适的类/对象中去

网格化

  • 图形界面本身有更高的清晰度
  • 但是将画面网格化后数据更易处理

image-20220912195010980

image-20220912195043598

除了C++都不支持多继承(那为啥不用C++)

9.20更新:Java可以通过接口实现多继承

接口

  • 接口是纯抽象类
    • 所有的成员函数都是抽象函数
    • 所有的成员变量都是public static final(常量),static方便调用,接口名.函数名即可调用
    • 无构造方法
    • 成员变量:
      • JDK7:接口中只能有抽象方法
      • JDK8:接口中可以定义有方法体的方法
      • JDK9:接口中可以定义私有方法
  • 接口规定了长什么样,但是不管里面有什么
image-20220912220141030

cell o的意思是任何实现了cell接口的都可以交给这个变量

interface是一种特殊的class,和class的地位是一样的,原来用class的地方都可以用interface

实现接口

  • 类用extends,接口用complements
  • 类可以实现很多接口
  • 接口可以继承接口,但不能继承类
  • 接口不能实现接口

讲的就是两个东西之间的链接

需要其他服务的时候,不是定义一个类,而是定义一个接口

cell是view和field定义的

面向接口的编程方式

  • 设计程序时先定义接口,再实现类
  • 任何需要在函数间传入传出的一定是接口而不是具体的类
  • 是Java成功的关键之一,因为极适合多人同时写一个大程序
  • 也是Java被批评的要点之一,因为代码量膨胀起来很快

补充自黑马程序员

抽象类:更多地用作父类,是对事物的抽象

抽取共性方法作为抽象方法,抽象方法所在的类便成为抽象类

接口:更多地侧重于规则,是对行为的抽象(如游泳接口,可由动物和人实现)

接口的子类也称作实现类

  • 要么重写接口中所有的抽象方法
  • 要么也是抽象类

可以多实现

1
public class Subclass implements Class1, Class2 {}

所以我感觉多接口继承也是用接口的重要原因之一

接口和类之间的关系

  • 类和类的关系
    • 继承关系,只能单继承,不能多继承,但是可以多重继承
  • 类和接口的关系
    • 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
      • 实现多个接口需要重写全部的抽象方法
      • 同名的话就是实现了不同接口的同名函数
  • 接口和接口的关系
    • 继承关系,可以单继承,也可以多继承
      • 实现最下面的子接口的话,需要重写所有的抽象方法

第七周 控制反转与MVC模式

7.1 布局管理器

Swing:所有的东西都叫做部件、另外一种东西叫做容器

容器可以加到容器里,部件也需要加到容器里

容器管部件在哪里,显示多大

布局管理器

layout manager

frame默认的layout manager是border

这章似乎和图形可视化等等关系比较大,所以我摸了()

第八周 异常处理与输入输出

8.1.1 捕捉异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String args[])
int[] a = new int[10];
int idx;
Scanner in = new Scanner(System.in);
idx = in.nextInt();
try {
a[idx] = 10;
System.out.println("Hello");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Caught");
}
}

// 输入5,输出Hello
// 输入10,输出Caught
// 说明第6行没做,从第6行跳到了第9行

8.1.2 异常处理机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ArrayIndex {

public static void f() {
int[] a = new int[10];
a[10] = 10;
System.out.println("hello");
}

public static void main(String[] args) {
try {
k();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("caught");
}
System.out.println("main");
}

}

a[10] = 10 出了异常,之后的hello不会执行,离开f回到调用的地方

8.1.3 捕捉到的异常

捉到了做什么

String getMessage()

String toString()

void printStackTrace

输出:

1
2
3
caught
10
java.lang.ArrayIndexOutOfBoundsException: 10

再度抛出

1
2
3
4
catch (Exception e) {
System.err.println("An exception with thrown");
throw e;
}

如果在这个层面上需要处理,但是不能做最终的决定

8.2.1 异常

读文件的例子

bussiness logic业务逻辑

image-20220913194007073

如果某一步出了问题,就用catch解决问题

异常:不寻常的事情发生之时,需要终止预期的任务,让另一段代码处理

异常机制最大的好处就是清晰地分开了业务逻辑代码和遇到情况时的处理代码

异常声明

  • 如果你的函数可能抛出异常,就必须在函数头部加以声明void f() throws TooBig, TooSmall, DivZero { //... }
  • 可以声明并不会真正抛出的异常

什么能扔?

  • 任何继承了Throwable类的对象
  • Exception类继承了Throwable
  • throw new Exception;
  • throw new Exception("Help");

catch怎么匹配的异常

8.3.1 流

流是输入输出的方式

  • 流是一维单向的
    • 一维:用一个数字就可以表达位置
    • 单向:读 or 写

所有的IO操作都是有风险的

Tips

printf()格式化输出

Java也支持printf格式化输出,格式与C语言相同

两个类里的main函数可以同时运行,当然还要看评测要求