包装类
包装类基本知识
Java是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。比如:将基本数据类型存储到Object[]数组或集合中的操作等等。
为了解决这个不足,Java在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
包装类均位于java.lang包,八种包装类和基本数据类型的对应关系如表8-1所示:
在这八个类名中,除了Integer和Character类以外,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写而已。
在这八个类中,除了Character和Boolean以外,其他的都是**“数字型**”,“数字型”都是java.lang.Number的子类。Number类是抽象类,因此它的抽象方法,所有子类都需要提供实现。
Number类提供了抽象方法:intValue()、longValue()、floatValue()、doubleValue(),意味着所有的“数字型”包装类都可以互相转型。
Number类的子类 Number类的抽象方法
Number是一个抽象类,里面的是抽象方法,需要子类去实现。
通过一个简单的示例认识一下包装类。
public class WrapperClassTest {
public static void main(String[] args) {
Integer i = new Integer(10);
Integer j = new Integer(50);
}
}
此时的Integer是一个对象,所以要通过new进行创建,并把创建好后的对象的地址(引用)传给i,j。
包装类的用途
对于包装类来说,这些类的用途主要包含两种:
- 作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如Object[]、集合等的操作。
- 包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法(这些操作方法的作用是在基本数据类型、包装类对象、字符串之间提供相互之间的转化!)。
包装类的使用
这些方法的使用者都是:包装类/包装类对象
public class Test {
/* 测试Integer的用法,其他包装类与Integer类似 */
void testInteger() {
// 基本类型转化成Integer对象
Integer int1 = new Integer(10);
Integer int2 = Integer.valueOf(20); //官方推荐这种写法,直接使用类的静态方法:valueOf()
// Integer对象转化成int或者double(因为数字型对象的父类number提供了几种类型之间的转化)
int a = int1.intValue();
double b = int1.doubleValue();
// 字符串转化成Integer对象
Integer int3 = Integer.parseInt("334");
//下面一行代码的源代码就是上面这一行
Integer int4 = new Integer("9909");
// 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();
}
}
结果:
int能表示的最大整数:2147483647
number类内部示例:字符串转成integer对象(如果字符串含有字母时会报错)
//jdk内部代码
//new Integer("9909"); 的实质就是 Integer.parseInt("9909");
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10); //parseInt的第二个参数是进制,不写默认为10进制。
}
自动装箱和拆箱
自动装箱和拆箱就是将基本数据类型和包装类之间进行自动的互相转换。JDK1.5后,Java引入了自动装箱(autoboxing)/拆箱(unboxing)。
自动装箱:
基本类型的数据处于需要对象的环境中时,会自动转为“对象”。
我们以Integer为例:在JDK1.5以前,这样的代码 Integer i = 5 是错误的,必须要通过Integer i = new Integer(5) 这样的语句来实现基本数据类型转换成包装类的过程;
而在JDK1.5以后,Java提供了自动装箱的功能,因此只需Integer i = 5这样的语句就能实现基本数据类型转换成包装类,这是因为JVM为我们执行了Integer i = Integer.valueOf(5)这样的操作,这就是Java的自动装箱。
自动拆箱:
每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用intValue()、doubleValue()等转型方法。
如 Integer i = 5;int j = i; 这样的过程就是自动拆箱。
我们可以用一句话总结自动装箱/拆箱:
自动装箱过程是通过调用包装类的valueOf()方法实现的,而不是new Integer()方法。
而自动拆箱过程是通过调用包装类的 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();
所以自动装箱与拆箱的功能是所谓的“编译器蜜糖(Compiler Sugar)”,虽然使用这个功能很方便,但在程序运行阶段您得了解Java的语义。
包装类空指针异常问题
public class Test1 {
public static void main(String[] args) {
Integer i = null;
int j = i;
}
}
之所以会出现空指针异常,是因为示例中的代码相当于:
public class Test1 {
public static void main(String[] args) {
//示例8-5的代码在编译时期是合法的,但是在运行时期会有错误,因为其相当于:
Integer i = null;
int j = i.intValue();
//因为i是空指针,仅仅是一个对象名,实际上没有指向任何实体,也就不可能操作intValue()方法,所以会有空指针异常
}
}
null表示i没有指向任何对象的实体,但作为对象名称是合法的(不管这个对象名称存是否指向了某个对象的实体)。由于实际上i并没有指向任何对象的实体,所以也就不可能操作intValue()方法,这样上面的写法在运行时就会出现NullPointerException错误。
自动装箱与拆箱
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(),因此抛空指针异常。
}
}
包装类的缓存问题
整型、char类型(Byte、Short、Integer、Long、Character)所对应的包装类,在自动装箱时,对于**-128~127**之间的值会进行缓存处理,其目的是提高效率。
记忆:[-128~-1]和[0~127]一共128*2=256个
缓存处理的原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。
每当自动装箱过程发生时**(或者手动调用valueOf()时)**,就会先判断数据是否在该区间,
如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象。
下面我们以Integer类为例,看一看Java为我们提供的源码,加深对缓存技术的理解,如示例8-7所示。
Integer类相关源码如下:
public static Integer valueOf(int i) {
//判断数据是否在cache的数组的区间中
if (i >= IntegerCache.low && i <= IntegerCache.high)
//在则直接获取数组中对应的包装类对象的引用
return IntegerCache.cache[i + (-IntegerCache.low)];
//不在该区间,则会通过new调用包装类的构造方法来创建对象
return new Integer(i);
}
这段代码中我们需要解释下面几个问题:
- IntegerCache类为Integer类的一个静态内部类,仅供Integer类使用。
- 一般情况下 IntegerCache.low为-128,IntegerCache.high为127,IntegerCache.cache为内部类的一个静态属性,如示例8-8所示。
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 因为128在缓存范围内,引用的是同一个地址
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 包装类对equals进行了重写,如果是同类型,同值 则返回true
}
}
注意
- JDK1.5以后,增加了自动装箱与拆箱功能,如:
Integer i = 100; int j = new Integer(100);
- 自动装箱调用的是valueOf()方法,而不是new Integer()方法。
- 自动拆箱调用的xxxValue()方法。
- 包装类在自动装箱时为了提高效率,对于-128~127之间的值会进行缓存处理。超过范围后,对象之间不能再使用==进行数值的比较,而是使用equals方法。
String类
string类基础
String 类对象代表不可变的Unicode字符序列,因此我们可以将String对象称为“不可变对象”。 那什么叫做“不可变对象”呢?指的是对象内部的成员变量的值无法再改变。我们打开String类的源码
我们发现字符串内容全部存储到value[]数组中,而变量value是final类型的,也就是常量(即只能被赋值一次)。 这就是“不可变对象”的典型定义方式。
我们发现在前面学习String的某些方法,比如:substring()是对字符串的截取操作,但本质是读取原字符串内容生成了新的字符串。测试代码如下:
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对象之间的比较时,我们需要特别注意
字符串常量拼接时的优化
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类常用的方法有
建议:一般都使用equals方法(只比较值,不比较是否为同一对象),不要用等号。
StringBuffer和StringBuilder
StringBuffer和StringBuilder非常类似,均代表可变的字符序列。 这两个类都是抽象类AbstractStringBuilder的子类,方法几乎一模一样。我们打开AbstractStringBuilder的源码
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char value[];
//以下代码省略
}
显然,内部也是一个字符数组,但这个字符数组没有用final修饰,随时可以修改。因此,StringBuilder和StringBuffer称之为“可变字符序列”。那两者有什么区别呢?
-
StringBuffer JDK1.0版本提供的类,线程安全,做线程同步检查, 效率较低。
-
StringBuilder JDK1.5版本提供的类,线程不安全,不做线程同步检查,因此效率较高。 建议采用该类。
-- 有buffer,安全;自己建造(Builder)不安全
常用方法列表:
StringBuffer和StringBuilder的方法大部分和string类似。
- 重载的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)
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));//追加单个字符
//追加单个字符,这里用到了强制类型转换,java的强制类型装换是使用括号包围类型名的方法对常量/变量进行转换
//小补充:字符 + 数字 = 数字;字符串 + 数字 = 字符串
}
System.out.println(sb.toString());//转换成String输出
//abcdefg
sb.append(", I can sing my abc!");//追加字符串
System.out.println(sb.toString());//abcdefg, I can sing my abc!
/**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());//字符串逆序 结果:国和共民人
}
}
不可变和可变字符序列使用陷阱
String使用的陷阱
String一经初始化后,就不会再改变其内容了。对String字符串的操作实际上是对其副本(原始拷贝)的操作,原来的字符串一点都没有改变。比如:
String s =“a”; 创建了一个字符串
s = s+“b”; 实际上原来的"a"字符串对象已经丢弃了,现在又产生了另一个字符串s+“b”(也就是"ab")。 如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的时间和空间性能,甚至会造成服务器的崩溃。
相反,StringBuilder和StringBuffer类是对原字符串本身操作的,可以对字符串进行修改而不产生副本拷贝或者产生少量的副本。因此可以在循环中使用。
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个对象:i也会变成一个对象,每次附加str8时也会产生一个新的str8对象,总共5000*2=10000个对象
}
long num2 = Runtime.getRuntime().freeMemory();
long time2 = System.currentTimeMillis();
System.out.println("String占用内存 : " + (num1 - num2)); //6110984
System.out.println("String占用时间 : " + (time2 - time1)); //84
/**使用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)); //0
System.out.println("StringBuilder占用时间 : " + (time4 - time3)); //1
}
}
结论:
用StringBuilder代替String创建字符串
StringBuilder sb1 = new StringBuilder("");
要点:
- String:不可变字符序列。
- StringBuffer:可变字符序列,并且线程安全,但是效率低。
- StringBuilder:可变字符序列,线程不安全,但是效率高(一般用它)。
时间处理相关类
“时间如流水,一去不复返”,时间是一个一维的东西。所以,我们需要一把刻度尺来表达和度量时间。在计算机世界,我们把1970 年 1 月 1 日 00:00:00定为基准时间,每个度量单位是毫秒(1秒的千分之一)。
我们用long类型的变量来表示时间,从基准时间往前几亿年,往后几亿年都能表示。如果想获得现在时刻的“时刻数值”,可以使用:
long now = System.currentTimeMillis();
这个“时刻数值”是所有时间类的核心值,年月日都是根据这个“数值”计算出来的。我们工作学习涉及的时间相关类有如下这些:
Date时间类(java.util.Date)
在标准Java类库中包含一个Date类。它的对象表示一个特定的瞬间,精确到毫秒。
--注意:date导入的是util的包,不是sql的包。
- Date() 分配一个Date对象,并初始化此对象为系统当前的日期和时间,可以精确到毫秒)。
- Date(long date) 分配 Date 对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。
- boolean after(Date when) 测试此日期是否在指定日期之后。
- boolean before(Date when) 测试此日期是否在指定日期之前。
- boolean equals(Object obj) 比较两个日期的相等性。
- long getTime() 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
- String toString() 把此 Date 对象转换为以下形式的 String:
dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun、 Mon、Tue、Wed、 Thu、 Fri、 Sat)。
Date类的使用
import java.util.Date;
public class TestDate {
public static void main(String[] args) {
Date date1 = new Date(); //获取当前时间
System.out.println(date1.toString());//toString()可以不加 Thu Jan 16 10:01:37 CST 2020
long i = date1.getTime(); //获取1970.1.1至今的毫秒数
Date date2 = new Date(i - 1000);
Date date3 = new Date(i + 1000);
System.out.println(date1.after(date2));//true
System.out.println(date1.before(date3));//true
System.out.println(date1.equals(date3));//false
System.out.println(new Date(1000L * 60 * 60 * 24 * 365 * 39L).toString());
//Mon Dec 22 08:00:00 CST 2008
}
}
查看API文档大家可以看到其实Date类中的很多方法都已经过时了。JDK1.1之前的Date包含了:日期操作、字符串转化成时间对象等操作。
JDK1.1之后,日期操作一般使用Calendar类,而字符串的转化使用DateFormat类。
--个人理解:
--Date类和Calendar类是存储时间相关数据(年,月,日等)的类,
--DateFormat是用来存储时间类型格式转换方法的类。
DateFormat类和SimpleDateFormat类
DateFormat类的作用
把时间对象转化成指定格式的字符串。反之,把指定格式的字符串转化成时间对象。
DateFormat是一个抽象类,一般使用它的的子类SimpleDateFormat类来实现。
记忆:
--format格式化为字符串(大象格式化,清空为蛇(串))
--parse转化为对象(蛇(串)转化,升级为大象)
--注意:调用方法的是与格式有关的DateFormat类的实例
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);
}
}
代码中的格式化字符的具体含义:
时间格式字符也可以为我们提供其他的便利。比如:获得当前时间是今年的第几天。
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);
}
}
Calendar日历类
Calendar 类是一个抽象类,为我们提供了关于日期计算的相关功能,比如:年、月、日、时、分、秒的展示和计算。
GregorianCalendar 是 Calendar 的一个具体子类,提供了世界上大多数国家/地区使用的标准日历系统。
初始化:
1、通过构造方法创建:
GregorianCalendar calendar = new GregorianCalendar(2999, 10, 9, 22, 10, 50);
2、先通过new初始化,再通过set将值设置给对象
GregorianCalendar calendar2 = new GregorianCalendar();
calendar2.set(Calendar.YEAR, 2999);
......
--日历对象和时间对象转化:
日历-->时间:get
GregorianCalendar calendar3 = new GregorianCalendar(2999, 10, 9, 22, 10, 50);
Date d = calendar3.getTime();
时间-->日历:set
GregorianCalendar calendar4 = new GregorianCalendar();
calendar4.setTime(new Date());
菜鸟雷区
注意月份的表示,一月是0,二月是1,以此类推,12月是11。 因为大多数人习惯于使用单词而不是使用数字来表示月份,这样程序也许更易读,父类Calendar使用常量来表示月份:JANUARY、FEBRUARY等等。
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(表示11月),注意此处:一月是0,二月是1,以此类推,12月是11。
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); //2999年2月3日,星期日 10:20:23
// 日期计算(增减时间操作)
GregorianCalendar calendar3 = new GregorianCalendar(2999, 10, 9, 22, 10, 50);
calendar3.add(Calendar.MONTH, -7); // 月份减7
calendar3.add(Calendar.DATE, 7); // 增加7天
printCalendar(calendar3); //2999年4月16日,星期2 10:10:50
// 日历对象和时间对象转化:getTime(),setTime(new Date())
Date d = calendar3.getTime(); //Tue Apr 16 22:10:50 CST 2999
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; // 星期几 周日是1,周一是2,。。。周六是7
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 ,如图所示为今日所在月份的日历:
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);
//split是将字符串变为一个数组
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六");
//打印空白位置(dow-1就是实际上的星期数(除了周日),星期数的数量和空白位置个数相同)
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);
}
}
}
Math类
java.lang.Math提供了一系列静态方法用于科学计算;其方法的参数和返回值类型一般为double型。如果需要更加强大的数学运算能力,计算高等数学中的相关内容,可以使用apache commons下面的Math类库。
Math类的常用方法:
- abs 绝对值
- acos,asin,atan,cos,sin,tan 三角函数
- sqrt 平方根
- pow(double a, double b) a的b次幂
- max(double a, double b) 取大值
- min(double a, double b) 取小值
- ceil(double a) 大于a的最小整数
- floor(double a) 小于a的最大整数
- random() 返回 0.0 到 1.0 的随机数 [0,1)
- long round(double a) double型的数据a转换为long型(四舍五入,5时选较大的那一边,e,g:-1.5->-1)
四舍五入原理:原来的数加上0.5,然后向下取整。
- toDegrees(double angrad) 弧度->角度
- toRadians(double angdeg) 角度->弧度
Math类的常用方法
public class TestMath {
public static void main(String[] args) {
//取整相关操作
System.out.println(Math.ceil(3.2)); //4.0
System.out.println(Math.floor(3.2)); //3.0
System.out.println(Math.round(3.2)); //3
System.out.println(Math.round(3.8)); //4
//绝对值、开方、a的b次幂等操作
System.out.println(Math.abs(-45)); //45
System.out.println(Math.sqrt(64)); //8.0
System.out.println(Math.pow(5, 2)); //25.0
System.out.println(Math.pow(2, 5)); //32.0
//Math类中常用的常量
System.out.println(Math.PI); //3.141592653589793
System.out.println(Math.E); //2.718281828459045
//随机数
System.out.println(Math.random());// [0,1) 0.810278955716287
}
}
Math类中虽然为我们提供了产生随机数的方法Math.random(),但是通常我们需要的随机数范围并不是[0, 1)之间的double类型的数据,这就需要对其进行一些复杂的运算。如果使用Math.random()计算过于复杂的话,我们可以使用例外一种方式得到随机数,即Random类,这个类是专门用来生成随机数的,并且Math.random()底层调用的就是Random的nextDouble()方法。
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包下。
File类
File类的基本用法
java.io.File类:代表文件和目录。 在开发中,读取文件、生成文件、删除文件、修改文件的属性时经常会用到本类。
File类的常见构造方法:public File(String pathname)
以pathname为路径创建File对象,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
文件的创建
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();
}
}
直接就在工程的下一级目录下生成文件a.txt
在eclipse项目开发中,user.dir就是本项目的目录。因此,执行完毕后,在本项目和D盘下都生成了新的文件(如果是eclipse下,一定按F5刷新目录结构才能看到新文件)。
通过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()); //false
System.out.println("File是否是目录:"+f.isDirectory()); //false
System.out.println("File是否是文件:"+f.isFile()); //false
System.out.println("File最后修改时间:"+new Date(f.lastModified()));
//Thu Jan 01 08:00:00 CST 1970
System.out.println("File的大小:"+f.length()); //0
System.out.println("File的文件名:"+f.getName()); //b.txt
System.out.println("File的目录路径:"+f.getPath()); //d:\b.txt
}
}
通过File对象创建空文件或目录(在该对象所指的文件或目录不存在的情况下)
使用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);//创建失败
}
}
使用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);//创建成功
}
}
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 {
//如果目录不存在,先创建目录
//注意,创建分为两步:
//先创建父级文件夹:d:/sxt
File dir = file.getParentFile();
dir.mkdirs();
//再创建文件:b.txt
flagn = file.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
System.out.println("创建失败");
e.printStackTrace();
}
}
//文件重命名(同学可以自己测试一下)
//file.renameTo(new File("d:/readme.txt"));
}
}
递归遍历目录结构和树状展现
本节结合前面给大家讲的递归算法,展示目录结构。大家可以先建立一个目录,下面增加几个子文件夹或者文件,用于测试。
使用递归算法,以树状结构展示目录树
import java.io.File;
public class TestFile6 {
public static void main(String[] args) {
File f = new File("F:\\电子书");
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()) {
//listFiles()得到的是一个文件数组
File[] files = file.listFiles();
//用增强for循环为每个文件进行递归判断与执行
for (File temp : files) {
//递归调用该方法:注意等+1
printFile(temp, level + 1);
}
}
}
}
结果:
电子书
-5345135495e5f
--影响力.txt
-5345135495e5f.zip
-《影响力》罗伯特·西奥迪尼.pdf
枚举
用于要定义一组常量时。
JDK1.5引入了枚举类型。枚举类型的定义包括枚举声明和枚举体。格式如下:
enum 枚举名 {
枚举体(常量列表)
}
枚举体就是放置一些常量。我们可以写出我们的第一个枚举类型,如示例8-27所示:
创建枚举类型
enum Season {
SPRING, SUMMER, AUTUMN, WINDER
}
所有的枚举类型隐性地继承自 java.lang.Enum。
枚举实质上还是类!而每个被枚举的成员实质就是一个枚举类型的实例(对象),他们默认都是public static final修饰的(相当于一个常量)。可以直接通过枚举类型名使用它们。
枚举的构造方法是私有的,所以不能通过new为枚举赋值。
//私有构造函数
private Day(String s, int i)
{
super(s, i);
}
反编译的结果,说明枚举 enum的值只能通过在定义时赋值。
static
{
//实例化枚举实例
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
WEDNESDAY = new Day("WEDNESDAY", 2);
THURSDAY = new Day("THURSDAY", 3);
FRIDAY = new Day("FRIDAY", 4);
SATURDAY = new Day("SATURDAY", 5);
SUNDAY = new Day("SUNDAY", 6);
$VALUES = (new Day[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
老鸟建议
- 当你需要定义一组常量时,可以使用枚举类型。
- 尽量不要使用枚举的高级特性,事实上高级特性都可以使用普通类来实现,没有必要引入枚举,增加程序的复杂性!
枚举的使用
import java.util.Random;
public class TestEnum {
public static void main(String[] args) {
// 枚举遍历
//static values():返回一个包含全部枚举值的数组,可以用来遍历所有枚举值;
//枚举里面的常量实际上是一个个对象,所以要用对象存储
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 {
星期一, 星期二, 星期三, 星期四, 星期五, 星期六, 星期日
}