java基础学习笔记(三)--包装类--字符串相关类--时间类

基本数据类型的包装类

  1. 包装类基本知识

    Java 是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。 比如:将基本数据类型存储到 Object[ ]数组或集合中的操作等等。
    为了解决这个不足,Java 在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
    在这八个类名中,除了 Integer 和 Character 类以外,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写而已。
    在这八个类中,除了 Character 和 Boolean 以外,其他的都是“数字型”,“数字型”都是 java.lang.Number 的子类。
    Number 类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number 类提供了抽象方法:intValue()、longValue()、floatValue()、doubleValue(), 意味着所有的“数字型”包装类都可以互相转型。
  2. 包装类用途
    对于包装类来说,这些类的用途主要包含两种:
    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();
    
        }
    }
    //包装类可以把基本数据类型,包装类,字符串之间相互转化
  3. 自动装箱和拆箱
    自动装箱过程是通过调用包装类的 valueOf()方法实现的,而自动拆箱过程是通过调用包装类的 xxxValue()方法实现的(xxx 代表对应的基本数据类型,如 intValue()、doubleValue()等)。
    1. 自动装箱
      Integer i = 100;//自动装箱 
      //相当于编译器自动为您作以下的语法编译: 
      Integer i = Integer.valueOf(100);//调用的是 valueOf(100),而不是 new Integer(100)

       

    2. 自动拆箱
      Integer i = 100; 
      int j = i;//自动拆箱 
      //相当于编译器自动为您作以下的语法编译: 
      int j = i.intValue();

       

    3. 包装类空指针异常
      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 错误。

       

  4. 包装类的缓存问题
    整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存处理,其目的是提高效率。
    缓存处理的原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。每当自动装箱过程发生时(或者手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象
    1. 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为内部类的一个静态属性
    2. 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() {}
          }
    3. 包装类的缓存测试
      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 
          } 
      }

      java基础学习笔记(三)--包装类--字符串相关类--时间类_第1张图片

  5. 自定义一个简单的包装类
    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;
        }
    }

     

  6. 注意
    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()是对字符串的截取操作,但本质是读取原字符串内容生成了新的字符串。
 
java基础学习笔记(三)--包装类--字符串相关类--时间类_第2张图片
 
  1. 字符串常量拼接时的优化
    在遇到字符串常量之间的拼接时,编译器会做出优化,即在编译期间就会完成字符串的拼接。因此,在使用==进行 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 ();代表当前时刻的毫秒数
这个“时刻数值”是所有时间类的核心值,年月日都是根据这个“数值”计算出来的。

java基础学习笔记(三)--包装类--字符串相关类--时间类_第3张图片

  1. 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());
        }
  2. DateFormat 类和 SimpleDateFormat 类
    1. DateFormat 类的作用
      把时间对象转化成指定格式的字符串。 反之,把指定格式的字符串转化成时间对象。DateFormat 是一个抽象类,一般使用它的的子类 SimpleDateFormat 类来实现。
    2. 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);
          }
      }
    3. 获取今天是本年度第几天
      public static void main(String[ ] args) { 
          SimpleDateFormat s1 = new SimpleDateFormat("D"); 
          String daytime = s1.format(new Date()); 
          System.out.println(daytime); 
      }
  3. Calendar 日历类
    1. 要点
      Calendar 类是一个抽象类,为我们提供了关于日期计算的相关功能,比如:年、月、日、时、分、秒的展示和计算。
      GregorianCalendar 是 Calendar 的一个具体子类,提供了世界上大多数国家/地区使用的标准日历系统。
      注意月份的表示,一月是 0,二月是 1,以此类推,12 月是 11。 因为大多数人习惯于使用单词而不是使用数字来表示月份,这样程序也许更易读,父类 Calendar 使用常量来表示月份:JANUARY、FEBRUARY 等等。
    2. 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);
          }
      }

 

你可能感兴趣的:(java超基础学习笔记)