From 1cfacd68a0f30ddbaa19ef9f5c5793a457023c55 Mon Sep 17 00:00:00 2001 From: CyC2018 <1029579233@qq.com> Date: Sat, 16 Jun 2018 14:33:21 +0800 Subject: [PATCH] auto commit --- notes/Java 基础.md | 1070 +++++++++++++++++++++++++------------------- 1 file changed, 619 insertions(+), 451 deletions(-) diff --git a/notes/Java 基础.md b/notes/Java 基础.md index 6424600a..80f07712 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -1,31 +1,36 @@ -* [一、关键字](#一关键字) - * [final](#final) - * [static](#static) -* [二、Object 通用方法](#二object-通用方法) +* [一、基本类型](#一基本类型) + * [包装类型](#包装类型) + * [缓存池](#缓存池) +* [二、String](#二string) * [概览](#概览) - * [equals()](#equals) - * [hashCode()](#hashcode) - * [toString()](#tostring) - * [clone()](#clone) + * [String 不可变的好处](#string-不可变的好处) + * [String, StringBuffer and StringBuilder](#string,-stringbuffer-and-stringbuilder) + * [String.intern()](#stringintern) +* [三、运算](#三运算) + * [参数传递](#参数传递) + * [float 与 double](#float-与-double) + * [隐式类型转换](#隐式类型转换) + * [switch](#switch) * [四、继承](#四继承) * [访问权限](#访问权限) * [抽象类与接口](#抽象类与接口) * [super](#super) * [覆盖与重载](#覆盖与重载) -* [五、String](#五string) - * [String, StringBuffer and StringBuilder](#string,-stringbuffer-and-stringbuilder) - * [String 不可变的原因](#string-不可变的原因) - * [String.intern()](#stringintern) -* [六、基本类型与运算](#六基本类型与运算) - * [包装类型](#包装类型) - * [switch](#switch) +* [五、Object 通用方法](#五object-通用方法) + * [概览](#概览) + * [equals()](#equals) + * [hashCode()](#hashcode) + * [toString()](#tostring) + * [clone()](#clone) +* [六、关键字](#六关键字) + * [final](#final) + * [static](#static) * [七、反射](#七反射) * [八、异常](#八异常) * [九、泛型](#九泛型) * [十、注解](#十注解) * [十一、特性](#十一特性) - * [面向对象三大特性](#面向对象三大特性) * [Java 各版本的新特性](#java-各版本的新特性) * [Java 与 C++ 的区别](#java-与-c-的区别) * [JRE or JDK](#jre-or-jdk) @@ -33,112 +38,521 @@ -# 一、关键字 +# 一、基本类型 -## final +## 包装类型 -**1. 数据** +八个基本类型: -声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。 +- boolean/1 +- byte/8 +- char/16 +- short/16 +- int/32 +- float/32 +- long/64 +- double/64 -- 对于基本类型,final 使数值不变; -- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。 +基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。 ```java -final int x = 1; -// x = 2; // cannot assign value to final variable 'x' -final A y = new A(); -y.a = 1; +Integer x = 2; // 装箱 +int y = x; // 拆箱 ``` -**2. 方法** +## 缓存池 -声明方法不能被子类覆盖。 - -private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是覆盖基类方法,而是在子类中定义了一个新的方法。 - -**3. 类** - -声明类不允许被继承。 - -## static - -**1. 静态变量** - -静态变量在内存中只存在一份,只在类初始化时赋值一次。 - -- 静态变量:类所有的实例都共享静态变量,可以直接通过类名来访问它; -- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 +new Integer(123) 与 Integer.valueOf(123) 的区别在于,new Integer(123) 每次都会新建一个对象,而 Integer.valueOf(123) 可能会使用缓存对象,因此多次使用 Integer.valueOf(123) 会取得同一个对象的引用。 ```java -public class A { - private int x; // 实例变量 - public static int y; // 静态变量 +Integer x = new Integer(123); +Integer y = new Integer(123); +System.out.println(x == y); // false +Integer z = Integer.valueOf(123); +Integer k = Integer.valueOf(123); +System.out.println(z == k); // true +``` + +编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。 + +```java +Integer m = 123; +Integer n = 123; +System.out.println(m == n); // true +``` + +valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接使用缓存池的内容。 + +```java +public static Integer valueOf(int i) { + if (i >= IntegerCache.low && i <= IntegerCache.high) + return IntegerCache.cache[i + (-IntegerCache.low)]; + return new Integer(i); } ``` -**2. 静态方法** - -静态方法在类加载的时候就存在了,它不依赖于任何实例,所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。 - -**3. 静态语句块** - -静态语句块在类初始化时运行一次。 - -**4. 静态内部类** - -内部类的一种,静态内部类不依赖外部类,且不能访问外部类的非静态的变量和方法。 - -**5. 静态导包** +在 Java 8 中,Integer 缓存池的大小默认为 -128\~127。 ```java -import static com.xxx.ClassName.* -``` +static final int low = -128; +static final int high; +static final Integer cache[]; -在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。 - -**6. 变量赋值顺序** - -静态变量的赋值和静态语句块的运行优先于实例变量的赋值和普通语句块的运行,静态变量的赋值和静态语句块的运行哪个先执行取决于它们在代码中的顺序。 - -```java -public static String staticField = "静态变量"; -``` - -```java static { - System.out.println("静态语句块"); + // high value may be configured by property + int h = 127; + String integerCacheHighPropValue = + sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); + if (integerCacheHighPropValue != null) { + try { + int i = parseInt(integerCacheHighPropValue); + i = Math.max(i, 127); + // Maximum array size is Integer.MAX_VALUE + h = Math.min(i, Integer.MAX_VALUE - (-low) -1); + } catch( NumberFormatException nfe) { + // If the property cannot be parsed into an int, ignore it. + } + } + high = h; + + cache = new Integer[(high - low) + 1]; + int j = low; + for(int k = 0; k < cache.length; k++) + cache[k] = new Integer(j++); + + // range [-128, 127] must be interned (JLS7 5.1.7) + assert IntegerCache.high >= 127; +} +``` + +Java 还将一些其它基本类型的值放在缓冲池中,包含以下这些: + +- boolean values true and false +- all byte values +- short values between -128 and 127 +- int values between -128 and 127 +- char in the range \u0000 to \u007F + +因此在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。 + +[StackOverflow : Differences between new Integer(123), Integer.valueOf(123) and just 123 +](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123) + +# 二、String + +## 概览 + +String 被声明为 final,因此它不可被继承。 + +内部使用 char 数组存储数据,该数组被申明为 final,也就是说 String 不可变。 + +```java +public final class String + implements java.io.Serializable, Comparable, CharSequence { + /** The value is used for character storage. */ + private final char value[]; +``` + +## String 不可变的好处 + +**1. 可以缓存 hash 值** + +因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。 + +**2. String Pool 的需要** + +如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。 + +

+ +**3. 安全性** + +String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。 + +**4. 线程安全** + +String 不可变性天生具备线程安全,可以在多个线程中安全地使用。 + +[Program Creek : Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/) + +## String, StringBuffer and StringBuilder + +**1. 可变性** + +- String 不可变 +- StringBuffer 和 StringBuilder 可变 + +**2. 线程安全** + +- String 不可变,因此是线程安全的 +- StringBuilder 不是线程安全的 +- StringBuffer 是线程安全的,内部使用 synchronized 来同步 + +[StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder) + +## String.intern() + +使用 String.intern() 可以保证相同内容的字符串变量引用相同的内存对象。 + +下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用,这个方法首先把 s1 引用的对象放到 String Poll(字符串常量池)中,然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。 + +```java +String s1 = new String("aaa"); +String s2 = new String("aaa"); +System.out.println(s1 == s2); // false +String s3 = s1.intern(); +System.out.println(s1.intern() == s3); // true +``` + +如果是采用 "bbb" 这种使用双引号的形式创建字符串实例,会自动地将新建的对象放入 String Poll 中。 + +```java +String s4 = "bbb"; +String s5 = "bbb"; +System.out.println(s4 == s5); // true +``` + +在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被放在堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 + +- [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning) +- [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html) + +# 三、运算 + +## 参数传递 + +Java 的参数是以值传递的形式传入方法中,而不是引用传递。 + +Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。 + +```java +public class Dog { + String name; + + Dog(String name) { + this.name = name; + } + + String getName() { + return name; + } + + String getObjectAddress() { + return super.toString(); + } } ``` ```java -public String field = "实例变量"; -``` +public class PassByValueExample { + public static void main(String[] args) { + Dog dog = new Dog("A"); + System.out.println(dog.getObjectAddress()); // Dog@4554617c + func(dog); + System.out.println(dog.getObjectAddress()); // Dog@4554617c + System.out.println(dog.getName()); // A + } -```java -{ - System.out.println("普通语句块"); + private static void func(Dog dog) { + System.out.println(dog.getObjectAddress()); // Dog@4554617c + dog = new Dog("B"); + System.out.println(dog.getObjectAddress()); // Dog@74a14482 + System.out.println(dog.getName()); // B + } } ``` -最后才运行构造函数 +[StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?](https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value) + +## float 与 double + +1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是下转型,会使得精度下降,因此 Java 不能隐式执行下转型。 ```java -public InitialOrderTest() { - System.out.println("构造函数"); +// float f = 1.1; +``` + +1.1f 字面量才是 float 类型。 + +```java +float f = 1.1f; +``` + +## 隐式类型转换 + +因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。 + +```java +short s1 = 1; +// s1 = s1 + 1; +``` + +但是使用 += 运算符可以执行隐式类型转换。 + +```java +s1 += 1; +``` + +上面的语句相当于将 s1 + 1 的计算结果进行了向下转型: + +```java +s1 = (short) (s1 + 1); +``` + +[StackOverflow : Why don't Java's +=, -=, *=, /= compound assignment operators require casting?](https://stackoverflow.com/questions/8710619/why-dont-javas-compound-assignment-operators-require-casting) + +## switch + +从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。 + +```java +String s = "a"; +switch (s) { + case "a": + System.out.println("aaa"); + break; + case "b": + System.out.println("bbb"); + break; } ``` -存在继承的情况下,初始化顺序为: +switch 不支持 long,是因为 swicth 的设计初衷是为那些只需要对少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。 -- 父类(静态变量、静态语句块) -- 子类(静态变量、静态语句块) -- 父类(实例变量、普通语句块) -- 父类(构造函数) -- 子类(实例变量、普通语句块) -- 子类(构造函数) +```java +// long x = 111; +// switch (x) { // Incompatible types. Found: 'long', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum' +// case 111: +// System.out.println(111); +// break; +// case 222: +// System.out.println(222); +// break; +// } +``` -# 二、Object 通用方法 +[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,如果不加访问修饰符,表示包级可见。 + +可以对类或类中的成员(字段以及方法)加上访问修饰符。 + +- 成员可见表示其它类可以用这个类的实例访问到该成员; +- 类可见表示其它类可以用这个类创建对象。 + +protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。 + +设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。 + +如果子类的方法覆盖了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。 + +字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。可以使用公有的 getter 和 setter 方法来替换公有字段。 + +```java +public class AccessExample { + public int x; +} +``` + +```java +public class AccessExample { + private int x; + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } +} +``` + +但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。 + +```java +public class AccessWithInnerClassExample { + private class InnerClass { + int x; + } + + private InnerClass innerClass; + + public AccessWithInnerClassExample() { + innerClass = new InnerClass(); + } + + public int getValue() { + return innerClass.x; // 直接访问 + } +} +``` + +## 抽象类与接口 + +**1. 抽象类** + +抽象类和抽象方法都使用 abstract 进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。 + +抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。 + +```java +public abstract class AbstractClassExample { + + protected int x; + private int y; + + public abstract void func1(); + + public void func2() { + System.out.println("func2"); + } +} +``` + +```java +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. 使用选择** + +使用抽象类: + +- 需要在几个相关的类中共享代码。 +- 需要能控制继承来的方法和域的访问权限,而不是都为 public。 +- 需要继承非静态(non-static)和非常量(non-final)字段。 + +使用接口: + +- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法; +- 需要使用多重继承。 + +在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 + +> [深入理解 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 关键字来引用父类的方法实现。 + +```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() +``` + +> [Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html) + +## 覆盖与重载 + +- 覆盖(Override)存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法; + +- 重载(Overload)存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。 + +# 五、Object 通用方法 ## 概览 @@ -490,419 +904,177 @@ e1.set(2, 222); System.out.println(e2.get(2)); // 2 ``` -# 四、继承 +# 六、关键字 -## 访问权限 +## final -Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。 +**1. 数据** -可以对类或类中的成员(字段以及方法)加上访问修饰符。 +声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。 -- 成员可见表示其它类可以用这个类的实例访问到该成员; -- 类可见表示其它类可以用这个类创建对象。 - -protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。 - -设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。 - -如果子类的方法覆盖了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。 - -字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。可以使用公有的 getter 和 setter 方法来替换公有字段。 +- 对于基本类型,final 使数值不变; +- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。 ```java -public class AccessExample { - public int x; -} +final int x = 1; +// x = 2; // cannot assign value to final variable 'x' +final A y = new A(); +y.a = 1; ``` +**2. 方法** + +声明方法不能被子类重写。 + +private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。 + +**3. 类** + +声明类不允许被继承。 + +## static + +**1. 静态变量** + +静态变量在内存中只存在一份,只在类初始化时赋值一次。 + +- 静态变量:类所有的实例都共享静态变量,可以直接通过类名来访问它; +- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 + ```java -public class AccessExample { - private int x; +public class A { + private int x; // 实例变量 + private static int y; // 静态变量 - public int getX() { - return x; - } - - public void setX(int x) { - this.x = x; + 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. 静态方法** + +静态方法在类加载的时候就存在了,它不依赖于任何实例,所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。 ```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 { - - protected int x; +public class A { + 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. 使用选择** - -使用抽象类: - -- 需要在几个相关的类中共享代码。 -- 需要能控制继承来的方法和域的访问权限,而不是都为 public。 -- 需要继承非静态(non-static)和非常量(non-final)字段。 - -使用接口: - -- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法; -- 需要使用多重继承。 - -在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 - -> [深入理解 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 关键字来引用父类的方法实现。 - -```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. 静态内部类** -## 覆盖与重载 - -- 覆盖(Override)存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法; - -- 重载(Overload)存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。 - -# 五、String - -## String, StringBuffer and StringBuilder - -**1. 是否可变** - -- String 不可变 -- StringBuffer 和 StringBuilder 可变 - -**2. 是否线程安全** - -- String 不可变,因此是线程安全的 -- StringBuilder 不是线程安全的 -- StringBuffer 是线程安全的,内部使用 synchronized 来同步 - -> [String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder) - -## String 不可变的原因 - -**1. 可以缓存 hash 值** - -因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。 - -**2. String Pool 的需要** - -如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。 - -

- -**3. 安全性** - -String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。 - -**4. 线程安全** - -String 不可变性天生具备线程安全,可以在多个线程中安全地使用。 - -> [Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/) - -## String.intern() - -使用 String.intern() 可以保证相同内容的字符串实例引用相同的内存对象。 - -下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用,这个方法首先把 s1 引用的对象放到 String Poll(字符串常量池)中,然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。 +非静态内部类依赖于需要外部类的实例,而静态内部类不需要。 ```java -String s1 = new String("aaa"); -String s2 = new String("aaa"); -System.out.println(s1 == s2); // false -String s3 = s1.intern(); -System.out.println(s1.intern() == s3); // true -``` - -如果是采用 "bbb" 这种使用双引号的形式创建字符串实例,会自动地将新建的对象放入 String Poll 中。 - -```java -String s4 = "bbb"; -String s5 = "bbb"; -System.out.println(s4 == s5); // true -``` - -在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被放在堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 - -> [What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
[深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html) - -# 六、基本类型与运算 - -## 包装类型 - -八个基本类型: - -- boolean/1 -- byte/8 -- char/16 -- short/16 -- int/32 -- float/32 -- long/64 -- double/64 - -基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。 - -```java -Integer x = 2; // 装箱 -int y = x; // 拆箱 -``` - -new Integer(123) 与 Integer.valueOf(123) 的区别在于,new Integer(123) 每次都会新建一个对象,而 Integer.valueOf(123) 可能会使用缓存对象,因此多次使用 Integer.valueOf(123) 会取得同一个对象的引用。 - -```java -Integer x = new Integer(123); -Integer y = new Integer(123); -System.out.println(x == y); // false -Integer z = Integer.valueOf(123); -Integer k = Integer.valueOf(123); -System.out.println(z == k); // true -``` - -编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。 - -```java -Integer m = 123; -Integer n = 123; -System.out.println(m == n); // true -``` - -valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接使用缓存池的内容。 - -```java -public static Integer valueOf(int i) { - if (i >= IntegerCache.low && i <= IntegerCache.high) - return IntegerCache.cache[i + (-IntegerCache.low)]; - return new Integer(i); -} -``` - -在 Java 8 中,Integer 缓存池的大小默认为 -128\~127。 - -```java -static final int low = -128; -static final int high; -static final Integer cache[]; - -static { - // high value may be configured by property - int h = 127; - String integerCacheHighPropValue = - sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); - if (integerCacheHighPropValue != null) { - try { - int i = parseInt(integerCacheHighPropValue); - i = Math.max(i, 127); - // Maximum array size is Integer.MAX_VALUE - h = Math.min(i, Integer.MAX_VALUE - (-low) -1); - } catch( NumberFormatException nfe) { - // If the property cannot be parsed into an int, ignore it. - } +public class OuterClass { + class InnerClass { } - high = h; - cache = new Integer[(high - low) + 1]; - int j = low; - for(int k = 0; k < cache.length; k++) - cache[k] = new Integer(j++); + static class StaticInnerClass { + } - // range [-128, 127] must be interned (JLS7 5.1.7) - assert IntegerCache.high >= 127; + 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(); + } } ``` -Java 还将一些其它基本类型的值放在缓冲池中,包含以下这些: +静态内部类不能访问外部类的非静态的变量和方法。 -- boolean values true and false -- all byte values -- short values between -128 and 127 -- int values between -128 and 127 -- char in the range \u0000 to \u007F - -因此在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。 - -> [Differences between new Integer(123), Integer.valueOf(123) and just 123 -](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123) - -## switch - -从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。 +**5. 静态导包** ```java -String s = "a"; -switch (s) { - case "a": - System.out.println("aaa"); - break; - case "b": - System.out.println("bbb"); - break; +import static com.xxx.ClassName.* +``` + +在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。 + +**6. 初始化顺序** + +静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。 + +```java +public static String staticField = "静态变量"; +``` + +```java +static { + System.out.println("静态语句块"); } ``` -switch 不支持 long,是因为 swicth 的设计初衷是为那些只需要对少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。 - ```java -// long x = 111; -// switch (x) { // Incompatible types. Found: 'long', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum' -// case 111: -// System.out.println(111); -// break; -// case 222: -// System.out.println(222); -// break; -// } +public String field = "实例变量"; ``` -> [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 +{ + System.out.println("普通语句块"); +} +``` + +最后才是构造函数的初始化。 + +```java +public InitialOrderTest() { + System.out.println("构造函数"); +} +``` + +存在继承的情况下,初始化顺序为: + +- 父类(静态变量、静态语句块) +- 子类(静态变量、静态语句块) +- 父类(实例变量、普通语句块) +- 父类(构造函数) +- 子类(实例变量、普通语句块) +- 子类(构造函数) # 七、反射 @@ -968,10 +1140,6 @@ Java 注解是附加在代码中的一些元信息,用于一些工具在编译 # 十一、特性 -## 面向对象三大特性 - -> [封装、继承、多态](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#%E5%B0%81%E8%A3%85%E7%BB%A7%E6%89%BF%E5%A4%9A%E6%80%81) - ## Java 各版本的新特性 **New highlights in Java SE 8**