diff --git a/SUMMARY.md b/SUMMARY.md index bc45e7f..d26ffc6 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -14,15 +14,15 @@ * [泛型](docs/generics.md) * [关键字](docs/keywords.md) * [IO](docs/io.md) - * [I/O 流](docs/IO Streams.md) - * [文件 I/O](docs/File IO.md) + * [I/O 流](docs/io-streams.md) + * [文件 I/O](docs/file-io.md) * [并发](docs/concurrency.md) - * [进程(Processes )和线程(Threads)](docs/concurrency-Processes and Threads.md) + * [进程(Processes )和线程(Threads)](docs/concurrency-Processes%20and%20Threads.md) * [同步](docs/concurrency-Synchronization.md) * [活跃度(Liveness)](docs/concurrency-Liveness.md) - * [Guarded Blocks](docs/concurrency-Guarded Blocks.md) - * [不可变对象(Immutable Objects)](docs/concurrency-Immutable Objects.md) - * [高级并发对象](docs/concurrency-High Level Concurrency Objects.md) + * [Guarded Blocks](docs/concurrency-Guarded%20Blocks.md) + * [不可变对象(Immutable Objects)](docs/concurrency-Immutable%20Objects.md) + * [高级并发对象](docs/concurrency-High%20Level%20Concurrency%20Objects.md) * [集合框架](docs/collection-framework.md) * 网络编程 * [网络基础](docs/networking.md) diff --git a/docs/File IO.md b/docs/file-io.md similarity index 99% rename from docs/File IO.md rename to docs/file-io.md index 77be4ce..1dacd64 100644 --- a/docs/File IO.md +++ b/docs/file-io.md @@ -533,7 +533,6 @@ Files 方法可以接受 可变参数,用法如 几个 Files 的方法,如 move,是可以在某些文件系统上执行某些原子操作的。 -原子文件操作是不能被中断或“部分”进行的操作。无论是执行或操作失败的整个操作。当你对文件系统的同一区域运行多个进程,并且需要保证每个进程访问一个完整的文件,这是非常重要的。 原子文件操作是不能被中断或不能进行“部分”的操作。整个操作要不就执行不要就操作失败。在多个进程中操作相同的文件系统,需要保证每个进程访问一个完整的文件,这是非常重要的。 diff --git a/docs/generics.md b/docs/generics.md index 3025141..b6f6b3c 100644 --- a/docs/generics.md +++ b/docs/generics.md @@ -34,7 +34,7 @@ String s = list.get(0); // no cast 通过使用泛型,程序员可以实现工作在不同类型集合的通用算法,并且是可定制,类型安全,易于阅读。 -## 泛型类型(Generic Types) +## 泛型类型(Generic Type) 泛型类型是参数化类型的泛型类或接口。下面是一个 Box 类例子来说明这个概念。 @@ -188,11 +188,787 @@ OrderedPair> p = new OrderedPair<>("primes", new Box { public void set(T t) { /* ... */ } // ... } ``` -为了创建 \ No newline at end of file +要创建参数化类型的`Box`,需要为形式类型参数T提供实际的类型参数: + +```java +Box intBox = new Box<>(); +``` + +如果想省略实际的类型参数,则需要创建一个`Box`的原生类型: + +```java +Box rawBox = new Box(); +``` + +因此,Box是泛型`Box`的原生类型。但是,非泛型的类或接口类型不是原始类型。 + + +JDK为了保证向后兼容,允许将参数化类型分配给其原始类型: + +```java +Box stringBox = new Box<>(); +Box rawBox = stringBox; // OK +``` + +但如果将原始类型与参数化类型进行管理,则会得到告警: + +```java +Box rawBox = new Box(); // rawBox is a raw type of Box +Box intBox = rawBox; // warning: unchecked conversion +``` + +如果使用原始类型调用相应泛型类型中定义的泛型方法,也会收到警告: + +```java +Box stringBox = new Box<>(); +Box rawBox = stringBox; +rawBox.set(8); // warning: unchecked invocation to set(T) +``` + + +警告显示原始类型绕过泛型类型检查,将不安全代码的捕获推迟到运行时。因此,开发人员应该避免使用原始类型。 + + +## 泛型方法(Generic Method) + +泛型方法是引入其自己的类型参数的方法。这类似于声明泛型类型,但类型参数的范围仅限于声明它的方法。允许使用静态和非静态泛型方法,以及泛型类构造函数。 + +泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回类型之前。对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前。 + +下面例子中,Util类包含一个泛型方法compare,用于比较两个Pair对象: + +```java +public class Util { + public static boolean compare(Pair p1, Pair p2) { + return p1.getKey().equals(p2.getKey()) && + p1.getValue().equals(p2.getValue()); + } +} + +public class Pair { + + private K key; + private V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + public void setKey(K key) { this.key = key; } + public void setValue(V value) { this.value = value; } + public K getKey() { return key; } + public V getValue() { return value; } +} +``` + +以下是方法的调用: + +```java +Pair p1 = new Pair<>(1, "apple"); +Pair p2 = new Pair<>(2, "pear"); +boolean same = Util.compare(p1, p2); +``` + +其中,compare方法的类型通常可以省略,因为编译器将推断所需的类型: + +```java +Pair p1 = new Pair<>(1, "apple"); +Pair p2 = new Pair<>(2, "pear"); +boolean same = Util.compare(p1, p2); +``` + + +## 有界类型参数(Bounded Type Parameter) + + +有时可能希望限制可在参数化类型中用作类型参数的类型。例如,对数字进行操作的方法可能只想接受Number或其子类的实例。这时,就需要用到有界类型参数。 + +要声明有界类型参数,先要列出类型参数的名称,然后是extends关键字,后面跟着它的上限,比如下面例子中的Number: + + +```java +public class Box { + + private T t; + + public void set(T t) { + this.t = t; + } + + public T get() { + return t; + } + + public void inspect(U u){ + System.out.println("T: " + t.getClass().getName()); + System.out.println("U: " + u.getClass().getName()); + } + + public static void main(String[] args) { + Box integerBox = new Box(); + integerBox.set(new Integer(10)); + integerBox.inspect("some text"); // error: this is still String! + } +} +``` + +上面代码将会编译失败,报错如下: + +``` +Box.java:21: inspect(U) in Box cannot + be applied to (java.lang.String) + integerBox.inspect("10"); + ^ +1 error +``` + + +除了限制可用于实例化泛型类型的类型之外,有界类型参数还允许调用边界中定义的方法: + +```java +public class NaturalNumber { + + private T n; + + public NaturalNumber(T n) { this.n = n; } + + public boolean isEven() { + return n.intValue() % 2 == 0; + } + + // ... +} +``` + + +上面例子中,isEven方法通过n调用Integer类中定义的intValue方法。 + +### 多个边界 + +前面的示例说明了使用带有单个边界的类型参数,但是类型参数其实是可以有多个边界的: + +```java + +``` + +具有多个边界的类型变量是绑定中列出的所有类型的子类型。如果其中一个边界是类,则必须首先指定它。例如: + +```java +Class A { /* ... */ } +interface B { /* ... */ } +interface C { /* ... */ } + +class D { /* ... */ } +``` + +如果未首先指定绑定A,则会出现编译时错误: + +```java +class D { /* ... */ } // compile-time error +``` + + +**注**:在有界类型参数中的extends,即可以表示“extends”(类中的继承)也可以表示“implements”(接口中的实现)。 + + + +## 泛型的继承和子类型 + +在Java中,只要类型兼容,就可以将一种类型的对象分配给另一种类型的对象。例如,可以将Integer分配给Object,因为Object是Integer的超类之一: + +```java +Object someObject = new Object(); +Integer someInteger = new Integer(10); +someObject = someInteger; // OK +``` + +在面向对象的术语中,这种关系被称为“is-a”。 由于Integer是一种Object,因此允许赋值。但是Integer同时也是一种Number,所以下面的代码也是有效的: + + +```java +public void someMethod(Number n) { /* ... */ } + +someMethod(new Integer(10)); // OK +someMethod(new Double(10.1)); // OK +``` + +在泛型中也是如此。 可以执行泛型类型调用,将Number作为其类型参数传递,如果参数与Number兼容,则允许任何后续的add调用: + +```java +Box box = new Box(); +box.add(new Integer(10)); // OK +box.add(new Double(10.1)); // OK +``` + + +现在考虑下面的方法: + +```java +public void boxTest(Box n) { /* ... */ } +``` + + +通过查看其签名,可以看到上述方法接受一个类型为`Box`的参数。也许你可能会想当然的认为这个方法也能接收`Box`或`Box`吧? 答案是否定的,因为`Box`和`Box`并不是`Box`的子类型。在使用泛型编程时,这是一个常见的误解,虽然Integer和Double是Number的子类型。 + +下图展示了泛型和子类型的之间的关系: + +![](../images/generics/generics-subtypeRelationship.gif) + + + + +### 泛型类及子类 + + +可以通过扩展或实现泛型类或接口来对其进行子类型化。一个类或接口的类型参数与另一个类或参数的类型参数之间的关系由extends和implements子句确定。 + +以Collections类为例,`ArrayList`实现了`List`,而`List`扩展了`Collection`。所以`ArrayList`是`List`的子类型,同时它也是`Collection`的子类型。只要不改变类型参数,就会在类型之间保留子类型关系。下图展示了这些类的层次关系: + + +![](../images/generics/generics-sampleHierarchy.gif) + + + + +现在假设我们想要定义我们自己的列表接口PayloadList,它将泛型类型P的可选值与每个元素相关联。它的声明可能如下: + +```java +interface PayloadList extends List { + void setPayload(int index, P val); + ... +} +``` + +以下是PayloadList参数化的`List`的子类型: + +* PayloadList +* PayloadList +* PayloadList + + +这些类的关系图如下: + + +![](../images/generics/generics-payloadListHierarchy.gif) + +## 通配符 + +通配符(?)通常用于表示未知类型。通配符可用于各种情况: +* 作为参数,字段或局部变量的类型; +* 作为返回类型。 + +在泛型中,通配符不用于泛型方法调用,泛型类实例创建或超类型的类型参数。 + + +### 上限有界通配符 + +可以使用上限通配符来放宽对变量的限制。例如,要编写一个适用于`List`、`List`和`List`的方法,可以通过使用上限有界通配符来实现这一点。比如下面的例子: + +```java +public static double sumOfList(List list) { + double s = 0.0; + for (Number n : list) + s += n.doubleValue(); + return s; +} +``` + +可以指定类型为List: + +```java +List li = Arrays.asList(1, 2, 3); +System.out.println("sum = " + sumOfList(li)); +``` + +则输出结果为: + + +``` +sum = 6.0 +``` + +可以指定类型为List: + +```java +List ld = Arrays.asList(1.2, 2.3, 3.5); +System.out.println("sum = " + sumOfList(ld)); +``` + +则输出结果为: + + +``` +sum = 7.0 +``` + + + +### 无界通配符 + +无界通配符类型通常用于定义未知类型,比如`List`。 + + +无界通配符通常有两种典型的用法: + +#### 1. 需要使用Object类中提供的功能实现的方法 + +考虑以下方法printList: + +```java +public static void printList(List list) { + for (Object elem : list) + System.out.println(elem + " "); + System.out.println(); +} +``` + +printList只能打印一个Object实例列表,不能打印`List`,`List`,`List`等,因为它们不是`List`的子类型。 + + +#### 2. 当代码使用泛型类中不依赖于类型参数的方法 + +例如,List.size或List.clear。实际上,经常使用`Class`,因为`Class`中的大多数方法都不依赖于T。比如下面的例子: + + +```java +public static void printList(List list) { + for (Object elem: list) + System.out.print(elem + " "); + System.out.println(); +} +``` + +因为`List`是`List`的子类,因此可以打印出任何类型: + +```java +List li = Arrays.asList(1, 2, 3); +List ls = Arrays.asList("one", "two", "three"); +printList(li); +printList(ls); +``` + +因此,要区分场景来选择使用`List`或是`List`。如果想插入一个Object或者是任意Object的子类,就可以使用`List`。但只能在`List`中插入null。 + + + +### 下限有界通配符 + +下限有界通配符将未知类型限制为该类型的特定类型或超类型。使用下限有界通配符语法为``。 + +假设要编写一个将Integer对象放入列表的方法。为了最大限度地提高灵活性,希望该方法可以处理`List`、`List`或者是`List`等可以保存Integer值的方法。 + +比如下面的例子,将数字1到10添加到列表的末尾: + +```java +public static void addNumbers(List list) { + for (int i = 1; i <= 10; i++) { + list.add(i); + } +} +``` + +### 通配符及其子类 + + +可以使用通配符在泛型类或接口之间创建关系。 + +给定以下两个常规(非泛型)类: + + +```java +class A { /* ... */ } +class B extends A { /* ... */ } +``` + +下面的代码是成立的: + +```java +B b = new B(); +A a = b; +``` + +此示例显示常规类的继承遵循此子类型规则:如果B扩展A,则类B是类A的子类型。但此规则不适用于泛型类型: + + +```java +List lb = new ArrayList<>(); +List la = lb; // compile-time error +``` + +鉴于Integer是Number的子类型,`List`和`List`之间的关系是什么?下图显示了`List`和`List`的公共父级是未知类型`List`。 + +![](../images/generics/generics-listParent.gif) + + +尽管Integer是Number的子类型,但`List`并不是`List`的子类型。 + +为了在这些类之间创建关系以便代码可以通过`List`的元素访问Number的方法,需使用上限有界通配符: + +```java +List intList = new ArrayList<>(); +List numList = intList; // OK. List is a subtype of List +``` + +因为Integer是Number的子类型,而numList是Number对象的列表,所以intList(Integer对象列表)和numList之间现在存在关系。下图显示了使用上限和下限有界通配符声明的多个List类之间的关系。 + +![](../images/generics/generics-wildcardSubtyping.gif) + +## 类型擦除 + +泛型被引入到Java语言中,以便在编译时提供更严格的类型检查并支持泛型编程。为了实现泛型,Java编译器将类型擦除应用于: + +* 如果类型参数是无界的,则用泛型或对象替换泛型类型中的所有类型参数。因此,生成的字节码仅包含普通的类\接口和方法。 +* 如有必要,插入类型铸件以保持类型安全。 +* 生成桥接方法以保留扩展泛型类型中的多态性。 + +类型擦除能够确保不为参数化类型创建新类,因此,泛型不会产生运行时开销。 + + +### 擦除泛型类型 + +在类型擦除过程中,Java编译器将擦除所有类型参数,并在类型参数有界时将其替换为第一个绑定,如果类型参数为无界,则替换为Object。 + +考虑以下表示单链表中节点的泛型类: + + +```java +public class Node { + + private T data; + private Node next; + + public Node(T data, Node next) { + this.data = data; + this.next = next; + } + + public T getData() { return data; } + // ... +} +``` + +因为类型参数T是无界的,所以Java编译器将其替换为Object: + +```java +public class Node { + + private Object data; + private Node next; + + public Node(Object data, Node next) { + this.data = data; + this.next = next; + } + + public Object getData() { return data; } + // ... +} +``` + +在以下示例中,泛型Node类使用有界类型参数: + + +```java +public class Node> { + + private T data; + private Node next; + + public Node(T data, Node next) { + this.data = data; + this.next = next; + } + + public T getData() { return data; } + // ... +} +``` + + +Java编译器将有界类型参数T替换为第一个绑定类Comparable: + +```java +public class Node { + + private Comparable data; + private Node next; + + public Node(Comparable data, Node next) { + this.data = data; + this.next = next; + } + + public Comparable getData() { return data; } + // ... +} +``` + + +### 擦除泛型方法 + +Java编译器还会擦除泛型方法参数中的类型参数。请考虑以下泛型方法: + +```java +public static int count(T[] anArray, T elem) { + int cnt = 0; + for (T e : anArray) + if (e.equals(elem)) + ++cnt; + return cnt; +} +``` + +因为T是无界的,Java编译器将会将它替换为Object: + +```java +public static int count(Object[] anArray, Object elem) { + int cnt = 0; + for (Object e : anArray) + if (e.equals(elem)) + ++cnt; + return cnt; +} +``` + +假设定义了以下类: + +```java +class Shape { /* ... */ } +class Circle extends Shape { /* ... */ } +class Rectangle extends Shape { /* ... */ } +``` + +可以使用泛型方法绘制不同的图形: + +```java +public static void draw(T shape) { /* ... */ } +``` + +Java编译器将会将T替换为Shape: + +```java +public static void draw(Shape shape) { /* ... */ } +``` + + +## 使用泛型的一些限制 + +### 无法使用基本类型实例化泛型 + +请考虑以下参数化类型: + +```java +class Pair { + + private K key; + private V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + // ... +} +``` + +创建Pair对象时,不能将基本类型替换为类型参数K或V: + +```java +Pair p = new Pair<>(8, 'a'); // compile-time error +``` + +只能将非基本类型替换为类型参数K和V: + +```java +Pair p = new Pair<>(8, 'a'); +``` + + +此时,Java编译器会自动装箱,将8转为`Integer.valueOf(8)`,将'a'转为`Character('a')`: + +```java +Pair p = new Pair<>(Integer.valueOf(8), new Character('a')); +``` + +### 无法创建类型参数的实例 + +无法创建类型参数的实例。例如,以下代码导致编译时错误: + +```java +public static void append(List list) { + E elem = new E(); // compile-time error + list.add(elem); +} +``` + +作为解决方法,您可以通过反射创建类型参数的对象: + +```java +public static void append(List list, Class cls) throws Exception { + E elem = cls.newInstance(); // OK + list.add(elem); +} +``` + +可以按如下方式调用append方法: + +```java +List ls = new ArrayList<>(); +append(ls, String.class); +``` + +### 无法声明类型为类型参数的静态字段 + +类的静态字段是类的所有非静态对象共享的类级变量。因此,不允许使用类型参数的静态字段。考虑以下类: + +```java +public class MobileDevice { + private static T os; + + // ... +} +``` + +如果允许类型参数的静态字段,则以下代码将混淆: + +```java +MobileDevice phone = new MobileDevice<>(); +MobileDevice pager = new MobileDevice<>(); +MobileDevice pc = new MobileDevice<>(); +``` + +因为静态字段os由phone、pager、pc所共享的,所以os的实际类型是什么呢?它不能同时是Smartphone、Pager或者TabletPC。因此,无法创建类型参数的静态字段。 + + +### 无法使用具有参数化类型的强制转换或instanceof + +因为Java编译器会擦除通用代码中的所有类型参数,所以无法验证在运行时使用泛型类型的参数化类型: + +```java +public static void rtti(List list) { + if (list instanceof ArrayList) { // compile-time error + // ... + } +} +``` + +传递给rtti方法的参数化类型集是: + +```java +S = { ArrayList, ArrayList LinkedList, ... } +``` + +运行时不跟踪类型参数,因此无法区分`ArrayList`和`ArrayList`。可以做的最多是使用无界通配符来验证列表是否为ArrayList: + +```java +public static void rtti(List list) { + if (list instanceof ArrayList) { // OK; instanceof requires a reifiable type + // ... + } +} +``` + +通常,除非通过无界通配符对参数化进行参数化,否则无法强制转换为参数化类型。例如: + +```java +List li = new ArrayList<>(); +List ln = (List) li; // compile-time error +``` + +但是,在某些情况下,编译器知道类型参数始终有效并允许强制转换。例如: + + +```java +List l1 = ...; +ArrayList l2 = (ArrayList)l1; // OK +``` + +### 无法创建参数化类型的数组 + +无法创建参数化类型的数组。例如,以下代码无法编译: + +```java +List[] arrayOfLists = new List[2]; // compile-time error +``` + +以下代码说明了将不同类型插入到数组中时会发生什么: + +```java +Object[] strings = new String[2]; +strings[0] = "hi"; // OK +strings[1] = 100; // An ArrayStoreException is thrown. +``` + +如果使用通用列表尝试相同的操作,则会出现问题: + +```java +Object[] stringLists = new List[]; // compiler error, but pretend it's allowed +stringLists[0] = new ArrayList(); // OK +stringLists[1] = new ArrayList(); // An ArrayStoreException should be thrown, + // but the runtime can't detect it. +``` + +如果允许参数化列表数组,则前面的代码将无法抛出所需的ArrayStoreException。 + +### 无法创建、捕获或抛出参数化类型的对象 + +泛型类不能直接或间接扩展Throwable类。例如,以下类将无法编译: + +```java +// Extends Throwable indirectly +class MathException extends Exception { /* ... */ } // compile-time error + +// Extends Throwable directly +class QueueFullException extends Throwable { /* ... */ // compile-time error +``` + +方法无法捕获类型参数的实例: + +```java +public static void execute(List jobs) { + try { + for (J job : jobs) + // ... + } catch (T e) { // compile-time error + // ... + } +} +``` + +但是可以在throws子句中使用类型参数: + +```java +class Parser { + public void parse(File file) throws T { // OK + // ... + } +} +``` + + +### 无法重载每个重载的形式参数类型擦除到相同原始类型的方法 + +类不能有两个重载方法,因为它们在类型擦除后具有相同的签名。 + + + + + +```java +public class Example { + public void print(Set strSet) { } + public void print(Set intSet) { } +} +``` + + +上述例子将生成编译时错误。 diff --git a/docs/IO Streams.md b/docs/io-streams.md similarity index 100% rename from docs/IO Streams.md rename to docs/io-streams.md diff --git a/docs/jdbc.md b/docs/jdbc.md index bff53a6..afac062 100644 --- a/docs/jdbc.md +++ b/docs/jdbc.md @@ -1 +1,4 @@ # JDBC + + +JDBC的内容已经单独开了课程,见 \ No newline at end of file diff --git a/images/generics/generics-listParent.gif b/images/generics/generics-listParent.gif new file mode 100644 index 0000000..de9a3a6 Binary files /dev/null and b/images/generics/generics-listParent.gif differ diff --git a/images/generics/generics-payloadListHierarchy.gif b/images/generics/generics-payloadListHierarchy.gif new file mode 100644 index 0000000..986f37a Binary files /dev/null and b/images/generics/generics-payloadListHierarchy.gif differ diff --git a/images/generics/generics-sampleHierarchy.gif b/images/generics/generics-sampleHierarchy.gif new file mode 100644 index 0000000..73d2a59 Binary files /dev/null and b/images/generics/generics-sampleHierarchy.gif differ diff --git a/images/generics/generics-subtypeRelationship.gif b/images/generics/generics-subtypeRelationship.gif new file mode 100644 index 0000000..46a9800 Binary files /dev/null and b/images/generics/generics-subtypeRelationship.gif differ diff --git a/images/generics/generics-wildcardSubtyping.gif b/images/generics/generics-wildcardSubtyping.gif new file mode 100644 index 0000000..1487418 Binary files /dev/null and b/images/generics/generics-wildcardSubtyping.gif differ