2023年10月3日星期二

Review of Java

 Interface(is -a)

  • 类似于C语言的.h文件,举个实体的例子,假设狗是interface,那么贵宾犬和哈巴狗就是狗的implents的class,interface有一些类,是狗共有的特性,如public void bark();,在interface里面只有声明方法就够了。如何想在interface里面添加有函数体的方法,需要用default声明。
  • 在public class poodle implents dog,相同的方法,如bark,需要@override(虽然不写也许,但是为了防止一些意想不到的错误,如bark写出了bart),
  • 对于override和overload区别,override是在子类中存在着与接口相同的方法,而overload是相同的方法,但是参数不一样。

动态类型和静态类型,动态类型是运行时候的类型,动态类型的方法覆盖了静态类型的方法,在调用函数的时候,调用的是动态类型的函数。

假设同一个类中有两个方法

public static void peek(List61B<String> list) {
    System.out.println(list.getLast());
}
public static void peek(SLList<String> list) {
    System.out.println(list.getFirst());
}

然后你运行这段代码

SLList<String> SP = new SLList<String>();
List61B<String> LP = SP;
SP.addLast("elk");
SP.addLast("are");
SP.addLast("cool");
peek(SP);
peek(LP);

第一次调用 peek() 将使用接受 SLList 的第二个 peek 方法。第二次调用 peek() 将使用第一个 peek 方法,该方法接受 List61B。这是因为两个重载方法之间的唯一区别是参数的类型。当Java检查要调用哪个方法时,它会检查静态类型并使用相同类型的参数调用该方法。


implents 是is-a关系,如Dog implents Animal,但不是has-a关系。

EXTEND

假设我们想要构建一个 RotatingSLList,它具有与 SLList 相同的功能,例如addFirstsize等,但需要执行一个附加rotateRight操作来将最后一项移至列表的前面。

实现此目的的一种方法是复制并粘贴 SLList 中的所有方法并rotateRight在其上写入 - 但这样我们就无法利用继承的力量!请记住,继承允许子类重用已定义的类中的代码。因此,让我们定义 RotatingSLList 类以继承 SLList。

我们可以在类头中使用关键字来设置这种继承关系,extends如下所示:

public class RotatingSLList<Item> extends SLList<Item>

与 AList 与 List61B 共享“is-a”关系一样,RotatingSLList 与 SLList 共享“is-a”关系。extends关键字使我们能够保留 SLList 的原始功能,同时使我们能够进行修改并添加附加功能



通过使用extends关键字,子类继承父类的所有成员。“成员”包括:

  • 所有实例和静态变量
  • 所有方法
  • 所有嵌套类

注意构造函数不是继承的,私有成员不能被子类直接访问。

public VengefulSLList() {
    super();
    deletedItems = new SLList<Item>();
}

或者不写构造函数,java默认有super();

但是当构造函数有参数的时候,必须用super(x),来call parentClass(x)


Abstract 抽象类

  • 方法可以是公共的或私有的
  • 可以有任何类型的变量
  • 无法实例化
  • 除非指定,否则方法默认是具体的abstract
  • 每个类只能实现一个
对于interface
  • 所有方法都必须是公共的。
  • 所有变量都必须是 public static final。
  • 无法实例化
  • 所有方法默认都是抽象的,除非指定为default
  • 每个类可以实现多个接口

泛型


正如您在视频中看到的,我们可以通过将参数声明为 String 和 Integer 来编写非常有限的方法,如下所示:

public static Integer get(Map61B<String, Integer> map, String key) {
    ...
}

我们限制此方法仅接受Map61B<String, Integer>,这不是我们想要的!我们希望它采用任何类型的Map61B,无论泛型的实际类型是什么。但是,以下方法头会产生编译错误:

public static V get(Map61B<K, V> map, String key) {
    ...
}

这是因为,对于在类头中定义的泛型,Java 会等待用户实例化该类的对象,以便了解每个泛型的实际类型。然而,在这里我们想要一个特定于此方法的泛型。此外,我们并不关心我们的参数中实际的类型KV呈现的类型Map61B——重要的是,无论是什么类型,都会返回V一个类型的对象。V

因此我们看到了对泛型方法的需求。要将方法声明为泛型,必须在返回类型之前指定形式类型参数:

public static <K,V> V get(Map61B<K,V> map, K key) {
    if map.containsKey(key) {
        return map.get(key);
    }
    return null;
}

下面是如何调用它的示例:

ArrayMap<Integer, String> isMap = new ArrayMap<Integer, String>();
System.out.println(mapHelper.get(isMap, 5));

您不需要对要插入的类型进行任何显式声明。Java 可以推断 isMap 是ArrayMapfromIntegersStrings

如果要进行比较的话:并非所有对象都有compareTo方法,不能直接k.compareTo

public static <K extends Comparable<K>, V> K maxKey(Map61B<K, V> map) {...}

ToString

public String toString() {
    String returnString = "{";
    for (int i = 0; i < size; i += 1) {
        returnString += keys[i];
        returnString += ", ";
    }
    returnString += "}";
    return returnString;
}

这个解决方案虽然看似简单优雅,但实际上非常幼稚。这是因为当您在 Java 中使用字符串连接时,如下所示:returnString += keys[i];您实际上不仅仅是附加到returnString,您正在创建一个全新的字符串这是非常低效的,因为创建一个新的字符串对象也需要时间!具体来说,与字符串的长度呈线性关系。

额外问题:假设将一个字符连接到一个字符串需要 1 秒。如果我们有一个大小为 5: 的 ArraySet {1, 2, 3, 4, 5},运行该方法需要多长时间toString()

答案:我们设置returnString为左括号,这需要一秒钟,因为这涉及{到添加到空字符串""添加第一个元素将涉及创建一个全新的字符串,添加 } 和 1,这将需要 2 秒。添加第二个元素需要 3 秒,因为我们需要添加{12此过程继续进行,因此对于整个数组集,总时间为1 + 2 + 3 + 4 + 5 + 6 + 7.

为了解决这个问题,Java 有一个特殊的类,称为StringBuilder它创建一个可变的字符串对象,因此您可以继续附加到同一个字符串对象,而不是每次都创建一个新的字符串对象。

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


练习6.4.3:让我们为ArraySet 类编写一个equals 方法。请记住,集合是唯一元素的无序集合。因此,要使两个集合被视为相等,您只需检查它们是否具有相同的元素。

解决方案

public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (other.getClass() != this.getClass()) {
            return false;
        }
        ArraySet<T> o = (ArraySet<T>) other;
        if (o.size() != this.size()) {
            return false;
        }
        for (T item : this) {
            if (!o.contains(item)) {
                return false;
            }
        }
        return true;
    }

Exception



被check的Exception是需要处理的,也是易于处理的,举一个例子:

public class Eagle {
    public static void gulgate() {
        if (today == “Thursday”) { 
            throw new IOException("hi"); }
        }
    }

$ javac UncheckedExceptionDemo.java
$ java UncheckedExceptionDemo
Exception in thread "main" java.lang.RuntimeException: as a joke.
at UncheckedExceptionDemo.main(UncheckedExceptionDemo.java:3)

解决方法:
public static void gulgate() {
    try {
        if (today == “Thursday”) { 
            throw new IOException("hi"); 
        }
    } catch (Exception e) {
        System.out.println("psych!");
    }
}

或者

如果我们不想在gulgate()方法中处理异常,我们可以将责任推迟到其他地方。我们通过修改方法定义来将方法标记或指定为危险方法,如下所示:

public static void gulgate() throws IOException {
... throw new IOException("hi"); ...
}

由于gulgate()可能会抛出未捕获的异常,现在main()也可以抛出该异常,并且以下代码将无法编译:

public static void main(String[] args) {
    Eagle.gulgate();
}

我们可以通过以下两种方式之一解决这个问题:catch,或者在调用方法中指定。

catch:

public static void main(String[] args) {
    try {
        gulgate();
    } catch(IOException e) {
        System.out.println("Averted!");
    }
} 

Specify

public static void main(String[] args) throws IOException {
    gulgate();
}

当您可以处理那里的问题时捕获错误。防止它逃跑!


Packages and JAR files


You should avoid using the default package except for very small example programs.


Using Packages

如果您从同一个包中访问该类,则可以仅使用其简单名称:

Dog d = new Dog(...);

如果您从包外部访问类,请使用其完整规范名称:

ug.joshh.animal.Dog d = new ug.joshh.animal.Dog(...);

为了让事情变得更简单,您可以导入包,并使用简单的名称!

import ug.joshh.animal.Dog;
...
Dog d = new Dog(...);

创建包

创建包需要以下两个步骤:

1.) 将包名称放在该包中每个文件的顶部

package ug.joshh.animal;

public class Dog {
    private String name;
    private String breed;
    …
}

2.) 将文件存储在具有适当文件夹名称的文件夹中。该文件夹的名称应与您的包匹配:

ieug.joshh.animal包位于 ug/joshh/animal 文件夹中

Default packages

文件顶部没有显式包名称的任何 Java 类都会自动被视为“默认”包的一部分。但是,在编写实际程序时,您应该避免将文件保留在默认包中(除非它是一个非常小的示例程序)。这是因为无法导入默认包中的代码,并且可能会意外地在默认包下创建同名的类。

例如,如果我要在默认包中创建一个“DogLauncher.java”类,我将无法在默认包之外的任何其他地方访问此 DogLauncher 类。

DogLauncher.launch(); // won’t work
default.DogLauncher.launch(); // doesn’t exist

因此,您的 Java 文件通常应以显式包声明开头。


Access Control



黑色的为不加

Private 只有来自给定类的代码才能访问私有成员。对于其他一切来说 都是真正私有的,因为子类、包和其他外部类无法访问私有成员。TL;DR:只有类需要这段代码

Package Private 如果没有写入显式修饰符,这是给予 Java 成员的默认访问权限。包私有意味着属于同一包的类可以访问,但不能访问子类!为什么这有用?通常,包由同一(组)人处理和修改。人们扩展最初没有编写的类也很常见。如果人们选择扩展它,正在扩展的类的原始所有者可能不希望某些功能或成员被篡改 - 因此,package-private 允许那些熟悉程序内部工作原理的人访问和修改某些功能或成员。成员,而它阻止子类化的人做同样的事情。 TL;DR:只有位于同一包中的类才能访问

Protected 受保护的成员受到保护,免受“外部”世界的影响,因此同一包和子类中的类可以访问这些成员,但世界的其余部分(例如包外部的类或非子类)不能! TL;DR:子类型可能需要它,但子类型客户端不会

Public 此关键字向所有人开放访问权限!这通常是包的客户端可以依赖使用的,并且一旦部署,公共成员的签名不应更改。这就像对使用此公共代码的人的承诺和合同,他们将始终可以访问它。通常,如果开发人员想要“摆脱”公共的东西,而不是删除它,他们会称之为“已弃用”。

Powershell中python始终指向anaconda问题

 Powershell中python始终指向anaconda,即便修改Path也没有办法改变,这时候使用 conda config --show,查看auto_activate_base,发现是true,conda config --set auto_activate_base ...