写在前面

OO Pre 的学期总结,痛并快乐着

[TOC]

也记得找jsgg debug的时候,他说:你push到仓库里,我明天帮你看看

可惜我当时甚至还没学会用idea进行版本管理

Pre0_task1

本次目标:完成git和idea的安装,学习其基本用法

题外话,Git 这东西真的魔力很大,但是确实也有点难学

课程组推荐给大家的我也推荐给大家(

Git 相关:

  1. Git 使用心得 & 常见问题整理

  2. 廖雪峰老师的 Git 教程

  3. Git 基础可视化教程Git 进阶可视化教程

  4. Git Pro

Java相关:

JDK Document

Commit message

可以采用插件规范自己的 commit message ,摘录一些如下:(来源于陈大大的期待的CSDN博客

Git Commit Template插件在使用时选择的git提交类型解释:

feat 功能feature的意思,也是最常用的。当你的功能有变更的时候,都可以采用这种类型的type

fix 当然指的是bug修复

docs 更新了文档,或者更新了注释

style 代码格式调整,比如执行了format、更改了tab显示等

refactor 重构代码。指的是代码结构的调整,比如使用了一些设计模式重新组织了代码

perf 对项目或者模块进行了性能优化。比如一些jvm的参数改动,把stringbuffer改为stringbuilder等

test 这个简单,就是增加了单元测试和自动化相关的代码

build 影响编译的一些更改,比如更改了maven插件、增加了npm的过程等

ci 持续集成方面的更改。现在有些build系统喜欢把ci功能使用yml描述。如有这种更改,建议使用ci

chore 其他改动。比如一些注释修改或者文件清理。不影响src和test代码文件的,都可以放在这里

revert 回滚了一些前面的代码

Pre1_task1

本次目标:初识Java,实现一系列基础的类,并且熟悉类、属性和方法的使用

本次作业只需要使用Java基础语法、容器和简单的getter、setter,以及对大数类 BigInteger 用法有基本了解即可完成

关于容器:

容器是好用的,容器是需要学习的

容器是现代程序设计非常基础而重要的手段

如果采用类似C语言结构体数组的写法,通过遍历数组查找所需要的信息,就不太符合Java的感觉,更好的做法是采用HashMap。概括起来就是下面这句话:

不要重复造轮子!不要重复造轮子!不要重复造轮子!

关于面向对象:

面向对象是一种主流的软件开发方法,也是一种思维方式,其核心是识别类,并在类之间建立层次式的协作关系。

有兴趣的同学也可以看这篇读书笔记——《面向对象是如何工作的(第2版)》读书笔记

啊当然也是还在紧锣密鼓地筹备当中辣

关于构造函数Constructor

一个压行的方法是将参数传入constuctor,省去了多次调用set方法

关于构造函数的理解:

构造函数的用途是在你需要创建一个对象的时候完成一些初始化工作,并给对象的所有属性赋予初始值。

也就是这两步:①创建这个对象 ②给这个对象部分初始化

一个愚蠢的压行方法:不用中间变量存储读入,直接将读入传入

详细说明如下,但是貌似不符合软工要求

有条理的写法

1
2
3
4
5
6
import ...

Scanner in = new Scanner(System.in);
String userId = in.next();
String bottleId = in.next();
function(userId, bottleId);

不太好的压行写法

1
2
3
4
import ...

Scanner in = new Scanner(System.in);
function(in.next(), in.next());

多说一嘴

为什么 ArrayList 可以存不同的对象呢?(这会在本单元第2、3次作业中有所体现),记住Java中一句非常重要的话:Everything is reference ,这会帮助你理解很多问题

什么、不太明白,请耐心看下去哦~

toString() 方法

操作10要求输出特定的Bottle类型实例的属性,建议在 Bottle 类中重写toString 方法,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Bottle {
private int id;
// ...
@Override public String toString() {
return "The bottle's id is " + id + ".";
}
}

class Main {
public static void main(String[] argv) {
Bottle bottle = new Bottle();
// ...
System.out.println(bottle);
}
}
// 输出 : The bottle's id is 1.

java.io.PrintStream.println(Object x)函数将调用String.valueOf(x)以获得实例x的字符串描述,而java.lang.String.valueOf(Object x)函数将在x不为null时返回x.toString(),因此我们可以通过重写toString方法使println函数输出自定义格式的“实例属性”。详见 println() - JDK DocumentationvalueOf() - JDK Documentation

简而言之这个 toString() 就是对象的字符串描述,println() 会打印它

Pre1_task2

本次目标:多态和继承的练习,以及了解工厂模式

虽然我没写出来工厂模式,但是我觉得这很漂亮,很符合我对设计模式的想象

工厂模式可以看我的另一篇博客(啊写好了会post上来的,问就是还在紧锣密鼓地筹备当中)

一、继承

继承就是定义子类继承父类的特征和行为,使得子类可以拥有父类的属性和方法,从而起到代码复用的目的。

从语义上来说,在 AB 类型满足 is-a 关系(A is a B),即A 类型是 B 类型的一种时,可以使用继承来在程序表述。

二、向上转型(Upcasting)

在建立了继承关系之后,可以使用父类型去引用通过子类型创建的对象。

在调用构造函数的时候会涉及先后问题,这里要注意,是先调用父类的构造函数,再调用子类的构造函数,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 父类构造函数
public Equipment(int tmpId, String tmpName, long tmpPrice) {
this.id = tmpId;
this.name = tmpName;
this.price = tmpPrice;
}

// 子类构造函数
// public class Bottle extends Equipment
public Bottle(int tmpId, String tmpName, long tmpPrice,
double tmpCapacity, boolean tmpFilled) {
super(tmpId, tmpName, tmpPrice);
this.capacity = tmpCapacity;
this.filled = tmpFilled;
}

// public class HealingPotion extends Bottle
public HealingPotion(int tmpId, String tmpName, long tmpPrice,
double tmpCap, boolean tmpFilled, double tmpEff) {
super(tmpId, tmpName, tmpPrice, tmpCap, tmpFilled);
this.efficiency = tmpEff;
}

三、向下转型(Downcasting)

一种比较危险的行为,需要搭配 instanceof 来使用

Java 语言提供了一个特殊的关键词 instanceof 用来判断一个对象引用所指向的对象的创建类型是否为特定的某个类,一般写为 obj instanceof A,其中 obj 为一个对象引用,A 为一个类型(类或接口),这个表达式的取值结果为布尔型,如果 obj 的创建类型为 A,则结果为 true,否则为 false。在这个表达式取值为 true 的情况下,可以使用向下转型 (down cast) 来使用一个 A 类型的对象来引用obj: A ao = (A)obj 。注意,实际上 obj 所指向对象的创建类型永远不会发生变化,转型的只是对象引用类型。

本次作业中的应用为:哦好像没有应用,在下一个 task 里有应用

四、对象方法的重写和复用

有时候,具有继承关系的类的某些行为具有递进关系,但又有自己特定的行为。

我们期望这两个类中实现的特定方法同名。这种让子类重新实现一个在父类中已经实现的方法是面向对象的一个重要机制,称为方法重写。方法重写获得的直接好处是让子类与父类在相应方法的调用上保持了一致性。

更通俗的说,重写方法与父类方法在行为上具有相似功能,但子类重写的方法一般额外增加一些行为。

在程序编写方面,一般会为重写方法标上一个@Override标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Course {
String name;
Course(String name) {
this.name = name;
}
void displayInfo() {
System.out.println("这门课很难");
}
}

class OOCourse extends Course {
@Override
void displayInfo() {
System.out.println("这门课很难"); // 这里出现了代码拷贝,是低质量的代码
System.out.println("上面那句是放p");
}
}

// 建议更换为下面的代码:
class OOCourse extends Course {
@Override
void displayInfo() {
super.displayInfo(); // 调用了类Course中定义的方法
System.out.println("上面那句是放p");
}
}

Java语言提供了一个重要的关键词super,它实际指代的是当前对象从父类继承得到的内容

联想上面构造函数里的super,你甚至可以理解为,这个super会替换为这个类的父类名称,这里使用 Course.displayInfo() 也是一样的效果,同理,构造函数 super(argument) 也可以替换为 Course(argument)

五、多态

实际调用的方法与对象引用的类型无关,取决于被引用对象的创建类型。请看下面的代码示例:

1
2
3
4
Course c1 = new Course();
Course c2 = new OOCourse();
c1.displayInfo();
c2.displayInfo();

其中通过c1调用的实际是Course类实现的displayInfo方法,而通过c2调用的则是OOCourse类重写的displayInfo方法,但实际上c1和c2的引用类型都是Course。上面我们提到的这个特性,就叫做多态。

参差多态,乃是幸福本源(逃)

六、异常处理

异常:程序运行时发生了意外的事件,阻止了程序按照程序员的预期正常执行

运行出错后,Java 提供了一种优秀的解决办法:异常处理机制,它采取显式的方式处理异常,包括两个方面:

  • 引入了专门的表达和处理方式,代码上一目了然就能看出是异常处理;
  • 一旦发生异常,会强迫程序执行进入异常处理分支。

在Java语言中,每个异常都被封装为Exception,异常有抛出和捕捉两种处理方式。所谓抛出,就是使用Java提供的throw关键词来产生一个Exception或者其某个子类的对象;而捕捉则是通过catch关键词来捕捉在一段代码的执行过程中所抛出的相关异常对象。

课程推荐使用异常处理机制来区分处理显著不同于一般情况下的数据状态。使用异常处理可以让你的代码更加清晰易于理解,降低出现 bug 的可能性。

👆上面内容来自指导书,相信我不说你也看出来了

前面代码出现异常后,后面的代码不会执行,程序会抛出异常并结束(×)

异常发生并不意味着程序终止,找到地方可以处理这个异常即可

当然,当前作用域,异常后面的部分不会执行

具体运行过程为:

一个很全面覆盖上面流程图的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ArrayIndex {

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

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

public static void h() {
int i = 10;
if (i < 100) {
g();
}
}

public static void k() {
try {
h();
} catch (NullPointerException e) {
System.out.println("k()");
}
}

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

}

输出:

解释:

f产生异常,所处不是try,所处是函数g,返回调用者g

g与f同理,返回调用者h

h产生异常,所处不是try,所处是if语句,不是函数,退出到if大括号的外层,此时所处是函数,返回调用者k

k产生异常,产生异常的代码块所处是try,catch异常不匹配(因为是AIOOBE),退出到外层

能不能把所有代码放到一个try块里,这样啥异常都能输出啊(逃)

《Core Java》的第七章相关内容请见:紧锣密鼓筹备中

Pre1_task3

前言 & 碎碎念

这次作业真的有点难度,我也是借助了 bygg @一个虚空的blog地址 的力量才勉强 AK 了

难点主要在 deep clone 和 所属关系的复制

deep clone 这里呼应了前面说的向下转型 downcasting,具体来说,需要根据 Commodity 的类型创建对应的 Commodity 副本,此时需要用到 instanceof 运算符

具体的实现可以见代码,代码我放到 GitHub仓库 里了

这里的版本控制的实现,和 Git 的好像不大一样,先挖个坑,有空思考了回来填上

一个细的点在于,不能对 Adventurer 所拥有的 Adventurer 直接 deep clone

举个例子,A 雇佣了 B,如果 deep clone,B 使用道具后的后果不能同步给 A 雇佣的这个 B,所以两个 B 就不一样了。这里直接 shallow copy即可(其实也节省了代码量)

Pre2_task1

挺好做的,可以用捕获组,也可以用字符串类的方法,不过后面迭代比较痛苦

另外,关于 substring() 方法,建议大家多试试下标的选择。不要怕麻烦,这个地方很可能背刺你

Pre2_task2

这次也很简单。我感觉正则的题目比第一次的大模拟还是友好不少的

Pre2_task3

注意跨年日期的判断(可以参考我的狼人样例,能过了强测基本没问题)

Pre3_task1

想起pre1_task3那周,我在图书馆坐了整整一周,最后还是de不出bug,果然肉眼debug的形式是不行的。

建议大家自己捏一些数据

我的数据也放在 GitHub仓库 里了

正则表达式的题目,就建议大家多试多练多学习。我个人感觉没什么好说的,大家比我聪明,看一看就能学会

我一直没学先验后验什么的,挖个坑,下学期学,下学期一定学,嗯

最后的最后

看过很多博客,还是觉得qsgg的“只有一个曲别针的小男孩”的故事最入我心

小男孩经过一系列的交换,把手里的曲别针换成了别墅

可我觉得这故事没什么意思,我更想看到小男孩用别墅继续交换继续交换,最后虽然损失了别墅,但是收获了远比别墅珍贵更多的东西,最后云淡风轻:“嗯,不过损失一个曲别针而已,我得到的是更大的世界”

我来的时候连一个曲别针也没有,我走的时候却收获了很多故事,认识了很多朋友,收获了比别墅价值大得多的东西

很多很多年以后,可能我还会想起那个新主楼的夜晚和风景,和五教二楼的美丽对拍

或许要问有什么遗憾,那就是最后一次的作业赶时间没用递归下降完成。以及这门课确实有点耽误我计组的时间了,不过这都过去了

他给我的最大的宝藏就是,如果方法得当,愿意多做调试愿意自己多捏数据,你能够对自己的成绩有十足的把握,每次强测之前我都有信心这次再拿100。以及,他让我知道小白也有从零开始成为金刚的权利,真的让我尝到了取得小成就的满足

后面的文字写的有些仓促,主要是计组进度压上来,顶不住了,对不住各位认真读到这里的看官

以及正则的题目确实不难,希望大家认真学认真练

希望诸位看官能从我这堆毫无章法的文字中读出些东西

如果看完什么也没看明白,那是怪我水平粗浅,与诸位无关

一路顺风