byte/8java
char/16程序员
short/16数组
int/32浏览器
float/32缓存
long/64安全
double/64bash
boolean/~网络
booleanjvm
boolean只有两个值:true、false,可使用 1 bit 来存储,可是具体大小没有明确规定。JVM 会在编译时期将 boolean 类型的数据转换为 int,使用 1 来表示 true,0 表示 false。JVM 支持 boolean 数组,可是是经过读写 byteide
所占位数->精度
float f = 1.1 编译没法经过。由于字面量 1.1 是 double 类型,它比 float 类型精度要高,所以不能隐式地将 double 类型下转型为 float 类型。
由于字面量 1 是 int 类型,它比 short 类型精度要高,所以不能隐式地将 int 类型下转型为 short 类型。
short s1 = 1;
// s1 = s1 + 1;
复制代码
可是使用 += 或者 ++ 运算符能够执行隐式类型转换。
s1 += 1;
// s1++;
复制代码
上面的语句至关于将 s1 + 1 的计算结果进行了向下转型:
s1 = (short) (s1 + 1);
复制代码
基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。
Integer x = 2; // 装箱
int y = x; // 拆箱
复制代码
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(缓存池实际也是经过new Integer()去实例的)。
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++);//这里说明也是经过new Integer去实例的
// 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= 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 系统属性,而后 IntegerCache 初始化的时候就会读取该系统属性来决定上界。
String 被声明为 final,所以它不可被继承。
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 来标识使用了哪一种编码。 这里咱们只讨论Java 8
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 不可变(此不可变非彼不可变)。
那么不可变有什么好处呢?
由于 String 的 hash 值常常被使用,例如 String 用作 HashMap 的 key。不可变的特性可使得 hash 值也不可变,所以只须要进行一次计算。
若是一个 String 对象已经被建立过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
String 常常做为参数,String 不可变性能够保证参数不可变。例如在做为网络链接参数的状况下若是 String 是可变的,那么在网络链接过程当中,String 被改变,改变 String 对象的那一方觉得如今链接的是其它主机,而实际状况却不必定是。
String 不可变性天生具有线程安全,能够在多个线程中安全地使用。
String 不可变 StringBuffer 和 StringBuilder 可变 2. 线程安全
String 不可变,所以是线程安全的 StringBuilder 不是线程安全的 StringBuffer 是线程安全的,内部使用 synchronized 进行同步
字符串常量池(String Pool)保存着全部字符串字面量(literal strings)
在 Java 7 以前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是由于永久代的空间有限,在大量使用字符串的场景下会致使 OutOfMemoryError 错误。
自动地将字符串放入 String Pool 中,而且返回该对象
String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6); // true
复制代码
使用这种方式一共会建立两个字符串对象(前提是 String Pool 中尚未 "abc" 字符串对象)。
"abc" 属于字符串字面量,所以编译时期会在 String Pool 中建立一个字符串对象,指向这个 "abc" 字符串字面量;
而使用 new 的方式会在堆中建立一个字符串对象。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
复制代码
个字符串调用 intern() 方法时,若是 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行肯定),那么就会返回 String Pool 中字符串的引用;不然,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
复制代码
public boolean equals(Object obj)
public native int hashCode()
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() 方法。
对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价
检查是否为同一个对象的引用,若是是直接返回 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;
}
}
复制代码
在覆盖 equals() 方法时应当老是覆盖 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
//因为许多基本类内部都经过hashCode()去判断key的相等
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size()); // 2
复制代码
那么咱们要如何重写hashCode()呢
理想的散列函数应当具备均匀性,即不相等的对象应当均匀分布到全部可能的散列值上。这就要求了散列函数要把全部域的值都考虑进来。能够将每一个域都当成 R 进制的某一位,而后组成一个 R 进制的整数。R 通常取 31,由于它是一个奇素数,若是是偶数的话,当出现乘法溢出,信息就会丢失,由于与 2 相乘至关于向左移一位。
另外一个选择31的缘由:
一个数与 31 相乘能够转换成移位和减法:31*x == (x<<5)-x,编译器会自动进行这个优化。
@Override
public int hashCode() {
int result = 17;
result = 31 * result + x;//若是x是引用类型 能够改为31*result + x.hashCode();
result = 31 * result + y;
result = 31 * result + z;
return result;
}
复制代码
默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
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() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,而且还须要类型转换。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
复制代码
声明数据为常量,能够是编译时常量,也能够是在运行时被初始化后不能被改变的常量。
对于基本类型,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 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
声明类不容许被继承。
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
父类>静态>实例块>构造函数
public static String staticField = "静态变量";
static {
System.out.println("静态语句块");
}
public String field = "实例变量";
{
System.out.println("普通语句块");
}
最后才是构造函数的初始化。
public InitialOrderTest() {
System.out.println("构造函数");
}
存在继承的状况下,初始化顺序为:
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数)
复制代码
可扩展性 :应用程序能够利用全限定名建立可扩展对象的实例,来使用来自外部的用户自定义类。 类浏览器和可视化开发环境 :一个类浏览器须要能够枚举类的成员。可视化开发环境(如 IDE)能够从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。 调试器和测试工具 : 调试器须要可以检查一个类里的私有成员。测试工具能够利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。
尽管反射很是强大,但也不能滥用。若是一个功能能够不用反射完成,那么最好就不用。在咱们使用反射技术时,下面几条内容应该牢记于心。
性能开销 :反射涉及了动态类型的解析,因此 JVM 没法对这些代码进行优化。所以,反射操做的效率要比那些非反射操做低得多。咱们应该避免在常常被执行的代码或对性能要求很高的程序中使用反射。
安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。若是一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
内部暴露 :因为反射容许代码执行一些在正常状况下不被容许的操做(好比访问私有的属性和方法),因此使用反射可能会致使意料以外的反作用,这可能致使代码功能失调并破坏可移植性。反射代码破坏了抽象性,所以当平台发生改变的时候,代码的行为就有可能也随着变化。
Throwable 能够用来表示任何能够做为异常抛出的类,分为两种: Error 和 Exception。其中 Error 用来表示 JVM 没法处理的错误,Exception 分为两种:
checked异常 :须要用 try...catch... 语句捕获并进行处理,而且能够从异常中恢复;
unchecked异常 :是程序运行时错误,例如除 0 会引起 Arithmetic Exception,此时程序崩溃而且没法恢复。