Java 学习笔记:第八章 常用类

8.1.1 包装类基本知识

Java 是面向对象的语言,把并不是 “纯面向对象” 的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化为对象,以便于操作。比如:将基本数据类型存储到 Objectp[] 数组或集合中的操作等等。

为了解决这个不足,Java 在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)

包装类均位于java.lang 包,八种包装类和基本数据类型的对应关系:

在这里插入图片描述
在这八个类名中,除了 Integer 和 Character 类以外,其它六个类和基本数据类型一致,只是类名的第一个字母大写而已。

在这八个类中,除了 character 和 Boolean 以外,其它的都是 “数字型”,“数字型”都是java.lang.Number的子类。Number类是抽象类,因此它的抽象方法,所有子类都需要提供实现。

Number类提供了抽象方法:initValue()、longValue()、floatValue()、doubleValue(),意味着所有的“数字型”包装类都可以互相转型。

在这里插入图片描述
在这里插入图片描述

下面我们通过一个简单的实例认识一下包装类。

【示例8-1】 初识包装类

public class WrapperClassTest {
    public static void main(String[] args) {
        Integer i = new Integer(10);
        Integer j = new Integer(50);
    }
}

在这里插入图片描述

8.1.2 包装类的用途

对于包装类来说,这些类的用途主要包含两种:

  1. 作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如 Object[]、集合等的操作。
  2. 包含每种基本数据类类型的相关属性如最大值、最小值等,以及相关的操作方法(这些操作方法的作用是在基本数据类型、包装类对象、字符串之间提供相互之间的转化!)。

【示例8-2】包装类的使用

public class Test {
    /** 测试Integer的用法,其他包装类与Integer类似 */
    void testInteger() {
        // 基本类型转化成Integer对象
        Integer int1 = new Integer(10);
        Integer int2 = Integer.valueOf(20); // 官方推荐这种写法
        // Integer对象转化成int
        int a = int1.intValue();
        // 字符串转化成Integer对象
        Integer int3 = Integer.parseInt("334");
        Integer int4 = new Integer("999");
        // Integer对象转化成字符串
        String str1 = int3.toString();
        // 一些常见int类型相关的常量
        System.out.println("int能表示的最大整数:" + Integer.MAX_VALUE); 
    }
    public static void main(String[] args) {
        Test test  = new Test();
        test.testInteger();
    }
}

执行结果:

在这里插入图片描述

8.1.3 自动装箱和拆箱

自动装箱和拆箱就是将基本数据类型和包装类之间进行自动的互相转换。JDK1.5后,Java 引入了自动装箱(autoboxing)/ 拆箱(unboxing).

自动装箱

节本类型的数据处于需要对象的环境中时,会自动转为 “对象”。

我们以 Integer 为例:在JDK1.5 以前,这样的代码 Integer i = 5 是错误的,必须要通过 Integer i = new Integer(5) 这样的语句来实现基本数据类型转换成包装类的过程;而在 JDK 1.5 以后,Java 提供了自动装箱的功能,因此只需 Integer i = 5 这样的语句就能实现基本数据类型转换成包装类,这是因为 JVM 为我们执行了 Integer i = Integer.valueOf(5) 这样的操作,这就是 Java 的自动装箱。

自动拆箱

每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用 intValue()、doubleValue()等转型方法。

如 Integer i = 5;int j = i; 这样的过程就是自动拆箱。

我们可以用一句话总结自动装箱、拆箱:

走动装箱过程是通过调用包装类的valueOf()方法实现的,而自动拆箱过程是通过调用包装类的 xxxValue()方法实现的 (xxx代表对应的基本数据类型 ,如initValue()、doubleValue()等)。

自动装箱和拆箱的功能事实上是编译器来帮的忙,编译器在编译时依据您所编写的语法,决定是否进行装箱或拆箱动作,如示例8-3 与 示例 8-4 所示。

【示例8-3】 自动装箱

Integer i = 100;//自动装箱
//相当于编译器自动为您作以下的语法编译:
Integer i = Integer.valueOf(100);//调用的是valueOf(100),而不是new Integer(100)

【示例8-4】自动拆箱

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

所以自动装箱与拆箱的功能是所谓的 “编译器蜜糖(Compiler Suger)”,虽然使用这个功能很方便,但在程序运行阶段您得了解Java 的语义。

【示例8-5】包装类空指针异常问题

public class Test1 {
    public static void main(String[] args) {
        Integer i = null;
        int j = i;
    }
}

在这里插入图片描述
示例8-5 的运行结果之所以会出现空指针异常,是因为示例 8-5 中的代码相当于:

public class Test1 {
    public static void main(String[] args) {
        //示例8-5的代码在编译时期是合法的,但是在运行时期会有错误,因为其相当于:
        Integer i = null; 
        int j = i.intValue();         
    }
}

null 表示 i 没有指向任何对象的实体,但作为对象名称是合法的(不管这个对象名称是否指向了某个对象的实体)。由于实际上并没有指向任何对象的实体,所以也就不可能操作 intValue()方法,这样上面的写法在运行时就会出现 NullPointException 错误。

【示例8-6】 自动装箱与拆箱

public class Test2 {
    /**
     * 测试自动装箱和拆箱 结论:虽然很方便,但是如果不熟悉特殊情况,可能会出错!
     */
    public static void main(String[] args) {
        Integer b = 23; // 自动装箱
        int a = new Integer(20); //自动拆箱
        // 下面的问题我们需要注意:
        Integer c = null;
        int d = c; // 此处其实就是:c.intValue(),因此抛空指针异常。
    }
}

8.1.4 包装类的缓存问题

整型、char 类型 所对应的包装类,在自动装箱时,对于-128~127 之间的值会自动进行缓存处理,其目的是提高效率。

缓存处理的原理为:如果数据在-128~127这个区间,那么在类加载是就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为 cache 的数组中,每当自动装箱过程发生时(或者手动调用 valueOf()时)。就会先判断数据是否在该区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过 new 嗲用包装类的构造方法来创建对象。

下面我们以 Integer 来为例,看一看 Java 为我们提供的源码,加深对缓存技术的理解:

【示例8-7】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 为内部类的一个静态属性:

【示例8-8】IntegerCache 类相关源码:

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() {}
}

由上面的源码我们可以看到,静态代码块的目的就是初始化数组cache 的,这个过程会在类加载时完成。

下面我们做一下代码测试:

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
    }
}

执行结果

在这里插入图片描述

示例8-9 的内存分析:

在这里插入图片描述
注意

  1. JDK1.5 以后,增加了自动装箱与拆箱功能,如:
Integer i = 100;  int j = new Integer(100);
  1. 自动装箱调用的是valueOf(),而不是new Integer() 方法。
  2. 自动拆箱用的是 xxxValue()方法。
  3. 包装类在自动装箱时为了提高效率,对于-128~127之间的值会进行缓存处理。超过范围后,对象之间不能再使用 == 进行数值的比较,而是使用 equals 方法。

8.2.1 String 类

String 类对象代表不可变的 Unicode 字符序列,因此我们可以将String对象 称为 “不可变对象”。那什么叫做“不可变对象”呢?指的是对象内部的成员变量的值无法再改变。我们打开String 类的源码:

在这里插入图片描述

我们发下字符串内容全部存储到 value[] 数组中,而变量 value 是final 类型的,也就是常量(即只能被赋值一次)。这就是“不可变对象”的典型地很定义方式。

我们发现在前面学习String 的某些方法,比如:substring()是对字符串的截取操作,但本质是读取原字符串内容生成了新的字符串。测试代码:

【示例8-10】String 测试代码

public class TestString1 {
    public static void main(String[] args) {
        String s1 = new String("abcdef");
        String s2 = s1.substring(2, 4);
        // 打印:ab199863
        System.out.println(Integer.toHexString(s1.hashCode()));
        // 打印:c61, 显然s1和s2不是同一个对象
        System.out.println(Integer.toHexString(s2.hashCode()));
    }
}

执行结果:
在这里插入图片描述
在遇到字符串常量之间的拼接时,编译器会做出优化,即在编译期间就会完成字符串的拼接。因此,在使用 == 进行 String 对象之间的比较时,我们需要特别注意。

【示例8-11】字符串常量拼接时的优化

public class TestString2 {
    public static void main(String[] args) {
        //编译器做了优化,直接在编译的时候将字符串进行拼接
        String str1 = "hello" + " java";//相当于str1 = "hello java";
        String str2 = "hello java";
        System.out.println(str1 == str2);//true
        String str3 = "hello";
        String str4 = " java";
        //编译的时候不知道变量中存储的是什么,所以没办法在编译的时候优化
        String str5 = str3 + str4;
        System.out.println(str2 == str5);//false
    }
}

执行结果

在这里插入图片描述
String 类常用的方法有(可翻看第五章5.11.4):

  1. String 类的下述方法能创建并返回一个新的 String 对象:concat()、replace()、subString()、toLowerCase()、toUpperCase()、trim() 等 。
  2. 提供查找功能的有关方法: endsWith()、startsWith()、indexOf()、lastIndexOf()。
  3. 提供比较功能的方法:equals()、equalsIgonoreCase()、compareTo()。
  4. 其他方法:chatAt()、length()。

8.2.2 StringBuffer 和 StringBuilder

StringBuffer 和 StringBuilder 非常类似,均代表可变的字符序列。这两个类都是抽象类 AbstractStringBuilder 的子类,方法几乎一模一样。我们打开 AbstractStringBuilder 的源码:

【示例8-11】 AbstractStringBuilder 部分源码

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char value[];
//以下代码省略
}

显然,内部也是一个字符数组,但这个祖父数组没有用 final 修饰,随时可以修改,因此,StringBuilder 和 StringBuffer 称之为“可变字符序列”。那两者有什么区别呢?

  1. StringBuffer JDK 1.0 版本提供的类,线程安全,做线程同步检查,效率较低。
  2. StringBuilder JDK 1.5 版本提供的类,线程不安全,不做线程同步检查,因此效率较高。建议采用该类。

常用方法列表

  1. 重载的 public StringBuilder append(…)方法
    可以为该 StringBuilder 对象添加字符序列,仍然返回自身对象。

  2. 方法 public StringBuilder delete(int start,int end)
    可以删除从 start 开始到 end -1为止的一段字符序列,仍然返回自身对象。

  3. 方法 public StringBuilder deleteCharAt(int index)
    移除此序列指定位置上的char,仍然返回自身对象。

  4. 重载的public StringBuilder insert(…)方法
    可以为该 StringBuilder 对象在指定位置插入字符序列,仍然返回自身对象。

  5. 方法 public StringBuilder reverse()
    用于将字符序列逆序,仍然返回自身对象。

  6. 方法 public String toString()返回此序列中数据的字符串表示形式。

  7. 和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)

【示例8-12】 StringBuffer / StringBuilder 基本用法

public class TestStringBufferAndBuilder 1{
    public static void main(String[] args) {
        /**StringBuilder*/
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 7; i++) {
            sb.append((char) ('a' + i));//追加单个字符
        }
        System.out.println(sb.toString());//转换成String输出
        sb.append(", I can sing my abc!");//追加字符串
        System.out.println(sb.toString());
        /**StringBuffer*/
        StringBuffer sb2 = new StringBuffer("中华人民共和国");
        sb2.insert(0, "爱").insert(0, "我");//插入字符串
        System.out.println(sb2);
        sb2.delete(0, 2);//删除子字符串
        System.out.println(sb2);
        sb2.deleteCharAt(0).deleteCharAt(0);//删除某个字符
        System.out.println(sb2.charAt(0));//获取某个字符
        System.out.println(sb2.reverse());//字符串逆序
    }
}

在这里插入图片描述

8.2.3 不可变和可变字符序列使用陷阱

String使用的陷阱

String 一经初始化后,就不会再改变其内容了,对String 字符串的操作实际上是对其副本(原始拷贝)的操作,原来的字符串一点都没有改变。

String s = “a” 创建了一个字符串

s = s+“b”,实际上原来的 “a” 字符串对象已经丢弃了了,现在又产生了另一个字符串 s+“b”(也就是“ab”)。如果对此执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的时间和空间性能,甚至会造成服务器的崩溃。

相反,StringBuilder 和 StringBuffer 类是对原字符串本身操作的,可以对字符串进行修改而不产生副本拷贝或者产生少量的副本,因此可以在循环中使用。

【示例 8-13】String 和StringBuilder 在频繁字符串修改时效率测试

public class Test {
    public static void main(String[] args) {
        /**使用String进行字符串的拼接*/
        String str8 = "";
        //本质上使用StringBuilder拼接, 但是每次循环都会生成一个StringBuilder对象
        long num1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
        long time1 = System.currentTimeMillis();//获取系统的当前时间
        for (int i = 0; i < 5000; i++) {
            str8 = str8 + i;//相当于产生了10000个对象
        }
        long num2 = Runtime.getRuntime().freeMemory();
        long time2 = System.currentTimeMillis();
        System.out.println("String占用内存 : " + (num1 - num2));
        System.out.println("String占用时间 : " + (time2 - time1));
        /**使用StringBuilder进行字符串的拼接*/
        StringBuilder sb1 = new StringBuilder("");
        long num3 = Runtime.getRuntime().freeMemory();
        long time3 = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            sb1.append(i);
        }
        long num4 = Runtime.getRuntime().freeMemory();
        long time4 = System.currentTimeMillis();
        System.out.println("StringBuilder占用内存 : " + (num3 - num4));
        System.out.println("StringBuilder占用时间 : " + (time4 - time3));
    }
}

执行结果:

在这里插入图片描述

要点

  1. String:不可变字符序列
  2. StringBuffer:可变字符序列,并且线程安全,但是效率低。
  3. StringBuilder:可变字符序列,线程不安全,但是效率高(一般用它)

8.3 时间处理相关类

“时间如流水,一去不复返”,时间是一个一维的东东。所以,我们需要一把刻度尺来表达和度量时间。在计算机世界,我们把1970年1月1日 00:00:00 定为基准时间,每个度量单位是毫秒(1秒的千分之一):

在这里插入图片描述

我们用 long 类型的变量来表示时间,从基准时间往前几亿年,往后几亿年都能表示。如果想获得现在时刻的“时刻数值”,可以使用:

long now = System.currentTimeMillis();

这个“时刻数值”,是所有时间类的核心值,年月日都是根据这个 “数值” 计算出来的。我们工作学习设计的时间相关类有如下这些:

在这里插入图片描述

8.3.1 Date 时间类(java.util.Date)

在标准Java类库中包含一个 Date 类。它的随想表示一个特定的瞬间,精确到毫秒。

  1. Date()分配一个Date对象,并初始化此对象为系统当前的日期和时间,可以精确到毫秒。
  2. Date(long date)分配Date 对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日 00:00:00 GMT)以来的指定毫秒数。
  3. boolean after(Date when)测试此日期是否在指定日期之后。
  4. boolean before (Date when)测试此日期是否在指定日期之前。
  5. boolean equals(Object obj)比较两个日期的相等性。
  6. long getTime()返回自1970 年 1月1日 00:00:00 GMT 以来此Date对象表示的毫秒数。
  7. String toString()把此Date 对象转化为 以下形式的 String:
    dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun、 Mon、Tue、Wed、 Thu、 Fri、 Sat)。

【示例8-14】Date 类的使用

import java.util.Date;
public class TestDate {
    public static void main(String[] args) {
        Date date1 = new Date();
        System.out.println(date1.toString());
        long i = date1.getTime();
        Date date2 = new Date(i - 1000);
        Date date3 = new Date(i + 1000);
        System.out.println(date1.after(date2));
        System.out.println(date1.before(date2));
        System.out.println(date1.equals(date2));
        System.out.println(date1.after(date3));
        System.out.println(date1.before(date3));
        System.out.println(date1.equals(date3));
        System.out.println(new Date(1000L * 60 * 60 * 24 * 365 * 39L).toString());
    }
}

在这里插入图片描述

查看 API 文档大家可以看到 其实 Date 类中的很多方法都已经过时了。JDK1.1 之前的 Date 包含了:日期操作、字符串化成时间对象等操作。JDK 1.1 之后,日期操作一般使用 Calendar 类,而字符串的转化使用 DateFormat 类。

8.3.2 DateFormat 类 和 SimpleDateFormat 类

DateFormat 类的作用

把时间对象转化成指定格式的字符串。反之,把制定格式的字符串转化为时间对象。

DateTime 是一个抽象类,一般使用它的子类 SimpleDateFormat 类来实现。

【示例8-15】 DateFormat 类和 SimpleDateFormat 类的使用

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDateFormat {
    public static void main(String[] args) throws ParseException {
        // new出SimpleDateFormat对象
        SimpleDateFormat s1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        SimpleDateFormat s2 = new SimpleDateFormat("yyyy-MM-dd");
        // 将时间对象转换成字符串
        String daytime = s1.format(new Date());
        System.out.println(daytime);
        System.out.println(s2.format(new Date()));
        System.out.println(new SimpleDateFormat("hh:mm:ss").format(new Date()));
        // 将符合指定格式的字符串转成成时间对象.字符串格式需要和指定格式一致。
        String time = "2007-10-7";
        Date date = s2.parse(time);
        System.out.println("date1: " + date);
        time = "2007-10-7 20:15:30";
        date = s1.parse(time);
        System.out.println("date2: " + date);
    }
}

执行结果

在这里插入图片描述

代码中的格式化字符的具体含义:
在这里插入图片描述

时间格式字符也可以为我们提供其他的便利,比如:获得当前时间今年的第几天。

【示例8-16】时间格式字符的使用

import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDateFormat2 {
    public static void main(String[] args) {
        SimpleDateFormat s1 = new SimpleDateFormat("D");
        String daytime = s1.format(new Date());
        System.out.println(daytime);
    }
}

执行结果

在这里插入图片描述

8.3.3 Calendar 日历类

Calendar 类是一个抽象类。为我们提供了关于日期计算的相关功能,比如:年、月、日、时、分、秒的展示和计算。

GregorianCalendar 是 Calendar 的一个具体子类,提供了世界上大多数国家和地区的标准日历系统。

菜鸟雷区

注意月份的表示,一月是0,二月是1,以此类推,12月是11.因为大多数人习惯于使用单词而不是使用数字来表示月份,这样程序也许更易读,父类 Calendar 使用常量来表示月份:JANUARY、FEBRUARY 等等。

【示例8-17】 GregorianCalendar类和 Calendar类的使用

import java.util.*;
public class TestCalendar {
    public static void main(String[] args) {
        // 得到相关日期元素
        GregorianCalendar calendar = new GregorianCalendar(2999, 10, 9, 22, 10, 50);
        int year = calendar.get(Calendar.YEAR); // 打印:2999
        int month = calendar.get(Calendar.MONTH); // 打印:10
        int day = calendar.get(Calendar.DAY_OF_MONTH); // 打印:9
        int day2 = calendar.get(Calendar.DATE); // 打印:9
        // 日:Calendar.DATE和Calendar.DAY_OF_MONTH同义
        int date = calendar.get(Calendar.DAY_OF_WEEK); // 打印:3
        // 星期几 这里是:1-7.周日是1,周一是2,。。。周六是7
        System.out.println(year);
        System.out.println(month);
        System.out.println(day);
        System.out.println(day2);
        System.out.println(date);
        // 设置日期
        GregorianCalendar calendar2 = new GregorianCalendar();
        calendar2.set(Calendar.YEAR, 2999);
        calendar2.set(Calendar.MONTH, Calendar.FEBRUARY); // 月份数:0-11
        calendar2.set(Calendar.DATE, 3);
        calendar2.set(Calendar.HOUR_OF_DAY, 10);
        calendar2.set(Calendar.MINUTE, 20);
        calendar2.set(Calendar.SECOND, 23);
        printCalendar(calendar2);
        // 日期计算
        GregorianCalendar calendar3 = new GregorianCalendar(2999, 10, 9, 22, 10, 50);
        calendar3.add(Calendar.MONTH, -7); // 月份减7
        calendar3.add(Calendar.DATE, 7); // 增加7天
        printCalendar(calendar3);
        // 日历对象和时间对象转化
        Date d = calendar3.getTime();
        GregorianCalendar calendar4 = new GregorianCalendar();
        calendar4.setTime(new Date());
        long g = System.currentTimeMillis();
    }
    static void printCalendar(Calendar calendar) {
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH) + 1;
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        int date = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期几
        String week = "" + ((date == 0) ? "日" : date);
        int hour = calendar.get(Calendar.HOUR);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);
        System.out.printf("%d年%d月%d日,星期%s %d:%d:%d\n", year, month, day,  
                        week, hour, minute, second);
    }
}

执行结果:

在这里插入图片描述

编写程序,利用 GregorianCalendar类,打印当前月份的日历,今天的日期是 2017-05-18:

在这里插入图片描述

【示例8-18】 可视化日历的编写

import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Scanner;
public class TestCalendar2 {
    public static void main(String[] args) throws ParseException {
        System.out.println("请输入日期(格式为:2010-3-3):");
        Scanner scanner = new Scanner(System.in);
        String dateString = scanner.nextLine(); // 2010-3-1
        // 将输入的字符串转化成日期类
        System.out.println("您刚刚输入的日期是:" + dateString);
        String[] str = dateString.split("-");
        int year = Integer.parseInt(str[0]);
        int month = new Integer(str[1]);
        int day = new Integer(str[2]);
        Calendar c = new GregorianCalendar(year, month - 1, day); // Month:0-11
        // 大家自己补充另一种方式:将字符串通过SImpleDateFormat转化成Date对象,
        //再将Date对象转化成日期类
        // SimpleDateFormat sdfDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        // Date date = sdfDateFormat.parse(dateString);
        // Calendar c = new GregorianCalendar();
        // c.setTime(date);
        // int day = c.get(Calendar.DATE);
        c.set(Calendar.DATE, 1);
        int dow = c.get(Calendar.DAY_OF_WEEK); // week:1-7 日一二三四五六
        System.out.println("日\t一\t二\t三\t四\t五\t六");
        for (int i = 0; i < dow - 1; i++) {
            System.out.print("\t");
        }
        int maxDate = c.getActualMaximum(Calendar.DATE);
        // System.out.println("maxDate:"+maxDate);
        for (int i = 1; i <= maxDate; i++) {
            StringBuilder sBuilder = new StringBuilder();
            if (c.get(Calendar.DATE) == day) {
                sBuilder.append(c.get(Calendar.DATE) + "*\t");
            } else {
                sBuilder.append(c.get(Calendar.DATE) + "\t");
            }
            System.out.print(sBuilder);
            // System.out.print(c.get(Calendar.DATE)+
            //                ((c.get(Calendar.DATE)==day)?"*":"")+"\t");
 
            if (c.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
                System.out.print("\n");
            }
            c.add(Calendar.DATE, 1);
        }
    }
}

8.4 Math 类

java.lang.Math 提供了一系列静态方法用于科学计算;其方法的参数和返回值类型一般为 double型。如果需要更加强大的数学运算能力,计算高等数学中的相关内容,可以使用 apache.commons 下面的 Math 类库。

Math 的常用方法

  1. abs 绝对值
  2. acos,asin,atan,cos,sin,tan 三角函数
  3. sqrt 平方根
  4. pow(double a,double b)a 的 b次幂
  5. max(double a,double b) 取大值
  6. min(double a,double b)取小值
  7. ceil(double a)大于a 的最小整数
  8. floor(double a)小于 a 的最大整数
  9. random()返回0.0到1.0的随机数
  10. long round(double a)double 型的数据 a 转换为 long 型(四舍五入)
  11. toDegrees(double angrad)弧度 -> 角度
  12. toRadians(double angdeg)角度->弧度

【示例8-19】Math类的常用方法

public class TestMath {
    public static void main(String[] args) {
        //取整相关操作
        System.out.println(Math.ceil(3.2));
        System.out.println(Math.floor(3.2));
        System.out.println(Math.round(3.2));
        System.out.println(Math.round(3.8));
        //绝对值、开方、a的b次幂等操作
        System.out.println(Math.abs(-45));
        System.out.println(Math.sqrt(64));
        System.out.println(Math.pow(5, 2));
        System.out.println(Math.pow(2, 5));
        //Math类中常用的常量
        System.out.println(Math.PI);
        System.out.println(Math.E);
        //随机数
        System.out.println(Math.random());// [0,1)
    }
}

执行结果:
在这里插入图片描述
Math类中虽然为我们提供了产生随机数的方法 Math.random(),但是通常我们需要的随机数范围并不是[0,1) 之间的double 类型的数据,这既需要对其进行一些复杂的运算。如果使用 Math.random()计算过于复杂的话,我们可以使用另外一种方式得到随机数,即 Random类,这个类是专门用来生成随机数的,并且 Math.random()底层调用的就是 Random 的 nextDouble()方法。

【示例8-20】 Random类的常用方法

import java.util.Random;
public class TestRandom {
    public static void main(String[] args) {
        Random rand = new Random();
        //随机生成[0,1)之间的double类型的数据
        System.out.println(rand.nextDouble());
        //随机生成int类型允许范围之内的整型数据
        System.out.println(rand.nextInt());
        //随机生成[0,1)之间的float类型的数据
        System.out.println(rand.nextFloat());
        //随机生成false或者true
        System.out.println(rand.nextBoolean());
        //随机生成[0,10)之间的int类型的数据
        System.out.print(rand.nextInt(10));
        //随机生成[20,30)之间的int类型的数据
        System.out.print(20 + rand.nextInt(10));
        //随机生成[20,30)之间的int类型的数据(此种方法计算较为复杂)
        System.out.print(20 + (int) (rand.nextDouble() * 10));
    }
}

执行结果:
在这里插入图片描述

注意

Random类位于java.util包下

8.5.1 File 类的基本用法

java.io.File类:代表文件和目录。在开发中,读取文件、生成文件、删除文件、修改文件的属性时经常用到本类。

File类的常见构造方法:public File(String pathname)

以pathname 为路径创建 File 对象,如果pathname 是相对路径,则默认的当前路径在系统属性 user.dir 中存储。

【示例8-21】文件的创建

import java.io.File;
public class TestFile1 {
    public static void main(String[] args) throws Exception {
        System.out.println(System.getProperty("user.dir"));
        File f = new File("a.txt"); //相对路径:默认放到user.dir目录下面
        f.createNewFile();//创建文件
        File f2 = new File("d:/b.txt");//绝对路径
        f2.createNewFile();
    }
}

在eclipse 项目开发中,user.dir 就是本项目的目录。因此,执行完毕后,在本项目和D盘下都生成了新的文件(如果是eclipse 下,一定按F5刷新目录才能看到文件)。

通过 File 对象可以访问文件的属性:

在这里插入图片描述
【示例8-22】 测试File类访问属性的基本用法

import java.io.File;
import java.util.Date;
public class TestFile2 {
    public static void main(String[] args) throws Exception {
        File f = new File("d:/b.txt");
        System.out.println("File是否存在:"+f.exists());
        System.out.println("File是否是目录:"+f.isDirectory());
        System.out.println("File是否是文件:"+f.isFile());
        System.out.println("File最后修改时间:"+new Date(f.lastModified()));
        System.out.println("File的大小:"+f.length());
        System.out.println("File的文件名:"+f.getName());
        System.out.println("File的目录路径:"+f.getPath());
    }
}

执行结果:
在这里插入图片描述

通过 File 对象创建空文件或目录(在该对象所指的文件或目录不存在的情况下)

在这里插入图片描述

【示例8-23】使用mkdir 创建目录

import java.io.File;
public class TestFile3 {
    public static void main(String[] args) throws Exception {
        File f = new File("d:/c.txt");
        f.createNewFile(); // 会在d盘下面生成c.txt文件
        f.delete(); // 将该文件或目录从硬盘上删除
        File f2 = new File("d:/电影/华语/大陆");
        boolean flag = f2.mkdir(); //目录结构中有一个不存在,则不会创建整个目录树
        System.out.println(flag);//创建失败
    }
}

执行结果:

在这里插入图片描述

【示例8-24】使用mkdirs 创建目录

import java.io.File;
public class TestFile4 {
    public static void main(String[] args) throws Exception {
        File f = new File("d:/c.txt");
        f.createNewFile(); // 会在d盘下面生成c.txt文件
        f.delete(); // 将该文件或目录从硬盘上删除
        File f2 = new File("d:/电影/华语/大陆");
        boolean flag = f2.mkdirs();//目录结构中有一个不存在也没关系;创建整个目录树
        System.out.println(flag);//创建成功
    }
}

执行结果:
在这里插入图片描述

【示例8-25】File 类的综合应用

import java.io.File;
import java.io.IOException;
public class TestFile5 {
    public static void main(String[] args) {
        //指定一个文件
        File file = new File("d:/sxt/b.txt");
        //判断该文件是否存在
        boolean flag= file.exists();
        //如果存在就删除,如果不存在就创建
        if(flag){
            //删除
            boolean flagd = file.delete();
            if(flagd){
                System.out.println("删除成功");
            }else{
                System.out.println("删除失败");
            }
        }else{
            //创建
            boolean flagn = true;
            try {
                //如果目录不存在,先创建目录
                File dir = file.getParentFile();
                dir.mkdirs();
                //创建文件
                flagn = file.createNewFile();
                System.out.println("创建成功");
            } catch (IOException e) {
                System.out.println("创建失败");
                e.printStackTrace();
            }          
        }
        //文件重命名(同学可以自己测试一下)
        //file.renameTo(new File("d:/readme.txt"));
    }
} 

在这里插入图片描述

8.5.2 递归遍历目录结构和树状展现

本节结合前面给大家讲的递归算法,展示目录结构。大家可以先建立一个目录,下面增加几个子文件夹或者文件,用于测试。

【示例8-26】使用递归算法,以树状结构展示目录树

import java.io.File;
public class TestFile6 {
    public static void main(String[] args) {
        File f = new File("d:/电影");
        printFile(f, 0);
    }
    /**
     * 打印文件信息
     * @param file 文件名称
     * @param level 层次数(实际就是:第几次递归调用)
     */
    static void printFile(File file, int level) {
        //输出层次数
        for (int i = 0; i < level; i++) {
            System.out.print("-");
        }
        //输出文件名
        System.out.println(file.getName());
        //如果file是目录,则获取子文件列表,并对每个子文件进行相同的操作
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File temp : files) {
                //递归调用该方法:注意等+1
                printFile(temp, level + 1);
            }
        }
    }
}

执行结果:

在这里插入图片描述

8.6 枚举

JDK 1.5 引入了枚举类型。枚举类型的定义包括枚举声明和枚举体。格式如下:

enum  枚举名 {
      枚举体(常量列表)
}

枚举体就是放置一些常量。我们可以写出我们的第一个枚举类型:
【示例8-27】创建枚举类型

enum Season {
    SPRING, SUMMER, AUTUMN, WINDER 
}

所有的枚举类型隐性地继承自 java.lang.Enum。枚举实质上还是类!而每个枚举的成员实质就是一个枚举类型的实例,他们默认都是 public static final 修饰的。可以直接通过枚举类型名使用它们。

老鸟建议

  1. 当你需要定义一组常量时,可以使用枚举类型。
  2. 尽量不要使用枚举的高级特性,事实上高级特性都可以使用普通类来实现,没有必要引入枚举,增加程序的复杂性!

【示例8-28】枚举的使用

import java.util.Random;
public class TestEnum {
    public static void main(String[] args) {
        // 枚举遍历
        for (Week k : Week.values()) {
            System.out.println(k);
        }
        // switch语句中使用枚举
        int a = new Random().nextInt(4); // 生成0,1,2,3的随机数
        switch (Season.values()[a]) {
        case SPRING:
            System.out.println("春天");
            break;
        case SUMMER:
            System.out.println("夏天");
            break;
        case AUTUMN:
            System.out.println("秋天");
            break;
        case WINDTER:
            System.out.println("冬天");
            break;
        }
    }
}
/**季节*/
enum Season {
    SPRING, SUMMER, AUTUMN, WINDTER
}
/**星期*/
enum Week {
    星期一, 星期二, 星期三, 星期四, 星期五, 星期六, 星期日
}

总结

  1. 每一个基本数据类型对应一个包装类

  2. 包装类的用途:
    作为和基本数据类型对应的引用类型存在,方便涉及到对象的操作。
    包含每种基本数据类型的相关属性如最大值、最小值以及相关的操作方法。

  3. JDK1.5 后再Java 中引入自动装箱和拆箱。

  4. 字符串相关类 String 、StringBuilder 和 StringBuffer
    String:不可变字符序列。
    StringBuffer:可变字符序列,并且线程安全,但是但是效率低。
    StringBuilder:可变字符序列,线程不安全,但是效率高(一般用它)。

日期与实践类 Date、DateFormat、SimpleDateFormat、Calendar、GregorianCalendar。

  1. Math 类的常用方法
    pow(double a,double b)
    max(double a,double b)
    min(double a,double b)
    random()
    long round(double a)

  2. 与操作文件相关的File类

  3. 当需要定义一组常量时,使用枚举类型。

猜你喜欢

转载自blog.csdn.net/weixin_44626569/article/details/89309330