Java高级编程
- 一、多线程
- 二、常用类
- 三、枚举与注解
- 四、Java集合
- 五、 泛型
- 六、IO流
- 七、网络编程
- 八、反射机制
- 九、Java8 的其它新特性
一、多线程
1、基本概念
1.1 程序、进程、线程
程序:(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一
段静态的代码,静态对象。
进程:(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
- 如:运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程:(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患
1.2 单核CPU和多核CPU的理解
- 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
- 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
- 一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
1.3 并行与并发
并行:多个CPU同时执行多个任务。
比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。
比如:秒杀、多个人做同一件事。
1.4 使用多线程的优点
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?多线程程序的优点:
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
2、线程的创建和使用
2.1 线程的创建和启动
- Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。
- Thread类的特性
1.每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体。
2.通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。
2.2 Thread类
构造器:
Thread()
:创建新的Thread对象Thread(String threadname)
:创建线程并指定线程实例名Thread(Runnable target)
:指定创建线程的目标对象,它实现了Runnable接口中的run方法Thread(Runnable target, String name)
:创建新的Thread对象
2.3 API中创建线程的两种方式
JDK1.5之前创建新执行线程有两种方法:
- 继承Thread类的方式
- 实现Runnable接口的方式
方式一:继承Thread类
1)定义子类继承Thread类。
2)子类中重写Thread类中的run方法。
3)创建Thread子类对象,即创建了线程对象。
4)调用线程对象start方法:启动线程,调用run方法
下面展示一些:
package com.tianfeng.exer;
/**
* @author 丰
* @create 2021-08-14 16:18
*/
public class ThreadDemo {
public static void main(String[] args) {
//3.创建Thread子类对象
MyTest1 t1 = new MyTest1();
MyTest2 t2 = new MyTest2();
//4.调用线程对象start方法
t1.start();
t2.start();
}
}
//2.定义子类继承Thread类
class MyTest1 extends Thread{
//子类中重写Thread类中的run方法
@Override
public void run() {
for (int i = 0 ;i<=100 ;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
//2.定义子类继承Thread类
class MyTest2 extends Thread{
//子类中重写Thread类中的run方法
@Override
public void run() {
for (int i = 0 ;i<=100 ;i++){
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
也可以通过创建匿名子对象实现对象的创建:
package com.tianfeng.exer;
/**
* @author 丰
* @create 2021-08-14 16:18
*/
public class ThreadDemo {
public static void main(String[] args) {
MyTest1 t1 = new MyTest1();
MyTest2 t2 = new MyTest2();
t1.start();
t2.start();
//创建匿名子类
new Thread(){
@Override
public void run() {
for (int i = 0 ;i<=100 ;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
//创建匿名子类
new Thread(){
@Override
public void run() {
for (int i = 0 ;i<=100 ;i++){
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
}
}
部分运行结果展示:两个进程同时运行。
注意:
- 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
- run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
- 想要启动多线程,必须调用start方法。
- 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。
方式二:实现Runnable接口
- 定义子类,实现Runnable接口。
- 子类中重写Runnable接口中的run方法。
- 通过Thread类含参构造器创建线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
package com.tianfeng.test;
/**
* @author 丰
* @create 2021-08-14 18:23
*/
public class WindowTest1 {
public static void main(String[] args) {
//3.通过Thread类含参构造器创建线程对象
Mteat m = new Mteat();
Thread W1 = new Thread(m);
Thread W2 = new Thread(m);
Thread W3 = new Thread(m);
//4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中
W1.setName("窗口1");
W2.setName("窗口2");
W3.setName("窗口3");
//5.调用Thread类的start方法
W1.start();
W2.start();
W3.start();
}
}
//1.定义子类,实现Runnable接口
class Mteat implements Runnable{
private int i = 100;
//2.子类中重写Runnable接口中的run方法
@Override
public void run() {
while (true){
if (i>0){
System.out.println(Thread.currentThread().getName() +":买票。票号为:"+i);
i--;
}
else
break;
}
}
}
2.4 继承方式和实现方式的联系与区别
public class Thread extends Object implements Runnable
- 区别
- 继承Thread:**线程代码存放Thread子类run方法中。
- 实现Runnable:线程代码存在接口的子类的run方法。
- 实现方式的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线
程来处理同一份资源。
2.5 Thread类的有关方法
方法 | Value |
---|---|
void start() | 启动线程,并执行对象的run()方法 |
run() | 线程在被调度时执行的操作 |
String getName() | 返回线程的名称 |
void setName(String name) | 设置该线程名称 |
static Thread currentThread() | 返回当前线程。在Thread子类中就是this,通常用于主线程Runnable实现类 |
static void yield() | 线程让步暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程若队列中没有同优先级的线程,忽略此方法 |
join() | 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止低优先级的线程也可以获得执行 |
static void sleep(long millis) | (指定时间:毫秒)令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。抛出InterruptedException异常 |
stop() | 强制线程生命期结束,不推荐使用 |
|返回boolean,判断线程是否还活着
3、线程的生命周期
3.1 JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
- 阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
- 死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
3.2 线程的生命周期
线程的生命周期:
线程状态转换图:
4、线程的同步
4.1 问题的提出
- 多个线程执行的不确定性引起执行结果的不稳定
- 多个线程对数据的共享,会造成操作的不完整性,会破坏数据。
4.2 Synchronized的使用方法
窗口买票问题:
以窗口买票为例,三个窗口同时进行卖票,相当于三个线程,一共有100张票,三个窗口同时卖票,之前的方法 (之前的实现代码)会出现票号重复,票号为0或者负数等不安全问题。
- 原因:当一个窗口操作车票过程中,尚未完成操作上,其他线程参与进来,也操作车票。
- 解决:当操作一个线程时,其他线程不能参与进来,直到这个线程操作完成,其他线程才能进来,即使该线程出现阻塞也不能被改变。
- 在java中我们通过同步机制来解决线程安全问题。
方式一:同步代码块
//同步监视器也被称为“锁”
synchronized(同步监视器){
//需要被同步的代码(操作共享数据的代码)
}
说明:
- 操作共享数据代码,需要被同步的代码,不能包多也不能包少
- 共享数据:多个线程共同操作的变量
- 同步监视器:锁。任何一个类的对象都可以充当同步监视器,多个线程必须要共用同一把锁。
实现类创建线程:
package com.tianfeng.tongbu;
/**
* @author 丰
* @create 2021-08-14 18:23
*/
public class WindowTest1 {
public static void main(String[] args) {
Mteat m = new Mteat();
Thread W1 = new Thread(m);
Thread W2 = new Thread(m);
Thread W3 = new Thread(m);
W1.setName("窗口1");
W2.setName("窗口2");
W3.setName("窗口3");
W1.start();
W2.start();
W3.start();
}
}
class Mteat implements Runnable{
private int i = 100;
Object obj =new Object();
@Override
public void run() {
while (true){
//synchronized(this)
synchronized(obj){//也可以不要obj,用this也可以继承则不行如下面的继承实现方法。
if (i>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +":买票。票号为:"+i);
i--;
}
else
break;
}
}
}
}
注意:
在实现Runnable接口创建的多线程中,我们可以使用this充当同步监视器。
部分结果展示:
继承创建线程实现方法:
package com.tianfeng.tongbu;
/**
* @author 丰
* @create 2021-08-14 17:50
*/
class Window extends Thread{
private static int ticket = 100;
static Object obj = new Object();
@Override
public void run() {
while (true){
//synchronized(Window.class):Class clazz= Window.class只会加载一次,这个方法现在只需了解,不用深究。
synchronized (obj){
if(ticket > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":买票,票号为:"+ticket);
ticket--;
}
else
break;
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
注意:
在继承Thread类创建的多线程中药慎用this充当同步监视器,可以考虑用当前类充当同步监视器。
部分结果展示:
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,不妨将此方法声明为同步。
- 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
- 非静态的同步方法:this。静态同步方法:当前类本身。
使用同步方法解决实现Runnable接口的线程安全:
package com.tianfeng.tongbu;
/**
* @author 丰
* @create 2021-08-14 18:23
*/
public class WindowTest3 {
public static void main(String[] args) {
Window3 m = new Window3();
Thread W1 = new Thread(m);
Thread W2 = new Thread(m);
Thread W3 = new Thread(m);
W1.setName("窗口1");
W2.setName("窗口2");
W3.setName("窗口3");
W1.start();
W2.start();
W3.start();
}
}
class Window3 implements Runnable{
private int i = 100;
@Override
public synchronized void run() {
while (true){
Show();
}
}
private synchronized void Show(){
if (i>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +":买票。票号为:"+i);
i--;
}
}
}
使用同步方法解决继承Thread类的线程安全问题:
package com.tianfeng.tongbu;
/**
* @author 田丰
* @create 2021-08-15 11:59
*/
class Window4 extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
private static synchronized void show(){
if(ticket > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":买票,票号为:"+ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
4.3 线程的死锁问题
死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
4.4 Lock(锁)
- 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码;
}finally{
lock.unlock();
}
}
}
package com.tianfeng.sisuo;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 田丰
* @create 2021-08-15 13:02
*/
class Window implements Runnable{
private int ticket = 100;
//1、实例化Lock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try{
//2、锁定
lock.lock();
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +":买票。票号为:"+ticket);
ticket--;
}
else {
break;
}
}finally {
//3、调用解锁
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
4.5 synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
练习:
银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?
【提示】
1,明确哪些代码是多线程运行代码,须写入run()方法
2,明确什么是共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
package com.tianfeng.exer;
/**
* @author 丰
* @create 2021-08-15 13:21
*/
class Account {
private double balance;
public Account(double balance){
this.balance=balance;
}
//存钱
public synchronized void deposit(double amt){
if (amt>0){
balance+=amt;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存钱成功"+"余额为:"+balance);
}
}
}
class Customer extends Thread{
private Account acct;
public Customer(Account acct){
this.acct = acct;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
acct.deposit(1000);
}
}
}
public class AccountTest {
public static void main(String[] args) {
Account acct = new Account(0);
Customer cust1 = new Customer(acct);
Customer cust2 = new Customer(acct);
cust1.setName("甲");
cust2.setName("乙");
cust1.start();
cust2.start();
}
}
5、线程的通信
- wait() 与 notify() 和 notifyAll()
- wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
- 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
- 因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明
例 题:
使用两个线程打印 1-100。线程1, 线程2 交替打印
package com.tianfeng.tongxin;
/**
* @author 丰
* @create 2021-08-15 15:19
*/
class Number implements Runnable{
private int number = 1;
//Object obj =new Object();
@Override
public void run() {//(obj)
while (true) {
synchronized (this) {
notify();//this.notify;obj.notify();
if (number <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else
break;
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number n = new Number();
Thread t1 = new Thread(n);
Thread t2 = new Thread(n);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
5.1 wait() 方法、notify()/notifyAll()
wait():
- 在当前线程中调用方法: 对象名.wait()
- 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。
- 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
- 调用此方法后,当前线程将释放对象监控权 ,然后进入等待
- 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。
notify()/notifyAll():
- 在当前线程中调用方法: 对象名.notify()
- 功能:唤醒等待该对象监控权的一个/所有线程。
- 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
5.2 经典问题:生产者/消费者问题
6、JDK5.0新增线程创建方式
6.1 新增方式一:实现Callable接口
Future接口
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- FutrueTask是Futrue接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
package com.tianfeng.exer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author 丰
* @create 2021-08-15 16:25
*/
class NumThread implements Callable{
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
NumThread n = new NumThread();
FutureTask f = new FutureTask(n);
Thread t = new Thread(f);
t.start();
try {
Object o = f.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
6.2 新增方式二:使用线程池
待更新。。。。。
二、常用类
1、字符串相关的类
1.1 String类
1.1.1 String的特性
- String类:代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
- String是一个final类,代表不可变的字符序列。
- 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
- String对象的字符内容是存储在一个字符数组value[]中的。
1.1.2 String对象的创建
//本质上this.value = new char[0];
String s1 = new String();
//this.value = original.value;
String s2 = new String(String original);
//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startIndex,int count);
- String s1 = “a”;
说明:在字符串常量池中创建了一个字面量为"a"的字符串。 - s1 = s1 + “b”;
说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s1+“b”(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。 - String s2 = “ab”;
说明:直接在字符串常量池中创建一个字面为"ab"的字符串。 - String s3 = “a” + “b”;
说明:s3指向字符串常量池中已经创建的"ab"的字符串。 - String s4 = s1.intern();
说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串
赋值给s4。
1.1.3 字符串的特性
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
- 只要其中有一个是变量,结果就在堆中
- 如果拼接的结果调用intern()方法,返回值就在常量池中
测试:
@Test
public void test3(){
String s1="javaee";
String s2="hadoop";
String s3 = "javahadoop";
String s4 = "javaee"+"hadoop";
String s5=s1+"hadoop";
String s6 = "javaee"+s2;
String s7 =s1+s2;
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
System.out.println(s5 == s4);
System.out.println(s5 == s7);
System.out.println(s6 == s7);
String s8 = s5.intern();
System.out.println(s4 == s8);
}
结果展示:
1.1.4 字符串相关的类:String常用方法
- int length():返回字符串的长度: return value.length
- char charAt(int index): 返回某索引处的字符return value[index]
- boolean isEmpty():判断是否是空字符串:return value.length == 0
- String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
- String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
- String trim():返回字符串的副本,忽略前导空白和尾部空白
测试:
@Test
public void test(){
String s1 = "Hello";
System.out.println(s1.length());
System.out.println(s1.charAt(0));
System.out.println(s1.isEmpty());
System.out.println(s1.toLowerCase());
System.out.println(s1.toUpperCase());
System.out.println(s1);
String s2=" He ll o ";
System.out.println(s2.trim());
}
结果展示:
- boolean equals(Object obj):比较字符串的内容是否相同
- boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
- String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
- int compareTo(String anotherString):比较两个字符串的大小
- String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
- String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
测试:
@Test
public void test1(){
String s1 = "hello";
String s2 = "Hello";
System.out.println(s1.equals(s2));
System.out.println(s1.equalsIgnoreCase(s2));
String s3 = "abc";
System.out.println(s3.concat("123"));
String s4 = new String("abd");
System.out.println(s3.compareTo(s4));
System.out.println(s1.substring(2));
System.out.println(s1.substring(1, 4));
}
结果展示:
- boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
- boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
- boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
测试:
@Test
public void test2(){
String s1= "helloworld";
System.out.println(s1.endsWith("rld"));
System.out.println(s1.startsWith("he"));
System.out.println(s1.startsWith("11", 2));
}
结果展示:
还有很多方法,就不一一测试了:
- boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
- int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
- int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
- int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
- int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1 - String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
- String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
- String replaceAll(String regex, String replacement) : 使 用 给 定 的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
- String replaceFirst(String regex, String replacement) : 使 用 给 定 的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
- boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
- String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
- String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
1.1.5 字符串相关的类:String与基本数据类型转换
- String–>基本数据类型,包装类:
Integer.parseXxx(Sting str) - 基本数据类型–>String:String.valueOf(int num)
/*
String与基本数据类型的转换,包装类。
String-->基本数据类型,包装类:Integer.parseXxx(Sting str)
基本数据类型-->String:String.valueOf(int num)
*/
@Test
public void test1(){
String s1 = "123";
int num = Integer.parseInt(s1);//String-->int:123
String s2 = String.valueOf(num);//int-->String:"123"
String s3 = num + "";//int-->String:"123"
System.out.println(s1 == s3);
}
1.1.6 字符串相关的类:String与字符数组转换
- String–>char[]:调用String的toCharArray()
- char[]–>String:调用String构造器
/*
String与char[]之间的转换
String-->char[]:调用String的toCharArray()
char[]-->String:调用String构造器
*/
@Test
public void test2(){
String s1 = "abc123";
char[] arr = s1.toCharArray();
for (int i = 0; i <arr.length ; i++) {
System.out.println(arr[i]);
}
char[] arr1 = new char[]{'h','e','l','l','o'};
String s2 = new String(arr1);
System.out.println(s2);
}
1.1.7 字符串相关的类:String与字节数组转换
- 编码:String–>byte[]:调用String的getBytes()
- 解码: byte[]–>String:调用String的构造器
测试:
/*
String与byte[]之间的转换
编码:String-->byte[]:调用String的getBytes()
解码: byte[]-->String:调用String的构造器
*/
@Test
public void test3() throws UnsupportedEncodingException {
String s1 = "abc123中国";
byte[] b =s1.getBytes();//utf-8编码
System.out.println(Arrays.toString(b));
byte[] gbks=s1.getBytes("gbk");//gbk编码
System.out.println(Arrays.toString(gbks));
String s2 = new String(b);//utf-8解码默utf-8编码的字符
System.out.println(s2);
String s3 = new String(gbks);//utf-8解码gbk编码的字符、出现乱码。
System.out.println(s3);//编码集和解码集不一致
String s4 = new String(gbks, "gbk");//gbk解码gbk编码的字符
System.out.println(s4);
}
结果展示:
1.2 StringBuffer
1.2.1 StringBuffer类
StringBuffer类不同于String,其对象必须使用构造器生成。有三个构造器:
- StringBuffer():初始容量为16的字符串缓冲区
- StringBuffer(int size):构造指定容量的字符串缓冲区
- StringBuffer(String str):将内容初始化为指定字符串内容
/*
String、Stringbuffer、StringBuilder三者异同
String:不可变的字符序列:底层使用char[]存储
StringBuffer:可变的字符序列:线程安全,效率低;底层使用char[]存储
StringBuilder:可变的字符序列:JDK5.0新增的,线程不安全,效率高;底层使用char[]存储
源码分析
*/
@Test
public void test1(){
StringBuffer sb1 = new StringBuffer("abc");
//初始化数组容量为16。char[] value = new char["abc".length+16]
//value[0] = 'a';
//value[1] = 'b';
//value[2] = 'c';
sb1.setCharAt(0,'m');
//value[0] = 'm';
System.out.println(sb1.length());
System.out.println(sb1);
sb1.append('a');//value[3] = 'a';
sb1.append('b');//value[4] = 'b';
sb1.append('c');//value[5] = 'c';
System.out.println(sb1.length());
System.out.println(sb1);
String s1 = new String();//char[] value = new char[0]
String s2 = new String("abc");//char[] value = new char[]{a,b,c}
}
1.2.2 StringBuffer类的常用方法
- StringBuffer append(xxx): 提供了很多的append()方法,用于进行字符串拼接
- StringBuffer delete(int start,int end): 删除指定位置的内容
- StringBuffer replace(int start, int end, String str): 把[start,end)位置替换为str
- StringBuffer insert(int offset, xxx): 在指定位置插入xxx
- StringBuffer reverse() : 把当前字符序列逆转
测试:
@Test
public void test2(){
StringBuffer s1 = new StringBuffer("abc");
s1.append(1);
s1.append('1');
System.out.println(s1);//abc11
// s1.delete(2,4);
// System.out.println(s1);//ab1
// s1.replace(2,4,"hello");
// System.out.println(s1);//abhello1
// s1.insert(2,false);
// System.out.println(s1);//abfalsec11
// s1.reverse();
// System.out.println(s1);//11cba
//单独测试
}
结果展示:在代码注释中。
1.3 StringBuilder
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样
面试题:对比String、StringBuffer、StringBuilder
- String(JDK1.0):不可变字符序列
- StringBuffer(JDK1.0):可变字符序列、效率低、线程安全
- StringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全
注意: 作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。
效率比较:
@Test
public void test3(){
//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));
// System.out.println(buffer);
// System.out.println(builder);
// System.out.println(text);
}
2、JDK8之前的日期时间API
2.1 java.lang.System类
- System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
- 此方法适于计算时间差。
测试:
//System类中的System.currentTimeMillis()
@Test
public void test1(){
long time = System.currentTimeMillis();
System.out.println(time);
//System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
//时间戳
}
结果展示:
2.2 java.util.Date类
表示特定的瞬间,精确到毫秒
构造器
- Date():使用无参构造器创建的对象可以获取本地当前时间。
- Date(long date)
常用方法
方法 | Value |
---|---|
getTime() | 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数 |
toString() | 把此 Date 对象转换为以下形式的String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准 |
测试:
/*
java.util.Date
1、两个构造器的使用
>构造器2:创建指定毫秒数的Date对象
>构造器1:Date():创建一个对应当前时间的Date对象
2、两个方法的使用
>toString():
>getTime():
3、对应着数据库中的日期类型的变量
*/
@Test
public void test2(){
//构造器1:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//Mon Aug 16 16:58:45 CST 2021
System.out.println(date1.getTime());//1629104410125
//构造器2:创建指定毫秒数的Date对象
Date date2 = new Date(1629104410125L);
System.out.println(date2.toString());
System.out.println(date2.getTime());
//创建java.sql.Date类的对象
java.sql.Date date3 = new java.sql.Date(1629104410125L);
System.out.println(date3.toString());
System.out.println(date3.getTime());
//Date-->java.sql.Date
Date d1 = new Date();
java.sql.Date d3 = new java.sql.Date(d1.getTime());
System.out.println(d3);
}
结果展示:
2.3 java.text.SimpleDateFormat类
- Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
- 它允许进行格式化:日期文本、解析:文本日期
格式化:
- SimpleDateFormat() :默认的模式和语言环境创建对象
- public SimpleDateFormat(String pattern):该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用:
- public String format(Date date):方法格式化时间对象date
解析:
- public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期。
测试:
package com.feng.java;
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author 田丰
* @create 2021-08-17 11:10
*/
public class DateTimeTest {
/*
SimpleDateFormat:对日期Date类的格式化和解析
1、两个操作
1.1、格式化:日期-->字符串
1.2、解析:格式化的逆过程,字符串-->日期
2、实例化
*/
@Test
public void testSimpleDateFormat() throws ParseException {
//实例化:使用默认
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化
Date date = new Date();
System.out.println(date);
String format = sdf.format(date);
System.out.println(format);
//解析
String str = "2021-8-10 上午12:12";
Date date1 = sdf.parse(str);
System.out.println(date1);
//设置格式:"yyyyy.MMMMM.dd GGG hh:mm aaa"。javaAPI中还有很多格式。
SimpleDateFormat format1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
System.out.println(format1.format(date));
}
}
结果展示:
注意:解析时,要求字符串必须是符合格式的字符串。
练习一:字符串“2020-11-11”转换为java.sql.Date
@Test
public void test() throws ParseException {
String birth = "2020-11-11";
SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd");
Date date = sim.parse(birth);
java.sql.Date date1 = new java.sql.Date(date.getTime());
System.out.println(date1);
}
练习二:1990-01-01起:三天打鱼两天晒网。问xxxx-xx-xx是打鱼还是晒网?
如:202-09-08?总天数
总天数%5 == 1、2、3:打鱼
总天数%5 == 0、4:晒网
总天数:总毫秒数/(10006060*24)+1–>总天数
2.4 java.util.Calendar(日历)类
1. Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能
2. 获取Calendar实例的方法
- 使用Calendar.getInstance()方法
- 调用它的子类GregorianCalendar的构造器。
3. 一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND
- public void set(int field,int value)
- public void add(int field,int amount)
- public final Date getTime()
- public final void setTime(Date date)
注意:
- 获取月份时:一月是0,二月是1,以此类推,12月是11
- 获取星期时:周日是1,周二是2 , 。。。。周六是7
@Test
public void testCa(){
Calendar calendar = Calendar.getInstance();
// 从一个 Calendar 对象中获取 Date 对象
Date date = calendar.getTime();
// 使用给定的 Date 设置此 Calendar 的时间
date = new Date(234234235235L);
calendar.setTime(date);
calendar.set(Calendar.DAY_OF_MONTH, 8);
System.out.println("当前时间日设置为8后,时间是:" + calendar.getTime());
calendar.add(Calendar.HOUR, 2);
System.out.println("当前时间加2小时后,时间是:" + calendar.getTime());
calendar.add(Calendar.MONTH, -2);
System.out.println("当前日期减2个月后,时间是:" + calendar.getTime());
}
3、JDK8中新的日期时间API
3.1 新时间日期API
项目 | Value |
---|---|
ava.time | 包含值对象的基础包 |
java.time.chrono | 提供对不同的日历系统的访问 |
java.time.format | 格式化和解析时间和日期 |
java.time.temporal | 包括底层框架和扩展特性 |
java.time.zone | 包含时区支持的类 |
3.2 LocalDate、LocalTime、LocalDateTime
- LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
- LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储 生日、纪念日等日期。
- LocalTime表示一个时间,而不是日期。
- LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。
方法 | 描述 |
---|---|
now() / * now(ZoneId zone) | 静态方法,根据当前时间创建对象/指定时区的对象 |
of() | 静态方法,根据指定日期/时间创建对象 |
getDayOfMonth()/getDayOfYear() | 获得月份天数(1-31) /获得年份天数(1-366) |
getDayOfWeek() | 获得星期几(返回一个 DayOfWeek 枚举值) |
getMonth() | 获得月份, 返回一个 Month 枚举值 |
getMonthValue() / getYear() | 获得月份(1-12) /获得年份 |
getHour()/getMinute()/getSecond() | 获得当前对象对应的小时、分钟、秒 |
withDayOfMonth()/withDayOfYear()/withMonth()/withYear() | 将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象 |
plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours() | 向当前对象添加几天、几周、几个月、几年、几小时 |
minusMonths() / minusWeeks()/minusDays()/minusYears()/minusHours() | 从当前对象减去几月、几周、几天、几年、几小时 |
/*
LocalDate、LocalTime、LocalDateTime
*/
@Test
public void test1(){
//now():获取当前日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
//of():设置指定的年月日时分秒。没有偏移量
LocalDateTime of = localDateTime.of(2020, 10, 10,13,23,12);
System.out.println(of);
}
3.3 瞬时:Instant
方法 | 描述 |
---|---|
now() | 静态方法,返回默认UTC时区的Instant类的对象 |
ofEpochMilli(long epochMilli) | 静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象 |
atOffset(ZoneOffset offset) | 结合即时的偏移来创建一个 OffsetDateTimetoEpochMilli() |
/*
Instant的使用
*/
@Test
public void test2(){
//now()获取本初子午线的时间
Instant now = Instant.now();
System.out.println(now);
//添加偏移量
OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
OffsetDateTime offsetDateTime1 = Instant.now().atOffset(ZoneOffset.ofHours(9));
System.out.println(offsetDateTime1);
//toEpochMilli():返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳
long l = now.toEpochMilli();
System.out.println(l);
//ofEpochMilli(long epochMilli) 静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒
Instant instant = Instant.ofEpochMilli(1629189780849L);
System.out.println(instant);
}
3.4 DateTimeFormatter
java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:
- 预定义的标准格式。如:
ISO_LOCAL_DATE_TIME;
ISO_LOCAL_DATE;
ISO_LOCAL_TIME - 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
- 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
方 法 | 描 述 |
---|---|
ofPattern(String pattern) | 静态方法 , 返 回 一 个 指 定 字 符 串 格 式 的 |
DateTimeFormatterformat(TemporalAccessor t) | 格式化一个日期、时间,返回字符串 |
parse(CharSequence text) | 将指定格式的字符序列解析为一个日期、时间 |
3.5 其它API
- ZoneId:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris
- ZonedDateTime:一个在ISO-8601日历系统时区的日期时间,如 2007-12-03T10:15:30+01:00 Europe/Paris。
- 其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如:Asia/Shanghai等
- Clock:使用时区提供对当前即时、日期和时间的访问的时钟。
- 持续时间:Duration,用于计算两个“时间”间隔
- 日期间隔:Period,用于计算两个“日期”间隔
- TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下一个工作日”等操作。
- TemporalAdjusters : 该类通过静态方法(firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用TemporalAdjuster 的实现。
4、Java比较器
- 在Java中经常会涉及到对象数组的排序问题那么就会涉及到对象之间的比较问题
- Java实现对象排序的方式有两种:
4.1 自然排序::java.lang.Comparable
- Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。
- 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。
- 如果当前对象this大于形参对象obj,则返回正整数,
- 如果当前对象this小于形参对象obj,则返回负整数,
- 如果当前对象this等于形参对象obj,则返回零。
- 实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
- 对于类 C 的每一个 e1 和 e2 来说,当且仅当e1.compareTo(e2) == 0 与e1.equals(e2) 具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals 一致。建议(虽然不是必需的)最好使自然排序与 equals 一致。
Comparable 的典型实现:(默认都是从小到大排列的)
- String:按照字符串中字符的Unicode值进行比较
- Character:按照字符的Unicode值来进行比较
- 数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值大小进行比较
- Boolean:true 对应的包装类实例大于 false 对应的包装类实例
- Date、Time等:后面的日期时间比前面的日期时间大
Goods类:
public class Goods implements Comparable {
private String name;
private double price;
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return name + '\'' +
+price;
}
@Override
public int compareTo(Object o) {
if (o instanceof Goods) {
Goods goods = (Goods) o;
if (this.price > goods.price)
return 1;
else if (this.price < goods.price)
return -1;
}
//return double.compare();
throw new RuntimeException("传入的数据类型不一致");
}
}
测试1:String
@Test
public void test1(){
String[] arr = new String[]{"AA","CC","BB","DD","GG","JJ"};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
测试2:自定义类
@Test
public void test2(){
Goods[] good = new Goods[4];
good[0]=new Goods("联想",2000);
good[1]=new Goods("中兴",200);
good[2]=new Goods("小米",20000);
good[3]=new Goods("苹果",1000);
Arrays.sort(good);
System.out.println(Arrays.toString(good));
}
4.2 定制排序:java.util.Comparator
- 当元素的类型没有实java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排序的比较。
- 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
- 可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。
- 还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
测试1:String
@Test
public void test3(){
String[] arr = new String[]{"AA","CC","BB","DD","GG","JJ","11"};
Arrays.sort(arr, new Comparator() {
//按照字符串从大到小
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String&&o2 instanceof String)
{
String s1 = (String)o1;
String s2 = (String)o2;
return -s1.compareTo(s2);
}
throw new RuntimeException("数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
测试2:自定义类
@Test
public void test4(){
Goods[] good = new Goods[5];
good[0]=new Goods("lenovoMouse",2000);
good[1]=new Goods("zhongxing",200);
good[2]=new Goods("xiaomi",20000);
good[3]=new Goods("pingguo",1000);
good[4]=new Goods("pingguo",3000);
Arrays.sort(good,new Comparator(){
//先按照产品名称从低到高,再安装价格从高到低
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Goods && o2 instanceof Goods )
{
Goods g1 = (Goods)o1;
Goods g2 = (Goods)o2;
if (g1.getName().equals(g2.getName())){
return -Double.compare(g1.getPrice(),g2.getPrice());
}else {
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException("异常");
}
});
System.out.println(Arrays.toString(good));
}
5、System类
- System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
- 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
- 成员变量:System类内部包含in、out和err三个成员变量,分别代表标准输入流
(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。 - 成员方法
- native long currentTimeMillis():
该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。 - void exit(int status):该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
- void gc():该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则
取决于系统中垃圾回收算法的实现以及系统执行时的情况。 - String getProperty(String key):该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
package com.feng.java;
/**
* @author 田丰
* @create 2021-08-18 12:05
*/
public class otherClass {
public static void main(String[] args) {
String javaVersion = System.getProperty("java.version");
System.out.println("java的version:" + javaVersion);
String javaHome = System.getProperty("java.home");
System.out.println("java的home:" + javaHome);
String osName = System.getProperty("os.name");
System.out.println("os的name:" + osName);
String osVersion = System.getProperty("os.version");
System.out.println("os的version:" + osVersion);
String userName = System.getProperty("user.name");
System.out.println("user的name:" + userName);
String userHome = System.getProperty("user.home");
System.out.println("user的home:" + userHome);
String userDir = System.getProperty("user.dir");
System.out.println("user的dir:" + userDir);
}
}
6、Math类
java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。
项目 | Value |
---|---|
abs | 绝对值 |
acos,asin,atan,cos,sin,tan | 三角函数 |
sqrt | 平方根 |
pow(double a,doble b) | a的b次幂 |
log | 自然对数 |
exp e | 为底指数 |
max(double a,double b) | 两个数最大值 |
min(double a,double b) | 两个数最小值 |
random() | 返回0.0到1.0的随机数 |
long round(double a) | double型数据a转换为long型( |
toDegrees(double angrad) | 弧度—>角度 |
toRadians(double angdeg) | 角度—>弧度 |
7、BigInteger与BigDecimal
7.1 BigInteger
- Integer类作为int的包装类,能存储的最大整型值为231-1,Long类也是有限的,最大为263-1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了。
- java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供java.lang.Math 的所有相关方法。另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、位操作以及一些其他操作。
- 构造器
如: BigInteger(String val):根据字符串构建BigInteger对象
常用方法
项目 | Value |
---|---|
public BigInteger abs() | 返回此 BigInteger 的绝对值的 BigInteger。 |
BigInteger add(BigInteger val) | 返回其值为 (this + val) 的 BigInteger |
BigInteger subtract(BigInteger val) | 返回其值为 (this - val) 的 BigInteger |
BigInteger multiply(BigInteger val) | 返回其值为 (this * val) 的 BigInteger |
BigInteger divide(BigInteger val) | 返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。 |
BigInteger remainder(BigInteger val) | 返回其值为 (this % val) 的 BigInteger。 |
BigInteger[] divideAndRemainder(BigInteger val) | 返回包含 (this / val) 后跟(this % val) 的两个BigInteger 的数组。 |
BigInteger pow(int exponent) | 返回其值为(thisexponent) 的 BigInteger。 |
7.2 BigDecimal类
- 一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到java.math.BigDecimal类。
- BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
- 构造器
- public BigDecimal(double val)
- public BigDecimal(String val)
- 常用方法
- public BigDecimal add(BigDecimal augend)
- public BigDecimal subtract(BigDecimal subtrahend)
- public BigDecimal multiply(BigDecimal multiplicand)
- public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
@Test
public void testBigInteger() {
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
// System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));
}
三、枚举与注解
1、枚举
- 类的对象只有有限个,确定的。举
- 当需要定义一组常量时,强烈建议使用枚举类
- 若枚举只有一个对象, 则可以作为一种单例模式的实现方式。
** 枚举类的属性**
- 枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰
- 枚举类的使用 private final 修饰的属性应该在构造器中为其赋值
- 若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的传入参数
1.1 自定义枚举类
- 私有化类的构造器,保证不能在类的外部创建其对象
- 在类的内部创建枚举类的实例。声明为:public static final
- 对象如果有实例变量,应该声明为private final,并在构造器中初始化
package com.feng.java;
/**
* @author 田丰
* @create 2021-08-18 12:42
*/
public class SeasonTest {
public static void main(String[] args) {
Senson autumn = Senson.AUTUMN;
Senson spring = Senson.SPRING;
System.out.println(spring);
}
}
//自定义枚举类
class Senson{
//声明Season对象的属性:private final
private final String seaName;
private final String seaDesc;
//2 私有化类的构造器,并给对象属性初始化
private Senson(String seaName, String seaDesc) {
//SPRING.Senson();
this.seaName = seaName;
this.seaDesc = seaDesc;
}
//提高当前枚举类的多个对象
public static final Senson SPRING = new Senson("春天","春回大地");
public static final Senson SUMMER = new Senson("夏天","夏回大地");
public static final Senson AUTUMN = new Senson("秋天","秋回大地");
public static final Senson WINTER = new Senson("冬天","冬回大地");
public String getSeaName() {
return seaName;
}
public String getSeaDesc() {
return seaDesc;
}
@Override
public String toString() {
return "Senson{" +
"seaName='" + seaName + '\'' +
", seaDesc='" + seaDesc + '\'' +
'}';
}
}
1.2 使用enum定义枚举类
使用说明
- 使用 enum 定义的枚举类默认继承了java.lang.Enum类,因此不能再继承其他类
- 枚举类的构造器只能使用 private 权限修饰符
- 枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的实例系统会自动添加 public static final 修饰
- 必须在枚举类的第一行声明枚举类对象
- JDK 1.5 中可以在 switch 表达式中使用Enum定义的枚举类的对象作为表达式, case 子句可以直接使用枚举值的名字, 无需添加枚举类作为限定。
package com.feng.java;
/**
* @author 田丰
* @create 2021-08-18 13:08
*/
public class SeasonTest1 {
public static void main(String[] args) {
Senson1 s = Senson1.SUMMER;
System.out.println(s.toString());
}
}
//使用enum关键字枚举类
enum Senson1{
//提高当前枚举类的多个对象
SPRING("春天","春回大地"),
SUMMER("夏天","夏回大地"),
AUTUMN("秋天","秋回大地"),
WINTER("冬天","冬回大地");
//声明Season对象的属性:private final
private final String seaName;
private final String seaDesc;
//2 私有化类的构造器,并给对象属性初始化
private Senson1(String seaName, String seaDesc) {
//SPRING.Senson();
this.seaName = seaName;
this.seaDesc = seaDesc;
}
public String getSeaName() {
return seaName;
}
public String getSeaDesc() {
return seaDesc;
}
// @Override
// public String toString() {
// return "Senson{" +
// "seaName='" + seaName + '\'' +
// ", seaDesc='" + seaDesc + '\'' +
// '}';
// }
}
Enum类的主要方法:
项目 | Value |
---|---|
values()方法 | 返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。 |
valueOf(String str) | 可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常IllegalArgumentException。 |
toString() | 返回当前枚举类对象常量的名称 |
2、注解(Annotation)
2.1 注解的概述
-
从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是Annotation(注解)
-
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
-
Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 “name=value” 对中。
-
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
-
未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式。
2.2 常见的注解
使用 Annotation 时要在其前面增加 @ 符号, 并把Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
示例一:生成文档相关的注解
注解名称 | 描述 |
---|---|
@author | 标明开发该类模块的作者,多个作者之间使用,分割 |
@version | 标明该类模块的版本 |
@see | 参考转向,也就是相关主题 |
@since | 从哪个版本开始增加的 |
@param | 对方法中某参数的说明,如果没有参数就不能写 |
@return | 对方法返回值的说明,如果方法的返回值类型是void就不能写 |
@exception | 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写 |
其中:
- @param @return 和 @exception 这三个标记都是只用于方法的。
- @param的格式要求:@param 形参名 形参类型 形参说明
- @return 的格式要求:@return 返回值类型 返回值说明
- @exception的格式要求:@exception 异常类型 异常说明
- @param和@exception可以并列多个
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
注解名称 | 描述 |
---|---|
@Override | 限定重写父类方法, 该注解只能用于方法 |
@Deprecated | 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择 |
@SuppressWarnings | 抑制编译器警告 |
@Deprecated:过时:Date
Ctrl+左键:点击Date 可以看到@Deprecated|注解的Date构造方法。
@SuppressWarnings
@SuppressWarnings("unused")
int num = 10;
@SuppressWarnings({"unused"})
ArrayList list =new ArrayList();
测试:
package com.feng.java1;
import java.util.ArrayList;
import java.util.Date;
/**
*
* 注解的使用
* 理解Annotation
* 1、JDK5.0新增
* 2、Annotation 其实就是代码里的**特殊标记**, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
* 3、在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在*JavaEE/Android*中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等
*
* 示例一:生成文档相关的注解
* 示例二:
* @Override: 限定重写父类方法, 该注解只能用于方法
* @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
* @SuppressWarnings: 抑制编译器警告
* 示例三:
*
*
*
* @author 田丰
* @create 2021-08-19 10:56
*/
public class AnnotationTest {
public static void main(String[] args) {
Person p = new Student();
p.walk();
System.out.println(new Date(2020, 2, 2));
@SuppressWarnings("unused")
int num = 10;
@SuppressWarnings({"unused","rawtypes"})
ArrayList list =new ArrayList();
}
}
//@MyAnnotation("hello")
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void walk(){
System.out.println("人走路");
}
public void eat(){
System.out.println("人吃饭");
}
}
interface Info{
void show();
}
class Student extends Person implements Info{
@Override
public void walk() {
System.out.println("学生走路");
}
@Override
public void show() {
}
}
示例三:跟踪代码依赖性,实现替代配置文件功能
- Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。
2.3 自定义注解
- 定义新的 Annotation 类型使用 @interface 关键字
- 自定义注解自动继承了java.lang.annotation.Annotation接口
- Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。
- 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值可使用 default 关键字
- 如果只有一个参数成员,建议使用参数名为value
- 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
- 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation
注意: 自定义注解必须配上注解的信息处理流程才有意义。
参照SuppressWarnings定义:
public @interface MyAnnotation {
String value();
}
@MyAnnotation(value = "hello")
class Person{
private String name;
private int age;
@MyAnnotation("hello")
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void walk(){
System.out.println("人走路");
}
public void eat(){
System.out.println("人吃饭");
}
}
2.4 JDK中的元注解
1、@Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命周期, @Rentention 包含一个RetentionPolicy 类型的成员变量, 使用@Rentention 时必须为该 value 成员变量指定值:
- RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
- RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
- RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。
2、@Target: 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。
3、@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。
- 定义为Documented的注解必须设置Retention值为RUNTIME。
4、@Inherited: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被@Inherited 修饰的 Annotation, 则其子类将自动具有该注解。
- 比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解
- 实际应用中,使用较少
2.5 利用反射获取注解信息
到反射内容看
2.6 JDK8中的注解新特性
类型注解:
- JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个:TYPE_PARAMETER,TYPE_USE。
- 在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。
- ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
- ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
四、Java集合
1、Java集合的概述
1.1 概述、特点
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储主要是指内存层面的存储,不涉及持久化的存储。
数组在内存存储方面的特点
- 数组初始化以后,长度就确定了。
- 数组声明的类型,就决定了进行元素初始化时的类型
数组在存储数据方面的弊端
- 数组初始化以后,长度就不可变了,不便于扩展
- 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数
- 数组存储的数据是有序的、可以重复的。---->存储数据的特点单一
Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
1.2 Java 集合框架
Java 集合可分为 Collection 和 Map 两种体系
- Collection接口:单列数据,定义了存取一组对象的方法的集合
- List:元素有序、可重复的集合。–>动态数组
- ArrayList
- LinkedList
- Vector
- Set:元素无序、不可重复的集合。–>类型高中数学里面的集合
- HashSet
- LinkedHashSet
- TreeSet
- List:元素有序、可重复的集合。–>动态数组
- Map接口:双列数据,保存具有映射关系“key-value对”的集合。用来存储一对数据。
- HashMap
- LinkedHashMap
- TreeMap
- Hashtable
- Properties
2、Collection接口方法
2.1 Collection 接口
- Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
- JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现。
- 在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。
2.2 Collection 接口方法
1、添加
- add(Object obj)
- addAll(Collection coll)
2、获取有效元素的个数
- int size()
3、清空集合
- void clear()
4、是否是空集合
- boolean isEmpty()
5、是否包含某个元素
- boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
- boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
6、删除
- boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
- boolean removeAll(Collection coll):取当前集合的差集
7、取两个集合的交集
- boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8、集合是否相等
- boolean equals(Object obj)
9、转成对象数组
- Object[] toArray()
10、获取集合对象的哈希值
- hashCode()
11、遍历
- iterator():返回迭代器对象,用于集合遍历
测试:
package com.feng.java2;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
//import java.sql.Date;
/**
* 一、集合框架的概述
* 1、集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
* 说明:此时的存储主要是指内存层面的存储,不涉及持久化的存储。
* 2、数组在存储多个数据方面的特点
* >一旦初始化,长度就确定了
* >需要指明数组的元素类型,元素类型也就确定了:比如:String[]、int[]
* >一旦确定,长度不能在修改
*
* >
*
* 二、集合的框架
* Collection接口:单列集合,用来存储一个一个的对象
* List接口:有序、可重复的数据。”动态数组“
* Set接口:无序、不可重复
* Map接口:双列集合:用来存储一对一对的数据
* @author 田丰
* @create 2021-08-20 11:26
*/
public class CollectionTest {
@Test
public void test1(){
Collection coll = new ArrayList();
Collection coll1 = new ArrayList();
//add()
coll.add("AA");
coll.add("BB");
coll.add(123);
coll.add(new Date());
//获取添加元素的个数
System.out.println(coll.size());
//System.out.println(coll.toString());
coll1.add(456);
coll1.add("CC");
coll.addAll(coll1);
System.out.println(coll.size());
System.out.println(coll);
// clear:清空与元素
coll.clear();
//isEmpty:集合中是否有元素
System.out.println(coll.isEmpty());
}
}
结果展示:
@Test
public void test2(){
//remove
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("tom"));
coll.add(false);
coll.add(new Person("tom",10));
System.out.println(coll.toString());
System.out.println(coll.remove(1234));
coll.remove(new Person("tom",10));
System.out.println(coll);
//removeAll:
Collection coll1 = Arrays.asList(123,456);
coll.removeAll(coll1);
System.out.println(coll);
}
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("tom"));
coll.add(false);
Collection coll1 = Arrays.asList(123,456);
//retainAll(Collection coll1):交集:获取当前集合和coll1集合的交集,并返回给当前集合coll。
coll.retainAll(coll1);
System.out.println(coll);
//equals(Objecct obj):当前集合与形参集合的元素和顺序都要相同才能返回true
Collection coll2 = Arrays.asList(123,456);
Collection coll3 = Arrays.asList(456,123);
System.out.println(coll1.equals(coll2));
System.out.println(coll1.equals(coll3));
}
@Test
public void test4(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("tom",32));
coll.add(false);
//HashCode():返回当前对象的哈希值
System.out.println(coll.hashCode());
//集合--->数组
Object[] objects = coll.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
System.out.println(Arrays.toString(objects));
//数组-->集合:Arrays.asList
List<int[]> ints = Arrays.asList(new int[]{1, 2, 3, 4, 5});
System.out.println(ints.toString());
Collection strings = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(strings);
Collection integers = Arrays.asList(1, 2, 3);
System.out.println(integers);
Collection integers1 = Arrays.asList(new Integer[]{123, 456});
System.out.println(integers1);
}
3、Iterator迭代器接口
3.1 使用 Iterator 接口遍历集合元素
- Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
- GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
- Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
- Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
- 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
3.2 Iterator接口的方法
方法 | 描述 |
---|---|
public boolean hasNext() | 判断集合是否有下一个元素 |
pulbic E next() | 取出当前元素 |
public void remove() | 删除当前元素 |
注意:
- 在调用it.next()方法之前必须要调it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛NoSuchElementException异常。
- Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
- 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
package com.feng.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* 集合元素的遍历。使用迭代器接口
*
*
* @author 田丰
* @create 2021-08-20 19:22
*/
public class IteratorTest {
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("tom",32));
coll.add(false);
//
Iterator iterator = coll.iterator();
//方式一:
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
//System.out.println(iterator.next());
//方式二:
for (int i = 0; i < coll.size(); i++) {
System.out.println(iterator.next());
}
//判断是否还有下一个元素
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("tom",32));
coll.add(false);
Iterator iterator = coll.iterator();
while (iterator.next()!=null){
System.out.println(iterator.next());
}
}
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("tom"));
coll.add(false);
//删除集合中的tom元素
Iterator iterator = coll.iterator();
while (iterator.hasNext())
{
Object obj = iterator.next();
if ("tom".equals(obj)){
iterator.remove();
}
}
Iterator iterator1 = coll.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
}
}
3.3 使用 foreach 循环遍历集合元素
- Java 5.0 提供了 foreach 循环迭代访问 Collection和数组。
- 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
- 遍历集合的底层调用Iterator完成操作。
- foreach还可以用来遍历数组。
用法:
for(Object obj : coll){
System.out.println(obj);
//Object:要遍历元素的类型
//obj:遍历后自定义元素的名称
//coll:要遍历的结构名称
package com.feng.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
/**
*
* JDK5.0新增了forearch循环
* @author 田丰
* @create 2021-08-21 10:20
*/
public class ForTest {
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("tom",32));
coll.add(false);
for(Object obj : coll){
System.out.println(obj);
}
}
//单元测试
@Test
public void test1(){
int[] arr = new int[]{1,2,3,4,5,6,4,3,2};
for (Object obj :arr){
System.out.println(obj);
}
}
}
4、Collection子接口一:List
4.1 List接口概述
- 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
- List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
- JDK API中List接口的实现类常用的有ArrayList、LinkedList和Vector
4.2 List接口的方法
List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。
方法 | 描述 |
---|---|
void add(int index, Object ele) | 在index位置插入ele元素 |
boolean addAll(int index, Collection eles) | 从index位置开始将eles中的所有元素添加进来 |
Object get(int index) | 获取指定index位置的元素 |
int indexOf(Object obj) | 返回obj在集合中首次出现的位置 |
int lastIndexOf(Object obj) | 返回obj在当前集合中末次出现的位置 |
Object remove(int index) | 移除指定index位置的元素,并返回此元素 |
Object set(int index, Object ele) | 设置指定index位置的元素为ele |
List subList(int fromIndex, int toIndex) | 返回从fromIndex到toIndex位置的子集合 |
测试:
package com.feng.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*
* void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中
的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex
位置的子集合
* @author 田丰
* @create 2021-08-21 11:16
*/
public class ListTest {
@Test
public void test1(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("tom",21));
list.add(456);
System.out.println(list);
//void add(int index, Object ele):在index位置插入ele元素
list.add(1,"BB");
System.out.println(list);
//boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
List asList = Arrays.asList(1, 2, 3);
//list.addAll(asList);
//list.add(asList);
System.out.println(list);//
//Object get(int index):获取指定index位置的元素
System.out.println(list.get(0));//123
}
@Test
public void test2(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("tom",21));
list.add(456);
//int indexOf(Object obj):返回obj在集合中首次出现的位置
int i = list.indexOf(456);
System.out.println(i);//1
//int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
System.out.println(list.lastIndexOf(456));
//Object remove(int index):移除指定index位置的元素,并返回此元素
Object remove = list.remove(0);
System.out.println(remove);//123
System.out.println(list);//[BB, 456, AA, Person{name='tom', age=21}, 456]
// Object set(int index, Object ele):设置指定index位置的元素为ele
System.out.println(list.set(1, "CC"));//AA
System.out.println(list);//[456, CC, Person{name='tom', age=21}, 456]
//List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex:左闭右开
List list1 = list.subList(2, 4);
System.out.println(list1);//[Person{name='tom', age=21}, 456]
}
}
总结:常用方法
方法 | 描述 |
---|---|
增 | add(Object obj) |
删 | remove(int index)/remove(Object obj) |
改 | set(int index,Object obj) |
查 | get(int index) |
插 | add(int index,Object obj) |
长度 | size() |
遍历 | Iterator/foreach/for |
遍历测试:
@Test
public void test3(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
//方式一:Iterator迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//方式二:增强for循环
for (Object obj : list){
System.out.println(obj);
}
//方式三:
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
4.3 List实现类
4.3.1 ArrayList
作为List接口的主要实现类:线程不安全,效率高,底层使用Object[] elementData存储
源码分析:
1、jdk 7 情况下
ArrayList list = new ArrayList();//底层创建了长度为10的Object[]数组elementData
List.add(123);//emementData[0] = new Integer(123)//如果添加元素导致数组容量不够,则会默认扩容容量为1.5倍,同时需要将原来数组中的元素复制到新的数组中。
- 建议使用带参构造器:
ArrayList list = new ArrayList(int capacity);
2、jdk 8情况下
ArrayList list = new ArrayList();//底层Object [] elementData并没有初始化数组
List.add(123);// 第一次·添加时,底层才创建了长度为10的数组
小结:
- JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组
- JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数
4.3.2 LinkedList
对于频繁使用插入、删除操作,使用此类的效率比ArrayList高:底层使用双向链表,
源码分析:
LinkedList list = new LinkedList();
//内部声明了Node类型的first和last属性,默认值为nulllist.add(123);
//将123封装到Node中,创建了Node对象- 其中Node定义为:体现了LinkedList的双向链表特性。
rivate static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
- 对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
- 新增方法:
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()
4.3.3 Vector
-
作为List接口的古老的实现类:线程安全,效率低:底层使用Object[ ] elementData存储
-
jdk 7和jdk8情况下通过Vector()构造器出创建对象,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来的2倍。
-
新增方法:
void addElement(Object obj)
void insertElementAt(Object obj,int index)
void setElementAt(Object obj,int index)
void removeElement(Object obj)
void removeAllElements()
5、Collection子接口二:Set
- Set接口是Collection的子接口,set接口没有提供额外的方法
- Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败。
- Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法
5.1 Set实现类之一:HashSet
- HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
- HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
- HashSet 具有以下特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
- HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的equals() 方法返回值也相等。
- 对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
/*
1、我们向HashSet中添加元素a,首先调用hashCode()方法,计算元素a的哈希值,此哈希值接着通过
某种计算方法算出在底层数组中的存放位置。判断数组此位置上,是否有元素:
如果没:则添加成功
如果有元素b,则比较a和b的hash值
如果值不同,添加成功
如果值相同,进而调用元素a的equals方法
返回true,添加失败
返回false,添加成功。
*/
@Test
public void test1(){
HashSet hashSet = new HashSet();
hashSet.add(456);
hashSet.add(123);
hashSet.add(123);
hashSet.add("aa");
hashSet.add("cc");
hashSet.add(new User("tom",12));
hashSet.add(new User("tom",12));
hashSet.add(129);
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
5.2 Set实现类之二:LinkedHashSet
- LinkedHashSet 是 HashSet 的子类
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
- LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
- LinkedHashSet 不允许集合元素重复。
@Test
public void test2(){
HashSet hashSet = new LinkedHashSet();
hashSet.add(456);
hashSet.add(123);
hashSet.add(123);
hashSet.add("aa");
hashSet.add("cc");
hashSet.add(new User("tom",12));
hashSet.add(new User("tom",12));
hashSet.add(129);
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
5.3 Set实现类之三:TreeSet
6、Map接口
6.1 Map接口概述
- Map与Collection并列存在。用于保存具有映射关系的数据:key-value
- Map 中的 key 和 value 都可以是任何引用类型的数据
- Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法
- 常用String类作为Map的“键”
- key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的value
- Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用频率最高的实现类
Map:双列数据,存储key-value对的数据
6.2 Map接口:常用方法
添加、删除、修改操作:
方法 | 描述 |
---|---|
Object put(Object key,Object value) | 将指定key-value添加到(或修改)当前map对象中 |
void putAll(Map m) | 将m中的所有key-value对存放到当前map中 |
Object remove(Object key) | 移除指定key的key-value对,并返回value |
void clear() | 清空当前map中的所有数据 |
@Test
public void test1(){
Map map = new HashMap();
//添加
map.put("AA",123);
map.put(45,13);
map.put("BB",23);
//修改
map.put("AA",12);
System.out.println(map);
//Map map = new HashMap();
Map map1 = new HashMap();
map1.put("CC",123);
map1.put("DD",13);
//添加map1集合
map.putAll(map1);
System.out.println(map);
//删除集合中的元素:remove
Object cc = map.remove("CC");
System.out.println(cc);
System.out.println(map);
//map = null:
map1.clear();
System.out.println(map1);
System.out.println(map1.size());//0
}
元素查询的操作:
方法 | 描述 |
---|---|
Object get(Object key) | 获取指定key对应的value |
boolean containsKey(Object key) | 是否包含指定的key |
boolean containsValue(Object value) | 是否包含指定的value |
int size() | 返回map中key-value对的个数 |
boolean isEmpty() | 判断当前map是否为空 |
boolean equals(Object obj) | 判断当前map和参数对象obj是否相等 |
测试:
@Test
public void test2()
{
Map map = new HashMap();
//添加
map.put("AA",123);
map.put(45,13);
map.put("BB",23);
//Object get(Object key)
System.out.println(map.get("AA"));//123
System.out.println(map.get("222"));//null
//containsKey(Object key)
boolean b = map.containsKey(45);
System.out.println(b);//true
//containsValue(Object value)
boolean b1 = map.containsValue(123);
System.out.println(b1);//true
//isEmpty()
System.out.println(map.isEmpty());//false
//equals(Object obj)
Map map1 = new HashMap();
//添加
map1.put("AA",123);
map1.put(45,13);
map1.put("BB",23);
System.out.println(map.equals(map1));
}
元视图操作的方法:
方法 | 描述 |
---|---|
Set keySet() | 返回所有key构成的Set集合 |
Collection values() | 返回所有value构成的Collection集合 |
Set entrySet() | 返回所有key-value对构成的Set集合 |
@Test
public void test3(){
Map map = new HashMap();
//添加
map.put("AA",123);
map.put(45,13);
map.put("BB",23);
//Set keySet()`|返回所有key构成的Set集合
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("################");
Collection values = map.values();
for (Object obj : values){
System.out.println(obj);
}
System.out.println("################");
//Set entrySet()`|返回所有key-value对构成的Set集合
//方式一:
Set set1 = map.entrySet();
Iterator iterator1 = set1.iterator();
while (iterator1.hasNext()){
Object next = iterator1.next();
Map.Entry entry = (Map.Entry)next;
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
System.out.println("################");
//方式二:
Set set2 = map.keySet();
Iterator iterator2 = set2.iterator();
while (iterator2.hasNext()){
Object key = iterator2.next();
Object o = map.get(key);
System.out.println(key+ "-->" + o);
}
}
6.3 实现类
6.3.1 Map实现类之一:HashMap
- HashMap是 Map 接口使用频率最高的实现类。
- 允许使用null键和null值,与HashSet一样,不保证映射的顺序。
- 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
- 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
- 一个key-value构成一个entry
- 所有的entry构成的集合是Set:无序的、不可重复的
- HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
- HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。
6.3.2 Map实现类之二:LinkedHashMap
- LinkedHashMap 是 HashMap 的子类
- 在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
- 与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致
6.3.3 Map实现类之三:TreeMap
- TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
- TreeSet底层使用红黑树结构存储数据
- TreeMap 的 Key 的排序:
- 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
- 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口
- TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。
6.3.4 Map实现类之四Hashtable
- Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
- Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
- 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
- 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
- Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
- Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
- 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
- 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
- Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。
6.3.5Map实现类之五Properties
- Properties 类是 Hashtable 的子类,该对象用于处理属性文件
- 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
- 存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
package com.feng.src;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
* @author 田丰
* @create 2021-08-23 16:19
*/
public class PropertiesTest {
public static void main(String[] args){
FileInputStream fis = null;
try {
Properties pro = new Properties();
fis = new FileInputStream("jdbc.properties");
pro.load(fis);
String name = pro.getProperty("name");
String password = pro.getProperty("password");
System.out.println(name + "," + password);
} catch (IOException e) {
e.printStackTrace();
}
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
7、Collections工具类
-
Collections 是一个操作 Set、List 和 Map 等集合的工具类
-
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
-
排序操作:(均为static方法)
reverse(List)
:反转 List 中元素的顺序shuffle(List)
:对 List 集合元素进行随机排序sort(List)
:根据元素的自然顺序对指定 List 集合元素按升序排序sort(List,Comparator)
:根据指定的 Comparator 产生的顺序对 List 集合元素进行排序swap(List,int, int)
:将指定 list 集合中的 i 处元素和 j 处元素进行交换
-
查找、替换
Object max(Collection)
:根据元素的自然顺序,返回给定集合中的最大元素Object max(Collection,Comparator)
:根据 Comparator 指定的顺序,返回给定集合中的最大元素Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object)
:返回指定集合中指定元素的出现次数void copy(List dest,List src)
:将src中的内容复制到dest中boolean replaceAll(List list,Object oldVal,Object newVal)
:使用新值替换List 对象的所有旧值
部分方法测试:
package com.feng.src;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author 田丰
* @create 2021-08-23 17:03
*/
public class CollectionsTest {
@Test
public void test1(){
List list = new ArrayList();
list.add(123);
list.add(-123);
list.add(13);
list.add(12);
list.add(23);
list.add(23);
list.add(23);
System.out.println(list);
//reverse(List):反转 List 中元素的顺序
Collections.reverse(list);
System.out.println(list);
//shuffle(List):对 List 集合元素进行随机排序
Collections.shuffle(list);
System.out.println(list);
//sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
System.out.println(list);
//swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Collections.swap(list,1,2);
System.out.println(list);
//frequency(Collection,Object):返回指定集合中指定元素的出现次数
int frequency = Collections.frequency(list,23);
System.out.println(frequency);
//copy(List dest,List src):将src中的内容复制到dest中
List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest,list);
System.out.println(dest);
}
}
结果不唯一,因为调用了随机排序:
五、 泛型
5.1 泛型的概述
泛型: 标签
泛型的设计背景:
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。
泛型的概念:
- 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
- 从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。
- JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
引入泛型的优点:
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。
5.2 在集合中使用泛型
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @author 田丰
* @create 2021-08-24 9:42
*/
public class GenericTest {
@Test
public void test1() {
ArrayList list = new ArrayList();
list.add(1111);
list.add(13);
list.add(32);
list.add(42);
//list.add("Tom");
//System.out.println(list);//[1111, 13, 32, 42, Tom]:类型不安全
for (Object object : list) {
int score = (int) object;
System.out.println(score);
}
}
@Test
public void test2() {
ArrayList<Integer> list = new ArrayList<>();
list.add(1111);
list.add(1111111);
list.add(1234);
for (Integer integer : list) {
System.out.println(integer);
}
System.out.println("@@@@@@@");
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
@Test
public void tese3() {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("tom", 1111);
map.put("jer", 21);
map.put("rose", 32);
map.put("rose", 32);
}
}
5.3 自定义泛型结构
5.3.1 自定义泛型类&自定义泛型接口
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
<E1,E2,E3>
- 泛型类的构造器如下:
public GenericClass(){}。
而下面是错误的:public GenericClass<E>(){}
- 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
- 泛型不同的引用不能相互赋值。
尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有
一个ArrayList被加载到JVM中。
- 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
- 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
- jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
- 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
- 异常类不能是泛型的
- 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
- 子类不保留父类的泛型:按需实现
- 没有类型 擦除
- 具体类型
- 子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father{//等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2<A, B> extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2, A, B> extends Father<Integer, T2> {
}
class Person<T> {
// 使用T类型定义变量
private T info;
// 使用T类型定义一般方法
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
// 使用T类型定义构造器
public Person() {
}
public Person(T info) {
this.info = info;
}
// static的方法中不能声明泛型
//public static void show(T t) {
//
//}
// 不能在try-catch中使用泛型定义
//public void test() {
//try {
//
//} catch (MyException<T> ex) {
//
//}
//}
}
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自
己的泛型
5.3.1 自定义泛型方法
- 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
- 泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常 - 泛型方法声明泛型时也可以指定上限(在12.5中讲
5.4 泛型在继承上的体现
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G <B>
并不是G<A>
的子类型!
比如:String
是Object
的子类,但是List<String >
并不是List<Object>
的子类。
@Test
public void test1(){
Object obj = null;
String s = null;
obj = s;
Object[] arr1 =null;
String[] arr2 = null;
arr2=arr2;
List<Object> list1 = null;
List<String> list2 = null;
list1=list2;//编译不通过
}
类A是类B的父类则:A<G>
是B<G>
的父类
@Test
public void test2(){
List<String> list1 =null;
ArrayList<String> list2 =null;
list1=list2;
}
5.4 通配符的使用
1.使用类型通配符:?
比如:List<?>
,Map<?,?>
,List<?>
是List<String>
、List<Object>
等各种泛型List的父类。
2.读取List<?>
的对象list
中的元素时,永远是安全的,因为不管list
的真实类型是什么,它包含的都是Object
。
3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。
- 唯一的例外是null,它是所有类型的成员。
@Test
public void test3(){
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
List<?> list = null;
list=list1;
list=list2;
}
有限制的通配符
<?>
- 允许所有泛型的引用调用
通配符指定上限上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<= - 通配符指定下限
下限super:使用时指定的类型不能小于操作的类,即>= - 举例:
<? extends Number>
(无穷小 , Number]只允许泛型为Number及Number子类的引用调用<? super Number>
[Number , 无穷大)只允许泛型为Number及Number父类的引用调用<? extends Comparable>
只允许泛型为实现Comparable接口的实现类的引用调用
六、IO流
1、File类的使用
1.1 File类的使用
java.io.File
类:文件和文件目录路径的抽象表示形式,与平台无关- File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
- 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
- File对象可以作为参数传递给流的构造器
1.2 File 类的使用:常用构造器
-
public File(String pathname)
以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。- 绝对路径:是一个固定的路径,从盘符开始
- 相对路径:是相对于某个位置开始
-
public File(String parent,String child)
以parent为父路径,child为子路径创建File对象。 -
public File(File parent,String child)
根据一个父File对象和子文件路径创建File对象
File实例化:
package com.feng.javaIO;
import org.junit.Test;
import java.io.File;
/**
* @author 丰
* @create 2021-08-26 14:10
* 1、File类的一个对象
*/
public class FileTest {
@Test
public void test1(){
//相对路径:相对于当前module
File file1 = new File("hello1.txt");
//绝对路径
File file2 = new File("D:\\Installed\\IntelliJ IDEA 2017.3.1\\workspace\\day08\\hello2.txt");
//构造器2:
File file3 = new File("D:\\Installed","hell");
//构造器3
File file4 = new File(file3,"hi.txt");
}
}
1.3 File 类的使用:路径分隔符
- 路径中的每级目录之间用一个路径分隔符隔开。
- 路径分隔符和系统有关:
windows
和DOS
系统默认使用“\”
来表示UNIX
和URL
使用“/”
来表示- Java程序支持跨平台运行,因此路径分隔符要慎用。
- 为了解决这个隐患,File类提供了一个常量:public static final String separator。根据操作系统,动态的提供分隔符。
1.4 File 类的使用:常用方法
File类的获取功能
项目 | Value |
---|---|
public String getAbsolutePath() | 获取绝对路径 |
public String getPath() | 获取路径 |
public String getName() | 获取名称 |
public String getParent() | 获取上层文件目录路径。若无,返回null |
public long length() | 获取文件长度(即:字节数)。不能获取目录的长度。 |
public long lastModified() | 获取最后一次的修改时间,毫秒值 |
public String[] list() | 获取指定目录下的所有文件或者文件目录的名称数组 |
public File[] listFiles() | 获取指定目录下的所有文件或者文件目录的File数组 |
@Test
public void test2(){
File file1 = new File("hi.txt");
File file2= new File("D:\\io\\hi1.txt");
//hi.txt文件存在里面存了8个字节
System.out.println(file1.getAbsolutePath());
System.out.println(file1.getName());
System.out.println(file1.getPath());
System.out.println(file1.getParent());
System.out.println(file1.length());
System.out.println(file1.lastModified());
System.out.println();
System.out.println(file2.getAbsolutePath());
System.out.println(file2.getName());
System.out.println(file2.getPath());
System.out.println(file2.getParent());
System.out.println(file2.length());
System.out.println(new Date(file2.lastModified()));
}
@Test
public void test3(){
File file = new File("D:\\Installed");
String[] list = file.list();
for (String str : list){
System.out.println(str);
}
File[] files = file.listFiles();
for (File fil : files){
System.out.println(fil);
}
}
public String[] list()
public File[] listFiles()
File类的重命名功能
项目 | Value |
---|---|
public boolean renameTo(File dest) | 把文件重命名为指定的文件路径 |
@Test
public void test4(){
File file1 =new File("hi.txt");//要存在
File file2 =new File("D:\\io\\hi1.txt");//不能存在在
boolean b = file1.renameTo(file2);
System.out.println(b);
}
File类的判断功能
项目 | Value |
---|---|
public boolean isDirectory( ) | 判断是否是文件目录 |
public boolean isFile() | 判断是否是文件 |
public boolean exists() | 判断是否存在 |
public boolean canRead() | 判断是否可读 |
public boolean canWrite() | 判断是否可写 |
public boolean isHidden() | 判断是否隐藏 |
@Test
public void test5(){
File file=new File("hi.txt");
System.out.println(file.isDirectory());
System.out.println(file.isFile());
System.out.println(file.exists());
System.out.println(file.canRead());
System.out.println(file.canWrite());
System.out.println(file.isHidden());
}
File类的创建功能
项目 | Value |
---|---|
public boolean createNewFile() | 创建文件。若文件存在,则不创建,返回false |
public boolean mkdir() | 创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。 |
public boolean mkdirs() | 创建文件目录。如果上层文件目录不存在,一并创建 |
注意事项 如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目
路径下。
File类的删除功能
项目 | Value |
---|---|
public boolean delete() | 删除文件或者文件夹删除 |
注意事项:Java中的删除不走回收站。要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
@Test
public void test6() throws IOException {
File file = new File("hi.txt");
if(!file.exists()){
file.createNewFile();
System.out.println("创建成功");
}
else{
boolean delete = file.delete();
System.out.println("删除");
}
}
2、IO流原理及流的分类
Java IO原理
- I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
- Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行。
- java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
- 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
- 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。
流的分类
- 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流
- Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个
抽象基类派生的。 - 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | InputStream | OutputStream | Reader | Writer |
访问数组 | InputStream | OutputStream | Reader | Writer |
访问管道 | InputStream | OutputStream | Reader | Writer |
访问字符串 | InputStream | OutputStream | Reader | Writer |
缓冲流 | InputStream | OutputStream | Reader | Writer |
3、节点流(或文件流)
4、缓冲流
5、转换流
5.1 转化流的使用
1、转换流属于字符流
InputStreamReader:将一个字节的输入流转换为字符的输入流
:将一个字符的输出流转换为字节的输出流
6、标准输入、输出流
7、打印流
8、数据流
9、对象流
10、随机存取文件流
RandomAccessFile 类
RandomAccessFile
声明在java.io包下,但直接继承于java.lang.Object
类。并且它实现了DataInput
、DataOutput
这两个接口,也就意味着这个类既可以读也可以写。- RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件
- 支持只访问文件的部分内容
- 可以向已存在的文件后追加内容
- RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。
RandomAccessFile 类对象可以自由移动记录指针:- long getFilePointer():获取文件记录指针的当前位置
- void seek(long pos):将文件记录指针定位到 pos 位置
构造器
-
public RandomAccessFile(File file, String mode)
-
public RandomAccessFile(String name, String mode)
-
创建
RandomAccessFile
类实例需要指定一个 mode 参数,该参数指定RandomAccessFile
的访问模式:- r: 以只读方式打开
- rw:打开以便读取和写入
- rwd:打开以便读取和写入;同步文件内容的更新
- rws:打开以便读取和写入;同步文件内容和元数据的更新
-
如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。
11 NIO.2中Path、Paths、Files类的使用
七、网络编程
1、网络编程概述
- Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
- Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。
计算机网络:
- 把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
网络编程的目的:
- 直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
网络编程中有两个主要的问题:
- 如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
- 找到主机后如何可靠高效地进行数据传
2、网络通信要素概述
-
IP和端口号
-
网络通信协议
如何实现网络中的主机互相通信
-
通信双方地址
- IP
- 端口号
-
一定的规则(即:网络通信协议。有两套参考模型)
- OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
- TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
3、通信要素1:IP和端口号
3.1 IP地址:InetAddress
- 唯一的标识 Internet 上的计算机(通信实体)
- 本地回环地址(hostAddress):
127.0.0.1
主机名(hostName):localhost
- IP地址分类方式1:IPV4 和 IPV6
- IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如
192.168.0.1
- IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,
数之间用冒号(:)分开,如3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
- IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如
- IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168.开头的就是私有址址,范围即为
192.168.0.0--192.168.255.255
,专门为组织机构内部使用 - 特点:不易记忆
3.2 端口号
- 端口号标识正在计算机上运行的进程(程序)
- 不同的进程有不同的端口号
- 被规定为一个 16 位的整数 0~65535。
- 端口分类:
- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口
80,FTP占用端口21,Telnet占用端口23) - 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
- 动态/私有端口:49152~65535。
- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口
- 端口号与IP地址的组合得出一个网络套接字:Socket。
3.3 InetAddress类
-
Internet上的主机有两种方式表示地址:
-
域名(hostName):www.atguigu.com
-
IP 地址(hostAddress):202.108.35.210
-
InetAddress类主要表示IP地址,两个子类:Inet4Address、Inet6Address。
-
InetAddress 类 对 象 含 有 一 个 Internet 主 机 地 址 的 域 名 和 IP 地 址 :www.atguigu.com 和 202.108.35.210。
-
域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。 -------域名解析
-
InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取
InetAddress实例public static InetAddress getLocalHost()
public static InetAddress getByName(String host)
-
InetAddress提供了如下几个常用的方法
public String getHostAddress()
:返回 IP 地址字符串(以文本表现形式)。public String getHostName()
:获取此 IP 地址的主机名public boolean isReachable(int timeout)
:测试是否可以达到该地址
package com.feng.java1;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author 丰
* @create 2021-08-29 10:11
*/
public class InetAddressTset {
public static void main(String[] args) {
try {
InetAddress byName = InetAddress.getByName("192.168.10.14");
System.out.println(byName);
InetAddress byName1 = InetAddress.getByName("www.baidu.com");
System.out.println(byName1);
InetAddress byName3 = InetAddress.getByName("127.0.0.1");
System.out.println(byName3);
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
System.out.println(byName1.getHostName());
System.out.println(byName1.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
//InetAddress ia = new InetAddress("192.153.2.2");
}
}
4、通信要素2:网络协议
网络通信协议
计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代
码结构、传输控制步骤、出错控制等制定标准。
问题:网络协议太复杂
计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩
解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢?
通信协议分层的思想
在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。
4.1 TCP/IP协议簇
传输层协议中有两个非常重要的协议:
传输控制协议TCP(Transmission Control Protocol)
用户数据报协议UDP(User Datagram Protocol)。
TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得
名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。
TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即
物理链路层、IP层、传输层和应用层。
4.2 TCP 和 UDP
TCP协议:
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端。
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP协议:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
4.3 Socket
- 利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。
- 网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
- 通信的两端都要有Socket,是两台机器间通信的端点。
- 网络通信其实就是Socket间的通信。
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
- Socket分类:
- 流套接字(stream socket):使用TCP提供可依赖的字节流服务
- 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务
Socket类的常用构造器:
项目 | Value |
---|---|
public Socket(InetAddress address,int port) | 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 |
public Socket(String host,int port) | 创建一个流套接字并将其连接到指定主机上的指定端口号。 |
Socket类的常用方法:
项目 | Value |
---|---|
public InputStream getInputStream() | 返回此套接字的输入流。可以用于接收网络消息 |
public OutputStream getOutputStream() | 返回此套接字的输出流。可以用于发送网络消息 |
public InetAddress getInetAddress() | 此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。 |
public InetAddress getLocalAddress() | 获取套接字绑定的本地地址。 即本端的IP地址 |
public int getPort() | 此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。 |
public int getLocalPort() | 返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的端口号。 |
public void close() | 关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和OutputStream。 |
public void shutdownInput() | 如果在套接字上调用shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。 |
public void shutdownOutput() | 禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。 |
5、TCP网络编程
5.1 基于Socket的TCP编程
5.1.1 客户端
客户端Socket的工作过程包含以下四个基本的步骤:
- 创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
- 打开连接到 Socket 的输入/出流: 使用getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输
- 按照一定的协议对 Socket 进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。
- 关闭 Socket:断开客户端到服务器的连接,释放线路
客户端创建Socket对象:
- 客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器方发起连接。Socket的构造器是:
Socket(String host,int port)throws UnknownHostException,IOException
:向服务器(域名是host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。Socket(InetAddress address,int port)throws IOException
:根据InetAddress对象所表示的IP地址以及端口号port发起连接。
- 客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求
Socket s = new
Socket(“192.168.40.165”,9999);
OutputStream out = s.getOutputStream();
out.write(" hello".getBytes());
s.close();
5.1.2 服务器端
服务器程序的工作过程包含以下四个基本的步骤:
- 调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
- 调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
- 调用 该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出流和输入流,开始网络数据的发送和接收。
- 关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字。
服务器建立 ServerSocket 对象
- ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字连接的ServerSocket对象。
- 所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象
ServerSocket ss = new ServerSocket(9999);
Socket s = ss.accept ();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int num = in.read(buf);
String str = new String(buf,0,num);
System.out.println(s.getInetAddress().toString()+”:”+str);
s.close();
ss.close();
5.1.3 例子
6、UDP网络编程
6.1 DatagramSocket&DatagramPacket
- 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
- UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
- DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
- UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样。
6.2 DatagramSocket&DatagramPacket类的常用方法
DatagramSocket的常用方法:
方法 | 描述 |
---|---|
public DatagramSocket(int port) | 创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。 |
public DatagramSocket(int port,InetAddress laddr) | 创建数据报套接字,将其绑定到指定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。 |
public void close() | 关闭此数据报套接字。 |
public void send(DatagramPacket p) | 从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。 |
public void receive(DatagramPacket p) | 从此套接字接收数据报包。当此方法返回时,DatagramPacket的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。 |
public InetAddress getLocalAddress() | 获取套接字绑定的本地地址。 |
public int getLocalPort() | 返回此套接字绑定的本地主机上的端口号。 |
public InetAddress getInetAddress() | 返回此套接字连接的地址。如果套接字未连接,则返回 null。 |
public int getPort() | 返回此套接字的端口。如果套接字未连接,则返回 -1。 |
DatagramPacket类的常用方法:
方法 | 描述 |
---|---|
public DatagramPacket(byte[] buf,int length) | 构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。 |
public DatagramPacket(byte[] buf,int length,InetAddress address,int port) | 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length参数必须小于等于 buf.length。 |
public InetAddress getAddress() | 返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。 |
public int getPort() | 返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。 |
public byte[] getData() | 返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量 offset 处开始,持续 length 长度。 |
public int getLength() | 返回将要发送或接收到的数据的长度。 |
7、URL编程
7.1 URL类
- URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
- 它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp 站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。 - URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
- 例如:
http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
- #片段名:即锚点,例如看小说,直接定位到章节
- 参数列表格式:参数名=参数值&参数名=参数值....
7.2 URL类构造器
为了表示URL,java.net 中实现了类 URL。我们可以通过下面的构造器来初始化一个 URL 对象:
public URL (String spec)
:通过一个表示URL地址的字符串可以构造一个URL对象。
例如:URL url = new URL ("http://www. atguigu.com/");
public URL(URL context, String spec)
:通过基 URL 和相对 URL 构造一个 URL 对象。
例如:URL downloadUrl = new URL(url, “download.html")
public URL(String protocol, String host, String file)
; 例如:new URL(“http”,
“www.atguigu.com”, “download. html");public URL(String protocol, String host, int port, String file)
; 例如: URL gamelan = new URL(“http”, “www.atguigu.com”, 80, “download.html");
URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常是用 try-catch
语句进行捕获。
7.3 URL类常用方法
一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性:
项目 | Value |
---|---|
public String getProtocol( ) | 获取该URL的协议名 |
public String getHost( ) | 获取该URL的主机名 |
public String getPort( ) | 获取该URL的端口号 |
public String getPath( ) | 获取该URL的文件路径 |
public String getFile( ) | 获取该URL的文件名 |
public String getQuery( ) | 获取该URL的查询名 |
URL url = new URL("https://jiejuntien.github.io/");
System.out.println("getProtocol() :"+url.getProtocol());
System.out.println("getHost() :"+url.getHost());
System.out.println("getPort() :"+url.getPort());
System.out.println("getPath() :"+url.getPath());
System.out.println("getFile() :"+url.getFile());
System.out.println("getQuery() :"+url.getQuery());
测试:
package com.feng.java1;
import java.net.MalformedURLException;
import java.net.URL;
/**
* @author 丰
* @create 2021-08-29 12:29
*/
public class URLTest {
public static void main(String[] args) {
try {
URL url = new URL("http://localhost:8080/examples/myTest.txt");
System.out.println("getProtocol() :"+url.getProtocol());
System.out.println("getHost() :"+url.getHost());
System.out.println("getPort() :"+url.getPort());
System.out.println("getPath() :"+url.getPath());
System.out.println("getFile() :"+url.getFile());
System.out.println("getQuery() :"+url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
package com.feng.java1;
import javax.net.ssl.HttpsURLConnection;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
下载网页:`https://jiajuntien.github.io/2019/10/04/%E9%9B%86%E5%90%88%E7%B1%BB/`这是我的一个测试网站。
/**
* @author 丰
* @create 2021-08-29 12:45
*/
public class URLTest1 {
public static void main(String[] args){
HttpsURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL("https://jiajuntien.github.io/2019/10/04/%E9%9B%86%E5%90%88%E7%B1%BB/");
urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.connect();
is = urlConnection.getInputStream();
fos = new FileOutputStream("12.html");
byte[] b = new byte[1024];
int len;
while ((len=is.read(b))!=-1){
fos.write(b,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is!=null)
{
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is!=null)
{
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is!=null)
{
urlConnection.disconnect();
}
}
}
}
7.4 URLConnection类
针对HTTP协议的URLConnection类:
- URL的方法 openStream():能从网络上读取数据
- 若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用URLConnection 。
- URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个 URL 对象上通过方法 openConnection() 生成对应的URLConnection对象。如果连接过程失败,将产生IOException.
- URL netchinaren = new URL (“http://www.atguigu.com/index.shtml”);
- URLConnectonn u = netchinaren.openConnection( );
URLConnection类
通过URLConnection对象获取的输入流和输出流,即可以与现有的CGI程序进行交互。
public Object getContent( ) throws IOException
public int getContentLength( )
public String getContentType( )
public long getDate( )
public long getLastModified( )
public InputStream getInputStream( )throws IOException
public OutputSteram getOutputStream( )throws IOException
URI、URL和URN的区别:
- URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。而URL是uniform resource locator,统一资源定位符,它是一种具体
的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。而URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com。也就是说,URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。 - 在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,
因此它不能是相对的。
八、反射机制
1、Java反射机制概述
1.1 Java Reflection
- Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:
反射
。
动态语言 vs 静态语言
- 动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang
。 - 静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++
。
1.2 Java反射机制研究及应用
- Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
1.3 反射相关的主要API
java.lang.Class
:代表一个类java.lang.reflect.Method
:代表类的方法java.lang.reflect.Field
:代表类的成员变量java.lang.reflect.Constructor
:代表类的构造器- … …
2、理解Class类并获取Class实例
2.1 Class 类
在Object类中定义了以下的方法,此方法
将被所有子类继承:
● public final Class getClass()
以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
● 对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
Class本身也是一个类
Class 对象只能由系统建立对象
一个加载的类在 JVM 中只会有一个Class实例
一个Class对象对应的是一个加载到JVM中的一个.class文件
每个类的实例都会记得自己是由哪个 Class 实例所生成
通过Class可以完整地得到一个类中的所有被加载的结构
Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的
Class对象
2.2 Class类的常用方法
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
Constructor[] getConstructors() | 返回一个包含某Constructor |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
Method getMethod(String name,Class … paramTypes) | 返回一个Method对象,此对象的形参类型为paramType |
//反射的操作
@Test
public void test2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class clazz = Penson.class;
//通过反射创建Peoson对象
Constructor cons = clazz.getConstructor(String.class, int.class);
Object obj = cons.newInstance("tom", 12);
System.out.println(obj);
//通过反射调用属性
Field age = clazz.getDeclaredField("age");
age.set(obj,10);
System.out.println(obj);
//通过反射调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(obj);
//反射调用私有构造器
Constructor con1 = clazz.getDeclaredConstructor(String.class);
con1.setAccessible(true);
Penson p1 = (Penson) con1.newInstance("jerry");
System.out.println(p1);
//反射调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"tianfeng");
System.out.println(p1);
//调用私有方法
Method nation = clazz.getDeclaredMethod("showNation",String.class);
nation.setAccessible(true);
Object china = nation.invoke(p1, "china");
String nation1 = (String)china;
System.out.println(nation1);
}
2.3 获取Class类的实例(四种方法)
1)前提:若已知具体的类,通过类的class
属性获取,该方法最为安全可靠,程序性能最高
实例:Class clazz = String.class;
2)前提:已知某个类的实例,调用该实例getClass()
方法获取Class对象
实例:Class clazz =“www.atguigu.com”.getClass();
3)前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()
获取,可能抛出ClassNotFoundException
实例:Class clazz=Class.forName(“java.lang.String”);
4)其他方式(不做要求)
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);
测试:
//获取class的实例对象
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时的属性
Class clazz1 = Penson.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象
Penson p1 =new Penson();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:通过Class静态方法:forName()
Class clazz3 = Class.forName("com.feng.java.Penson");
System.out.println(clazz3);
//方式四:classLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.feng.java.Penson");
System.out.println(clazz4);
System.out.println(clazz1 == clazz2);
}
2.4 哪些类型可以有Class对象?
class:
外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类interface:
接口[]:
数组enum:
枚举annotation:
注解@interfaceprimitive type:
基本数据类型void
@Test
public void test()
{
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);//true
}
3、类的加载与ClassLoader的理解
3.1 了解:类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
- 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的
<clinit>()
方法在多线程环境中被正确加锁和同步。
4、创建运行时类的对象
创建类的对象:
调用Class对象的newInstance()方法
要 求:
- 类必须有一个无参数的构造器。
- 类的构造器的访问权限需要足够。
难道没有无参的构造器就不能创建对象了吗?不是!只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
步骤如下:
- 通过Class类的getDeclaredConstructor(Class …parameterTypes)取得本类的指定形参类型的构造器
- 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
- 通过Constructor实例化对象。
package com.feng.java;
/**
* @author 田丰
* @create 2021-08-29 15:46
*/
public class Penson {
private String name;
@Override
public String toString() {
return "Penson{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Penson() {
}
public Penson(String name, int age) {
this.name = name;
this.age = age;
}
private Penson(String name) {
this.name = name;
}
public void show(){
System.out.println("我是人");
}
private String showNation(String nation){
System.out.println("我的国籍"+nation);
return nation;
}
}
//newInstance():创建对应运行时对象
@Test
public void test1() throws Exception{
Class clazz = Penson.class;
/*
newInstance():创建对应运行时对象
*/
Object o = clazz.newInstance();
System.out.println(o);
}
//构造器创建对象
@Test
public void test12() throws Exception {
Class clazz = Person.class;
Constructor cons = clazz.getDeclaredConstructor(String.class);
cons.setAccessible(true);
Object tianfeng = cons.newInstance("tianfeng");
Person p =(Person)tianfeng;
System.out.println(p);
}
5、获取运行时类的完整结构
通过反射获取运行时类的完整结构
Field、Method、Constructor、Superclass、Interface、Annotation
- 实现的全部接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的Field
1. 实现的全部接口
- public Class<?>[] getInterfaces()
确定此对象所表示的类或接口实现的接口。
2. 所继承的父类
- public Class<? Super T> getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的
Class。
3. 全部的构造器
public Constructor<T>[] getConstructors()
返回此 Class 对象所表示的类的所有public构造方法。public Constructor<T>[] getDeclaredConstructors()
返回此 Class 对象表示的类声明的所有构造方法。
Constructor类中:
- 取得修饰符:
public int getModifiers();
- 取得方法名称:
public String getName();
- 取得参数的类型:
public Class<?>[] getParameterTypes();
4. 全部的方法
public Method[] getDeclaredMethods()
返回此Class对象所表示的类或接口的全部方法public Method[] getMethods()
返回此Class对象所表示的类或接口的public的方法- Method类中:
public Class<?> getReturnType()
取得全部的返回值public Class<?>[] getParameterTypes()
取得全部的参数public int getModifiers()
取得修饰符public Class<?>[] getExceptionTypes()
取得异常信息
5. 全部的Field
public Field[] getFields()
返回此Class对象所表示的类或接口的public的Field。public Field[] getDeclaredFields()
返回此Class对象所表示的类或接口的全部Field。
Field方法中:
public int getModifiers()
以整数形式返回此Field的修饰符public Class<?> getType()
得到Field的属性类型public String getName()
返回Field的名称。
6. Annotation相关
get Annotation(Class<T> annotationClass)
getDeclaredAnnotations()
7. 泛型相关
- 获取父类泛型类型:
Type getGenericSuperclass()
- 泛型类型:
ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()
8. 类所在的包 Package getPackage()
6、调用运行时类的指定结构
6.1 调用指定方法
通过反射,调用类中的方法,通过Method类完成。步骤:
- 通过
Class
类的getMethod(String name,Class…parameterTypes)
方法取得一个Method
对象,并设置此方法操作时所需要的参数类型。 - 之后使用
Object invoke(Object obj, Object[] args)
进行调用,并向方法中传递要设置的obj对象的参数信息。
Object invoke(Object obj, Object … args)
Object
对应原方法的返回值,若原方法无返回值,此时返回null
- 若原方法若为静态方法,此时形参
Object obj
可为null
- 若原方法形参列表为空,则
Object[] args为null
- 若原方法声明为
private,
则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)
方法,将可访问private
的方法。
/*
如何操作运行时类中的指定方法
*/
@Test
public void testMethod() throws Exception {
Class<Person> clazz = Person.class;
//创建运行时对象
Person p = (Person) clazz.newInstance();
//获取指定的方法
/*
getDeclaredMethod():指明方法名和形参列表
*/
Method method = clazz.getDeclaredMethod("show",String.class);
method.setAccessible(true);
//invoke():参数一:方法调用者。参数二:实参
String invoke = (String) method.invoke(p, "中国");
System.out.println(invoke);
System.out.println("**********");
Method showdesc = clazz.getDeclaredMethod("showdesc");
showdesc.setAccessible(true);
Object invoke1 = showdesc.invoke(null);
System.out.println(invoke1);
}
6.2 调用指定属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
public Field getField(String name)
返回此Class
对象表示的类或接口的指定的public
的Field。
public Field getDeclaredField(String name)
返回此Class对象表示的类或接口的指定的Field。
在Field中:
public Object get(Object obj)
取得指定对象obj上此Field的属性内容public void set(Object obj,Object value)
设置指定对象obj上此Field的属性内容
关于setAccessible方法的使用
- Method和Field、Constructor对象都有setAccessible()方法。
- setAccessible启动和禁用访问安全检查的开关。
- 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
- 使得原本无法访问的私有成员也可以访问。
- 参数值为false则指示反射的对象应该实施Java语言访问检查。
/*
如何操作运行时类中的指定属性
*/
@Test
public void test2() throws Exception {
Class<Person> clazz = Person.class;
//1、创建运行时对象
Person p = clazz.newInstance();
//2、获取指定属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p,"tianfeng");
Object o = name.get(p);
// String o = (String) name.get(p);
System.out.println(o);
}
6.3 调用指定构造器
/*
调用运行时类的构造器
*/
@Test
public void test12() throws Exception {
Class clazz = Person.class;
Constructor cons = clazz.getDeclaredConstructor(String.class);
cons.setAccessible(true);
Object tianfeng =cons.newInstance("tianfeng");
Person p =(Person)tianfeng;
System.out.println(p);
}
7、反射的应用:动态代理
代理设计模式的原理:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
九、Java8 的其它新特性
Java 8新特性简介
- Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。
- Java 8 是oracle公司于2014年 3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性
- 速度更快
- 代码更少(增加了新的语法:Lambda 表达式)
- 强大的 Stream API
- 便于并行
- 最大化减少空指针异常:Optional
- Nashorn引擎,允许在JVM上运行JS应用
并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API
可以声明性地通过 parallel()
与 sequential()
在并行流与顺序流
之间进行切换。
1、Lambda表达式
为什么使用 Lambda 表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
Lambda 表达式:语法
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->”
, 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
左侧: 指定了 Lambda 表达式需要的参数列表。
右侧: 指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
package com.feng.java1;
import org.junit.Test;
import java.util.Comparator;
import java.util.function.Consumer;
/**
* @author 田丰
* @create 2021-08-31 14:32
*/
public class LamdbaTest1 {
//语法格式一:无参,无返回值
@Test
public void test1(){
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱共产党");
}
};
r1.run();
System.out.println("**************");
Runnable r2 = () ->System.out.println("我爱共产党");
r2.run();
}
//语法格式二:Lambda 需要一个参数,但是没有返回值。
@Test
public void test2(){
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("我爱共产党");
System.out.println("****");
Consumer<String> con1 =(String s) ->System.out.println(s);
con1.accept("我爱共产党");
}
//语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
@Test
public void test3(){
Consumer<String> con1 =(String s) ->{System.out.println(s);};
Consumer<String> con2=(s) ->{System.out.println(s);};//类型推断
con2.accept("我爱共产党");
}
//语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
@Test
public void test4(){
Consumer<String> con2=(s) ->{System.out.println(s);};//类型推断
Consumer<String> con1 =s->{System.out.println(s);};
con1.accept("我爱共产党");
}
//语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
@Test
public void test5(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
int compare = com.compare(12, 21);
System.out.println(compare);
System.out.println("**********");
Comparator<Integer> com1 = (o1,o2) ->
{
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
int compare1 = com1.compare(12, 21);
System.out.println(compare1);
}
//语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
@Test
public void test6(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
int compare = com.compare(12, 21);
System.out.println(compare);
System.out.println("******************");
Comparator<Integer> com1 =(o1, o2)-> o1.compareTo(o2);
int compare1 = com1.compare(12, 21);
System.out.println(compare1);
}
}
2、函数式(Functional)接口
什么是函数式(Functional)接口:
只包含一个抽象方法的接口,称为函数式接口
。- 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
- 我们可以在一个接口上使用
@FunctionalInterface
注解,这样做可以检查它是否是一个函数式接口。同时javadoc
也会包含一条声明,说明这个接口是一个函数式接口。 在
java.util.function包下定义了Java 8 的丰富的函数式接口
如何理解函数式接口:
- Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即
java不但可以支持OOP还可以支持OOF(面向函数编程)
- 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——
函数式接口。
- 简单的说,在Java8中,
Lambda表达式就是一个函数式接口的实例。
这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
Java 内置四大核心函数式接口
package com.feng.java1;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* @author 丰
* @create 2021-08-31 17:37
*/
public class LamdbaTest2 {
@Test
public void test1(){
happy(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("学习加油"+aDouble);
}
});
System.out.println("*********");
happy(400,aDouble ->System.out.println("学习加油"+aDouble));
}
public void happy(double money, Consumer<Double> con){
con.accept(money);
}
@Test
public void test2(){
List<String> str = Arrays.asList("北京","天京","普京","天井");
List<String> filterlist = filterString(str, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(filterlist);
System.out.println("***********");
List<String> filterlist1 = filterString(str, t -> t.contains("京"));
System.out.println(filterlist1);
Iterator<String> iterator = filterlist1.iterator();
while (iterator.hasNext())
{
System.out.println(iterator.next());
}
}
public List<String> filterString(List<String> list , Predicate<String> pre){
ArrayList<String> filterList = new ArrayList<>();
for (String s :list){
if (pre.test(s)){
filterList.add(s);
}
}
return filterList;
}
}
3、方法引用与构造器引用
3.1 方法引用(Method References)
当要传递给
Lambda体的操作,已经有实现的方法了,可以使用方法引用!
- 方法引用可以看做是
Lambda
表达式深层次的表达。换句话说,方法引用就是Lambda
表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda
表达式的一个语法糖。 - 要求:
实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
- 格式:使用操作符
“::”
将类(或对象) 与 方法名分隔开来。 - 如下三种主要使用情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
@Test
public void test(){
//1、对象::实例方法名
Consumer<String> str1 = s->System.out.println(s);
str1.accept("Lamdba:"+"北京");
Consumer<String> str2 = System.out::println;
System.out.print("对象::实例方法名:");
str2.accept("北京");
System.out.println("*******");
//2、类::静态方法名
//例子1:
Comparator<Integer> com = (o1,o2)->Integer.compare(o1,o2);
System.out.println("Lamdba:"+com.compare(12, 21));
Comparator<Integer> com1 =Integer::compare;
System.out.println("类::静态方法名"+com1.compare(12, 21));
System.out.println("*******");
//例子2:
Function<Double,Long> fun = (d) -> Math.round(d);
System.out.println("Lamdba:"+fun.apply(12.3));
Function<Double,Long> function1 =Math::round;
System.out.println("类::静态方法名"+function1.apply(12.2));
System.out.println("*******");
//3、类::实例方法名
BiPredicate<String,String> pre1 = (t1, t2)->t1.equals(t2);
System.out.println("Lamdba:"+pre1.test("abcd", "abcd"));
BiPredicate<String,String> pre2 =String::equals;
System.out.println("类::静态方法名"+pre2.test("abcd", "abcd"));
}
3.2 构造器引用
格式: ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
3.3 数组引用
格式: type[] :: new
4、强大的Stream API
4.1 Stream API说明
- Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是Stream API。
Stream API ( java.util.stream)
把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。- Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询
。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
4.2 为什么要使用Stream API
- 实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。
- Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。
前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。
4.3Stream到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,Stream讲的是计算!”
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
4.2 Stream 的操作三个步骤
4.2.1 创建 Stream
一个数据源(如:集合、数组),获取一个流
创建 Stream方式一:通过集合
创建 Stream方式二:通过数组
创建 Stream方式三:通过Stream的of()
创建 Stream方式四:创建无限流
4.2.2 中间操作
- 一个中间操作链,对数据源的数据进行处理。
- 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
1-筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收 Lambda , 从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |
2-映 射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
3-排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
4.2.3 终止操作(终端操作)
- 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例
如:List、Integer,甚至是 void 。 - 流进行了终止操作后,不能再次使用。一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使
1-匹配与查找
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了) |
2-归约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 Optional |
备注: map 和 reduce 的连接通常称为 map-reduce 模式,因 Google
用它来进行网络搜索而出名。
3-收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。
另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
5、Optional类
-
到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
-
Optional<T> 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
-
Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
-
Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
-
创建Optional类对象的方法:
方法 | 描述 |
---|---|
Optional.of(T t) | 创建一个 Optional 实例,t必须非空; |
Optional.empty() | 创建一个空的 Optional 实例 |
Optional.ofNullable(T t) | t可以为null |
- 判断Optional容器中是否包含对象:
方法 | 描述 |
---|---|
boolean isPresent() | 判断是否包含对象 |
void ifPresent(Consumer<? super T> consumer) | 如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。 |
- 获取Optional容器的对象:
方法 | 描述 |
---|---|
T get() | 如果调用对象包含值,返回该值,否则抛异常 |
T orElse(T other) | 如果有值则将其返回,否则返回指定的other对象。 |
T orElseGet(Supplier<? extends T> other) | 如果有值则将其返回,否则返回由Supplier接口实现提供的对象。 |
T orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。 |
package com.feng.java4;
import org.junit.Test;
import java.util.Optional;
/**
* @author 丰
* @create 2021-09-01 19:18
*/
public class OptionalTest {
@Test
public void test(){
Girl girl =new Girl();
Optional<Girl> optional = Optional.of(girl);
}
@Test
public void test2(){
Girl girl =new Girl();
//girl =null;
Optional<Girl> optional = Optional.ofNullable(girl);
System.out.println(optional);
Girl girl1 = optional.orElse(new Girl("nihao"));
System.out.println(girl1);
}
public String getName(Boy boy){
//return boy.getGirl().getName();
//
Optional<Boy> boy1 = Optional.ofNullable(boy);
Boy boy2 = boy1.orElse(new Boy(new Girl("王菲")));
Girl name = boy2.getGirl();
// System.out.println(name);
Optional<Girl> girl = Optional.ofNullable(name);
Girl girl1 = girl.orElse(new Girl("guli1"));
return girl1.getName();
}
@Test
public void test3(){
Boy boy = new Boy();
boy=null;
String name = getName(boy);
System.out.println(name);
}
}
结束了!!!
你的坚持,终将美好。。。。。。。