day04【常用API2、Collection集合和Iterator迭代器】


day04【常用API2、Collection集合和Iterator迭代器】

主要内容

  • BigInteger类

  • BigDecimal类

  • Arrays类

  • 包装类

  • 泛型

  • Collection集合

教学目标

  • 能够使用日期类输出当前日期
  • 能够使用将日期格式化为字符串的方法
  • 能够使用将字符串转换成日期的方法
  • 能够使用System类的数组复制方法
  • 能够使用System类获取当前毫秒时刻值
  • 能够说出使用StringBuilder类可以解决的问题
  • 能够使用StringBuilder进行字符串拼接操作
  • 能够说出8种基本类型对应的包装类名称
  • 能够说出自动装箱、自动拆箱的概念
  • 能够将字符串转换为对应的基本类型
  • 能够将基本类型转换为对应的字符串
  • 能够说出Collection集合的常用功能
  • 能够使用泛型创建集合对象
  • 能够理解泛型上下限
  • 能够阐述泛型通配符的作用

 

第一章 BigInteger类

1 概述

java.math.BigInteger 类,不可变的任意精度的整数。如果运算中,数据的范围超过了long类型后,可以使用 BigInteger类实现,该类的计算整数是不限制长度的。

 

2 构造方法

方法说明
BigInteger(String value) 可以把字符串整数变成对象

BigInteger(String value) 将 BigInteger 的十进制字符串表示形式转换为 BigInteger。超过long类型的范围,已经不能称为数字了,因此构造方法中采用字符串的形式来表示超大整数,将超大整数封装成BigInteger对象。

 

3 成员方法

BigInteger类提供了对很大的整数进行加、减、乘、除的方法,注意:都是与另一个BigInteger对象进行运算。

方法声明描述
add(BigInteger value) 返回其值为 (this + val) 的 BigInteger,超大整数加法运算
subtract(BigInteger value) 返回其值为 (this - val) 的 BigInteger,超大整数减法运算
multiply(BigInteger value) 返回其值为 (this * val) 的 BigInteger,超大整数乘法运算
divide(BigInteger value) 返回其值为 (this / val) 的 BigInteger,超大整数除法运算,除不尽取整数部分

【示例】

package com.itheima.sh.demo_03;

import java.math.BigInteger;

public class Test01 {
   public static void main(String[] args) {
       //基本类型的整数都有大小限制
       //int a = 10000000000;
       //long b = 10000000000L;

       //BigInteger表示一个无限大的整数
       BigInteger big1 = new BigInteger("23454375347534975347534");
       BigInteger big2 = new BigInteger("23454375347534343433343");
       //加法运算
       BigInteger add = big1.add(big2);
       System.out.println("求和:" + add);
       //减法运算
       BigInteger sub = big1.subtract(big2);
       System.out.println("求差:" + sub);
       //乘法运算
       BigInteger mul = big1.multiply(big2);
       System.out.println("乘积:" + mul);
       //除法运算(java中整数计算的结果还是整数)
       BigInteger div = big1.divide(big2);
       System.out.println("除法:" + div);
  }
}

 

第二章 BigDecimal类

  • 概述:

    表示无限大的小数类型,使用基本类型做浮点数运算精度问题.对于浮点运算,不要使用基本类型,而使用BigDecimal类类型不会损失精度。

  • 构造方法:

方法说明
BigDecimal(double val) 把小数数值变成BigDecimal对象
BigDecimal(String val) 把字符串值变成BigDecimal对象(建议使用)
  • 常用方法:

方法说明
BigDecimal add(BigDecimal value) 加法运算
BigDecimal subtract(BigDecimal value) 减法运算
BigDecimal multiply(BigDecimal value) 乘法运算
BigDecimal divide(BigDecimal value) 除法运算
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) 除法运算

注意:对于divide方法来说,如果除不尽的话,就会出现java.lang.ArithmeticException异常。此时可以使用divide方法的另一个重载方法;

BigDecimal divide(BigDecimal divisor, int scale, int roundingMode): 参数说明:divisor:除数对应的BigDecimal对象;scale:精确的位数;roundingMode取舍模式,表示舍入方式 RoundingMode.HALF_UP四舍五入

  • 示例代码:

    package com.itheima.sh.b_bigdecimal_02;
    import java.math.BigDecimal;
     import java.math.RoundingMode;

     public class BigDecimalDemo01 {
         public static void main(String[] args) {
             method_2();
    }
     
     private static void method_2() {
         /*
           底层原理:例如将0.09*100变为整数9 0.01*100变为整数1然后整数进行运算 ---》9+1=10然后除以100.0---0.10
          */
         //1.创建BigDecimal类的对象
         BigDecimal b1 = new BigDecimal("0.09");
         BigDecimal b2 = new BigDecimal("0.01");
         //2.使用对象调用方法进行运算
         //加法
         BigDecimal b3 = b1.add(b2);
         System.out.println("b3 = " + b3);
       //减法
         System.out.println(b1.subtract(b2));
         //乘法
         System.out.println(b1.multiply(b2));
         //除法 divide 如果除不尽就会报异常 b1/b2--可以除尽
       System.out.println(b1.divide(b2));
     
         //创建对象
         BigDecimal b4 = new BigDecimal("10.0");
       BigDecimal b5 = new BigDecimal("3");
         //这里b4/b5是无法除尽的,所以该方法会报异常
           //       System.out.println(b4.divide(b5));
             //我们对于除不尽使用如下方法
             // 第二个参数2表示保留小数2位,RoundingMode.HALF_UP 表示四舍五入
             BigDecimal b6 = b4.divide(b5, 2, RoundingMode.HALF_UP);
           System.out.println("b6 = " + b6);
        }
         //ctrl+alt+M 抽取方法
     private static void method_1() {
     /*
         1.在计算机中有的小数存在本身就是不精确的,如果小数之间在进行运算那么会导致结果是不准确的
      */
         double d1 = 0.09;
         double d2 = 0.01;
         System.out.println(d2+d1);
         System.out.println(10.0/3);
         System.out.println(1.2+1.3);
    }
    }

小结:Java中小数运算有可能会有精度问题,如果要解决这种精度问题,可以使用BigDecimal

 

第三章 Arrays类

1 Arrays类概述

java.util.Arrays类:该类包含用于操作数组的各种方法(如排序和搜索)。Arrays本身也是一个工具类。工具类不能创建对象(构造方法被private修饰了),所有的方法都是静态的。

2 Arrays类常用方法

  • public static void sort(int[] a):按照数字顺序排列指定的数组

  • public static String toString(int[] a):返回指定数组的内容的字符串表示形式

  • 示例代码:

  public static void main(String[] args) {
     int[] arr = {432, 53, 6, 323, 765, 7, 254, 37, 698, 97, 64, 7};
     //将数组排序
     Arrays.sort(arr);
     //打印数组
     System.out.println(Arrays.toString(arr));
}

打印结果:

[6, 7, 7, 37, 53, 64, 97, 254, 323, 432, 698, 765]

第四章 包装类

1 概述

Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:

基本类型对应的包装类(位于java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

2 Integer类

  • Integer类概述

    包装一个对象中的原始类型 int 的值

  • Integer类构造方法及静态方法

方法名说明
public Integer(int value) 根据 int 值创建 Integer 对象(过时)
public Integer(String s) 根据 String 值创建 Integer 对象(过时)
public static Integer valueOf(int i) 返回表示指定的 int 值的 Integer 实例
public static Integer valueOf(String s) 返回保存指定String值的 Integer 对象
  • 示例代码

 

public class IntegerDemo {
   public static void main(String[] args) {
       //public Integer(int value):根据 int 值创建 Integer 对象(过时)
       Integer i1 = new Integer(100);
       System.out.println(i1);

       //public Integer(String s):根据 String 值创建 Integer 对象(过时)
       Integer i2 = new Integer("100");
//Integer i2 = new Integer("abc"); //NumberFormatException
       System.out.println(i2);
       System.out.println("--------");

       //public static Integer valueOf(int i):返回表示指定的 int 值的 Integer 实例
       Integer i3 = Integer.valueOf(100);
       System.out.println(i3);

       //public static Integer valueOf(String s):返回保存指定String值的Integer对象
       Integer i4 = Integer.valueOf("100");
       System.out.println(i4);
  }
}

 

3 装箱与拆箱

基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:

  • 装箱:从基本类型转换为对应的包装类对象。

    用Integer与 int为例:(看懂代码即可)

    基本数值---->包装对象

    Integer i = new Integer(4);//使用构造函数函数
    Integer iii = Integer.valueOf(4);//使用包装类中的valueOf方法
  • 拆箱:从包装类对象转换为对应的基本类型。

包装对象---->基本数值

int num = i.intValue();

 

4 自动装箱与自动拆箱

由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:

Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。

 

5 基本类型与字符串之间的转换

基本类型转换为String

  • 转换方式

  • 方式一:直接在数字后加一个空字符串

  • 方式二:通过String类静态方法valueOf()

  • 示例代码

public class IntegerDemo {
public static void main(String[] args) {
//int --- String
int number = 100;
//方式1
String s1 = number + "";
System.out.println(s1);
//方式2
//public static String valueOf(int i)
String s2 = String.valueOf(number);
System.out.println(s2);
System.out.println("--------");
}
}

String转换成基本类型

除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型:

  • public static byte parseByte(String s):将字符串参数转换为对应的byte基本类型。

  • public static short parseShort(String s):将字符串参数转换为对应的short基本类型。

  • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。

  • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。

  • public static float parseFloat(String s):将字符串参数转换为对应的float基本类型。

  • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。

  • public static boolean parseBoolean(String s):将字符串参数转换为对应的boolean基本类型。

代码使用(仅以Integer类的静态方法parseXxx为例)如:

  • 转换方式

    • 方式一:先将字符串数字转成Integer,再调用valueOf()方法

    • 方式二:通过Integer静态方法parseInt()进行转换

  • 示例代码

public class IntegerDemo {
public static void main(String[] args) {
//String --- int
String s = "100";
//方式1:String --- Integer --- int
//Integer i = Integer.valueOf(s);
//public int intValue()
//int x = i.intValue();
int x = Integer.valueOf(s);
System.out.println(x);
//方式2
//public static int parseInt(String s)
int y = Integer.parseInt(s);
System.out.println(y);
}
}

注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

 

包装类扩展

package com.itheima.sh.d_integer_04;
/*
fftf
*/
public class IntegerDemo02 {
public static void main(String[] args) {
//1.创建包装类对象
Integer i1 = new Integer(50);
Integer i2 = new Integer(50);
System.out.println(i1==i2);//f

Integer i3 = new Integer(500);
Integer i4 = new Integer(500);
System.out.println(i3==i4);//f

//自动装箱原理:底层执行Integer.valueOf(50);
/*
public static Integer valueOf(int i) {
IntegerCache.low 的值是-128 ,IntegerCache.high 127
if (i >= IntegerCache.low && i <= IntegerCache.high)
//满足if条件到cache数组中获取数据,这里i是50,所以索引是50+128
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
*/
Integer i5 = 50;
Integer i6 = 50;
//底层都会去数组的同一个空间获取数据,所以比较的结果是true
System.out.println(i5==i6);//t

Integer i7 = 500;
Integer i8 = 500;
/*
由于i7的值是500不在-128到127之间,所以执行 return new Integer(i); 创建一个新的对象
由于i8的值是500不在-128到127之间,所以执行 return new Integer(i); 创建一个新的对象
由于创建两个对象,地址值肯定不相等,所以i7==i8的结果是false

*/
System.out.println(i7==i8);//f
}
}

 

 

第五章 引用类型使用小结

实际的开发中,引用类型的使用非常重要,也是非常普遍的。我们可以在理解基本类型的使用方式基础上,进一步去掌握引用类型的使用方式。基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类型也是可以的。在这我们使用两个例子 , 来学习一下。

1 类名作为方法参数和返回值(课下自己看)

public class Person{
public void eat(){
System.out.println("吃饭");
}
}
public class Test{
public static void main(String[] args){
method(new Person());
Person p = createPerson();
}

//引用类型作为方法参数
pubic static void method(Person p){
p.eat();
}

//引用类型作为返回值
public static Person createPerson(){
//当类作为返回值类型时,实际返回的是类的对象
return new Person();
}
}

 

2 抽象类作为方法参数和返回值

  • 抽象类作为形参:表示可以接收任何此抽象类的"子类对象"作为实参;

  • 抽象类作为返回值:表示"此方法可以返回此抽象类的任何子类对象";

/* 定义一个抽象类 */
public abstract class Person{
public void eat(){
System.out.println("吃饭");
}
public abstract void work();
}
/*定义子类Student*/
public class Student extends Person{
public void work(){
System.out.println("学生的工作是学习...");
}
}
public class Teacher extends Person{
public void work(){
System.out.println("老师的工作是教书育人...");
}
}
/*测试类*/
public class Test{
public static void main(String[] args){
//1.调用method1()方法,可以传入一个Student对象,也可以传入一个Teacher对象
method1(new Student());//OK的
method1(new Teacher());//OK的

//2.调用method2()方法,接收的可能是Student对象,也可能是Teacher对象
//所以,这里使用Person类型接收
Person p = method2();
p.work();//如果是Student对象,则调用的是Student的work();如果是Teacher对象,调用的是Teacher的work()。
}
//-------------------------------------------------------------------------------//
//抽象类Person作为形参——可以接收任何它的子类对象
public static void method1(Person p){
p.eat();
p.work();
}

//抽象类Person作为返回值——此方法可以返回一个Student对象,也可以返回一个Teacher对象
public static Person method2(){
// return new Student();//OK的
return new Teacher();//OK的
}

}

 

3 接口作为方法参数和返回值(课下自己看)

  • 接口作为方法的形参:【同抽象类】

  • 接口作为方法的返回值:【同抽象类】

/*定义一个接口*/
public interface USB{
public void run();
}
/*定义子类*/
public class Keyboard implements USB{
public void run(){
System.out.println("使用键盘...");
}
}
public class Mouse implements USB{
public void run(){
System.out.println("使用鼠标...");
}
}
/*定义测试类*/
public class Test{
public static void main(String[] args){
//1.调用method1()方法,需要传入USB的任何子类对象都可以
method1(new Keyboard());
method2(new Mouse());

//2.调用method2()方法,此方法可能返回一个Keyboard对象,也可能返回一个Mouse对象
USB usb = method2();
usb.run();
}
//接口作为形参
public static void method1(USB usb){
usb.run();
}
//接口作为返回值
public static USB method2(){
// return new Keyboard();//OK的
return new Mouse();//OK的
}
}

 

4 类名作为成员变量的数据类型(课下自己看)

我们每个人(Person)都有一个身份证(IDCard) , 为了表示这种关系 , 就需要在Person中定义一个IDCard的成员变量。定义Person类时,代码如下:

class Person {
String name;//姓名
int age;//年龄
}

使用String 类型表示姓名 , int 类型表示年龄。其实,String本身就是引用类型,我们往往忽略了它是引用类型。如果我们继续丰富这个类的定义,给Person 增加身份证号 , 身份证签发机关等属性,我们将如何编写呢?这时候就需要编写一个IDCard类了

定义IDCard(身份证)类,添加身份证号 , 签发地等属性:

class IDCard {
String idNum;//身份证号
String authority;//签发地

//getter和setter方法
//...

//toString方法
//...
}

修改Person类:

public class Person {
String name;//姓名
int age;//年龄

IDCard idCard;//表示自己的身份证信息

//name和age的getter、setter方法
//...

public IDCard getIdCard() {
return idCard;
}

public void setIdCard(IDCard idCard) {
this.idCard = idCard;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", idCard=" + idCard +
'}';
}
}

测试类:

public class TestDemo {
public static void main(String[] args) {
//创建IDCard对象
IDCard idCard = new IDCard();
//设置身份证号
idCard.setIdNum("110113201606066666");
//设置签发地
idCard.setAuthority("上海浦东区公安局");

//创建Person对象
Person p = new Person();
//设置姓名
p.setName("小顺子");
//设置年龄
p.setAge(2);
//设置身份证信息
p.setIdCard(idCard);

//打印小顺子的信息
System.out.println(p);
}
}
输出结果:
Person{name='小顺子', age=2, idCard=IDCard{idNum='110113201606066666', authority='北京市顺义区公安局'}}

类作为成员变量时,对它进行赋值的操作,实际上,是赋给它该类的一个对象。

 

5 抽象类作为成员变量

  • 抽象类作为成员变量——为此成员变量赋值时,可以是任何它的子类对象

/*定义抽象类*/
public abstract class Animal{
public abstract void sleep();
}
/*定义子类*/
public class Cat extends Animal {
String name="一只可爱的小猫";
int age = 2;
public void sleep(){
System.out.println("小猫睡觉...");
}


@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Dog extends Animal {
String name="一只可爱的小狗";
int age = 3;
public void sleep(){
System.out.println("小狗睡觉...");
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/*定义Student类*/
public class Student {
private String name;
private int age;
private Animal animal;//表示学生有一个动物,可以是Cat,也可以是Dog

public Student(String name,int age,Animal animal){
this.name = name;
this.age = age;
this.animal = animal;
}

public Student() {
}

public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
public void setAnimal(Animal animal){
this.animal = animal;
}
public Animal getAnimal(){
return this.animal;
}

public String toString(){
return "Student [name = " + name +
" , age = " + age +
" , animal = " + animal +
"]";
}
}
/*定义测试类*/
public class Test01 {
public static void main(String[] args) {
Student stu = new Student();
stu.setName("章子怡");
stu.setAge(19);
// stu.setAnimal(new Cat());//表示Student有一只猫
stu.setAnimal(new Dog());//表示Student有一只狗

System.out.println(stu);//隐式调用stu.toString(),而stu.toString()中会隐式调用animal的toString().
}
}

 

6 接口作为成员变量(课下自己看)

  • 接口类型作为成员变量——【同抽象类,这里需要学员自己去学习】

/*定义接口*/
public interface Animal{
public abstract void sleep();
}
/*定义子类*/
public class Cat implements Animal{
String name="一只可爱的小猫";
int age = 2;
public void sleep(){
System.out.println("小猫睡觉...");
}


@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Dog implements Animal{
String name="一只可爱的小狗";
int age = 3;
public void sleep(){
System.out.println("小狗睡觉...");
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/*定义Student类*/
public class Student{
private String name;
private int age;
private Animal animal;//表示学生有一个动物,可以是Cat,也可以是Dog

public Student(String name,int age,Animal animal){
this.name = name;
this.age = age;
this.animal = animal;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
public void setAnimal(Animal animal){
this.animal = animal;
}
public Animal getAnimal(){
return this.animal;
}

public String toString(){
return "Student [name = " + name +
" , age = " + age +
" , animal = " + animal +
"]";
}
}
/*定义测试类*/
public class Test{
public static void main(String[] args){
Student stu = new Student();
stu.setName("章子怡");
stu.setAge(19);
// stu.setAnimal(new Cat());//表示Student有一只猫
stu.setAnimal(new Dog());//表示Student有一只狗

System.out.println(stu);//隐式调用stu.toString(),而stu.toString()中会隐式调用animal的toString().
}

}

 

7 引用数据类型使用小结 案例(ppt上面的案例)

【1】需求:

# 1.分别定义含有Player,Mp3Player,IpodNano参数的方法,并调用传入实参进行测试
# 2.定义一个学生类,里面定义含有Player, Mp3Player,IpodNano类型的成员变量,创建学生对象并给成员变量赋值

【2】代码实现:

package com.itheima.sh.k_zongjie_11;

public interface Player {
void play();
}
package com.itheima.sh.k_zongjie_11;

public abstract class Mp3Player implements Player {

}
package com.itheima.sh.k_zongjie_11;

public class IPodNano extends Mp3Player {
@Override
public void play() {
System.out.println("IPodNano 播放音乐~~");
}
}
package com.itheima.sh.k_zongjie_11;

public class Test02 {
public static void main(String[] args) {
IPodNano iPodNano = new IPodNano();
show(iPodNano);
show1(iPodNano);
show2(iPodNano);
}

private static void show(Player player) {
player.play();
}
private static void show1(Mp3Player player) {
player.play();
}
private static void show2(IPodNano player) {
player.play();
}
}

 

package com.itheima.sh.k_zongjie_11;

public class Student {
Player player;
Mp3Player mp3Player;
IPodNano iPodNano;

public Student(Player player, Mp3Player mp3Player, IPodNano iPodNano) {
this.player = player;
this.mp3Player = mp3Player;
this.iPodNano = iPodNano;
}
public void show(){
player.play();
mp3Player.play();
iPodNano.play();
}
}
package com.itheima.sh.k_zongjie_11;

public class Test03 {
public static void main(String[] args) {
Student s = new Student(new IPodNano(),new IPodNano(),new IPodNano());
s.show();
}
}

 

 

 

 

第六章 String类常用方法(只学习没有讲解的)

1 concat

  • 方法原型:public String concat (String str)

  • 功能:将当前字符串与参数字符串进行"拼接",返回一个新字符串。

    String s1 = "Hello";
    String s2 = "World";
    String result = s1.concat(s2);
    System.out.println("result = " + result);
    System.out.println("s1 = " + s1);
    System.out.println("s2 = " + s2);

    concat的作用和+符号的不同:

    1). concat只能拼接String类型,不能拼接其它类型。

    +符号可以拼接任何类型。

    2). concat的效率要高于+符号。

2 contains

  • 方法原型:public boolean contains (CharSequence s)

    CharSequence是一个接口,String类是它的实现类。

  • 功能:判断参数字符串在当前字符串中是否存在(区分大小写)。存在,返回true,否则,返回false。

    String s = "我爱Java,我爱学习!";
    System.out.println("字符串中是否包含Java:" + s.contains("Java"));//true
    System.out.println("字符串中是否包含java:" + s.contains("java"))//false

3 endsWith

  • 方法原型:public boolean endsWith(String suffix)

  • 功能:测试此字符串是否以指定的后缀结尾(区分大小写)。

    String name = "Test.java";
    System.out.println("判断name是否以java结尾:" + name.endsWith("java"));//true
    System.out.println("判断name是否以Java结尾:" + name.endsWith("Java"));//false

4 startsWith

  • 方法原型:public boolean startsWith(String prefix)

  • 功能:测试此字符串是否以指定的前缀开始(区分大小写)

    String name = "我爱Java";
    System.out.println("字符串是否以‘我’开头:" + name.startsWith("我"));//true

5 indexOf

  • 方法原型:public int indexOf(String str)

  • 功能:返回指定子字符串第一次出现的字符串内的索引。如果不包含,则返回-1.

    String str = "我爱Java,我爱学习!";
    System.out.println("在字符串中,Java第一次出现的位置:" + str.indexOf("Java"));//2
    System.out.println("在字符串中,java第一次出现的位置:" + str.indexOf("java"));//-1

6 lastIndexOf

  • 方法原型:public int lastIndexOf(String str)

  • 功能:返回指定子字符串最后一次出现的字符串中的索引。 如果不包含,则返回-1.

    String str = "我爱Java,我爱学习!";
    System.out.println("在字符串中,'我'最后一次出现的位置:" + str.lastIndexOf("我"));//7

7 replace

  • 方法原型:public String replace(CharSequence target,CharSequence replacement)

  • 功能:将与字面目标序列匹配的字符串的每个子字符串替换为指定的文字替换序列。 替换从字符串开始到结束,例如,在字符串“aaa”中用“b”替换“aa”将导致“ba”而不是“ab”。

    String str = "我爱吃红烧鲤鱼,我太想吃红烧鲤鱼了!";
    System.out.println("将'红烧鲤鱼'替换为'咸水鸡':" + str.replace("红烧鲤鱼","咸水鸡"));
    System.out.println("原字符串:" + str);//我爱吃咸水鸡,我太想吃咸水鸡了!

8 substring

  • 方法原型:public String substring(int beginIndex):将当前字符串从beginIndex开始截取到末尾。

  • 方法原型:public String substring(int beginIndex, int endIndex):将当前字符串从beginIndex开始截取到endIndex - 1处。

  • 功能:截取字符串,并将截取后的字符串返回。原字符串不变。

    String str = "我爱Java";
    System.out.println("截取'Java':" + str.substring(2));//Java
    System.out.println("截取'我爱':" + str.substring(0,2))//我爱

9 toCharArray

  • 方法原型:public char[] toCharArray()

  • 功能:将当前字符串转换为char[]数组。

     String str = "I Love Java, I Love Heima";
    //计算字符 a 出现的次数(要求使用toCharArray)
    char[] chars = str.toCharArray();
    int count = 0;
    for (char ch : chars) {
    if (ch == 'a') {
    count++;
    }
    }
    System.out.println("count = " + count);

 

10 charAt

  • 方法原型:public char charAt(int index)

  • 功能:将字符串中指定索引的字符获取

private static void method_1() {
String str = "I Love Java, I Love Heima";
//计算字符 a 出现的次数(要求使用charAt)
int count = 0;
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (ch == 'a') {
count++;
}
}
System.out.println("count = " + count);
}

 

 

10 toLowerCase

  • 方法原型: public String toLowerCase()

  • 功能:将当前字符串中的所有英文字符转换为小写,并返回一个转换后的新字符串,原字符串不变。

    String str = "我爱Java";
    System.out.println("转换为小写:" + str.toLowerCase());//我爱java
    System.out.println("原字符串:" + str);//我爱Java

11 toUpperCase

  • 方法原型:public String toUpperCase()

  • 功能:将当前字符串中的所有英文字符转换为大写,并返回一个转换后的新字符串,原字符串不变。

    String str = "我爱Java";
    System.out.println("转换为大写:" + str.toUpperCase());//我爱JAVA
    System.out.println("原字符串:" + str);//我爱Java

12 trim

  • 方法原型:public String trim()

  • 功能:去掉当前字符串的前后空格,并返回一个新字符串,原字符串不变。

    String str = "  ad  min     ";
    System.out.println("去掉前后空格后|" + str.trim() + "|");//去掉前后空格后|ad min|
    System.out.println("原字符串|" + str + "|");//原字符串| ad min |

13 split

  • 方法原型:public String[] split(String regex)

  • 功能:切割字符串——将字符串以regex作为分隔符进行切割。

    String str = "张三,20,男,13513153355";
    String[] arr = str.split(",");
    for(int i = 0;i < arr.length ; i++){
    System.out.println(arr[i]);
    }

    说明:此方法的参数实际上是一个"正则表达式",功能很强大,后边学到正则表达式时会再次看到此方法。

 

第七章 正则表达式

1正则表达式概念

在开发中,通常很多数据都会使用String类存储。原因:操作字符串的功能比较多,比较方便。在操作String类对象时,会经常遇到对字符串进行验证的功能,例如:年龄必须是2位的数字、用户名必须是8位长度而且只能包含大小写字母、数字等。而按照我们之前学习的String类,我们使用String类中的诸多函数是可以完成对字符串校验功能的,但是代码相对来说比较麻烦,所以在Java中引入正则表达式的概念来解决上述问题,即简化代码。

正则表达式:专门用于操作字符串的技术,并且可以简化代码,用于对字符串的复杂操作。

正则表达式弊端:代码可读性比较差。

2正则表达式引入

  • 需求:验证QQ号码是否合法。

分析:

1、第一位不能是零;

2、QQ号码在5到12之间(包含);

3、QQ号码都是由数字组成;

步骤:

1)定义一个RegexDemo类,在这个类中定义一个主函数main和一个自定义函数method_1;

2)在main函数中调用自定义函数method_1;

3)在自定义函数method_1中定义一个字符串变量QQ,并赋值为12345;

4)使用if-elseIf-else结构对字符串QQ分别按照上述给定的三个条件进行判断;

5)使用String类中的charAt()函数获取字符串QQ中的第一个字符和字符0进行比较,相等则告诉用户不能以0开始;

6)使用String类中的length()函数获得字符串QQ的长度,判断是否在5~12之间;

7)验证字符串QQ中是否都是数字,使用Long.parseLong(QQ)把一个字符串转成 long 类型的数据,此函数容易发生异常,所以使用try-catch代码块对该代码进行捕获异常处理;

package cn.itcast.sh.a_regex_demo;
/*
* 需求:验证QQ号码是否合法。
分析:
1、第一位不能是零;
2、QQ号码在5到12之间(包含);
3、QQ号码都是有数字组成;
*/
public class RegexDemo {
public static void main(String[] args) {
method_1();
}
private static void method_1() {
// 定义一个字符串变量
String QQ = "12345676";
/*
* 使用判断结构判断字符串是否合法
*/
// 判断字符串的第一位是否是0 QQ.charAt(0)表示通过charAt函数根据指定的下标获取下标对应的字符
if (QQ.charAt(0) == '0') {
// 说明字符串以0开始
System.out.println("QQ号码不能以0开始");
} else if (QQ.length() < 5 || QQ.length() > 12) {
// 说明qq的号码的长度不在5~12之间
System.out.println("QQ号码的长度错误");
} else {
/*
* 说明QQ的号码一定不是以0开始,并且长度一定在5~12之间,接下来验证是否为数字
* 使用包装类Long中的parseLong()函数判断字符串中的字符是否为数字
* 如果不为数字,这个函数会抛异常ctrl+alt+T写try-catch代码块
*/
try {
long parseLong = Long.parseLong(QQ);// 将字符串转换为long类型的数字
System.out.println("QQ号码是:" + QQ);
System.out.println("QQ号码是:" + parseLong);
} catch (NumberFormatException e) {
System.out.println("QQ号码中有其他非数字的字符");
}
}
}
}

说明:

1)使用包装类Long中的parseLong()函数判断字符串中的字符是否为数字。如果不为数字,这个函数会抛异常,既然要抛异常所以写个try-catch代码块对异常进行处理;

2)对某块代码进行try-catch处理,可以按ctrl+alt+T快捷键生成try-catch代码块;

 

  • 在开发中字符串是我们使用频率最高的一类数据,针对上述程序仅仅就是为了验证一个字符串中的数据是否正确,用上述代码能够解决问题,但是代码很麻烦。在计算机语言中针对这类数据的验证和其他操作给出了更加简单的处理方案。

    这个方案就是正则表达式。正则表达式它的存在就是用来简化代码的书写,方便完成对字符串的操作。

  • 说明:

    • String类中提供一个boolean matches(String regex) 函数,可以判断字符串对象是否匹配正则表达式。

      • 如果匹配,则返回true;

      • 如果不匹配,则返回false;

    • [1-9]:表示字符串中第一位能够出现1~9任何一个数字;
    • [0-9]{4,11}:表示字符串中从第2位开始后面的数字只能出现0~9之间的数字,并且最少出现4次,最多出现11次;
  • 将上述代码进行优化,结果如下:

package cn.itcast.sh.a_regex_demo;
/*
* 需求:验证QQ号码是否合法。
分析:
1、第一位不能是零;
2、QQ号码在5到12之间(包含);
3、QQ号码都是有数字组成;
*/
public class RegexDemo {
public static void main(String[] args) {
method_2();
}
// 使用正则表达式完成QQ号码的验证
private static void method_2() {
// 定义一个字符串变量
String QQ = "12345";
/*
* String类中提供一个matches()函数,可以判断字符串对象是否匹配正则表达式
* 如果匹配,则返回true
* 如果不匹配,则返回false
* [1-9]:表示字符串中第一位能够出现1~9任何一个数字
* [0-9]{4,11}:表示字符串中从第2位开始后面的数字只能出现0~9之间的数字,并且最少出现4次,最多出现11次
* 如果满足上述条件则返回true,否则返回false
*/
boolean flag = QQ.matches("[1-9][0-9]{4,11}");
System.out.println(flag);
}
}

小结:

1.正则表达式其实就是通过一些符号简化了代码的书写。

2.正则表达式的弊端:符号越多,阅读性越差。所以要想学习正则表达式就得先学习一些符号。

3正则表达式常用符号介绍(掌握)

正则表达式:正确的规则组成的一个表达式。其实就是用来简化字符串的操作。通过一些限定符号组成一种规则,来验证字符串是否符合规则

它的功能主要是用来对字符串进行各种的操作(验证、匹配、切割、替换,获取等)。

结论:正则表达式只能使用在字符串上。

学习正则表达式:主要是学习正则表达式中的常用符号。它是用我们所熟悉的 大括号、中括号 、小括号 、字母 、数字、特殊符号等代替Java代码对字符串进行操作。

在jdk6的api中有正则表达式的符号介绍:

 

我们使用正则表达式其中一个重要的功能就是验证字符串中每个字符是否正确:学习怎么写一个正则表达式去匹配(验证)字符串的每一位。

3.1 字符类

  • 语法示例

[ ]表示范围的意思。表示某一位上可以出现的字符数据,如果正则中需要匹配的某个字符串中某一位上的字符是唯一的,这时可以省略中括号。

1.[abc]  表示要么是a要么是b还可以是c(只能是其中任意一个)
例:”NBA” 正则:”N[ABC]A” 匹配正确:NBA NAA NCA
2.[^abc] 当前要匹配的某个字符串中的某一位上不能是a 或b 或c(除了a,b,c都可以)
3.[a-z] 表示26个小写字母中的任意一个
4.[A-Z] 表示26个大写字母中的任意一个
5.[a-zA-Z] 表示26个大小写字母中的任意一个
6.[a-d[m-p]] 当前要匹配的某个字符串中的某一位上 可以是a - d 或 m - p 的字符
7.[a-d&&[d-f]] 表示只能是d。必须在两个范围中都要符合 。(交集)
8.[a-d&&[^d-f]] 表示只能是a,b,c
9.[a-z&&[^xyz]] 表示只能是除去x,y,z后的所有小写字母
10.[0-9] 表示0~9之间任意数字
11.[a-zA-Z0-9] 表示a-z或者A-Z或者0-9之间的任意一个字符。
  • 代码示例

    1.s变量的值只能是一个字符,且必须是abc中的一个

    2.s变量的值只能是一个字符,且必须是abc以外的字符

3.s变量的值必须是两个字符,且第一个字符是abc中的一个,第二个字符是def中的一个

4.[a-d&&[d-f]] 表示只能是d。必须在两个范围中都要符合 。(交集)

 

    @Test
public void show1() {
String s = "d";

//字符串和正则表达式判断的方法
//1.s变量的值只能是一个字符,且必须是abc中的一个
// boolean b = s.matches("[abc]");//String s = "a";
// System.out.println("b = " + b);//true
//2.s变量的值只能是一个字符,且必须是abc以外的字符
// boolean b = s.matches("[^abc]");//String s = "a";
// System.out.println("b = " + b);//false
//3.s变量的值必须是两个字符,且第一个字符是abc中的一个,第二个字符是def中的一个
// boolean b = s.matches("[abc][def]");// String s = "ad";
// System.out.println("b = " + b);//true
//4.[a-d&&[d-f]] 表示只能是d。必须在两个范围中都要符合 。(交集)
boolean b = s.matches("[a-d&&[d-f]]");//String s = "d";
System.out.println("b = " + b);//true
}

3.2 逻辑运算符

  • 语法示例:

1. &&:并且 上面已经演示过
2. | :或者 举例:[a|b|c]:a或b或c 和[abc]效果是一样的

3.3 预定义字符

  • 语法示例

    1. "." :点 表示当前需要匹配的字符串位置上可以是任意的字符。例:“a.” 以a开始后面可以是任意一个字符  
    2. "\d":表示数字[0-9]
    3. "\D":表示非数字[^0-9] 了解
    4. "\w":单词字符:[a-zA-Z_0-9]的简写
    5. "\W":表示[^a-zA-Z_0-9]表示和\w相反。了解
  • 代码示例

    	@Test
    public void show2() {
    String s = "0";
    //可以匹配一个任何字符
    // boolean b = s.matches(".");// String s = "a";
    // System.out.println("b = " + b);// true
    //可以匹配一个数字
    boolean b1 = s.matches("\\d"); //String s = "0";
    System.out.println("b1 = " + b1);//true
    }

    说明:

    为什么在上述正则表达式中书写\\d,而不是直接书写\d呢?
    \d 代表着正则表达式中的一个符号,\和d放在一起代表0~9的十个数字。一旦将\d书写在””双引号中作为字符串,会出现一个问题,\就会把这个d转义了,一旦转义就不表示\d是一起的了,那怎么解决这种问题呢?
    我们应该在\d前面在加一个\,如:\\d,第一个 \ 表示将第二个 \ 转义成普通的反斜线字符,而变成普通的反斜线之后和d组合就代表着正则中的数字,所以我们需要这样写:”\\d” 等同于”[0-9]”

3.4 数量词

  • 语法示例

    • 注意:数量词前面必须有存在正则符号。

    ? 	 : 表示0次或1次
    * : 表示任意次
    + : 表示1次或多次
    {n} : 必须出现n次
    {n,} : 至少出现n次
    {n,m}: 可以出现n到m次
  • 代码示例

       @Test
    public void show3() {
    String s = "123";
    //出现0次或1次单词字符 [a-zA-Z_0-9]
    // boolean b = s.matches("\\w?");//String s = "0";
    // System.out.println("b = " + b);//true

    //出现一次或多次数字
    // boolean b = s.matches("\\d+");//String s = "0";
    // System.out.println("b = " + b);//true
    //第一位是单词字符,后面出现2到4个数字字符
    boolean b = s.matches("\\w\\d{2,4}"); //a0001-true B12-true 123-true
    System.out.println("b = " + b);//true
    }

 

 

3.5边界词

image-20210314143554278

	 //1.定义字符串
String str = "12345";
// 在正则中 ^ 表示以什么开始 $ 表示以什么结尾
String regex = "^[1-9]\\d{4,14}$";
System.out.println(str.matches(regex));

注意:在java中边界词只能使用在验证方法matches中。

 

4正则的功能介绍(掌握)

正则表达式的主要功能是用来对字符串进行操作:匹配(验证)、切割、替换、获取。

正则表达式需要和String类中的某些函数结合使用。

4.1 切割

  • String类的split()方法原型:

public String[] split(String regex)//参数regex就是一个正则表达式。可以将当前字符串中匹配regex正则表达式的符号作为"分隔符"来切割字符串。
  • 需求1:

    使用String类中的split函数根据正则表达式规则,以叠词对已知的字符串进行切割。

    叠词:就是重复出现的字符。

    1)在RegexDemo2类中定义一个method_2函数;

    2)在method_2函数中定义一个字符串str,并赋值为

    ”sfljs#######lfj234#######k454sd#####sdhd######hf”;

    3)定义一个正则表达式规则:regex=”#+”;

    4)使用定义好的字符串str调用split()函数对正则表达式进行切割;

    5)遍历切割后的数组;

public class RegexDemo2 {
public static void main(String[] args) {
method_2();
}

//以同一个叠词 切割
public static void method_2() {
String str = "sfljs#######lfj234#######k454sd#####sdhd######hf";

String regex = "#{2,}";
String[] split = str.split(regex);

for (int i = 0 ; i<split.length ; i++) {
//sfljs lfj234 k454sd sdhd hf
System.out.print(split[i]+” “);
}
}
  • 需求2:

以叠词对已知字符串”sfljs####lfj234TTTTTTTk454sdOOOOOOOsdhd11111111hf”进行切割。

分析:这个字符串不再像我们之前做过的字符串,他比较特殊,我们之前的叠词都是一样的字符,而这个叠词中的字符都不相同,如果按照我们之前去切割是不能够实现的,那么我们该如何去切割已知的字符串呢?

我们需要借助正则中的来完成。

正则中组的概念:

组:把已经存在的一个正则规则使用小括号封装起来,当在正则表达式中的其他位置上需要使用已经存在的正则规则的时候,这时没有必要再书写重复的规则,而直接去引用被封装好的正则规则。

 

例如:"([a-z_A-Z])bc[a-z_A-Z]"

上述正则表达式:在第一位和第四位上是相同的正则规则,同一正则表达式中不同位置上存在了相同规则的正则,在书写上重复没有必要。我们可以在正则表达式中的第一个位置上把[a-z_A-Z] 这个规则进行封装到一个组中。然后在正则的第四位上需要使用和第一位上相同的规则即可。 这时可以在第四位上引用这个被封装好的组。

在正则中一旦把某个规则使用小括号封装成组之后,由于我们只能使用小括号进行组的封装,而无法给组起名, 这时会自动的给这些组进行编号,组的编号从1开始,一个小括号就是一组。

如果在当前分组的这个正则表达式中引用已经存在的组,需要使用  \\组的编号
例如:"([a-z_A-Z])bc\\1"

完成上述需求:使用String类中的split函数根据正则表达式规则,以叠词对已知的字符串进行切割。(练习正则表达式中的组的概念)

1)在RegexDemo2类中定义一个method_3函数;

2)在method_3函数中定义一个字符串str,并赋值为

”sfljs####lfj234TTTTTTTk454sdOOOOOOOsdhd11111111hf”;

3)定义一个正则表达式规则:regex=”(.)\1+”;

4)使用定义好的字符串str调用split()函数对正则表达式进行切割;

5)遍历切割后的数组;

	@Test
public void show5() {
String str = "sfljs####lfj234TTTTTTTk454sdOOOOOOOsdhd11111111hf";
/*
(.) 表示第一组可以是任意字符
\\1 表示使用第一组
+ 表示\\1的内容至少出现一次
*/
String regex = "(.)\\1+";
String[] arr = str.split(regex);
//[sfljs, lfj234, k454sd, sdhd, hf]
System.out.println(Arrays.toString(arr));
}

小结:

1.

(.) 表示第一组可以是任意字符
\\1 表示使用第一组
+ 表示\\1的内容至少出现一次

2.组的练习

需求1:验证字符串 “哈哈哈”

需求2:验证字符串"哼哈哈哼"

	@Test
public void show6() {
// String s = "哈哈哈";
// //第一对儿()表示第一组,\\1表示再出现一次第一组的内容,{2}表示前面的内容\\1 出现2次
// boolean b = s.matches("(.)\\1{2}"); //String s = "哈哈哈";
// System.out.println("b = " + b);//true

String s = "哼哈哈哼";
//(.)表示第一组是任意字符 (.)表示第二组是任意字符 \\2表示第二组再出现一次 \\1表示第一组再出现一次
//一个组里面可以是任意字符
boolean b = s.matches("(.)(.)\\2\\1");
System.out.println("b = " + b);//true
}

 

4.2 替换

  • String类的replaceAll()方法原型:

    public String replaceAll(String regex,String newStr)//参数regex就是一个正则表达式。可以将当前字符串中匹配regex正则表达式的字符串替换为newStr。
  • 操作字符串:"just do it,suoge521 nothing id impossible99"

    需求1:使用replace方法使用*替换d

String replace(CharSequence oldStr, CharSequence newStr)
参数:
oldStr:不能使用正则,旧的字符串
newStr:新的字符串

需求2:使用replaceAll把所有字符都换成*

public String replaceAll(String regex,String newStr)
也是用新的字符串替换旧的字符串,但是第一个参数可以用正则

需求3:使用replaceAll把每个数字换成*

需求4:使用replaceAll把每段数字换成一个*

 

@Test
public void show7() {
String s = "just do it,suoge521 nothing id impossible99";

// String replace(CharSequence oldStr, CharSequence newStr)
// :用新的字符串newStr代替旧的字符串oldStr

//用*把d都替换掉
// System.out.println(s.replace("d","*"));


/*
public String replaceAll(String regex,String newStr)
也是用新的字符串替换旧的字符串,但是第一个参数可以用正则
*/

//.表示任意字符 在此时把所有字符都换成*
//结果:*******************************************
// System.out.println(s.replaceAll(".","*"));

//把每个数字换成*
//结果:just do it,suoge*** nothing id impossible**
// System.out.println(s.replaceAll("\\d","*"));

//把每段数字换成一个*
//结果:just do it,suoge* nothing id impossible*
System.out.println(s.replaceAll("\\d+","*"));
}

 

 

 

 

第八章 Collection集合

1 集合概述

在前面我们已经学习过并使用过集合ArrayList<E> ,那么集合到底是什么呢?

  • 集合:集合是java中提供的一种容器,可以用来存储多个数据。

集合和数组既然都是容器,它们有什么区别呢?

  • 数组的长度是固定的。集合的长度是可变的。

  • 数组中存储的是同一类型的元素,可以定义任意类型的数组存储任意类型数据。集合存储的都是引用数据类型。如果想存储基本类型数据需要存储对应的包装类型。

存储整数:int[]       存储字符串:String[]
存储整数:ArrayList<Integer> 存储字符串:ArrayList<String>

 

2 集合常用类的继承体系

Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.Listjava.util.Set。其中,List的特点是元素存取有序、元素可重复 ; Set的特点是元素存取无序,元素不可重复List接口的主要实现类有java.util.ArrayListjava.util.LinkedListSet接口的主要实现类有java.util.HashSetjava.util.LinkedHashSet

从上面的描述可以看出JDK中提供了丰富的集合类库,为了便于初学者进行系统地学习,接下来通过一张图来描述集合常用类的继承体系

image-20201130211717763

注意:这张图只是我们常用的集合有这些,不是说就只有这些集合。

集合本身是一个工具,它存放在java.util包中。在Collection接口定义着单列集合框架中最最共性的内容。

 

3 Collection 常用功能

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

  • public boolean add(E e): 把给定的对象添加到当前集合中 。

  • public void clear() :清空集合中所有的元素。

  • public boolean remove(E e): 把给定的对象在当前集合中删除。

  • public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。

  • public boolean isEmpty(): 判断当前集合是否为空。

  • public int size(): 返回集合中元素的个数。

  • public Object[] toArray(): 把集合中的元素,存储到数组中

tips: 有关Collection中的方法可不止上面这些,其他方法可以自行查看API学习。

public class Demo02ListMethod {
public static void main(String[] args) {
//多态写法
Collection<String> c = new ArrayList<>();

//boolean add(E e)
//添加方法(添加成功会返回true,添加失败会返回false.但是ArrayList只会成功不会失败所以返回永远是true,不接受这个返回值)
c.add("石原里美");
c.add("新垣结衣");
c.add("石原里美");

//void clear()
//清空集合中的元素
//c.clear(); //调用后集合被清空

//boolean remove(Object e)
//删除集合中的某个元素(返回的是true或false代表删除成功或失败,只会删除第一个匹配的元素)
c.remove("柳岩"); //没有柳岩,删除没作用
c.remove("石原里美"); //集合中有两个“石原里美”,会删除第一个

//boolean contains(Object obj)
//判断集合是否包含某个元素
boolean b = c.contains("石原里美");
System.out.println(b); //集合中包含有“石原里美”这个元素所以结果是true


//boolean isEmpty()
//判断集合是否为空
boolean b2 = c.isEmpty();
System.out.println(b2); //集合里面有元素,不为空 结果是false

//int size()
//获取集合的长度
int size = c.size();
System.out.println(size); //2

//Object[] toArray()
//把集合转成Object[]类型
Object[] arr = c.toArray();
//打印数组
System.out.println(Arrays.toString(arr));

//如果想要转成具体的类型,用到了一个高级写法,我现在可以给演示一下,不讲原理
String[] strs = c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(strs));
}
}

第九章 Iterator迭代器

1 Iterator接口

在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator

想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:

方法说明
public Iterator iterator() 获取集合对应的迭代器,用来遍历集合中的元素的。

下面介绍一下迭代的概念:

  • 迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

    注意:调用next()方法获取数据之前一定先调用hasNext()方法判断有没有有数据,否则会报异常。

Iterator接口的常用方法如下:

方法说明
E next() 获取集合中的元素
boolean hasNext() 判断集合中有没有下一个元素,如果仍有元素可以迭代,则返回 true。
void remove() 删除当前元素

接下来我们通过案例学习如何使用Iterator迭代集合中元素:

代码如下:

public class Demo02 {
public static void main(String[] args) {
// 使用多态方式 创建对象
Collection<String> coll = new ArrayList<String>();
// 添加元素到集合
coll.add("aaaa");
coll.add("bbbb");
coll.add("cccc");
//根据当前集合对象生成迭代器对象
Iterator<String> it = coll.iterator();
//获取数据
System.out.println(it.next());
System.out.println(it.next());
System.out.println(it.next());
System.out.println(it.next());
}
}

运行结果:

aaaa
bbbb
cccc
Exception in thread "main" java.util.NoSuchElementException
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:896)
at com.itheima.sh.demo_03.Demo02.main(Demo02.java:22)

分析异常的原因:

说明:当迭代器对象指向集合时,可以获取集合中的元素,如果迭代器的光标移动集合的外边时,此时迭代器对象不再指向集合中的任何元素,会报NoSuchElementException没有这个元素异常。

解决方案:在使用next()函数获取集合中元素前,使用hasNext()判断集合中是否还有元素。

上述代码一条语句重复执行多次,我们可以考虑使用循环来控制执行的次数,

循环条件是 迭代器对象.hasNext() 为false时表示集合中没有元素可以获取了,循环条件迭代器对象.hasNext()  为true的时候说明还可以获取元素。

代码演示如下:

package cn.itcast.sh.iterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/*
* 演示迭代器的使用
*/
public class IteratorDemo {
public static void main(String[] args) {
//创建集合对象
Collection coll=new ArrayList();
//向集合中添加数据
coll.add("aaaa");
coll.add("bbbb");
coll.add("cccc");
//根据当前集合获取迭代器对象
Iterator it = coll.iterator();
//取出数据
/*System.out.println(it.next());//it.next()表示获取迭代器对象指向集合中的数据
System.out.println(it.next());
System.out.println(it.next());
System.out.println(it.next());*/
//使用while循环遍历集合
while(it.hasNext())//it.hasNext()表示循环条件,如果为true,说明集合中还有元素可以获取,否则没有元素
{
//获取元素并输出
System.out.println(it.next());
}
}
}

tips:

1.使用while循环迭代集合的快捷键:itit

2.在进行集合元素获取时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会抛出java.util.NoSuchElementException没有集合元素异常。

 

2 迭代器的实现原理

我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。

Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

1)ctrl + N   搜索  ArrayList这个类
2)在ArrayList里面按 alt + 7 左边点击Itr
3)Itr是ArrayList里面的一个内部类。(这些东西不要求研究)
  • 源码

public class ArrayList<E>{
//Itr属于ArrayList集合的成员内部类,实现了迭代器Iterator接口
private class Itr implements Iterator<E> {
//定义游标,默认值是0
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
/*
1.第一个 modCount 集合结构变动次数,如:一开始add调用了4次,那么这个变量就是4
2.第二个 expectedModCount表示期望的更改次数 在调用iterator()方法时,初始化值等于modCount ,初始化时也是4,这里 int expectedModCount = modCount;
*/
int expectedModCount = modCount;
//判断集合是否有数据的方法,如果有数据返回true,没有返回false
//size表示集合长度,假设添加3个数据,那么size的值就是3
public boolean hasNext() {
/*
判断游标变量cursor是否等于集合长度size,假设向集合中存储三个数据那么size等于3
1.第一次cursor的默认值是0 size是3 --》cursor != size --》0!=3-->返回true
*/
return cursor != size;
}

@SuppressWarnings("unchecked")
public E next() {
//主要作用是判断it迭代器数据是否和list集合一致 我们下面会讲解
checkForComodification();
//每次调用一次next方法都会将游标cursor的值赋值给变量i
int i = cursor;
//判断i是否大于等于集合长度size,如果大于等于则报NoSuchElementException没有这个元素异常
if (i >= size)
throw new NoSuchElementException();
//获取ArrayList集合的数组
Object[] elementData = ArrayList.this.elementData;
//判断i的值是否大于等于数组长度,如果为true则报并发修改异常
if (i >= elementData.length)
throw new ConcurrentModificationException();
/*
将i的值加1赋值给游标cursor.就是将游标向下移动一个索引,可以理解指针向下移动一次
*/
cursor = i + 1;
/*
elementData就是集合的底层数组,获取索引i位置的元素返回给调用者
*/
return (E) elementData[lastRet = i];
}
}
}
  • 讲解图

3迭代器的问题:并发修改异常

  • 异常:

    ConcurrentModificationException(并发修改异常)
  • 产生原因:

    • 在迭代器遍历的同时如果使用集合对象修改集合长度(增或删)就会出现并发修改异常。

  • 代码演示:

    package com.itheima.sh.demo_04;

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;

    public class Test01 {
    public static void main(String[] args) {
    Collection<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("ddd");
    list.add("bbb");
    list.add("ccc");
    Iterator<String> it = list.iterator();
    while(it.hasNext()){
    String s = it.next();
    if("ddd".equals(s)){
    //使用集合调用方法删除
    list.remove(s);
    }
    }
    System.out.println(list);
    }
    }

     

  • 解决办法:

    • 使用集合增加或删除都会出现并发修改异常。

    • 在Iterator迭代器中,有删除方法可以避免异常。但是他没有增加的方法。

    public class Test01 {
    public static void main(String[] args) {
    Collection<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("ddd");
    list.add("bbb");
    list.add("ccc");
    Iterator<String> it = list.iterator();
    while(it.hasNext()){
    String s = it.next();
    if("ddd".equals(s)){
    //使用集合调用方法删除
    // list.remove(s);
    //使用迭代器的删除方法
    it.remove();
    }
    }
    System.out.println(list);
    }
    }

     

     

  • 在迭代器中有特殊的子类可以做元素的增加,这个类是ArrayList里面的内部类,叫ListItr.

    因为以后也不用,所以现在不讲了,但是简单扩展一下告诉你有这个东西,以后万一有兴趣大家自己学一下。

 

4 增强for

增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。

格式:

for(元素的数据类型  变量 : Collection集合or数组){ 
//写操作代码
}

它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。

代码演示

public class NBForDemo1 {
public static void main(String[] args) {
int[] arr = {3,5,6,87};
//使用增强for遍历数组
for(int a : arr){//a代表数组中的每个元素
System.out.println(a);
}

Collection<String> coll = new ArrayList<String>();
coll.add("小河神");
coll.add("老河神");
coll.add("神婆");

for(String s :coll){
System.out.println(s);
}
}
}

 

tips:

1.快捷键: 1)数组/集合.for 2)iter

2.增强for循环必须有被遍历的目标,目标只能是Collection或者是数组;

3.增强for(迭代器)仅仅作为遍历操作出现,不能对集合进行增删元素操作,否则抛出ConcurrentModificationException并发修改异常

4.如果需要使用索引,也不能使用增强for

 

小结:Collection是所有单列集合的根接口,如果要对单列集合进行遍历,通用的遍历方式是迭代器遍历或增强for遍历。

 
posted @ 2023-02-15 16:09  忘了鱼尾纱的猫  阅读(12)  评论(0)    收藏  举报