CS61B Lec9、10、11 Lab4

47 阅读11分钟

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>

list_subclasses.png

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();
    }
}

类型检查

image.png 动态方法选择:如果父类中的方法被子类重写 则在运行时编译器根据对象的静态类型来确定某些内容是否有效 根据动态类型确定调用的具体方法
比如下面的程序 哪行会导致编译错误 哪行会使用动态选择? image.png

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接口 接受的是泛型 就可以不用进行强制转换了 comparable_interface.png

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();

comparator.png image.png

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);
    }
}

image.png

因此如果我们想构建自己的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类 继承的方法如下 image.png

我们想要通过继承来覆盖前两个方法

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方法有以下几个要求
image.png 下面是我们的解决办法

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));
    }
}