Integer
内部存储数据格式
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
所以Integer类中只有构造器和getter方法,并没有setter方法,原因就是value被final修饰不可再更改。因此我们想要修改Integer类的值只能新建Integer对象
构造器
* @deprecated
* It is rarely appropriate to use this constructor. The static factory
* {@link #valueOf(int)} is generally a better choice, as it is
* likely to yield significantly better space and time performance.
*/
@Deprecated(since="9")
public Integer(int value) {
this.value = value;
}
这种构造器现已不推荐使用,建议换成静态方法public static Integer valueOf(int i)
关于该方法将在常量池中进一步说明
* @deprecated
* It is rarely appropriate to use this constructor.
* Use {@link #parseInt(String)} to convert a string to a
* {@code int} primitive, or use {@link #valueOf(String)}
* to convert a string to an {@code Integer} object.
*/
@Deprecated(since="9")
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}
将传入的字符串按10进制进行解释。如"123",返回value为123的Integer对象
常量池
实际上利用static在类加载时,预先为[-128,127]区间整数建立Integer对象,并在日后需要时将引用指向这些预加载的对象。
下面我们看一下public static Integer valueOf(int i)
的源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
当传入参数IntegerCache.low<=i<=IntegerCache.high
时,返回IntegerCache.cache[]
中的对象,否则在堆中新建实例。
接着我们再看Integer
中的静态内部类IntegerCache
的部分源码
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
for(int i = 0; i < c.length; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
}
cache = archivedCache;
可以看到在类加载时会预先为某个区间缓存。(这个区间就是[-128,127],为啥看不懂)
我们可以用如下代码进行测试
Integer a1 = -127;
Integer a2 = -127;
Integer b1 = 128;
Integer b2 = 128;
System.out.println(a1==a2);
System.out.println(b1==b2);
常用方法
-
hashCode
/** * Returns a hash code for an {@code int} value; compatible with * {@code Integer.hashCode()}. * * @param value the value to hash * @since 1.8 * * @return a hash code value for an {@code int} value. */ public static int hashCode(int value) { return value; }
将Integer中保存的int值作为hash code
-
equals
public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; }
调用
equals()
判断相等时,首先先进行类型匹配判断,如果传入了非Integer的实例,则直接判定为false。
如Integer i = 10; Byte b = 10; System.out.println("i == b " + i.equals(b));\\i==b false
之后比较内部保存的int变量。
Integer a1 = 10; Integer a2 = new Integer(10); System.out.println(a1==a2);//false System.out.println(a1.equals(a2));//true
note:关于其中==与equals方法的不同这里不做说明,详情百度
-
sum
在java里没有重载操作符的,所以Integer a1 = 10; Integer a2 = new Integer(10); System.out.println(20==(a1+a2)); //true
上面这段代码实际上进行了自动拆箱(见下),因为Integer是不能相加的。最后是两个int类型的数据判断,而非地址判断。
自动拆装箱
自动拆装箱是一个语法糖。所谓语法糖就是让你在写代码的时候少写点,后期编译器帮你加上。我们来看这一段程序
```java
public static void main(String[] args) {
Integer a1 = 10;
Integer a2 = new Integer(10);
System.out.println(Integer.valueOf(20)==(a1+a2));
}
```
通过javap指令查看字节码指令我们可以看到,实际运行(部分)时是这样的
```
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
```
执行`Integer a1 = 10;`实际调用了Integer类的静态方法`Integer.valueOf`进行装箱
在判断`Integer.valueOf(20)==(a1+a2)`时
```
28: invokevirtual #6 // Method java/lang/Integer.intValue:()I
31: aload_1
32: invokevirtual #6 // Method java/lang/Integer.intValue:()I
35: aload_2
36: invokevirtual #6 // Method java/lang/Integer.intValue:()I
39: iadd
```
调用`Integer.intValue()`方法进行拆箱,最后是俩个基本类型做比较
String
官方文档 Java SE8 String
成员变量
/**
* The value is used for character storage.
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*
* Additionally, it is marked with {@link Stable} to trust the contents
* of the array. No other facility in JDK provides this functionality (yet).
* {@link Stable} is safe here, because value is never null.
*/
@Stable
private final byte[] value;
存储字符串内容。
note:同Integer一样,也是final修饰。表示String一经创建则不可变
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private final byte coder;
编码类型。支持LATIN1和UTF16,其中UTF16编码一个字符占2位
/** Cache the hash code for the string */
private int hash; // Default to 0
hash值缓存。关于hash将在下文详细说明
构建String对象
- 直接赋值
String s1 = "abc";
引用方法区的运行时常量池对象(关于常量池在下文中具体说明),这一过程中没有新建String对象 - String(String)
String s2 = new String("abc");
这种方法比较浪费内存。该构造方法会在heap中申请一块内存,内存中存放字符串内容的引用。所以在使用时,我们访问了引用指向内存中的String引用。因此初始化String为常量时,推荐上面的写法。
常量池
和字符串相关的常量池有三个,分别是:.class文件中的常量池,运行时常量池和字符串常量池
1.常量池
存放编译期生成的字面量及符号引用,详情请调用javap指令看一下,这里只放个截图
2. 运行时常量池
位于方法区,在类加载之后由常量池转换而来,还附加了部分符号引用的直接引用
3. 全局字符串常量池
存放String实例的引用
接下来我们看一看这几个常量池的关系:
运行时常量池由常量池转化而来,在程序初始 全局字符串常量池 沿用了 运行时常量池
为了验证这一结论,笔者设计这么一段程序 如有错误,还请务必指出
public class C1{
public static String s0 = "abc";
public String s1 = "abc";
}
public class language {
public static String s0 = "abc";
public static void main(String[] args) {
C1 tmp = new C1();
String s1 = "abc";
String s2 = new String("abc");
System.out.println(C1.s0==s0); //ture
System.out.println(s1==s0); //true
System.out.println(s1==tmp.s1); //true
}
}
首先,C1.s0==s0
说明运行时常量池不是私有的,而是共享的。
其次,下面两句表明:对于已出现的字面量,将直接引用运行时常量池中的实例对象,而不是在首次运行时创建
而对于没有出现在运行时常量池中的字符串,如下
public static String s0 = "abc";
public static String s3 = "abcabc";
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
System.out.println((s1+s1)==s3); \\false
}
则新建对象加入全局字符串常量池
在1.8以前,对字符串的相加会被编译器转换为StringBuilder的append,然后toString返回字符串;
在1.8以后,则是调用了方法`makeConcatWithConstants`进行字符串的拼接,执行过程较为复杂,暂不说明<del>如果看到记得喊我回来天坑</del>
常用方法
-
intern
Returns a canonical representation for the string object.
When the intern method is invoked, if the pool already contains a string equal to this {@code String} object as determined by the {@link #equals(Object)} method, then the string from the pool is returned. Otherwise, this {@code String} object is added to the pool and a reference to this {@code String} object is returned.根据JDK源码的注释,intern方法用于向全局字符串常量池(以下简称常量池,并非代表class文件中的常量池)中添加String实例。
调用重写的equals()
方法判断是否相同。
如果在常量池中已有相同的文本,则返回常量池中的引用;如果没有,则向常量池中添加该对象的引用,并返回该对象的引用
下面用一段程序验证前半句public class C1{ public static String s1 = "abcabc"; }
public static void main(String[] args) { String s0 = "abc"; String s1 = s0 + s0; System.out.println(s1==C1.s1); //false String s2 =s1.intern(); System.out.println(s2==C1.s1); //true System.out.println(s1==s2); //false }
在类加载时,会将字面量添加到常量池中,而根据
s1==C1.s1
可知s1此并不在常量池中,调用intern方法向常量池中添加,由于常量池中已存在该文本实例,因此返回了C1.s1
的引用 -
equals
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String aString = (String)anObject; if (coder() == aString.coder()) { return isLatin1() ? StringLatin1.equals(value, aString.value) : StringUTF16.equals(value, aString.value); } } return false; } @HotSpotIntrinsicCandidate public static boolean equals(byte[] value, byte[] other) { if (value.length == other.length) { int len = value.length >> 1; for (int i = 0; i < len; i++) { if (getChar(value, i) != getChar(other, i)) { return false; } } return true; } return false; }
首先比较地址,之后比较类型,最后遍历是否相同
-
hashcode
public static int hashCode(byte[] value) { int h = 0; int length = value.length >> 1; for (int i = 0; i < length; i++) { h = 31 * h + getChar(value, i); } return h; }
就一散列码,没啥好说的,如果有对系数31感兴趣的可以看看底下知乎的链接
-
subString
public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = length() - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } if (beginIndex == 0) { return this; } return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen) : StringUTF16.newString(value, beginIndex, subLen); } public static String newString(byte[] val, int index, int len) { if (String.COMPACT_STRINGS) { byte[] buf = compress(val, index, len); if (buf != null) { return new String(buf, LATIN1); } } int last = index + len; return new String(Arrays.copyOfRange(val, index << 1, last << 1), UTF16); }
注意:String类是不可变的,因此创建字串是构建了一个新的String实例,而并非对原有String改动。中间借助了byte数组作为缓存实现对String的改动。
参考文章