共计 21261 个字符,预计需要花费 54 分钟才能阅读完成。
数据类型
根本类型
- byte/8
- char/16
- short/16
- int/32
- float/32
- long/64
- double/64
- boolean/~
boolean 只有两个值:true、false,能够应用 1 bit 来存储,然而具体大小没有明确规定。JVM 会在编译期间将 boolean 类型的数据转换为 int,应用 1 来示意 true,0 示意 false。JVM 反对 boolean 数组,然而是通过读写 byte 数组来实现的。
包装类型
根本类型都有对应的包装类型,根本类型与其对应的包装类型之间的赋值应用主动装箱与拆箱实现。
Integer x = 2; // 装箱 调用了 Integer.valueOf(2) | |
int y = x; // 拆箱 调用了 X.intValue() |
缓存池
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
- new Integer(123) 每次都会新建一个对象;
- Integer.valueOf(123) 会应用缓存池中的对象,屡次调用会获得同一个对象的援用。
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() 办法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就间接返回缓存池的内容。
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。
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.} | |
} | |
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; | |
} |
编译器会在主动装箱过程调用 valueOf() 办法,因而多个值雷同且值在缓存池范畴内的 Integer 实例应用主动装箱来创立,那么就会援用雷同的对象。
Integer m = 123; | |
Integer n = 123; | |
System.out.println(m == n); // true |
根本类型对应的缓冲池如下:
- 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
在应用这些根本类型对应的包装类型时,如果该数值范畴在缓冲池范畴内,就能够间接应用缓冲池中的对象。
在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很非凡,这个缓冲池的下界是 – 128,上界默认是 127,然而这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax=<size> 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 零碎属性,而后 IntegerCache 初始化的时候就会读取该零碎属性来决定上界。
String
概览
String 被申明为 final,因而它不可被继承。(Integer 等包装类也不能被继承)
在 Java 8 中,String 外部应用 char 数组存储数据。
public final class String | |
implements java.io.Serializable, Comparable<String>, CharSequence { | |
/** The value is used for character storage. */ | |
private final char value[];} |
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时应用 coder
来标识应用了哪种编码。
public final class String | |
implements java.io.Serializable, Comparable<String>, CharSequence { | |
/** The value is used for character storage. */ | |
private final byte[] value; | |
/** The identifier of the encoding used to encode the bytes in {@code value}. */ | |
private final byte coder; | |
} |
value 数组被申明为 final,这意味着 value 数组初始化之后就不能再援用其它数组。并且 String 外部没有扭转 value 数组的办法,因而能够保障 String 不可变。
不可变的益处
- 能够缓存 hash 值
因为 String 的 hash 值常常被应用,例如 String 用做 HashMap 的 key。不可变的个性能够使得 hash 值也不可变,因而只须要进行一次计算。
- String Pool 的须要
如果一个 String 对象曾经被创立过了,那么就会从 String Pool 中获得援用。只有 String 是不可变的,才可能应用 String Pool。
- 安全性
String 常常作为参数,String 不可变性能够保障参数不可变。例如在作为网络连接参数的状况下如果 String 是可变的,那么在网络连接过程中,String 被扭转,扭转 String 的那一方认为当初连贯的是其它主机,而理论状况却不肯定是。
- 线程平安
String 不可变性天生具备线程平安,能够在多个线程中平安地应用。
String, StringBuffer and StringBuilder
- 可变性
- String 不可变
- StringBuffer 和 StringBuilder 可变
- 线程平安
- String 不可变,因而是线程平安的
- StringBuilder 不是线程平安的
- StringBuffer 是线程平安的,外部应用 synchronized 进行同步
String Pool
字符串常量池(String Pool)保留着所有字符串字面量(literal strings),这些字面量在编译期间就确定。不仅如此,还能够应用 String 的 intern() 办法在运行过程将字符串增加到 String Pool 中。
当一个字符串调用 intern() 办法时,如果 String Pool 中曾经存在一个字符串和该字符串值相等(应用 equals() 办法进行确定),那么就会返回 String Pool 中字符串的援用;否则,就会在 String Pool 中增加一个新的字符串,并返回这个新字符串的援用。
上面示例中,s1 和 s2 采纳 new String() 的形式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 和 s2.intern() 办法获得同一个字符串援用。intern() 首先把 “aaa” 放到 String Pool 中,而后返回这个字符串援用,因而 s3 和 s4 援用的是同一个字符串。
String s1 = new String("aaa"); | |
String s2 = new String("aaa"); | |
System.out.println(s1 == s2); // false | |
String s3 = s1.intern(); | |
String s4 = s2.intern(); | |
System.out.println(s3 == s4); // true |
如果是采纳 “bbb” 这种字面量的模式创立字符串,会主动地将字符串放入 String Pool 中。
String s5 = "bbb"; | |
String s6 = "bbb"; | |
System.out.println(s5 == s6); // true |
在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永恒代。而在 Java 7,String Pool 被移到堆中。这是因为永恒代的空间无限,在大量应用字符串的场景下会导致 OutOfMemoryError 谬误。
new String(“abc”)
应用这种形式一共会创立两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。
- “abc” 属于字符串字面量,因而编译期间会在 String Pool 中创立一个字符串对象,指向这个 “abc” 字符串字面量;
- 而应用 new 的形式会在堆中创立一个字符串对象。
创立一个测试类,其 main 办法中应用这种形式来创立字符串对象。
public class NewStringTest {public static void main(String[] args) {String s = new String("abc"); | |
} | |
} |
应用 javap -verbose 进行反编译,失去以下内容:
// ... | |
Constant pool: | |
// ... | |
#2 = Class #18 // java/lang/String | |
#3 = String #19 // abc | |
// ... | |
#18 = Utf8 java/lang/String | |
#19 = Utf8 abc | |
// ... | |
public static void main(java.lang.String[]); | |
descriptor: ([Ljava/lang/String;)V | |
flags: ACC_PUBLIC, ACC_STATIC | |
Code: | |
stack=3, locals=2, args_size=1 | |
0: new #2 // class java/lang/String | |
3: dup | |
4: ldc #3 // String abc | |
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V | |
9: astore_1 | |
// ... |
在 Constant Pool 中,#19 存储这字符串字面量 “abc”,#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 办法中,0: 行应用 new #2 在堆中创立一个字符串对象,并且应用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。
以下是 String 构造函数的源码,能够看到,在将一个字符串对象作为另一个字符串对象的结构函数参数时,并不会齐全复制 value 数组内容,而是都会指向同一个 value 数组。
public String(String original) { | |
this.value = original.value; | |
this.hash = original.hash; | |
} |
运算
参数传递
Java 的参数是以值传递的模式传入办法中,而不是援用传递。
以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个办法时,实质上是将对象的地址以值的形式传递到形参中。
public class Dog { | |
String name; | |
Dog(String name) {this.name = name;} | |
String getName() {return this.name;} | |
void setName(String name) {this.name = name;} | |
String getObjectAddress() {return super.toString(); | |
} | |
} |
在办法中扭转对象的字段值会扭转原对象该字段值,因为援用的是同一个对象。
class PassByValueExample {public static void main(String[] args) {Dog dog = new Dog("A"); | |
func(dog); | |
System.out.println(dog.getName()); // B | |
} | |
private static void func(Dog dog) {dog.setName("B"); | |
} | |
} |
然而在办法中将指针援用了其它对象,那么此时办法里和办法外的两个指针指向了不同的对象,在一个指针扭转其所指向对象的内容对另一个指针所指向的对象没有影响。
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 | |
} | |
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 | |
} | |
} |
float 与 double
Java 不能隐式执行向下转型,因为这会使得精度升高。
1.1 字面量属于 double 类型,不能间接将 1.1 间接赋值给 float 变量,因为这是向下转型。
// float f = 1.1;
1.1f 字面量才是 float 类型。
float f = 1.1f;
隐式类型转换
因为字面量 1 是 int 类型,它比 short 类型精度要高,因而不能隐式地将 int 类型向下转型为 short 类型。
short s1 = 1; | |
// s1 = s1 + 1; |
然而应用 += 或者 ++ 运算符会执行隐式类型转换。
s1 += 1; | |
s1++; |
下面的语句相当于将 s1 + 1 的计算结果进行了向下转型:
s1 = (short) (s1 + 1);
switch
从 Java 7 开始,能够在 switch 条件判断语句中应用 String 对象。
String s = "a"; | |
switch (s) { | |
case "a": | |
System.out.println("aaa"); | |
break; | |
case "b": | |
System.out.println("bbb"); | |
break; | |
} |
switch 不反对 long、float、double,是因为 switch 的设计初衷是对那些只有少数几个值的类型进行等值判断,如果值过于简单,那么还是用 if 比拟适合。
// 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; | |
// } |
关键字
final
- 数据
申明数据为常量,能够是编译时常量,也能够是在运行时被初始化后不能被扭转的常量。
- 对于根本类型,final 使数值不变;
- 对于援用类型,final 使援用不变,也就不能引用其它对象,然而被援用的对象自身是能够批改的。
final int x = 1; | |
// x = 2; // cannot assign value to final variable 'x' | |
final A y = new A(); | |
y.a = 1; |
- 办法
申明办法不能被子类重写。
private 办法隐式地被指定为 final,如果在子类中定义的办法和基类中的一个 private 办法签名雷同,此时子类的办法不是重写基类办法,而是在子类中定义了一个新的办法。
- 类
申明类不容许被继承。
static
- 动态变量
- 动态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享动态变量,能够间接通过类名来拜访它。动态变量在内存中只存在一份。
- 实例变量:每创立一个实例就会产生一个实例变量,它与该实例同生共死。
public class A { | |
private int x; // 实例变量 | |
private static int y; // 动态变量 | |
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 abstract class A {public static void func1(){ } | |
// public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'} |
只能拜访所属类的动态字段和静态方法,办法中不能有 this 和 super 关键字,因为这两个关键字与具体对象关联。
public class A { | |
private static 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 class A { | |
static {System.out.println("123"); | |
} | |
public static void main(String[] args) {A a1 = new A(); | |
A a2 = new A();} | |
} |
- 动态外部类
非动态外部类依赖于外部类的实例,也就是说须要先创立外部类实例,能力用这个实例去创立非动态外部类。而动态外部类不须要。
public class OuterClass {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();} | |
} |
动态外部类不能拜访外部类的非动态的变量和办法。
- 动态导包
在应用动态变量和办法时不必再指明 ClassName,从而简化代码,但可读性大大降低。
import static com.xxx.ClassName.*
- 初始化程序
动态变量和动态语句块优先于实例变量和一般语句块,动态变量和动态语句块的初始化程序取决于它们在代码中的程序。
public static String staticField = "动态变量";
static {System.out.println("动态语句块"); | |
} |
public String field = "实例变量";
{System.out.println("一般语句块"); | |
} |
最初才是构造函数的初始化。
public InitialOrderTest() {System.out.println("构造函数"); | |
} |
存在继承的状况下,初始化程序为:
- 父类(动态变量、动态语句块)
- 子类(动态变量、动态语句块)
- 父类(实例变量、一般语句块)
- 父类(构造函数)
- 子类(实例变量、一般语句块)
- 子类(构造函数)
Object 通用办法
概览
public native int hashCode() | |
public boolean equals(Object obj) | |
protected native Object clone() throws CloneNotSupportedException | |
public String toString() | |
public final native Class<?> getClass() | |
protected void finalize() throws Throwable {} | |
public final native void notify() | |
public final native void notifyAll() | |
public final native void wait(long timeout) throws InterruptedException | |
public final void wait(long timeout, int nanos) throws InterruptedException | |
public final void wait() throws InterruptedException |
equals()
等价关系 ,两个对象具备等价关系,须要满足以下五个条件:
- 自反性
x.equals(x); // true
- 对称性
x.equals(y) == y.equals(x); // true
- 传递性
if (x.equals(y) && y.equals(z)) | |
x.equals(z); // true; |
- 一致性
屡次调用 equals() 办法后果不变
x.equals(y) == x.equals(y); // true
- 与 null 的比拟
对任何不是 null 的对象 x 调用 x.equals(null) 后果都为 false
x.equals(null); // false;
等价与相等
- 对于根本类型,== 判断两个值是否相等,根本类型没有 equals() 办法。
- 对于援用类型,== 判断两个变量是否援用同一个对象,而 equals() 判断援用的对象是否等价。
Integer x = new Integer(1); | |
Integer y = new Integer(1); | |
System.out.println(x.equals(y)); // true | |
System.out.println(x == y); // false |
实现
- 查看是否为同一个对象的援用,如果是间接返回 true;
- 查看是否是同一个类型,如果不是,间接返回 false;
- 将 Object 对象进行转型;
- 判断每个要害域是否相等。
public class EqualExample { | |
private int x; | |
private int y; | |
private int z; | |
public EqualExample(int x, int y, int z) { | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
} | |
@Override | |
public boolean equals(Object o) {if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
EqualExample that = (EqualExample) o; | |
if (x != that.x) return false; | |
if (y != that.y) return false; | |
return z == that.z; | |
} | |
} |
hashCode()
hashCode() 返回哈希值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值肯定雷同,然而散列值雷同的两个对象不肯定等价,这是因为计算哈希值具备随机性,两个值不同的对象可能计算出雷同的哈希值。
在笼罩 equals() 办法时该当总是笼罩 hashCode() 办法,保障等价的两个对象哈希值也相等。
HashSet 和 HashMap 等汇合类应用了 hashCode() 办法来计算对象应该存储的地位,因而要将对象增加到这些汇合类中,须要让对应的类实现 hashCode() 办法。
上面的代码中,新建了两个等价的对象,并将它们增加到 HashSet 中。咱们心愿将这两个对象当成一样的,只在汇合中增加一个对象。然而 EqualExample 没有实现 hashCode() 办法,因而这两个对象的哈希值是不同的,最终导致汇合增加了两个等价的对象。
EqualExample e1 = new EqualExample(1, 1, 1); | |
EqualExample e2 = new EqualExample(1, 1, 1); | |
System.out.println(e1.equals(e2)); // true | |
HashSet<EqualExample> set = new HashSet<>(); | |
set.add(e1); | |
set.add(e2); | |
System.out.println(set.size()); // 2 |
现实的哈希函数该当具备平均性,即不相等的对象该当均匀分布到所有可能的哈希值上。这就要求了哈希函数要把所有域的值都思考进来。能够将每个域都当成 R 进制的某一位,而后组成一个 R 进制的整数。
R 个别取 31,因为它是一个奇素数,如果是偶数的话,当呈现乘法溢出,信息就会失落,因为与 2 相乘相当于向左移一位,最右边的位失落。并且一个数与 31 相乘能够转换成移位和减法:31*x == (x<<5)-x
,编译器会主动进行这个优化。
@Override | |
public int hashCode() { | |
int result = 17; | |
result = 31 * result + x; | |
result = 31 * result + y; | |
result = 31 * result + z; | |
return result; | |
} |
toString()
默认返回 ToStringExample@4554617c 这种模式,其中 @ 前面的数值为散列码的无符号十六进制示意。
public class ToStringExample { | |
private int number; | |
public ToStringExample(int number) {this.number = number;} | |
} |
ToStringExample example = new ToStringExample(123); | |
System.out.println(example.toString()); |
ToStringExample@4554617c
clone()
- cloneable
clone() 是 Object 的 protected 办法,它不是 public,一个类不显式去重写 clone(),其它类就不能间接去调用该类实例的 clone() 办法。
public class CloneExample { | |
private int a; | |
private int b; | |
} |
CloneExample e1 = new CloneExample(); | |
// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object' |
重写 clone() 失去以下实现:
public class CloneExample { | |
private int a; | |
private int b; | |
@Override | |
public CloneExample clone() throws CloneNotSupportedException {return (CloneExample)super.clone();} | |
} |
CloneExample e1 = new CloneExample(); | |
try {CloneExample e2 = e1.clone(); | |
} catch (CloneNotSupportedException e) {e.printStackTrace(); | |
} |
java.lang.CloneNotSupportedException: CloneExample
以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。
应该留神的是,clone() 办法并不是 Cloneable 接口的办法,而是 Object 的一个 protected 办法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 办法,就会抛出 CloneNotSupportedException。
public class CloneExample implements Cloneable { | |
private int a; | |
private int b; | |
@Override | |
public Object clone() throws CloneNotSupportedException {return super.clone(); | |
} | |
} |
- 浅拷贝
拷贝对象和原始对象的援用类型援用同一个对象。
public class ShallowCloneExample implements Cloneable {private int[] arr; | |
public ShallowCloneExample() {arr = new int[10]; | |
for (int i = 0; i < arr.length; i++) {arr[i] = i; | |
} | |
} | |
public void set(int index, int value) {arr[index] = value; | |
} | |
public int get(int index) {return arr[index]; | |
} | |
@Override | |
protected ShallowCloneExample clone() throws CloneNotSupportedException {return (ShallowCloneExample) super.clone();} | |
} |
ShallowCloneExample e1 = new ShallowCloneExample(); | |
ShallowCloneExample e2 = null; | |
try {e2 = e1.clone(); | |
} catch (CloneNotSupportedException e) {e.printStackTrace(); | |
} | |
e1.set(2, 222); | |
System.out.println(e2.get(2)); // 222 |
- 深拷贝
拷贝对象和原始对象的援用类型援用不同对象。
public class DeepCloneExample implements Cloneable {private int[] arr; | |
public DeepCloneExample() {arr = new int[10]; | |
for (int i = 0; i < arr.length; i++) {arr[i] = i; | |
} | |
} | |
public void set(int index, int value) {arr[index] = value; | |
} | |
public int get(int index) {return arr[index]; | |
} | |
@Override | |
protected DeepCloneExample clone() throws CloneNotSupportedException {DeepCloneExample result = (DeepCloneExample) super.clone(); | |
result.arr = new int[arr.length]; | |
for (int i = 0; i < arr.length; i++) {result.arr[i] = arr[i]; | |
} | |
return result; | |
} | |
} |
DeepCloneExample e1 = new DeepCloneExample(); | |
DeepCloneExample e2 = null; | |
try {e2 = e1.clone(); | |
} catch (CloneNotSupportedException e) {e.printStackTrace(); | |
} | |
e1.set(2, 222); | |
System.out.println(e2.get(2)); // 2 |
- clone() 的代替计划
应用 clone() 办法来拷贝一个对象即简单又有危险,它会抛出异样,并且还须要类型转换。Effective Java 书上讲到,最好不要去应用 clone(),能够应用拷贝构造函数或者拷贝工厂来拷贝一个对象。
public class CloneConstructorExample {private int[] arr; | |
public CloneConstructorExample() {arr = new int[10]; | |
for (int i = 0; i < arr.length; i++) {arr[i] = i; | |
} | |
} | |
public CloneConstructorExample(CloneConstructorExample original) {arr = new int[original.arr.length]; | |
for (int i = 0; i < original.arr.length; i++) {arr[i] = original.arr[i]; | |
} | |
} | |
public void set(int index, int value) {arr[index] = value; | |
} | |
public int get(int index) {return arr[index]; | |
} | |
} |
CloneConstructorExample e1 = new CloneConstructorExample(); | |
CloneConstructorExample e2 = new CloneConstructorExample(e1); | |
e1.set(2, 222); | |
System.out.println(e2.get(2)); // 2 |
继承
拜访权限
Java 中有三个拜访权限修饰符:private、protected 以及 public,如果不加拜访修饰符,示意包级可见。
能够对类或类中的成员(字段和办法)加上拜访修饰符。
- 类可见示意其它类能够用这个类创立实例对象。
- 成员可见示意其它类能够用这个类的实例对象拜访到该成员;
protected 用于润饰成员,示意在继承体系中成员对于子类可见,然而这个拜访修饰符对于类没有意义。
设计良好的模块会暗藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不须要晓得其余模块的外部工作状况,这个概念被称为信息暗藏或封装。因而拜访权限该当尽可能地使每个类或者成员不被外界拜访。
如果子类的办法重写了父类的办法,那么子类中该办法的拜访级别不容许低于父类的拜访级别。这是为了确保能够应用父类实例的中央都能够应用子类实例去代替,也就是确保满足里氏替换准则。
字段决不能是私有的,因为这么做的话就失去了对这个字段批改行为的管制,客户端能够对其随便批改。例如上面的例子中,AccessExample 领有 id 私有字段,如果在某个时刻,咱们想要应用 int 存储 id 字段,那么就须要批改所有的客户端代码。
public class AccessExample {public String id;}
能够应用私有的 getter 和 setter 办法来替换私有字段,这样的话就能够管制对字段的批改行为。
public class AccessExample { | |
private int id; | |
public String getId() {return id + "";} | |
public void setId(String id) {this.id = Integer.valueOf(id); | |
} | |
} |
然而也有例外,如果是包级公有的类或者公有的嵌套类,那么间接裸露成员不会有特地大的影响。
public class AccessWithInnerClassExample { | |
private class InnerClass {int x;} | |
private InnerClass innerClass; | |
public AccessWithInnerClassExample() {innerClass = new InnerClass(); | |
} | |
public int getValue() {return innerClass.x; // 间接拜访} | |
} |
抽象类与接口
- 抽象类
抽象类和形象办法都应用 abstract 关键字进行申明。如果一个类中蕴含形象办法,那么这个类必须申明为抽象类。
抽象类和一般类最大的区别是,抽象类不能被实例化,只能被继承。
public abstract class AbstractClassExample { | |
protected int x; | |
private int y; | |
public abstract void func1(); | |
public void func2() {System.out.println("func2"); | |
} | |
} |
public class AbstractExtendClassExample extends AbstractClassExample { | |
@Override | |
public void func1() {System.out.println("func1"); | |
} | |
} |
// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated | |
AbstractClassExample ac2 = new AbstractExtendClassExample(); | |
ac2.func1(); |
- 接口
接口是抽象类的延长,在 Java 8 之前,它能够看成是一个齐全形象的类,也就是说它不能有任何的办法实现。
从 Java 8 开始,接口也能够领有默认的办法实现,这是因为不反对默认办法的接口的保护老本太高了。在 Java 8 之前,如果一个接口想要增加新的办法,那么要批改所有实现了该接口的类,让它们都实现新增的办法。
接口的成员(字段 + 办法)默认都是 public 的,并且不容许定义为 private 或者 protected。从 Java 9 开始,容许将办法定义为 private,这样就能定义某些复用的代码又不会把办法裸露进来。
接口的字段默认都是 static 和 final 的。
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} |
public class InterfaceImplementExample implements InterfaceExample { | |
@Override | |
public void func1() {System.out.println("func1"); | |
} | |
} |
// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated | |
InterfaceExample ie2 = new InterfaceImplementExample(); | |
ie2.func1(); | |
System.out.println(InterfaceExample.x); |
- 比拟
- 从设计层面上看,抽象类提供了一种 IS-A 关系,须要满足里式替换准则,即子类对象必须可能替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种办法实现契约,并不要求接口和实现接口的类具备 IS-A 关系。
- 从应用上来看,一个类能够实现多个接口,然而不能继承多个抽象类。
- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限度。
- 接口的成员只能是 public 的,而抽象类的成员能够有多种拜访权限。
- 应用抉择
应用接口:
- 须要让不相干的类都实现一个办法,例如不相干的类都能够实现 Comparable 接口中的 compareTo() 办法;
- 须要应用多重继承。
应用抽象类:
- 须要在几个相干的类中共享代码。
- 须要能管制继承来的成员的拜访权限,而不是都为 public。
- 须要继承非动态和十分量字段。
在很多状况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,能够灵便地为一个类增加行为。并且从 Java 8 开始,接口也能够有默认的办法实现,使得批改接口的老本也变的很低。
super
- 拜访父类的构造函数:能够应用 super() 函数拜访父类的构造函数,从而委托父类实现一些初始化的工作。应该留神到,子类肯定会调用父类的构造函数来实现初始化工作,个别是调用父类的默认构造函数,如果子类须要调用父类其它构造函数,那么就能够应用 super() 函数。
- 拜访父类的成员:如果子类重写了父类的某个办法,能够通过应用 super 关键字来援用父类的办法实现。
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()"); | |
} | |
} |
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()"); | |
} | |
} |
SuperExample e = new SuperExtendExample(1, 2, 3); | |
e.func(); |
SuperExample.func() | |
SuperExtendExample.func() |
重写与重载
- 重写(Override)
存在于继承体系中,指子类实现了一个与父类在办法申明上完全相同的一个办法。
为了满足里式替换准则,重写有以下三个限度:
- 子类办法的拜访权限必须大于等于父类办法;
- 子类办法的返回类型必须是父类办法返回类型或为其子类型。
- 子类办法抛出的异样类型必须是父类抛出异样类型或为其子类型。
应用 @Override 注解,能够让编译器帮忙查看是否满足下面的三个限度条件。
上面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 办法。其中:
- 子类办法拜访权限为 public,大于父类的 protected。
- 子类的返回类型为 ArrayList<Integer>,是父类返回类型 List<Integer> 的子类。
- 子类抛出的异样类型为 Exception,是父类抛出异样 Throwable 的子类。
- 子类重写办法应用 @Override 注解,从而让编译器主动查看是否满足限度条件。
class SuperClass {protected List<Integer> func() throws Throwable {return new ArrayList<>(); | |
} | |
} | |
class SubClass extends SuperClass { | |
@Override | |
public ArrayList<Integer> func() throws Exception {return new ArrayList<>(); | |
} | |
} |
在调用一个办法时,先从本类中查找看是否有对应的办法,如果没有再到父类中查看,看是否从父类继承来。否则就要对参数进行转型,转成父类之后看是否有对应的办法。总的来说,办法调用的优先级为:
- this.func(this)
- super.func(this)
- this.func(super)
- super.func(super)
class A {public void show(A obj) {System.out.println("A.show(A)"); | |
} | |
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 {} |
public static void main(String[] args) {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) | |
} |
- 重载(Overload)
存在于同一个类中,指一个办法与曾经存在的办法名称上雷同,然而参数类型、个数、程序至多有一个不同。
应该留神的是,返回值不同,其它都雷同不算是重载。
class OverloadingExample {public void show(int x) {System.out.println(x); | |
} | |
public void show(int x, String y) {System.out.println(x + " " + y); | |
} | |
} |
public static void main(String[] args) {OverloadingExample example = new OverloadingExample(); | |
example.show(1); | |
example.show(1, "2"); | |
} |
反射
每个类都有一个 Class 对象,蕴含了与类无关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保留着 Class 对象。
类加载相当于 Class 对象的加载,类在第一次应用时才动静加载到 JVM 中。也能够应用 Class.forName("com.mysql.jdbc.Driver")
这种形式来管制类的加载,该办法会返回一个 Class 对象。
反射能够提供运行时的类信息,并且这个类能够在运行时才加载进来,甚至在编译期间该类的 .class 不存在也能够加载进来。
Class 和 java.lang.reflect 一起对反射提供了反对,java.lang.reflect 类库次要蕴含了以下三个类:
- Field:能够应用 get() 和 set() 办法读取和批改 Field 对象关联的字段;
- Method:能够应用 invoke() 办法调用与 Method 对象关联的办法;
- Constructor:能够用 Constructor 的 newInstance() 创立新的对象。
反射的长处:
- 可扩展性 :应用程序能够利用全限定名创立可扩大对象的实例,来应用来自内部的用户自定义类。
- 类浏览器和可视化开发环境 :一个类浏览器须要能够枚举类的成员。可视化开发环境(如 IDE)能够从利用反射中可用的类型信息中受害,以帮忙程序员编写正确的代码。
- 调试器和测试工具 :调试器须要可能查看一个类里的公有成员。测试工具能够利用反射来主动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。
反射的毛病:
只管反射十分弱小,但也不能滥用。如果一个性能能够不必反射实现,那么最好就不必。在咱们应用反射技术时,上面几条内容应该牢记于心。
- 性能开销 :反射波及了动静类型的解析,所以 JVM 无奈对这些代码进行优化。因而,反射操作的效率要比那些非反射操作低得多。咱们应该防止在常常被执行的代码或对性能要求很高的程序中应用反射。
- 平安限度 :应用反射技术要求程序必须在一个没有平安限度的环境中运行。如果一个程序必须在有平安限度的环境中运行,如 Applet,那么这就是个问题了。
- 外部裸露 :因为反射容许代码执行一些在失常状况下不被容许的操作(比方拜访公有的属性和办法),所以应用反射可能会导致意料之外的副作用,这可能导致代码性能失调并毁坏可移植性。反射代码毁坏了抽象性,因而当平台产生扭转的时候,代码的行为就有可能也随着变动。
异样
Throwable 能够用来示意任何能够作为异样抛出的类,分为两种:Error 和 Exception。其中 Error 用来示意 JVM 无奈解决的谬误,Exception 分为两种:
- 受检异样 :须要用 try…catch… 语句捕捉并进行解决,并且能够从异样中复原;
- 非受检异样 :是程序运行时谬误,例如除 0 会引发 Arithmetic Exception,此时程序解体并且无奈复原。
泛型
public class Box<T> { | |
// T stands for "Type" | |
private T t; | |
public void set(T t) {this.t = t;} | |
public T get() { return t;} | |
} |
注解
Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和应用,起到阐明、配置的性能。注解不会也不能影响代码的理论逻辑,仅仅起到辅助性的作用。
个性
Java 各版本的新个性
Java SE 8 的新性能
- Lambda 表达式
- 管道和流
- 日期和工夫 API
- 默认办法
- 类型注解
- Nashorn JavaScript 引擎
- 并发累加器
- 并行操作
Java SE 7 的新性能
- Switch 语句中的字符串
- 泛型实例创立时的类型推断
- 多异样解决
- 对动静语言的反对
- 资源的 try-with 语句
- Java nio 包
- 二进制字面量、字面量中的下划线
- 菱形语法
Java 与 C++ 的区别
- Java 是纯正的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即反对面向对象也反对面向过程。
- Java 通过虚拟机从而实现跨平台个性,然而 C++ 依赖于特定的平台。
- Java 没有指针,它的援用能够了解为平安指针,而 C++ 具备和 C 一样的指针。
- Java 反对主动垃圾回收,而 C++ 须要手动回收。
- Java 不反对多重继承,只能通过实现多个接口来达到雷同目标,而 C++ 反对多重继承。
- Java 不反对操作符重载,尽管能够对两个 String 对象执行加法运算,然而这是语言内置反对的操作,不属于操作符重载,而 C++ 能够。
- Java 的 goto 是保留字,然而不可用,C++ 能够应用 goto。
JRE or JDK
- JRE:Java Runtime Environment,Java 运行环境的简称,为 Java 的运行提供了所需的环境。它是一个 JVM 程序,次要包含了 JVM 的规范实现和一些 Java 根本类库。
- JDK:Java Development Kit,Java 开发工具包,提供了 Java 的开发及运行环境。JDK 是 Java 开发的外围,集成了 JRE 以及一些其它的工具,比方编译 Java 源码的编译器 javac 等。