- 一、File类
- lambda表达式
- 二、JAVA -IO
- 1、文件流:
- 2、高级流
- 3、对象流
- 4、字符流
- 5、缓冲字符流
- IO总结
- 四、网络编程
- 五、多线程
- 线程:一个顺序的单一的程序执行流程就是一个线程。代码一句一句的有先后顺序的执行。
- 多线程:多个单一顺序执行的流程并发运行。造成"感官上同时运行"的效果。
- 六、线程API
- 七、多线程并发安全问题
- 八、集合框架
-
File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径)
使用File可以做到:
-
1:访问其表示的文件或目录的属性信息,例如:名字,大小,修改时间等等。
-
2:创建和删除文件或目录。
-
3:访问一个目录中的子项。
但是File不能访问文件数据。
-
-
**创建一个新文件:**createNewFile()方法,可以创建一个新文件。
-
**删除一个文件:**delete()方法可以将File表示的文件删除。
-
创建目录:
- mkDir():创建当前File表示的目录。
- mkDirs():创建当前File表示的目录,同时将所有不存在的父目录一同创建。
-
**删除目录:**delete()方法可以删除一个目录,但是只能删除空目录。
-
**访问一个目录中的所有子项:**listFiles()方法可以访问一个目录中的所有子项。
-
获取目录中符合特定条件的子项:
- 重载的listFiles方法:File[] listFiles(FileFiter)
- 该方法要求传入一个文件过滤器,并仅将满足该过滤器要求的子项返回。
- JDK8之后,java支持了lambda表达式这个特性。
- lambda可以用更精简的代码创建匿名内部类,但是该匿名内部类实现的接口只能有一个抽象方法,否则无法使用。
- lambda表达式是编译器认可的,最终会将其改为内部类编译到class文件中。
- lambda表达式的参数类型可以忽略不写。
- lambda表达式方法体中若只有一局代码,则{}可以省略,如果这句话有return关键字,那么return也要一并省略。
java将IO比喻为"流",即:stream. 就像生活中的"电流",“水流"一样,它是以同一个方向顺序移动的过程.只不过这里流动的是字节(2进制数据).所以在IO中有输入流和输出流之分,我们理解他们是连接程序与另一端的"管道”,用于获取或发送数据到另一端.
-
java将IO按照方向划分为输入与输出:
- 输入:用来读取数据的,是从外界到程序的方向,用于获取数据。
- 输出:用来写出数据的,是从程序到外界的方向,用于发送数据。
-
JAVA定义了两个超类(抽象类):
-
**java.io.InputStream:**所有字节输入流的超类,其中定义了读取数据的方法.因此将来不管读取的是什么设备(连接该设备的流)都有这些读取的方法,因此我们可以用相同的方法读取不同设备中的数据。
-
**java.io.OutputStream:**所有字节输出流的超类,其中定义了写出数据的方法。
-
-
JAVA将流分为两类:节点流与处理流:
- **节点流:**也称为低级流,节点流的另一端是明确的,是实际读写数据的流,读写一定是建立在节点流基础上进行的。
- **处理流:**也称为高级流,处理流不能独立存在,必须连接在其他流上,目的是当数据流经当前流时对数据进行加工处理来简化我们对数据的该操作。
实际应用中,我们可以通过串联一组高级流到某个低级流上以流水线式的加工处理对某设备的数据进行读写,这个过程也称为流的连接,这也是IO的精髓所在。
1、文件流:文件流:java.io.FileInputStream和FileOutputStream是一对低级流,用于读写文件数据的流,用于连接程序与文件(硬盘)的"管道"。负责读写文件数据。
-
**文件输出流:**java.io.FileOutputStream【写】
- 文件流创建时,如果该文件不存在会自动将其创建(前提是该文件所在目录必须存在!)
- void write(int d):向文件中写入1个字节,写入的内容是给定的int值对应的2进制的"低八位"
-
**文件输入流:**FileInputStream【读】
- int read():读取一个字节,并以int型返回。返回的整数中读取的字节部分在该整数2进制的最后8位上
-
块读写:
- **块读:**int read(byte[] data):一次性将给定的字节数组所有字节写入到文件中
- **块写:**void write(byte[] data):一次性将给定的字节数组从下标offset处开始的连续len个字节写入文件
-
写文本数据:
-
String提供方法:byte[] getByte(String charsetName) 将当前字符串转换为一组字节
参数为字符集的名字,常用的是UTF-8。其中中文3字节表示1个,英文1字节表示1个。
-
-
文件输出流—追加模式:
-
重载的构造方法可以将文件输出流创建为追加模式
-
FileOutputStream(String path,boolean append)
-
FileOutputStream(File file,boolean append)
以上两个方法:当第二个参数传入true时,文件流为追加模式,即:指定的文件若存在,则原有数据保留,新写入的数据会被顺序的追加到文件中
-
-
-
读取文本数据:
- String提供了将字节数组转换为字符串的构造方法:
- String(byte[]data,String charsetName): 将给定的字节数组中所有字节按照指定的字符集转换为字符串
- **String(byte[]data,int offset,int len,String charsetName):**将给定的字节数组从下标offset处开始的连续len个字节按照指定的字符集转换为字符串
- String提供了将字节数组转换为字符串的构造方法:
-
缓冲流:java.io.BufferedOutputStream和java.io.BufferedInpputStream。
- 缓冲流是一对高级流,作用是提高读写数据的效率。
- 缓冲流内部有一个字节数组,默认长度是8K。缓冲流读写数据时一定是将数据的读写方式转换为块读写来保证读写效率。
-
缓冲输出流写出数据时的缓冲区问题:
-
通过缓冲流写出的数据会被临时存入缓冲流内部的字节数组,直到数组存满数据才会真实写出一次。
-
缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。
- 注:该方法实际上实在字节输出流的超类OutputStream上定义的,并非只有缓冲输出流有这个方法。但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现该方法的目的仅仅是为了在流连接过程中传递flush动作给缓冲输出流。
-
- java.io.ObjectOutputStream和ObjectInputSteam
- 对象流是一对高级流,在流连接中的作用是进行对象的序列化与反序列化。
- 对象序列化:将一个java对象按照其结构转换为一组字节的过程。
- 对象反序列化:将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节)
- 对象反序列化:
- 需要进行序列化的类必须实现接口:java.io.Serializable
- 实现序列化接口后最好主动定义序列化版本号这个常量。
- 这样一来对象序列化时就不会根据类的结构生成一个版本号,而是使用该固定值。
- 那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。
- transient关键字可以修饰属性,用于在进行对象序列化时忽略不必要的属性,达到对象瘦身的目的
Java将按照读写单位划分为字节流与字符流,java.io.Reader和java.io.Writer则是所有字符流的超类,它们和字节流的超类是平级关系
-
java.io.Reader(字符输入流)和java.io.Writer(字符输出流)是两个抽象类,里面规定了所有字符流都必须具备的读写字符的相关方法。
-
字符流最小读写单位为字符(char),但是底层实际还是读写字节,只是字符与字节的转换工作由字符流完成。
-
转换流:java.io.InputStreamReader和OutputStreamWriter
- 它们是字符流非常常用的一对实现类同时也是一对高级流,实际开发中我们不直接操作它们,但是它们在流连接中是非常重要的一环.
- 使用转换输出流向文件中写出文本数据
- 转换输出流将写出的字符按照字符集转换为字节。(高级流,字符流)
- 文件输出流向文件中写入字节。(低级流,字节流)
- 使用转换输入流从文件中读取文本数据
- 文件输入流从文件中读取所有文字(返回值实际上是一个char的低16位,返回是-1表示读完了)。
- 转换输入流将读取的数据按照字符集转换为字符。
-
在流连接中的使用:
FileOutputStream fos = new FileOutputStream("pw2.txt",true); //转换输出流(是一个高级流,且是一个字符流)。1:衔接字符与字节流 2:将写出的字符转换为字节 OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); //缓冲输出流(是一个高级流,且是一个字符流)。块写文本数据加速 BufferedWriter bw = new BufferedWriter(osw); //具有自动行刷新的缓冲字符输出流 PrintWriter pw = new PrintWriter(bw); -
转换流的意义:
- 实际开发中我们还有功能更好用的字符高级流。但是其他的字符高级流都有一个共通点:不能直接连接在字节流上。而实际操作设备的流都是低级流同时也都是字节流,因此不能直接在流连接中串联起来。转换流是一对可以连接在字节流上的字符流,其他的高级字符流可以连接在转换流上。在流连接中起到"转换器"的作用(负责字符与字节的实际转换)。
-
缓冲字符输出流:java.io.PrintWriter
- java.io.BufferedWriter
- 缓冲字符流内部也有一个缓冲区,读写文本数据以块读写形式加快效率。并且缓冲流有一个特别的功能:可以按行读写文本数据。
- java.io.PrintWriter具有自动行刷新的缓冲字符输出流,实际开发中更常用。它内部总是会自动连接BufferedWriter作为块写加速使用。
- 提供的方法:
- **pw.println(String String ):**向文件中写入字符串。
- PrintWriter的自动行刷新功能:
- **PrintWriter pw = new PrintWriter(bw,true):**如果实例化PW时当第一个参数为一个流时,就支持再传入一个boolean 型的参数表示是否自动行刷新。当该值为true时则打开了自动行刷新功能。这意味着每当我们调用println方法后会自动flush一次。
-
缓冲字符输入流:java.io.BufferedReader
- 是一个高级的字符流,特点是块读文本数据,并且可以按行读取字符串。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PTViQ6OI-1653996530137)(C:Users1QQPCMgrDesktop传奇笔记IO.png)]
##三、异常处理
-
java异常处理机制:
- java中所有错误的超类为:Throwable。其下有两个子类:Error和Exception。
- Error的子类描述的都是系统错误,比如虚拟机内存溢出等。
- Exception的子类描述的都是程序错误,比如空指针,下表越界等。
- 通常我们程序中处理的异常都是Exception。
-
异常处理机制中的try-catch:
-
语法:
try{ 可能出现异常的代码片段 }catch(XXXException e){ try中出现XXXException后的处理代码 } -
try语句块不能独立存在,后面必须跟catch语句块或finally语句块。
-
若try语句块中某句话出错了,则剩下的代码都不会执行!
-
-
异常处理机制中的finally:
- finally块定义在异常处理机制中的最后一块。它可以直接跟在try之后,或者最后一个catch之后。
- finally可以保证只要程序执行到了try语句块中,无论try语句块中的代码是否出现异常,最终finally都必定执行。
- finally通常用来做释放资源这类操作。
-
自动关闭特性:
-
JDK7之后,java提供了一个新的特性:自动关闭。旨在IO操作中可以更简洁的使用异常处理机制完成最后的close操作。
-
语法:
语法: try( 定义需要在finally中调用close()方法关闭的对象. ){ IO操作 }catch(XXXException e){ ... } -
上述语法中可在try的"()"中定义的并初始化的对象必须实现了java.io.AutoCloseable接口,否则编译不通过。
-
-
throw关键字:
-
throw用来对外主动抛出一个异常,通常下面两种情况我们主动对外抛出异常:
-
当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者。
-
程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时可以抛出给调用者。
public void setAge(int age) throws Exception { if(age<0||age>100){ //使用throw对外抛出一个异常 throw new RuntimeException("年龄不合法!"); } this.age = age; }
-
-
-
throws关键字:
- 当一个方法中使用throw抛出一个非RuntimeException的异常时,就要在该**方法上使用throws声明这个异常的抛出。**此时调用该方法的代码就必须处理这个异常,否则编译不通过。
- 当我们调用一个含有throws声明异常抛出的方法时,编译器要求我们必须处理这个异常,否则编译不通过。 处理手段有两种:
- 使用try-catch捕获并处理这个异常。
- 在当前方法上继续使用throws声明该异常的抛出给调用者解决。 具体选取那种取决于异常处理的责任问题。
- 注意:永远不应当在main方法上使用throws!!
-
含有throws的方法被子类重写时的规则:
- 可以不再抛出任何异常。
- 可以仅抛出部分异常。
- 可以抛出超类方法抛出异常的子类型异常。
- 不允许抛出额外异常(超类方法中没有的,并且没有继承关系的异常)。
- 不可以抛出超类方法抛出异常的超类型异常
-
Java异常可以分为可检测异常,非检测异常:
- **可检测异常:**可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或声明规则,不捕捉这个异常,编译器就通不过,不允许编译。
- **非检测异常:**非检测异常不遵循处理或者声明规则。在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异常。
- **RuntimeException 类属于非检测异常,**因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制。
-
常见的RuntimeException子类:
- **IllegalArgumentException:**抛出的异常表明向方法传递了一个不合法或不正确的参数。
- **NullPointerException:**当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
- **ArrayIndexOutOfBoundsException:**当使用的数组下标超出数组允许范围时,抛出该异常。
- **ClassCastException:**当试图将对象强制转换为不是实例的子类时,抛出该异常。
- **NumberFormatException:**当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
-
异常中常用的方法
catch (NumberFormatException e) { //异常最常用的方法,用于将当前错误信息输出到控制台 e.printStackTrace(); //获取错误消息.记录日志的时候或提示给用户可以使用它 String message = e.getMessage(); System.out.println(message); } -
自定义异常:
- **自定义异常通常用来定义那些业务上的异常问题。**定义自定义异常需要注意以下问题:
- 异常的类名要做到见名知义
- 需要是Exception的子类(继承Exception)
- 提供超类异常提供的所有种类构造器
- **自定义异常通常用来定义那些业务上的异常问题。**定义自定义异常需要注意以下问题:
-
总结:
- 异常处理机制是用来处理那些可能存在的异常,但是无法通过修改逻辑完全规避的场景。
- 如果通过修改逻辑可以规避的异常是bug,不应当用异常处理机制在运行期间解决!应当在编码时及时修正
-
java.net.Socket:(套接字)
- Socket(套接字)封装了TCP协议的通讯细节,使得我们使用它可以与服务端建立网络链接,并通过 它获取两个流(一个输入一个输出),然后使用这两个流的读写操作完成与服务端的数据交互
- 实例化Socket时要传入两个参数:
- 参数1:服务端的地址信息:可以是IP地址,如果链接本机可以写"localhost"
- 参数2:服务端开启的服务端口我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上的服务端应用程序。
- 实例化的过程就是链接的过程,如果链接失败会抛出异常:java.net.ConnectException: Connection refused: connect
-
java.net.ServerSocket
-
ServerSocket运行在服务端,作用有两个:
- 向系统申请服务端口,客户端的Socket就是通过这个端口与服务端建立连接的。
- 监听服务端口,一旦一个客户端通过该端口建立连接则会自动创建一个Socket,并通过该Socket与客户端进行数据交互。
-
实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他应用程序占用的端口相同,否则会抛出异常:java.net.BindException:address already in use端口是一个数字,取值范围:0-65535之间。
6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
-
ServerSocket提供了接受客户端链接的方法:Socket accept()
- 这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例通过这个Socket就可以与客户端进行交互了。
如果我们把Socket比喻为电话,那么ServerSocket相当于是某客服中心的总机。
-
-
客户端与服务端完成第一次通讯(发送一行字符串)
- Socket提供了两个重要的方法:
- **OutputStream() getOutputStream():**该方法会获取一个字节输出流,通过这个输出流写出的字节数据会通过网络发送给对方。
- **InputStream getInputStream():**通过该方法获取的字节输入流读取的是远端计算机发送过来的数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HLxosWWw-1653996530139)(C:Users1QQPCMgrDesktop传奇笔记1650329529897.png)]
- Socket提供了两个重要的方法:
-
需要注意的几个点:
- 当客户端不再与服务端通讯时,需要调用socket.close()断开链接,此时会发送断开链接的信号给服务端。这时服务端的br.readLine()方法会返回null,表示客户端断开了链接。
- 当客户端链接后不输入信息发送给服务端时,服务端的br.readLine()方法是出于阻塞状态的,直到读取了一行来自客户端发送的字符串。
-
并发:
-
多个线程实际运行是走走停停的。线程调度程序会将CPU运行时间划分为若干个时间片段并尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。当超时后线程调度
程序会再次分配一个时间片段给一个线程使得CPU执行它。如此反复。由于CPU执行时间在
纳秒级别,我们感觉不到切换线程运行的过程。所以微观上走走停停,宏观上感觉一起运行
的现象成为并发运行!
-
-
用途:
- 当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程上"同时"运行
- 一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行
-
创建线程有两种方式:
-
**方式一:继承Thread并重写run方法:**定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。
**注:**启动该线程要调用该线程的start方法,而不是run方法!!!
- **优点:**结构简单,利于匿名内部类形式创建。
- 缺点:
- 由于java是单继承的,会导致不能在继承其他类去复用方法,这在实际开发中是非常不便的。
- 定义线程的同时重写了run方法,会导致线程与线程任务绑定在了一起,不利于线程的重用。
-
**方式二:**实现Runnable接口单独定义线程任务,也要重写run方法。
以上两种方式都可以用匿名内部类创建
-
java中的代码都是靠线程运行的,执行main方法的线程称为“主线程”
- **static Thread currentThread():**该方法可以获取运行这个方法的线程
-
-
获取线程相关信息的方法:
- Thread main = Thread.currentThread();//获取主线程
- String name = main.getName();//获取线程的名字
- long id = main.getId();//获取该线程的唯一标识
- int priority = main.getPriority();//获取该线程的优先级
- boolean isAlive = main.isAlive();//该线程是否活着
- boolean isDaemon = main.isDaemon();//是否为守护线程
- boolean isInterrupted = main.isInterrupted();//是否被中断了
-
线程优先级:
线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程.
-
线程有10个优先级,使用整数1-10表示
- 1为最小优先级,10为最高优先级.5为默认值。
- 调整线程的优先级可以最大程度的干涉获取时间片的几率.优先级越高的线程获取时间片的次数越多,反之则越少。
-
-
sleep阻塞:
- **static void sleep(long ms):**使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行。
- sleep方法处理异常:**InterruptedException:**当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞。
-
**守护线程:**也称为后台线程
- 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异。
- 守护线程的结束时机上有一点与普通线程不同,即:进程的结束。
- 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程。
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪.
临界资源:操作该资源的全过程同时只能被单个线程完成
-
**static void yield():**线程提供的这个静态方法作用是让执行该方法的线程主动放弃本次时间片。
-
synchronized关键字:
- 在方法上修饰,此时该方法变为一个同步方法。
- 在方法上修饰,此时该方法变为一个同步方法。
-
**同步方法:**当一个方法使用synchronized修饰后,这个方法称为"同步方法",即:多个线程不能同时在方法内部执行。将多个线程并发操作临界资源的过程改为同步操作就可以有效的解决多线程并发 安全问题。相当于让多个线程从原来的抢着操作改为排队操作。
-
**同步块:**有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率。同步块可以更准确的控制需要多个线程排队执行的代码片段。
-
语法:
synchronized(同步监视器对象){ 需要多线程同步执行的代码片段 } -
同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个。
-
-
**在静态方法上使用synchronized:**当在静态方法上使用synchronized后,该方法是一个同步方法。由于静态方法所属类,所以一定具有同步效果。
静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象
-
**互斥锁:**当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的。当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时,这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。
-
死锁:两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。这个现象就是死锁。
- 解决死锁:
- 尽量避免在持有一个锁的同时去等待持有另一个锁(避免synchronized嵌套)。
- 当无法避免synchronized嵌套时,就必须保证多个线程锁对象的持有顺序必须一致。即:A线程在持有锁1的过程中去持有锁2时,B线程也要以这样的持有顺序进行。
- 解决死锁:
-
**什么是集合:**集合与数组一样,可以保存一组元素,并且提供了操作元素的相关方法,使用更方便。
-
**java集合框架中相关接口:java.util.Collection接口:**java.util.Collection是所有集合的顶级接口,Collection下面有多种实现类,因此我们有更多的数据结构可供选择。Collection下面有两个常见的子接口:
-
java.util.List:线性表,是可重复集合,并且有序。
-
java.util.Set:不可重复的集合,大部分实现类是无序的。
这里可重复指的是集合中的元素是否可以重复,而判定重复元素的标准是依靠元素自身equals比较的结果,为true就认为是重复元素。
-
-
集合常见API:
- **boolean add(E e):**向当前集合中添加一个元素.当元素成功添加后返回true。
- **int size():**返回当前集合的元素个数。
- **boolean isEmpty():**判断当前集合是否为空集(不含有任何元素)。
- **clear():**清空集合。
- **boolean contains(Object o):**判断当前集合是否包含给定元素,这里判断的依据是给定元素是否与集合现有元素存在equals比较为true的情况。
- **remove(value):**用来从集合中删除给定元素,删除的也是与集合中equals比较为true的元素。注意,对于可以存放重复元素的集合而言,只删除第一次出现的。
-
集合存放的是元素的引用:集合只能存放引用类型元素,并且存放的是元素的引用。
-
**集合间的操作:**集合提供了如取并集,删交集,判断包含子集等操作
- **boolean addAll(Collection c):**将给定集合中的所有元素添加到当前集合中。当前集合若发生了改变则返回true。
- **boolean containsAll(Collection c):**判断当前集合是否包含给定集合中的所有元素。
- **boolean removeAll(Collection c):**删除当前集合中与给定集合中的共有元素。
-
**集合的遍历:**Collection提供了统一的遍历集合方式:迭代器模式 ——Iterator iterator():该方法会获取一个用于遍历当前集合元素的迭代器。
-
**java.util.Iterator接口:**迭代器接口,定义了迭代器遍历集合的相关操作。规定了迭代器遍历集合的相关操作,不同的集合都提供了一个用于遍历自身元素的迭代器实现类,不过我们不需要直到它们的名字,以多态的方式当成Iterator使用即可。迭代器遍历集合遵循的步骤为:问->取->删。其中删除不是必须操作。
-
**迭代器遍历过程中不得通过集合的方法增删元素:**迭代器提供的相关方法:
-
**boolean hasNext():**判断集合是否还有元素可以遍历。
-
**E next():**获取集合下一个元素(第一次调用时就是获取第一个元素,以此类推)。
**注意:**迭代器要求遍历的过程中不得通过集合的方法增删元素否则会抛出异常:ConcurrentModificationException。迭代器的remove方法可以将通过next方法获取的元素从集合中删除。
-
-
-
**增强型for循环:**JDK5之后推出了一个特性:增强型for循环,也称为新循环,使得我们可以使用相同的语法遍历集合或数组.
- 语法:
for(元素类型 变量名 : 集合或数组的名称){ 循环体 } -
**泛型:**JDK5之后推出的另一个特性:泛型。泛型也称为参数化类型,允许我们在使用一个类时指定它当中属性,方法参数或返回值的类型。
- 泛型在集合中被广泛使用,用来指定集合中的元素类型。
- 有泛型支持的类在使用时若不指定泛型的具体类型,则默认为原型Object。
- 迭代器也支持泛型,指定的与其遍历的集合指定的泛型一致即可。
-
**List集合:**java.util.List接口,继承自Collection。List集合是可重复集,并且有序,提供了一套可以通过下标操作元素的方法。常用实现类:
- java.util.ArrayList:内部使用数组实现,查询性能更好。
- java.util.LinkedList:内部使用链表实现,首尾增删元素性能更好。
-
List集合常见方法:
- **E get(int index):**获取指定下标对应的元素。
- **E set(int index,E e):**将给定元素设置到指定位置,返回值为该位置原有的元素。
- **void add(int index,E e):**将给定元素插入到指定位置。
- **E remove(int index):**删除并返回指定位置上的元素。
- **List subList(int start,int end):**获取当前集合中指定范围内的子集。两个参数为开始与结束的下标(含头不含尾)。
-
集合与数组的转换:
-
**集合转换为数组:**Collection提供了一个方法:toArray,可以将当前集合转换为一个数组。
-
数据转换为List集合:数组的工具类Arrays提供了一个静态方法asList(),可以将一个数组转换为一个List集合。
**注意:**对数组转换的集合进行元素操作就是对原数组对应的操作。由于数组是定长的,因此对该集合进行增删元素的操作是不支持的,会抛出异常:java.lang.UnsupportedOperationException。
-
-
**集合的排序:java.util.Collection类:**Collections是集合的工具类,里面定义了很多静态方法用于操作集合。
-
**Collections.sort(List list)方法:**可以对List集合进行自然排序(从小到大)。
-
排序自定义类型元素:Collections.sort(List list)该方法要求集合中的元素类型必须实现接口:Comparable,该接口中有一个抽象方法compareTo,这个方法用来定义元素之间比较大小的规则。所以只有实现了该接口的元素才能利用这个方法比较出大小进而实现排序操作。
**注意:实际开发中,我们并不会让我们自己定义的类(如果该类作为集合元素使用)去实现Comparable接口,因为这对我们的程序有侵入性。**侵入性:当我们调用某个API功能时,其要求我们为其修改其他额外的代码,这个现象就是侵入性。侵入性越强的API越不利于程序的后期可维护性,应当尽量避免。
-
可以用匿名内部类重写方法compare
-
-
**排序字符串:**java中提供的类,如:String,包装类都实现了Comparable接口,但有时候这些比较规则不能满足我们的排序需求时,同样可以临时提供一种比较规则来进行排序。



