[Java教程]15.实现可变数组与时间类的使用

Java教程专栏:https://blog.csdn.net/qq_41806966/category_9929686.html

hello,I'm shendi

本节将使用数组实现一个类便于使用,以及学习使用时间类.


目录

 

封装数组的类,实现可变长度数组

实现

接下来先从最简单的开始,获取(get)

其次简单的是设置(set)

然后完成添加操作

最后完成删除操作

时间类

Date类

SimpleDateFormat类

Calendar 类

总结


封装数组的类,实现可变长度数组

从之前学过的数组,我们知道数组的长度是不可变的,为了方便(也为了锻炼),自己将对数组的操作封装成一个工具类,使得长度不能改变的数组变得可以改变.

思路:

我们可以通过创建一个新的数组来改变长度,所以要实现一个对数组封装的工具类很简单.

 

分析数组的功能:

数组长度不能变,但是可以改变里面每一个项的值,每一个项默认都为默认值.

我们不能获取/改变超过数组长度的项,会出现错误.

用数组的大多数场景都是项很多,而且一般都是未知的.需要循环遍历等...

 

通过分析,我们的类里面需要有修改指定内容的方法.

为了简单,简便,我们不希望再有长度固定的概念,而是你的项有多少,就有多长...

所以我们类里面需要有 添加/删除 的方法(不需要默认值,删除掉就没有这一项了).

我们也需要提供一个获取项的功能(与数组一样,超出项现有的数量就报错(自定义异常没学,先用输出代替))


实现

既然是对数组功能的封装类,类名就叫 MyArray 吧(我的数组)

因为是工具类,所以我们不用写 main 函数(供其他类使用)

首先,根据上面的分析,可以知道这个类最少有四个方法 [增加/删除/设置/获取]

并且,除了增加,其余方法都是对指定项进行操作(并不是所有).

所以我们除了增加的方法,其余的方法都要加上一个参数,用于指定是操作哪一个项.(int index)

我们的增加方法,需要有一个参数,参数内容为我们需要添加进数组的值.

增加(add),删除(delete,简写为del),修改也等于设置(set),获取(get)

我们现在分析一下方法的返回值.

  • 我们的增加方法,因为是增加,我们不需要方法给我们传递什么信息,所以是无返回值
  • 删除方法,也不需要返回什么信息,所以也无返回值
  • 设置方法....无返回值
  • 获取,我们需要获取到指定项的值,所以需要一个返回值
  • Object类是所有类的父类(万物都是Object),所以我们使用Object作为参数类型,使用的时候需要强转
    • 后面学到泛型可以更好地处理此操作

所以我们类里面代码如下

现在我们就要去实现以下花括号里的空白了.

在实现之前我们继续确定需求,我们是要封装数组,简便操作...

所以我们肯定需要数组.

数组长度是固定的,所以我们需要知道当前项的数量(而不是数组的长度).

在当前项的数量等于数组的长度的时候,我们就要换新数组了,因为原数组长度无法满足我们需求了.

新数组长度肯定比原数组长度大,至于大多少,我们自己设置.


现在有了 数组,数组内有效(项)的数量,数组默认长度和每次换新数组加的长度.

接下来就是分析四个方法具体实现了.

  • 添加
    • 我们添加项到数组内,添加到当前有效项的最后面.
    • 数组长度固定的,有限的,所以我们添加前需要判断当前有效项和数组长度是否相等,相等我们就换新数组,将原来数组的值拷贝到新数组内
  • 删除
    • 删除指定项,如果没有指定项,那就不进行操作,也不提醒(因为没必要,删除本来就是不需要那个项)
    • 我们删除指定项,删除的不是最后一个项就需要将后面的数据往前移动,并且将当前项的数量-1
    • 例如有以下几个项
      • 1,2,3,4,5,6,7,8
    • 我们把5删除,那么现在的项就为
      • 1,2,3,4,6,7,8
    • 而不是
      • 1,2,3,4,0,6,7,8(0为默认值)
  • 设置
    • 设置指定项的值,如果没有此项那么就提醒,并且不执行后面操作.
    • 也就是赋值操作.
    • 因为我们类型是Object的,所以什么类型的都可以往里面设置(虽然感觉挺方便的,但是你会弄不清楚是什么类型),所以建议统一类型...
  • 获取
    • 这个简单,直接先判断是否有没有这个项,有则返回,无则提示

接下来就一个一个进行实现.

首先定义好数组和变量,数组为Object类型,变量为int类型(实际数量什么的)


接下来先从最简单的开始,获取(get)


其次简单的是设置(set)

判断项是否存在与获取是一样的,所以我们可以复制过来.


然后完成添加操作

我们直接将要添加的项放到数组现有值的最后,但是在添加前需要先判断数组是否超过大小了


最后完成删除操作

删除操作代码比较多,因为我们在删除后,需要将后面的项一个个往前移.

我们还需要提供一个获取当前有效长度大小的方法

最后试一下,看看使用起来感觉如何.

写一个类,里面让用户输入字符串,提供增删改查功能


编译 MyArray 和 TestArray,运行TestArray看一下结果

现在我们不需要担心长度问题,因为这个长度是可以一直增加的,重新测试一下

因为我们的程序没有写退出功能,所以这个时候需要用到快捷键 Ctrl + C,强制退出.

下面是两个类的代码

MyArray


public class MyArray {

	/**
	 * 数组刚开始的大小为 10.
	 */
	Object[] objs = new Object[10];
	
	/**
	 * 实际项的数量,刚开始的时候没有一个项,所以为0.
	 */
	int num = 0;
	
	/**
	 * 数组超过大小时,增加的大小.
	 */
	int newLength = 5;
	
	/**
	 * 添加进数组的方法
	 */
	public void add(Object obj) {
		// 超过了数组的长度则新建一个数组,并复制
		if (num == objs.length) {
			// 原数组放到另一个变量里保存起来,然后原数组改成新数组,在原长度上增加.
			Object[] temp = objs;
			objs = new Object[temp.length + newLength];
			// 将 temp 拷贝到原数组(已经被替换成新数组)
			// 使用循环 一个个赋值...(也有更高效的方法,但是现在重点不是这个)
			for (int i = 0;i < temp.length;i++) {
				objs[i] = temp[i];
			}
		}
		// 添加新项,位置为现有的最后一个
		objs[num] = obj;
		// 现有项 + 1
		num++;
	}

	/**
	 * 删除指定项的方法, index是这个项在数组内的位置.
	 * 这个地方也可以加个返回值,返回删除的内容.
	 */
	public void del(int index) {
		// 先判断是否有指定项,有则删 无则不操作
		if (index < 0 || index >= num) {
			return;
		}
		// 删除,直接将此项后面的往前移...
		// 这里 - 1 是为了避免在执行下面操作数组越界
		int length = objs.length - 1;
		for (int i = index;i < length;i++) {
			objs[i] = objs[i + 1];	
		}
		// 总数量 - 1
		num--;
	}
	
	/**
	 * 设置指定项的方法, index是把 obj 设置到哪个位置(位置必须存在)
	 */
	public void set(int index,Object obj) {
		// 判断项是否存在与 get 方法是一样的,所以复制过来
		// 不同的是没有返回值,我们直接return,代表后面代码不执行.
		if (index < 0 || index >= num) {
			System.err.println("你设置的东西没有了");
			return;
		}
		// 直接将指定位置值设为传递的值
		objs[index] = obj;
	}

	/**
	 * 获取指定位置的项,返回的是Object类型,因为我们需要让这个类可以存所有类型...
	 * 不好的就是,你不知道这个类的具体类型是什么(使用的时候要强转,类型不对会出错).
	 */
	public Object get(int index) {
		// 获取 index 位置的数,如果不存在,则提醒并返回空.
		// index 小于 0 或者 大于 当前实际的项总数,就代表没有这个项,提醒并返回空
		if (index < 0 || index >= num) {
			// 之前我们使用的都是 System.out... err代表错误输出(颜色是红色的)
			System.err.println("你获取的东西没有了,给你返回空");
			// 返回后就不会执行下面的代码了,并且获取到的值为 null
			return null;
		}
		// 直接返回.
		return objs[index];
	}
	
	/**
	 * 获取当前有效大小.
	 */
	public int getSize() {
		return num;
	}
}

TestArray

import java.util.Scanner;

public class TestArray {
	public static void main(String[] args) {
		Scanner	sc = new Scanner(System.in);
	
		// 创建我们自己写的数组类的对象
		MyArray my = new MyArray();
		while (true) {
			System.out.println("1增,2删,3改,4查");
			int input = sc.nextInt();
			switch (input) {
			case 1:
				System.out.println("请输入要保存的数据");
				my.add(sc.next());
				break;
			case 2:
				System.out.println("请输入要删除的数据位置(数字)");
				my.del(sc.nextInt());
				break;
			case 3:
				System.out.println("请输入要设置的数据位置(数字)");
				int index = sc.nextInt();
				System.out.println("请输入要设置的数据");			
				my.set(index,sc.next());
				break;
			case 4:
				// 这里直接循环获取进行输出
				for (int i = 0;i < my.getSize();i++) {
					System.out.println(i + "\t" + my.get(i));
				}
				break;
			}
		}
	}
}

至此,对数租的掌握程度已经上了一个台阶.


时间类

我们制作软件的时候难免会使用到时间,像定时闹钟,时间显示,签到等等,都需要时间.

在 Java 中,提供了 Date 类用于表示时间, Date 位于 Java.util 包中

我们获取当前时间有以下几种方法.

  1. 使用 System.currentTimeMillis() 获取当前时间戳(时间戳就是表示时间的一串数字)
  2. 使用 new Date() 获取当前时间.
  3. 使用Calendar

我们可以使用一下上面两种,看看输出是什么结果

新建一个类,TestDate 内容如下

输出如下

上面那个是时间戳,下面那个是Date的输出形式,可以看出 年月日 时间,只不过是英文的表示形式

现在我们认识一个新的类, SimpleDateFormat,在java.text 包中.

此类可以将 时间戳,Date 转为我们熟悉的形式

例如我们要输出如下

  • 年-月-日 时:分:秒

代码如下

运行结果如下

Date类

我们每一次执行 new Date() 获取到的都是当前的时间.

Date类提供了很多方法,但是有些已经弃用了(不推荐使用),经过我自己的测试,发现使用Date的效率比Calendar快很多...

官方文档介绍(中文翻译版)

拥有获取 年,月,日的方法等

需要了解更多可以自行查看官方提供的 API

我这里也提供链接方便下载,1.8中文版的 API

https://download.csdn.net/download/qq_41806966/12518739

SimpleDateFormat类

此类用于将时间转换为我们认识的,熟悉的形式.

从上面例子来看,我们创建此类对象,然后才能进行转换.

在创建的时候,我们有个参数为字符串的

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

上面的 yyyy 代表年,MM代表月,dd代表天,HH代表小时,mm代表分钟,ss代表秒

格式可以自己更改,例如去掉空格等

Calendar 类

因为 Date 类的大部分方法都被弃用了,所以需要学习使用Calendar类(虽然我感觉Date能满足我的所有对日期处理的需求)

与 Date 一样, Calendar也在java.util包下

创建Calendar类的对象需要使用这种方法

通过Calendar.getInstance(); 来获取此类对象.

我们看一下官方的描述

所述Calendar类是一个抽象类,可以为在某一特定时刻和一组之间的转换的方法calendar fields如YEAR , MONTH , DAY_OF_MONTH , HOUR ,等等,以及用于操纵该日历字段,如获取的日期下个星期。 时间上的瞬间可以用毫秒值表示,该值是从1970年1月1日00:00 00:00.000 GMT(Gregorian)的Epoch的偏移量。 
该类还提供了用于在包外部实现具体日历系统的其他字段和方法。 这些字段和方法定义为protected 。 

与其他区域设置敏感的类一样, Calendar提供了一种类方法getInstance ,用于获取此类型的一般有用的对象。 Calendar的getInstance方法返回一个Calendar对象,其日历字段已使用当前日期和时间进行初始化: 

     Calendar rightNow = Calendar.getInstance();
 Calendar对象可以产生实现特定语言和日历风格的日期时间格式化所需的所有日历字段值(例如日语 - 公历,日语 - 繁体)。 Calendar定义某些日历字段返回的值的范围及其含义。 例如,日历系统第一个月的值为MONTH == JANUARY为所有日历。 其他值由具体的子类定义,如ERA 。 有关详细信息,请参阅各个实体文档和子类文档。 

获取和设置日历字段值 
可以通过调用set方法设置日历字段值。 在Calendar设置的任何字段值将不被解释,直到它需要计算其时间值(从Epoch的毫秒)或日历字段的值。 调用get , getTimeInMillis , getTime , add和roll涉及这样的计算。 

宽大 
Calendar有两种方式来解释日历字段, 宽松和不宽泛。 当Calendar处于宽松模式时,它接受的日历字段值范围比产生的范围更广。 当Calendar计算日历字段值返回get()时,所有日历字段都被归一化。 例如,宽松的GregorianCalendar解释MONTH == JANUARY DAY_OF_MONTH == 32为2月1日。 

当Calendar处于非宽松模式时,如果其日历字段中存在任何不一致,则会抛出异常。 例如, GregorianCalendar总是在1和月份之间生成DAY_OF_MONTH值。 如果设置了超出范围的字段值,则不宽大的GregorianCalendar会在计算其时间或日历字段值时抛出异常。 

First Week 
Calendar使用两个Calendar定义了一个特定地区的七天周:第一周的第一天和第一周的最小天数(从1到7)。 当Calendar时,这些数字取自区域设置资源数据。 也可以通过设置其值的方法来明确指定它们。 
当设置或获取WEEK_OF_MONTH或WEEK_OF_YEAR字段时, Calendar必须确定月或年的第一周作为参考点。 一个月或一年的第一周被定义为最早的七天内开始对getFirstDayOfWeek() ,并含有至少getMinimalDaysInFirstWeek()那一个月或一年的天。 星期编号...,-1,0在第一周之前; 周数为2,3,...跟随。 请注意,由get()返回的get()可能不同。 例如,特定的Calendar子类可以指定一年的第1周之前的一周的周一周的n 。 

日历字段分辨率 
在计算日历字段的日期和时间时,可能没有足够的计算信息(例如只有年月日没有月份),或可能存在不一致的信息(例如1996年7月15日星期二(格里高利) - 1996年7月15日实际上是星期一)。 Calendar将以Calendar方式解析日历字段值以确定日期和时间。 
If there is any conflict in calendar field values, Calendar gives priorities to calendar fields that have been set more recently.以下是日历字段的默认组合。 将使用由最近设置的单个字段确定的最新组合。 

For the date fields : 

 YEAR + MONTH + DAY_OF_MONTH
 YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
 YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
 YEAR + DAY_OF_YEAR
 YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
 For the time of day fields : 
 HOUR_OF_DAY
 AM_PM + HOUR
 如果在所选字段组合中没有设置其值的任何日历字段, Calendar使用其默认值。 每个字段的默认值可能会因具体的日历系统而异。 例如, GregorianCalendar ,字段的默认值是相同的时代的开始:即YEAR = 1970 , MONTH = JANUARY , DAY_OF_MONTH = 1 ,等等。 

注意:在某些奇异时期的解释中存在某些可能的含糊之处,其解决方式如下: 

23:59是一天的最后一分钟,00:00是第二天的第一分钟。 因此,一九九九年十二月三十一日(星期三)在二○○○年一月一日(星期一) 
虽然历史上不太精确,但午夜也属于“am”,中午属于“pm”,所以在同一天12:00(午夜)上午12时01分,中午12:00(中午)下午01点 
日期或时间格式字符串不是日历定义的一部分,因为这些字符串必须在运行时由用户修改或覆盖。 使用DateFormat格式化日期。 

现场操纵 
:日历字段可以用三种方法来改变set() , add()和roll() 。 
set(f, value)将日历字段f改为value 。 此外,它设置内部成员变量以指示日历字段f已被改变。 尽管日历字段f立即更改,以毫秒为单位日历的时间值不重新计算,直到下一次调用get() , getTime() , getTimeInMillis() , add() ,或roll()而成。 因此,对set()多次调用不会触发多个不必要的计算。 由于使用set()更改日历字段,其他日历字段也可能会根据日历字段,日历字段值和日历系统而更改。 此外,在set计算日历字段之后, get(f)会返回value设置的调用set方法。 具体细节由具体的日历类确定。 

示例 :考虑一个GregorianCalendar设置为1999年8月31日的GregorianCalendar.呼叫set(Calendar.MONTH, Calendar.SEPTEMBER)将日期设置为1999年9月31日。这是一个临时内部表示,解析为1999年10月1日,如果getTime()被调用。 但是,在拨打set(Calendar.DAY_OF_MONTH, 30)之前致电getTime()将日期设置为1999年9月30日,因为set()本身后没有重新set() 。 

add(f, delta)增加delta到现场f 。 这相当于调用set(f, get(f) + delta)进行了两次调整: 

Add rule 1. The value of field f after the call minus the value of field f before the call is delta, modulo any overflow that has occurred in field f. Overflow occurs when a field value exceeds its range and, as a result, the next larger field is incremented or decremented and the field value is adjusted back into its range.

Add rule 2. If a smaller field is expected to be invariant, but it is impossible for it to be equal to its prior value because of changes in its minimum or maximum after field f is changed or other constraints, such as time zone offset changes, then its value is adjusted to be as close as possible to its expected value. A smaller field represents a smaller unit of time. HOUR is a smaller field than DAY_OF_MONTH. No adjustment is made to smaller fields that are not expected to be invariant. The calendar system determines what fields are expected to be invariant.

此外,与set()不同, add()强制立即重新计算日历的毫秒数和所有字段。 

示例 :考虑一个GregorianCalendar设置为1999年8月31日的add(Calendar.MONTH, 13)呼叫add(Calendar.MONTH, 13)将日历设置为2000年9月30日。 添加规则1将MONTH字段设置为9月,因为在8月份添加13个月将给明年9月。 由于DAY_OF_MONTH不能31月在GregorianCalendar , 增加规则2套DAY_OF_MONTH 30,最接近的可能值。 虽然它是一个较小的领域, DAY_OF_WEEK没有被规则2调整,因为预计在GregorianCalendar的月份变化时会改变。 

roll(f, delta)增加delta到现场f不更改更大的字段。 这相当于调用add(f, delta)进行以下调整: 

Roll rule. Larger fields are unchanged after the call. A larger field represents a larger unit of time. DAY_OF_MONTH is a larger field than HOUR.

示例 :参见GregorianCalendar.roll(int, int) 。 

使用模式 。 为了激励add()和roll()的行为,请考虑一个用户界面组件,其中包含月,日和年的增量和减量按钮,以及底层的GregorianCalendar 。 如果接口读取1999年1月31日,用户按月增量按钮,应该读什么? 如果基础实施使用set() ,则可能会在1999年3月3日发布。更好的结果是1999年2月28日。此外,如果用户再次按下月增加按钮,应该在1999年3月31日,而不是1999年3月28日通过保存原始日期并使用add()或roll() ,根据是否影响较大的字段,用户界面可以像大多数用户一样直观地期待。 

从以下版本开始: 
JDK1.1 

我们想要获取年/月/日的值,需要使用对象的 get方法

参数为此类的某个字段

例如获取 天

年月同理, 年 YEAR,月 MONTH

同样,我们可以设置时间,通过set方法...

更多的参考API. 都是使用的方法,没有什么思想.这里不过多累述

总结

  1. 数组要想改变长度需要创建新数组,然后将原数组复制到新数组
  2. 创建 Date 可以表示当前的时间
  3. 使用System.currentTimeMillis() 获取当前时间的时间戳表示形式
  4. 使用 SimpleDateFormat 可以将格式变为我们熟悉的格式
  5. Calendar 类提供了更方便的方法...

下一节我们实现排序.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章