阿一的日常Java笔记,实时更新,有什么问题可以留言交流一下,大家一起共同进步!!!
1.Java基础
1.1.基本语法
1.1.1.关键字
定义:被java赋予特殊含义的字符串(单词);
关键字中字母全为小写;
保留字:现Java版本尚未使用,但以后版本可能会作为关键字使用。
保留字:goto,const
1.1.2.变量的分类
注意:void也是一个基本数据类型,但我们无法对void操作,所有不对void进行分类;
数据类型 | 字节数 | 说明 |
---|---|---|
btye | 1 | |
short | 2 | |
int | 4 | |
long | 8 | 声明时要以l或L |
float | 4 | float表示数值的范围比long还大 |
double | 8 | 浮点型的常量,默认类型为:double |
char | 2 | |
boolean | boolean占用的字节 |
1.4面向对象
类的赋值的有哪些?先后顺序如何?
- 默认初始化
- 显示初始化
- 代码块中初始化
- 构造器中初始化
- 通过“对象.属性”或“对象.方法”的方式赋值
代码块、构造器、main方法的执行顺序
静态代码块>main方法>普通代码块>构造器
1.4.6.抽象类与抽象方法
abstract关键字的使用
- abstract:抽象的
- abstract可以用来修饰的结构:类、方法
abstract修饰类:抽象类
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
abstract修饰方法:抽象方法
- 抽象方法只有方法的声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
- 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
- 若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
abstract使用上的注意点:
- abstract不能用来修饰:属性、构造器等结构
- abstract不能用来修饰私有方法、静态方法、final的方法、final的类
- 私有方法不能被重写
- 在子类中同名同参数的静态方法被认为是被重写的方法
- final修饰的方法不能被重写,但是abstract要求要重写,final修饰的类一样
创建抽象类的匿名子类对象
public class PersonTest {
public static void main(String[] args) {
Person p = new Person() {
@Override
public void eat() {
}
@Override
public void work() {
}
};
}
}
abstract class Person{
public abstract void eat();
public abstract void work();
}
1.4.7.接口
是自定义的一组规范,一定程度上解决了类的单继承;
-
接口使用interface来定义
-
Java中,接口和类是并列的两个结构
-
如何定义接口:定义接口中的成员
- JDK7及以前:只能定义全局常量和抽象方法
- 全局常量:public static final的.但是书写时,可以省略不写
- 抽象方法:public abstract的
- JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)
- JDK7及以前:只能定义全局常量和抽象方法
-
接口中不能定义构造器的!意味着接口不可以实例化
-
Java开发中,接口通过让类去实现(implements)的方式来使用.
- 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
- 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
-
Java类可以实现多个接口(弥补了Java单继承性的局限性)
- 格式:class AA extends BB implements CC,DD,EE
-
接口与接口之间可以继承,而且可以多继承
-
接口的具体使用,体现多态性
-
面试题:抽象类与接口有哪些异同?
什么是驱动?驱动就是一组实现类的集合
创建接口匿名实现类的对象
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//2. 创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
//3. 创建了接口的匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
com.transferData(phone);
//4. 创建了接口的匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("mp3开始工作");
}
@Override
public void stop() {
System.out.println("mp3结束工作");
}
});
}
}
class Computer{
public void transferData(USB usb){//USB usb = new Flash();
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
接口练习题(如何比较两个对象的大小)
public interface CompareObject {
int compareTo(Object obj);
}
class Circle{
private Double redius;
public Double getRedius() {
return redius;
}
public void setRedius(Double redius) {
this.redius = redius;
}
}
class ComparableCircle extends Circle implements CompareObject{
@Override
public int compareTo(Object obj) {
if (this == obj){
return 0;
}
if (obj instanceof ComparableCircle){
ComparableCircle c = (ComparableCircle)obj;
//方法一
// return this.getRedius() > c.getRedius() ? 1 : -1;
//方法二
return this.getRedius().compareTo(c.getRedius());
}else {
throw new RuntimeException("传入的数据类型不匹配");
}
}
}
class ComparableCircleTest{
public static void main(String[] args) {
ComparableCircle c1 = new ComparableCircle();
ComparableCircle c2 = new ComparableCircle();
c1.setRedius(2.1);
c2.setRedius(2.3);
System.out.println(c1.compareTo(c2)); //-1
}
}
1.n.设计模式
1.n.1.单例设计模式
采取一定的方法保证整个软件系统中,对某个类只能存在一个对象实例。
-
懒汉式(加载时间过长)
public class Singleton { private Singleton(){} private static Singleton singleton = new Singleton(); public static Singleton getInstance(){ return singleton; } }
-
饱汉式(在需要的时候才创建,节省内存)
public class Singleton { private Singleton(){} private static Singleton singleton = null; public static Singleton getInstance(){ if (singleton == null){ singleton = new Singleton(); } return singleton; } }
单例设计模式的应用场景
- 网站的计数器
- 应用程序的日志应用
- 数据库连接池
- 读取配置文件的类
- Application也是单例的典型应用
1.n.2.模板方法设计模式
- 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的,我们可以把不确定的部分暴露出去,让子类实现;
- 在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了,但是某些部分易变,我们可以将易变的部分抽象出来,让不同的子类去实现,这就是模板模式;
1.n.3.代理模式
代理模式是java开发中使用较多的一种设计模式,代理设计就是为其他对象提供一种代理以控制对这个对象的访问;
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork{
public void browse();
}
//代理类和被代理类共同实现是一个接口(Network)
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() { //联网工作,由ProxyServer代替Server来做,同时还要做Server没有做的一些任务
check();
work.browse();
}
}
1.n.4.工厂模式
工厂模式:实现了创建者与调用者的分离,即将即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
1.简单工厂模式
class BYD implements Car {
@Override
public void run() {}
}
class OD implements Car {
@Override
public void run() {}
}
class CarFactory{
public static Car getCar(String type){
if ("BYD".equals(type)){
return new BYD();
}else if ("OD".equals(type)){
return new OD();
}else {
return null;
}
}
}
2.工厂方法模式
interface Car {
void run();
}
class BYD implements Car {
@Override
public void run() {}
}
class OD implements Car {
@Override
public void run() {}
}
interface CarFactory{
Car getCar();
}
class ODFactory implements CarFactory{
@Override
public Car getCar() {
return new OD();
}
}
class BYDFactory implements CarFactory{
@Override
public Car getCar() {
return new BYD();
}
}
3.反射机制的工厂模式
4.抽象工厂模式
2.java高级
2.1.多线程
1.基本概念:程序、进程、线程
- **程序(program)**是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- **进程(process)**是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
- 如:运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的;
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
- 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的线程作为调度和执行的单位
- 每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享方法区和堆,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。
- 但多个线程操作共享的系统资源可能就会带来安全的隐患
1.并发和并行的区别
-
并发是指一个处理器同时处理多个任务
- 并发:将一个处理器运行时间划分成n个时间片,每个时间片内只能处理一个线程;而CPU将会轮换调度多个线程执行,在逻辑上多个线程是同时进行的,但是每个时间片内只有一个线程在执行;
-
并行是指多个处理器同时处理多个任务
- 并行:多个处理器,每个处理器同时处理不同的线程;
-
并发是逻辑上同时发生,并行是物理上同时发生
2.多线程的优点
缺点:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),比用多个线程来完成用的时间更短
优点:
- 提高应用程序的响应,对于图形化界面更有意义,可增强用户的体验;
- 提高计算机系统CPU的利用率;
- 改善程序结构。将冗长的进程划分成多个线程,易于理解;
3.多线程的应用场景:
- 程序需要同时进行多个任务;
- 程序需要实现一些需要等待的任务时:用户输入、文件读写、网络操作、搜索等;
- 需要运行一些后台程序的时候;
2.线程的创建和使用
线程创建方式一:Thread子类
1.创建Thread的子类
/**
* 多线程的创建方式,方式一:继承与Thread类
* 1.创建一个类继承与Thread
* 2.重写Thread中的run() --> 将此线程需要执行的操作都放在run()方法中
* 3.创建Thread的子类的实例
* 4.调用该实例的start()方法
* Description: JavaSenior
* Created by dell on 2021/7/3 18:32
*/
public class ThreadTest {
@Test
public void test() {
MyThread myThread1 = new MyThread(); //要想同时启动多个线程就要同时创建多个对象
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
System.out.println(i + ", " + Thread.currentThread().getName());
}
}
}
}
2.通过Thread的匿名子类来实现多线程
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 != 0)
System.out.println(i + ", " + Thread.currentThread().getName());
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 == 0)
System.out.println(i + ", " + Thread.currentThread().getName());
}
}
}.start();
}
1.线程中常用的方法
- start(): 启动当前线程,调用当前线程的run方法
- run(): 通常需要重写Thread类中的方法,将创建的线程要执行的操作声明在此方法中
- currentThread(): 静态方法,返回执行当前代码的线程
- getName(): 获取当前线程的名字
- setName(): 设置当前线程的名字(还可以用Thread(String name)构造器来设置)
- yield(): 释放当前线程
- join: 在线程a中调用线程b的join(),此时线程a就会进入阻塞状态,直到线程b执行完以后,线程a才进入就绪态
- stop(): 已过时,当执行此方法是,强制接受当前线程
- sleep(long millitime): 让当前线程阻塞指定的毫秒数
- isAlive(): 判断当前线程是否存活
2.线程的调度
- setPriority(): 设置线程的优先级
- Thread.MAX_PRIORITY=10, 最高优先级
- Thread.MIN_PRIORITY=1, 最低优先级
- Thread.NORM_PRIORITY=5, 默认优先级
- getPriority(): 获取当前线程的优先级值
线程创建方式二:Runnable接口
- 创建一个类实现Ruunable接口
- 实现接口里的run()方法;
- 创建实现类的实例
- 将实现类的实例作为参数传递到Thread构造器中,创建Thread对象,调用start()方法
class Ayi_04_MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
public class Ayi_04_Runnable {
@Test
public void test() {
Ayi_04_MyThread myThread = new Ayi_04_MyThread();
Thread thread1 = new Thread(myThread);
thread1.setName("线程1:");
thread1.start();
//可以共用一个Runnable接口实现类,更好的实现数据共享
Thread thread2 = new Thread(myThread);
thread2.setName("线程2:");
thread2.start();
}
}
底层原理
在Thread类的run方法中加了一个判断
@Override
public void run() {
//如果target的值不为空,就调用target中的run方法
if (target != null) {
target.run();
}
}
Thread构造器中对target变量做了初始化
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
两种创建方式的异同
- 在实际开发中,我们优先选择Runnable接口的方式:
- 实现的方式没有类的单继承的局限性
- 实现的方式更适合处理多线程数据共享的情况
- 联系:Thread也实现了Runnable接口
- 两个方式都需要重写run(),将线程需要执行的逻辑声明在run()方法中;
3.线程的生命周期
4.线程的同步
方式一:同步代码块
-
任何对象都可以当作同步锁,但是要求多个线程共用一把同步锁才可以解决线程安全问题
-
局限性:操作同步代码时,只能有一个线程参与,相当于单线程的过程,影响了系统的执行效率
synchronized(同步锁){//需要被同步的代码}
重点:
-
在实现方式的多线程里可以考虑使用this调用当前对象作为同步锁
-
在继承方式的多线程里可以考虑使用当前对象的类作为同步锁
-
明确同步代码,不能包多了,也不能包少了
方式二:同步方法
- 非静态:默认锁是this(实现方式使用)
- 静态:默认锁是当前类对象(继承方式使用)
class Ayi_01_MyThread implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() {
//while (true) { 如果将while也作为同步代码上锁,那么后果是,只有一个线程一顿操作将所有的票都卖了
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号: " + ticket);
ticket--;
}
}
}
释放锁的方法
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的**wait()**方法,当前线程暂停,并释放锁
不会释放锁的方法
- sleep()方法、yield()方法、suspend()方法
方式三:Lock方法
private int ticket = 100;
//无参构造器
//private ReentrantLock lock = new ReentrantLock();
//有参构造器,设置该锁是否是公平的,即是否采用先进先出方式分配线程
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true){
try {
lock.lock(); //手动上锁
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "ticket: " + ticket);
ticket--;
}else {
break;
}
} finally {
lock.unlock(); //手动解锁
}
}
}
synchronized和Lock方法的异同?
- Lock是手动锁(需要手动开启和关闭锁),synchronized隐式锁,出了作用域自动释放;
- Lock只有代码块锁,没有方法锁;
- 使用Lock锁,JVM将花费更少的时间来调度线程,性能更好;并且具有更好的拓展性(提供更多的子类)
使用顺序:Lock ——> 同步代码块(进入了方法体,分配了非共享的资源) ——> 同步方法(在方法体之外)
将单例模式中的懒汉式改为线程安全的
class Bank {
private Bank() {
}
private static Bank instance = null;
//方式一:效率差
/*public static synchronized Bank getInstance(){
if (instance == null){
instance = new Bank();
}
return instance;
}*/
//方式二:
public static Bank getInstance() {
//效率差
/*synchronized (Bank.class){
if (instance == null){
instance = new Bank();
}
return instance;
}*/
if (instance == null) { //当其他线程调用该方法时,如果实例已经存在,就不用在进入同步代码了,效率更高
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
5.线程的通信
三个方法wait()、notify()、notifyAll()
- wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中;
- wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器;否则就会出现IllegalMonitorStateException异常
- wait()、notify()、notifyAll()三个方法是定义在java.lang.Object类中的
@Override
public void run() {
while (true){
synchronized (this){
//notify()/wait()/notifyAll()默认的调用是当前类,如果是静态方法里的,默认就是当前类对象,就是同步锁
//notify()/wait()/notifyAll()三者的调用对象和监视器必须是同一个对象
this.notify(); //唤醒阻塞的线程,如果有多个线程的话,就唤醒优先级较高的哪个
if (i <= 100){
System.out.println(Thread.currentThread().getName() + ", " + i + ", 优先级: " + Thread.currentThread().getPriority());
i++;
}else {
break;
}
try {
wait(); //能够释放当前线程持有的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
sleep()和wait()方法的异同?
- 相同点:调用两个方法都可以是当前线程进入阻塞状态;
- 不同点:
- 声明的位置不同:sleep()声明在Thread类中,wait()声明在Object类中
- 调用的要求不同:sleep()在任何地方都可以调用,wait()必须在同步代码块或同步方法中调用;
- 是否释放同步锁:sleep()不会释放当前线程的同步锁,wait()会释放当前线程的同步锁;
生产者消费者问题
producer
public class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("Producer开始生产产品");
while (true) {
try {
Thread.sleep(1000); //调整睡眠时间可以调整生产者的生产速度
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduction();
}
}
}
consumer
public class Customer implements Runnable {
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("consumer开始消费产品");
while (true) {
try {
Thread.sleep(2000); //调整睡眠时间可以调整消费者的消费速度
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduction();
}
}
}
clerk
public class Clerk {
private int[] production;
private int index = -1;
public Clerk(int length) {
production = new int[length];
}
public synchronized void consumeProduction() {
if (isEmpty()){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println("consumer: " + production[index--]);
notify(); //消费者消费了一个产品之后就可以唤醒生产者
}
}
public synchronized void produceProduction() {
if (isFull()){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
Random random = new Random();
int num = random.nextInt();
System.out.println("producer: " + num);
production[++index] = num;
notify(); //生产者生产了一个产品之后就可以唤醒消费者
}
}
public boolean isFull() {
return index == production.length - 1;
}
public boolean isEmpty() {
return index == -1;
}
}
Test
public class MyTest {
public static void main(String[] args) {
Clerk clerk = new Clerk(10);
Customer customer = new Customer(clerk);
Producer producer = new Producer(clerk);
Thread thread1 = new Thread(customer, "customer");
Thread thread2 = new Thread(producer, "productor");
thread1.start();
thread2.start();
}
}
6.JDK5.0新增线程创建方式
创建线程的方式三:Callable接口
Callable接口
- 与Runnable接口相比Callable接口更强大
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
Future接口
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- FutureTask是Future接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
public class Ayi_01_Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//第三步,创建实现类的实例
NumThread numThread = new NumThread();
//第四步,创建FutureTask类的实例
FutureTask<Integer> futureTask = new FutureTask<>(numThread);
//第五步,开启线程
new Thread(futureTask).start();
//获取返回值
Integer sum = futureTask.get();
System.out.println("总和为: " + sum);
}
}
//第一步,创建Callable接口的实现类
class NumThread implements Callable<Integer> {
//第二步,重写call()方法,声明此线程的执行逻辑
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(i);
sum += i;
}
}
//将结果返回
return sum;
}
}
创建线程的方式四:线程池
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止;
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor(一般使用该子类)
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable接口实现的线程
- <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable接口实现的线程
- void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
public class Ayi_02_ThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建线程池
ThreadPoolExecutor service = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
//设置线程池属性
// service.setCorePoolSize(10);
// service.setKeepAliveTime();
//2.创建Runnable实现类类实例
Ayi_02_NumThread01 numThread01 = new Ayi_02_NumThread01();
//3.开启线程
service.execute(numThread01);
//创建Callable接口实现类实例
Ayi_02_NumThread02 numThread02 = new Ayi_02_NumThread02();
//开启线程,返回Future对象
Future<Integer> future = service.submit(numThread02);
//获得返回值
System.out.println(future.get());
service.shutdown();
}
}
class Ayi_02_NumThread01 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class Ayi_02_NumThread02 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
sum += i;
}
}
return sum;
}
}
面试题:创建线程有几种方式: 四种!!!
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池
2.2常用类
1.字符串相关的类
String字符串的一些特性:
- String声明为final的,不可以被继承
- String实现了Serializable接口:表示字符串是支持序列化的;实现了Comparable接口,字符串可以比较大小
- String内部定义了final char[] value用于存储串数据
- final说明String字符串序列是不可变的,所有对字符串的修改操作,都是新建一个字符串
- 字符串常量池:
- 通过字面量的方式给字符串赋值,会将字面量保存在字符串常量池中。
- 字符串常量池中是不会保持相同内容的字符串的。
String对象的创建
String str = "hello";
//本质上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);
字面量方式定义字符串和new String()方式创建对象的区别?
- 字面量只有在第一次出现的时候才会在字符串常量池中创建一个char [],之后就会直接引用常量池中的该对象;
- new String(),每次都会在堆中创建一个对象,每次创建的对象的地址都不同
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
System.out.println(str1 == str2); //true
System.out.println(str1 == str3); //false
System.out.println(str1 == str4); //false
System.out.println(str3 == str4); //false
}
面试题:String str = new String(“abc”);会在内存中创建几个对象?1或2
- 如果字符串常量池中没有"abc",就只创建了一个对象
- 如果常量池中没有"abc",就会在常量池中额外创建一个对象
字符串拼接问题是否在常量池
- 当两个字面量进行拼接时,拼接的结果是在常量池中的
- 只有拼接中有变量,拼接的结果就是在堆中的
- 如果拼接之后调用intern()方法,无论拼接中是否有变量,结果都是在常量池中的
public static void main(String[] args) {
String s1 = "java";
String s2 = "EE";
String s3 = "java" + "EE";
String s4 = "javaEE";
String s5 = s1 + "EE";
String s6 = "java" + s2;
String s7 = (s1 + s2).intern(); //结果在常量池中
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //false
System.out.println(s3 == s6); //false
System.out.println(s3 == s7); //true
System.out.println(s5 == s6); //false
System.out.println(s5 == s7); //false
}
字符串常用的方法
-
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():返回字符串的副本,忽略前导空白和尾部空白
-
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(不包含)的一个子字符串。
-
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
-
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
-
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
-
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个,如果超过了,剩下的全部都放到最后一个元素中。
String str = "12hello34world5java7891mysql456";
//把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
System.out.println(string);
String str = "12345";
//判断str字符串中是否全部由数字组成,即有1-n个数字组成
boolean matches = str.matches("\\d+");
System.out.println(matches); //true
String tel = "0571-4534289";
//判断这是否是一个杭州的固定电话
boolean result = tel.matches("0571-\\d{7,8}");
System.out.println(result); //true//true
String str = "hello|world|java";
String[] strs = str.split("\\|");
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
System.out.println();
String str2 = "hello.world.java";
String[] strs2 = str2.split("\\.");
for (int i = 0; i < strs2.length; i++) {
System.out.println(strs2[i]);
}
以上方法都可以迭代调用
String与基本数据类型转换、包装类、数组等等
@Test
public void test1() throws UnsupportedEncodingException {
//转基本数据类型
String str1 = "123";
int num = Integer.parseInt(str1);
System.out.println("num=" + num);
String str3 = "123.0";
double parseDouble = Double.parseDouble(str3);
System.out.println("parseDouble=" + parseDouble);
//基本数据类型转字符串
String str2 = String.valueOf(num);
str2 = num + "";
System.out.println("str2=" + str2);
//字符串转char[]
String str4 = "abc123";
char[] charArray = str4.toCharArray();
System.out.println("charArray=" + Arrays.toString(charArray));
//char[]转字符串
String str5 = new String(charArray);
System.out.println("str5=" + str5);
//字符串转byte[]
String str6 = "abc123中国";
byte[] bytes = str6.getBytes(); //使用默认的编码集编码
System.out.println("bytes=" + Arrays.toString(bytes));
byte[] gbks = str6.getBytes("gbk");
System.out.println("gbks=" + Arrays.toString(gbks));
//byte[]转字符串
String str7 = new String(bytes); //使用默认的编码集解码
System.out.println("str7=" + str7);
String str8 = new String(gbks, "gbk");
System.out.println("str8=" + str8); //使用默认的编码集解码
}
字符串与字符数组相转换
字符数组==>字符串
- String 类的构造器:String(char[]) 和 String(char[],int offset,intlength) 分别用字符数组中的全部字符和部分字符创建字符串对象。
字符串==>字符数组
- public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。
- public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。
字符串与比特数组相转换
字节数组==>字符串
- String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
- String(byte[],int offset,int length) :用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象。
字符串==>字节数组
- public byte[] getBytes() :使用平台的默认字符集将此 String 编码为byte 序列,并将结果存储到一个新的 byte 数组中。
- public byte[] getBytes(String charsetName) :使用指定的字符集将 此 String 编码到 byte 序列,并将结果存储到新的 byte 数组
StringBuffer和StringBuilder
String、StringBuffer、StringBuilder三者的异同?
- String是不可变序列;底层使用char[]存储
- StringBuffer是可变序列;线程安全的,效率低;底层使用char[]存储
- StringBuilder是可变序列,jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
源码分析:
String str = new String(); //char[] value = new char[0];
String str1 = new String("abc"); //char[] value = new char[]{'a', 'b', 'c'};
StringBuffer sb1 = new StringBuffer(); //char[] value = new char[16]; 底层创建了一个长度为16的数组
System.out.println(sb1.length()); //0
sb1.append('a'); //value[0] = 'a';
sb2.append('b'); //value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc");
//char[] value = new char["abc".length() + 16];底层创建了一个长度为字符串长度+16的数组
问题1: StringBuffer字符串长度获取
在StringBuffer的父类中定义了一个count属性;用来记录char[]中最后一个字符串的位置,即字符串的长度
length函数返回的就是count;
问题2:扩容问题
扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组
思路:新建一个容量大的char数组,将原来数组中的数据copy到新数组中,即可扩容;
- 默认情况下扩容为原来的容量的2倍+2;
- 在实际开发中我们要避免扩容问题的出现,因为效率太低;所以我们就要在初始化的时候提前定义好较大的容量:new StringBuffer(int capacity);指定容量
常用方法
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() :把当前字符序列逆转
//以上方法都支持方法链调用
public int indexOf(String str)
public String substring(int start,int end)
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
//增: append()
//删: delete(int start, int end)
//改: setCharAt(int n, char ch)/replace(int start, int end, String str)
//查: charAt(int n)
//插: insert(int offset, xxxx)
//长度: length()
2.日期时间API
jdk8之前的日期API
- System类currentTimeMillis();
@Test
public void test1() {
long timeMillis = System.currentTimeMillis();
System.out.println("timeMillis = " + timeMillis);
}
-
java.util.Date和java.sql.Date
- java.util.Date 子类:java.sql.Date
- 两个构造器的使用
- new Date()==>获取当前的时间
- new Date(long timeMills)==>获取指定时间戳下的时间
- 两个方法的使用
- toString()
- getTime()==>获取当前Data对象的时间戳
Date date1 = new Date();
System.out.println("date1 = " + date1); //date1 = Tue Jul 06 09:47:42 CST 2021
System.out.println("date1.getTime() = " + date1.getTime()); //date1.getTime() = 1625536103670
Date date2 = new Date(123456789L);
System.out.println("date2 = " + date2); //date2 = Fri Jan 02 18:17:36 CST 1970
java.sql.Date date3 = new java.sql.Date(123456789L);
System.out.println("date3 = " + date3); //date3 = 1970-01-02
//将java.util.Date转换成java.sql.Date
java.sql.Date date4 = new java.sql.Date(date1.getTime());
System.out.println("date4 = " + date4); //date4 = 2021-07-06
- SimpleDateFormat
@Test
public void test1() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化
Date date = new Date();
System.out.println("date = " + date);
String format = sdf.format(date);
System.out.println("format = " + format);
//解析
String str = "2019-08-12 12:00:00";
//设置解析日期字符串的模式,可以在创建SimpleDateFormat(String pattern);设置
sdf.applyPattern("yyyy-MM-dd hh:mm:ss");
sdf.parse(str);
System.out.println("str = " + str);
}
-
Calendar
-
创建实例:
- 方式1:new GregorianCalendar() ===>是Calendar的子类
- 方式2:Calendar.getInstance();返回的也是GregorianCalendar类的实例;
-
方法的使用:
-
get()
注意:获取月份时:一月是0,二月是1,以此类推,12月是11
获取星期时:周日是1,周二是2,。。 。。周六是7
-
set()
-
add()
-
getTime()
-
setTime()
-
@Test
public void test() {
Calendar calendar = Calendar.getInstance();
//1.get()
int day = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println("day = " + day);
//2.set()
calendar.set(Calendar.DAY_OF_MONTH, 8);
int day1 = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println("day1 = " + day1);
//add()
calendar.add(Calendar.DAY_OF_MONTH, 3);
int day2 = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println("day2 = " + day2);
calendar.add(Calendar.DAY_OF_MONTH, -3);
int day3 = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println("day3 = " + day3);
//getTime()
Date date = calendar.getTime();
System.out.println("date = " + date);
//setTime()
Date date1 = new Date(123456798L);
System.out.println("date1 = " + date1);
calendar.setTime(date1);
int day4 = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println("day4 = " + day4);
}
jdk8中的日期API
jdk8.0之前的日期API的缺陷:
- 可变性:像日期、时间这样的类应该是不可变的;
- 偏移性:Date中的年份都是从1900年开始,月份都是从0开始的;
- 格式化:格式化只对Date类型数据有效,无法应用于Calendar;
- 安全性:Date、Calendar都是线程不安全的,此外还不能处理闰秒的情况;
新日期API
- java.time – 包含值对象的基础包
- java.time.chrono – 提供对不同的日历系统的访问
- java.time.format – 格式化和解析时间和日期
- java.time.temporal – 包括底层框架和扩展特性
- java.time.zone – 包含时区支持的类
大多数情况只会用到基础包和format包,偶尔会用到temporal包
LocalDate、LocalTime、LocalDateTime
- 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() | 从当前对象减去几月、几周、几天、几年、几小时 |
@Test
public void test1() {
System.out.println("now()==============================");
//now(): 获取当前日期,时间,日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDate = " + localDate); //2021-07-06
System.out.println("localTime = " + localTime); //18:05:16.139
System.out.println("localDateTime = " + localDateTime); //2021-07-06T18:05:16.140
System.out.println("of()===============================");
//of(): 根据指定日期/时间创建对象(没有偏移量)
LocalDateTime localDateTimeWithOf = LocalDateTime.of(1999, 06, 12, 6, 30, 0);
System.out.println("localDateTimeWithOf = " + localDateTimeWithOf); ///1999-06-12T06:30
//getXxx():
System.out.println("getXxx()===========================");
int dayOfMonth = localDateTime.getDayOfMonth();
System.out.println("dayOfMonth = " + dayOfMonth); //6
DayOfWeek dayOfWeek = localDateTime.getDayOfWeek(); //TUESDAY
System.out.println("dayOfWeek = " + dayOfWeek);
System.out.println("dayOfWeek.getValue() = " + dayOfWeek.getValue()); //2
System.out.println("with()=============================");
//with(): 修改指定值===>返回一个新的对象
LocalDateTime localDateTime1 = localDateTimeWithOf.withYear(2021);
System.out.println("localDateTime1 = " + localDateTime1); //2021-06-12T06:30
LocalDateTime localDateTime2 = localDateTime1.withDayOfYear(120);
System.out.println("localDateTime2 = " + localDateTime2); //2021-04-30T06:30
System.out.println("plusXxx()==========================");
//plusXxx(): 在指定的年月日/时间上加上指定的值===>返回新的对象
LocalDateTime localDateTime3 = localDateTime2.plusSeconds(30);
System.out.println("localDateTime3 = " + localDateTime3); //2021-04-30T06:30:30
LocalDateTime localDateTime4 = localDateTime3.plusHours(8);
System.out.println("localDateTime4 = " + localDateTime4); //2021-04-30T14:30:30
System.out.println("minusXxx()=========================");
//minusXxx(): 在指定的年月日/时间上减去指定的值===>返回新的对象
LocalDateTime localDateTime5 = localDateTime4.minusHours(8);
System.out.println("localDateTime5 = " + localDateTime5); //2021-04-30T06:30:30
LocalDateTime localDateTime6 = localDateTime5.minusDays(120);
System.out.println("localDateTime6 = " + localDateTime6); //2020-12-31T06:30:30
}
Instant
@Test
public void test1() {
//now(): 获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println("instant = " + instant);
//获取指定偏移量的标准时间,+表示东区;-表示西区
//获取东八区,即北京时间
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
//获取标准时间对应的时间戳
System.out.println("instant.toEpochMilli() = " + instant.toEpochMilli());
//通过指定的毫秒数,获取Instant实例
Instant instant1 = Instant.ofEpochMilli(1625569576277L);
System.out.println("instant1 = " + instant1.atOffset(ZoneOffset.ofHours(8)));
}
DateFormatter
格式化和解析日期时间
- 预定义的标准格式:ISO_LOCAL_DATE_TIME; ISO_LOCAL_DATE; ISO_LOCAL_TIME
- 本地化相关的格式:ofLocalizedDateTime(FormatStyle.LONG)
- 自定义的格式:ofPattern()
@Test
public void test1() {
//1.预定义的标准格式:ISO_LOCAL_DATE_TIME; ISO_LOCAL_DATE; ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
LocalDateTime localDateTime = LocalDateTime.now();
String format = formatter.format(localDateTime);
System.out.println("format = " + format); //2021-07-06T19:57:29.391
TemporalAccessor parse = formatter.parse("2021-07-06T19:42:01.801");
//将TemporalAccessor转为LocalDateTime
LocalDateTime localDateTime1 = LocalDateTime.from(parse);
System.out.println("localDateTime1 = " + localDateTime1); //2021-07-06T19:42:01.801
//2.本地化相关的格式:ofLocalizedDateTime(FormatStyle.LONG)
//提供了三个常量:
// FormatStyle.LONG: 2021年7月6日 下午07时52分01秒
// FormatStyle.MEDIUM: 2021-7-6 19:52:14
// FormatStyle.SHORT: 21-7-6 下午7:52
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
System.out.println(dateTimeFormatter.format(localDateTime)); //21-7-6 下午7:57
//3.本地化相关的格式:ofLocalizedDate(FormatStyle.LONG)
//提供了四个常量:
// FormatStyle.FULL: 2021年7月6日 星期二
// FormatStyle.LONG: 2021年7月6日
// FormatStyle.MEDIUM: 2021-7-6
// FormatStyle.SHORT: 21-7-6
dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
System.out.println(dateTimeFormatter.format(localDateTime)); //21-7-6
//4.自定义的格式:ofPattern()
dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
System.out.println(dateTimeFormatter.format(localDateTime)); //2021-07-06
}
ZoneId: 类中包含了所有的时区信息
- ZoneId.getAvailableZoneIds(); 获取所有的时区集
- LocalDateTime.now(ZoneId.of(“Asia/Shanghai”)); 获取指定时区的时间
@Test
public void test1() {
//获得所有的时区集
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
//获取上海时区的当前时间
LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("localDateTime = " + localDateTime); //2021-07-06T20:44:54.828
}
ZonedDateTime:带时区的日期时间
- ZonedDateTime.now(); 获取本时区的对象,不带时区
- ZonedDateTime.now(ZoneId.of(“Asia/Shanghai”)); 获取指定时区的对象
@Test
public void test2() {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println("zonedDateTime = " + zonedDateTime); //2021-07-06T20:44:22.490+08:00[Asia/Shanghai]
ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("zonedDateTime1 = " + zonedDateTime1); //2021-07-06T20:44:22.490+08:00[Asia/Shanghai]
}
Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
- Duration.between(TemporalAccessor time1, TemporalAccessor time2); 返回一个Duration对象实例
- duration.getSeconds(); 返回两个时间之间相差的秒数
- duration.getNano(); 返回两个时间相差的毫微妙(Long),对1秒取余
- duration1.toDays(); 返回两个时间相差的天数
@Test
public void test3() throws InterruptedException {
LocalTime localTime1 = LocalTime.now();
Thread.sleep(9543L);
LocalTime localTime2 = LocalTime.now();
Duration duration = Duration.between(localTime1, localTime2);
System.out.println("duration = " + duration); //duration = PT9.639S
System.out.println("duration.getSeconds() = " + duration.getSeconds());
//返回毫微妙级的long值
System.out.println("duration.getNano() = " + duration.getNano());
LocalDateTime localDateTime1 = LocalDateTime.of(2019, 6, 13, 8, 0, 0);
LocalDateTime localDateTime2 = LocalDateTime.of(2019, 6, 15, 13, 30, 0);
Duration duration1 = Duration.between(localDateTime1, localDateTime2);
System.out.println("duration1 = " + duration1);
System.out.println("duration1.toDays() = " + duration1.toDays());
}
Period:用于计算两个“日期”间隔,以年、月、日衡量
- Period.between(localDate2, localDate1); 返回一个Period对象实例
- period1.getYears()、period1.getMonths()、period1.getDays()相差的年月日,三者拼在一起才是两个日期只差
- period1.withYears(int year); 设置Period对象实例;
- period1.addTo(TemporalAccessor time); 把当前Period对象记录的日期差加到指定的日期上,返回一个TemporalAccessor
@Test
public void test4() {
LocalDate localDate1 = LocalDate.of(2021, 7, 6);
LocalDate localDate2 = LocalDate.of(2020, 6, 13);
Period period1 = Period.between(localDate2, localDate1);
System.out.println("period1 = " + period1);
System.out.println("period1.getYears() = " + period1.getYears()); //1
System.out.println("period1.getMonths() = " + period1.getMonths()); //0
System.out.println("period1.getDays() = " + period1.getDays()); //23
//所以两个日期相差了1年零23天
//设置period1的值
Period period2 = period1.withYears(2);
//把当前相差的日期加到localDate1上,返回一个Date
Temporal temporal = period1.addTo(localDate1);
System.out.println("period2 = " + period2);
System.out.println("temporal = " + temporal);
}
TemporalAdjuster:时间校正器
@Test
public void test5() {
//获取当前日期的下一个周日是哪一天
TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
LocalDate localDate = LocalDate.now().with(temporalAdjuster);
System.out.println("localDate = " + localDate);
//返回当前日期的下一个工作日是哪一天
LocalDate localDate2 = LocalDate.now().with(new TemporalAdjuster() {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = (LocalDate) temporal;
if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
return date.plusDays(3);
} else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
return date.plusDays(2);
} else {
return date.plusDays(1);
} }
});
System.out.println("localDate2 = " + localDate2);
}
3.Java比较器
如果要对Object类型的数组进行排序,则需要实现Compare接口,并实现其compareTo方法:
compareTo(Object obj)重写的规则:
- this大于obj返回1;
- this等于obj返回0;
- this小于obj返回-1;
需要调用Arrays.sort(Object[] obj); 对数组进行排序
class Goods implements Comparable{
private String name;
private double price;
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + 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;
}else {
//对价格相等的情况下对名称进行排序
return this.name.compareTo(goods.name);
}
}
//如果o instanceof Goods == false抛异常
throw new RuntimeException("传入的数据类型不一致");
}
}
public static void main(String[] args) {
Goods[] goods = new Goods[5];
goods[0] = new Goods("aiguozhe", 36);
goods[1] = new Goods("huawei", 65);
goods[2] = new Goods("xiaomi", 24);
goods[3] = new Goods("lianxiang", 24);
goods[4] = new Goods("pingguo", 12);
Arrays.sort(goods);
System.out.println(Arrays.toString(goods));
//[Goods{name='pingguo', price=12.0}, Goods{name='lianxiang', price=24.0}, Goods{name='xiaomi', price=24.0}, Goods{name='aiguozhe', price=36.0}, Goods{name='huawei', price=65.0}]
}
Comparator定制排序
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排序的比较。
public static void main(String[] args) {
Goods[] goods = new Goods[5];
goods[0] = new Goods("aiguozhe", 36);
goods[1] = new Goods("huawei", 65);
goods[2] = new Goods("xiaomi", 24);
goods[3] = new Goods("lianxiang", 24);
goods[4] = new Goods("pingguo", 12);
Arrays.sort(goods, new Comparator(){
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Goods && o2 instanceof Goods){
Goods goods1 = (Goods)o1;
Goods goods2 = (Goods)o2;
//先按名称由高到低排序,再按价格由低到高排序
if (goods1.getName().compareTo(goods2.getName()) == 1){
return 1;
}else if (goods1.getName().compareTo(goods2.getName()) == -1){
return -1;
}else {
return Double.compare(goods1.getPrice(), goods2.getPrice());
}
}
throw new RuntimeException("传入的数据类型不一致");
}
});
System.out.println(Arrays.toString(goods));
}
4.System类
由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
成员变量
System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
成员方法
- native long currentTimeMillis():返回时间戳
- void exit(int status):推出程序,0是正常推出,1是异常推出;使用该方法可以在图形界面编程中实现程序的退出功能等。
- void gc():请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
- String getProperty(String key): 返回对应的系统属性
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);
5.Math类
方法 | 描述 |
---|---|
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) | 角度—>弧度 |
6.BigInteger和BigDecimal
2.3.枚举类
1.如何自定义枚举类
- 定义该枚举类的一些属性(private final)
- 私有化构造器,并给属性赋值
- 提供多个类对象,供外界使用(public static final)
- 提供属性的get方法
- 根据实际情况重写toString方法
public class Ayi_01_CreateOwnEnum {
public static void main(String[] args) {
Season spring = Season.SPRING;
Season autumn = Season.AUTUMN;
System.out.println("spring = " + spring); //Season{seasonName='春天', seasonDesc='春暖花开'}
System.out.println("autumn = " + autumn); //Season{seasonName='秋天', seasonDesc='秋高气爽'}
}
}
class Season{
//1.声明Season的属性,修饰为private final
private final String seasonName;
private final String seasonDesc;
//2.私有化构造器,并给属性赋值
private Season(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3.提供当前枚举类的多个对象,给外界直接调用(public static final)
public static final Season SPRING = new Season("春天", "春暖花开");
public static final Season SUMMER = new Season("夏天", "夏日炎炎");
public static final Season AUTUMN = new Season("秋天", "秋高气爽");
public static final Season WINTER = new Season("冬天", "冰冷刺骨");
//4.提供Season属性的get方法
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//5.重写toString方法(可以根据实际情况进行重写)
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
2.如何使用关键字enum定义枚举类
- 使用enum定义的枚举类默认继承与java.lang.Enum
- 如果不对toString()方法进行重写默认输出的是枚举类提供的多个类的标识符
public class Ayi_02_enum {
public static void main(String[] args) {
System.out.println(Ayi_02_Season.SPRING); //Season{seasonName='春天', seasonDesc='春暖花开'}
System.out.println(Ayi_02_Season.class.getSuperclass()); //class java.lang.Enum
}
}
enum Ayi_02_Season {
//3.提供当前枚举类的多个对象,给外界直接调用(public static final)
SPRING("春天", "春暖花开"),
SUMMER("夏天", "夏日炎炎"),
AUTUMN("秋天", "秋高气爽"),
WINTER("冬天", "冰冷刺骨");
//1.声明Season的属性,修饰为private final
private final String seasonName;
private final String seasonDesc;
//2.私有化构造器,并给属性赋值
private Ayi_02_Season(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.提供Season属性的get方法
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//5.重写toString方法(可以根据实际情况进行重写)
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
3.Enum类的主要方法
public static void main(String[] args) {
Ayi_02_Season spring = Ayi_02_Season.SPRING;
//toString()方法
System.out.println("spring.toString() = " + spring.toString());//Season{seasonName='春天', seasonDesc='春暖花开'}
System.out.println(Ayi_02_Season.class.getSuperclass());//class java.lang.Enum
//values()方法,返回枚举类中所有的枚举对象
Ayi_02_Season[] values = Ayi_02_Season.values();
for (Ayi_02_Season value: values) {
System.out.println("value = " + value);
}
//valueOf()方法:返回指定枚举名称的枚举对象
Ayi_02_Season autumn = Ayi_02_Season.valueOf("AUTUMN");
System.out.println("autumn = " + autumn);
}
4.实现接口的枚举类
- 正常情况,实现接口,在类中重写方法;
@Override
public void show() {
System.out.println("这是一个季节");
}
- 对每个枚举对象重写方法;
SPRING("春天", "春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里?");
}
},
SUMMER("夏天", "夏日炎炎"){
@Override
public void show() {
System.out.println("夏天在哪里?");
}
},
AUTUMN("秋天", "秋高气爽"){
@Override
public void show() {
System.out.println("秋天在哪里?");
}
},
WINTER("冬天", "冰冷刺骨"){
@Override
public void show() {
System.out.println("冬天在哪里?");
}
};
2.4.注解
1.自定义注解
- 注解声明为:@interface
- 内部定义成员,通常使用value表示
- 可以指定成员的默认值,使用default定义
- 如果自定义注解没有成员,表明是一个标识作用(Serializable接口)
public class Ayi_01_CreateOwnAnno {
@MyAnnotation(value = "world")
@Test
public void test() {
}
}
@interface MyAnnotation{
String value() default "hello";
}
如何声明自定义注解的信息处理流程?
通过反射得到该注解中的属性,在执行相应的操作
2.元注解
用于修饰其他注解的注解
- Retention:用于指定该Annotation的生命周期
- RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
- RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM不会保留注解。 这是默认值
- RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。
- Target: 用于指定被修饰的 Annotation 能用于修饰哪些程序元素
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类,接口,注解
- Documented:用于指定被该元注解修饰的注解将被javadoc解析时保留下来
- Inherited:指定被修饰的注解可以被继承
java8中注解新特性
可重复注解
@Repeatable
@Repeatable(MyAnnotations.class)
//只有MyAnnotation的@Retention和@Target与MyAnnotations一样时,才可以使用@Repeatable
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PARAMETER, TYPE_PARAMETER, TYPE_USE})
@interface MyAnnotation{
String value() default "hello";
}
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PARAMETER, TYPE_PARAMETER, TYPE_USE})
@interface MyAnnotations{
MyAnnotation[] value();
}
TYPE_PARAMETER:表示可以修饰类的泛型
TYPE_USE:表示可以修饰在关键字
2.5.集合
数组存储数据的特点和缺陷
数组在存储方面的特点:
- 数组在初始化以后,长度就确定了,不能修改
- 数组声明的类型,就决定了进行元素初始化时的类型
- 顺序存储,可以重复存储的,
数组在存储数据方面的弊端:
- 数组的长度不可变;
- 数组中提供的属性和方法较少,不便于进行增、删、改、查,效率低下;
- 无法直接获得数组中存储的元素的个数;
- 数组中存储的元素是有序的,且可以重复的;
1.集合框架
-
Collection接口:单列集合,用来存储一个一个的对象
-
List接口:存储有序的,可重复的数据(“动态”数组)
-
Set接口:存储无序的,不可重复的数据(高中的“集合”)
HashSet:作为Set接口的主要实现类:线程不安全,可以存储null值
LinkedHashSet:是HashSet的子类:因为它是使用双向链表存储,所以可以按照添加的顺序进行遍历
TreeSet:可以按照添加对象的指定属性,进行排序
-
-
Map接口:双列集合,用来存储(key:value)键值对的数据
2.Collection接口
1.Collection中的一些方法
方法 | 描述 |
---|---|
add(Object obj) | 向指定集合中添加obj元素 |
size() | 返回集合中元素的个数 |
addAll(Collection coll) | 把集合coll添加到指定集合中 |
void clear() | 清除指定集合中的所有元素 |
boolean isEmpty() | 判断指定集合中是否有元素 |
boolean contains(Object obj); | 实际上调用了obj.equals(); |
boolean containsAll(Collection coll); | 判断指定集合中是否包含coll集合 |
boolean remove(Object obj) | 通过obj的equals方法判断是否是要删除的元素,只会删除第一个元素 |
boolean removeAll(Collection coll) | 和remove一样,只会删除coll集合中在指定集合中第一次出现的元素 |
boolean retainAll(Collection coll) | 把交集存在指定集合中,不会影响coll集合 |
boolean equals(Collection coll) | 判断两个集合是否相等 |
Object[] toArray() | 将指定集合转成对象数组 |
hashCode() | 获取集合对象的hash值 |
iterator() | 遍历,返回迭代器对象,用于遍历集合 |
2.Iterator迭代器接口
- hashNext(): 判断是否有下一个元素
- next(): 取下一个元素
- remove(): 删除游标所在的元素
==注:==如果游标在迭代的首位,或者在当前游标处连续调用两次remove会抛出IllegalStateException异常
foreach语句在对集合进行遍历时默认调用的就是集合的迭代器
@Test
public void test1() {
Collection collection1 = new ArrayList();
collection1.add(123);
collection1.add(456);
collection1.add(new String("123"));
collection1.add(new Person("ayi", 18));
Iterator iterator = collection1.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
if (next.equals("123")){
iterator.remove();
}
}
iterator = collection1.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println("next = " + next);
}
}
3.List接口
面试题:ArrayList、LinkedList、Vector三者的异同?
同:三者都实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
不同:
- ArrayList:作为List接口的主要实现类:线程不安全,效率高;底层采用Object类型的数组实现;
- LinkedList:对于频繁的插入、删除操作,使用LinkedList效率比ArrayList高;底层采用双向链表实现;
- Vector:jdk1.0;作为List接口的最早实现类,线程安全,效率低;底层使用Object类型的数组实现;
1.ArrayList源码分析
jdk1.7
构造器
1.空参构造器(默认创建长度为10的ArrayList集合)
public ArrayList() {
this(10); //调用本类中已定义的初始化容量构造器
}
2.初始化容量构造器
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//创建一个长度为initialCapacity的Object类型的数组
this.elementData = new Object[initialCapacity];
}
3.通过Collection创建ArrayList
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//注:这时候ArrayList的Object数组的长度就是c的size
size = elementData.length;
//toArray有可能返回的不是Object类型的数组,如果不是则将数组中的值copy到Object数组中,并返回给elementData
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
扩容问题
add()方法
public boolean add(E e) {
//在我们添加元素到ArrayList集合中时,都会先做如下操作,保证我们Object数组的容量足够
//实际上是反复新建容量较大的数组,将原数组内容copy到新数组中
//类似StringBuilder(线程不安全)
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal()方法
private void ensureCapacityInternal(int minCapacity) {
modCount++;
//判断期望的容量和Object数组容量哪个大
if (minCapacity - elementData.length > 0)
//如果Object数组不够,就扩容
grow(minCapacity);
}
grow()方法:扩容
private void grow(int minCapacity) {
//先记录原数组的长度
int oldCapacity = elementData.length;
//新数组的长度=原数组长度*1.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新数组的长度还是小于我们添加元素之后需要的长度minCapacity,就直接取minCapacity为新数组的长度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新数组的长度超过了,自定义的数组最大长度,就取Integer.MAX_VALUE为新数组的长度;溢出就抛异常
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//将原数组内容copy到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
jdk1.8
1.空参构造器
- 与1.7默认创建10容量的ArrayList不同;
- 1.8中调用空参构造器只会创建一个空的Object类型的数组;
- 分析:1.7创建方式可以类比单例模式中的饿汉式;1.8为饱汉式;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.初始化容量构造器
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//创建指定容量的Object类型的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//空的Object[]
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
3.通过Collection创建ArrayList
和1.7一样,先toArray,在判断是否是空数组,最后判断toArray的数组类型是否为Object类型
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
扩容问题
add();和1.7一样,调用一个确保容量足够的函数
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static final int DEFAULT_CAPACITY = 10; //默认容量还是10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果elementData还没有初始化(即ArrayList中还没数组),则取默认容量和所需要的容量中的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity); //和1.7中的一致
}
总结:
构造器
-
初始化时的区别:
- jdk1.7:在使用空参构造器创建ArrayList对象时,会默认创建一个容量为10的Object数组(饿汉式);
- jdk1.8:在使用空参构造器创建ArrayList对象时,不会去初始化Object类型的数组,只有在添加元素的时候才初始化(饱汉式);
-
初始化容量构造器的区别:
-
jdk1.7:在判断initCapacity不合法后,就创建一个Object[initCapacity]数组;
-
jdk1.8:则会判断initCapacity是否是0,如果是0,依旧不初始化Object数组;
注:这里如果initCapacity<10;则会创建一个长度为10的Object数组
-
-
通过Collection创建ArrayList的区别:
- jdk1.7:依旧没有对添加的数据是否为空,进行判断;
- jdk1.8:如果添加的数据为空,依旧不会初始化Object数组;
add()方法:扩容
jdk1.7和jdk1.8一样,扩容的过程:
- 调用ensureCapacityInternal()函数,确保容量足够;
- 通过比较期望容量和ArrayList中Object数组的长度,来确定要扩容的容量;
- 调用grow()函数进行扩容:扩容至原来的1.5倍,如果还是不够,就直接取期望容量为扩容后的容量;注:这里有一个MAX_ARRAY_SIZE默认最大容量
多次出现的modCount的作用(在Iterator接口遍历集合的使用会用到)
主要用来保证集合在生成Iterator遍历器之后,该集合不能被修改(遍历器修改除外),否则就会抛ConcurrentModificationException异常
https://blog.csdn.net/lhq1170159388/article/details/107586616
2.LinkedList
源码分析
注:因为是否双向链表存储,所以不存在扩容问题
//静态内部类Node节点
private 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;
}
}
添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//l:指针,先将指针指向最后一个节点,如果是首次添加则l=null
final Node<E> l = last;
//新建一个节点存储数据
final Node<E> newNode = new Node<>(l, e, null);
//记录最后一个节点的位置(last私有化属性)
last = newNode;
if (l == null)
//如果是第一次赋值就将first指针指向新建的节点
first = newNode;
else
//否则就将l(最后一个节点)的next指针指向新建的节点
l.next = newNode;
size++;
modCount++;
}
模拟LinkedList自己实现简单的链表
public class MyNode {
private Node first;
private Node last;
public boolean add(String str){
final Node l = last;
final Node newNode = new Node(l, str, null);
//记录last
last = newNode;
//判断是否是首次添加
if (l == null){
first = newNode;
}else {
l.next = newNode;
}
return true;
}
public void forEach(){
Node temp = first;
while (temp != null){
System.out.println(temp.element);
temp = temp.next;
}
}
public static class Node{
String element;
Node prev;
Node next;
public Node(Node prev, String element, Node next) {
this.prev = prev;
this.element = element;
this.next = next;
}
}
@Test
public void test() {
MyNode myNode = new MyNode();
myNode.add("123");
myNode.add("456");
myNode.add("自己定义的链表");
myNode.forEach();
}
}
3.Vector
jdk1.7和jdk1.8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组;
在扩容方面,默认扩容为原来的数组长度的2倍;
List中常用的方法
方法 | 描述 |
---|---|
void add(int index, Object ele) | 在index位置插入ele元素 |
boolean addAll(int index, Collection coll) | 从index位置开始将coll中的所有元素添加进来 |
Object get(int index) | 获取指定位置的元素 |
int indexOf(Object obj) | 返回obj在集合中首次出现的位置 |
int lastIndexOf(Object obj) | 返回obj在集合中最后一次出现的位置 |
Object remove(int index) | 删除指定位置的元素。并返回该元素 |
Object set(int index, Object obj) | 修改指定位置的元素,并返回修改后的元素 |
List subList(int fromIndex, int toIndex) | 返回从fromIndex到toIndex位置的子集合 |
方法 | 描述 |
---|---|
boolean add(Object) | 增 |
Object remove(int index) | 删 |
Object set(intdex, Object obj) | 改 |
Object get(int index) | 查 |
void add(int index, Object obj) | 插 |
int size() | 长度 |
iterator();foreach | 遍历 |
4.Set接口
HashSet
-
无序性:并不是随机存储,存储的数据在底层数组(并非数组,以数组为例说明)中按照添加对象的hashCode的散列值进行存放;
-
不可重复性:调用equals()方法,保证Set集合中没有重复的元素
-
不可重复性的实现:
-
我们向HashSet中添加元素ele时,首先调用元素ele所在类的hashCode()生成一个hash值
-
通过指定的散列函数,获取散列值,存放在数组的相应位置
-
如果此位置是空闲的(没有其他元素),则直接在散列值对应的位置上添加此元素==>添加成功
-
如果此位置上已经有元素了,则先比较两个元素的hash值是否一样
-
hash值不相同,则添加ele元素==>添加成功
-
hash值相同,则调用ele的equals()方法与已有的元素比较,不相同就添加==>添加成功
否则就不添加
-
-
-
注:
- 在jdk1.7中,每个散列值对应的位置上对个元素按照头插法形成链表
- 在jdk1.8中,则使用尾插法形成链表
-
LinkedHashSet
- 是HashSet的子类,所以内容的实现和HashSet一样;也是用数组存储的;
- 但是在LinkedHashSet中,存储的每个元素都记录的prev和next,即该元素的前置和后驱节点;形成了一个链表
- 所以LinkedHashSet在添加数据的时候既有HashSet中散列存储的特点,又有链式存储的特点;
- 在遍历的时候是按照链式结构进行遍历的;
- 所以在遍历的时候能够按照添加的顺序进行遍历;
TreeSet
- 内部使用红黑树结构进行存储对象;
- 向TreeSet中添加数据时,要求数据是同一个类的对象;
- 在TreeSet中判断两个元素是否相等的方法不再是equals()方法和hashCode()方法了,而是compareTo()方法;
TreeSet中的定制排序
@Test
public void test2() {
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person || o2 instanceof Person){
Person person1 = (Person) o1;
Person person2 = (Person) o2;
int i = person1.getName().compareTo(person2.getName());
return i == 1 ? 1 : (i == -1 ? -1 : person1.getAge()-person2.getAge());
}else {
throw new RuntimeException("传入的参数不一致");
}
}
});
treeSet.add(new Person("4", 4));
treeSet.add(new Person("3", 3));
treeSet.add(new Person("3", 1));
treeSet.add(new Person("2", 2));
treeSet.add(new Person("2", 1));
treeSet.add(new Person("1", 1));
treeSet.add(new Person("1", 1));
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()){
System.out.println("iterator.next() = " + iterator.next());
}
}
对Set进行排序
//假设Set中已经有了很多数据了
Set<Map.Entry<Character, Integer>> entries = map.entrySet();
//对Set进行排序:使用List
ArrayList<Map.Entry<Character, Integer>> arrayList = new ArrayList<>(entries);
Comparator<Map.Entry<Character, Integer>> comparator = new Comparator<Map.Entry<Character, Integer>>() {
@Override
public int compare(Map.Entry<Character, Integer> o1, Map.Entry<Character, Integer> o2) {
return o1.getKey().compareTo(o2.getKey());
}
};
Collections.sort(arrayList, comparator);
for (Map.Entry<Character, Integer> entry : arrayList) {
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
5.Map接口
1.Map框架
- HashMap:作为Map的主要实现类:线程不安全,效率高;可以存储null类型的key或value
- LinkedHashMap:保证在遍历Map元素时,可以按照添加的顺序实现遍历,对于频繁的遍历操作,此类执行效率高于HashMap
- TreeMap:保证按照添加的key-value键值对进行排序,实现排序遍历,此时考虑key的自然排序或定制排序;底层使用红黑树
- Hashtable:作为最初的实现类(jdk1.0)线程安全的,效率低;不能存储null类型的key或value
- Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表(jdk1.7);数组+链表+红黑树(jdk1.8)
2.Map结构的理解
- Map中的key:无序的、不可重复的,使用Set存储所有的key ====>key所在的类要重写equals()和hashCode()【HashMap】
- Map中的value:无序的、可重复的,使用Collection存储所有的value ====>所在的类要重写equals()
- 一个键值对:key-value构成了一个Entry对象;
- Map中的entry:无序的、不可重复的,使用Set存储所有的entry;
3.HashMap底层实现原理
1.jdk1.7
- HashMap map = new HashMap();在实例化后,底层默认创建了一个长度是16的一位数组Entry[] table;
- map.put(key1,value1);
- 首先,调用key1所在类的hashCode()计算key1的hash值,经过散列函数计算出key1在Entry数组中的存放位置。
- 如果,该位置数据为空,则直接添加;
- 如果,该位置数据不为空:
- 判断key1的hash值与该位置所存的entry的key的hash值是否相等,如果不相等则添加成功
- 反之用value1替换原有的value;
扩容:默认的扩容方式是扩容为原来的2倍
2.jdk1.8
-
new HashMap(): 底层没有创建一个长度为16的数组
-
jdk1.8底层的数组是Node[],而不是Entry[]
-
首次调用put方法时,底层才会创建一个长度为16的数组
-
jdk1.7底层结构只有:数组+链表。jdk1.8中底层结构采用:数组+链表+红黑树;
当数组的某一个索引位置上的元素以链表的形式保存的数据个数 > 8,且当前数组长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存;
4.HashMap源码分析
jdk1.7
构造器
static final int DEFAULT_INITIAL_CAPACITY = 16; //默认长度:在jdk1.7中创建对象的同时初始化数组长度
static final float DEFAULT_LOAD_FACTOR = 0.75f; //装载因子:capacity*factor得到一个数,如果Entry[]的已经存储数据的位置大于了这个数,就需要进行扩容了
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
int capacity = 1;
//注意:这里最终求得的容量只能是2的n次方
//例如如果我们指定容量为15,实例化得到的Entry[]数组的容量为16(2^4)
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
//threshold:当数组中已经使用的位置>threshold;就需要对Entry[]数组进行扩容了
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//transient Entry<K,V>[] table;
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
添加方法put(key1,value1)
putForNullKey()、hash()、addEntry、createEntry、Entry内部类
public V put(K key, V value) {
//HashMap可以存储null值的数据,Hashtable则不能
if (key == null)
//将key=null的entry保存在Entry[0]中
return putForNullKey(value);
//通过HashMap内置的hash函数对hashCode再次hash一下
//这里说明我们在key所在类中得到的hashCode并不直接用来散列计算
int hash = hash(key);
//散列函数,得到散列值i(就是我们存储在Entry[]中的位置)= hash & table.length-1
int i = indexFor(hash, table.length);
//添加操作
// 1.首先取出当前位置的第一个元素(链表的头部),判断是否为空
// 如果为空,跳出循环直接添加元素;
// 2.如果不为空,进入if判断
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//if判断:
// 1.首先判断该元素与要添加元素的hash是否一样
// 如果不一样,则继续遍历,最后如果都不一样,结束循环添加元素(头插法jdk1.7)
// 如果一样,在判断该元素key的地址值和要添加元素key的地址值是否一样,最后调用key的equals()方法
// 满足条件:即hash一样,key一样,就覆盖value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
//******返回被修改之前的value值******
return oldValue;
}
}
modCount++;
//添加元素
addEntry(hash, key, value, i);
return null;
}
putForNullKey()
private V putForNullKey(V value) {
//判断e == null,表明这个位置为空,直接存
//如果e != null,表明有数据,则访问其下一个节点,直到找到key==null的节点,将它的value覆盖
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
hash()
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
//在集合中使用位运算符可以大大增加运算速度
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
addEntry()
//参数:hash(并不是key的hashCode),bucketIndex(在Entry[]数组中的存放位置)
void addEntry(int hash, K key, V value, int bucketIndex) {
//****涉及到扩容问题****
//判断已经使用的位置size >= threshold ? 判断该位置是否为空,如果为空就直接添加 : 不为空,就扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//默认扩容为原来的2倍
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//创建一个Entry类型的对象,并添加到Entry[bucketIndex]的首部
createEntry(hash, key, value, bucketIndex);
}
createEntry()
void createEntry(int hash, K key, V value, int bucketIndex) {
//先将bucketIndex位置上的节点先取出来
Entry<K,V> e = table[bucketIndex];
//创建一个新的Entry对象,并将这个对象存在table[bucketIndex]中
//通过下面的Entry内部类中定义的构造器,我们可以看出,
//在新建Entry对象的时候,把bucketIndex位置上原有的头部节点,作为新创建的Entry对象的下一个节点
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
Entry内部类
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
//将头部节点作为新建节点的next节点;
next = n;
key = k;
hash = h;
}
....
}
jdk1.8
构造器
//我们可以看出在jdk1.8中new HashMap(),没有给我们初始化一个长度为16的Node[] table
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
添加方法put(key, value)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
putVal()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab:Node[] table; p:要添加的位置处的头部元素; n:table.length; i:要添加的位置的缩影索引
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断Node[] table isNull? ----> 初始化数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//1.得到散列值[i=(table.length & hash)];
//2.p = table[i],如果p == null,即该位置无数据,则直接新建节点,添加
// 否则,遍历链表,判断....
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//e:遍历链表的某个节点, k = e.key
Node<K,V> e; K k;
//如果hash和key一样则将e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断是否是红黑树,暂且不看
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//遍历链表,找hash和key一样的,如果没找到就在尾部插入新节点
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
//(jdk1.8尾插法)
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果找到了hash和key一样的节点,则覆盖value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果size > 阈值就扩容,默认扩容为原来的2倍
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
5.LinkedHashMap底层实现原理
LinkedHashMap继承的HashMap,使用的都是HashMap中的方法,但是
在LinkedHashMap中调用putVal()方法时,对newNode()方法进行了重写,创建的是自己的LinkedHashMap.Entry
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
//在LinkedHashMap中添加了前置和后驱节点,使用遍历的时候可以按顺序遍历
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
HashSet&HashMap
总结,在HashSet集合中,底层就使用了HashMap进行存储,HashSet的特点是存储无序的,不可重复的元素。所以在HashSet中将元素都保存在key中,而value则保存的是一个全局静态的空对象;
//HashSet中的add方法
private static final Object PRESENT = new Object();
public boolean add(E e) {
//将数据保存在map的key中,而value则保存的是PRESENT:一个全局静态的空对象
return map.put(e, PRESENT)==null;
}
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中的所有数据 |
元素查询的操作:
方法 | 描述 |
---|---|
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是否相等 |
元视图操作的方法:
方法 | 描述 |
---|---|
Set keySet() | 返回所有key构成的Set集合 |
Collection values() | 返回所有value构成的Collection集合 |
Set entrySet() | 返回所有key-value对构成的Set集合 |
TreeMap
对TreeMap添加key: value时,要求key是同一个类的对象,实现了Comparable接口中的compareTo()方法;或添加Comparator对象当作参数传递到TreeMap构造器中;
对Map进行排序
TreeMap
TreeMap是默认升序的(按照所存储数据key所在类实现的Comparable接口决定的),直接获取keySet(),然后遍历输出即可
public class TreeMapTest {
public static void main(String[] args) {
Map<String, String> map = new TreeMap<String, String>(
new Comparator<String>() {
public int compare(String obj1, String obj2) {
// 降序排序
return obj2.compareTo(obj1);
}
});
map.put("c", "ccccc");
map.put("a", "aaaaa");
map.put("b", "bbbbb");
map.put("d", "ddddd");
Set<String> keySet = map.keySet();
Iterator<String> iter = keySet.iterator();
while (iter.hasNext()) {
String key = iter.next();
System.out.println(key + ":" + map.get(key));
}
}
}
HashMap
可以通过将HashMap转为ArrayList,再通过ArrayList的定制排序来解决
public class HashMapTest {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("c", "ccccc");
map.put("a", "aaaaa");
map.put("b", "bbbbb");
map.put("d", "ddddd");
List<Map.Entry<String,String>> list = new ArrayList<Map.Entry<String,String>>(map.entrySet());
Collections.sort(list,new Comparator<Map.Entry<String,String>>() {
//升序排序
public int compare(Entry<String, String> o1,
Entry<String, String> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
for(Map.Entry<String,String> mapping:list){
System.out.println(mapping.getKey()+":"+mapping.getValue());
}
}
}
6.Collections工具类
方法 | 描述 |
---|---|
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 对象的所有旧值 |
synchronizedXxx(Xxx xxx); | 将传入的集合或者Map转为线程安全的 |
2.7.泛型
泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass(){}
在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型
异常类不能是泛型的
不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];一般不使用,直接声明为Object类型的数组即可
泛型方法
方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
泛型方法的定义:[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
泛型在继承上的体现
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<B>并不是G<A>的子类型!两者是并列的
通配符
List<?>是List<String>、List<Object>等各种泛型List的父类。
使用通配符定义的泛型类,只读不写(可以写null)
- <? extends Number> (无穷小 , Number]:只允许泛型为Number及Number子类的引用调用
- <? super Number> [Number , 无穷大) :只允许泛型为Number及Number父类的引用调用
- <? extends Comparable>:只允许泛型为实现Comparable接口的实现类的引用调用
public static void addString1(List<? extends Object> list){
//两个都报错;
//list里存储的都是Object的子类,假设list中存放的是String类型,这时我再添加Object类型会报错,父类不能赋值给子类会报错
list.add(new Person());
list.add(new Object());
}
public static void addString2(List<? super Person> list){
//不会报错
//list里存储的都是Person的父类,这时我添加一个Person类,肯定是先前已经存储的对象的子类,子类可以赋值给父类,不会报错
list.add(new Person());
//会报错
//同addString1()
list.add(new Object());
}
2.8.IO流
1.File
1.构造器
- 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对象
2.File中常用的方法
File类的获取功能:
-
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数组
File类的重命名功能
- public boolean renameTo(File dest):把文件重命名为指定的文件路径(要求被重命名的文件要存在,dest的文件不存在)
File类的判断功能
- public boolean isDirectory():判断是否是文件目录
- public boolean isFile() :判断是否是文件
- public boolean exists() :判断是否存在
- public boolean canRead() :判断是否可读
- public boolean canWrite() :判断是否可写
- public boolean isHidden() :判断是否隐藏
File类的创建功能
- public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
- public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
- public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建
==注意事项:==如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。
File类的删除功能
public boolean delete():删除文件或者文件夹
==删除注意事项:==Java中的删除不走回收站。 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
2.IO流
1.IO流的分类
- 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流
常用的流
抽象基类 | 节点流(文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream | BufferedInputStream |
OutputStream | FileOutputStream | BufferedOutputStream |
Reader | FileReader | BufferedReader |
Writer | FileWrite | BufferedWriter |
2.节点流(文件流)
1.FileReader&FileWriter
FileReader
- 读入的文件一定要存在,否则就会报FileNotFoundException
- 使用try-catch-finally,保证最后一定能关闭你流,节省资源
- 在关闭流之前一定要判断流是否为null,否则就会报NullPointerException
@Test
public void test1() {
FileReader fileReader = null;
try {
File file = new File("hello.txt");
fileReader = new FileReader(file);
int read;
while ((read = fileReader.read()) != -1) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null)
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 调用int read(char chuf[]);方法,每次读入指定char数组长度的字符,返回每次读取字符的个数;可能最后读取的字符个数小于数组长度,那么本次读取的字符只会覆盖前面的字符,最后的几个不会覆盖(保留上一次读取的字符)
- int read(char chuf[], int off, int len);将指定个数(len)的字符读取到chuf数组中,从指定位置(off)开始存放
@Test
public void test2() {
FileReader fileReader = null;
try {
File file = new File("hello.txt");
fileReader = new FileReader(file);
char[] chBuffer = new char[5];
int len;
while ((len = fileReader.read(chBuffer)) != -1) {
String s = new String(chBuffer, 0, len);
System.out.print(s);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileWriter
- 文件可以不存在;
- fileWriter.writer(String str);不会写入换行
- new FileWriter(File file, boolean append); append == true ? 在原有文件上追加内容 : 覆盖原有文件
@Test
public void test3() {
FileWriter fileWriter = null;
try {
File file = new File("hello1.txt");
fileWriter = new FileWriter(file);
fileWriter.write("I have a dream!");
fileWriter.write("You must have a dream!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileInputStream&FileOutputStream
public void fileCopy(String srcPath, String destPath){
FileInputStream is = null;
FileOutputStream os = null;
try {
File srcFile = new File(srcPath);
File destFile = new File(destPath);
is = new FileInputStream(srcFile);
os = new FileOutputStream(destFile);
byte[] buffer = new byte[2 << 20];
int len;
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.缓冲流(处理流的一种)
public void copyFileWithBuffered(String srcPath, String destPath) {
BufferedInputStream inputStream = null;
BufferedOutputStream outputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(srcPath));
outputStream = new BufferedOutputStream(new FileOutputStream(destPath));
byte[] buf = new byte[1024];
int len;
//BufferedReader.readLine(); 一次读取一行数据
while ((len = inputStream.read(buf)) != -1) {
//outputStream.flush?
outputStream.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
统计文本中每个字符出现的次数
@Test
public void test2() {
String file = "hello.txt";
Map<Character, Integer> map = getCharFrequency(file);
Set<Map.Entry<Character, Integer>> entries = map.entrySet();
//对Set进行排序:使用List
ArrayList<Map.Entry<Character, Integer>> arrayList = new ArrayList<>(entries);
Comparator<Map.Entry<Character, Integer>> comparator = new Comparator<Map.Entry<Character, Integer>>() {
@Override
public int compare(Map.Entry<Character, Integer> o1, Map.Entry<Character, Integer> o2) {
return o1.getKey().compareTo(o2.getKey());
}
};
Collections.sort(arrayList, comparator);
for (Map.Entry<Character, Integer> entry : arrayList) {
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
}
//遍历文本统计字符出现次数
public Map<Character, Integer> getCharFrequency(String filePath) {
Map<Character, Integer> map = null;
BufferedReader reader = null;
try {
map = new HashMap<>();
reader = new BufferedReader(new FileReader(filePath));
char[] chBuff = new char[10];
int len;
while ((len = reader.read(chBuff)) != -1) {
for (int i = 0; i < len; i++) {
char ch = chBuff[i];
int value = 1;
if (map.containsKey(ch)) {
value = map.get(ch);
value++;
}
map.put(ch, value);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return map;
}
BufferedReader中一次读取一行
@Test
public void test3() {
BufferedReader bufferedReader = null;
BufferedWriter outputStream = null;
// BufferedOutputStream outputStream = null;
try {
bufferedReader = new BufferedReader(new FileReader("hello.txt"));
outputStream = new BufferedWriter(new FileWriter("test.txt"));
String str;
while ((str = bufferedReader.readLine()) != null) {
outputStream.write(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
// outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.转换流
InputStreamReader: 将字节输入流转换成字符输入流
//1.InputStreamReader: 将字节输入流转换成字符输入流
@Test
public void test1() {
InputStreamReader reader = null;
try {
FileInputStream inputStream = new FileInputStream("hello.txt");
reader = new InputStreamReader(inputStream, "gbk");
char[] chBuff = new char[20];
int len;
while ((len = reader.read(chBuff)) != -1){
String str = new String(chBuff, 0, len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
OutputStreamWriter: 将字符输出流转换成字节输出流
//2.OutputStreamWriter: 将字符输出流转换成字节输出流
@Test
public void test2() {
InputStreamReader reader = null;
OutputStreamWriter writer = null;
try {
FileInputStream inputStream = new FileInputStream("hello.txt");
FileOutputStream outputStream = new FileOutputStream("hello1.txt");
reader = new InputStreamReader(inputStream, "utf-8");
writer = new OutputStreamWriter(outputStream, "gbk");
char[] chBuff = new char[20];
int len;
while ((len = reader.read(chBuff)) != -1){
writer.write(chBuff, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5.标准输入输出流
- System.in: 标准输入流,默认从键盘输入
- System.out: 标准输出流,默认从控制台输出
- 不支持单元测试
- 使用System.setIn(InputStream is)/System.setOut(PrintStream ps), 可以重新指定输入和输出流
/*
标准输入输出流
从键盘输入字符串,将其转成大写,直到“e”、“exit”为止;
*/
public static void main(String[] args) {
BufferedReader reader = null;
try {
InputStreamReader is = new InputStreamReader(System.in);
reader = new BufferedReader(is);
while (true) {
System.out.println("请输入字符串:");
String str = reader.readLine();
if (str == null || "e".equalsIgnoreCase(str) || "exit".equalsIgnoreCase(str)){
System.out.println("程序结束");
break;
}
System.out.println(str.toUpperCase());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
6.打印流
-
PrintStream和PrintWriter都属于输出流,分别针对输出字节和字符。
-
PrintStream和PrintWriter提供了重载的print()、println()方法用于多种数据类型的输出。
-
PrintStream和PrintWriter不会抛出异常,用户通过检测错误状态获取错误信息。
-
PrintStream和PrintWriter有自动flush 功能。
**重点:**PrintWriter即使遇到换行符(\n)也不会自动清空缓冲区,只在设置了autoflush模式下使用了println方法后才自动清空缓冲区。PrintWriter相对PrintStream最有利的一个地方就是println方法的行为,在Windows的文本换行是"\r\n",而Linux下的文本换行是"\n",如果希望程序能够生成平台相关的文本换行,而不是在各种平台下都用"\n"作为文本换行,那么就应该使用PrintWriter的println方法时,PrintWriter的println方法能根据不同的操作系统而生成相应的换行符。
构造器
PrintStream
-
PrintStream(OutputStream out)
-
PrintStream(OutputStream out, boolean autoFlush)
-
PrintStream(OutputStream out, boolean autoFlush, String encoding)
-
PrintStream(String fileName)
-
PrintStream(String fileName, String csn)
-
PrintStream(File file)
-
PrintStream(File file, String csn)
PrintWriter
- PrintWriter(OutputStream out)
- PrintWriter(OutputStream out, boolean autoFlush)
- PrintWriter(String fileName)
- PrintWriter(String fileName, String csn)
- PrintWriter(File file)
- PrintWriter(File file, String csn)
- PrintWriter(Writer out)
- PrintWriter(Writer out, boolean autoFlush)
@Test
public void test1() {
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream("PrintStream.txt");
//创建打印输出流,设置为自动刷新模式(写入换行符或字节'\n'时都会刷新输出缓冲区)
ps = new PrintStream(fos, true);
if (ps != null) {
System.setOut(ps); //把标准输出流(控制台输出)改成文件
}
for (int i = 0; i < 255; i++) {
System.out.print((char)i);
if (i % 50 == 0) {
System.out.println();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}
7.数据流
@Test
public void test2() throws IOException {
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("DataStreamTest.txt"));
dataOutputStream.writeUTF("阿一");
//这个flush();到底有什么用?注释了依然可以正常写入数据
// dataOutputStream.flush();
dataOutputStream.writeInt(21);
// dataOutputStream.flush();
dataOutputStream.writeBoolean(true);
// dataOutputStream.flush();
dataOutputStream.close();
}
@Test
public void test3() throws IOException {
DataInputStream dataInputStream = new DataInputStream(new FileInputStream("DataStreamTest.txt"));
//必须按照写入的顺序读取
String name = dataInputStream.readUTF();
int age = dataInputStream.readInt();
boolean isMan = dataInputStream.readBoolean();
System.out.println("name = " + name);
System.out.println("age = " + age);
System.out.println("isMan = " + isMan);
dataInputStream.close();
}
3.对象流的使用
对象流
- ObjectInputStream和ObjectOutputStream
作用
- ObjectOutputStream:内存中的对象 ====> 存储中的文件、通过网络传输出去 ====> 序列化过程
- ObjectInputStream:存储中的文件、通过网络传输出去 ====> 内存中的对象 ====> 序列化过程
对象的序列化机制
对象序列化机制允许把内存中的java对象转成平台无关的二进制流,从而允许把这种二进制流持久化地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其他程序获取了这种二进制流,就可以恢复成原来的java对象;
重点:实现序列化的对象需要满足的要求
- 需要实现接口:Serializable或
- 当前类需要提供一个全局常量:serialVersionUID
- 除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
- ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
SerialVersionUID的理解
如果在将对象进行序列化时没有显示的声明serialVersionUID,那么在java会根据运行时的环境自动生成一个serialVersionUID;但是自动生成的serialVersionUID可能会出错(在对对象进行修改时,可能就找不到这个对象了,寻找的时候根据serialVersionUID)
/*
ObjectOutputStream
*/
@Test
public void test1() throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ObjectStream.dat"));
Person ayi1 = new Person("ayi1", 18);
objectOutputStream.writeObject(ayi1);
objectOutputStream.writeUTF(new String("123456"));
objectOutputStream.writeObject(new Integer[]{1,2,3,4,5,6});
objectOutputStream.close();
}
/*
ObjectInputStream
*/
@Test
public void test2() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ObjectStream.dat"));
Person ayi1 = (Person) objectInputStream.readObject();
String str = objectInputStream.readUTF();
Integer[] arr = (Integer[]) objectInputStream.readObject();
System.out.println("ayi1 = " + ayi1);
System.out.println("str = " + str);
for (int i = 0; i < arr.length; i++) {
System.out.println("arr["+ i +"] = " + arr[i]);
}
objectInputStream.close();
}
4.随机存取文件流
RandomAccessFile介绍:
- 声明在java.io下,直接继承与Object;实现了DataInput、DataOutput;可读可写
- 随机访问:RandomAccessFile可以实现在文件的任意位置进行读或写(可以只访问部分内容,也可以向已存在的文件追加内容)
- 记录指针:RandomAccessFile对象包含了一个记录指针,用于标记当前读写的位置;
- long getFilePointer(): 获取文件指针的当前位置;
- void seek(long pos): 设置文件指针的位置;
构造器:
- public RandomAccessFile(File file, String mode)
- public RandomAccessFile(String name, String mode)
mode访问模式:
-
r: 以只读方式打开
-
rw: 打开以便读取和写入==(RandomAccessFile对文件进行写操作时,是对文件内容的覆盖,而不是对整个文件的覆盖)==
-
rwd: 打开以便读取和写入;同步文件内容的更新
-
rws: 打开以便读取和写入;同步文件内容和元数据的更新
-
注意:
-
r模式下,如果文件不存在不会去创建一个文件,而会抛FileNotFoundException异常;rw模式:则会创建文件
-
在jdk1.6时每一次write写数据时,如果在rw模式下,不会立即写到硬盘中,调用flush才会写入;而rwd数据会立即写入到硬盘;
所以如果在数据写入过程中发生异常,rwd模式可以保存已经写过的数据,但是rw则全部丢失;
-
断点下载
我们可以通过RandomAccessFile实现多线程断点下载功能:
在下载的时候会建立两个临时文件,一个是与被下载文件大小相同的空文件,另一个则是记录文件指针位置的文件,每一次暂停的时候都会保存上一次的指针,然后断点下载的时候,又会从上一次指针的地方继续下载,从而实现了断点下载或上传的功能。
RandomAccessFile实现文件复制
//RandomAccessFile复制文件
@Test
public void test1() {
RandomAccessFile r = null;
RandomAccessFile rw = null;
try {
r = new RandomAccessFile("desktop.png", "r");
rw = new RandomAccessFile("desktop3.png", "rw");
byte[] buff = new byte[1024];
int len;
while ((len = r.read(buff)) != -1){
rw.write(buff, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (r != null) {
try {
r.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (rw != null) {
try {
rw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
RandomAccessFile对文件写操作的覆盖
/*
RandomAccessFile对文件进行写操作时,是对文件内容进行覆盖,而不是对整个文件进行覆盖
*/
@Test
public void test2() throws IOException {
//原来文件中的内容为:abcdefghijklmnopqrstuvwxyz
RandomAccessFile rw = new RandomAccessFile("RandomAccessFilerwTest.txt", "rw");
rw.write("123".getBytes());
//写入数据之后:123defghijklmnopqrstuvwxyz
rw.seek(10);
rw.write("987".getBytes());
//写入数据之后:123defghij987nopqrstuvwxyz
rw.close();
}
RandomAccessFile实现插入效果
//使用RandomAccessFile实现数据插入效果
@Test
public void test3(){
RandomAccessFile r = null;
RandomAccessFile rw = null;
try {
r = new RandomAccessFile("RandomAccessFilerwTest.txt", "r");
rw = new RandomAccessFile("RandomAccessFilerwTest.txt", "rw");
//用来记录插入位置之后的数据
StringBuilder builder = new StringBuilder((int) r.length());
byte[] buff = new byte[10];
int len;
//假设我们在10的位置进行插入"ayi"
r.seek(10);
while ((len = r.read(buff)) != -1){
builder.append(new String(buff, 0, len));
}
rw.seek(10);
rw.write("ayi".getBytes());
rw.write(builder.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (r != null) {
try {
r.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (rw != null) {
try {
rw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UTF-8字符编码
2.9.网络编程
1.IP和端口号
IP:唯一标识Internet上的计算机
端口号:不同的进程被分配不同的端口号,是一个16位的整数(0~65535)
端口号分类:
- 公认端口号:0~1023。被预先定义的服务通信占用(HTTP:80,FTP:21,Telnet:23);
- 注册端口号:1024~49151。分配给用户进程,或应用程序。(Tomcat:8080,Mysql:3306,Oracle:1521)。
- 动态/私有端口:49152~65535。
端口号与IP地址的组合就是一个网络套接字:Socket;
2.InetAddress类
两个子类:Inet4Address、Inet6Address
一个InetAddress类对象包含一个Internet主机的域名和IP地址
InetAddress没有公共的构造器,需要通过几个静态方法来获取InetAddress实例
public static InetAddress getLocalHost() //获得本地主机对应的InetAddress实例
public static InetAddress getByName(String host) //获得指定的InetAddress实例
InetAddress提供的几个实例化方法
public String getHostAddress(); //返回 IP 地址字符串(以文本表现形式)。
public String getHostName(); //获取此 IP 地址的主机名
public boolean isReachable(int timeout); //测试是否可以达到该地址
3.Socket编程
1.Socket常用构造器
- public Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
- public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口号
2.Socket常用方法
- 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。 即不能通过此套接字的输出流发送任何数据。
TCP协议:
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端。
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP协议:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
TCP/IP实例
客户端
@Test
public void testClient() {
Socket client = null;
BufferedOutputStream bos = null;
try {
//1.创建IP对象
InetAddress localHost = InetAddress.getLocalHost();
//2.创建Socket对象(IP、端口号)
client = new Socket(localHost, 8193);
//3.获取Socket的输出流
OutputStream os = client.getOutputStream();
bos = new BufferedOutputStream(os);
//4.向服务端发送数据
bos.write("你好啊,你是谁?".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (client != null) {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器端
@Test
public void testServer() {
ServerSocket serverSocket = null;
Socket accept = null;
ByteArrayOutputStream baos = null;
try {
//1.创建ServerSocket对象
serverSocket = new ServerSocket(8193);
//2.serverSokcet.accept接受客户端的链接
accept = serverSocket.accept();
//3.调用InputStream
InputStream inputStream = accept.getInputStream();
//4.ByteArrayOutputStream存储数据
baos = new ByteArrayOutputStream();
socket.shutdownOutput();
byte[] buff = new byte[5];
int len;
while ((len = inputStream.read(buff)) != -1){
baos.write(buff, 0, len);
}
System.out.println(baos.toString());
System.out.println("收到来自"+accept.getInetAddress().getHostName()+"的数据");
System.out.println("收到来自"+accept.getInetAddress().getHostAddress()+"的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (accept != null) {
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UDP实例
@Test
public void testClient() throws IOException {
DatagramSocket socket = new DatagramSocket();
byte[] bytes = "我是UDP发送的数据导弹".getBytes();
InetAddress localhost = InetAddress.getByName("localhost");
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, localhost, 8193);
socket.send(datagramPacket);
socket.close();
}
@Test
public void testServer() throws IOException {
DatagramSocket socket = new DatagramSocket(8193);
byte[] bytes = new byte[100];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);
socket.receive(datagramPacket);
System.out.println(new String(datagramPacket.getData(), 0, datagramPacket.getLength()));
}
URL实例
方法
URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom");
//获取该URL的协议名
System.out.println(url.getProtocol());
//获取该URL的主机名
System.out.println(url.getHost());
//获取该URL的端口号
System.out.println(url.getPort());
//获取该URL的文件路径
System.out.println(url.getPath());
//获取该URL的文件名
System.out.println(url.getFile());
//获取该URL的查询名
System.out.println(url.getQuery());
public static void main(String[] args) {
HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
is = urlConnection.getInputStream();
fos = new FileOutputStream("day10\\beauty3.jpg");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
System.out.println("下载完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(urlConnection != null){
urlConnection.disconnect();
}
}
}
2.10.反射
1.基本概念
1.反射的概念
- Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
- 每一个类加载-完成之后,就会在内存中创建一个Class类型的对象(一个类只有一个Class对象),这个对象包含了对当前类的所有结构信息,是对当前类的一个描述对象;
2.静态和动态语言
- 动态语言:在运行时可以改变自身结构的语言(引入新的类,已有的函数可以被删除等等);即在运行时代码可以根据某些条件改变自身的结构;Object-C、C#、JavaScript、PHP、Python、Erlang。
- 静态语言:运行时不可改变代码结构的语言;java、C、C++;
3.java反射机制提供的功能(在运行时)
- 判断任意一个对象所属的类;
- 创建任意一个对象;
- 判断任意一个对象所具有的成员变量和方法;
- 获取泛型信息;
- 调用任意一个对象的成员变量和方法;
- 处理注解;
- 生成动态代理
4.反射相关的API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
2.Class理解
1.对java.lang.Class类的理解
- 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解析运行。相当于将某个字节码文件加载到内存。此过程就称为类的加载。加载到内存中的类,就称为运行时类,此运行时类就是Class的一个实例。
- 我们不能直接new一个Class类的实例,只能在解析运行时有JVM的类加载器自动将类加载到内存,作为Class的一个实例;
- 总的来说:Class的实例就是一个运行时类;
2.获取Class实例的四种方式
- 调用运行时类的属性class
- 通过运行时类的实例,调用getClass方法
- 调用Class的静态方法:Class.forName(String classPath)
- 使用类的加载器:ClassLoader
//1.调用运行时类的属性class
Class<Person> clazz1 = Person.class;
System.out.println("clazz1 = " + clazz1);
//2.通过运行时类的实例,调用getClass方法
Person person = new Person();
Class<? extends Person> clazz2 = person.getClass();
System.out.println("clazz2 = " + clazz2);
//3.调用Class的静态方法:Class.forName(String classPath)
Class<?> clazz3 = Class.forName("com.atguigu.Person");
System.out.println("clazz3 = " + clazz3);
//4.使用类的加载器:ClassLoader
ClassLoader classLoader = Ayi_02_getClass.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.Person");
System.out.println("clazz4 = " + clazz4);
System.out.println("(clazz1 == clazz2) = " + (clazz1 == clazz2));
System.out.println("(clazz1 == clazz3) = " + (clazz1 == clazz3));
System.out.println("(clazz1 == clazz4) = " + (clazz1 == clazz4));
3.Class实例可以是哪些结构?
@Test
public void test2() {
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);
}
3.ClassLoader理解
1.类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存,那么系统就会通过如下三个步骤对该类进行初始化
- 类的加载
- 将class字节码文件内容加载到内存中
- 并将其中静态数据转换成方法区的运行时数据结构
- 然后生成一个代表这个类的java.lang.Class对象,作为方法区中该类数据的访问入口(引用地址)
- 所有需要访问和使用类数据的操作只能通过这个Class对象,这个过程需要类加载器参与
- 类的链接:将Java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范。保证没有安全方面的问题
- 准备:正式为变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
- 初始化:
- 执行类构造器<clinit>方法的过程。类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
- 类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按顺序合并产生
- 当初始化一个类的时候,如果发现其父类还没进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
public class Ayi_03_ClassLoadingTest {
public static void main(String[] args) {
//类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按顺序合并产生
System.out.println(A.m);
}
}
class A{
static {
m = 300;
}
static int m = 100;
//类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按顺序合并产生
/*
类似与
<clinit>(){
m = 300;
m = 100;
}
*/
}
public class Ayi_03_ClassLoadingTest {
@Test
public void test1() {
//调用静态常用不会加载类,如果调用的是父类中的静态常量,子类也不会加载
A.show();
/*
输出:
B
public static final void show()
说明:
1.调用父类中的静态方法时(无论有没有被final修饰)都会初始化这个类
2.调用父类中的静态域时,只有初始化父类,不会初始化子类
*/
}
@Test
public void test2() {
System.out.println(A.b);
/*
输出:
200
说明:调用静态常量变量,不会初始化这个类(因为常量在**链接**阶段就存入调用类的常量池中了)
*/
}
}
class A extends B{
static {
System.out.println((m=300));
System.out.println("A");
}
static int m = 100;
}
class B{
static {
System.out.println("B");
}
public static final void show(){
System.out.println("public static final void show()");
}
public static final int b = 200;
}
什么时候会发生类的初始化?
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动时,优先初始化main方法所在类
- 创建一个对象实例时;
- 调用类的静态成员(finally修饰除外)和静态方法(finally也会初始化)时;
- 子类初始化父类
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域的时候,只有声明这个域的类才会被初始化
- 同过子类调用父类静态变量和静态方法,不会导致子类初始化
- 定义类的引用时不会初始化类;
- 引用静态常量时不会初始化类(常量在链接阶段就存入调用类的常量池中了)
- 当访问一个静态域的时候,只有声明这个域的类才会被初始化
2.类的加载器
类加载器的作用:
- 将.class文件字节码内容加载到内存中
- 将这些静态数据转换成方法区中运行时的数据结构
- 在堆中生成一个java.lang.Class对象,作为方法区中类数据的访问入口。
类的缓存:
- 标准的JavaSE类加载器可以按要求查找类
- 一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间
- JVM垃圾回收机制可以回收这些Class对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kazY8i3V-1629283075523)(C:/Users/dell/Desktop/image-20210714224117240.png)]
3.ClassLoader的分类
类加载器的作用就是把类(class)装载进内存
- 引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
- 扩展类加载器:负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库
- 系统类加载器:是最常用的加载器,负责加载我们自定的类,和我们引入的第三方类库
@Test
public void test1() {
//我们自定义的类就是系统类加载器加载进内存的
ClassLoader classLoader1 = Ayi_04_getClassLoaders.class.getClassLoader();
//输出的是系统类加载器
System.out.println("classLoader1 = " + classLoader1);
//获取系统类加载的父类加载器,即扩展类加载器
ClassLoader classLoader2 = classLoader1.getParent();
//输出的是扩展类加载器
System.out.println("classLoader2 = " + classLoader2);
//获取扩展类加载器的父类加载器,即引导类加载器
ClassLoader classLoader3 = classLoader2.getParent();
//输出的结果为null,说明我们不可以获取到引导类加载器,它是专门用来加载java平台的核心类库
System.out.println("classLoader3 = " + classLoader3);
ClassLoader classLoader4 = String.class.getClassLoader();
//输出的依然为null,说明String类也是引导类加载器加载的
System.out.println("classLoader4 = " + classLoader4);
}
4.ClassLoader加载加载配置文件
Properties properties = new Properties();
ClassLoader classLoader = Ayi_04_getClassLoaders.class.getClassLoader();
//默认路径是在当前Module的src目录下
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
properties.load(is);
String username = properties.getProperty("username");
String password = properties.getProperty("password");
System.out.println("username = " + username);
System.out.println("password = " + password);
Class类的常用方法
方法 | 描述 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用无参构造函数,返回该Class对象的一个实例 |
getName() | 返回Class对象所表示类的全类名 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Method getMethod(String name,Class … paramTypes) | 返回一个Method对象,此对象的形参类型为paramType |
获取属性 | |
Field[] getFields() | 返回当前对象以及所有超类中声明为Public的属性 |
Field[] getDeclaredFields() | 返回当前对象声明的所有属性(不包括父类的) |
获取方法 | |
Method[] getMethods() | 获取当前类及其超类中声明为public的所有方法 |
Method[] getDeclaredMethods() | 返回当前类中声明的所有方法 |
获取构造方法 | |
Constructor[] getConstructors() | 获取当前类中所有声明为public的构造器 |
Constructor[] getDeclaredConstructors() | 获取当前类中所有的构造器 |
获取父类 | |
Class getSuperclass() | 获取类的父类 |
Type getGenericSuperclass() | 获取类带泛型的父类 |
获取接口 | |
Class[] getInterfaces() | 获取类实现的接口 |
Class[] getGenericInterfaces() | 获取类实现的带泛型的接口 |
获取类的包和注解 | |
Package getPackage() | 获取类所在的包 |
Annotation[] getAnnotations() | 获取类的注解 |
获取指定的属性 | |
Field getField(String fieldName) | 获取类的实例的声明为public的属性(包括父类) |
FieldgetDeclaredField(String fieldName) | 获取类实例的任意属性(不包括父类) |
获取指定方法 | |
Method getMethod(String methodName, Class… args) | 获取类实例声明为public的方法(包括父类) |
Method getDeclaredMethod(String methodName, Class… args) | 获取类实例的任意方法(不包括父类) |
获取运行时类的带泛型的父类的泛型
/*
获取运行时类的带泛型的父类的泛型
*/
@Test
public void test4(){
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
System.out.println((actualTypeArguments[0]).getTypeName());
}
Field中常用的方法
方法 | 描述 |
---|---|
int getModifiers() | 返回修饰符对应的一个枚举值 可以通过ModiFier.toString(int modifier)获取字符串类型 |
Class getType() | 返回返回值类型的Class类 |
String getName() | 返回方法名 |
Method中常用的方法
方法 | 描述 |
---|---|
Annotation[] getAnnotations() | 获取方法声明的所有注解 |
int getModifiers() | 获取方法的权限修饰符 |
Class getReturnType() | 获取方法的返回值类型的Class |
String getName() | 获取方法名 |
Class[] getParameterTypes() | 获取形参列表所有的Class |
Class[] getExceptionTypes() | 获取方法抛出的所有异常 |
2.11.java8新特性
1.Lambda表达式
本质是函数式接口的实例
-
举例:(o1,o2) -> Integer.compare(o1,o2);
-
格式:(o1,o2) -> Integer.compare(o1,o2);
- ->: Lambda操作符,又称箭头操作符;
-
-> 左边: 方法的参数列表;
- ->右边: 方法体;
-
Lambda表达式的使用规则:
-
参数列表:
如果只有一个参数,那么括号可以省略
如果使用了泛型,那么参数的类型也可以省略
-
方法体:
如果方法体中只有一行代码,那么大括号可以省略;如果这行代码是一个return语句,那么return关键字也可省略;
-
public class LambdaTest {
@Test
public void test1() {
Person person = nation -> System.out.println("我是一个" + nation + "人!!");
person.show("中国");
}
@Test
public void test2() {
Comparator<Integer> comparator1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(comparator1.compare(12, 4));
Comparator<Integer> comparator2 = (o1, o2) -> o1.compareTo(o2);
System.out.println(comparator2.compare(12, 36));
}
}
interface Person {
void show(String nation);
}
2.函数式接口
- 只包含一个抽象方法的接口,称为函数式接口。
- 在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
1.四大核心函数式接口
接口 | 方法 |
---|---|
Consumer<T>消费型接口 | void accept(T t) |
Supplier<T>供给型接口 | T get() |
Function<T, R>函数型接口 | R apply(T t) |
Predicate<T>断定型接口 | boolean test(T t) |
@Test
public void test1() {
List<String> list = Arrays.asList("北京", "南京", "上海", "广州", "深圳", "东京");
List<String> filterString1 = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.endsWith("京");
}
});
System.out.println("filterString1 = " + filterString1);
System.out.println("********************************");
List<String> filterString2 = filterString(list, str -> str.endsWith("京"));
System.out.println("filterString2 = " + filterString2);
}
public List<String> filterString(List<String> list, Predicate<String> pre) {
ArrayList<String> arrayList = new ArrayList<>();
for (String str : list) {
if (pre.test(str)) {
arrayList.add(str);
}
}
return arrayList;
}
@Test
public void test2() {
BiFunction<Integer, Integer, Integer> addNum = (num1, num2) -> num1 + num2;
System.out.println(addNum.apply(1, 2));
}
####2.其他函数式接口
接口 | 方法 |
---|---|
BiFunction<T, U, R> | R apply(T t, U u); |
UnaryOperator<T>(Function子接口) | T apply(T t); |
BinaryOperator<T>(BiFunction 子接口) | T apply(T t1, T t2); |
BiConsumer<T, U> | void accept(T t, U u) |
BiPredicate<T,U> | boolean test(T t,U u) |
ToIntFunction<T> | int applyAsInt(T t) |
ToLongFunction<T> | long applyAsLong(T t) |
ToDoubleFunction<T> | double applyAsDouble(T t) |
IntFunction<R> | R apply(int value) |
LongFunction<R> | R apply(long value) |
DoubleFunction<R> | R apply(double value) |
3.方法引用与构造器引用
当要传递给Lambda体的操作已经有实现的方法了,这时我们就可以使用方法引用了
要求:实现接口的抽象方法的形参列表和返回值类型,与要引用的方法的形参列表和返回值类型必须一样;
方法引用的使用场景主要有以下三种情况:
- 对象 :: 实例方法名
- 类 :: 静态方法名
- 类 :: 实例方法名
/*
情况1:对象 :: 实例方法
Supplier<String> String get() 和 Person String getName()类似,所以我们可以将getName()方法引用进来使用
*/
@Test
public void test1() {
Person person = new Person("ayi", 18);
Supplier<String> supplier1 = new Supplier<String>() {
@Override
public String get() {
return person.getName();
}
};
System.out.println("supplier1.get() = " + supplier1.get());
System.out.println("**************************");
Supplier<String> supplier2 = () -> person.getName();
System.out.println("supplier2.get() = " + supplier2.get());
System.out.println("**************************");
Supplier<String> supplier3 = person :: getName;
System.out.println("supplier3.get() = " + supplier3.get());
}
/*
情况2:类 :: 静态方法
举例:Function<Double, Long> Long apply(Double value)和
Math中 Long round(Double value)类似所以我们可以引用此函数
*/
@Test
public void test2() {
Function<Double, Long> function1 = new Function<Double, Long>() {
@Override
public Long apply(Double aDouble) {
return Math.round(aDouble);
}
};
System.out.println("function1.apply(12.3) = " + function1.apply(12.3));
System.out.println("****************************************");
Function<Double, Long> function2 = value -> Math.round(value);
System.out.println("function2.apply(12.6) = " + function2.apply(12.6));
System.out.println("****************************************");
Function<Double, Long> function3 = Math :: round;
System.out.println("function3.apply(12.5) = " + function3.apply(12.5));
System.out.println("****************************************");
}
/*
情况3:类 :: 实例方法
Function<Person, String>中
String apply(Person p){
return p.getName()
}
和
Person中 String p.getName()类似
*/
@Test
public void test3() {
Person person = new Person("ayi", 20);
Function<Person, String> function1 = new Function<Person, String>() {
@Override
public String apply(Person person) {
return person.getName();
}
};
System.out.println("function1.apply(person) = " + function1.apply(person));
System.out.println("****************************************");
Function<Person, String> function2 = person1 -> person1.getName();
System.out.println("function2.apply(person) = " + function2.apply(person));
System.out.println("****************************************");
Function<Person, String> function3 = Person::getName;
System.out.println("function3.apply(person) = " + function3.apply(person));
}
/*
同样的还有String中的compare(String str)和
BiFunction<String, String, Integer> Integer apply(String, String)
也类似
*/
@Test
public void test4() {
BiFunction<String, String, Integer> function = String::compareTo;
System.out.println("function.apply(\"abc\", \"abd\") = " + function.apply("abc", "abd"));
Person person1 = new Person("ayi", 22);
Person person2 = new Person("ayi", 22);
BiPredicate<Person, Person> predicate = Person::equals;
System.out.println("predicate.test(person1,person2) = " + predicate.test(person1, person2));
}
构造器引用
@Test
public void test1() {
/*
引用空参构造器
*/
Supplier<Person> supplier1 = Person::new;
System.out.println("supplier1.get() = " + supplier1.get());
/*
引用new Person(int age);
*/
Function<Integer,Person> function1 = Person::new;
System.out.println("function1.apply(18) = " + function1.apply(18));
BiFunction<String, Integer, Person> function2 = Person::new;
System.out.println("function2.apply(\"ayi\",18) = " + function2.apply("ayi", 18));
/*
这样会报错泛型的类型顺序和构造的类型顺序必须一致
BiFunction<Integer, String, Person> != new Person(String name, int age)
*/
//BiFunction<Integer, String, Person> function3 = Person::new;
}
数组引用
@Test
public void test2() {
Function<Integer, String[]> function1 = String[]::new;
String[] strs = function1.apply(10);
System.out.println("strs = " + Arrays.toString(strs));
Function<Integer, String[][]> function2 = String[][]::new;
String[][] strs2 = function2.apply(10);
for (int i = 0; i < 10; i++) {
strs2[i] = new String[i];
}
}
4.StreamAPI
1.基本概念
使用Stream API对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。
实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。 所以在java 8中引入了StreamAPI专门用来对集合等数据进行处理,弥补了Radis等无法对数据进行处理的缺陷;
Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而Stream是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
2.性质:
- Stream不会自己存储数据;
- Stream不会改变数据源,他们会返回一个持有结果的新Stream;
- Stream操作时延迟执行的,他们会等到需要的时候才执行
3.Stream操作的三个步骤
- 创建Stream ====> 通过一个数据,获取流;
- 中间操作 ====> 是一些列对数据的操作(过滤、映射等);
- 终止操作 ====> 又称终端操作,一旦执行终止操作,就会执行中间操作链,并返回结果Stream。之后就不会再使用了
4.创建Stream的四种方法
**方法1:**java8中的Collection接口被扩展了,提供了两个获取流的实例方法
- default Stream<E> stream() : 返回一个顺序流
- default Stream<E> parallelStream() : 返回一个并行流(在执行操作的时候,多个元素是并行操作的)
/*
方法1:通过集合中声明是方法创建Stream
java8中的Collection接口被扩展了,提供了两个获取流的方法
*/
@Test
public void test1() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
//default Stream<E> stream() : 返回一个顺序流
Stream<Integer> stream1 = list.stream();
//default Stream<E> parallelStream() : 返回一个并行流: 在执行操作的时候,多个元素是并行操作的
Stream<Integer> stream2 = list.parallelStream();
}
**方法2:**Java8中的Arrays的静态方法stream()可以获取数组流
- static <T> Stream<T> stream(T[] array): 返回一个流
/*
方法2:通过数组创建Stream
java8中的Arrays提供了一个静态方法stream()可以获取数组流;
*/
@Test
public void test2() {
int[] arr = new int[]{1,2,3,4,5,6,7,8,9,10};
//public static IntStream stream(T array)
IntStream stream1 = Arrays.stream(arr);
Person p1 = new Person("ayi", 18);
Person p2 = new Person("John", 22);
Person[] people = new Person[]{p1, p2};
Stream<Person> personStream = Arrays.stream(people);
}
**方法3:**调用Stream类静态方法 of(),通过显示值创建一个流。它可以接受任意数量的参数。
- public static <T> Stream<T> of(T… values) : 返回一个流
/*
方法3:通过Stream的静态方法of()
*/
@Test
public void test3() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
**方法4:**使用Stream的静态方法iterate和generate方法创建无限流
注意:interface UnaryOperator<T> 继承了 Function<T, T>: 传入一个T, 返回的也是T
- 迭代: public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
- 生成: public static<T> Stream<T> generate(Supplier<T> s)
/*
方法4:使用Stream的静态方法iterate和generate方法
*/
@Test
public void test4() {
//迭代: public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//interface UnaryOperator<T> 继承了 Function<T, T>: 传入一个T, 返回的也是T
Stream.iterate(0, t -> t+2).limit(10).forEach(System.out::println);
//生成: public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
5.Stream的中间操作
1.筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 过滤 |
distinct() | 通过流的hashCode()和equals()去重 |
limit(long maxSize) | 输出前n个元素(<=) |
skip(long n) | 跳过前n个元素(>) |
@Test
public void test1() {
List<Person> list = new ArrayList<>();
list.add(new Person("ayi1", 19));
list.add(new Person("ayi2", 21));
list.add(new Person("ayi3", 37));
list.add(new Person("ayi4", 18));
list.add(new Person("ayi4", 18));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi6", 6));
//过滤:filter
Stream<Person> stream1 = list.stream();
stream1.filter(person -> person.getAge() >= 18).forEach(System.out::println);
System.out.println("================================");
//流一旦执行终止操作就被关闭,想用就必须在创建
//去重:distinct
Stream<Person> stream2 = list.stream();
stream2.distinct().forEach(System.out::println);
System.out.println("================================");
//limit
Stream<Person> stream3 = list.stream();
stream3.limit(3).forEach(System.out::println);
System.out.println("================================");
//skip
Stream<Person> stream4 = list.stream();
stream4.skip(3).forEach(System.out::println);
}
2.映射
方法 | 描述 |
---|---|
map(Function f) | 映射每一个元素 |
flatMap(Function f) | 如果流中嵌套着流,那么把所有的流汇成一个大流,类似list.addAll(list1) |
mapToDouble(ToDoubleFunction f) | 映射每一个元素,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 映射每一个元素,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 映射每一个元素,产生一个新的 LongStream。 |
@Test
public void test1() {
List<Person> list = new ArrayList<>();
list.add(new Person("ayi1", 19));
list.add(new Person("ayi2", 21));
list.add(new Person("ayi3", 37));
list.add(new Person("ayi4", 18));
list.add(new Person("ayi4", 18));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi6", 6));
//普通的映射;注意:是映射不能过滤,如果下面代码中else去了,就成过滤了,会报错
list.stream().map(Person::getName).map(str -> {
if (str.endsWith("6"))
return str;
else
return null;
}).forEach(System.out::println);
System.out.println("====================================");
//获取姓名最后一个数字大于3和姓名
list.stream().map(Person::getName).filter(str -> {
char c = str.charAt(str.length() - 1);
return c > '3';
}).forEach(System.out::println);
System.out.println("====================================");
list.stream().map(Person::getName).filter(Ayi_03_StreamMap::filterName).forEach(System.out::println);
System.out.println("====================================");
//如果流中还嵌套着流,可以用flatMap,类似与list.addAll(list)
Stream<Stream<Character>> stream =
list.stream().map(Person::getName).map(Ayi_03_StreamMap::getStreamFromStr);
stream.forEach(stream1 -> stream1.forEach(System.out::println));
System.out.println("====================================");
Stream<Character> stream1 = list.stream().map(Person::getName).flatMap(Ayi_03_StreamMap::getStreamFromStr);
stream1.forEach(System.out::println);
}
public static boolean filterName(String str) {
return str.charAt(str.length() - 1) > '3';
}
public static Stream<Character> getStreamFromStr(String str) {
List<Character> list = new ArrayList<>();
for (char c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
3.排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序(集合元素要实现Comparable接口) |
sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
@Test
public void test1() {
List<Person> list = new ArrayList<>();
list.add(new Person("ayi1", 19));
list.add(new Person("ayi2", 21));
list.add(new Person("ayi3", 37));
list.add(new Person("ayi4", 18));
list.add(new Person("ayi4", 18));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi6", 6));
Stream<Person> stream1 = list.stream();
Stream<Person> sorted1 = stream1.sorted(); //按年龄降序
sorted1.forEach(System.out::println);
System.out.println("==============================");
Stream<Person> stream2 = list.stream();
Stream<Person> sorted2 = stream2.sorted((person1, person2) -> Integer.compare(person1.getAge(), person2.getAge())); //按年龄升序
sorted2.forEach(System.out::println);
}
4.终止操作
1.匹配与查找
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素;只要匹配到元素就返回false |
findFirst() | 返回第一个元素;与并行流无关 |
findAny() | 返回当前流中的任意元素;并行流 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(Collection去做迭代,称为外部迭代。) |
2.规约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 Optional<T> |
@Test
public void test1() {
//计算1~100的和
Integer sum1 = Stream.iterate(1, v -> v + 1).limit(100).reduce(0, Integer::sum);
System.out.println("sum1 = " + sum1);
Optional<Double> sum2 = Stream.generate(Math::random).limit(100).reduce(Double::sum);
System.out.println("sum2 = " + sum2);
}
3.收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流中的数据保存在集合中 |
Collector中的方法
方法 | 描述 |
---|---|
List<T> toList() | 把流中的元素收集到list中 |
Set<T> toSet() | 把流中元素收集到Set中 |
Collection<T> toCollection(Supplier<ArrayList<T>>) | 把流中元素收集到Collection中 |
Long counting() | 计算流中元素的个数 |
Integer summingInt() | 对流中元素的整数属性求和 |
Double averagingInt() | 对流中元素Integer属性求平均值 |
IntSummaryStatistics summarizingInt() | 对流中Integer属性求统计值 IntSummaryStatistics{ count=9, sum=167, min=6, average=18.555556, max=37 } |
String joining() | 连接流中每个字符串 |
Optional<T> maxBy(Compator<T> compator) | 根据比较器选择最大值 |
Optional<T> minBy(Compator<T> compator) | 根据比较器选择最小值 |
<T> reducing() | 规约 |
<T> collectingAndThen | 包裹另一个收集器,对其结果转换函数 |
Map<K, Lis\t> groupingBy() | 根据某属性值对流分组,属性为K,结果为V |
Map<Boolean, List<T>> partitioningBy(Predicate) | 根据true或false进行分区 |
public class Ayi_07_Collect {
public static List<Person> list = new ArrayList<>();
static {
list.add(new Person("ayi1", 19));
list.add(new Person("ayi2", 21));
list.add(new Person("ayi3", 37));
list.add(new Person("ayi4", 18));
list.add(new Person("ayi4", 18));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi5", 16));
list.add(new Person("ayi6", 6));
}
@Test
public void test1() {
//List<T> Collector.toList()
List<Person> list1 = list.stream().filter(person -> person.getAge() > 18).collect(Collectors.toList());
list1.forEach(System.out::println);
System.out.println("===============================");
//Set<T> Collector.toSet()
Set<Person> set = list.stream().filter(person -> person.getAge() < 18).collect(Collectors.toSet());
set.forEach(System.out::println);
System.out.println("===============================");
//Collection<T> Collector.toCollection(Supplier<ArrayList<T>>)
ArrayList<Person> collect = list.stream().collect(Collectors.toCollection(ArrayList::new));
collect.forEach(System.out::println);
System.out.println("===============================");
//Long counting()
Long count = list.stream().collect(Collectors.counting());
System.out.println("count = " + count);
System.out.println("===============================");
}
@Test
public void test2() {
Integer summingInt = list.stream().collect(Collectors.summingInt(Person::getAge));
System.out.println("summingInt = " + summingInt);
System.out.println("===============================");
Double averagingInt = list.stream().collect(Collectors.averagingInt(Person::getAge));
System.out.println("averagingInt = " + averagingInt);
System.out.println("===============================");
IntSummaryStatistics summarizingInt = list.stream().collect(Collectors.summarizingInt(Person::getAge));
System.out.println("summarizingInt = " + summarizingInt);
System.out.println("===============================");
String joining = list.stream().map(Person::getName).collect(Collectors.joining());
System.out.println("joining = " + joining);
System.out.println("===============================");
}
@Test
public void test3() {
Optional<Person> maxBy = list.stream().collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));
System.out.println("maxBy = " + maxBy);
System.out.println("===============================");
Optional<Person> minBy = list.stream().collect(Collectors.minBy(Comparator.comparingInt(Person::getAge)));
System.out.println("minBy = " + minBy);
System.out.println("===============================");
Integer reducing = list.stream().collect(Collectors.reducing(0, Person::getAge, Integer::sum));
System.out.println("reducing = " + reducing);
System.out.println("===============================");
Integer collectingAndThen = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
System.out.println("collectingAndThen = " + collectingAndThen);
System.out.println("===============================");
Integer collect = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
Integer sum = 0;
for (Person p : list) {
sum += p.getAge();
}
return sum;
}));
System.out.println("collect = " + collect);
}
@Test
public void test4() {
Map<Integer, List<Person>> groupingBy = list.stream().collect(Collectors.groupingBy(Person::getAge));
groupingBy.entrySet().forEach((entry) -> {
System.out.println(entry.getKey() + "," + entry.getValue());
});
System.out.println("===============================");
Map<Boolean, List<Person>> partitioningBy1 = list.stream().collect(Collectors.partitioningBy(person -> person.getAge() > 18));
partitioningBy1.entrySet().forEach((entry) -> {
System.out.println(entry.getKey() + "," + entry.getValue());
});
System.out.println("===============================");
}
}
5.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接口实现提供的异常。
2.n.java中BigDecimal的用法
2.n.1.构造器
构造器 | 作用 |
---|---|
BigDecimal(int) | 创建一个具有参数所指定整数值的对象。 |
BigDecimal(double) | 创建一个具有参数所指定双精度值的对象。 //不推荐使用 |
BigDecimal(long) | 创建一个具有参数所指定长整数值的对象。 |
BigDecimal(String) | 创建一个具有参数所指定以字符串表示的数值的对象。//推荐使用 |
BigDecimal(double)会有精度误差
如果一定要用double作为构造器的源,则应先将double转为字符串
或则
用BigDecimal的静态方法
BigDecimal bigDecimal = BigDecimal.valueOf(3.6666);
2.n.2.方法
方法名 | 作用 |
---|---|
add(BigDecimal) | 加 |
subtract(BigDecimal) | 减 |
multiply(BigDecimal) | 乘 |
divide(BigDecimal) | 除 |
toString() | 转字符串 |
doubleValue() | 返回double |
floatValue() | 返回float |
longValue() | 返回long |
intValue() | 放回int |
2.n.3.四舍五入
需要对BigDecimal进行截断和四舍五入可用setScale方法
常数 | 值 | 作用 |
---|---|---|
ROUND_UP | 0 | 像上约(绝对值) |
ROUND_DOWN | 1 | 像下约(绝对值) |
ROUND_CEILING | 2 | 向正无穷方向舍入 |
ROUND_FLOOR | 3 | 向负无穷方向舍入 |
ROUND_HALF_UP | 4 | 离哪个近就像哪约(操作绝对值) 如果相等就像上约 |
ROUND_HALF_DOWN | 5 | 离哪个近就像哪约(操作绝对值) 如果相等就像下约 |
ROUND_HALF_EVEN | 6 | 离哪个近就像哪约 如果相等,就向相邻的偶数舍入。 |
ROUND_UNNECESSARY | 7 |
3.java语言面试
3.1.java中boolean到底占了几个字节
1.官方文档描述
boolean: The boolean data type has only two possible values: true and false.Use this datatype for simple flags that track true/false conditions. This data type represents one bit ofinformation, but its “size” isn’t something that’s precisely defined.
布尔类型:布尔数据类型只有两个可能的值:真和假。使用此数据类型为跟踪真/假条件的简单标记。这种数据类型就表示这一点信息,但是它的“大小”并不是精确定义的。也就是说,在java规范中没有明确指出boolean的大小。
2.区分大小的三种说法
-
1、1个bit (1/8个字节)
理由:boolean类型的值只有true和false两种逻辑值,在编译后会使用1和0来表示,这两个数在内存中按位算,仅需1位(bit)即可存储,位是计算机最小的存储单位。在传智播客java基础班中也有有此理由(复习时所参考的视频)。 -
2、1个字节
理由:虽然编译后1和0只需占用1位空间,但计算机处理数据的最小单位是1个字节,1个字节等于8位,实际存储的空间是:用1个字节的最低位存储,其他7位用0填补,如果值是true的话则存储的二进制为:000o 0001,如果是false的话则存储的二进制为:0000 0000。 -
3、4个字节
理由:在《Java虚拟机规范》—书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。==也就是说在JVM规范指出boolean当做int处理,也就是4字节,boolean数组当做byte数组处理,==这样我们可以得出boolean类型占了单独使用是4个字节,在数组中是确定的1个字节。
抽象类与接口有哪些异同?
接口
interface A{
int x = 1;
}
class B{
int x = 1;
}
class C extends B implements A{
public void px(){
//报错
System.out.println(x);
System.out.println(super.x); //调用B里的x
System.out.println(A.x); //调用A里的x
}
public static void main(String[] args) {
new C().px();
}
}
interface A{
void play();
}
interface B{
void play();
}
interface C extends A,B{
Ball ball = new Ball("football");
}
class Ball implements A,B{
private String name;
public String getName(){
return name;
}
public Ball(String name){
this.name = name;
}
@Override
public void play() {
//同时了A,B里的play();
ball = new Ball("basketball"); //继承了interface C里的ball(常量),不能赋值
System.out.println(ball.getName);
}
}