java基础
- 类:类的成员(方法和变量)。
- 静态语言与动态语言的区别。
- 类的实例化(对象)
- 实例变量,构造器(决定如何实例化类)
- 静态方法与非静态方法及其区别(调用)
- private、this关键词
对于变量对应的是变量的值,对象和数组对应的是内存位置的指针。
Java中使用变量必须实现声明;变量的定义和声明可以用一条语句实现。对于对象和数组需要进行实例化,通过定义将内存中的数据与对象(数组名)连接。
lists
首先使用类构建一个单向的链表,每个数据单元保存找该单元的值和下一个单元位置的指针(内存地址)。之后引入sllist,这个类通过嵌套类的方法再原先链表的基础上加入一个中间层操作链表,方便用户调用定义,参数(之前的链表每次加入新的值都需要加入一个指针指向原来首个单元的地址)。并且通过将来的类以及一些变量设置为private,形成一个黑盒,从而避免用户访问而造成错误。
通过递归或者是迭代来实现get,size以及addfirst,addlast等方法。但无论是使用使用迭代还是递归有的方法都需要遍历整个链表来实现,这会使得计算的速度随着链表数据的增多而线性增加。然而显然对于这些方法显然有更好的实现方式。对于size方法可以在中间层维护一个size变量每次链表数据长度产生变化便进行记录。虽然其他方法时间会略微延长(几乎可以忽略),但使得size方法的速度大大加快(只用返回一个数就可以)。
对于addlast方法根据原来链表书是否有数据需要分情况讨论?在这里可以引入一个哨兵节点使得所有的方法统一。在编写代码中要尽可能地减少特殊情况,less is more 。哨兵节点永远位于链表地最前端,并指向第一个数据哨兵节点内地值可以忽略,添加数据都从器后面添加。
要加快addlast方法的访问速度可以使用结合前面的sllist双向链表,为了避免bug可以在尾部再添加一个哨兵节点(它一直位于最后)。进一步节点只用一个哨兵节点即使开头也是结尾,是整个双向链表构成一个环形。
为了实现链表的泛化功能,可以在定义类时在类名字后面加入< >其中写入默认储存的类型。在实际声明时可以在进行指定(实例化的地方可写可不写)。
最后结合数组来实现这些上面类的方法。同时介绍了二维数组。二维数组中实际时是由多个一维数组组成的。数组名指向的时一个储存着各个一维数组的地址。这些一维数组的地址才指向数据。接着介绍了数组和类调用参数和成员的区别。
结合数组使用中间层(bureaucracy)构建Alist;设置固定的数组长度,删除末尾的数据只需要将保存的size值减少不去索引末尾的值即可。从用户层面看就是删去了。当内部数组储存的数据慢了之后要再添加数据就需要新建更长的数组复制原数据以及加入新数据。但这种如果加一个数据就延长一次当添加数据量大(现实中这种情况还是存在的)时需要多次创建新的数组,使得速度下降。每次数组慢时直接多增加几个格子并不能完全解决问题。实际解决方案是在原数据长度的基础上倍增数组长度。泛化与sllist相似,但是定义数据时需要使用不同语法
。在删除数据是因为泛化后如果仅减少size值但原格子中数据不删除的的话会造成内存的浪费,因此需要替换为null。(Glorp []) new Object[cap];
`或者是
`new Glorp[cap];
测试
在现实中面对大型的工程文件一次debug是需要花费很长的时间的,但可以通过编写测试代码对各个单元(就是其中的各个方法)进行测试。使用Java中的JUnit库可以减少许多的比较之类的重复代码。结合这个库不需要再测试文件中加入主方法,只需要再每个测试方法前加入@org.junit.Test。运行与运行代码略微有些区别。与python类似,通过import语句看以减少调用函数时重复语句的书写。
课程中结合选择排序及其测试进行展开。选择排序每次都寻找最小值并于开头交换最后完成排序(概略)。结合测试产生了一种编写程序的方法,测试驱动开发(TDD)。先写测试代码再写各个环节的函数通过测试代码测试程序不断改进,这个方法有利有弊。
继承和实现
重载指的是同一个方法名但输入输出参数不同。通过interface可以避免多次重载(overload)。interface 命名作为上位词,其中包括了各子类(下位词)共同拥有的方法,只需表示方法不需要实现。在interface实现方法并加入default关键词。在子类中实现在这些方法时可以在方法上加入@Override标签。(重写标识,便于寻找bug)当子类中没有该实现方法时便使用interface中的实现。静态类型(编译时类型)是变量声明时确定的,实例化时决定变量的动态类型(运行时类型)。动态类型决定动态方法的选择。这种方法的动态选择实际比较复杂, 大概就是现在对象下的方法中寻找,寻找参数最准确的(下位词参数比上位词精准)。
子类可以通过extend关键字继承其上级类的方法,同时可以在子类中重写方法@override以及定义新方法。extend表示子类与上类之间地关系是is a。通过extend可以继承上一级类中的所有方法、实例和静态变量以及嵌套类(不包括私有),但类的构造方法并不会继承。实现子类的构造方法时会自动地隐式地继承其上级类地构造方法(也可以显式地添加super语句)。但如果要向上级传入参数则比喻通过super()语句。
java中所有的类都是对象类的子类。所以每个类都继承对象类的equal等等方法。而有的类也是接口地实际实现。封装:下层地实现对上层不可见,上层调用下层地类或者方法是不需要关注它是如何实现的,减少大量工程的复杂度。但是继承有时会打破封装产生bug。new右边的类代表了变量的动态类型(运行时类型)而等号的左边或声明语句定义了类的静态类型(编译时类型)。编译器检查时会看编译时类型而动态方法选择会看变量的动态类型。子类动态类型可以赋予给父类但不可以反过来。
通过()中定义类型可以使得该语句跳过类型检查。但这并不会改变原变量的类型(动态或静态),只是让编译器将后面的作为括号中的类。
Java中需要调用通过重写接口方法类实现高级函数(将函数作为参数)。
子类的多态性,从如何泛化max函数讲起。通过设置定义接口在每个类中继承并实现比较的方法。也可以通过Java中自带的一个接口Comparable
后边讲了Java中的图和集合。相比于python中定义字典和集合只需要使用{ }即可。在Java中则需要准确定义集合或是字典键值对应的类型,同时需要选择其中的实现方式。语法相对复杂但也正因如此自定义程度更高,也能够根据需要选择合适的实现方式,提高效率。抽象数据类型与介于接口和具体的类之间,在抽象数据类型中的方法默认需要书写其实现通过abstract语句可以不用编写实现,方法可以是私有的但同样不能被实例化。最后介绍了引用Java中的包。
泛化与包装
java中的每个基本类型的变量都对应一个包装类(wrapper calss)也称为引用类,比如在对于一些泛化的类和方法我们在使用的时候会根据需要定义其类型(如Integer)。这些就是包装类。两者其实可以换转换。Java语法可以自动将基本类转换为引用类(int→Integer)也可以将引用类转换为基本类。但对于数组是无法自动包装和解包装。但实际上使用引用类会占用更多的内存,两者之间的转换会影响性能。
同时Java也会自动拓宽(Widening)根据情况需要原变量。比如将一个int的变量输入给函数,但该函数需要的是double型。但反过来就需要手动转换。
可变的数据类型诸如之前定义的数组是可以使用方法对其进行更改。在Java中使用final关键词可以定义恒定值,无法被修改。但对于类等数据,在其声明时加入关键词智能保证其对应的变量名内存地址不变。
后面构建的map类型,类似于字典具有键值对应的关系。但对于其泛化类型,由于键和值的组合对应在编写map中的一些方法时需要进行一些改变。例如:
public static <K,V> V get(Map61B<K,V> map, K key) {
if map.containsKey(key) {
return map.get(key);
}
return null;
}
同时为了比较键的需要,需要对应的泛型带有比较方法。
public static <K extends Comparable<K>, V> K maxKey(Map61B<K, V> map) {...}
异常,迭代与对象方法
使用import语句可以调用Java中提供的数组接口及其对应的实现,同样也包括集合。使用throw关键词可以返回带有错误信息的语句并使得程序停止运行(不过课程里面的代码使用throw更多一些)。使用try catch语句也可以实现一场捕获。要对用户构造的类实现类似for (String city : s)的迭代操作。借助迭代器的接口在类实现时嵌套对应迭代器类的实现来实现上述功能。
所有的类都是继承对象类的方法,但对于自定义的一些类(数组等)其继承的方法不一定适用,可以通过一些重构来实现。
包,访问控制及特性
包是组织一系列类和接口的命名空间。声明和调用类时需要完整的包名和类名字可以通过在代码开头添加import语句来省略。通过intelliJ可以直接创建包。报名字中的.符号的分割会以文件目录的形成创建层次文件夹。(将.看作\);
如果在编写类时没有在代码开头写明包,则会自动归类到默认包中默认包中的类无法被在其他包中调用。JAR文件是一种类似压缩包的东西,可以将包中的Java文件打包到一块。
访问控制
回过头来再看自己学习的时候做的笔记,虽然确实有些细节已经记的不太清楚,但却不知不觉发现已经走了很远了。