看到一篇写的很好的Java基础的文章,不自己再写一遍的话好像就没有看过一样,方便以后看,有很多东西都是直接copy过来的。最后分享一下原文的链接:原文链接
一、数据类型
1.Java有8种基本数据类型,基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。
- boolean/(1) —— Boolean 没有明确给出占的字节数,只是说明代表true和false两个值
- byte/8 —— Byte
- char/16 —— Character
- short/16 —— Short
- int/32 —— Integer
- float/32 —— Float
- long/64 —— Long
- double/64 —— Double
2.除了boolean类型外,其他的基本数据类型都可以相互转换。自动转换级别:
char--> byte-->short-->int-->long-->float-->double
3.缓冲池
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
Integer x = 1;
Integer y = 1;
System.out.println(x==y); //true
第一种方式每次都会创建一个新的对象,第二种方式会使用缓存池中的对象,多次调用会取得同一个对象的引用,第三种(自动装箱)会自动调用valueOf方法
基本类型对应的缓冲池如下:
- 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
在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。其他基本类型没有缓冲池概念。
Double a = 1d;
Double b = 2d;
System.out.println(a == b); //false
在 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;
}
从源码中我们可以看出,缓冲池中创建了缓冲池默认大小的对象。
二、String
1.String 被声明为 final,因此它不可被继承。
内部使用 char 数组存储数据,该数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
三、Object 通用方法
1.equals()
(1)该方法具有以下几个特点:自反性、对称性、传递性和一致性,对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
(2)equals()和==的比较
- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价
Long c = 1l;
System.out.println(c.equals(1)); //false
System.out.println(c==1); //true 会进行自动拆箱操作
Long x = new Long(1);
Long y = new Long(1);
System.out.println(x==y); //false
System.out.println(x.equals(y)); //true
String s = "1";
System.out.println(x.equals(s)); //false
System.out.println(s.equals(x)); //false
AccessToken accessToken = new AccessToken();
accessToken.setAccessToken("1");
AccessToken accessToken1 = new AccessToken();
accessToken1.setAccessToken("1");
System.out.println(accessToken.equals(accessToken1)); //false
我们可以通过源码来看一下为什么会出现这种情况
//Object的实现:
public boolean equals(Object obj) {
return (this == obj);
}//由此我们可以看出对于自定义类来说,如果没有重写equals方法,它就会调用父类方法,对于对象来说equals比较的仅仅是对象是否是自身比较
//String的实现:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//Long的实现:
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
//从上面两种包装类的实现上我们可以看出,每次在校验两个对象是否是等价时,首先会检查一下是否是同一种数据类型,如果数据类型不同就直接返回false,那么对于上面几种结果我们就可以理解了
//对于重写equals方法的思路跟String是差不多的,首先检查是否是自身比较和是否是同一类型,然后再比较各个域对应的值是否相等
2.hashCode()
hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
3.toString()
默认返回 对象具体名称@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
4.clone()
(1)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();
}
}
(2)浅拷贝
拷贝对象和原始对象的引用类型引用同一个对象,当原始对象修改后,拷贝对象也会修改
(3)深拷贝
拷贝对象和原始对象的引用类型引用不同对象,即当原始设备修改后,拷贝对象不会修改
(4)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
四、关键字
1.final
(1) 数据 声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
- 对于基本类型,final 使数值不变;
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。也就是对象的属性值可以修改
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) 类 声明类不允许被继承。
2.static
(1)被static修饰后的字段属性和方法,不需要实例化,直接通过类名就可以直接调用。
静态方法在类加载的时候就存在了,它不依赖于任何实例,所以静态方法必须有实现,也就是说它不能是抽象方法,而且只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。
静态语句块在类初始化时运行一次。
非静态内部类依赖于外部类的实例,而静态内部类不需要,静态内部类不能访问外部类的非静态的变量和方法。
(2)静态导包
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
import static com.xxx.ClassName.*
(3)初始化顺序
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
存在继承的情况下,初始化顺序为:
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)