(六)Java关于内置工具类

Java 内置工具类(String、StringBuffer、LinkedList、ArrayList、HashMap等工具)

​ Java提供了异常丰富的工具类数量巨阳之多,对于 Java学习者而言,通过下面讲解的为数不多的几个类,理解和掌握类的共性,做到举一反三,触类旁通;尤其要学会利用程序编辑器和网络资源,主地查阅相关信息;通过编写具体的例子,验证类及其方法的功用,进而掌握个类,这才是最重要的。

下面分两部分,先讲述String、StringBuffer、StringBuilder、Calendar、数组等基本工具类;然后就要更深入地来了解“泛型”的基本概念,在其基础上来学习LinkedList、ArrayList、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() 方法相同,原因也一样。

Complex() 复数类的实现过程及代码:https://blog.csdn.net/SwaggerHB/article/details/129735124

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的遍历,有很多种方法,最常用的是按键遍历;

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HB0o0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值