Lec9
继承 extends
- 子类继承父类 拥有父类非private的属性和方法和嵌套类 同时也可以拥有自己的属性和方法或对父类的方法进行重写
- 单继承 一个子类不可继承多个父类 但支持多重继承 B继承A C继承B
- 构造函数无法继承 创建子类时必须先调用父类的构造函数之一 如果不调用 java将默认隐式调用父类的无参数构造函数
super
关键字可用于调用重写的父类方法和构造函数- Object类是所有类的父类 java会隐式继承Object类 Object 类的文档 Object类提供了每个Object都应该能够执行的操作 例如
.equals(Object obj)
.hashCode()
toString()
例如 我们想定义RotatingSLList类继承SLList 定义rotateRight
方法 使列表每个元素向右移动一格 最右端元素移动到列表前面
public class RotatingSLList<Item> extends SLList<Item>
public void rotateRight(){
Item x = removeLast();
addFirst(x);
}
如果我们想要定义一个类来储存并打印删除的元素
public class VengefulSLList<Item> extends SLList<Item> {
SLList<Item> deletedItems;//储存被删除元素的列表
public VengefulSLList() {//构造器
super();
deletedItems = new SLList<Item>();
}
public VengefulSLList(Item x) {
super(x);
deletedItems = new SLList<Item>();
}
@Override//重写父类方法
public Item removeLast() {
Item x = super.removeLast();//访问父类方法用super方法
deletedItems.addLast(x);//保存被删除元素
return x;
}
/** Prints deleted items. */
public void printLostItems() {
deletedItems.print();
}
}
类型检查
动态方法选择:如果父类中的方法被子类重写 则在运行时编译器根据对象的静态类型来确定某些内容是否有效 根据动态类型确定调用的具体方法
比如下面的程序 哪行会导致编译错误 哪行会使用动态选择?
VengefulSLList<Integer> vsl = new VengefulSLList<Integer>(9);
SLList<Integer> sl = vsl;
这两行没有错误 声明了一个子类实例vsl(静态类型和动态类型都是子类) 一个父类实例sl也指向vsl(静态类型是父类 动态类型是子类) 因为子类 is a 父类 所以用父类容器来装子类实例是可行的
sl.addLast(50);//addLast方法没有重写 所以会执行父类的方法
sl.removeLast();//removeLast方法被重写 ls动态类型是子类 所以执行子类中的方法
sl.printLostItems();//ls静态类型是父类 父类中未定义printLostItems方法 导致编译错误
VengefulSLList<Integer> vsl2 = sl;//ls静态类型是父类 vs12静态类型是子类 父类不一定都是子类 子类容器无法装父类 导致编译错误
编译时类型:方法调用或使用new关键字声明类型时 比如
SLList<Integer> sl = new VengefulSLList<Integer>();//表达式右侧的编译时类型是子类 编译器检查子类是否是父类 才允许次赋值
VengefulSLList<Integer> vsl = new SLList<Integer>();//表达式右侧的编译时类型是父类 编译器检查时发现错误 父类不一定是子类 编译错误
public static Dog maxDog(Dog d1, Dog d2) { ... }//方法传入的参数是父类Dog 返回值类型是父类Dog 因此调用方法时将具有编译值类型Dog
Poodle frank = new Poodle("Frank", 5);
Poodle frankJr = new Poodle("Frank Jr.", 15);
Dog largerDog = maxDog(frank, frankJr);//传入的参数是子类Poodle 子类是父类 可以编译
Poodle largerPoodle = maxDog(frank, frankJr);//返回值类型是父类Dog 父类不一定是子类 无法将父类对象分配给子类变量 编译错误
但是可以使用强制类型转换() 即告诉编译器不用进行类型检查
封装
在计算机科学术语中,模块可以定义为一组方法,这些方法作为一个整体协同工作以执行一项任务或一组相关任务。这可能类似于表示列表的类。现在,如果一个模块的实现细节在内部被隐藏,并且与它交互的唯一方法是通过一个记录的接口,那么该模块就被称为封装。使用private关键字,外界几乎不可能查看对象内部,确保底层复杂性不会暴露给外界。
高阶函数
可以使用接口实现函数套娃 即把函数作为数据传入另一个函数
public interface IntUnaryFunction {
int apply(int x);
}
public class TenX implements IntUnaryFunction{
@Override
public int apply(int x) {
return 10 * x;
}
public static int twice(IntUnaryFunction f, int x){
return f.apply(f.apply(x));
}
public static void main(String[] args) {
System.out.println(twice(new TenX(), 2));
}
}
Lec10
多态
我们想编译一个maxDog
函数 返回Dog数组中的最大值
public static Dog maxDog(Dog[] dogs){
if(dogs.length ==0 || dogs == null){
return null;
}
Dog maxDog = dogs[0];
for(Dog d : dogs){//用dogs数组里的元素依次为Dog d赋值
if(d.size > maxDog.size){
maxDog = d;
}
}
return maxDog;
}
那么如果我们想要不止对比Dog 也想对比其他类型的数组呢 就可以借助接口
- 创建一个OurComparable接口 包含compareTo方法(this.size大则返回1 与o.size相等则返回0 小则返回-1)
public interface OurComparable {
public int compareTo(Object o);
}
- 在Dog类中实现compareTo方法
public class Dog implements OurComparable {//编译器会首先检查声明是否为真 如果没有实现comepareTo方法 就会编译错误
private String name;
private int size;
public int compareTo(Object o) {
Dog d = (Dog) o;//将o强制转换为Dog 以便使用变量size
if(this.size < d.size){
return -1;
} else if (this.size == d.size) {
return 0;
}
return 1;
}
}
- 在OurComparable类中添加max方法 不是接受任意对象数组 而是接受Ourparable对象
public static OurComparable max(OurComparable[] items){
int maxDex = 0;
for(int i = 0; i < items.length; i++){
int cmp = items[i].compareTo(items[maxDex]);
if(cmp > 0){
maxDex = i;
}
}
return items[maxDex];
}
使用继承时有几个注意事项
- 方法的形参和返回值类型都是接口名 在使用方法时用实现了该接口的实例来代表该接口对象
- 继承使我们不需要每个类中都写一个最大化代码(如maxDog)
但是使用compareTo方法时 需要进行强制转换 有没有不进行强制转换的办法呢 java中有一个已经存在的Comparable接口 接受的是泛型 就可以不用进行强制转换了
public class Dog implements Comparable<Dog> {
...
public int compareTo(Dog uddaDog) {
return this.size - uddaDog.size;
}
}
如果我们不想使用狗的大小而是名字字符串进行排序呢 下面我们引出一个新的接口Comparator 其中声明了compare方法(对两只狗进行比较) 并在Dog中使用嵌套类来实现Comparator接口
public interface Comparator<T> {
int compare(T o1, T o2);
}
import java.util.Comparator;
public class Dog implements Comparable<Dog> {
private String name;
private int size;
public Dog(String n, int s) {
name = n;
size = s;
}
public void bark() {
System.out.println(name + " says: bark");
}
public int compareTo(Dog uddaDog) {//返回size的差值
return this.size - uddaDog.size;
}
private static class NameComparator implements Comparator<Dog>{//必须声明为静态类 这样就可以无需实例化Dog来获取NameComparator
public int compare(Dog a, Dog b) {
return a.name.compareTo(b.name);//利用Comparable中的compare方法
}
}
public static Comparator<Dog> getNameComparator() {
return new NameComparator();
}
}
import java.util.Comparator;
public class DogLauncher {
public static void main(String[] args) {
Dog d1 = new Dog("Elyese", 3);
Dog d2 = new Dog("Sture", 9);
Dog d3 = new Dog("Ben", 15);
Comparator<Dog> nc = Dog.getNameComparator();
if(nc.compare(d1, d3) > 0){
d1.bark();
}else {
d3.bark();
}
}
}
总而言之 我们有一个 Dog 类 它有一个私有的NameComparator类和一个返回NameComparator的方法 我们可以用它来按名称的字母顺序比较狗 我们可以这样检索我们的NameComparator
Comparator<Dog> nc = Dog.getNameComparator();
Lec11
List
java内置了列表List接口和几种实现 如ArrayList(使用时必须确定它的一个实现
标准用法是
java.util.List<Integer> L = new java.util.ArrayList<>();
或者导入java库
import java.util.List;
import java.util.ArrayList;
public class Example {
public static void main(String[] args) {
List<Integer> L = new ArrayList<>();
L.add(5);
L.add(10);
System.out.println(L);
}
}
Set
java内置了Set接口和几种实现 如HashSet(使用时必须确定它的一个实现
类似地
import java.util.Set;
import java.util.HashSet;
public class Example {
public static void main(String[] args) {
Set<String> S = new HashSet<>();
S.add("Tokyo");
S.add("Lagos");
System.out.println(S.contains("Tokyo"));//L中是否存在Tokyo
}
}
ArraySet
我们要自己创造一个数组集 包括以下功能
add(value)
:将值添加到集合中(如果尚不存在)contains(value)
:检查ArraySet是否包含此值size()
:返回有几个值- tips
抛出自定义异常的格式为throw new ExceptionObject(parameter1, ...)
例如IllegalArgumentException(String)
抛出异常为自定义的字符串
import java.util.Iterator;
public class ArraySet<T> implements Iterable<T>{
private T[] items;
private int size;
public ArraySet() {
items = (T[]) new Object[100];
size = 0;
}
/* Returns true if this map contains a mapping for the specified key.
*/
public boolean contains(T x) {
for(int i =0; i< size; i++){
if(items[i].equals(x)){
return true;
}
}
return false;
}
/* Associates the specified value with the specified key in this map.
Throws an IllegalArgumentException if the key is null. */
public void add(T x) {
if(x == null){
throw new IllegalArgumentException("can't add null");
}
if(contains(x)) {
return;
}
items[size] = x;
size += 1;
}
/* Returns the number of key-value mappings in this map. */
public int size() {
return size;
}
public static void main(String[] args) {
ArraySet<String> s = new ArraySet<>();
s.add(null);
s.add("horse");
s.add("fish");
s.add("house");
s.add("fish");
System.out.println(s.contains("horse"));
System.out.println(s.size());
}
/* Also to do:
1. Make ArraySet implement the Iterable<T> interface.
2. Implement a toString method.
3. Implement an equals() method.
*/
}
迭代器
我们有一个Set数组 要依次检查并打印 可以这样操作
public static void main(String[] args) {
Set<Integer> javaset = new HashSet<>();
javaset.add(5);
javaset.add(10);
javaset.add(15);
Iterator<Integer> seer = javaset.iterator();//编译器会检查HashSet是否有iterator()方法
while (seer.hasNext()) {//编译器会检查迭代器接口是否有hasNext和next方法
int i = seer.next();
System.out.println(i);
}
}
因此如果我们想构建自己的ArraySet类来支持迭代 也需要满足上面两个条件
public Iterator<T> iterator() {//ArraySet需要有iterator方法 返回接口
return new ArraySetIterator();
}
private class ArraySetIterator implements Iterator<T> {//接口要有hasNex方法来判断数组里还是否有变量 next方法来返回当前变量
private int wizPos;
public ArraySetIterator() {
wizPos = 0;
}
public boolean hasNext() {
return wizPos < size;
}
public T next() {
T returnItem = items[wizPos];
wizPos += 1;
return returnItem;
}
}
public static void main(String[] args) {
ArraySet<Integer> aset = new ArraySet<>();
aset.add(5);
aset.add(10);
aset.add(15);
Iterator<Integer> aseer = aset.iterator();
while(aseer.hasNext()) {
int i = aseer.next();
System.out.println(i);
}
}
}
另外有一个化简的迭代器可以使用
for (数据类型 变量名 : 可迭代对象){}
数据类型是可迭代对象里数据的类型 迭代时将依次为变量赋值为迭代对象里的元素
for (int i : aset) {
System.out.println(i);
}
但注意 使用时要先让java了解要迭代的对象是可迭代的 即在ArraySet数组后继承接口implements Iterable(但上面我们自己写的迭代器可以省略这一步)
Object方法
所有类都继承自Object类 继承的方法如下
我们想要通过继承来覆盖前两个方法
toString
toString()
方法提供对象的字符串表示形式 比如在调用System.out.println(dog)
时 实际上编译器是这样做的
String s = dog.toString()
System.out.println(s)
编写toString方法 以便在打印ArraySet时打印大括号内用逗号分隔的元素即 {1, 2, 3, 4} 下面是我们的一个解决办法
@Override
public String toString() {
String returnString = "{";
for (int i = 0; i < size; i += 1) {
returnString += items[i];
returnString += ", ";
}
returnString += "}";
return returnString;
}
但是returnString += items[i];
实际上是创建一个新的字符串 而不是在原字符串上添加字符 所以是比较耗费时间的 因此我们想了一个更高效的解决方式
Java 有一个名为 StringBuilder
的引用类型 它会创建一个可变的字符串对象 可以继续追加到同一个字符串
@Override
public String toString() {
StringBuilder returnSB = new StringBuilder("{");
for (int i = 0; i < size - 1; i += 1) {
returnSB.append(items[i].toString());
returnSB.append(", ");
}
returnSB.append(items[size - 1]);
returnSB.append("}");
return returnSB.toString();
}
equal
==
在java中是检查等号两边在内存中是否是同一对象(对基本类型来说是检查值是否相同 对引用类型来说是检查地址/指针是否相同)相同则返回true 不同则返回false
equal(Object)
的作用类似于==
不同的是如果两个集合具有相同的元素(顺序不重要 集合是无序的) 那么也被视为相等
重写equal方法有以下几个要求
下面是我们的解决办法
public boolean equals(Object other) {
if(this == other){
return true;
}
if(other == null){
return false;
}
if(this.getClass() != other.getClass()){
return false;
}
ArraySet<T> o = (ArraySet<T>) other;
if(this.size != o.size){
return false;
}
for (T item : this){
if(!o.contains(item)){
return false;
}
}
return true;
}
下面是本节内容的完整代码
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class ArraySet<T> implements Iterable<T>{
private T[] items;
private int size;
public ArraySet() {
items = (T[]) new Object[100];
size = 0;
}
/* Returns true if this map contains a mapping for the specified key.
*/
public boolean contains(T x) {
for(int i =0; i< size; i++) {
if (items[i].equals(x)) {
return true;
}
}
return false;
}
/* Associates the specified value with the specified key in this map.
Throws an IllegalArgumentException if the key is null. */
public void add(T x) {
if(x == null){
throw new IllegalArgumentException("can't add null");
}
if(contains(x)) {
return;
}
items[size] = x;
size += 1;
}
/* Returns the number of key-value mappings in this map. */
public int size() {
return size;
}
/** returns an iterator (a.k.a. seer) into ME */
public Iterator<T> iterator() {
return new ArraySetIterator();
}
private class ArraySetIterator implements Iterator<T> {
private int wizPos;
public ArraySetIterator() {
wizPos = 0;
}
public boolean hasNext() {
return wizPos < size;
}
public T next() {
T returnItem = items[wizPos];
wizPos += 1;
return returnItem;
}
}
@Override
public String toString() {
StringBuilder returnSB = new StringBuilder("{");
for (int i = 0; i < size - 1; i += 1) {
returnSB.append(items[i].toString());
returnSB.append(", ");
}
returnSB.append(items[size - 1]);
returnSB.append("}");
return returnSB.toString();
}
@Override
public boolean equals(Object other) {
if(this == other){
return true;
}
if(other == null){
return false;
}
if(this.getClass() != other.getClass()){
return false;
}
ArraySet<T> o = (ArraySet<T>) other;
if(this.size != o.size){
return false;
}
for (T item : this){
if(!o.contains(item)){
return false;
}
}
return true;
}
public static void main(String[] args) {
ArraySet<Integer> aset = new ArraySet<>();
aset.add(5);
aset.add(10);
aset.add(15);
//iteration
for (int i : aset) {
System.out.println(i);
}
//toString
System.out.println(aset);
//equals
ArraySet<Integer> aset2 = new ArraySet<>();
aset2.add(5);
aset2.add(23);
aset2.add(42);
System.out.println(aset.equals(aset2));
System.out.println(aset.equals(null));
System.out.println(aset.equals("fish"));
System.out.println(aset.equals(aset));
}
}