Java 内置工具类(String、StringBuffer、LinkedList、ArrayList、HashMap等工具)
Java提供了异常丰富的工具类数量巨阳之多,对于 Java学习者而言,通过下面讲解的为数不多的几个类,理解和掌握类的共性,做到举一反三,触类旁通;尤其要学会利用程序编辑器和网络资源,主地查阅相关信息;通过编写具体的例子,验证类及其方法的功用,进而掌握个类,这才是最重要的。
下面分两部分,先讲述String、StringBuffer、StringBuilder、Calendar、数组等基本工具类;然后就要更深入地来了解“泛型”的基本概念,在其基础上来学习LinkedList、ArrayList、HashMap 等有技巧的工具。
文章目录
- Java 内置工具类(String、StringBuffer、LinkedList、ArrayList、HashMap等工具)
- 基础工具类
- String 类
- StringBuffer 类 和 StringBuilder 类
- 关于输入Scanner类
- 数组
- 一维数组
- 关于 array.length()
- 数组的 clone() 方法与“浅克隆”
- 二维数组
- 日历类:Calendar
- 泛型初步
- 进阶工具类
- ArrayList 与 LinkedList
- 前言
- ArrayList
- LinkedList
- ArrayList 与 LinkedList 特点及方法
- 1、泛型的好处
- 2、元素定位法
- 3、判断元素是否存在
- HashMap
- 前言
- HashMap()
- 使用注意事项
- HashMap 的遍历
基础工具类
String 类
“字符串”几乎在所有程序设计语言中,都被“认真对待”;提供大量的字符串处理函数或类似工具。Java 当然也不例外。下面则是其最基本用法“
public class AboutString1 {
//String 类
public static void main(String[] args) {
//字符串(类)对象的生成方法
//1、new 实例化
String str1 = new String("第一种生成方法");
//2、直接赋初值(字符串常量)
String str2 = "第二种生成方法";
//这种方法是类型转换,而且重载的方法很多,很犀利;
String str3 = String.valueOf(12300);
//String 类的 length() 方法可以得到字符串的长度;
System.out.println(str1.length());
//String 类提供非常多的方法,使得字符串操作更方便;
System.out.println(str1.substring(3,5));
//前面提到过“String + 数值”,会产生自动转换类型……
System.out.println(str3 + 321);
}
}
关于 String 类,最重要的一个声明是:
String 类的值不能被修改。
public class AboutString2 {
public static void main(String[] args) {
String str = "abcdefg123xy";
System.out.println(str.replace("123","456"));
System.out.println(str);
}
}
观察输出结果:
str.replace() 方法是将源字符串中的“123”替换成“456”,输出结果似乎确实更改了 str 的内容,但后续的输出明确指出 str 内容似乎并没有更改!
实际上,str.replace() 的结果是一个新的字符串,是String新的对象。
public class AboutString3 {
public static void main(String[] args) {
System.out.println("" + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9);
String str = "";
for(int i = 1; i < 100; i++) {
str += 1;
}
System.out.println(str);
}
}
输出结果为:
上图中的操作结果大家应该都能看出来,都是字符串类型的数据;
程序第五行的目的就是让大家明白,第五行和第八行的本质是一样的,都是字符串与数值型数据“相加”,得到新的字符串的过程。但是,这段程序是非常低效率的,原因是第九行每执行一次,都会 new 一个新的 String 对象!
要想改变这种低效也是可以的,但是不能再使用 String 类了,就需要使用我们接下来要介绍的 StringBuffer 类或者 StringBuilder 类。
StringBuffer 类 和 StringBuilder 类
由于 String 类是不可更改的,所以,在需要进行大量“字符串拼凑”的时候,用 String 类型进行操作是非常低效的,对内存的消耗也很高。这时候就应该用到我们的 StringBuffer 类或者 StringBuilder 类。
这两个类非常的相似,不同点是关于线程安全性方面的,这里暂时先不赘述。
以 StringBuffer 为例,我们给出他的使用方法:
通常情况下,如果要处理的字符串是简单常量,或者字符串在程序中的变化不大,那用 String 就好,效率反而高。如果字符串在程序中会不断变化,那还是用StringBuffer 类或者 StringBuilder 类。
关于输入Scanner类
在学习 C 语言的时候,我们很早就接触了输入函数 scanf() ,但是,直到现在我们依旧没有提及Java中的输入,Java中的输入需要用到一个类Scanner,当我们了解了类的基本概念后,我们对Scanner就可以进行学习了。
Scanner 类的功能比 C 语言中 scanf() 函数强大太多了,使用起来也相对复杂,首先我们需要用下面的形式初始化Scanner 类对象。
Scanner in = new Scanner(System.in);
其中的 System.in 就是“标准输入设备”,也就是键盘。在使用时,我们也需要注意关闭操作。即通过在程序末尾写上:
in.close();
我们常用的方法是:
String str = in.next();
完整的程序段如下:
Public class AboutScanner {
public static void main(String[] args) {
Scanner in = new Scanner(Sysetm.in);
String str = in.next();
System.out.println(str);
in.close();
}
}
关于输入的控制, Scanner 类提供一类 hasNext***()方法,通常配合循环一起使用,完成多个数据的输入:
Public class AboutScanner1 {
public static void main(String[] args) {
Scanner in = new Scanner(Sysetm.in);
int dataIn;
while(in.hasNestInt()) {
dataIn = in.nextInt();
System.out.println(dataIn);
}
in.close();
}
}
由此可见,Java的输入也是非常的容易上手的;
数组
一维数组
数组在 Java 中是”类“,这与我们曾经学习的C语言中的数组有非常大的差别!
那么Java数组怎么定义?java中数组的定义及使用方法?首先我们先了解下 Java 中数组的定义和初始化:
public class AboutArrar1 {
public static void main(String[] args) {
// 第一种定义方法:声明一维数组:数据类型 [] 数组名 = {数据……};
int [] array1 = {1,2,3,4};
// 第二种定义方法:声明一维数组:数据类型 [] 数组名 = new 数据类型[长度];
int [] array2 = new int[10];
// 第三种定义方法:声明一维数组:数据类型 数组名[];
double array3[]; //声明数组;
array3 = new double[5]; //分配内存空间
}
}
一维数组可以存放上千万个数据,并且这些数据的类型是完全相同的,以上使用方法并不固定,使用 java 数组,必须声明数组和分配内存给该数组。数组声明后实际上是在栈内存中保存了此数组的名称,然后需要在堆内存中配置数组所需要的内存,通知编译器,所声明的数组要存放多少个元素,而 new 则是命令编译器根据括号里的长度分配空间。
Java数组本身只是一个”指针“,通过 new 申请空间后,在没有给数组元素赋初值前,各数组元素的值是根据是否是八大基本类型,或者是类类型,Java 都有其默认的初始值。
八大基本类型的初始值为 0 (double为0.0、boolean为 false );而所有类类型的初值都是null,即空指针。如下;
public class AboutArrar1 {
public static void main(String[] args) {
int [] array1 = {1,2,3,4};
for(int i = 0; i < arr1.length; i++) {
System.out.println(array1[i] + " ");
}
int [] array2 = new int[10];
for(int i = 0; i < arr2.length; i++) {
System.out.println(array2[i] + " ");
}
String array3[];
array3 = new String[5];
for(int i = 0; i < arr3.length; i++) {
System.out.println(array3[i] + " ");
}
}
}
输出结果为:
关于 array.length()
既然数组是类,那么length就是类成员,而它的意思也很明确:数组元素个数(长度)。有关数组下标的最大边界值控制,都是用 length 成员的,这是确保不出现”下标越界“的方法。
数组的 clone() 方法与“浅克隆”
数组类提供一个 clone() 方法,顾名思义:克隆。这个方法可以”复制“出一个数组。
例如:
public class AboutArrayClone {
public static void main(String[] args) {
int [] arr1 = new int[10];
for(int i = 0; i < arr1.length; i++) {
arr1[i] = i;
}
int [] arr2 = arr1.clone();
for(int i = 0; i < arr2.length; i++) {
System.out.println((i == 0 ? "" : ",") + arr2[i]);
}
}
}
输出结果为:
上面代码可以看到 clone() 的使用。所以什么是”浅克隆“呢?即就是,arr.clone()只是克隆了arr各成员的值,如果数组元素不是八大基本类型,那么 clone() 仍然只克隆数组元素的值;
其实就是说,arr初始化时对每一个对象都申请了实例空间,而每一个对象就是所申请实例空间的首地址;clone() 方法”克隆“的只是 arr 数组每一个元素本身的值,也就是首地址值,所以克隆出来的 arr 数组,表面是一个”独立于“ arr 的数组,但其实都指向同一实例空间。这即是”浅克隆“。
二维数组
二维数组的本质也是”类“。同样,先了解一下二维数组的基本定义和初始化:
public class AboutArrar1 {
public static void main(String[] args) {
//1、通过赋初值确定二维数组行、列个数;
int [][] array1 = {{1,2},{1,2,3},{1,2,3,4}};
//2、通过new确定行、列个数;
int [][] array2 = new int[3][2];
//3、先初始化行,再逐行初始化列;
int [][] array3 = new int[3][];
for(int i = 0; i < array3.length; i++) {
array3[i] = new int[2];
}
}
}
对于 Java 的二维数组对象,其本质依然是一个指针,而这个指针应该先初始化,为其分配一段空间,而这段空间是由”一维数组对象“组成的,需要进一步初始化。即如下图解;
Java数组对象的类型判别该怎么操作呢?
array.getClass().isArray();
日历类:Calendar
日期类型是很多高级程序设计语言所提供的一种强力工具类型,通过它可以方便地进行日期数据的处理和输出,在Java程序设计中广泛使用。
Calendar类的对象初始化方法很另类,其采用一种所谓”工厂“模式初始化日历类对象:
Calendar today = Calendar.getInstance();
//其中getInstance()方法是 Calendar 类的基本使用方式;
下面通过例子讲解 Calendar 类的基本使用方法。如何取得年、月、日、周、时、分、秒及毫秒的值:
public class AboutCalendar {
public static void main(String[] args) {
Calendar today = Calendar.getInstance();
System.out.println("年:" + today.get(Calendar.YEAR));
System.out.println("月:" + today.get(Calendar.MONTH));
System.out.println("日:" + today.get(Calendar.DATE));
System.out.println("周:" + today.get(Calendar.DAY_OF_WEEK));
System.out.println("时:" + today.get(Calendar.HOUR));
System.out.println("分:" + today.get(Calendar.MINUTE));
System.out.println("秒:" + today.get(Calendar.SECOND));
System.out.println("毫秒:" + today.get(Calendar.MILLISECOND));
}
}
看输出结果:
如果想输出成标准格式,可以自己尝试尝试。
泛型初步
什么是泛型?泛型在Java中有什么作用?
泛型是 Java1.5 版本提供的一种强大机制,它使得 Java 编程变得更灵活,更安全。那又为什么要引入”泛型“的概念?
功能需求中没有确定数组元素的具体类型,假设我们把类型限定到八大基本类型,那么,就现在我们学到的知识中,可以用“重载方法”的手段;但在我们运用的过程中会发现,这样的解决方法感觉非常”笨“;为了简化这种问题的处理,Java 提出了”泛型“的概念。泛型最重要的一点是:
编码时不确定,运行时再确定!
public class AboutGeneric {
public static <T> T[] revArray(T[] m) {
T temp;
for (int i = 0; i < m.length / 2; i++) {
temp = m[i];
m[i] = m[m.length - i -1];
m[m.length - i -1] = temp;
}
return m;
}
}
上述程序中:
在方法返回值类型前出现的,说明这个方法用到泛型;在以后的代码 T 就代表某种类类型;而这个类类型的具体情况,要等到方法被调用时,再根据实参具体类型进行判定。
需要特别注意的是:泛型只能是类类型!
利用泛型,还可以定义泛型类:
public class MyGeneric<T> {
private T member;
public MyGeneric(){
}
public MyGeneric(T memeber){
this.member = member;
}
public T getMember() {
return member;
}
public void setMember(T member) {
this.member = member;
}
}
上述代码中:
public class MyGeneric<T>
类名称后面的是”泛型标志“,此后在代码中可以用T作为类型。除此之外,泛型还可以是多个:
public class MyGeneric<T, K> {}
进阶工具类
ArrayList 与 LinkedList
前言
数组是编程中常见的数据结构,使用频率是很高的。但是,数组固有其缺陷,比如数组大小必须事先定义,而且在使用过程中不能改变。这使得很多问题用数组解决时很不方便。C 语言中也有类似情况,那时的解决万案是“链表"。用链表可以避免上述问题。但是,链表也有其不便之处,比如 ,编程复杂、不支持“随机定位”等。
Java为解决上述问题,提供了两个高度封装的类: ArrayList 和 LinkedList,说它们是“高度封装”的意思是,这两个类的内部实现都很不简单,功能十分强大,但是,给工具使用者提供的手段却很简洁,极易使用。
首先,这两个类都是List后缀,这就说明两个类都是“列表”(其实就是“数组”),只是各自的实现原理有所不同:ArrayList 内部使用“动态数组”,而 LinkedList 内部使用“链表”。
其次,无论内部使用什么方式,它们所提供的基本功能都是类似的。可以认为这两个类都是“动态数组“,完全可以替代前面所介绍的数组。
ArrayList
首先我们先看一段代码:
public class AboutArrayList {
public static void main(String[] args) {
//初始化 ArrayList 对象 array
ArrayList<Integer> array = new ArrayList<>();
//增加一个 int 数值 10
array.add(10);
//增加一个 int 数值 20
array.add(20);
//取得下标为 1 的第 2 个数值
int num = array.get(1);
System.out.println(num);
}
}
首先看到的是 ArrayList 是泛型类,可以存储任意指定类型的数据。
ArrayList 最大的好处就是 ”动态“ ,可以不必关心数组空间的大小。ArrayList 可以根据实际装入的数据调整数组空间大小。在访问数组中的数值时,也可以用下标遍历的方法。如下
public class AboutArrayList {
public static void main(String[] args) {
int[] a = {1,2,3,4,5};
ArrayList<Integer> array = new ArrayList<>();
for (int i = 0; i < a.length; i++) {
array.add(a[i]);
}
for (int num : array) {
System.out.println(num);
}
}
}
上述代码中,
for (int num : array)
理解:”:“前是元素,元素的类型必须与 ArrayList 的泛型一致;这个元素就是后面数组或 LIst 中的每一个元素,而且是按下标顺序从前往后取得的。
上述操作对于LinkedList也是相同的。
LinkedList
public class AboutLinkedList {
public static void main(String[] args) {
LinkedList<Integer> arr = new LinkedList();
arr.add(42);
arr.add(777);
arr.add(1314);
for (int num : arr) {
System.out.println(num);
}
}
}
我的教主曾在数据结构中提到过”数组与链表的异同“:数组是连续的存储结构,存储空间利用率高,支持随机定位,但是数组空间大小需要事先定义(动态空间申请的数组也需要在申请空间时确定大小);链表是非连续存储结构,存储空间利用率不如数组高,只能顺序定位,但其空间大小灵活可变;数组对于插入、删除操作,时间复杂度较高,为 O(n);链表对于插入、删除的操作,复杂度为常量,可以认为是 O(1)。
上面的说法说明,如果未来程序中基本没有数据的插入和删除,主要是查找的话,那么, ArrayList 效率更好;反之,LinkedList 效率更好。
ArrayList 与 LinkedList 特点及方法
1、泛型的好处
ArrayList 与 LinkedList 都是泛型类,这使得两个”容器“都可以方便的存储和处理任何类型的数据。
2、元素定位法
indexOf() 方法是 ArrayList 与 LinkedList 都提供的方法,可以获得指定元素的下标。
public static void main(String[] args) {
LinkedList<Complex> complexLinkedList = new LinkedList();
complexLinkedList.add(new Complex());
complexLinkedList.add(new Complex(1));
complexLinkedList.add(new Complex(4,2));
complexLinkedList.add(new Complex(7,7));
for (Complex complex : complexLinkedList) {
System.out.println(complex);
}
Complex c = new Complex(4,2);
int index = complexLinkedList.indexOf(c);
System.out.println(index);
}
}
输出结果为:
关于 indexOf() 的输出结果为:2;
这里要注意一个问题:Complex类的equals()方法。
上述程序能够定位成功的关键是,在 Complex类中,我们覆盖了equals()方法。而这个方法在 indexOf() 方法执行过程中被自动调用,用以判定“相等”与否。如果我们没有覆盖 equals()方法,那么结果会怎样?
首先,无论我们是否覆盖 equals()方法,indexOf() 方法都会自动调用 equals() 方法进行“相等”判断!只是,在没有覆盖的情况下,调用的是 Object 类原始的 equals() 方法,其“相等”比较原则是“地址相等”(因为对象的本质是指针!)。
如果我们将 Complex() 类中的 equals() 方法注释起来,这时候在执行上述代码,就会调用 Object 类原始的 equals() 方法,这时候比较的就是首地址是否相等,所以其返回值就是 -1;
3、判断元素是否存在
在数组中判断是否存在某个值,这样的操作也是非常有用的。
ArrayList 与 LinkedList 有专门的实现方法:contains()方法;
public class AboutLinkedList {
public static void main(String[] args) {
Complex c = new Complex(4,2);
LinkedList<Complex> complexLinkedList = new LinkedList();
complexLinkedList.add(new Complex());
complexLinkedList.add(new Complex(1));
complexLinkedList.add(new Complex(4,2));
complexLinkedList.add(new Complex(7,7));
System.out.println(complexLinkedList.contains(c));
}
}
返回值当然为 true 了;
如果这里将 Complex() 中的 equals() 方法注释起来,结果会和上面的 indexOf() 方法相同,原因也一样。
HashMap
前言
我们知道,在现实社会的生活和工作活动由“唯一编号”是应用场景非常广阔的,管理人、事、物的普遍手段。比如我们人人都有身份证号、大学生都有学号、车辆有号牌、图书有图书编号.……这些“唯一性"编号都对应一个(类)唯一的实体。编号和实体之间存在一种关系: 键值对( Key – Value )
(Key-Value)即,键值对,在程序设计中也是被广泛使用的一种数据形式,在Java所提供的众多工具类中,有一类类就是专门为处理“键值对“而设计的。这样的类很多,这里我们关注一个类:HashMap类。
HashMap()
HashMap是一个双泛型类,因为它是处理键值对的,当然要涉及两种数据的类型。
下面程序示例 HashMap的基本使用方法:
public class AboutHashMap {
public static void main(String[] args) {
//初始化 HashMap 对象 nameMap
HashMap<String, String > nameMap = new HashMap<>();
//增加一个键值类型都为 String 类型的键值对
nameMap.put("7076","黄博");
nameMap.put("7077","张三");
nameMap.put("7078","李四");
nameMap.put("7079","王麻子");
nameMap.put("7080","陈大锤");
//根据“7076”的键来获取其对应的值
String name = nameMap.get("7076");
System.out.println(name);
}
HashMap 类与其它类一样,其对象也需要先 new ;HashMap类最常用的方法:put()用来“存储”一个键值对;另一个常用的方法:get()用来根据“键”取出该键对应的值。正如上述程序及其执行结果一样。
使用注意事项
1、键不能重复;
put(键,值)方法内部的大致执行过程是:先查找键是否已经存在;若不存在,添加这个键值对;若已经存在,则用参数提供的值替换原值。
2、值可以重复,甚至为 null;
3、遍历与无序性;
与 ArrayList、LinkedList 不同,HashMap在存储键值对时,没有记录“输入顺序”的信息,所以,键值对在 HashMap 中是无序的。这个结论可以通过对 HashMap 存储的键值对进行遍历查证。
关于 HashMap的遍历,有很多种方法,最常用的是按键遍历;
4、效率问题:
在“进阶工具类”中提出 HashMap 类,是因为 HashMap 不但能处理“键值对“这种在以后高级Java编程中经常使用的存储结构,还因为其性能非常优秀,能大幅度优化程序的时间复杂度!
HashMap在其实现过程中,在键值对数量大于16组后,采用"红黑树”这种高效的数据结构。(红黑树是一种“自平衡二叉查找树”,其插入、删除和查找的最差情况的时间复杂度都是 O(IogN)。对于键值对数据量十分庞大时,其操作的高效性尤为突出!)
HashMap 的遍历
public void showNameMap(HashMap<String, String> nameMap) {
for (String key : nameMap.keySet()) {
String value = nameMap.get(key);
System.out.println("键:" + key + "值:" + value);
}
}
Map 中是无序的。这个结论可以通过对 HashMap 存储的键值对进行遍历查证。
关于 HashMap的遍历,有很多种方法,最常用的是按键遍历;