auto commit
This commit is contained in:
parent
12a513cb70
commit
8f466d2c6f
|
@ -14,20 +14,20 @@
|
|||
* [float 与 double](#float-与-double)
|
||||
* [隐式类型转换](#隐式类型转换)
|
||||
* [switch](#switch)
|
||||
* [四、继承](#四继承)
|
||||
* [访问权限](#访问权限)
|
||||
* [抽象类与接口](#抽象类与接口)
|
||||
* [super](#super)
|
||||
* [重写与重载](#重写与重载)
|
||||
* [四、关键字](#四关键字)
|
||||
* [final](#final)
|
||||
* [static](#static)
|
||||
* [五、Object 通用方法](#五object-通用方法)
|
||||
* [概览](#概览)
|
||||
* [equals()](#equals)
|
||||
* [hashCode()](#hashcode)
|
||||
* [toString()](#tostring)
|
||||
* [clone()](#clone)
|
||||
* [六、关键字](#六关键字)
|
||||
* [final](#final)
|
||||
* [static](#static)
|
||||
* [六、继承](#六继承)
|
||||
* [访问权限](#访问权限)
|
||||
* [抽象类与接口](#抽象类与接口)
|
||||
* [super](#super)
|
||||
* [重写与重载](#重写与重载)
|
||||
* [七、反射](#七反射)
|
||||
* [八、异常](#八异常)
|
||||
* [九、泛型](#九泛型)
|
||||
|
@ -446,340 +446,178 @@ switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数
|
|||
|
||||
[StackOverflow : Why can't your switch statement data type be long, Java?](https://stackoverflow.com/questions/2676210/why-cant-your-switch-statement-data-type-be-long-java)
|
||||
|
||||
# 四、继承
|
||||
|
||||
## 访问权限
|
||||
# 四、关键字
|
||||
|
||||
Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。
|
||||
## final
|
||||
|
||||
可以对类或类中的成员(字段以及方法)加上访问修饰符。
|
||||
**1. 数据**
|
||||
|
||||
- 类可见表示其它类可以用这个类创建实例对象。
|
||||
- 成员可见表示其它类可以用这个类的实例对象访问到该成员;
|
||||
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
|
||||
|
||||
protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。
|
||||
|
||||
设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。
|
||||
|
||||
如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。
|
||||
|
||||
字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。
|
||||
- 对于基本类型,final 使数值不变;
|
||||
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
|
||||
|
||||
```java
|
||||
public class AccessExample {
|
||||
public String id;
|
||||
}
|
||||
final int x = 1;
|
||||
// x = 2; // cannot assign value to final variable 'x'
|
||||
final A y = new A();
|
||||
y.a = 1;
|
||||
```
|
||||
|
||||
可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。
|
||||
**2. 方法**
|
||||
|
||||
声明方法不能被子类重写。
|
||||
|
||||
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
|
||||
|
||||
**3. 类**
|
||||
|
||||
声明类不允许被继承。
|
||||
|
||||
## static
|
||||
|
||||
**1. 静态变量**
|
||||
|
||||
- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
|
||||
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
|
||||
|
||||
```java
|
||||
public class AccessExample {
|
||||
public class A {
|
||||
|
||||
private int id;
|
||||
private int x; // 实例变量
|
||||
private static int y; // 静态变量
|
||||
|
||||
public String getId() {
|
||||
return id + "";
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = Integer.valueOf(id);
|
||||
public static void main(String[] args) {
|
||||
// int x = A.x; // Non-static field 'x' cannot be referenced from a static context
|
||||
A a = new A();
|
||||
int x = a.x;
|
||||
int y = A.y;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。
|
||||
**2. 静态方法**
|
||||
|
||||
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
|
||||
|
||||
```java
|
||||
public class AccessWithInnerClassExample {
|
||||
|
||||
private class InnerClass {
|
||||
int x;
|
||||
}
|
||||
|
||||
private InnerClass innerClass;
|
||||
|
||||
public AccessWithInnerClassExample() {
|
||||
innerClass = new InnerClass();
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return innerClass.x; // 直接访问
|
||||
public abstract class A {
|
||||
public static void func1(){
|
||||
}
|
||||
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
|
||||
}
|
||||
```
|
||||
|
||||
## 抽象类与接口
|
||||
|
||||
**1. 抽象类**
|
||||
|
||||
抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。
|
||||
|
||||
抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。
|
||||
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因此这两个关键字与具体对象关联。
|
||||
|
||||
```java
|
||||
public abstract class AbstractClassExample {
|
||||
public class A {
|
||||
|
||||
protected int x;
|
||||
private static int x;
|
||||
private int y;
|
||||
|
||||
public abstract void func1();
|
||||
|
||||
public void func2() {
|
||||
System.out.println("func2");
|
||||
public static void func1(){
|
||||
int a = x;
|
||||
// int b = y; // Non-static field 'y' cannot be referenced from a static context
|
||||
// int b = this.y; // 'A.this' cannot be referenced from a static context
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. 静态语句块**
|
||||
|
||||
静态语句块在类初始化时运行一次。
|
||||
|
||||
```java
|
||||
public class AbstractExtendClassExample extends AbstractClassExample {
|
||||
@Override
|
||||
public void func1() {
|
||||
System.out.println("func1");
|
||||
public class A {
|
||||
static {
|
||||
System.out.println("123");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
A a1 = new A();
|
||||
A a2 = new A();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
|
||||
AbstractClassExample ac2 = new AbstractExtendClassExample();
|
||||
ac2.func1();
|
||||
```
|
||||
|
||||
**2. 接口**
|
||||
|
||||
接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
|
||||
|
||||
从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
|
||||
|
||||
接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
|
||||
|
||||
接口的字段默认都是 static 和 final 的。
|
||||
|
||||
```java
|
||||
public interface InterfaceExample {
|
||||
|
||||
void func1();
|
||||
|
||||
default void func2(){
|
||||
System.out.println("func2");
|
||||
}
|
||||
|
||||
int x = 123;
|
||||
// int y; // Variable 'y' might not have been initialized
|
||||
public int z = 0; // Modifier 'public' is redundant for interface fields
|
||||
// private int k = 0; // Modifier 'private' not allowed here
|
||||
// protected int l = 0; // Modifier 'protected' not allowed here
|
||||
// private void fun3(); // Modifier 'private' not allowed here
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class InterfaceImplementExample implements InterfaceExample {
|
||||
@Override
|
||||
public void func1() {
|
||||
System.out.println("func1");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated
|
||||
InterfaceExample ie2 = new InterfaceImplementExample();
|
||||
ie2.func1();
|
||||
System.out.println(InterfaceExample.x);
|
||||
```
|
||||
|
||||
**3. 比较**
|
||||
|
||||
- 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
|
||||
- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
|
||||
- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
|
||||
- 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
|
||||
|
||||
**4. 使用选择**
|
||||
|
||||
使用接口:
|
||||
|
||||
- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
|
||||
- 需要使用多重继承。
|
||||
|
||||
使用抽象类:
|
||||
|
||||
- 需要在几个相关的类中共享代码。
|
||||
- 需要能控制继承来的成员的访问权限,而不是都为 public。
|
||||
- 需要继承非静态和非常量字段。
|
||||
|
||||
在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
|
||||
|
||||
- [Abstract Methods and Classes](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html)
|
||||
- [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/)
|
||||
- [When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface)
|
||||
|
||||
|
||||
## super
|
||||
|
||||
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super 函数。
|
||||
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
|
||||
|
||||
```java
|
||||
public class SuperExample {
|
||||
|
||||
protected int x;
|
||||
protected int y;
|
||||
|
||||
public SuperExample(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void func() {
|
||||
System.out.println("SuperExample.func()");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class SuperExtendExample extends SuperExample {
|
||||
|
||||
private int z;
|
||||
|
||||
public SuperExtendExample(int x, int y, int z) {
|
||||
super(x, y);
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void func() {
|
||||
super.func();
|
||||
System.out.println("SuperExtendExample.func()");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
SuperExample e = new SuperExtendExample(1, 2, 3);
|
||||
e.func();
|
||||
```
|
||||
|
||||
```html
|
||||
SuperExample.func()
|
||||
SuperExtendExample.func()
|
||||
123
|
||||
```
|
||||
|
||||
[Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html)
|
||||
**4. 静态内部类**
|
||||
|
||||
## 重写与重载
|
||||
|
||||
**1. 重写(Override)**
|
||||
|
||||
存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
|
||||
|
||||
为了满足里式替换原则,重写有以下三个限制:
|
||||
|
||||
- 子类方法的访问权限必须大于等于父类方法;
|
||||
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
|
||||
- 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
|
||||
|
||||
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。
|
||||
|
||||
下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中:
|
||||
|
||||
- 子类方法访问权限为 public,大于父类的 protected。
|
||||
- 子类的返回类型为 ArrayList<Integer>,是父类返回类型 List<Integer> 的子类。
|
||||
- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。
|
||||
- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。
|
||||
非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。
|
||||
|
||||
```java
|
||||
class SuperClass {
|
||||
protected List<Integer> func() throws Throwable {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
public class OuterClass {
|
||||
|
||||
class SubClass extends SuperClass {
|
||||
@Override
|
||||
public ArrayList<Integer> func() throws Exception {
|
||||
return new ArrayList<>();
|
||||
class InnerClass {
|
||||
}
|
||||
|
||||
static class StaticInnerClass {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
|
||||
OuterClass outerClass = new OuterClass();
|
||||
InnerClass innerClass = outerClass.new InnerClass();
|
||||
StaticInnerClass staticInnerClass = new StaticInnerClass();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有查找到再到父类中查看,看是否有继承来的方法。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:
|
||||
静态内部类不能访问外部类的非静态的变量和方法。
|
||||
|
||||
- this.func(this)
|
||||
- super.func(this)
|
||||
- this.func(super)
|
||||
- super.func(super)
|
||||
**5. 静态导包**
|
||||
|
||||
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
|
||||
|
||||
```java
|
||||
/*
|
||||
A
|
||||
|
|
||||
B
|
||||
|
|
||||
C
|
||||
|
|
||||
D
|
||||
*/
|
||||
import static com.xxx.ClassName.*
|
||||
```
|
||||
|
||||
**6. 初始化顺序**
|
||||
|
||||
class A {
|
||||
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
|
||||
|
||||
public void show(A obj) {
|
||||
System.out.println("A.show(A)");
|
||||
}
|
||||
```java
|
||||
public static String staticField = "静态变量";
|
||||
```
|
||||
|
||||
public void show(C obj) {
|
||||
System.out.println("A.show(C)");
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
|
||||
@Override
|
||||
public void show(A obj) {
|
||||
System.out.println("B.show(A)");
|
||||
}
|
||||
}
|
||||
|
||||
class C extends B {
|
||||
}
|
||||
|
||||
class D extends C {
|
||||
```java
|
||||
static {
|
||||
System.out.println("静态语句块");
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
public String field = "实例变量";
|
||||
```
|
||||
|
||||
A a = new A();
|
||||
B b = new B();
|
||||
C c = new C();
|
||||
D d = new D();
|
||||
|
||||
// 在 A 中存在 show(A obj),直接调用
|
||||
a.show(a); // A.show(A)
|
||||
// 在 A 中不存在 show(B obj),将 B 转型成其父类 A
|
||||
a.show(b); // A.show(A)
|
||||
// 在 B 中存在从 A 继承来的 show(C obj),直接调用
|
||||
b.show(c); // A.show(C)
|
||||
// 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C
|
||||
b.show(d); // A.show(C)
|
||||
|
||||
// 引用的还是 B 对象,所以 ba 和 b 的调用结果一样
|
||||
A ba = new B();
|
||||
ba.show(c); // A.show(C)
|
||||
ba.show(d); // A.show(C)
|
||||
```java
|
||||
{
|
||||
System.out.println("普通语句块");
|
||||
}
|
||||
```
|
||||
|
||||
**2. 重载(Overload)**
|
||||
最后才是构造函数的初始化。
|
||||
|
||||
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
|
||||
```java
|
||||
public InitialOrderTest() {
|
||||
System.out.println("构造函数");
|
||||
}
|
||||
```
|
||||
|
||||
应该注意的是,返回值不同,其它都相同不算是重载。
|
||||
存在继承的情况下,初始化顺序为:
|
||||
|
||||
- 父类(静态变量、静态语句块)
|
||||
- 子类(静态变量、静态语句块)
|
||||
- 父类(实例变量、普通语句块)
|
||||
- 父类(构造函数)
|
||||
- 子类(实例变量、普通语句块)
|
||||
- 子类(构造函数)
|
||||
|
||||
# 五、Object 通用方法
|
||||
|
||||
|
@ -814,6 +652,8 @@ public final void wait() throws InterruptedException
|
|||
|
||||
**1. 等价关系**
|
||||
|
||||
两个对象具有等价关系,需要满足以下五个条件:
|
||||
|
||||
Ⅰ 自反性
|
||||
|
||||
```java
|
||||
|
@ -897,11 +737,13 @@ public class EqualExample {
|
|||
|
||||
## hashCode()
|
||||
|
||||
hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
|
||||
hashCode() 返回哈希值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。
|
||||
|
||||
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
|
||||
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象哈希值也相等。
|
||||
|
||||
下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hashCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。
|
||||
HashSet 和 HashMap 等集合类使用了 hashCode() 方法来计算对象应该存储的位置,因此要将对象添加到这些集合类中,需要让对应的类实现 hashCode() 方法。
|
||||
|
||||
下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象。但是 EqualExample 没有实现 hashCode() 方法,因此这两个对象的哈希值是不同的,最终导致集合添加了两个等价的对象。
|
||||
|
||||
```java
|
||||
EqualExample e1 = new EqualExample(1, 1, 1);
|
||||
|
@ -913,9 +755,9 @@ set.add(e2);
|
|||
System.out.println(set.size()); // 2
|
||||
```
|
||||
|
||||
理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
|
||||
理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上。这就要求了哈希函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。
|
||||
|
||||
一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。
|
||||
R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位,最左边的位丢失。并且一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。
|
||||
|
||||
```java
|
||||
@Override
|
||||
|
@ -1144,177 +986,340 @@ e1.set(2, 222);
|
|||
System.out.println(e2.get(2)); // 2
|
||||
```
|
||||
|
||||
# 六、关键字
|
||||
# 六、继承
|
||||
|
||||
## final
|
||||
## 访问权限
|
||||
|
||||
**1. 数据**
|
||||
Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。
|
||||
|
||||
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
|
||||
可以对类或类中的成员(字段和方法)加上访问修饰符。
|
||||
|
||||
- 对于基本类型,final 使数值不变;
|
||||
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
|
||||
- 类可见表示其它类可以用这个类创建实例对象。
|
||||
- 成员可见表示其它类可以用这个类的实例对象访问到该成员;
|
||||
|
||||
protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。
|
||||
|
||||
设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。
|
||||
|
||||
如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例去代替,也就是确保满足里氏替换原则。
|
||||
|
||||
字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。
|
||||
|
||||
```java
|
||||
final int x = 1;
|
||||
// x = 2; // cannot assign value to final variable 'x'
|
||||
final A y = new A();
|
||||
y.a = 1;
|
||||
public class AccessExample {
|
||||
public String id;
|
||||
}
|
||||
```
|
||||
|
||||
**2. 方法**
|
||||
|
||||
声明方法不能被子类重写。
|
||||
|
||||
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
|
||||
|
||||
**3. 类**
|
||||
|
||||
声明类不允许被继承。
|
||||
|
||||
## static
|
||||
|
||||
**1. 静态变量**
|
||||
|
||||
- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
|
||||
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
|
||||
可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。
|
||||
|
||||
```java
|
||||
public class A {
|
||||
public class AccessExample {
|
||||
|
||||
private int x; // 实例变量
|
||||
private static int y; // 静态变量
|
||||
private int id;
|
||||
|
||||
public static void main(String[] args) {
|
||||
// int x = A.x; // Non-static field 'x' cannot be referenced from a static context
|
||||
A a = new A();
|
||||
int x = a.x;
|
||||
int y = A.y;
|
||||
public String getId() {
|
||||
return id + "";
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = Integer.valueOf(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. 静态方法**
|
||||
|
||||
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
|
||||
但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。
|
||||
|
||||
```java
|
||||
public abstract class A {
|
||||
public static void func1(){
|
||||
public class AccessWithInnerClassExample {
|
||||
|
||||
private class InnerClass {
|
||||
int x;
|
||||
}
|
||||
|
||||
private InnerClass innerClass;
|
||||
|
||||
public AccessWithInnerClassExample() {
|
||||
innerClass = new InnerClass();
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return innerClass.x; // 直接访问
|
||||
}
|
||||
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
|
||||
}
|
||||
```
|
||||
|
||||
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。
|
||||
## 抽象类与接口
|
||||
|
||||
**1. 抽象类**
|
||||
|
||||
抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。
|
||||
|
||||
抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。
|
||||
|
||||
```java
|
||||
public class A {
|
||||
public abstract class AbstractClassExample {
|
||||
|
||||
private static int x;
|
||||
protected int x;
|
||||
private int y;
|
||||
|
||||
public static void func1(){
|
||||
int a = x;
|
||||
// int b = y; // Non-static field 'y' cannot be referenced from a static context
|
||||
// int b = this.y; // 'A.this' cannot be referenced from a static context
|
||||
public abstract void func1();
|
||||
|
||||
public void func2() {
|
||||
System.out.println("func2");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. 静态语句块**
|
||||
|
||||
静态语句块在类初始化时运行一次。
|
||||
|
||||
```java
|
||||
public class A {
|
||||
static {
|
||||
System.out.println("123");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
A a1 = new A();
|
||||
A a2 = new A();
|
||||
public class AbstractExtendClassExample extends AbstractClassExample {
|
||||
@Override
|
||||
public void func1() {
|
||||
System.out.println("func1");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
|
||||
AbstractClassExample ac2 = new AbstractExtendClassExample();
|
||||
ac2.func1();
|
||||
```
|
||||
|
||||
**2. 接口**
|
||||
|
||||
接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
|
||||
|
||||
从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类,让它们都实现新增的方法。
|
||||
|
||||
接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
|
||||
|
||||
接口的字段默认都是 static 和 final 的。
|
||||
|
||||
```java
|
||||
public interface InterfaceExample {
|
||||
|
||||
void func1();
|
||||
|
||||
default void func2(){
|
||||
System.out.println("func2");
|
||||
}
|
||||
|
||||
int x = 123;
|
||||
// int y; // Variable 'y' might not have been initialized
|
||||
public int z = 0; // Modifier 'public' is redundant for interface fields
|
||||
// private int k = 0; // Modifier 'private' not allowed here
|
||||
// protected int l = 0; // Modifier 'protected' not allowed here
|
||||
// private void fun3(); // Modifier 'private' not allowed here
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class InterfaceImplementExample implements InterfaceExample {
|
||||
@Override
|
||||
public void func1() {
|
||||
System.out.println("func1");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated
|
||||
InterfaceExample ie2 = new InterfaceImplementExample();
|
||||
ie2.func1();
|
||||
System.out.println(InterfaceExample.x);
|
||||
```
|
||||
|
||||
**3. 比较**
|
||||
|
||||
- 从设计层面上看,抽象类提供了一种 IS-A 关系,需要满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
|
||||
- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
|
||||
- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
|
||||
- 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
|
||||
|
||||
**4. 使用选择**
|
||||
|
||||
使用接口:
|
||||
|
||||
- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
|
||||
- 需要使用多重继承。
|
||||
|
||||
使用抽象类:
|
||||
|
||||
- 需要在几个相关的类中共享代码。
|
||||
- 需要能控制继承来的成员的访问权限,而不是都为 public。
|
||||
- 需要继承非静态和非常量字段。
|
||||
|
||||
在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
|
||||
|
||||
- [Abstract Methods and Classes](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html)
|
||||
- [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/)
|
||||
- [When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface)
|
||||
|
||||
|
||||
## super
|
||||
|
||||
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super() 函数。
|
||||
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
|
||||
|
||||
```java
|
||||
public class SuperExample {
|
||||
|
||||
protected int x;
|
||||
protected int y;
|
||||
|
||||
public SuperExample(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void func() {
|
||||
System.out.println("SuperExample.func()");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class SuperExtendExample extends SuperExample {
|
||||
|
||||
private int z;
|
||||
|
||||
public SuperExtendExample(int x, int y, int z) {
|
||||
super(x, y);
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void func() {
|
||||
super.func();
|
||||
System.out.println("SuperExtendExample.func()");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
SuperExample e = new SuperExtendExample(1, 2, 3);
|
||||
e.func();
|
||||
```
|
||||
|
||||
```html
|
||||
123
|
||||
SuperExample.func()
|
||||
SuperExtendExample.func()
|
||||
```
|
||||
|
||||
**4. 静态内部类**
|
||||
[Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html)
|
||||
|
||||
非静态内部类依赖于外部类的实例,而静态内部类不需要。
|
||||
## 重写与重载
|
||||
|
||||
**1. 重写(Override)**
|
||||
|
||||
存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
|
||||
|
||||
为了满足里式替换原则,重写有以下三个限制:
|
||||
|
||||
- 子类方法的访问权限必须大于等于父类方法;
|
||||
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
|
||||
- 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
|
||||
|
||||
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。
|
||||
|
||||
下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中:
|
||||
|
||||
- 子类方法访问权限为 public,大于父类的 protected。
|
||||
- 子类的返回类型为 ArrayList<Integer>,是父类返回类型 List<Integer> 的子类。
|
||||
- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。
|
||||
- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。
|
||||
|
||||
```java
|
||||
public class OuterClass {
|
||||
|
||||
class InnerClass {
|
||||
class SuperClass {
|
||||
protected List<Integer> func() throws Throwable {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
static class StaticInnerClass {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
|
||||
OuterClass outerClass = new OuterClass();
|
||||
InnerClass innerClass = outerClass.new InnerClass();
|
||||
StaticInnerClass staticInnerClass = new StaticInnerClass();
|
||||
class SubClass extends SuperClass {
|
||||
@Override
|
||||
public ArrayList<Integer> func() throws Exception {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
静态内部类不能访问外部类的非静态的变量和方法。
|
||||
在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有再到父类中查看,看是否从父类继承来。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:
|
||||
|
||||
**5. 静态导包**
|
||||
- this.func(this)
|
||||
- super.func(this)
|
||||
- this.func(super)
|
||||
- super.func(super)
|
||||
|
||||
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
|
||||
|
||||
```java
|
||||
import static com.xxx.ClassName.*
|
||||
```
|
||||
/*
|
||||
A
|
||||
|
|
||||
B
|
||||
|
|
||||
C
|
||||
|
|
||||
D
|
||||
*/
|
||||
|
||||
**6. 初始化顺序**
|
||||
|
||||
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
|
||||
class A {
|
||||
|
||||
```java
|
||||
public static String staticField = "静态变量";
|
||||
```
|
||||
public void show(A obj) {
|
||||
System.out.println("A.show(A)");
|
||||
}
|
||||
|
||||
```java
|
||||
static {
|
||||
System.out.println("静态语句块");
|
||||
public void show(C obj) {
|
||||
System.out.println("A.show(C)");
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
|
||||
@Override
|
||||
public void show(A obj) {
|
||||
System.out.println("B.show(A)");
|
||||
}
|
||||
}
|
||||
|
||||
class C extends B {
|
||||
}
|
||||
|
||||
class D extends C {
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public String field = "实例变量";
|
||||
```
|
||||
public static void main(String[] args) {
|
||||
|
||||
```java
|
||||
{
|
||||
System.out.println("普通语句块");
|
||||
A a = new A();
|
||||
B b = new B();
|
||||
C c = new C();
|
||||
D d = new D();
|
||||
|
||||
// 在 A 中存在 show(A obj),直接调用
|
||||
a.show(a); // A.show(A)
|
||||
// 在 A 中不存在 show(B obj),将 B 转型成其父类 A
|
||||
a.show(b); // A.show(A)
|
||||
// 在 B 中存在从 A 继承来的 show(C obj),直接调用
|
||||
b.show(c); // A.show(C)
|
||||
// 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C
|
||||
b.show(d); // A.show(C)
|
||||
|
||||
// 引用的还是 B 对象,所以 ba 和 b 的调用结果一样
|
||||
A ba = new B();
|
||||
ba.show(c); // A.show(C)
|
||||
ba.show(d); // A.show(C)
|
||||
}
|
||||
```
|
||||
|
||||
最后才是构造函数的初始化。
|
||||
**2. 重载(Overload)**
|
||||
|
||||
```java
|
||||
public InitialOrderTest() {
|
||||
System.out.println("构造函数");
|
||||
}
|
||||
```
|
||||
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
|
||||
|
||||
存在继承的情况下,初始化顺序为:
|
||||
|
||||
- 父类(静态变量、静态语句块)
|
||||
- 子类(静态变量、静态语句块)
|
||||
- 父类(实例变量、普通语句块)
|
||||
- 父类(构造函数)
|
||||
- 子类(实例变量、普通语句块)
|
||||
- 子类(构造函数)
|
||||
应该注意的是,返回值不同,其它都相同不算是重载。
|
||||
|
||||
|
||||
# 七、反射
|
||||
|
|
777
notes/Java 基础.md
777
notes/Java 基础.md
|
@ -14,20 +14,20 @@
|
|||
* [float 与 double](#float-与-double)
|
||||
* [隐式类型转换](#隐式类型转换)
|
||||
* [switch](#switch)
|
||||
* [四、继承](#四继承)
|
||||
* [访问权限](#访问权限)
|
||||
* [抽象类与接口](#抽象类与接口)
|
||||
* [super](#super)
|
||||
* [重写与重载](#重写与重载)
|
||||
* [四、关键字](#四关键字)
|
||||
* [final](#final)
|
||||
* [static](#static)
|
||||
* [五、Object 通用方法](#五object-通用方法)
|
||||
* [概览](#概览)
|
||||
* [equals()](#equals)
|
||||
* [hashCode()](#hashcode)
|
||||
* [toString()](#tostring)
|
||||
* [clone()](#clone)
|
||||
* [六、关键字](#六关键字)
|
||||
* [final](#final)
|
||||
* [static](#static)
|
||||
* [六、继承](#六继承)
|
||||
* [访问权限](#访问权限)
|
||||
* [抽象类与接口](#抽象类与接口)
|
||||
* [super](#super)
|
||||
* [重写与重载](#重写与重载)
|
||||
* [七、反射](#七反射)
|
||||
* [八、异常](#八异常)
|
||||
* [九、泛型](#九泛型)
|
||||
|
@ -446,340 +446,178 @@ switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数
|
|||
|
||||
[StackOverflow : Why can't your switch statement data type be long, Java?](https://stackoverflow.com/questions/2676210/why-cant-your-switch-statement-data-type-be-long-java)
|
||||
|
||||
# 四、继承
|
||||
|
||||
## 访问权限
|
||||
# 四、关键字
|
||||
|
||||
Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。
|
||||
## final
|
||||
|
||||
可以对类或类中的成员(字段以及方法)加上访问修饰符。
|
||||
**1. 数据**
|
||||
|
||||
- 类可见表示其它类可以用这个类创建实例对象。
|
||||
- 成员可见表示其它类可以用这个类的实例对象访问到该成员;
|
||||
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
|
||||
|
||||
protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。
|
||||
|
||||
设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。
|
||||
|
||||
如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。
|
||||
|
||||
字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。
|
||||
- 对于基本类型,final 使数值不变;
|
||||
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
|
||||
|
||||
```java
|
||||
public class AccessExample {
|
||||
public String id;
|
||||
}
|
||||
final int x = 1;
|
||||
// x = 2; // cannot assign value to final variable 'x'
|
||||
final A y = new A();
|
||||
y.a = 1;
|
||||
```
|
||||
|
||||
可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。
|
||||
**2. 方法**
|
||||
|
||||
声明方法不能被子类重写。
|
||||
|
||||
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
|
||||
|
||||
**3. 类**
|
||||
|
||||
声明类不允许被继承。
|
||||
|
||||
## static
|
||||
|
||||
**1. 静态变量**
|
||||
|
||||
- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
|
||||
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
|
||||
|
||||
```java
|
||||
public class AccessExample {
|
||||
public class A {
|
||||
|
||||
private int id;
|
||||
private int x; // 实例变量
|
||||
private static int y; // 静态变量
|
||||
|
||||
public String getId() {
|
||||
return id + "";
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = Integer.valueOf(id);
|
||||
public static void main(String[] args) {
|
||||
// int x = A.x; // Non-static field 'x' cannot be referenced from a static context
|
||||
A a = new A();
|
||||
int x = a.x;
|
||||
int y = A.y;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。
|
||||
**2. 静态方法**
|
||||
|
||||
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
|
||||
|
||||
```java
|
||||
public class AccessWithInnerClassExample {
|
||||
|
||||
private class InnerClass {
|
||||
int x;
|
||||
}
|
||||
|
||||
private InnerClass innerClass;
|
||||
|
||||
public AccessWithInnerClassExample() {
|
||||
innerClass = new InnerClass();
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return innerClass.x; // 直接访问
|
||||
public abstract class A {
|
||||
public static void func1(){
|
||||
}
|
||||
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
|
||||
}
|
||||
```
|
||||
|
||||
## 抽象类与接口
|
||||
|
||||
**1. 抽象类**
|
||||
|
||||
抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。
|
||||
|
||||
抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。
|
||||
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因此这两个关键字与具体对象关联。
|
||||
|
||||
```java
|
||||
public abstract class AbstractClassExample {
|
||||
public class A {
|
||||
|
||||
protected int x;
|
||||
private static int x;
|
||||
private int y;
|
||||
|
||||
public abstract void func1();
|
||||
|
||||
public void func2() {
|
||||
System.out.println("func2");
|
||||
public static void func1(){
|
||||
int a = x;
|
||||
// int b = y; // Non-static field 'y' cannot be referenced from a static context
|
||||
// int b = this.y; // 'A.this' cannot be referenced from a static context
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. 静态语句块**
|
||||
|
||||
静态语句块在类初始化时运行一次。
|
||||
|
||||
```java
|
||||
public class AbstractExtendClassExample extends AbstractClassExample {
|
||||
@Override
|
||||
public void func1() {
|
||||
System.out.println("func1");
|
||||
public class A {
|
||||
static {
|
||||
System.out.println("123");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
A a1 = new A();
|
||||
A a2 = new A();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
|
||||
AbstractClassExample ac2 = new AbstractExtendClassExample();
|
||||
ac2.func1();
|
||||
```
|
||||
|
||||
**2. 接口**
|
||||
|
||||
接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
|
||||
|
||||
从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
|
||||
|
||||
接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
|
||||
|
||||
接口的字段默认都是 static 和 final 的。
|
||||
|
||||
```java
|
||||
public interface InterfaceExample {
|
||||
|
||||
void func1();
|
||||
|
||||
default void func2(){
|
||||
System.out.println("func2");
|
||||
}
|
||||
|
||||
int x = 123;
|
||||
// int y; // Variable 'y' might not have been initialized
|
||||
public int z = 0; // Modifier 'public' is redundant for interface fields
|
||||
// private int k = 0; // Modifier 'private' not allowed here
|
||||
// protected int l = 0; // Modifier 'protected' not allowed here
|
||||
// private void fun3(); // Modifier 'private' not allowed here
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class InterfaceImplementExample implements InterfaceExample {
|
||||
@Override
|
||||
public void func1() {
|
||||
System.out.println("func1");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated
|
||||
InterfaceExample ie2 = new InterfaceImplementExample();
|
||||
ie2.func1();
|
||||
System.out.println(InterfaceExample.x);
|
||||
```
|
||||
|
||||
**3. 比较**
|
||||
|
||||
- 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
|
||||
- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
|
||||
- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
|
||||
- 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
|
||||
|
||||
**4. 使用选择**
|
||||
|
||||
使用接口:
|
||||
|
||||
- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
|
||||
- 需要使用多重继承。
|
||||
|
||||
使用抽象类:
|
||||
|
||||
- 需要在几个相关的类中共享代码。
|
||||
- 需要能控制继承来的成员的访问权限,而不是都为 public。
|
||||
- 需要继承非静态和非常量字段。
|
||||
|
||||
在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
|
||||
|
||||
- [Abstract Methods and Classes](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html)
|
||||
- [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/)
|
||||
- [When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface)
|
||||
|
||||
|
||||
## super
|
||||
|
||||
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super 函数。
|
||||
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
|
||||
|
||||
```java
|
||||
public class SuperExample {
|
||||
|
||||
protected int x;
|
||||
protected int y;
|
||||
|
||||
public SuperExample(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void func() {
|
||||
System.out.println("SuperExample.func()");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class SuperExtendExample extends SuperExample {
|
||||
|
||||
private int z;
|
||||
|
||||
public SuperExtendExample(int x, int y, int z) {
|
||||
super(x, y);
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void func() {
|
||||
super.func();
|
||||
System.out.println("SuperExtendExample.func()");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
SuperExample e = new SuperExtendExample(1, 2, 3);
|
||||
e.func();
|
||||
```
|
||||
|
||||
```html
|
||||
SuperExample.func()
|
||||
SuperExtendExample.func()
|
||||
123
|
||||
```
|
||||
|
||||
[Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html)
|
||||
**4. 静态内部类**
|
||||
|
||||
## 重写与重载
|
||||
|
||||
**1. 重写(Override)**
|
||||
|
||||
存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
|
||||
|
||||
为了满足里式替换原则,重写有以下三个限制:
|
||||
|
||||
- 子类方法的访问权限必须大于等于父类方法;
|
||||
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
|
||||
- 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
|
||||
|
||||
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。
|
||||
|
||||
下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中:
|
||||
|
||||
- 子类方法访问权限为 public,大于父类的 protected。
|
||||
- 子类的返回类型为 ArrayList<Integer>,是父类返回类型 List<Integer> 的子类。
|
||||
- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。
|
||||
- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。
|
||||
非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。
|
||||
|
||||
```java
|
||||
class SuperClass {
|
||||
protected List<Integer> func() throws Throwable {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
public class OuterClass {
|
||||
|
||||
class SubClass extends SuperClass {
|
||||
@Override
|
||||
public ArrayList<Integer> func() throws Exception {
|
||||
return new ArrayList<>();
|
||||
class InnerClass {
|
||||
}
|
||||
|
||||
static class StaticInnerClass {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
|
||||
OuterClass outerClass = new OuterClass();
|
||||
InnerClass innerClass = outerClass.new InnerClass();
|
||||
StaticInnerClass staticInnerClass = new StaticInnerClass();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有查找到再到父类中查看,看是否有继承来的方法。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:
|
||||
静态内部类不能访问外部类的非静态的变量和方法。
|
||||
|
||||
- this.func(this)
|
||||
- super.func(this)
|
||||
- this.func(super)
|
||||
- super.func(super)
|
||||
**5. 静态导包**
|
||||
|
||||
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
|
||||
|
||||
```java
|
||||
/*
|
||||
A
|
||||
|
|
||||
B
|
||||
|
|
||||
C
|
||||
|
|
||||
D
|
||||
*/
|
||||
import static com.xxx.ClassName.*
|
||||
```
|
||||
|
||||
**6. 初始化顺序**
|
||||
|
||||
class A {
|
||||
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
|
||||
|
||||
public void show(A obj) {
|
||||
System.out.println("A.show(A)");
|
||||
}
|
||||
```java
|
||||
public static String staticField = "静态变量";
|
||||
```
|
||||
|
||||
public void show(C obj) {
|
||||
System.out.println("A.show(C)");
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
|
||||
@Override
|
||||
public void show(A obj) {
|
||||
System.out.println("B.show(A)");
|
||||
}
|
||||
}
|
||||
|
||||
class C extends B {
|
||||
}
|
||||
|
||||
class D extends C {
|
||||
```java
|
||||
static {
|
||||
System.out.println("静态语句块");
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
public String field = "实例变量";
|
||||
```
|
||||
|
||||
A a = new A();
|
||||
B b = new B();
|
||||
C c = new C();
|
||||
D d = new D();
|
||||
|
||||
// 在 A 中存在 show(A obj),直接调用
|
||||
a.show(a); // A.show(A)
|
||||
// 在 A 中不存在 show(B obj),将 B 转型成其父类 A
|
||||
a.show(b); // A.show(A)
|
||||
// 在 B 中存在从 A 继承来的 show(C obj),直接调用
|
||||
b.show(c); // A.show(C)
|
||||
// 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C
|
||||
b.show(d); // A.show(C)
|
||||
|
||||
// 引用的还是 B 对象,所以 ba 和 b 的调用结果一样
|
||||
A ba = new B();
|
||||
ba.show(c); // A.show(C)
|
||||
ba.show(d); // A.show(C)
|
||||
```java
|
||||
{
|
||||
System.out.println("普通语句块");
|
||||
}
|
||||
```
|
||||
|
||||
**2. 重载(Overload)**
|
||||
最后才是构造函数的初始化。
|
||||
|
||||
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
|
||||
```java
|
||||
public InitialOrderTest() {
|
||||
System.out.println("构造函数");
|
||||
}
|
||||
```
|
||||
|
||||
应该注意的是,返回值不同,其它都相同不算是重载。
|
||||
存在继承的情况下,初始化顺序为:
|
||||
|
||||
- 父类(静态变量、静态语句块)
|
||||
- 子类(静态变量、静态语句块)
|
||||
- 父类(实例变量、普通语句块)
|
||||
- 父类(构造函数)
|
||||
- 子类(实例变量、普通语句块)
|
||||
- 子类(构造函数)
|
||||
|
||||
# 五、Object 通用方法
|
||||
|
||||
|
@ -814,6 +652,8 @@ public final void wait() throws InterruptedException
|
|||
|
||||
**1. 等价关系**
|
||||
|
||||
两个对象具有等价关系,需要满足以下五个条件:
|
||||
|
||||
Ⅰ 自反性
|
||||
|
||||
```java
|
||||
|
@ -897,11 +737,13 @@ public class EqualExample {
|
|||
|
||||
## hashCode()
|
||||
|
||||
hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
|
||||
hashCode() 返回哈希值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。
|
||||
|
||||
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
|
||||
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象哈希值也相等。
|
||||
|
||||
下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hashCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。
|
||||
HashSet 和 HashMap 等集合类使用了 hashCode() 方法来计算对象应该存储的位置,因此要将对象添加到这些集合类中,需要让对应的类实现 hashCode() 方法。
|
||||
|
||||
下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象。但是 EqualExample 没有实现 hashCode() 方法,因此这两个对象的哈希值是不同的,最终导致集合添加了两个等价的对象。
|
||||
|
||||
```java
|
||||
EqualExample e1 = new EqualExample(1, 1, 1);
|
||||
|
@ -913,9 +755,9 @@ set.add(e2);
|
|||
System.out.println(set.size()); // 2
|
||||
```
|
||||
|
||||
理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
|
||||
理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上。这就要求了哈希函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。
|
||||
|
||||
一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。
|
||||
R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位,最左边的位丢失。并且一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。
|
||||
|
||||
```java
|
||||
@Override
|
||||
|
@ -1144,177 +986,340 @@ e1.set(2, 222);
|
|||
System.out.println(e2.get(2)); // 2
|
||||
```
|
||||
|
||||
# 六、关键字
|
||||
# 六、继承
|
||||
|
||||
## final
|
||||
## 访问权限
|
||||
|
||||
**1. 数据**
|
||||
Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。
|
||||
|
||||
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
|
||||
可以对类或类中的成员(字段和方法)加上访问修饰符。
|
||||
|
||||
- 对于基本类型,final 使数值不变;
|
||||
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
|
||||
- 类可见表示其它类可以用这个类创建实例对象。
|
||||
- 成员可见表示其它类可以用这个类的实例对象访问到该成员;
|
||||
|
||||
protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。
|
||||
|
||||
设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。
|
||||
|
||||
如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例去代替,也就是确保满足里氏替换原则。
|
||||
|
||||
字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。
|
||||
|
||||
```java
|
||||
final int x = 1;
|
||||
// x = 2; // cannot assign value to final variable 'x'
|
||||
final A y = new A();
|
||||
y.a = 1;
|
||||
public class AccessExample {
|
||||
public String id;
|
||||
}
|
||||
```
|
||||
|
||||
**2. 方法**
|
||||
|
||||
声明方法不能被子类重写。
|
||||
|
||||
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
|
||||
|
||||
**3. 类**
|
||||
|
||||
声明类不允许被继承。
|
||||
|
||||
## static
|
||||
|
||||
**1. 静态变量**
|
||||
|
||||
- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
|
||||
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
|
||||
可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。
|
||||
|
||||
```java
|
||||
public class A {
|
||||
public class AccessExample {
|
||||
|
||||
private int x; // 实例变量
|
||||
private static int y; // 静态变量
|
||||
private int id;
|
||||
|
||||
public static void main(String[] args) {
|
||||
// int x = A.x; // Non-static field 'x' cannot be referenced from a static context
|
||||
A a = new A();
|
||||
int x = a.x;
|
||||
int y = A.y;
|
||||
public String getId() {
|
||||
return id + "";
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = Integer.valueOf(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. 静态方法**
|
||||
|
||||
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
|
||||
但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。
|
||||
|
||||
```java
|
||||
public abstract class A {
|
||||
public static void func1(){
|
||||
public class AccessWithInnerClassExample {
|
||||
|
||||
private class InnerClass {
|
||||
int x;
|
||||
}
|
||||
|
||||
private InnerClass innerClass;
|
||||
|
||||
public AccessWithInnerClassExample() {
|
||||
innerClass = new InnerClass();
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return innerClass.x; // 直接访问
|
||||
}
|
||||
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
|
||||
}
|
||||
```
|
||||
|
||||
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。
|
||||
## 抽象类与接口
|
||||
|
||||
**1. 抽象类**
|
||||
|
||||
抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。
|
||||
|
||||
抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。
|
||||
|
||||
```java
|
||||
public class A {
|
||||
public abstract class AbstractClassExample {
|
||||
|
||||
private static int x;
|
||||
protected int x;
|
||||
private int y;
|
||||
|
||||
public static void func1(){
|
||||
int a = x;
|
||||
// int b = y; // Non-static field 'y' cannot be referenced from a static context
|
||||
// int b = this.y; // 'A.this' cannot be referenced from a static context
|
||||
public abstract void func1();
|
||||
|
||||
public void func2() {
|
||||
System.out.println("func2");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. 静态语句块**
|
||||
|
||||
静态语句块在类初始化时运行一次。
|
||||
|
||||
```java
|
||||
public class A {
|
||||
static {
|
||||
System.out.println("123");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
A a1 = new A();
|
||||
A a2 = new A();
|
||||
public class AbstractExtendClassExample extends AbstractClassExample {
|
||||
@Override
|
||||
public void func1() {
|
||||
System.out.println("func1");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
|
||||
AbstractClassExample ac2 = new AbstractExtendClassExample();
|
||||
ac2.func1();
|
||||
```
|
||||
|
||||
**2. 接口**
|
||||
|
||||
接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
|
||||
|
||||
从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类,让它们都实现新增的方法。
|
||||
|
||||
接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
|
||||
|
||||
接口的字段默认都是 static 和 final 的。
|
||||
|
||||
```java
|
||||
public interface InterfaceExample {
|
||||
|
||||
void func1();
|
||||
|
||||
default void func2(){
|
||||
System.out.println("func2");
|
||||
}
|
||||
|
||||
int x = 123;
|
||||
// int y; // Variable 'y' might not have been initialized
|
||||
public int z = 0; // Modifier 'public' is redundant for interface fields
|
||||
// private int k = 0; // Modifier 'private' not allowed here
|
||||
// protected int l = 0; // Modifier 'protected' not allowed here
|
||||
// private void fun3(); // Modifier 'private' not allowed here
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class InterfaceImplementExample implements InterfaceExample {
|
||||
@Override
|
||||
public void func1() {
|
||||
System.out.println("func1");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated
|
||||
InterfaceExample ie2 = new InterfaceImplementExample();
|
||||
ie2.func1();
|
||||
System.out.println(InterfaceExample.x);
|
||||
```
|
||||
|
||||
**3. 比较**
|
||||
|
||||
- 从设计层面上看,抽象类提供了一种 IS-A 关系,需要满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
|
||||
- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
|
||||
- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
|
||||
- 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
|
||||
|
||||
**4. 使用选择**
|
||||
|
||||
使用接口:
|
||||
|
||||
- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
|
||||
- 需要使用多重继承。
|
||||
|
||||
使用抽象类:
|
||||
|
||||
- 需要在几个相关的类中共享代码。
|
||||
- 需要能控制继承来的成员的访问权限,而不是都为 public。
|
||||
- 需要继承非静态和非常量字段。
|
||||
|
||||
在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
|
||||
|
||||
- [Abstract Methods and Classes](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html)
|
||||
- [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/)
|
||||
- [When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface)
|
||||
|
||||
|
||||
## super
|
||||
|
||||
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super() 函数。
|
||||
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
|
||||
|
||||
```java
|
||||
public class SuperExample {
|
||||
|
||||
protected int x;
|
||||
protected int y;
|
||||
|
||||
public SuperExample(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void func() {
|
||||
System.out.println("SuperExample.func()");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class SuperExtendExample extends SuperExample {
|
||||
|
||||
private int z;
|
||||
|
||||
public SuperExtendExample(int x, int y, int z) {
|
||||
super(x, y);
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void func() {
|
||||
super.func();
|
||||
System.out.println("SuperExtendExample.func()");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
SuperExample e = new SuperExtendExample(1, 2, 3);
|
||||
e.func();
|
||||
```
|
||||
|
||||
```html
|
||||
123
|
||||
SuperExample.func()
|
||||
SuperExtendExample.func()
|
||||
```
|
||||
|
||||
**4. 静态内部类**
|
||||
[Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html)
|
||||
|
||||
非静态内部类依赖于外部类的实例,而静态内部类不需要。
|
||||
## 重写与重载
|
||||
|
||||
**1. 重写(Override)**
|
||||
|
||||
存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
|
||||
|
||||
为了满足里式替换原则,重写有以下三个限制:
|
||||
|
||||
- 子类方法的访问权限必须大于等于父类方法;
|
||||
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
|
||||
- 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
|
||||
|
||||
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。
|
||||
|
||||
下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中:
|
||||
|
||||
- 子类方法访问权限为 public,大于父类的 protected。
|
||||
- 子类的返回类型为 ArrayList<Integer>,是父类返回类型 List<Integer> 的子类。
|
||||
- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。
|
||||
- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。
|
||||
|
||||
```java
|
||||
public class OuterClass {
|
||||
|
||||
class InnerClass {
|
||||
class SuperClass {
|
||||
protected List<Integer> func() throws Throwable {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
static class StaticInnerClass {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
|
||||
OuterClass outerClass = new OuterClass();
|
||||
InnerClass innerClass = outerClass.new InnerClass();
|
||||
StaticInnerClass staticInnerClass = new StaticInnerClass();
|
||||
class SubClass extends SuperClass {
|
||||
@Override
|
||||
public ArrayList<Integer> func() throws Exception {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
静态内部类不能访问外部类的非静态的变量和方法。
|
||||
在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有再到父类中查看,看是否从父类继承来。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:
|
||||
|
||||
**5. 静态导包**
|
||||
- this.func(this)
|
||||
- super.func(this)
|
||||
- this.func(super)
|
||||
- super.func(super)
|
||||
|
||||
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
|
||||
|
||||
```java
|
||||
import static com.xxx.ClassName.*
|
||||
```
|
||||
/*
|
||||
A
|
||||
|
|
||||
B
|
||||
|
|
||||
C
|
||||
|
|
||||
D
|
||||
*/
|
||||
|
||||
**6. 初始化顺序**
|
||||
|
||||
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
|
||||
class A {
|
||||
|
||||
```java
|
||||
public static String staticField = "静态变量";
|
||||
```
|
||||
public void show(A obj) {
|
||||
System.out.println("A.show(A)");
|
||||
}
|
||||
|
||||
```java
|
||||
static {
|
||||
System.out.println("静态语句块");
|
||||
public void show(C obj) {
|
||||
System.out.println("A.show(C)");
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
|
||||
@Override
|
||||
public void show(A obj) {
|
||||
System.out.println("B.show(A)");
|
||||
}
|
||||
}
|
||||
|
||||
class C extends B {
|
||||
}
|
||||
|
||||
class D extends C {
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public String field = "实例变量";
|
||||
```
|
||||
public static void main(String[] args) {
|
||||
|
||||
```java
|
||||
{
|
||||
System.out.println("普通语句块");
|
||||
A a = new A();
|
||||
B b = new B();
|
||||
C c = new C();
|
||||
D d = new D();
|
||||
|
||||
// 在 A 中存在 show(A obj),直接调用
|
||||
a.show(a); // A.show(A)
|
||||
// 在 A 中不存在 show(B obj),将 B 转型成其父类 A
|
||||
a.show(b); // A.show(A)
|
||||
// 在 B 中存在从 A 继承来的 show(C obj),直接调用
|
||||
b.show(c); // A.show(C)
|
||||
// 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C
|
||||
b.show(d); // A.show(C)
|
||||
|
||||
// 引用的还是 B 对象,所以 ba 和 b 的调用结果一样
|
||||
A ba = new B();
|
||||
ba.show(c); // A.show(C)
|
||||
ba.show(d); // A.show(C)
|
||||
}
|
||||
```
|
||||
|
||||
最后才是构造函数的初始化。
|
||||
**2. 重载(Overload)**
|
||||
|
||||
```java
|
||||
public InitialOrderTest() {
|
||||
System.out.println("构造函数");
|
||||
}
|
||||
```
|
||||
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
|
||||
|
||||
存在继承的情况下,初始化顺序为:
|
||||
|
||||
- 父类(静态变量、静态语句块)
|
||||
- 子类(静态变量、静态语句块)
|
||||
- 父类(实例变量、普通语句块)
|
||||
- 父类(构造函数)
|
||||
- 子类(实例变量、普通语句块)
|
||||
- 子类(构造函数)
|
||||
应该注意的是,返回值不同,其它都相同不算是重载。
|
||||
|
||||
|
||||
# 七、反射
|
||||
|
|
Loading…
Reference in New Issue
Block a user