前言
第一章详细分析了java的类型转换,不同数据类型之间的强转和隐转。
本来第二章应该分析面向对象的基础知识的,但是发现这种只有理论的知识分析起来很枯燥,并且也不容易理解。所以从第二章开始,将逐渐在文中阐述自己对面向对象的一些认识和理解。如果有不对的地方还望能多多帮忙斧正。
这一章基于JDK1.8版本,分析JAVA的包装类之Integer,因为这个类是经常用到的类,基础中的基础,所以分析透彻是非常有必要的。
面向对象
JAVA是一个世界,虚拟的世界。现实生活中的世界,是真实的世界。不管是什么样的世界,其组成原理必然遵循一定的法则。在我的理解中,JAVA是一个高度模仿现实世界的代码世界。古人云:道可道,非常道。这个道就是现实世界中万物遵循的一个法则,万物的抽象。
在太极中,道生两仪,两仪生四象,四象生八卦。道就是万物的开始。二进制就是计算机的开始。Object就是Java的开始。
于是在这基础上,诞生了形形色色的物种和物质。现实世界中有各种人,各种动物,各种植物,各种建筑。如果把这些用JAVA来模仿的话,就是不同种类的类,有Integer类,有String类,有File类,有Thread类等等…类是面向对象的开始。
世界有了这些人和物以后,它们便开始协调的配合,创建了工厂(Factory),发电机,采矿机(Mybatis),城市(Spring),等等。世界慢慢开始运转起来了。就像网站一样,可以给用户使用了。这个网站便是你创造的世界。
类是一个对象的模具。可以用人来比喻。它有属性,有对外的接口,也有支撑接口的方法。
比如:
属性(细胞、血液、蛋白质等等)
对外的接口(手、眼、鼻、嘴等等)
支撑接口的方法(骨骼、心脏、肺、肾等等);不同的器官支撑不同的对接,或者共同支撑一个对外接口。它们是多对多关系。
Integer
今天分析的这个类——Integer,也从这三个部分来分析。由外层向内层逐渐解析。我大概做了一个图,将Integer这个类做了几个划分。
图中左侧是对外API及部分内部方法,有三四十个。左侧是其属性。
Api我们大概分析常用的几种,下面黄色基本很少很少用,也比较复杂,等来日学艺精进后再来研究,灰色的很简单基本不用。
所以本章重点分析最上面的四组方法,分别对应的API有
第一组:构造器、parseInt();
第二组:valueOf()、IntegerCatch内部类
第三组:toString()、getChars()、stringSize();
第四组:toHexString()、toOctalString()、toBinaryString()、toUnsignedString0、formatUnsignedInt()
第一组:构造器、parseInt()
Integer的构造器有两个,如下:
# 1. 以int 类型为入参
public Integer(int value) {
this.value = value;
}
# 2. 以String 类型为入参
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}
其中,以int类型为入参很简单,就是将入参值赋予给Integer对象中维持的value属性。
而以String 为入参的构造器,其核心调用的是parseInt方法,因此,我们具体来分析一下parseInt();
(1) parseInt()方法
parseInt()方法的作用,就是将一个字符串转换为数字。
它有两个方法重载。分别是
parseInt(String s) 和 pasenInt(String s ,int radix),其中 s 是要转换的对象,radix是转换的进制基数,如radix = 10,就是十进制。parseInt(String s) 方法默认是转换为十进制。 我们重点分析 parseInt(String s,int radix) 。
parseInt(String s,int radix) 注释翻译
/**
* Parses the string argument as a signed integer in the radix
* specified by the second argument.
*
* 翻译:将字符串参数作为基数中的有符号整数进行分析由第二个参数指定
*
* The characters in the string
* must all be digits of the specified radix
* (as determined by whether {@link java.lang.Character#digit(char, int)} returns a nonnegative value),
*
* 翻译:字符串中的字符必须都是指定基数的数字,
* 就像 Character类中的 digit(char,int)方法一样,返回一个非负值。
*
* except that the first character may be an
* ASCII minus sign {@code '-'} ({@code '\u005Cu002D'}) to
* indicate a negative value or an ASCII plus sign {@code '+'}
* ({@code '\u005Cu002B'}) to indicate a positive value. The
* resulting integer value is returned.
*
* 翻译:第一个字符可能是ASCII减号(-)或者加号(+),都将返回一个整数值。
*
* <p>An exception of type {@code NumberFormatException} is
* thrown if any of the following situations occurs:
*
* 翻译: 如果发生下列情况之一,将会返回NumberFormatException
*
* <ul>
* <li>The first argument is {@code null} or is a string of
* length zero.
*
* 翻译: 第一个参数值是null对象 或者其 string长度为0的情况
*
* <li>The radix is either smaller than
* {@link java.lang.Character#MIN_RADIX} or
* larger than {@link java.lang.Character#MAX_RADIX}.
*
* 翻译: radix参数值没有在 Character.MIN_RADIX 到 Character.MAX_RADIX 之间的
*
* <li>Any character of the string is not a digit of the specified
* radix, except that the first character may be a minus sign
* {@code '-'} ({@code '\u005Cu002D'}) or plus sign
* {@code '+'} ({@code '\u005Cu002B'}) provided that the
* string is longer than length 1.
*
* 翻译:转换对象的每个字符都是一个数字,加号或者减号除外。不能只有一个加号和减号
*
* <li>The value represented by the string is not a value of type
* {@code int}.
* </ul>
*
* 翻译:转换对象表达的不是一个int类型的数字。而是其他类型如 char 或者 boolean等等
*
* <p>这里举了几个例子:
* <blockquote><pre>
* parseInt("0", 10) returns 0
* parseInt("473", 10) returns 473
* parseInt("+42", 10) returns 42
* parseInt("-0", 10) returns 0
* parseInt("-FF", 16) returns -255
* parseInt("1100110", 2) returns 102
* parseInt("2147483647", 10) returns 2147483647
* parseInt("-2147483648", 10) returns -2147483648
* parseInt("2147483648", 10) throws a NumberFormatException
* parseInt("99", 8) throws a NumberFormatException
* parseInt("Kona", 10) throws a NumberFormatException
* parseInt("Kona", 27) returns 411787
* </pre></blockquote>
*
* @param s the {@code String} containing the integer
* representation to be parsed
* @param radix the radix to be used while parsing {@code s}.
* @return the integer represented by the string argument in the
* specified radix.
* @exception NumberFormatException if the {@code String}
* does not contain a parsable {@code int}.
*/
方法解析
public static int parseInt(String s, int radix) throws NumberFormatException{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
* 警告: 此方法可以在初始化IntegerCache之前的VM初始化前调用。注意不要使用valueOf() 方法
*/
// 1. 入参判断
if (s == null) {
throw new NumberFormatException("null");
}
if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
// 2. 实现转换
int result = 0; //初始化返回参数
boolean negative = false; //默认这个字符串是一个正数,而不是负数,如“-234”
int i = 0, len = s.length(); // i 表示的真值开始的下标
int limit = -Integer.MAX_VALUE;
int multmin;
int digit;
// (1) 判断字符串的正负,及真值的长度
if (len > 0) {
char firstChar = s.charAt(0);
//这里通过比较第一个字符ASCII码的值,来判断
if (firstChar < '0') { // Possible leading "+" or "-"
// 如果为负号,转换标志参数,并界定下限不得低于 Integer能表达的最小值。
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
// 如果不为正或负,证明格式有误,抛出异常
throw NumberFormatException.forInputString(s);
// 当第一个字符小于0的情况下,肯定有一个符号(无论正负),但不能只有一个符号,因此需要判断字符串的长度
if (len == 1) // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s);
i++; // i 表示的真值开始的下标,此时字符串第一个字符为符号位,因此真值是从第二个开始,
// 也就是下标要加1。 如果字符串前面没有符号,则i从0开始就表示真值(此情况发生在正数身上)
}
//判断到这步,可以确定第一个字符肯定为+ 或者 为 -了,
//但是无法判断后续的字符是否是数字还是其他字符。如 “++23++2”。
// (2) 对数值按radix表示的进制基数进行转换。
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
// 翻译:负数累加避免接近最大值时出现意外
// 这里调用digit(char,int)方法,digit()是个边界值判断,不过边界返回字符数字本身数值,超过边界即返回 -1
// 这个方法就是判断每个字符是不是数字,并返回相应的值
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
} else {
// 就像开头注释所说,如果这个字符串的长度为0,将会抛出异常。如字符串 "".
throw NumberFormatException.forInputString(s);
}
// 根据正负形判断返回正负值
return negative ? result : -result;
}
(2) 构造器
parseInt() 方法分析完后,现在可以很清晰的理解构造器了。
构造器的两个方法
# 1. 以int 类型为入参
public Integer(int value) {
this.value = value;
}
# 2. 以String 类型为入参
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}
第二组 valueOf() 和 IntegerCatch内部类
(1)valueOf()
valueOf 方法的作用就是包装类的自动装箱。
它有两个重载方法:
// int类型入参,自动装箱过程
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// String类型过程,其调用了parseInt() 方法,先转为int类型,再装箱。
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
从valueOf的方法中可以看出,其内部调用了IntegerCatch缓存内存类。如果没有在缓存内部类最高限和最低限之内,就新创建一个对象,否则提供缓存类中的对象。缓存类是怎样结构的呢,下面来分析一下。
(2) IntegerCatch 内部类
private static class IntegerCache {
static final int low = -128; //限定了最低值为 -128
static final int high; //最高值未限定,在其初始化过程中有其他作用。
static final Integer cache[]; // 维持的Integer数组
// 内部类加载过程中,初始化Integer数组,
static {
// high value may be configured by property
// 翻译: 最高限定值可以通过配置来决定。
int h = 127; // 如果没有配置,则默认最高限值为127,否则可以通过配置java.lang.Integer.IntegerCache.high的值来决定
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127); //如果配置的属性值小于127,那么还是采用默认的127作为最高限值
// Maximum array size is Integer.MAX_VALUE
// 如果配置的值过大,这里限定了最大不得超过Integer能表示的最大值 减去 129
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;
// 最后将最大缓存到最小缓存之间的对象存到Integer数组中
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++); //缓存中的顺序为 从最低值-128开始到最高值。
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
因此,回到valueOf() 方法中,如果调用者传入的值在最大缓存和最小缓存之间,那么就将IntegerCatch中对应的对象返回给调用者。否则重新创建一个对象。
第三组:toString() 、 getChars() 、StringSize()
为什么将这三个方法划分为一组呢,因为toString方法有两个重载,先看第一个toString方法
作用是将入参数字按不同的进制进行转换。
(1) toString() 方法
这个方法中调用了 difits[] 数组。数组中记录了0-9,a-z的小写字符。共36个字符。
为什么要定义这个数组呢? 因为 Character.MIN_RADIX =2,Character.MAX_RADIX = 36.
代表一个入参数值转换进制,从2进制到36进制都是允许的。所以需要提供这么多可能的字符,不同进制取不同的值。
final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
public static String toString(int i, int radix) {
//如果超过了进制范围,默认为10进制
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
radix = 10;
/* Use the faster version */
if (radix == 10) {
return toString(i);
}
char buf[] = new char[33]; // 创建一个33位长度的空数组
boolean negative = (i < 0);
int charPos = 32; //下标从32开始,代表buf[] 的最后一个值开始。
if (!negative) {
i = -i;
}
while (i <= -radix) {
buf[charPos--] = digits[-(i % radix)]; // 这里根据转换进制的不同,获取数组中对应的字符。
// 对于超过10进制而言的数据,允许小写字母来表示
// 最常见的16进制:0x834等等。
i = i / radix;
}
buf[charPos] = digits[-i];
if (negative) {
buf[--charPos] = '-';
}
return new String(buf, charPos, (33 - charPos));
}
(1)toString()
第二个toString()方法,其内部调用了getChars() 和 StringSize();
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
(2) getChars()方法
这个方法的目的是为了获取每个数字代表的字符是什么。看了这个方法,才知道大神们为了提升效率,无所不用其极!!巧妙了利用了两个一维数组,精妙的设计,每次迭代可以识别出相邻两位数字,惊叹!。
先看下定义的两个二维数组。
final static char [] DigitTens = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
'2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
'3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
'4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
'5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
'6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
'7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
'8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
'9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
} ;
final static char [] DigitOnes = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
} ;
方法体:
static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// Generate two digits per iteration
// 每次迭代识别两位数字
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
r = i - ((q << 6) + (q << 5) + (q << 2)); // 这里使用位运算,向左移了6位也就是乘以 2^6 = 64, 2^5 =32, 2^2 = 4.
// 整个意思就是 q * 100
// 可能为了提升CPU做乘法运算的效率,因此这样写的。
// 每次循环,r 都为 入参值的最后两位。比如36
i = q;
buf [--charPos] = DigitOnes[r]; // 然后 36在这里是第三行,得到3,
buf [--charPos] = DigitTens[r]; // 然后 36在这里是第三行, 得到6,
}
// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
q = (i * 52429) >>> (16+3);
r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
buf [--charPos] = digits [r];
i = q;
if (i == 0) break;
}
if (sign != 0) {
buf [--charPos] = sign;
}
}
(3) stringSize() 方法
这也是一个很巧妙的设计,值得学习。通过比较该位上最大值来判断其长度,并将长度融入下标中。极大化的精简了代码和程序的运行。
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };
// Requires positive x
static int stringSize(int x) {
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
第四组:toHexString()、toOctalString()、toBinaryString()、toUnsignedString0、formatUnsignedInt()
上个结构图:
这一组方法中,对外接口分别是 toHexString()、toOctalString()、toBinaryString()。这几个接口的功能就是将数字转换为16进制、8进制、2进制的作用。
实例演示
int a = 16;
System.out.println(Integer.toBinaryString(a)); // 结果:10000
System.out.println(Integer.toOctalString(a)); // 结果:20
System.out.println(Integer.toHexString(a)); // 结果 : 10
这三个对外接口的核心实现都是 toUnsignedString0()方法。源码是这样的。
// toBinaryString()方法:
public static String toBinaryString(int i) {
return toUnsignedString0(i, 1);
}
// toOctalString()方法
public static String toOctalString(int i) {
return toUnsignedString0(i, 3);
}
// toHexString()方法
public static String toHexString(int i) {
return toUnsignedString0(i, 4);
}
下来具体分析核心方法 toUnsignedString0();
(1) toUnsignedString0()
这个方法的作用就是
/**
* Convert the integer to an unsigned number.
*/
private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
//这里用了Integer.numberOfLeadingZeros(val)方法,这个方法作用是计算传入值转换为二进制后,
// 真值前面有多少个零. 如: System.out.println(Integer.numberOfLeadingZeros(16));,
// 16的二进制是10000.Integer占4个字节共计32位,因此前面有27个零。这个结果就是27;
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
// 这一步暂时没看懂
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];
formatUnsignedInt(val, shift, buf, 0, chars);
// Use special constructor which takes over "buf".
return new String(buf, true);
}
总结
第一组作用是介绍将字符串转换为数字。
第二组方法是介绍Integer的自动装箱。
第三组方法是介绍将数字转换为不同进制的字符串表示,返回值是字符串。
第四组方法也是介绍将数字转换为不同进制的字符串表示,返回值是字符串。和第三组方法有点重复了。
以上大概就是Integer类常用的方法源码分析。这一部分只占Integer整个类的一小部分,需要学习的还有很多很多啊。不过那些不常见不常用的,等以后用到了再来学习吧!