基本数据类型的包装类
-
包装类基本知识
Java 是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。 比如:将基本数据类型存储到 Object[ ]数组或集合中的操作等等。为了解决这个不足,Java 在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。在这八个类名中,除了 Integer 和 Character 类以外,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写而已。在这八个类中,除了 Character 和 Boolean 以外,其他的都是“数字型”,“数字型”都是 java.lang.Number 的子类。Number 类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number 类提供了抽象方法:intValue()、longValue()、floatValue()、doubleValue(), 意味着所有的“数字型”包装类都可以互相转型。 - 包装类用途
对于包装类来说,这些类的用途主要包含两种:1. 作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如 Object[ ]、集合等的操作。2. 包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法( 这些操作方法的作用是在基本数据类型、包装类对象、字符串之间提供相互之间的转化! )。
public class Test01 { public static void main(String[] args) { //把数字转成包装类 Integer i1=new Integer(20); Integer i2=Integer.valueOf(30); //包装类转为基本数据类型 float i3=i2.floatValue(); //将字符串数字转成包装类对象 Integer i4=Integer.valueOf("304"); Integer i5=Integer.parseInt("305"); //将数字转成字符串 String s= i5.toString(); } } //包装类可以把基本数据类型,包装类,字符串之间相互转化
- 自动装箱和拆箱
自动装箱过程是通过调用包装类的 valueOf()方法实现的,而自动拆箱过程是通过调用包装类的 xxxValue()方法实现的(xxx 代表对应的基本数据类型,如 intValue()、doubleValue()等)。
- 自动装箱
Integer i = 100;//自动装箱 //相当于编译器自动为您作以下的语法编译: Integer i = Integer.valueOf(100);//调用的是 valueOf(100),而不是 new Integer(100)
- 自动拆箱
Integer i = 100; int j = i;//自动拆箱 //相当于编译器自动为您作以下的语法编译: int j = i.intValue();
- 包装类空指针异常
public class Test1 { public static void main(String[ ] args) { Integer i = null; int j = i; } } public class Test1 { public static void main(String[ ] args) { /上面的代码在编译时期是合法的,但是在运行时期会有错误 因为其相当于下面两行代码*/ Integer i = null; int j = i.intValue(); } } null 表示 i 没有指向任何对象的实体,但作为对象名称是合法的(不管这个对象名称存 是否指向了某个对象 的实体)。由于实际上 i 并没有指向任何对象的实体,所以也就不可能 操作 intValue()方法,这样上面的 写法在运行时就会出现 NullPointerException 错误。
- 自动装箱
- 包装类的缓存问题
整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存处理,其目的是提高效率。缓存处理的原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。每当自动装箱过程发生时(或者手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象
- Integer 类相关源码
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } 这段代码中我们需要解释下面几个问题: 1. IntegerCache类为Integer类的一个静态内部类,仅供Integer类使用。 2. 一般情况下 IntegerCache.low为-128,IntegerCache.high为127, IntegerCache.cache为内部类的一个静态属性
- IntegerCache 类相关源码
由下面的源码我们可以看到,静态代码块的目的就是初始化数组cache的,这个过程会在类加载时完成。
private static class IntegerCache { 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; } private IntegerCache() {} }
- 包装类的缓存测试
public class Test3 { public static void main(String[ ] args) { Integer in1 = -128; Integer in2 = -128; System.out.println(in1 == in2);//true 因为 123 在缓存范围内 System.out.println(in1.equals(in2));//true Integer in3 = 1234; Integer in4 = 1234; System.out.println(in3 == in4);//false 因为 1234 不在缓存范围内 System.out.println(in3.equals(in4));//true } }
- Integer 类相关源码
- 自定义一个简单的包装类
public class MyInteger { private int value; private static MyInteger[] cache; public static final int MIN=-128; public static final int MAX=127; static{ for(int i=-MIN;i<=MAX;i++){ cache[i+128]=MyInteger.valueOf(i); } } public static MyInteger valueOf(int i){ if(i>-MIN&&i<=MAX){ return cache[i+128]; } return new MyInteger(i); } public MyInteger(int i) { this.value=i; } }
- 注意
JDK1.5 以后,增加了自动装箱与拆箱功能,如: Integer i = 100; int j = new Integer(100);自动装箱调用的是 valueOf()方法,而不是 new Integer()方法。自动拆箱调用的 xxxValue()方法。包装类在自动装箱时为了提高效率,对于-128~127 之间的值会进行缓存处理。超过范围后,对象之间不能再使用==进行数值的比较,而是使用 equals 方法。
空指针异常就是对象为空,你调用了它的方法
字符串相关类
String 类、StringBuilder 类、StringBuffer 类是三个字符串相关类。String 类是的对象代表不可变的字符序列,StringBuilder 类和 StringBuffer 类代表可变字符序列。
1.String 类源码分析
String 类对象代表不可变的 Unicode 字符序列,因此我们可以将 String 对象称为“不可变对象”。 那什么叫做“不可变对象”呢?指的是对象内部的成员变量的值无法再改变。给过一次值,就不会再被赋值了。
我们发现字符串内容全部存储到 value[ ]数组中,而变量 value 是 final 类型的,也就是常量(即只能被赋值一次)。 这就是“不可变对象”的典型定义方式。
我们发现在前面学习 String 的某些方法,比如:substring()是对字符串的截取操作,但本质是读取原字符串内容生成了新的字符串。
- 字符串常量拼接时的优化
在遇到字符串常量之间的拼接时,编译器会做出优化,即在编译期间就会完成字符串的拼接。因此,在使用==进行 String 对象之间的比较时,我们需要特别注意
public class TestString2 { public static void main(String[ ] args) { //编译器做了优化,直接在编译的时候将字符串进行拼接 String str1 = "hello" + " java";//相当于 str1 = "hello java"; String str2 = "hellojava"; System.out.println(str1 == str2);//true String str3 = "hello"; String str4 = " java"; //编译的时候不知道变量中存储的是什么,所 以没办法在编译的时候优化 String str5 = str3 + str4; System.out.println(str2 == str5);//false } }
2.StringBuffer 和 StringBuilder
StringBuffer 和 StringBuilder 非常类似,均代表可变的字符序列。 这两个类都是抽象类 AbstractStringBuilder 的子类,方法几乎一模一样。我们打开 AbstractStringBuilder的源码
- AbstractStringBuilder 部分源码
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;//没有final修饰
}
显然,内部也是一个字符数组,但这个字符数组没有用 final 修饰,随时可以修改。因此,StringBuilder 和 StringBuffer 称之为“可变字符序列”。
- StringBuffer 和 StringBuilder的区别:
StringBuffer JDK1.0 版本提供的类,线程安全,做线程同步检查, 效率较低。StringBuilder JDK1.5 版本提供的类, 线程不安全,不做线程同步检查,因此效率 较高。 建议采用该类。
-
常用方法列表:重载的 public StringBuilder append (…)方法可以为该 StringBuilder 对象添加字符序列, 仍然返回自身对象。方法 public StringBuilder delete (int start,int end)可以删除从 start 开始到 end-1 为止的一段字符序列, 仍然返回自身对象。方法 public StringBuilder deleteCharAt (int index)移除此序列指定位置上的 char, 仍然返回自身对象。重载的 public StringBuilder insert (…)方法可以为该 StringBuilder 对象在指定位置插入字符序列, 仍然返回自身对象。方法 public StringBuilder reverse ()用于将字符序列逆序, 仍然返回自身对象。方法 public String toString () 返回此序列中数据的字符串表示形式。和 String 类含义类似的方法:public int indexOf (String str)public int indexOf (String str,int fromIndex)public String substring (int start)public String substring (int start,int end)public int length ()char charAt (int index)
3.不可变和可变字符序列使用陷阱
- String 使用的陷阱
String 一经初始化后,就不会再改变其内容了。对 String 字符串的操作实际上是对其副本(原始拷贝)的操作,原来的字符串一点都没有改变。比如:String s ="a"; 创建了一个字符串 s = s+"b"; 实际上原来的"a"字符串对象已经丢弃了,现在又产生了另一个字符串 s+"b"(也就是"ab")。 如果多次执行这些改变串内容的操作,会导致大量副本字符串对象 存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的时间和空间性能, 甚至会造成服务器的崩溃。相反,StringBuilder 和 StringBuffer 类是对原字符串本身操作的,可以对字符串进行修改而不产生副本拷贝或者产生少量的副本。因此可以在循环中使用。
-
String 和 StringBuilder 在字符串频繁修改时的效率 测试
public class TestStringEtc { public static void main(String[] args) { String s1=""; long memory1=Runtime.getRuntime().freeMemory();//获取系统剩余内存空间 long time1=System.currentTimeMillis();//获取系统时间 for(int i=1;i<=5000;i++){ s1=s1+i; } long memory2=Runtime.getRuntime().freeMemory(); long time2=System.currentTimeMillis(); System.out.println("String占用内存:"+(memory1-memory2)); System.out.println("String占用时间:"+(time2-time1)); StringBuffer s2=new StringBuffer(""); long memory3=Runtime.getRuntime().freeMemory(); long time3=System.currentTimeMillis(); for(int i=1;i<=5000;i++){ s2=s2.append(i); } long memory4=Runtime.getRuntime().freeMemory(); long time4=System.currentTimeMillis(); System.out.println("StringBuffer占用内存:"+(memory3-memory4)); System.out.println("StringBuffer占用时间:"+(time4-time3)); } }
-
要点String:不可变字符序列。StringBuffer:可变字符序列,并且线程安全,但是效率低。StringBuilder:可变字符序列,线程不安全,但是效率高(一般用它)
时间处理相关类
在计算机世界,我们把 1970 年 1 月 1 日 00:00:00 定为基准时间,每个度量单位是毫秒(1 秒的千分之一)
如果想获得现在时刻的“时刻数值”,可以使用:
long
now = System.
currentTimeMillis
();代表当前时刻的毫秒数
这个“时刻数值”是所有时间类的核心值,年月日都是根据这个“数值”计算出来的。
-
Date 时间类(java.util.Date)
long times=System.currentTimeMillis();//当前时刻距离1970年的毫秒数 System.out.println(times); Date date=new Date(); System.out.println(date.getTime());//当前时刻距离1970年的毫秒数 Date d2 = new Date(1000L * 3600 * 24 * 365 * 150); //距离 1970 年 150 年 System.out.println(d2); //Date类构造方法 public Date() { this(System.currentTimeMillis()); }
-
DateFormat 类和 SimpleDateFormat 类
-
DateFormat 类的作用把时间对象转化成指定格式的字符串。 反之,把指定格式的字符串转化成时间对象。DateFormat 是一个抽象类,一般使用它的的子类 SimpleDateFormat 类来实现。
-
DateFormat 类和 SimpleDateFormat 类的使用
public class TimeTest { public static void main(String[] args) throws Exception{ Date time=new Date();//系统当前时间 System.out.println(time); System.out.println(time.getTime()); SimpleDateFormat stime1=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); SimpleDateFormat stime2=new SimpleDateFormat("yyyy-MM-dd"); //将Date对象转成字符串 System.out.println(stime1.format(new Date())); System.out.println(stime2.format(new Date())); //将字符串转成Date对象 String times="2020-06-18"; Date date=stime2.parse(times); System.out.println(date); } }
- 获取今天是本年度第几天
public static void main(String[ ] args) { SimpleDateFormat s1 = new SimpleDateFormat("D"); String daytime = s1.format(new Date()); System.out.println(daytime); }
-
-
Calendar 日历类
-
要点Calendar 类是一个抽象类,为我们提供了关于日期计算的相关功能,比如:年、月、日、时、分、秒的展示和计算。GregorianCalendar 是 Calendar 的一个具体子类,提供了世界上大多数国家/地区使用的标准日历系统。注意月份的表示,一月是 0,二月是 1,以此类推,12 月是 11。 因为大多数人习惯于使用单词而不是使用数字来表示月份,这样程序也许更易读,父类 Calendar 使用常量来表示月份:JANUARY、FEBRUARY 等等。
-
GregorianCalendar 类和 Calendar 类的使用
public class TestCalendar { public static void main(String[] args) { GregorianCalendar gregorianCalendar=new GregorianCalendar(); int year=gregorianCalendar.get(Calendar.YEAR); int mouth=gregorianCalendar.get(Calendar.MONTH); int day=gregorianCalendar.get(Calendar.DAY_OF_MONTH); int day2=gregorianCalendar.get(Calendar.DATE); int week=gregorianCalendar.get(Calendar.DAY_OF_WEEK); //这里是1-7周日是1 System.out.println(year);//2020 System.out.println(mouth);//打印6,实际是7月,从0开始的 System.out.println(day);//28 System.out.println(day2);//28 System.out.println(week);//打印周三,实际是周二 GregorianCalendar gregorianCalendar1=new GregorianCalendar(); gregorianCalendar1.set(Calendar.YEAR,1996);//指定1996年 gregorianCalendar1.set(Calendar.MONTH,8);//指定8月 gregorianCalendar1.set(Calendar.DATE,9);//指定9号 printCalendar(gregorianCalendar1); GregorianCalendar gregorianCalendar2=new GregorianCalendar(2020,6,19); gregorianCalendar2.add(Calendar.YEAR,1);//给2020+1 gregorianCalendar2.add(Calendar.MONTH,1);//给6月+1 printCalendar(gregorianCalendar2);//2021 07 19 //日历对象和时间对象转换 Date d=gregorianCalendar2.getTime(); GregorianCalendar calendar3=new GregorianCalendar(); calendar.setTime(new Date()) } static void printCalendar(GregorianCalendar gregorianCalendar){ int year=gregorianCalendar.get(Calendar.YEAR); int mouth=gregorianCalendar.get(Calendar.MONTH); int day=gregorianCalendar.get(Calendar.DAY_OF_MONTH); System.out.println(year); System.out.println(mouth); System.out.println(day); } }
-