一、基本数据类型的系统描述: 1. 总述: Java 基本数据类型分为两大类: boolean 类型和数值类型。数值类型可分为整数类型和浮点类型,而其中字符类型可单独对待。所以 Java 只包含 8 种基本数据类型。 !注意!字符串不是基本数据类型,字符串是一个类,是一个引用类型。这个在下一篇我们会仔细讨论它! boolean 数值只有 true 和 false ,不能用 0 代替。其他数值类型不能转换成 boolean 。包装类– Boolean byte 内存 8 位,无符号位时最大存储 255 ,表数范围: -128~127 。包装类– Byte short 内存 16 位,无符号位时最大存储 65536 ,表数范围: -32768~32767 。包装类– Short int 内存 32 位,无符号位时最大存储 2 的 32 次方减 1 ,表数范围:负的 2 的 31 次方到正的 2 的 31 次方减 1 。包装类– Integer 。 long 内存 64 位,无符号位时最大存储 2 的 64 次方减 1 ,表数范围:负的 2 的 63 次方到正的 2 的 63 次方减 1 。包装类– Long 。 float 内存 32 位,数据范围在 3.4e-45~1.4e38 ,直接赋值时必须在数字后加上 f 或 F 。包装类– Float 。 double 内存 64 位,数据范围在 4.9e-324~1.8e308 ,赋值时可以加 d 或 D 也可以不加。包装类– Double 。 char : 16 位,存储 Unicode 字符集,用单引号赋值。可以参与加减乘除运算的,也可以比较大小的!!包装类– Character 。 二、数据类型的包装类理解(含部分源码解析) 首先要知道为什么 Java 会为每一个基础数据类型都提供一个相应包装类的目的,在于将 Java 的所有东西都抽象成对象,可以更方便的控制和使用。这就是面向对象! 然后对于包装类,主要作用是: 1. 作为和基本数据类型对应的类类型存在,方便涉及到对象的操作。 2. 包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法。 下面我们将一起讨论下包装类中的重要源码!! 1. 深入 boolean 基本类型、 Boolean 类以及细节点: public final class Boolean implements java.io.Serializable, Comparable<Boolean>{ // 看这两个对应的原始对象。享元模式的使用,达到多个对象都使用一份内存。至于什么是享元,以及它与单例的区别,这里就不多说了。 public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); private final boolean value; // 两个构造器,可见它是可以为 null 的啦,使用 Boolean 这个类的话 public Boolean(boolean value) { this.value = value; } public Boolean(String s) { this(parseBoolean(s)); } public static boolean parseBoolean(String s) { return ((s != null) && s.equalsIgnoreCase("true")); } //jdk 文档建议用 valueOf 代替 new 方式来创建 Boolean 类对象。 new 创建的 Boolean 对象是不断的新创建一个实例对象,而 valueOf 则是返回 Boolean 类里的静态成员变量 , 也就是使用享元模式的那个对象。 public static Boolean valueOf(String s) { return parseBoolean(s) ? TRUE : FALSE; } // 下面是令人困惑的设计了,我也是看了下 stackoverflow 里面讨论才有点懂。 // 原汁原味链接: http://stackoverflow.com/questions/3912303/boolean-hashcode //1. 使用质素是因为假如要把 Boolean 指插入到 hashtable 中,如果不是质素的话可能会比较容易造成哈希冲突。符合对象计算 hashcode 的时候通常会把各个属性的 hashcode 相加然后再做 hash, 如果是比较小的质素,容易造成 hash 分布不均匀。 //2. Maps 是可以包裹 Boolean 的,而如果 map 除了包含 Boolean 对象,还包含其他对象,那么如果不适当处理,就很容易有冲突了 public static int hashCode(boolean value) { return value ? 1231 : 1237; } } 总括下: 1.boolean 是基础数据类型,而 Boolean 是一个类。 2.boolean 一般存在于桟空间中,而 Boolean 对象存在堆空间中。 3.boolean 有 true 和 false 俩种值, Boolean 除了 true 和 false 外,还有 null 。 下面我们看份代码:(解析在代码中) public class Main { public static void main (String []args) { Boolean bool1 = Boolean.valueOf(true); // 这里均使用 valueof 创建对象, new 创建的 Boolean 对象是不断的新创建一个实例对象,而 valueOf 则是返回 Boolean 类里的静态成员变量 Boolean bool2 = Boolean.valueOf("True"); // 这里上一句代码验证使用 String 变量作为参数时,不区分大小写的。 Boolean bool3 = Boolean.valueOf("ASD"); boolean x1 = bool1.booleanValue(); boolean x2 = bool2.booleanValue(); System.out.println("bool1:" + x1 + ",bool2:" + x2 + ",bool3:" + bool3); boolean x3 = bool1.equals(bool2); // 这个就是验证享元模式,使用的是同一个对象 boolean x4 = bool1.equals(bool3); // 肯定不是同一对象啦。 System.out.println("bool1.equals(bool2):" + x3 + ",bool1.equals(bool3):" + x4); String str1 = Boolean.toString(bool1); // 可见 Boolean 对象是可以转换成字符的 String str2 = Boolean.toString(false); String str3 = bool3.toString(); System.out.println("bool1:" + str1 + ",str2:" + str2 + ",bool3:" + str3); boolean x5 = Boolean.parseBoolean("ASD"); // 源码是直接判断然后与 true 对比,因此打印为 false System.out.println(x5); } } // 也可以看到是一个 final 修饰的类,只能用,不能被继承咯 public final class Byte extends Number implements Comparable<Byte>{ public static final int SIZE = 8; // 只能是一个字节咯 public Byte(byte value) { this.value = value; // 传入的要为 Byte 类型的值 } public Byte(String s) throws NumberFormatException { this.value = parseByte(s, 10); // 传入的要求是可转换成 Byte 的字符串 } private static class ByteCache { private ByteCache(){} static final Byte cache[] = new Byte[-(-128) + 127 + 1];// 声明缓存数组的长度为 256 static { for(int i = 0; i < cache.length; i++) cache = new Byte((byte)(i - 128));//然后将 -128~127 进行缓存 } } public static byte parseByte(String s, int radix) throws NumberFormatException { //radix是外汇返佣 http://www.kaifx.cn/ 解析字符串时候的基数,在此方法下有个解析基数的含义。 int i = Integer.parseInt(s, radix);//解析字符串并返回,所以 s 必须是 -128~127 的字符,至于为什么用这个方法 int 的包装类方法来解析,一会我们会谈到。 if (i < MIN_VALUE || i > MAX_VALUE) throw new NumberFormatException( "Value out of range. Value:\"" + s + "\" Radix:" + radix); return (byte)i; } public static Byte decode(String nm) throws NumberFormatException { int i = Integer.decode(nm);//一会重点讲解 Integer 的系列方法 if (i < MIN_VALUE || i > MAX_VALUE) throw new NumberFormatException( "Value " + i + " out of range from input " + nm); return valueOf((byte)i); } } b[0] = Byte.parseByte(“ 11 ” , 2) = 3 表示 字符串 11以 2 为基数表示为 10 进制的 byte 值是 3 ,这里的 11 表示的是一个 2 进制数 b[0] = Byte.parseByte(“ 11 ” , 3) = 4 表示 字符串 11以 3 为基数表示为 10 进制的 byte 值是 4 ,这里的 11 表示的是一个 3 进制数 byte=55; public final class Integer extends Number implements Comparable<Integer> { public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");//原始类型 int 的 Class 实例。 //所有可能的将数字表示为字符串的字符集合做缓存。 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 Integer(int value) { this.value = value; } public Integer(String s) throws NumberFormatException { this.value = parseInt(s, 10);//涉及了 String 转换成 int ,一会仔细讨论这个。 } //像上面 Byte 类型中解释的那样的方法,返回第二个参数所指定的进制数的第一个参数的字符串表示形式。处理各种进制的 Integer. public static String toString(int i, int radix) { if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) /* Use the faster version */ if (radix == 10) { return toString(i); } char buf[] = new char[33]; boolean negative = (i < 0); int charPos = 32; if (!negative) { i = -i; } while (i <= -radix) { buf[charPos--] = digits[-(i % radix)]; i = i / radix; } buf[charPos] = digits[-i]; if (negative) { buf[--charPos] = '-'; } return new String(buf, charPos, (33 - charPos)); } //一会有事例代码演示这个,这个其实就是把 int 型包装成 Integer 然后再转化成 String 字符串 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); } //与 toString 组合形成一方法去转换成字符串咯 static void getChars(int i, int index, char[] buf) { int q, r; int charPos = index; char sign = 0; if (i < 0) { //如果 i 为负数,则设置 i 的符号字符为 '-' 。 i = -i; //将负数转化为正数处理,提高效率 } // Generate two digits per iteration while (i >= 65536) { //如果 i 大于 65536 ,则每一次都获取十位和个位上的数字对应的字符。将值判断大小后取每个数字,较大的数字一次取两位(大数字运算消耗大) q = i / 100; // really: r = i - (q * 100); r = i - ((q << 6) + (q << 5) + (q << 2)); //利用位运算,每次获得 i 的最后两位数,不断循环提取处理 buf [--charPos] = DigitOnes[r]; //存储 r 中在个位数集合中对应的字符 buf [--charPos] = DigitTens[r]; //存储 r 中在十位数集合中对应的字符 } // Fall thru to fast mode for smaller numbers // assert(i <= 65536, i); for (;;) { //i<65536的情况,小数字运算消耗较小,故一次只取一位 q = (i * 52429) >>> (16+3);//52429/(2*19)约等于 1 ,此处这样设计是为了提高精度 r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ... //每次获得 i 的最后两位数 buf [--charPos] = digits [r];//取最后一位的数字 if (i == 0) break; } if (sign != 0) { buf [--charPos] = sign; //设置符号 } } //定义 sizeTable 表示 int 中每个位数中最大的数,用于简便确定 int 数的长度。 final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE }; //使用上面的 sizeTable 定义来确定 int 数的字符串表示长度。 static int stringSize(int x) { for (int i=0; ; i++) if (x <= sizeTable) return i+1; } //炒鸡重要的方法啦!! parseInt(String s,int radix) 使用第二个参数指定的基数,将字符串参数解析为有符号的整数。 parseInt(String s) 只能将数字字符串转化十进制数 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. */ 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"); } int result = 0; boolean negative = false; int i = 0, len = s.length();//i表示当前遍历的 s 的位数 int limit = -Integer.MAX_VALUE;//设置最小值为负的 Integer 的最大值 int multmin; int digit; if (len > 0) {//如果字符串长度大于 0 ,则进行转换 char firstChar = s.charAt(0);//获取第一位字符 if (firstChar < '0') { // Possible leading "+" or "-" if (firstChar == '-') {//判断是否为负数 negative = true; limit = Integer.MIN_VALUE;//将限制转换为 Integer 的最小值,不能小于 Integer 的最小值 } else if (firstChar != '+') throw NumberFormatException.forInputString(s);//第一个 char 不为 + 也不为 - ,则抛出异常 if (len == 1) // Cannot have lone "+" or "-" throw NumberFormatException.forInputString(s);//若只有一个符号,则抛出异常 i++; } multmin = limit / radix;//设定不同进制下的极限值 while (i < len) {//进行进制的转换 // Accumulating negatively avoids surprises near MAX_VALUE 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 { throw NumberFormatException.forInputString(s); } return negative ? result : -result;//根据符号返回正数还是负数 } //看吧,我们经常用的 parseInt 只是个帮我们制定好 10 进制规则的静态方法 public static int parseInt(String s) throws NumberFormatException { return parseInt(s,10); } private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[];//利用数组来缓存 //原理:初始化数组将一定范围的整数放到 cache 数组中,然后在调 valueOf 方法的时候首先判断范围然后从缓存数组中去抓取数据 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; //获取 Integer 中所有能保存的数据,初始化缓存数组 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; } private IntegerCache() {} } //还有这个我们经常用的,官方也推荐使用这个方法去创建对象的 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } } public static void main (String []args) { Integer i =null; i = Integer.valueOf(10); System.out.println(">>>>"+i.toString()); } public static void main(String[] args) { Integer a1 = 1; Integer a2 = 1; Integer b1 = 200; Integer b2 = 200; Integer c1 = Integer.valueOf(1); // Integer c2 = new Integer(1); 官方不推荐这种建对象的方法喔 Integer c2 = Integer.valueOf(1); Integer d1 = Integer.valueOf(200); Integer d2 = Integer.valueOf(200); System.out.println("a1==a2?" + (a1 == a2)); System.out.println("b1==b2?" + (b1 == b2)); System.out.println("c1==c2?" + (c1 == c2)); System.out.println("d1==d2?" + (d1 == d2)); } 上面一段代码的运行结果就是我们要深思的东西啦,也是结合源码要懂的东西。 a1==a2? true b1==b2? false c1==c2? false d1==d2? false 第一个为什么是 true呢,因为 Integer 的缓存机制嘛,刚刚我们看到的,缓存了 [-128,127] ,这些可以直接取出。而剩余的为什么是 false ,因为他们都超过了缓存的那个范围,就建了个新对象咯。 至于 short、 float 、 double 这类的包装类设计原理有的跟 Integer 差不多,但是比如 Double ,很难去阅读,感觉自己程度还不够,以后会补上。而 Character 这个包装类,,源码 8000 多行,我们就讨论它的一些基本知识吧。 Character 类在对象中包装一个基本类型 char 的值。 Character 类型的对象包含类型为 char 的单个字段。该类提供了几种方法,以确定字符的类别(小写字母,数字,等等),并将字符从大写转换成小写,从小写转换成大写。 Character 类的方法和数据是通过 UnicodeData 文件中的信息定义的。至于 Unicode 大家就百度了解下就好。 更多技术资讯可关注:itheimaGZ获取 |