From 8f466d2c6f9f2835b21e4426d86e9fa406532a76 Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Tue, 10 Dec 2019 01:28:00 +0800 Subject: [PATCH] auto commit --- docs/notes/Java 基础.md | 777 ++++++++++++++++++++-------------------- notes/Java 基础.md | 777 ++++++++++++++++++++-------------------- 2 files changed, 782 insertions(+), 772 deletions(-) diff --git a/docs/notes/Java 基础.md b/docs/notes/Java 基础.md index 74f49c70..479c809d 100644 --- a/docs/notes/Java 基础.md +++ b/docs/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,是父类返回类型 List 的子类。 -- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。 -- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。 +非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。 ```java -class SuperClass { - protected List func() throws Throwable { - return new ArrayList<>(); - } -} +public class OuterClass { -class SubClass extends SuperClass { - @Override - public ArrayList 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,是父类返回类型 List 的子类。 +- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。 +- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。 ```java -public class OuterClass { - - class InnerClass { +class SuperClass { + protected List 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 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("构造函数"); -} -``` +存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。 -存在继承的情况下,初始化顺序为: - -- 父类(静态变量、静态语句块) -- 子类(静态变量、静态语句块) -- 父类(实例变量、普通语句块) -- 父类(构造函数) -- 子类(实例变量、普通语句块) -- 子类(构造函数) +应该注意的是,返回值不同,其它都相同不算是重载。 # 七、反射 diff --git a/notes/Java 基础.md b/notes/Java 基础.md index 74f49c70..479c809d 100644 --- a/notes/Java 基础.md +++ b/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,是父类返回类型 List 的子类。 -- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。 -- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。 +非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。 ```java -class SuperClass { - protected List func() throws Throwable { - return new ArrayList<>(); - } -} +public class OuterClass { -class SubClass extends SuperClass { - @Override - public ArrayList 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,是父类返回类型 List 的子类。 +- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。 +- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。 ```java -public class OuterClass { - - class InnerClass { +class SuperClass { + protected List 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 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("构造函数"); -} -``` +存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。 -存在继承的情况下,初始化顺序为: - -- 父类(静态变量、静态语句块) -- 子类(静态变量、静态语句块) -- 父类(实例变量、普通语句块) -- 父类(构造函数) -- 子类(实例变量、普通语句块) -- 子类(构造函数) +应该注意的是,返回值不同,其它都相同不算是重载。 # 七、反射