1.Java的基本语法
1.基本格式
// 类的修饰包括:public,abstract,final 修饰符 class 类名{ 程序代码 } 例: public class Test{ public static void main(String[] args){ System.out.println("hello " + "world"); } }
- 语法说明
- 1.
Java
程序代码都必须放在类中,类需要用class
关键字定义,class
前面可以有修饰符(public
,default
默认不显示,abstract
,final
)- 2.
main
函数:主函数是程序的入口,想要执行的代码必须写入主函数中,因为JVM
执行时首先执行main
方法,且写法是固定的(一个类中只能有一个主函数)- 3.
String[] args
:main
函数的参数,可使用数组形式也可使用可变参数形式,可利用该参数在启动时给程序传递指定的参数,多个参数用空格
分隔
- 4.中括号
{}
:划分代码块的边界- 5.
Java
源文件后缀名为.java
- 6.当类不是定义在
java.lang
等基本包中时,要使用import
将相应的包加载到当前类中- 7.
Java
程序中一句连续的字符串不能分开在两行中书写,可以使用加号+
将连续的字符串分成两个字符串在不同行书写
2.基本规范
- 1.
Java
中类名,接口名采用大驼峰命名法:所有单词的首字母大写- 2.变量名,函数名采用小驼峰命名法:从第二个单词开始首字母大写
- 3.常量名所有字母都大写,多个单词之间用下划线
_
隔开- 4.包名全小写,且只能放在当前文件有效代码的第一行
- 5.望名知意
2.Java中的输入输出
1.输出
- 1.打印输出到
标准输出流
(控制台窗口)/** 通过调用打印流printStream中的方法进行输出 */ //输出内容单独一行,输出内容可以为空 System.out.println(); //输出内容不换行,输出内容不能为空 System.out.print();
2.输入
- 1.
JDK5.0
之后可通过控制台读取输入public static void main(String[] args) { // 利用Scanner类,其定义在java.util包中 // 首先创建一个Scanner对象并传入标准输入流System.in Scanner in = new Scanner(System.in); String next = in.next(); System.out.println(next); }
3.Java中的注释
- 1.
Java
中的注释有3
种类型
1.单行注释
//单行注释
2.多行注释
/* 多行注释 */
3.文档注释
/** * 文档注释 */ 例: /** * 这是类外部 */ package net.csdn.editor; public class Test{ /** * 这是方法外部,类内部 */ public static void main(String[] args){ /** * 这是方法内部 */ System.out.println("test"); } }
//带包编译 javac -d . Test.java //解释执行 java net.csdn.editor.Test; //生成说明文档 javadoc -d 文件名 源文件名.java //文件名是文档生成的目标路径,设为.表示在当前路径下生成 javadoc -d . Test.java
- 语法说明
- 1.
-d
表示带包,.
表示在当前文件中生成,可使用其他文件名,如果文件不存在会自动生成- 2.注释是对程序的功能或代码的解释说明
- 3.注释只在
Java
源文件中有效,不参与代码的编译和运行(编译程序时,编译器会忽略这些注释信息,不会将其编译到class
字节码文件中去)- 4.文档注释用来对某一个函数或者某个类添加说明
- 5.文档注释可以使用
javadoc
命令将文档注释提取出来生成帮助文档- 6.只有写在类外部和方法外部,类内部的文档注释才有作用
4.Java中的标识符
- 1.
Java
中需要定义符号用来标记名称,这些符号被称为标识符- 2.标识符可以由字母,数字,下划线(
_
)和美元符号($
)或人民币符号(¥
)组成,但不能以数字开头,也不能是Java
中的关键字或者保留字
5.Java中修饰符
1.访问控制修饰符
修饰符 本类 同包 不同包子类 不同包非子类 public 可以 可以 可以 可以 protected 可以 可以 可以 default 可以 可以 private 可以
- 1.决定内容的
访问权限
- 1.
类的访问控制
:只有public
和default
可以修饰类
,public
类可以在任何地方访问,default
类只能在同一个包内访问- 2.
成员的访问控制
:以上4个访问修饰符
都可以修饰属性
,方法
,构造方法
- 3.
局部变量的访问控制
:以上4个访问修饰符
都不能修饰局部变量
,只有final
可以修饰局部变量- 4.
继承中的访问控制
:子类可以访问父类的public
,default
成员(除非在同一包内)和protected
成员,但不能访问private
成员- 5.
接口的访问控制
:接口的成员默认是public
,不能用private
或protected
修饰- 2.
jdk9
之前default
不允许显式声明,之后允许在接口
中显式声明default
- 3.子类重写方法的访问权限不能比父类更严格(父类是
default
,子类重写该方法不能是private
)
1.public
- 1.所有类可见
- 2.可修饰
- 1.类(
外部类
,成员内部类
,静态内部类
,局部内部类
,抽象类
)- 2.接口
- 3.方法(
静态方法
,普通方法
)- 4.变量(
静态变量,全局变量
)- 5.常量(
常量
,静态常量
)- 3.不可修饰
- 1.类(
匿名内部类
)- 2.方法(
接口默认方法
)- 3.变量(
局部变量
)
2.protected
- 1.同包且所有子类(包括同包和不同包)可见
- 2.可修饰
- 1.类(
非顶级类/外部类
,非匿名内部类
)- 2.方法(
静态方法
,普通方法
)- 3.变量(
静态变量,全局变量
)- 4.常量(
常量
,静态常量
)- 3.不可修饰
- 1.类(
顶级类/外部类
,匿名内部类
)- 2.接口
- 3.方法(
接口默认方法
,接口中的变量
)- 4.变量(
局部变量
)- 5.枚举常量
3.default
- 1.默认,同包可访问
- 2.可修饰
- 1.类(
非匿名内部类
)- 2.方法(
静态方法
,普通方法
)- 3.变量(
静态变量,全局变量
)- 4.常量(
常量
,静态常量
)- 3.不可修饰
- 1.类(
匿名内部类
)- 2.接口
- 3.方法(
接口默认方法
)- 4.变量(
局部变量
,接口中的变量
)- 5.枚举常量
4.private
- 1.同类可访问
- 2.可修饰
- 1.类(
非顶级类/外部类
,非匿名内部类
)- 2.方法(
静态方法
,普通方法
)- 3.变量(
静态变量,全局变量
)- 4.常量(
常量
,静态常量
)- 3.不可修饰
- 1.类(
顶级类/外部类
,匿名内部类
)- 2.接口
- 3.方法(
接口默认方法
)- 4.变量(
局部变量
,接口中的变量
)- 5.枚举常量
2.非访问控制修饰符
1.static
- 1.
修饰属性
- 1.
类变量/静态变量
:该变量不独属于某个对象,被整个类所有的对象所共有(而不是每个对象都有一个该变量)- 2.静态变量存在
方法区
并只存在一份,静态变量在类加载时初始化,且只初始化一次,静态成员的生命周期与类的生命周期相同(类加载时初始化,类卸载时销毁)- 3.静态属性可以直接通过
类名.属性名
的方式访问- 4.类名.属性名会直接去
方法区
查找静态变量- 5.
static
不能修饰局部变量
,只能修饰成员变量
- 6.静态成员变量虽然独立于对象,但所有的静态方法和静态变量都可通过对象访问
- 2.
修饰方法
- 1.静态方法:静态方法属于类,而不是类的实例,静态方法中无法访问非静态的内容(因为非静态成员依赖于实例),如果需要访问非静态成员,必须先创建类的实例
- 2.通过
类名.方法名()
的方式访问- 3.静态方法仍然可以通过
对象名.方法名()
的方式访问- 4.
static
无法修饰构造方法- 5.静态方法内部无法使用
this
和super
关键字- 6.静态内容可以直接从方法区中访问可能会导致
类的实例对象还未创建
也不会存在this
和super
,有可能会产生冲突- 3.
修饰代码块
- 1.静态代码块:随着类加载而加载,静态代码块在类加载时执行,且只执行一次,一般为静态变量赋初始值,可以提升程序性能
- 2.因此通常会将一些只需要进行一次的初始化操作都放在
static
代码块中- 3.静态初始化块可置于类中的任何地方,类中可有多个静态初始化块
- 4.类初次被加载时,会按照静态初始化块的顺序来执行每个块,并且只会执行一次
- 4.
修饰类
- 1.静态内部类:可以直接通过外部类访问,而不需要外部类的实例
- 2.静态内部类不能直接访问外部类的非静态成员
- 3.静态内部类可以有静态成员(变量、方法等)
2.final
- 1.
修饰变量
- 1.常量,
final
变量一旦初始化后不能被修改,常量不存在默认值,一般情况下在创建时必须进行初始化- 2.只有一种情况例外:要求该变量必须在构造方法中被初始化,并且不能有空参数的构造方法,这样可让每个实例都有一个不同的变量,并且这个变量在每个实例中只会被初始化一次,于是这个变量在单个实例里就是常量
- 3.实例变量:必须在声明时或构造方法中初始化
- 4.局部变量:
final
可以修饰局部变量
(局部变量只能用final
修饰),可以在声明时或后续代码中初始化,但只能赋值一次- 5.静态变量:
final
通常和static
一起使用创建类常量,类常量必须在声明时或静态代码块中初始化- 6.
final
可以修饰引用,表现为堆地址不可修改,但对象内容可以修改
- 7.
final
和abstract
这两个关键字是互斥的,不能同时出现- 8.接口中声明的所有变量本身是
final
的- 2.修饰方法
- 1.将方法变成最终的方法,即可以被子类继承,但不可以被子类重写,防止该方法的内容被修改
- 2.
final
方法不能被重写,但可以被重载(即定义同名但参数不同的方法)- 3.
final
方法比非final
快一些,final
方法比非final
方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定,final
方法调用时使用的是invokespecial
指令- 3.
修饰类
- 1.该类不可以被继承
- 2.不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等
- 3.匿名类中所有变量都必须是
final
变量- 4.匿名类中如果访问外部方法的局部变量,那么这些局部变量必须是
final
或有效 final(effectively final)
,且匿名类中也不能修改其值,这是由Java
语言设计决定的,主要是为了保证数据的一致性
和线程安全性
- 5.匿名类可以修改外部类的实例变量,因为这些变量的生命周期与外部类实例绑定,而不是与方法的局部变量绑定
- 6.数据一致性
- 1.匿名类的生命周期可能比外部方法的生命周期更长,如果匿名类访问的外部方法的局部变量不是
final
,那么在外部方法执行完毕后,局部变量的值可能会被修改,导致匿名类中访问的值不一致- 2.通过将局部变量声明为
final
,可以确保匿名类中访问的值始终是外部方法执行时的值,避免数据不一致的问题- 7.线程安全性
- 1.匿名类可能在一个线程中运行,而外部方法可能在另一个线程中运行,如果局部变量不是
final
,多个线程可能会同时修改该变量,导致线程安全问题- 2.通过将局部变量声明为
final
,可以确保变量的值在匿名类中不会被修改,从而保证线程安全
3.native
- 1.用来声明
本地方法
,该方法的实现由非Java
语言实现- 2.一般用于
java
与外环境交互或与操作系统交互- 3.
native
可以与所有其它的java
标识符连用,但是abstract
除外, 因为native
暗示这些方法是有代码实现的,只不过这些实现体是非java
的- 4.
native
方法的优点
- 1.可以访问底层系统功能
- 2.可以提高性能
- 3.可以复用现有代码
- 5.
native
方法的缺点
- 1.破坏了 Java 的跨平台性
- 2.增加了代码的复杂性
- 3.容易引入内存泄漏和崩溃问题
- 6.
native
方法通过JNI
(Java Native Interface
)实现,JNI
是Java
提供的一种机制,用于在Java
代码中调用本地代码或在本地代码中调用Java
代码- 7.
native
方法可以是实例方法,也可以是静态方法,且native
方法可以通过JNI
抛出Java
异常
4.abstract
/** * 车子类 */ public abstract class Car { Car(){ System.out.println("抽象方法无参构造函数"); } Car(String a){ System.out.println("抽象有参构造方法"); } public abstract void run(); public void stop(){ System.out.println("抽象类的实例方法"); } } /** * 自行车 */ class Bicycle extends Car{ @Override public void run() { System.out.println("跑"); } }
- 1.
abstract
是Java
中的一个关键字,用于定义抽象类或抽象方法,不能用于变量,其主要作用是提供一种机制,允许定义不完整的类或方法,强制子类去实现或扩展- 2.抽象类可以包含抽象方法和非抽象方法
- 3.抽象类不能实例化对象,但是抽象类有构造函数(因为子类创建对象时会先初始化父类的属性和方法),声明抽象类的唯一目的是为了将来对该类进行继承扩充
- 4.抽象类通常用于定义一种通用的模板或规范,强制子类实现特定的行为
- 5.一个类不能同时被
abstract
和final
修饰,如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误- 6.抽象方法是一种没有任何实现的方法(没有方法体,只有方法签名),该方法的具体实现由子类提供
- 7.抽象方法不能用
private
修饰,因为抽象方法必须被子类实现(覆写),而子类来不能访问private
权限,所以就会产生矛盾- 8.抽象方法不能被声明成
final
(抽象方法需要被继承重写)和static
(抽象方法没有方法体,只有被继承重写才有意义)- 9.抽象方法必须存在于抽象类或接口中,任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类
- 10.如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法
- 11.构造方法不能被声明为
abstract
,因为构造方法的作用是初始化对象,而抽象类不能被实例化
5.synchronized
- 1.
synchronized
是Java
中的关键字,用于实现多线程同步,它可以确保多个线程在访问共享资源时,同一时刻只有一个线程能够执行特定的代码块或方法,从而避免数据竞争
和不一致性
问题- 2.当一个线程进入
synchronized
块或方法时会获得独占锁,这会阻止其他线程同时进入相同的synchronized
块或方法,从而确保了共享资源的互斥访问- 3.
synchronized
关键字在Java
中有三种主要的使用方式
- 1.修饰非静态方法
public synchronized void synchronizedInstanceMethod() { // 同步的代码块 }
- 1.当非静态方法使用
synchronized
关键字修饰时,会将该方法变成同步方法,相当于对当前实例对象(this
)加锁,this
作为对象监视器- 2.因此只有一个线程可同时执行该实例方法,以确保对该实例的互斥访问
- 3.当前类创建多个实例对象时,
synchronized
会独立控制每个实例对象的同步- 2.修饰静态方法
public static synchronized void synchronizedInstanceMethod() { // 同步的代码块 }
- 1.当静态方法使用
synchronized
关键字修饰时,会将该方法变为同步静态方法,相当于对当前类的Class对象
加锁,当前类的Class对象
作为对象监视器(锁定的是类的Class对象
,因此会阻止不同实例以及静态方法之间的并发执行,因为它们共享相同的Class对象
)- 2.因此只有一个线程可同时执行该静态方法,以确保对该类的互斥访问
- 3.当前类创建多个实例对象时,所以实例对应同一个静态方法,所以
synchronized
控制所有实例对象的同步- 3.修饰代码块
// 方式一:当使用 synchronized(class) 时,锁定的是整个类的对象,而不是实例对象 // 即无论多少实例对象存在,它们都会竞争同一个锁 Object lock = new Object(); synchronized (lock) { // 同步的代码块 } // 方式二:当使用 synchronized(this) 时,锁定的是当前实例对象(this) // 即同一实例的不同方法调用会相互排斥,但不同实例之间的方法调用不会相互排斥 synchronized (this) { // 同步的代码块 }
- 1.使用
synchronized
关键字创建同步代码块,这样可指定要加锁的对象,括号中的对象是对象监视器- 2.该方式允许更细粒度的控制,可选择对某个特定对象进行同步,而不是整个方法或类
- 4.
synchronized
只能用于方法(普通方法,静态方法)或代码块,不能用于
- 1.变量
- 2.构造方法:为构造方法本身是线程安全的(每个线程都会创建自己的对象实例)
- 3.接口方法:接口中的方法不能被
synchronized
修饰,因为接口方法默认是抽象的- 4.
Lambda
表达式- 5.
synchronized
底层原理(具体参考高并发 3.锁及其原理
)1.synchronized 通过监视器锁来实现线程同步 2.每个 Java 对象都有一个监视器锁 3.线程在获取了对象的监视器锁后,可以执行被修饰的代码 4.线程在释放了对象的监视器锁后,其他线程可以尝试获取监视器锁 5.Monitor 的结构: 1.Owner:当前持有锁的线程 2.EntryList:等待获取锁的线程队列 3.WaitSet:调用 wait() 方法后进入等待状态的线程队列 6.锁的状态: 1.无锁状态:对象未被任何线程锁定 2.偏向锁:只有一个线程访问锁时,JVM 会优化为偏向锁,避免 CAS 操作 1.轻量级锁:多个线程竞争锁时,JVM 会升级为轻量级锁,使用 CAS 操作 1.重量级锁:当竞争激烈时,JVM 会升级为重量级锁,线程进入阻塞状态 5.对象头与锁:Java 对象在内存中的布局分为三部分: 1.对象头(Header):包含锁信息、GC 信息等 2.实例数据(Instance Data):对象的字段数据 3.对齐填充(Padding):为了内存对齐而填充的字节 6.synchronized 的锁信息存储在对象头中,对象头的主要组成部分包括: 1.Mark Word:存储对象的哈希码、锁状态、GC 分代年龄等信息 1.Klass Pointer:指向对象所属类的元数据 7.Mark Word中锁的状态通过不同的标志位来表示 1.无锁状态:锁标志位为 01 2.偏向锁:锁标志位为 01,并记录偏向线程 ID 3.轻量级锁:锁标志位为 00 4.重量级锁:锁标志位为 10 8.锁的升级过程:JVM 对 synchronized 的锁进行了优化,锁的状态会根据竞争情况逐步升级 1.偏向锁 1.适用场景:只有一个线程访问锁 2.原理:在对象头中记录偏向线程 ID,后续该线程可以直接获取锁,无需 CAS 操作 3.优点:减少锁竞争的开销 2.轻量级锁 1.适用场景:多个线程交替访问锁,但没有竞争 2.原理:使用 CAS 操作将对象头的 Mark Word 替换为指向线程栈中锁记录的指针 3.优点:避免线程阻塞,减少上下文切换 3.重量级锁 1.适用场景:多个线程竞争锁 2.原理:将对象头的 Mark Word 替换为指向 Monitor 的指针,线程进入阻塞状态 3.优点:解决高并发竞争问题 9.synchronized 的底层实现 1.字节码层面:当方法或代码块被 synchronized 修饰时,JVM 会在字节码中插入 monitorenter 和 monitorexit 指令 1.monitorenter:获取对象的 Monitor 2.monitorexit:释放对象的 Monitor 2.JVM 层面 1.JVM 会根据锁的状态和竞争情况,选择合适的锁机制(偏向锁、轻量级锁、重量级锁)。 2.如果锁竞争激烈,JVM 会将锁升级为重量级锁,线程进入阻塞状态,等待锁释放 10.synchronized 的优化:为了提升 synchronized 的性能,JVM 引入了以下优化技术 1.锁消除(Lock Elimination):JVM 在编译时通过逃逸分析,判断锁对象是否只被一个线程访问。如果是,则消除锁 2.锁粗化(Lock Coarsening):如果多个连续的锁操作作用于同一个对象,JVM 会将多个锁合并为一个锁,减少锁的获取和释放次数 3.自适应自旋(Adaptive Spinning):在轻量级锁竞争时,线程会自旋等待锁释放。JVM 会根据历史数据动态调整自旋次数
- 1.
synchronized
底层是基于JVM
的指令和对象的监视器(monitor
)来实现的,每个Java
对象都与一个Monitor
相关联,Monitor
是JVM
实现的一种同步机制,用于控制线程对共享资源的访问- 2.
synchronized
可以修饰方法或者代码块,用来保证线程的同步和安全- 3.当一个线程要执行一个被
synchronized
修饰的方法或代码块时,需要先获取该方法或代码块所属对象的监视器- 4.如果获取成功则该线程可执行同步代码,并且监视器的计数器加一
- 5.如果获取失败则该线程就会阻塞,直到监视器被释放
- 6.当一个线程执行完同步代码后会释放监视器,并且监视器的计数器减一
- 7.如果计数器为零则说明没有线程持有该监视器,其他线程可竞争获取该监视器
- 8.
synchronized
修饰方法时,字节码层面会有一个ACC_SYNCHRONIZED
标志,用来表示该方法是同步的- 9.
synchronized
修饰代码块时,字节码层面会有monitorenter
和monitorexit
两个指令,分别用来进入和退出监视器- 6.
JDK
底层优化
- 1.
synchronized
在JDK1.6
之后进行了优化,引入了偏向锁,轻量级锁,自旋锁等概念,用来提高性能和减少阻塞开销- 2.偏向锁:会将锁定的对象与线程相关联,当一个线程获得锁时,会标记对象为已偏向该线程,以后再次进入同步块时,不需要竞争锁而是直接获得,可减少无竞争情况下的锁开销
- 3.轻量级锁:低竞争情况下,
JDK
使用轻量级锁来减小锁开销,轻量级锁采用自旋方式来等待锁的释放,而不是进入阻塞状态- 4.自旋锁:当轻量级锁尝试获取锁失败时,
JDK
可选择使用自旋锁,自旋锁不会使线程进入阻塞状态,而是一直尝试获取锁,通常在短时间内完成- 5.适应性自旋:
JDK
中的锁可根据历史性能数据来调整自旋等待的次数,以达到更好的性能
6.transient
- 1.
transient
是Java
中的一个关键字,用于修饰类的成员变量- 2.作用是阻止变量被序列化,当一个
对象
被序列化时,被transient
修饰的变量
不会被包含在序列化的数据中- 3.如果某些变量包含敏感信息(如密码、密钥等)或者是临时数据(如缓存、计算结果等),可以使用
transient
修饰避免序列化,如果某些变量引用了不可序列化
的对象(如线程、文件句柄
等),可以使用transient
修饰,避免序列化时抛出异常- 4.优点
- 1.保护敏感信息:避免敏感数据在序列化时被泄露
- 2.减少序列化数据量:避免序列化不必要的临时数据,减少序列化后的数据大小
- 3.避免序列化异常:避免序列化不可序列化的对象
- 5.缺点
- 1.数据丢失:被
transient
修饰的变量在反序列化后会丢失,需要手动初始化(默认显示初始化的值也会被丢弃,被赋值为当前类型的默认值,如Integer
为null
)- 2.增加代码复杂性:需要在反序列化时手动恢复
transient
变量的值- 5.
transient
和@JSONField(serialize = false)
都用于控制字段的序列化行为,但应用场景和实现方式有所不同
![]()
- 1.
transient
- 1.
transient
是Java
的关键字,用于修饰类的成员变量- 2.作用是阻止字段被
Java
的默认序列化机制(ObjectOutputStream
)序列化- 3.使用场景:保护敏感信息(如密码),避免序列化临时数据(如缓存),避免序列化不可序列化的对象(如线程)
- 4.仅对
Java
的默认序列化机制有效,被transient
修饰的字段在反序列化后会丢失,需要手动恢复- 2.
@JSONField(serialize = false)
注解
- 1.其是
Fastjson
库中的注解,用于控制字段在JSON
序列化时的行为, 阻止字段被Fastjson
序列化为JSON
- 2.适用 控制
JSON
序列化时的字段输出,避免将某些字段暴露在JSON
中等场景(如敏感信息)- 3.其仅对
Fastjson
的JSON
序列化有效,不影响Java
的默认序列化机制- 4.其只会影响序列化行为,反序列化时字段值会保持默认值(默认显示初始化的值不会被丢弃)
1.序列化 (Serialization)
- 1.定义:将对象转换为一种可存储或传输的格式(如二进制数据、
JSON
字符串等)- 2.目的:方便将对象保存到文件、数据库,或通过网络传输(例将
Java
对象转换为字节流或JSON
字符串)- 3.常见的序列化方式包括
- 1.
Java
对象序列化- 2.
JSON
序列化- 3.
XML
序列化- 4.
Protobuf
序列化- 5.其他序列化方式(如
Thrift、Avro
等)
- 4.
Java
对象序列化
- 1.使用
Java
的Serializable
接口,序列化为二进制格式,性能高- 2.仅适用于
Java
语言,适用Java
内部数据传输或存储,如RPC
调用、缓存等场景
- 5.
JSON
序列化
- 1.使用
JSON
库(如Fastjson、Jackson、Gson
等),序列化为JSON
字符串,可读性好- 2.支持多种语言,适用跨语言数据传输或存储,如
RESTful API
、配置文件等场景
- 6.
XML
序列化
- 1.使用
XML
库(如JAXB、XStream
等),序列化为XML
字符串,可读性好- 2.支持多种语言,适用跨语言数据传输或存储,如配置文件、
Web Service
等场景
- 7.
Protobuf
序列化
- 1.使用
Protocol Buffers
序列化为二进制格式,性能高- 2.支持多种语言,需要预定义
.proto
文件
2.反序列化(Deserialization)
- 1.定义:将序列化后的数据重新恢复为对象
- 2.目的:从文件、数据库或网络中读取数据并重建对象(例将字节流或
JSON
字符串恢复为Java
对象)
7.volatile
- 1.
volatile
是Java
中的一个关键字,用于修饰变量,它的主要作用是确保多线程环境下的可见性
和有序性
,但不能保证原子性
- 1.可见性
- 1.普通变量在多线程环境下可能会被缓存到线程的本地内存中,导致其他线程看不到最新的值
- 2.
volatile
修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值,而且当成员变量发生变化时,会强制线程将变化值回写到共享内存- 3.这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,
volatile
确保当一个线程修改了变量的值后,其他线程能够立即看到修改后的值- 2.有序性
- 1.普通变量可能会被
JVM
或CPU
进行指令重排序优化,导致程序行为不符合预期- 2.
volatile
禁止指令重排序,确保变量的读写操作按照代码的顺序执行- 3.不保证原子性:
volatile
不能保证复合操作(如i++
)的原子性,如果需要原子性操作应使用synchronized
或原子类- 2.
volatile
的应用场景
- 1.状态标记:假设一个线程调用
run()
方法,另一个线程调用stop()
方法,正常情况下当第一行中循环开始后调用第二行将active
设置为false
循环不会停止,但是以上代码中使用了volatile
修饰active
所以该循环会停止
- 2:单写多读:当一个变量只被一个线程修改,其他线程只读取时,可以使用
volatile
- 3.双重检查锁定:单例模式中可使用
volatile
确保实例的可见性
8.strictfp
- 1.
strictfp
是Java
中的一个关键字,用于修饰类、接口或方法,确保浮点数运算在不同平台上的结果一致,它的全称是strict floating-point
,即严格的浮点运算- 2.
strictfp
的作用
- 1.平台一致性:不同平台上(如不同的
CPU
架构或操作系统),浮点数运算的结果可能会有微小差异,strictfp
可以确保浮点数运算的结果在所有平台上保持一致- 2.遵循
IEEE 754
标准:strictfp
强制浮点数运算遵循IEEE 754
标准,避免平台相关的优化或扩展- 3.
strictfp
的使用方式:strictfp
可以修饰类、接口或方法
- 1.修饰类:类中的所有方法都遵循严格的浮点运算规则
- 2.修饰接口:接口中的所有方法都遵循严格的浮点运算规则
- 3.修饰方法:只有被修饰的方法遵循严格的浮点运算规则,不能修饰构造方法
- 4.
strictfp
的优点
- 1.平台一致性:确保浮点数运算在所有平台上结果一致
- 2.可移植性:适合需要跨平台运行的应用程序
- 3.符合
IEEE 754
标准:避免平台相关的浮点数优化或扩展- 5.
strictfp
的缺点
- 1.性能损失:严格的浮点运算可能会限制某些平台的优化,导致性能下降
- 2.使用场景有限:大多数应用程序对浮点数运算的精度要求不高,因此
strictfp
的使用场景较少
9.assert
// 语法1:condition是一个布尔表达式;如果 condition为false,则抛出AssertionError,如果condition为true,程序会继续执行而不会抛出异常 assert condition; // 语法2:message是一个表达式,通常是一个字符串,用于描述断言失败的原因 // 如果condition为false,则抛出AssertionError,并将message作为错误信息 assert condition : message;
- 1.
assert
是Java
中的一个关键字,用于在代码中插入断言(assertion
),帮助开发者在开发和测试阶段检查程序的正确性- 2.运行
Java
程序时,需要显示使用-ea
参数才能启用断言
- 3.
assert
有两种语法形式
- 1.简单形式
- 2.带消息的形式
- 4.
assert
的作用
- 1.调试和测试:开发和测试阶段,用于检查程序的假设条件是否成立
- 2.文档化:通过断言明确代码的预期行为,提高代码的可读性
- 3.快速失败:条件不满足时立即抛出异常,避免程序继续执行导致更严重的问题
- 5.
assert
的优点
- 1.简单易用:语法简单,易于在代码中插入断言
- 2.提高代码质量:帮助开发者发现潜在的错误和逻辑问题
- 3.不影响生产环境:默认情况下,断言在生产环境中是禁用的,不会影响性能
- 6.
assert
的缺点
- 1.默认禁用:断言在
JVM
中默认是禁用的,需要通过-ea
参数启用- 2.不适合处理用户输入:断言通常用于内部检查,不适合处理用户输入或外部数据
- 3.错误信息有限:简单形式的断言提供的错误信息较少,可能不足以定位问题
- 7.实际开发中,很多框架和库并没有直接使用
assert
,而是通过其他方式(如单元测试框架、日志系统、异常处理等)来实现类似的功能
- 1.
JUnit
:JUnit
是Java
中最流行的单元测试框架,它提供了丰富的断言方法(如assertEquals
、assertTrue
、assertNotNull
等),用于验证测试结果是否符合预期
- 2.
Spring Framework
:Spring
框架本身并没有直接使用assert
,但它提供了一些工具类(如Assert
类),用于在代码中进行断言检查
- 3.自定义断言工具
6.Java中的关键字
- 1.关键字是编程语言里事先定义好并赋予了特殊含义的单词
abstract assert boolean break byte case catch char class const continue default do double else enum extends final finally float for goto if implements import import instanceof int interface long new package private protected public return short static stricttfp super switch synchronized this throw throws transient try void volatile while - 语法说明
- 1.所有的关键字都是小写
- 2.程序中的标识符不能以关键字命名
- 3.
const
和goto
是保留关键字,虽然在Java
中还没有任何意义,但是在程序中不能作为自定义的标识符- 4.
true
,false
,null
不属于关键字,它们是一个单独标识类型,不能直接使用
7.Java中的常量
- 1.常量是指在程序运行期间其值不能被修改的变量,
Java
中常量用final
修饰- 2.不可变性:常量的值在初始化后不能被修改
- 3.必须初始化:常量必须在
声明时
或构造方法
中初始化- 4.命名规范:常量通常使用全大写字母,单词之间用下划线分隔(如
MAX_VALUE
)- 5.应用场景
- 1.配置值:如最大连接数、超时时间等
- 2.枚举值:如状态码、错误码等
- 3.数学常量:如圆周率
PI
1.静态常量
- 1.静态常量是指通过
static final
关键字定义的常量,其属于类而不是实例- 2.静态常量在类加载时初始化,且只有一份副本,并且在整个程序运行期间保持不变
- 3.一般推荐使用
static final
修饰常量,如果不加则每次new
一个对象都会为该常量重新分配一次内存(创建类的多个对象时,用static
修饰的常量在内存中只有一份拷贝,不用static
修饰则可能有多份拷贝)
- 1.不管静态常量还是普通常量只要是基本类型就不能在初始化后重新修改其值
- 2.对象类型普通常量、静态常量可以在初始化后修改其对象状态,但不可以改变其引用
- 4.特点
- 1.不可修改:一旦静态常量被赋值,就无法再修改其值,任何试图修改静态常量的操作都会导致编译错误
- 2.类级别:静态常量属于类,而不是实例
- 3.全局共享:所有实例共享同一个静态常量,静态常量可以被类的所有实例和其他类直接访问
- 4.内存效率高:静态常量在类加载时就会被初始化,存储在方法区中的常量池中且只有一份副本,不需要每次创建对象时都重新分配内存
- 5.应用场景
- 1.全局配置:如数据库连接字符串、
API
密钥等- 2.枚举替代:不需要枚举类型时,可以用静态常量代替
- 3.工具类常量:如
Math.PI、Integer.MAX_VALUE
等
2.常量池
- 1.常量池是
Java
虚拟机(JVM
)中用于存储常量数据的一块内存区域,主要用于存储编译期
生成的字面量(字符串,数字)
和符号引用(类,方法,字段的名称和描述符)
- 2.设计目的是为了提高性能和减少内存开销,通过共享常量数据来避免重复创建对象
- 3.常量池分为以下几种
- 1.
Class
文件常量池:存储在编译后的.class
文件中,包含类、方法、字段等的符号引用和字面量- 2.运行时常量池:是
Class
文件常量池在运行时的表现形式,类加载后Class
文件常量池会被加载到方法区
中,形成运行时常量池(运行时常量池可以动态添加常量,如String.intern()
方法)- 3.字符串常量池:字符串常量池是
运行时常量池
的一部分(缓存所有字符串字面量没有范围限制),专门用于存储字符串字面量,JDK 1.7
之前位于方法区
,JDK 1.7
及之后移至堆
中,通过String.intern()
方法可以将字符串添加到常量池中- 4.包装类常量池:存储特定范围的包装类对象(如
Integer、Long
等包装类在-128~127
之间的值会被缓存到常量池中),包装类常量池始终位于堆
中- 4.特点
- 1.共享性:相同的字面量在常量池中只有一份副本
- 2.不可变性:字符串常量池中的字符串是不可变的
- 3.性能优化:减少重复字符串的内存占用
- 5.底层原理
- 1.存储机制
- 1.字符串常量池使用
哈希表
存储字符串对象,确保相同的字符串字面量只存储一份- 2.包装类常量池使用
静态数组
缓存特定范围的对象,超出范围则创建新对象
- 2.
intern()
方法:将字符串添加到字符串常量池并返回其引用,JDK 1.6
及之前会复制字符串到字符串常量池,JDK 1.7
及之后则直接存储堆中字符串的引用- 3.
字符串拼接
的底层实现:使用StringBuilder
或StringBuffer
的append()
方法,最终生成新字符串对象- 4.
valueOf()
方法:通过valueOf()
方法从包装类常量池中获取对象,超出范围则创建新对象- 6.应用场景
- 1.字符串优化:通过
String.intern()
方法减少重复字符串的内存占用- 2.符号引用:存储类、方法、字段等的符号引用,支持动态链接
- 3.包装类常量池:缓存范围固定,不支持动态扩展,仅适用于频繁使用的小范围整数值的共享
- 8.字符串常量池和包装类常量池对比
- 1.相同点
- 1.目的相同:两者都是为了减少内存开销,通过共享常量数据来避免重复创建对象
- 2.缓存机制:都采用了缓存机制,字符串常量池缓存字符串对象,包装类常量池缓存特定范围的包装类对象
- 3.提高性能:通过共享对象,减少了内存分配和垃圾回收的压力,从而提高了程序的性能
- 4.不可变性:字符串和包装类对象都是不可变的,这种特性使得它们适合被缓存和共享
- 2.区别
- 1.存储内容不同:
- 1.字符串常量池:存储字符串字面量
- 2.包装类常量池:存储特定范围的包装类对象
- 2.缓存范围不同
- 1.字符串常量池:缓存所有字符串字面量,没有范围限制
- 2.包装类常量池:仅缓存特定范围的值(
-128~127
)- 3.存储位置不同
- 1.字符串常量池:
JDK 1.6
及之前位于方法区,JDK 1.7
及之后移至堆中- 2.包装类常量池:始终位于堆中
- 4.实现方式不同
- 1.字符串常量池:使用
哈希表
存储字符串对象,确保相同的字符串字面量只存储一份- 2.包装类常量池:通过
静态数组
缓存特定范围的对象,超出范围则创建新对象- 5.动态性不同
- 1.字符串常量池:支持动态添加字符串,如通过
String.intern()
方法- 2.包装类常量池:缓存范围固定,不支持动态扩展
- 6.适用场景不同
- 1.字符串常量池:适用于所有字符串字面量的共享
- 2.包装类常量池:适用于频繁使用的小范围整数值的共享
3.常量类型
- 1.整型常量:整数类型的数据(包括二进制,八进制,十进制,十六进制
4
种表现形式)
- 二进制: 由
0b
或0B
开头,并以数字0
和1
组成的数字序列- 八进制: 由
0
开头,并以数字0 ~ 7
组成的数字序列- 十进制: 以数字
0 ~ 9
组成的数字序列- 十六进制: 由
0x
或0X
开头,并以0 ~ 9,A ~ F
(包括0
和9
,A
和F
,字母不区分大小写)组成的数字序列- 2.浮点数常量
- 1.浮点数常量分单精度浮点数和双精度浮点数两种类型
- 2.单精度浮点数必须以
F
或者f
结尾- 3.双精度浮点数可以以
D
或d
结尾- 4.使用浮点数时不加任何后缀,虚拟机会默认为
double
双精度浮点数- 3.字符常量
- 1.字符常量表示一个字符,一个字符常量要用一对英文单引号
''
引起来,可以是英文字母,数字,标点符号以及由转义字符来表示的特殊字符- 2.
Java
采用的是Unicode
字符集,Unicode
字符以\u
开头,空白字符在Unicode
码表中对应的值为\u0000
- 4.字符串常量
- 1.字符串常量表示一串连续的字符,一个字符串常量要用一对英文双引号
""
引起来- 2.一个字符串可以包含一个字符或多个字符,也可以不包含任何字符
- 5.布尔常量
- 1.布尔常量的两个值
true
和false
,用于区分一个事物的真与假- 6.
null
常量
- 1.
null
常量只有一个值null
,表示对象的引用为空
4.字符集
- 1.字符(
Character
):各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等- 2.字符集(
Character set
):字符集是字符与二进制编码之间的映射关系,是计算机中用于表示字符的编码规则,Java
中的字符集处理主要涉及Charset
类、String
的编码和解码操作,以及与字符集相关的输入输出流- 3.常见字符集名称
- 1.
ASCII
字符集:7
位编码,表示英文字符- 2.
GB2312
字符集- 3.
GB18030
字符集- 4.
Unicode
字符集- 5.
UTF-8
字符集:可变长度编码,支持全球字符,使用1
到4
个字节表示字符,兼容ASCII
- 6.
UTF-16
字符集:固定长度编码,支持全球字符,使用2
或4
个字节表示字符,不兼容ASCII
- 7.
GBK
字符集:支持中文字符- 4.
Java
中的字符集处理
- 1.
Charset
类:Charset
类是Java
中用于表示字符集的类- 5.编码(
Encoding
)
- 1.将字符转换为二进制数据的过程
- 6.解码(
Decoding
)
- 2.将二进制数据转换为字符的过程
- 7.字符集与输入输出流
- 1.
InputStreamReader
:将字节流转换为字符流,支持指定字符集- 2.
OutputStreamWriter
:将字符流转换为字节流,支持指定字符集- 8.应用场景
- 1.文件读写:读取或写入文件时需要指定字符集以确保正确处理字符
- 2.网络通信:网络通信中需要确保发送方和接收方使用相同的字符集
- 3.数据库存储:存储和读取数据库时,需要指定字符集以确保正确处理字符
- 4.
Web
开发:需要确保请求和响应的字符集一致
5.进制转换
- 1.十进制和二进制之间的转换
- 十进制转换成二进制
- 将要转换的数除以
2
,得到商和余数,将商继续除以2
,直到商为0
,最后将所有余数倒序排列,得到的数就是转换结果- 二进制转换成十进制
- 从右到左用二进制位上的每个数去乘以
2
的相应次方(次方从0
开始),然后把所有相乘后的结果相加,得到的数就是转换结果- 2.二进制和八进制的转换
- 八进制转换为二进制
- 将要转换的数除以
2
,得到商和余数,将商继续除以2
,直到商为0
,最后将所有余数倒序排列,得到的数就是转换结果- 二进制转换为八进制
- 从右到左每三位为一组,如果最左边不够三位用
0
补齐,然后将每一组从左到右乘以2
的相应次方(次方从0
到2
),最后把所有相乘后的结果相加,得到的数就是转换结果- 3.二进制和十六进制的转换
- 十六进制转换为二进制
- 将要转换的数除以
2
,得到商和余数,将商继续除以2
,直到商为0,最后将所有余数倒序排列,得到的数就是转换结果- 二进制转换为十六进制
- 从右到左每四位为一组,如果最左边不够四位用
0
补齐,然后将每一组从左到右乘以2
的相应次方(次方从0
到2
),最后把所有相乘后的结果相加,得到的数就是转换结果
8.Java中的变量
- 1.变量:是计算机内存中的一块存储空间,是存储数据的基本单元
- 2.变量名:是标识变量(内存单元)的标识符
- 3.变量值:是变量(内存单元)中存储的数据
- 4.变量是存储数据值的容器,每个变量都有一个类型和一个名称,且变量必须先声明后使用
1.变量的创建方式(4种)
- 1.先声明再赋值
数据类型 变量名; 变量名 = 值; 例:int x; x=5;
- 2.声明的同时直接赋值
数据类型 变量名 = 值; 例:int x = 5;
- 3.同时声明多个变量,之后再一一赋值
数据类型 变量名1,变量名2,...; 变量名1 = 值1; 变量名2 = 值2; 。。。 例:int x,y,z; x = 5;y = 6;z = 7;
- 4.同时声明多个变量并赋值
数据类型 变量名1 = 值1,变量名2 = 值2 ... ; 例:int x = 1,y = 2, z = 3;
2.变量的数据类型
- 1.
Java
是一门强类型的编程语言,定义变量时必须声明变量的数据类型- 2.
Java
中变量的数据类型分为两种:基本数据类型(八种) 和 引用数据类型
- 3.基本数据类型的十进制数值范围保存在其对应包装类中
- 4.基本数据类型:直接存储在
栈内存
中- 5.引用数据类型:引用(变量名)存储在栈内存中,实际对象存储在
堆内存
中
3.变量类型的范围
1.整数类型变量
类型名 占用空间 取值范围 十进制取值范围 默认值 byte(字节型) 1B/8位 -27 ~ 27-1 -128 ~ 127 0 short(短整型) 2B/16位 -215 ~ 215-1 -32768 ~ 32767 0 int(整型) 4B/32位 -231 ~ 231-1 -2147483648 ~ 2147483647 0 long(长整型) 8B/64位 -263 ~ 263-1 -9223372036854775808 ~ 9223372036854775807 0L 2.浮点数类型变量
类型名 占用空间 取值范围 默认值 float(单精度浮点数) 4B/32位 1.4E-45 ~ 3.4E+38 , -3.4E+38 ~ -1.4E-45 0.0f double(双精度浮点数) 8B/64位 4.9E-324 ~ 1.7E+308 , -1.7E+308 ~ 4.9E-324 0.0d 3.字符类型变量
类型名 占用空间 取值范围 默认值 char 2B/16位 0~65535(十六位二进制的Unicode字符) u0000 语法说明:
- 1.字符类型变量的创建方式
//1.直接赋字符 char 变量名 = '值'; //2.赋0~65535的整数值 char 变量名 = 整数值; //3.利用16进制Unicode编码赋值 char 变量名 = 'Unicode编码值';
- 2.第一种赋值方式,单引号内部只能放一个字符
- 3.第二种赋值方式,整数范围不能超过取值范围
- 4.
Java
中每个char
类型的字符变量都会占用2
个字节- 5.
char
类型的变量赋值时,需要用英文单引号''
把字符括起来,也可以将char类型的变量赋值为0~65535
范围内的整数,计算机会自动将这些整数转化为所对应的字符4.布尔类型变量
类型名 占用空间 取值范围 boolean Java中boolean类型通常占用 1个字节 true,false 语法说明:
- 1.
boolean
(布尔)类型有两个值:false
和true
,用来判定逻辑条件;这两个值不能与整数进行相互转换- 2.
Java
虚拟机规范对boolean
的描述:boolean
类型在编译后会被映射为int
类型(占4
字节)或byte
类型(占1
字节),数组中boolean
数组的每个元素占用1
字节,具体取决于JVM
的实现
4.变量的作用域
- 1.
Java
中的变量可以分为
- 1.类变量(静态变量)
- 2.全局变量(成员变量)
- 3.局部变量
1.静态变量
- 1.静态变量是使用
static
修饰的成员变量,也叫类变量,其具有唯一性和共享性,一般用于存储整个程序都需要使用的数据- 2.静态变量指在类中定义的一个变量,其属于类而不是实例,即无论创建多少个类实例,静态变量在内存中只有一份拷贝,所有该类的实例共享同一个类变量的值
- 3.类变量在类加载时被初始化,且只初始化一次,并且其初始化顺序与定义顺序有关(即静态变量定义在使用后也行,但如果一个静态变量依赖于另一个静态变量,那么其必须在后面定义)
- 4.静态变量的生命周期与程序的生命周期一样长,即在类加载时被创建并在整个程序运行期间都存在直到程序结束才会被销毁;因此静态变量可用来存储整个程序都需要使用的数据(配置信息、全局变量等)
- 5.常量与静态变量的区别
- 1.常量是用
final
关键字修饰的变量,一旦被赋值就不能再修改,但是多个对象可能会存在多个拷贝;静态变量是用static
关键字修饰的变量,静态变量只会在类加载时初始化一次,所有对象共享一份内存数据- 2.常量在编译时就已确定了值,而静态变量的值可在运行时改变
- 3.常量通常用于存储一些固定的值(数学常数、配置信息等),而静态变量通常用于存储可变的数据(计数器、全局状态等)
- 6.静态变量的线程安全性
- 1.
Java
中的静态变量是属于类而不是对象的实例;因此当多个线程同时访问一个包含静态变量的类时需要考虑其线程安全性- 2.静态变量在内存中只有一份拷贝,被所有实例共享;因此如果一个线程修改了静态变量的值,那么其他线程在访问该静态变量时也会看到修改后的值;这可能会导致并发访问的问题,因为多个线程可能同时修改静态变量,导致不确定的结果或数据一致性问题
- 3.为了确保静态变量的线程安全性需要采取适当的同步措施(同步机制、原子类或
volatile
关键字),以便在多线程环境中正确地读取和修改静态变量的值- 7.静态变量的使用场景
- 1.存储全局状态或配置信息
- 2.计数器或统计信息
- 3.缓存数据或共享资源
- 4.工具类的常量或方法
- 5.单例模式中的实例变量
2.成员变量
- 1.直接在类中声明的变量叫成员变量(又称全局变量)
- 2.如果未对成员变量设置初始值,则系统会根据成员变量的类型自动分配初始值
- 3.成员变量定义后,其作用域是其所在的整个类
- 4.成员变量的定义没有先后顺序(定义在使用后也行,但如果一个成员变量依赖于另一个成员变量,那么其必须在后面定义),但最好将成员变量的定义集中在类的顶部
- 5.成员变量和静态变量的区别
- 1.生命周期不同
- 1.成员变量随着对象的创建而存在,随着对象的回收而释放
- 2.静态变量随着类的加载而存在,随着类的消失而消失,且优先于对象存在
- 2.调用方式不同
- 1.成员变量只能被对象调用
- 2.静态变量可以被对象调用,还可以被类名调用
- 3.数据存储位置不同
- 1.成员变量存储在堆内存的对象中,是对象的特有数据
- 2.静态变量数据存储在方法区(共享数据区)的静态区
class Test1{ private static byte a1; private static short a2; private static int a3; private static long a4; private static float a5; private static double a6; private static char a7; private static boolean a8; private static String a9; public static void main(String[]args){ System.out.println(a1); System.out.println(a2); System.out.println(a3); System.out.println(a4); System.out.println(a5); System.out.println(a6); System.out.println(a7); System.out.println(a8); System.out.println(a9); } }
3.局部变量
- 1.局部变量包括形参,方法中定义的变量,代码块中定义的变量
- 2.局部变量没有默认值,使用前必须显示初始化
- 3.局部变量只能用
final
修饰符修饰
- 4.局部变量的作用域范围从定义的位置开始到其所在语句块结束
- 5.如果局部变量的变量名和全局变量的变量名相同,则在局部变量的作用范围内全局变量暂时失效,如果在局部变量的作用范围内想访问该成员变量,则必须使用关键字
this
类引用成员变量
- 6.注意:基本数据类型的变量传递的是值,而引用数据类型的变量传递的是引用所指的地址(对值的修改不会影响本身的数据,而对堆地址中的数据进行修改,指向该地址的引用的数据值都会改变)
public class Test01 { public static void main(String[] args) { //基本数据类型的变量 int a = 10; //引用数据类型的变量 MyClass b = new MyClass(); Test01 t = new Test01(); //输出原本的值 System.out.println(a); System.out.println(b.value); //调用指定方法 t.test(a,b); //输出调用方法后的值,其中基本数据类型的变量值没有发生变化,引用数据类型的变量值发生了变化 //基本数据类型变量依旧没有变化 System.out.println(a); //引用数据类型的变量值发生了变量 System.out.println(b.value); } //进入方法,首先给形参赋值:int a = 10;然后给MyClass b赋予上面引用指向的地址 public void test(int a,MyClass b) { //局部变量a的值发生变量,但是没有影响到原本变量 a = 20; //引用类型变量指向的对地址的数据值发生变量,会影响到指向该对地址的引用的数据值 b.value = 100; } } class MyClass{ int value = 50; }
5.变量的线程安全
1.静态变量
- 1.只要有修改变量值的操作,无论是在单例或非单例都是线程不安全的
- 2.如果线程只是读取变量的值,而不会改变变量的值,这种情况下则是线程是安全的
- 3.产生线程安全问题的原因:
![]()
- 1.静态变量只在类加载时初始化一次,且其位于
方法区
被所有对象共享- 2.由于共享一份内存,一旦静态变量被修改,其他对象均对修改可见,故线程非安全
- 4.如图所示执行流程:
- 1.当线程
1
执行了number = 1; number = 2;
后,线程2
获得执行权执行number = 1;
- 2.然后当线程
1
获得执行权执行打印第二次获取number
时; 输出结果为获取第二次number = 1
- 5.解决方式
![]()
- 1.使用同步方法:将
increment()
方法声明为synchronized
,这样一次只有一个线程能够执行该方法,从而保证线程安全性
- 2.使用同步代码块:使用
synchronized
关键字创建同步代码块,只有一个线程能够进入代码块执行
- 3.使用原子类:
Java
提供了java.util.concurrent.atomic.AtomicInteger
类,可以保证对整数类型的原子操作
- 4.使用
volatile
关键字:修饰静态成员变量,从而保证可见性,volatile
关键字保证了每次读取该变量的值时都是最新的,而不是使用缓存的值
2.成员变量
- 1.如果线程只读取变量的值而不改变,则无论是单例还是非单例都是线程安全的
- 2.如果有修改变量值的操作,则单例模式因为只有一个对象实例
singleton
存在,多线程同时操作时是不安全的,而非单例模式下多线程操作是安全的- 3.实例变量为对象实例私有,分配在虚拟机的堆(
heap
)中- 4.若在系统中只存在一个对象的实例,则多线程环境下类似静态变量在被某个线程修改后,其他线程对修改均可见,故线程非安全(如
springmvc controller
是单例非线程安全的)- 5.如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,故线程安全(如
struts2 action
默认是非单例的,每次请求在heap
中new
新的action
实例,故struts2 action
可用成员变量)
3.局部变量
- 1.局部变量的作用域在方法内部,其存放在虚拟机栈中
- 2.如果一个变量需要跨越方法的边界就必须创建在堆中
- 3.每个线程都有自己独立的调用栈,因此不同的线程可同时用不同的参数调用相同的方法
- 4.
Java
方法内部的局部变量不存在并发问题,因为每个线程都有自己的调用栈,局部变量保存在线程各自的调用栈里,不会共享自然也不存在并发问题- 5.线程封闭:方法中的局部变量因为不会和其他线程共享所以不会存在并发问题,这种解决问题的技术也叫做线程封闭(官方解释:仅在单线程内访问数据,由于不存在共享,所以即使不设置同步也不会出现并发问题)
6.Java中只有值传递
- 1.
Java
中只有值传递- 2.
基本数据类型
作为形参时传递的是值的拷贝,并不会改变原有的值- 3.
引用数据类型
作为形参时传递的是地址引用的拷贝,改变堆地址中的数据,原数据也会受到影响
9.Java类型转换
1.自动类型转换(隐式类型转换)
- 1.指两种数据类型在转换的过程中不需要显式地进行声明
- 2.自动类型转换必须同时满足两个条件:
- 1.两种数据类型彼此兼容
- 2.目标类型的取值范围大于源类型的取值范围
- 3.数据类型大小排序:
byte < char < short < int < long < float < double
- 1.
char
类型默认转换为int
类型的数据,如果将char
转换为byte
或short
需要进行强转
- 2.
float
类型转换为long
类型需要强转,而long
类型转换为float
则不需要
2.强制类型转换(显示类型转换)
- 1.指两种数据类型之间的转换需要进行显式的声明
- 2.当两种类型彼此不兼容,或者目标类型取值范围小于源类型时,自动类型转换无法进行,此时需要强制类型转换
目标类型 变量 = (目标类型)值
- 3.强制类型转换可能会发生数据精度缺失
class Test1{ public static void main(String[]args){ byte a; int b = 299; a = (byte)b; System.out.println(b); System.out.println(a); } } /* 原理: int类型4个字节,32位 299转换为2进制为00000000 00000000 00000001 00101011 byte类型占1个字节,8位 将int类型的数据强转为byte类型 会丢失前面多余24位,变为00101011即为43 */
- 4.
Java
中把浮点数类型强转为整数类型,会直接舍弃小数位,只保留整数位,且不会四舍五入
3.自动类型提升
- 1.只发生在数学运算期间
- 2.当运算中有高精度类型的数值参与,则结果会自动提升为高精度类型
- 3.
Java
中如果比int
类型小的类型做运算,Java
在编译时就会将它们统一强转成int
类型;当是比int
类型大的类型做运算,会自动转换成它们中的最大类型
class Test1{ public static void main(String[]args){ double a = 15.0; float b = 14.0f; float y = a+b+12+23; System.out.println(y); } }
- 4.
JVM
操作数栈的最小操作单元是int
(32
位),因此所有小于int
的类型(如byte、char、short
)在计算时都会被提升为int
,这种设计简化了JVM
的实现,因为不需要为每种小类型单独设计指令- 5.注意:
- 1.任意数据类型加上双引号或者拼接双引号都会变成
String
字符串类型- 2.程序从上到下,从左到右运行
class Test1{ public static void main(String[]args){ byte a = 13; byte b = 14; int x = 5; int y = 6; char m = 'a'; char n = 'A'; System.out.println(m+n+"A"); System.out.println("A"+m+n); System.out.println(a+b+"A"); System.out.println("A"+x+y); } }
10.Java中的运算符
1.算术运算符
运算符 运算 范例 结果 + 正号 +3 3 - 负号 -4 -4 + 加 5+5 10 - 减 10-5 5 * 乘 3*4 12 / 除 5/5 1 % 取模(求余) 7%5 2 ++ 自增(前) a=2;b=++a; a=3,b=3 ++ 自增(后) a=2;b=a++ a=3,b=2 - - 自减(前) a=2;b=–a; a=1,b=1 - - 自减(后) a=2;b=a–; a=1,b=2
- 语法说明:
- 1.自增自减运算时,如果运算符
++
或- -
放在操作数的前面则先进行自增或自减运行,再进行其他运算;如果运算符放在操作数的后面则是先进行其他运算再进行自增或自减运算
- 2.除法运算时,当除数和被除数都为整数时,结果也是一个整数;如果除法运算有小数参与,得到的结果会是一个小数(相当于自动类型提升)
- 3.取模运算时,运算结果取决于被模数(
%
左边的数)的符号,与模数(%
右边的符号无关)
2.赋值运算符
运算符 运算 范例 结果 = 赋值 a=3;b=2; a=3;b=2; += 加等于 a=3;b=2;a+=b; a=5;b=2; -= 减等于 a=3;b=2;a-=b; a=1;b=2; *= 乘等于 a=3;b=2;a*=b; a=6;b=2; /= 除等于 a=3;b=2;a/=b; a=1;b=2; %= 模等于 a=3;b=2;a%=b; a=1;b=2;
- 语法说明:
- 1.变量赋值时,如果两种类型彼此不兼容,或者目标类型取值范围小于源类型时,需要进行强制类型转换
- 2.但是如果使用
+=,-=,*=,/=,%=
运算符进行赋值时,强制类型转换会自动完成,程序不需要做任何显式地声明(虽然b+=a
的结果为int
类型,但是Java
虚拟机会自动完成强制类型转换)
3.比较运算符
运算符 运算 范例 结果 == 相等于 4 == 3 false != 不等于 4 != 3 true < 小于 4<3 false > 大于 4>3 true <= 小于等于 4<=3 false >= 大于等于 4>=3 true
- 语法说明:
- 1.基本数据类型的变量存放在虚拟机栈的局部变量表中的是值,
==
在比较基本数据类型时,比较的是变量值是否相同- 2.引用数据类型的变量存放在虚拟机栈的局部变量表中的是对象引用/堆地址,比较的是堆地址是否相同
4.逻辑运算符
运算符 运算 范例 结果 & 与 true&false false | 或 true|false true ^ 异或 true ^ true,true ^ false false,true ! 非 !true false && 短路与 true&&true true || 短路或 true||false true
- 语法说明:
- 1.使用
&
和|
进行运算时,不论左边为true
或者为false
,右边的表达式都会进行运算- 2.如果使用
&&
进行运算,当左边为false
时,右边的表达式则不会运算;如果使用||
进行运算,当左边为true
时,右边的表达式不会运算package test.com.wd.test; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub int x=0; int y=0; int z=0; boolean a,b; a = x>0 & y++>1; System.out.println(a); System.out.println("y="+y); System.out.println("----------------------------"); a = x>0 && y++>1; System.out.println(a); System.out.println("y="+y); System.out.println("----------------------------"); b = x==0 | z++>1; System.out.println(b); System.out.println("z="+z); System.out.println("----------------------------"); b = x==0 || z++>1; System.out.println(b); System.out.println("z="+z); } }
5.三元运算符
运算符 运算 范例 结果 判断条件?表达式1:表达式2; 类似if-else 3>5?“对”:“错” 错 package test.com.wd.test; public class Test{ public static void main(String[] args) { String str = 3>6?"对":"错"; System.out.println(str); } }
6.位逻辑运算符
运算符 运算 范例 结果 & 按位与运算 4&5 4 | 按位或运算 4 5 ^ 按位异或运算 4^5 1 ~ 按位反运算 ~4 -5 >> 右移运算符 8>>2 2 >>> 无符号右移运算符 -8>>>2 1073741822 << 左移运算符 8<<2 32
- 1.位与运算符: 将数字转换为二进制,低位对齐,高位不足的补
0
;如果对应的二进制位同时为1
,则结果为1
,否则为0
,最后将二进制转换为十进制- 2.位或运算符: 将数字转换为二进制,低位对齐,高位不足的补
0
;如果对应的二进制位有一个为1
,则结果为1
,如果对应的二进制都为0
,结果才为0
,最后将二进制转换为十进制- 3.位异或运算符: 将数字转换为二进制,低位对齐,高位不足的补
0
;如果对应的二进制位不同,则结果为1
,如果对应的二进制相同,结果才为0
,最后将二进制转换为十进制- 4.位取反运算符: 将数字转换为二进制,将二进制中的
1
改为0
,将0
改为1
,最后将二进制转换为十进制- 5.右移运算符: 将数字转换为二进制,将其二进制数组向右移动,即等价于十进制的除
2
的n次方,向右移动一位则除以2
的1
次方,正数左边第一位补0
,负数补1
- 6.无符号右移运算符: 正数的运算同右移运算法相同,无符号右移运算符和右移运算符的主要区别在于负数的计算,因为无符号右移是高位补
0
,移多少位补多少个0
;负数无符号右移,负数的二进制是正数的二进制取反再加1,例:-6
的二进制是6
的二进制取反再加1
,6
的二进制是0000 0000 0000 0000 0000 0000 0000 0110
,取反后加1
为1111 1111 1111 1111 1111 1111 1111 1010
,右移三位0001 1111 1111 1111 1111 1111 1111 1111
- 7.左移运算符: 将数字转换为二进制,将其二进制数组向左移动,即等价于十进制的乘
2
的n次方,向左移动一位则乘以2
的1
次方,正数左边第一位补0
,负数补1
1.按位与运算
- 1.与(
&
):是一种位运算符,同时也是一种逻辑运算符,特点是都为1时才为1,否则为0
- 2.按位与运算具有以下重要特性
- 1.清零特性:任何二进制位与
0
进行按位与运算,结果都为0
- 2.保留特性:任何二进制位与
1
进行按位与运算,结果保留原值- 3.结果的范围:按位与运算的结果不会超过参与运算的两个整数中的较小值
- 3.应用场景
- 1.掩码操作(提取或屏蔽某些二进制位)
- 2.判断奇偶性
- 3.权限控制
- 4.清零操作(通过按位与运算可以将某些二进制位清零)
- 5.检查特定位是否为
1
- 6.优化性能(判断一个数是否是
2
的幂)
- 7.图像处理(提取或操作像素的某些位)
2.按位或运算
- 1.或(
|
):是一种位运算符,同时也是一种逻辑运算符,特点是有一个为1就为1,都为0时才为0
- 2.按位或运算具有以下重要特性
- 1.置位特性:任何二进制位与
1
进行按位或运算,结果都为1
- 2.保留特性:任何二进制位与
0
进行按位或运算,结果保留原值- 3.结果的范围:按位或运算的结果不会小于参与运算的两个整数中的较大值
- 3.应用场景
- 1.合并权限/合并数据
- 2.设置特定位
- 3.优化算法(快速计算两个集合的并集)
- 4.图像处理(合并像素的某些位)
3.按位异或运算
- 1.异或(
XOR,^
) :是一种位运算符,同时也是一种逻辑运算符,特点是相同为 0,不同为 1
- 2.异或运算具有以下重要特性
- 1.交换律:
a ^ b = b ^ a
- 2.结合律:
a ^ (b ^ c) = (a ^ b) ^ c
- 3.自反性:
a ^ a = 0
- 4.与
0
的关系:a ^ 0 = a
- 5.可逆性:如果
c = a ^ b
,那么a = c ^ b
或b = c ^ a
- 6.如果一个数被同一个数异或两次,结果为其本身
- 3.应用场景
- 1.交换两个变量的值(不使用临时变量)
- 2.找出出现计奇数次的数字
- 3.简单数据加密解密
- 4.异或校验和:想要检查原始数据是否在传输过程中被篡改,可通过对数据的每个字节进行异或运算,得到一个校验位;然后将数据和校验位一起传输给接收端,接收端将接收到的数据再次进行异或运算,如果和校验位相同说明数据没有被篡改
- 5.判断两个数是否符号相反
- 6.利用异或运算可以快速判断一个数的奇偶性
- 7.简单的权限控制
4.按位右移运算
- 1.按位右移运算:是一种位运算符,用于将二进制数的所有位向右移动指定的位数,右移运算符有两种形式
- 1.带符号右移(
>>
):将二进制数的所有位向右移动,左边空出的位用符号位填充(正数补0
,负数补1
)- 2.无符号右移(
>>>
):将二进制数的所有位向右移动,左边空出的位用0
填充- 2.右移运算符的特性
- 1.等价于除以
2
的幂:右移n
位等价于将数除以2n(向下取整)- 3.应用场景
- 1.快速除以
2
的幂- 2.提取特定位
- 3.位操作优化(快速计算哈希值、掩码)
4.按位左移运算
- 1.按位左移运算:是一种位运算符,用于将二进制数的所有位向左移动指定的位数,右边空出的位用
0
填充- 2.按位左移运算的特性
- 1.等价于乘以
2
的幂:左移n
位等价于将数乘以 2n- 2.溢出问题:如果左移后的结果超出了数据类型的范围,会发生溢出
- 3.应用场景
- 1.快速乘以
2
的幂- 2.位操作优化(快速计算哈希值、掩码)
7.instanceof 运算符
- 1.
Java
中的一个关键字,用于检查一个对象
是否是指定类
或其子类
的实例,返回值为boolean
类型:如果对象是指定类或其子类的实例,则返回true
,否则返回false
- 2.使用范围
- 1.可用于类、接口和数组
- 2.不能用于基本数据类型
- 3.如果对象为
null
,instanceof
会返回false
- 4.由于
Java
的泛型擦除机制,instanceof
不能直接用于泛型类型
- 3.应用场景
- 1.类型检查:根据对象的类型执行不同逻辑时,可使用
instanceof
进行类型检查- 2.类型转换:类型转换之前,通常使用
instanceof
检查对象的类型,以避免ClassCastException
- 3.多态性:多态场景中
instanceof
可以用于判断对象的实际类型- 4.接口实现检查:检查对象是否实现了某个接口
8.运算符的优先级
- 1.运算符的优先级决定了表达式中运算的执行顺序,优先级从高到低如下
9.扩展:原码,反码,补码
- 1.原码: 一个整数按照绝对值大小转换成的二进制数,称为原码(最高位是符号位
0
表示正数,1
表示负数,其余位表示数值的绝对值)
- 2.反码: 反码是对原码的改进,正数的反码与原码相同,负数的反码是对其原码的符号位不变,其余位取反
- 3.补码: 补码是对反码的进一步改进,正数的补码与原码相同,负数的补码是对其反码加
1
- 4.计算机使用补码表示整数:补码解决了
0
的表示问题,统一了加减法运算,简化了硬件实现
- 1.原码和反码中,
0
有两种表示(+0 和 -0
);补码中,0
只有一种表示:00000000
- 2.补码使得加法和减法可以使用相同的硬件电路实现:
A - B
可以转换为A + (-B)
11.java中的选择结构语句
1.if语句
- 基本语法:
if(判断语句){ // 条件为 true 时执行的代码 }
2.if-else语句
- 基本语法:
if(判断语句){ // 条件为 true 时执行的代码 }else{ // 条件为 false 时执行的代码 }
3.多重if-else语句
- 基本语法:
if(判断语句1){ // 条件1 为 true 时执行的代码 }else if(判断语句2){ // 条件2 为 true 时执行的代码 } ... else if(判断语句n){ //执行语句n }else{ //执行语句n+1; }
- 语法说明:
- 1.多重
if-else
语句是一个整体,只能执行其中一个分支语句,一旦进入一个,其他的分支则没用,比较顺序从上到下- 2.注意判断语句的使用方式,同样的数值,不同的使用方式,结果不同
package test.com.wd.test; public class Test{ public static void main(String[] args) { int key = 75; if(key>100) { System.out.println("A"); }else if(key>90) { System.out.println("B"); }else if(key>80) { System.out.println("C"); }else if(key>60) { System.out.println("D"); }else { System.out.println("E"); } System.out.println("------------"); if(key<100) { System.out.println("A"); }else if(key<90) { System.out.println("B"); }else if(key<80) { System.out.println("C"); }else if(key<60) { System.out.println("D"); }else { System.out.println("E"); } } }
4.switch条件语句
- 基本语法:
switch(表达式){ case 目标值1: // 表达式等于 值1 时执行的代码 break; case 目标值2: // 表达式等于 值2 时执行的代码 break; ...... case 目标值n: 执行语句1; break; default: // 表达式不等于任何 case 值时执行的代码 }
package com.entity; import java.util.Random; public enum Color { RED("red color",0), GREEN("green color",1), BLUE("blue color",2), YELLOW("yellow color",3), BLACK("BLACK color",4); Color(String name,int id){ name = name; id = id; } private String colorName; private int id; public String getColorName(){ return colorName; } public int getId(){ return id; } public static Color getColor(int max){ Random random = new Random(System.currentTimeMillis()); int num = random.nextInt(max); switch(num){ case 0: return Color.RED; case 1: return Color.GREEN; case 2: return Color.BLUE; case 3: return Color.YELLOW; default: return Color.BLACK; } } }
package com.wd.test; import com.entity.Color; public class Test02 { public static void main(String[] args) { int length = Color.values().length; Color color = Color.getColor(length); switch (color){ case RED: System.out.println("select " + "RED"); break; case GREEN: System.out.println("select " + "GREEN"); break; case BLUE: System.out.println("select " + "BLUE"); break; case YELLOW: System.out.println("select " + "YELLOW"); break; default: System.out.println("select " + "BLACK!!!"); } } }
- 语法说明:
- 1.
switch
语句中,表达式和目标值都不支持null
且不能不写,否则会出现编译错误;表达式和目标值的类型需要兼容,否则也会出现编译错误;目标值只能声明为常量表达式,不能声明为范围,比较结果,变量
- 2.
case
语句的目标值不能重复,对于字符串类型
也一样;注意:字符串可以包含Unicode
转义字符,而case
重复值的检查是在Java
编译器对Java
源代码进行相关的词法转换之后进行的;即:有些目标值在源代码中不同,但是经过词法转换后是一样,就会造成编译错误(例:"男"
和"\u7537"
会发生编译错误)
- 3.
JDK1.5
之前,switch
语句只支持byte,short,char,int
四种数据类型- 4.
JDK1.5
及之后,switch
语句支持了枚举类
与byte,short,char,int的包装类
(注意:这种说法不完全正确,之所以switch
能够支持相应的包装类,是因为JDK1.5
开始支持自动拆/装箱,编译器会自动调用相应的xxxValue()
方法将其转换为int
类型;之所以支持枚举类,是因为枚举类会调用ordinal()
方法,该方法返回的是int
类型)
- 5.
Java1.7
及之后,switch
语句支持使用String
类型,实际底层调用的是String
类型的hashCode()
方法和String
类型重写的equals
方法(注意:该特性是在编译器层次上实现,在Java
虚拟机和字节码层次上还是只支持在swicth
语句中使用与整数类型兼容的类型,且在case
子句对应的语句块中仍需要使用String
的equals
方法进行字符串比较,因为哈希函数在映射的时候可能存在冲突,结合equals
方法可以解决冲突)- 6.
switch
中可以使用枚举替代String
类型,可以提高可读性和可维护性 ,则可以使用{}
防止变量名冲突
- 7.
case
之后最好带上break
(因为case
比较的结果为布尔类型,如果有一个case
结果为true
,其余case
不会再进行布尔判断),直接会从进入点开始一直往下执行,直到进入default
后退出switch
语句,无法起到分支的作用;且default
类似于if-else
分支中的else
,如果上述case
分支都没有结果则执行default
中的内容,default
也可以省略,建议加上,default
后的break
可加可不加,没有影响(此情况适用于default
在switch
最后,如果default
位置在switch
语句的其他位置,则最好加上break
,否则也会从default
往下一直执行到程序结束)- 8.如果
switch-case
结构中的多个case
的执行语句相同,则可以考虑进行合并,提高效率@Test public void test02(){ int score = 78; switch(score/10){ case 1: case 2: case 3: case 4: case 5: System.out.println("不及格!"); case 6: case 7: case 8: case 9: case 10: System.out.println("及格!"); } //优化 switch (score/60){ case 0: System.out.println("不及格"); case 1: System.out.println("及格"); } }
- 9.凡是可以使用
switch-case
的结构,都可以转换为if-else
,反之不成立,因为switch
对表达式和目标值有限制要求
5.三元运算符,if-else,switch-case的区别
- 1.凡是可以使用三元运算符(三元运算符内部只能是表达式,所以不能代替
if else
,而if else
能取代任何有三元和switch-case
结构的地方)- 2.但是反之不一定成立,如果可以使用三元运算符或者
switch-case
则使用他两,因为效率更高- 3.可使用
switch
语句或设计模式(如策略模式)避免if-else
语句的嵌套过深
12 java中的循环语句
1.定义:
- 1.在某些条件满足的情况下,反复执行特定代码的功能
2.循环语句分类
- 1.while循环语句
- 2.do…while循环语句
- 3.for循环语句
3.循环语句的四个组成部分
- 1.初始化部分
- 2.循环条件部分,必须是
boolean
类型- 3.循环体部分
- 4.迭代部分
4.优化循环性能
- 1.减少循环内部的复杂计算
- 2.使用
break
或continue
提前终止循环- 3.避免在循环中创建不必要的对象
1.while循环语句
- 基本语法:
while(循环条件){ 执行语句; ... }
- 语法说明:
- 1.
while
语句会反复地进行条件判断,只要条件成立,{}
内的执行语句就会执行,直到条件不成立,while
循环结束- 2.
{}
中执行语句被称作循环体,循环体是否执行取决于循环条件,当循环条件为true
,循环体就会执行,循环体执行完毕时会继续判断循环条件,如条件仍为true
则会继续执行,直到循环条件为false
,整个循环过程才会结束- 3.
while
循环适用于循环次数不确定
的情况下
2.do…while()循环语句
- 基本语法:
do{ 执行语句; ... }while(循环条件);
- 语法说明:
- 1.关键字
do
后面{}
中的执行语句是循环体- 2.
do...while
循环语句将循环条件放在了循环体的后面。即:循环体会无条件执行一次,然后在根据循环条件来决定是否继续执行- 3.
while
和do...while
的区别:如果循环条件在循环语句开始时就不成立,那么while
循环的循环体一次都不会执行,而do...while
循环的循环体还是会执行一次
3.for循环语句
- 基本语法:
for(初始化部分;循环条件部分;迭代部分){ 循环体部分 }
- 语法说明:
- 1.
for
循环的执行顺序:初始化部分–>循环条件部分(满足条件)–>循环体部分–>迭代部分–>循环条件部分(满足条件)–>…–>(循环条件部分不满足)–>结束循环- 2.
for
循环的循环条件部分必须为boolean
类型,所以只能有一个整体结果为boolean
类型的循环条件,也可以没有,如果没有循环条件部分,即成为死循环- 3.
for
循环的其他部分都可以存在多个语句,对循环本身没有影响,多个语句之间用逗号分隔;而不同部分用分号分隔- 4.
for
循环()
中的三部分内容都可以省略,但是必须通过;
进行语法占位,此时为无限死循环
- 5.
for
循环语句一般用在循环次数已知
的情况下
4.嵌套循环以及循环标签
- 基本语法:
for(初始化部分;循环条件部分;迭代部分){ 循环体部分; ... for(初始化部分;循环条件部分;迭代部分){ 循环体部分; ... } ... }
/** * @Author 依山观澜 * @Date 2021/9/12 8:43 * @Description 质数(素数):只能被1和它本身整除的自然数,1不是质数;即从2开始,到这个数-1结束为止,都不能被这个数整除 * @Since version-1.0 */ public class Test01 { public static void main(String[] args){ //标识i是否被j除尽,一旦除尽,即不是质数,修改其值 boolean isFlag = true; //计数器:用于记录质数的个数 int sum = 0; //获取当前时间距离1970-01-01 00:00:00的毫秒数 long start = System.currentTimeMillis(); //遍历100000以内的自然数 for(int i=2; i<100000; i++){ //让i除以j //优化二:两种优化方式:第一种j<i/2;第二种:Math.sqrt(i); //只对本身是质数有效 //临界值:除一个数等于这个数本身,即开方 for(int j=2; j<=Math.sqrt(i); j++){ if(i % j == 0){ isFlag = false; break;//优化一:只对本身非质数的自然数有效 } } if(isFlag){ sum++; } //重置isFlag isFlag = true; } long end = System.currentTimeMillis(); System.out.println("所花费的时间为:"+(end-start)+"毫秒"); System.out.println("质数个数为:"+sum); } }
//方法二:利用带标签的continue实现 label:for(int i=2; i<=100000; i++){ for(int j=2; j<Math.sqrt(i); j++){ if(i%j == 0){ continue label; } } sum++; }
- 语法说明:
- 1.嵌套循环是指在一个循环语句的循环体中再定义一个循环语句的语法结构
- 2.一般两层循环的嵌套循环,外层循环用来控制行数,内层循环用来控制列数,外层循环执行一次,内层循环执行一遍;也可以嵌套多层
- 3.
while,do...while,for
循环语句都可以进行嵌套,并且它们之间也可以相互嵌套
5.跳转语句
public static void main(String[] args) { label:for(int i=1; i<5; i++){ for(int j=1; j<10; j++){ if(j%4 == 0){ break label; } System.out.print(j+","); } System.out.println(); } }
- 1.
break
语句
- 1.使用范围:
- 1.
switch-case
- 2.循环结构
- 2.
swtich-case
中的作用:
- 终止某个
case
并跳出switch
语句- 3.循环中的作用:
- 结束当前循环(如果是嵌套循环,只能结束当前层循环,如果想使用
break
语句跳出外层循环,则需要对外层循环添加标记)- 4.带标签的
break
语句
- 结束指定标识的一层循环结构
- 5.注意:
break
关键字的(直接)后面不能声明执行语句,否则会编译错误(因为无法执行)public static void main(String[] args) { label:for(int i=1; i<5; i++){ for(int j=1; j<10; j++){ if(j%4 == 0){ continue label; } System.out.print(j+","); } System.out.println(); } }
- 2.
continue
语句
- 1.使用范围:
- 循环结构
- 2.循环中的作用:
- 1.终止本次循环,执行下一次循环(嵌套循环语句中
- 2.
continue
语句后面也可以通过使用标记的方式结束本次外层循环,用法与break
语句相似)- 3.带标签的
continue
语句
- 结束指定标识的一层循环结构的当次循环
- 4.注意:
continue
关键字的(直接)后面不能声明执行语句,否则会编译错误(因为无法执行)
13.java中的数组
1.数组的定义
- 1.数组是内存中一块连续的存储空间,用来存储多个
相同类型
的数据- 2.数组是一组数据的集合,数组中的每个数据被称作元素;数组可以存放任意类型的元素,但同一个数组里存放的元素类型必须一致
- 3.数组存储基本数据类型存储的是
值
,存储引用数据类型存储的是堆地址
2.数组的种类
- 1.按照维度:一维数组,二维数组,多维数组
- 2.按照元素的数据类型:基本数据类型元素的数组,引用数据类型元素的数组(即对象数组)
3.数组的相关概念
- 1.数据类型
- 2.数组名
- 3.数组元素
- 4.数组下标(索引,角标)
- 5.数组长度
4.数组的特点
- 1.数组是有序排列的
- 2.数组的长度一旦确定,就不能修改
- 3.可以直接通过下标(索引)的方式调用指定位置的元素,索引从
0
开始- 4.数组本身是引用数据类型,而数组中的元素既可以是基本数据类型,也可以是引用数据类型
- 5.创建数组对象会在内存中开辟一整块连续的空间,而数组名引用的是这块连续空间的
首地址
- 6.如果创建数组时没有显性赋值,数组会根据相应的元素类型自动赋予默认值
1.一维数组
1.声明和初始化
//1.声明 数据类型[] 数组名; //2.1静态初始化:定义数组的同时为数组的每个元素赋值 数据类型[] 数组名 = new 数据类型[]{元素,元素。。。}; 数据类型[] 数组名 = {元素,元素。。。}; //2.1动态初始化:定义数组时只指定数组的长度,由系统赋初值(或显式赋值) 数据类型[] 数组名 = new 数据类型[数组长度];
- 说明:
- 1.不管是动态初始化还是静态初始化,数组一旦完成初始化,其长度就确定了,因为内存中要分配指定的长度的内存空间
- 2.数组的长度必须为整数
- 3.通过动态初始化的方式创建数组时必须指定长度,否则无法分配空间,而通过静态初始化的方式创建数组可以不指明长度,长度由
{}
的元素个数决定,如果没有元素,则为空数组,即长度为0
- 4.注意当使用
数据类型[] 数组名 = new 数据类型[]{元素,元素。。。};
不能再指定数组的长度,否则会报错,因为编译器会认为数组指定的元素个数与实际存储的元素个数有可能不一致,存在一定的安全隐患
2.访问数组元素
int[] arr = new int[]{5,6,7}; System.out.println(arr[2]);
- 说明:
- 1.可以通过数组下标的方式访问数组中的元素
- 2.数组的下标都有一个范围,即
0~数组长度-1
;访问数组的元素时,下标不能超出这个范围,否则会报数组下标越界异常
3.数组的长度
属性:length 数组名.length
4.数组遍历
int[] arr = new int[]{5,6,7}; for(int i=0; i<arr.length; i++){ System.out.println(arr[i]); }
- 说明:
- 1.依次获取数组中每个元素的过程称为数组遍历
5.默认初始化值
- 1.整型:默认初始化值
0
- 2.浮点型:默认初始化值
0.0
- 3.
char
: 默认初始化值\u0000
,而非0
, 效果像空格,但并不是空格,空格也有对应的asill
值- 4.
boolean
:默认初始化值是false
- 5.引用类型:默认初始值是
null
6.数组的内存解析
- 1.
栈
中存放数组引用
,该引用并非实际的数据,而是保存指向数组首元素的地址
- 2.
堆
中存放的才是数组中的数据- 3.引用类型之间,相互赋值传递的是堆地址
- 4.堆地址寻址公式:
数组首元素地址+下标*数组数据类型对应的字节长度
7.数组扩容
- 1.创建一个长度更大的数组,通常扩大两倍
- 2.把原数组中的数据复制到新数组中(循环)
- 3.把原数组的堆地址指向新数组的堆地址
- 4.扩容方式(三种)
//1.for循环扩容 int[] arr = {1,2,3,4,5}; int[] arr2 = new int[arr.length*2]; for(int i=0; i<arr.length; i++){ arr2[i] = arr[i]; } arr = arr2; System.out.println(Arrays.toString(arr)); //2.利用System.arraycopy本地方法进行扩容 int[] arr = {1,2,3,4,5}; int[] arr2 = new int[arr.length*2]; System.arraycopy(arr,0,arr2,0,arr.length); arr = arr2; System.out.println(Arrays.toString(arr)); //3.利用Arrays.copyOf扩容,其本质也是利用System.arraycopy本地方法进行扩容 int[] arr = {1,2,3,4,5}; arr = Arrays.copyOf(arr,arr.length*2); System.out.println(Arrays.toString(arr));
8.可变长参数数组
- 1.方法中长度可变的参数,实际长度由调用者传入的实参个数决定
- 2.一个方法中只能定义一个
0-1
个可变长参数,如果多于一个,则无法区分两个可变长参数的边界值- 3.如果参数列表中存在多个参数,可变长参数必须写在
最后一位
访问修饰符 返回值类型 函数名(数据类型...参数名){ 方法体 }
2.二维数组
1.声明和初始化
- 1.一个一维数组(高纬度)中的元素还是一个一维数组(低维度)
- 2.创建方式:
//1.先声明再指明数组长度 数据类型[][] 数组名; 数组名 = new 数据类型[高维度数组长度][低纬度数组长度]; //2.声明的同时直接指明数组长度 数据类型[][] 数组名 = new 数据类型[高维度数组长度][低纬度数组长度] //3.创建的同时直接赋值 数据类型[][] 数组名 = {{值1,值2},{值3,值4},{值5,值6}...}; //4.不规则创建 数据类型[][] 数组名 = new 数据类型[高纬度数组长度][]; 数组名[高纬度数组下标] = new 数据类型[低纬度数组长度];
- 说明:
- 1.第一种/第二种方式元素个数=高纬度数组长度
*
低纬度数组长度- 2.第三种方式外层
{}
代表高纬度数组,内层{}
代表低纬度数组;高维数组长度由内层{}
个数决定,低维数组长度由内层{}
中的元素个数决定- 3.第四种创建方式元素个数=所有低纬度数组长度之和
2.二维数组的调用
- 1.数组名[高维度下标]:返回高维下标对应的低维度数组
- 2.数组名[高纬度下标][低纬度下标]:返回高纬度下标对应的低纬度数组中下标所对应的元素
3.二维数组的遍历
- 1.结合双层嵌套循环
- 2.外层循环循环高纬度数组
- 3.内层循环循环低纬度数组
- 4.在内层循环操作数组元素
- 5.通过
数组名.length
获取高纬度数组长度- 6.通过
数组名[高纬度数组下标].length
获取低纬度数组长度for(int i=0; i<数组名.length; i++){ for(int j=0; j<数组名[i].length; j++){ 循环体; } }
3.数组的应用场景
- 1.数据存储:用于存储一组相同类型的数据
- 2.缓存实现:数组可以用于实现缓存(如
LRU
缓存)或哈希表- 3.图像处理:二维数组可以表示图像的像素矩阵
- 4.算法实现:数组是许多算法的基础数据结构
4.数组和集合区别以及转换
- 1.数组转集合:
- 1.将数组转换为集合时,通常使用
Arrays.asList()
方法- 2.
Arrays.asList()
返回的列表是基于原始数组的视图,修改列表会影响原始数组,因此不能添加或删除元素(返回的是用final
修饰的常量数组)- 3.如果需要可变的列表可使用
new ArrayList<>(Arrays.asList(array))
- 4.通过
Stream API
可以将数组转换为集合,这种方式会创建一个新的集合,与原始数组无关
- 5.手动遍历(自定义逻辑)
- 2.集合转数组
- 1.使用
Collection
接口提供的toArray()
方法,用于将集合转换为数组,推荐使用toArray(new T[0])
(本质调用的是System.arraycopy
方法)- 2.使用
Stream API
将集合转换为数组,适用于需要处理或过滤集合元素的场景
- 3.手动遍历集合并添加到数组