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
,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.决定内容的
访问权限
- 2.只有
public
和default
可以修饰类
- 3.以上
4个访问修饰符
都可以修饰属性
,方法
,构造方法
- 4.以上
4个访问修饰符
都不能修饰局部变量
,只有final
可以修饰局部变量- 5.
jdk9
之前default
不允许显式声明,之后允许在接口
中显式声明default
1.public
- 1.所有类可见
- 2.可修饰
- 1.类(
外部类
,成员内部类
,静态内部类
,局部内部类
,抽象类
)- 2.接口
- 3.方法(
静态方法
,普通方法
)- 4.变量(
静态变量,全局变量
)- 5.常量(
常量
,静态常量
)- 3.不可修饰
- 1.类(
匿名内部类
)- 2.方法(
接口默认方法
)- 3.变量(
局部变量
)
2.protected
- 1.同包且所有子类(包括同包和不同包)可见
- 2.可修饰(变量,方法)
- 3.不可修饰类(外部类,内部类除外)和接口及接口的成员变量和成员方法
3.default
- 1.默认,同包可访问
- 2.可修饰(类,接口,变量,方法)
4.private
- 1.同类可访问
- 2.可修饰变量,常量,方法
- 3.不可修饰类(外部类)和接口及接口中的变量和方法
2.非访问控制修饰符
1.static
- 1.
修饰属性
- 1.类变量,该变量不独属于某个对象,被整个类所有的对象所共有,堆空间只有一个该变量(而不是每个对象都有一个该变量)
- 2.静态属性可以直接通过
类名.属性名
的方式访问- 3.类名.属性名会直接去
方法区
查找静态变量- 4.
static
不能修饰局部变量
,只能修饰成员变量
- 5.静态成员变量虽然独立于对象,但所有的静态方法和静态变量都可通过对象访问
- 2.
修饰方法
- 1.静态方法,静态方法中无法访问非静态的内容
- 2.通过
类名.方法名()
的方式访问- 3.静态方法仍然可以通过
对象名.方法名()
的方式访问- 4.
static
无法修饰构造方法- 5.静态方法内部无法使用
this
和super
关键字- 6.静态内容可以直接从方法区中访问可能会导致
类的实例对象还未创建
也不会存在this
和super
,有可能会产生冲突- 3.修饰代码块
- 1.静态代码块,随着类加载而加载,一般为静态变量赋初始值,可以提升程序性能
- 3.因此通常会将一些只需要进行一次的初始化操作都放在
static
代码块中- 4.静态初始化块可置于类中的任何地方,类中可有多个静态初始化块
- 5.类初次被加载时,会按照静态初始化块的顺序来执行每个块,并且只会执行一次
面试题1
public class Test extends Base{ static{ System.out.println("test static"); } public Test(){ System.out.println("test constructor"); } public static void main(String[] args) { new Test(); } } class Base{ static{ System.out.println("base static"); } public Base(){ System.out.println("base constructor"); } } // 执行结果 1.base static 2.test static 3.base constructor 4.test constructor
- 1.执行流程说明
- 1.首先找到程序入口
main
方法,但执行main
方法之前需要先加载Test
类- 2.加载
Test
类时发现Test
类继承Base
类,因此先加载Base
类- 3.加载
Base
类时发现Base
类存在static
块,因此先执行static
块输出base static
- 4.
Base
类加载完后然后加载Test
类发现Test
类也存在static
块,因此执行Test
类中的static
块输出test static
- 5.
Base
类和Test
类加载完成后,接着执行main
方法,调用子类构造器之前会先调用父类构造器,调用Test
的父类构造器Base()
输出base constructor
,最后再调用子类构造器输出test constructor
面试题2
public class Test { Person person = new Person("Test"); static{ System.out.println("test static"); } public Test() { System.out.println("test constructor"); } public static void main(String[] args) { new MyClass(); } } class Person{ static{ System.out.println("person static"); } public Person(String str) { System.out.println("person "+str); } } class MyClass extends Test { Person person = new Person("MyClass"); static{ System.out.println("myclass static"); } public MyClass() { System.out.println("myclass constructor"); } } // 执行结果 1.test static 2.myclass static 3.person static 4.person Test 5.test constructor 6.person MyClass 7.myclass constructor
- 1.执行流程说明
- 1.首先找到程序入口
main
方法,但执行main
方法之前需要先加载Test
类- 2.加载
Test
类时发现Test
类存在static
块,因此先执行static
块输出test static
- 3.然后执行
new MyClass()
,执行此代码前先加载MyClass
类,发现MyClass
类继承Test
类,而是要先加载Test
类,Test
类已加载过- 4.因此直接加载
MyClass
类,发现MyClass
类有static
块,于是先执行static
块,输出myclass static
- 5.接着调用
MyClass
类的构造器生成对象,生成对象前需先初始化父类Test
的成员变量,执行Person person = new Person("Test")
代码- 6.此时
Person
类没有加载,因此需要先加载Person
类,加载时发现Person
类有static
块,所有先执行static
块,输出person static
- 7.然后执行
Person
构造器输出person Test
- 8.接着调用父类
Test
构造器完成父类Test
的初始化,输出test constructor
- 9.然后再初始化
MyClass
类成员变量,执行Person
构造器输出person MyClass
- 10.最后调用
MyClass
类构造器完成MyClass
类的初始化,输出myclass constructor
面试题3
public class Test { static{ System.out.println("test static 1"); } public static void main(String[] args) { } static{ System.out.println("test static 2"); } } test static 1 test static 2
- 1.
static
块可以出现类中的任何地方(只要不是方法内部,记住任何方法内部都不行),并且按照static
块的出现顺序执行
面试题4
public class TestStatic { static int j=1; public static void main(String args[]){ int f2=f2(1); System.out.println("f2的最终值="+f2); } static int f2(int n) { n += j; j++; System.out.println("j="+j); if (n >= 5) return n; return n + f2(n); } } // 执行结果 1.j=2 2.j=3 3.j=4 4.f2的最终值=13
面试题5
成员变量和静态变量的区别
- 1.生命周期不同
- 1.成员变量随着对象的创建而存在,随着对象的回收而销毁
- 2.静态变量在类加载的时候创建,随着类的消失而消失
- 2.调用方式不同
- 1.成员变量只能被对象调用
- 2.静态变量能被对象调用,也能被类名调用
- 3.别名不一样
- 1.成员变量叫实例变量
- 2.静态变量叫类变量
- 4.数据存储不一样
- 1.成员变量储存在堆内存的对象中,也叫对象的特有数据
- 2.静态变量储存在方法区的静态区,所以也叫对象的共享数据
2.final
- 1.修饰属性
- 1.常量,值不可改变
- 2.常量不存在默认值,一般情况下在创建时必须进行初始化,只有下面这种情况例外,要求该变量必须在构造方法中被初始化,并且不能有空参数的构造方法(这样可让每个实例都有一个不同的变量,并且这个变量在每个实例中只会被初始化一次,于是这个变量在单个实例里就是常量)
- 3.
final
可以修饰局部变量
(局部变量只能用final
修饰),变成常量值不可修改- 4.
final
可以修饰引用,表现为堆地址不可修改,但对象内容可以修改- 5.
final
通常和static
一起使用创建类常量,类常量必须在声明时初始化,类变量和常量可在构造函数中初始化
- 6.
final
和abstract
这两个关键字是互斥的,不能同时出现- 7.接口中声明的所有变量本身是
final
的- 2.修饰方法
- 1.将方法变成最终的方法,即可以被子类继承,但不可以被子类重写,防止该方法的内容被修改
- 2.
final
方法比非final
快一些,final
方法比非final
方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定,final
方法调用时使用的是invokespecial
指令- 3.修饰类
- 1.该类不可以被继承
- 2.不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等
- 3.匿名类中所有变量都必须是final变量
面试题5
- 1.
final
关键字不同于finally
关键字,后者用于异常处理- 2.
final
关键字容易与finalize()
方法搞混,后者是在Object
类中定义的方法,是在垃圾回收之前被JVM
调用的方法- 3.
final
方法在编译阶段绑定,称为静态绑定(static binding)- 4.将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
- 3.要使用final 方法,可能是出于对两方面理由的考虑:
- 第一:为方法“上锁”,防止任何继承类改变它的本来含义。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。
第二:程序执行的效率。将一个方法设成 final 后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里。只要编译器发现一个final 方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。- 我们需要注意到:若方法体积太大,那么程序也会变得雍肿,可能受到到不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。Java 编译器能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个final 方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。
- final关键字提高了性能。JVM和Java应用都会缓存final变量。
- final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
- 使用final关键字,JVM会对方法、变量及类进行优化。
面试题5
public class Main { public static void main(String[] args) { String a = "xiaomeng2"; final String b = "xiaomeng"; String d = "xiaomeng"; String c = b + 2; String e = d + 2; System.out.println((a == c)); System.out.println((a == e)); } } 这段代码的输出结果是什么呢? 答案是: true 和 false 原因: 变量a指的是字符串常量池中的 xiaomeng2; 变量 b 是 final 修饰的,变量 b 的值在编译时候就已经确定了它的确定值,换句话说就是提前知道了变量 b 的内容到底是个啥,相当于一个编译期常量; 变量 c 是 b + 2得到的,由于 b 是一个常量,所以在使用 b 的时候直接相当于使用 b 的原始值(xiaomeng)来进行计算,所以 c 生成的也是一个常量,a 是常量,c 也是常量,都是 xiaomeng2 而 Java 中常量池中只生成唯一的一个 xiaomeng2 字符串,所以 a 和 c 是相等的! d 是指向常量池中 xiaomeng,但由于 d 不是 final 修饰,也就是说在使用 d 的时候不会提前知道 d 的值是什么,所以在计算 e 的时候就不一样了,e的话由于使用的是 d 的引用计算,变量d的访问却需要在运行时通过链接来进行,所以这种计算会在堆上生成 xiaomeng2 ,所以最终 e 指向的是堆上的 xiaomeng2 , 所以 a 和 e 不相等。 总得来说就是:a、c是常量池的xiaomeng2,e是堆上的xiaomeng2
3.native
- 1.用来声明
本地方法
,该方法的实现由非Java
语言实现- 2.一般用于
java
与外环境交互或与操作系统交互- 3.
native
可以与所有其它的java
标识符连用,但是abstract
除外, 因为native
暗示这些方法是有代码实现的,只不过这些实现体是非java
的
4.abstract
- 1.用来创建抽象类和抽象方法
/** * 车子类 */ 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("跑"); } }
- 2.抽象类可以包含抽象方法和非抽象方法
- 3.抽象类不能实例化对象,但是抽象类有构造函数(因为子类创建对象时会先初始化父类的属性和方法),声明抽象类的唯一目的是为了将来对该类进行继承扩充
- 4.一个类不能同时被
abstract
和final
修饰,如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误- 5.抽象方法是一种没有任何实现的方法,该方法的具体实现由子类提供
- 6.抽象方法不能用
private
修饰,因为抽象方法必须被子类实现(覆写),而子类来不能访问private
权限,所以就会产生矛盾- 7.抽象方法不能被声明成
final
(抽象方法需要被继承重写)和static
(抽象方法没有方法体,只有被继承重写才有意义)- 8.任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类
- 9.如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法
4.synchronized
- 1.
synchronized
是Java
中的关键字,用于实现多线程同步,当一个线程进入synchronized
块或方法时会获得独占锁,这会阻止其他线程同时进入相同的synchronized
块或方法,从而确保了共享资源的互斥访问- 2.
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.该方式允许更细粒度的控制,可选择对某个特定对象进行同步,而不是整个方法或类
- 3.
synchronized
底层原理1.synchronized 通过监视器锁来实现线程同步 2.每个 Java 对象都有一个监视器锁 3.线程在获取了对象的监视器锁后,可以执行被修饰的代码 4.线程在释放了对象的监视器锁后,其他线程可以尝试获取监视器锁
- 1.
synchronized
底层是基于JVM
的指令和对象的监视器(monitor
)来实现的- 2.
synchronized
可以修饰方法或者代码块,用来保证线程的同步和安全- 3.当一个线程要执行一个被
synchronized
修饰的方法或代码块时,需要先获取该方法或代码块所属对象的监视器- 4.如果获取成功则该线程可执行同步代码,并且监视器的计数器加一
- 5.如果获取失败则该线程就会阻塞,直到监视器被释放
- 6.当一个线程执行完同步代码后会释放监视器,并且监视器的计数器减一
- 7.如果计数器为零则说明没有线程持有该监视器,其他线程可竞争获取该监视器
- 8.
synchronized
修饰方法时,字节码层面会有一个ACC_SYNCHRONIZED
标志,用来表示该方法是同步的- 9.
synchronized
修饰代码块时,字节码层面会有monitorenter
和monitorexit
两个指令,分别用来进入和退出监视器- 4.JDK底层优化
- 1.
synchronized
在JDK1.6
之后进行了优化,引入了偏向锁,轻量级锁,自旋锁等概念,用来提高性能和减少阻塞开销- 2.偏向锁:会将锁定的对象与线程相关联,当一个线程获得锁时,会标记对象为已偏向该线程,以后再次进入同步块时,不需要竞争锁而是直接获得,可减少无竞争情况下的锁开销
- 3.轻量级锁:低竞争情况下,
JDK
使用轻量级锁来减小锁开销,轻量级锁采用自旋方式来等待锁的释放,而不是进入阻塞状态- 4.自旋锁:当轻量级锁尝试获取锁失败时,
JDK
可选择使用自旋锁,自旋锁不会使线程进入阻塞状态,而是一直尝试获取锁,通常在短时间内完成- 5.适应性自旋:
JDK
中的锁可根据历史性能数据来调整自旋等待的次数,以达到更好的性能
5.面试题
- 1.synchronized 是什么?它的作用是什么?
- 1.synchronized 是Java中的关键字,用于实现多线程同步,它的主要作用是确保在同一时刻只有一个线程可以访问被synchronized修饰的代码块或方法,以避免多线程之间的竞态条件和数据不一致问题
- 2.synchronized 有哪些用法?
- 同步非静态实例方法:synchronized 修饰非静态方法,锁定的是实例对象。
- 同步静态方法:synchronized 修饰静态方法,锁定的是类对象。
- 同步代码块:synchronized 修饰代码块,可以手动指定锁对象,灵活控制同步范围
- 3.什么是锁对象?
- 锁对象是在synchronized代码块或方法中用于实现同步的对象。对于同步实例方法,锁对象是实例对象本身(this),而对于同步静态方法,锁对象是类的Class对象。对于同步代码块,你可以手动指定锁对象
- 4.什么是重入锁?
- 重入锁是指当一个线程持有锁时,它可以多次进入被锁定的代码块或方法而不会被阻塞。Java中的synchronized是可重入锁的一个例子,这意味着同一线程可以多次获取同一个锁,而不会造成死锁
- 5.什么是偏向锁?
- 偏向锁是一种优化,用于减少锁竞争的开销。它允许线程在没有竞争的情况下快速获得锁,而不必像重量级锁那样进入阻塞状态。只有在多线程竞争锁时,偏向锁才会升级为重量级锁。
- 6.什么是自旋锁?
- 自旋锁是一种轻量级锁,它在尝试获取锁时不会立即阻塞线程,而是会在循环中不断尝试获取锁。这可以减少线程阻塞和恢复的开销,但如果锁竞争激烈,自旋锁可能会浪费大量CPU时间
- 7.什么是锁粗化?
- 锁粗化是一种优化,它将多个连续的synchronized块合并成一个更大的同步块,减少了锁操作的开销。这可以避免频繁地获取和释放锁,提高性能
- 8.什么是锁消除?
- 锁消除是一种优化,它通过静态分析来检测某些锁是不必要的,然后将其删除,以提高程序的性能。这通常发生在编译器级别
- 9.synchronized 与 volatile 有什么区别?
- synchronized 用于实现多线程同步,确保线程之间的可见性和原子性。volatile 用于确保变量的可见性,但不能实现原子性。synchronized 具有更广泛的用途,而 volatile 主要用于标记变量以确保可见性。
5.transient
- 序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型
6.volatile
- volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
一个 volatile 对象引用可能是 null
通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法。 如果 第一行 中缓冲区的 active 值被使用,那么在 第二行 的 active 值为 false 时循环不会停止。
但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止
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.常量一般采用大写字母,不同单词间用
_
隔开
1.静态常量
- 1.一般推荐使用
static final
修饰常量,如果不加则每次new
一个对象都会为该常量重新分配一次内存(创建类的多个对象时,用static
修饰的常量在内存中只有一份拷贝,不用static
修饰则可能有多份拷贝)
- 1.不管静态常量还是普通常量只要是基本类型就不能在初始化后重新修改其值
- 2.对象类型普通常量、静态常量可以在初始化后修改其对象状态,但不可以改变其引用
在Java中,静态常量是指在类中使用关键字"static"和"final"修饰的常量。静态常量在类加载时就会被初始化,并且在整个程序运行期间保持不变。
静态常量的定义语法如下:
public class MyClass {
public static final int MY_CONSTANT = 10;
在上面的例子中,MY_CONSTANT被定义为一个静态常量,类型为int,值为10。注意,静态常量的命名通常使用全大写字母,多个单词之间用下划线分隔。
静态常量的特点:
-
不可修改:一旦静态常量被赋值,就无法再修改其值。任何试图修改静态常量的操作都会导致编译错误。
-
全局可访问:静态常量可以被类的所有实例和其他类直接访问。可以通过类名加点操作符来访问静态常量,例如MyClass.MY_CONSTANT。
-
内存效率高:静态常量在类加载时就会被初始化,存储在方法区中的常量池中。不需要每次创建对象时都重新分配内存。
静态常量的用途:
-
定义常用的常量值:例如数学中的π(Math.PI)或者颜色的RGB值。
-
作为方法的参数:静态常量可以作为方法的参数,用于传递固定的值。
-
用于条件判断:静态常量可以用于条件判断,提高代码的可读性和可维护性。
静态常量是Java中一种非常有用的特性,它可以在类加载时被初始化,并且在整个程序运行期间保持不变。通过使用静态常量,可以提高代码的可读性和可维护性,并且节省内存空间。
2.常量类型
- 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
,表示对象的引用为空
3.字符集
- 1.字符(
Character
):各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等- 2.字符集(
Character set
):多个字符的集合,字符集是一张码表,它规定了文字与数字的一一对应关系,与计算的内部表示没有必然的联系,字符集种类较多,每个字符集包含的字符个数不同- 3.常见字符集名称
- 1.
ASCII
字符集- 2.
GB2312
字符集- 3.
GB18030
字符集- 4.
Unicode
字符集等
4.进制转换
- 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.变量值:是变量(内存单元)中存储的数据
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.基本数据类型的十进制数值范围保存在其对应包装类中
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 如果boolean是单独使用:boolean占4个字节;如果boolean是以“boolean数组”的形式使用:boolean占1个字节 true,false 语法说明:
- 1.boolean(布尔)类型有两个值:false和true,用来判定逻辑条件;这两个值不能与整数进行相互转换
4.变量的作用域
- 1.Java中的变量可以分为类变量,全局变量(成员变量)和局部变量
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
类引用成员变量
- 注意:基本数据类型的变量传递的是值,而引用数据类型的变量传递的是引用所指的地址(对值的修改不会影响本身的数据,而对堆地址中的数据进行修改,指向该地址的引用的数据值都会改变)
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.线程封闭:方法中的局部变量因为不会和其他线程共享所以不会存在并发问题,这种解决问题的技术也叫做线程封闭(官方解释:仅在单线程内访问数据,由于不存在共享,所以即使不设置同步也不会出现并发问题)
5.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); } }
- 注意:
- 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 ^ 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
7.扩展:原码,反码,补码
- 原码: 一个整数按照绝对值大小转换成的二进制数,称为原码
- 反码: 将二进制数按位取反,所得的新二进制数称为原二进制数的反码
- 补码: 反码加
1
称为补码- 计算机中负数以原码的补码形式表达
11.java中的选择结构语句
1.if语句
- 基本语法:
if(判断语句){ //代码块 }
2.if-else语句
- 基本语法:
if(判断语句){ //代码块 }else{ //代码块 }
3.多重if-else语句
- 基本语法:
if(判断语句1){ //执行语句1 }else if(判断语句2){ //执行语句2 } ... 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: 执行语句n+1; break; }
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!!!"); break; } } }
- 语法说明:
- 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结构的都可以转换为if-else
- 2.但是反之不一定成立,如果可以使用三元运算符或者switch-case则使用他两,因为效率更高
12 java中的循环语句
1.定义:
- 在某些条件满足的情况下,反复执行特定代码的功能
2.循环语句分类
- while循环语句
- do…while循环语句
- for循环语句
3.循环语句的四个组成部分
- 初始化部分
- 循环条件部分,必须是boolean类型
- 循环体部分
- 迭代部分
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.使用范围:
- switch-case
- 循环结构
- 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.循环中的作用:
- 终止本次循环,执行下一次循环(嵌套循环语句中,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.可以直接通过下标(索引)的方式调用指定位置的元素
- 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: 默认初始化值0或’\u0000’,而非’0’, 效果像空格,但并不是空格,空格也有对应的asill值
- 4.boolean:默认初始化值是 false
- 5.引用类型:默认初始值是
null
6.数组的内存解析
- 1.栈中存放数组引用,该引用并非实际的数据,而是保存指向数组首元素的地址
- 2.堆中存放的才是数组中的数据
- 3.引用类型之间,相互赋值传递的是堆地址
- 4.堆地址寻址公式:数组首元素地址+下标*数组数据类型对应的字节长度
7.数组扩容
- 1.创建一个长度更大的数组,通常扩大两倍
- 2.把原数组中的数据复制到新数组中(循环)
- 3.把原数组的堆地址指向新数组的堆地址
- 扩容方式(三种)
//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扩容 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++){ 循环体; } }