文章目录
- 一、用于定义访问权限修饰符的关键字: private ,default ,protected,public
- 二、用于定义类,函数,变量修饰符的关键字:abstract ,final,static,synchronized
- 三、用于定义类与类之间的关键字:extends,implements
- 四、用于定义建立实例及引用实例,判断实例的关键字:new,this,super, instanceof
- 五、用于处理异常的关键字:try,catch,finally,throw,throws
- 六、用于包的关键字:package,import
- 七、其他修饰符关键字:native, strictfp,transient,volatile,assert
一、用于定义访问权限修饰符的关键字: private ,default ,protected,public
1.private
(1)在同一类内可见,可以修饰变量和方法,不能修饰外部类。
(2)最严格的访问级别:被声明为private的方法、变量和构造方法只能被所属类访问,并且类和接口不能被声明为private。
(3)声明为私有类型的变量只能通过类中公共的getter方法被外部类访问。
2.default
这是默认情况下的访问修饰符,可以修饰变量和方法,对同一个包里的类是可见的;还可以修饰类和接口,在同一包内可见。
3.protected
- 对同一包内的类和所有子类可见,可以修饰变量、方法,不能修饰外部类。
- 如果子类和基类在同一个包中:被声明为protected的变量、方法和构造器能被同一个包中的任何其他类访问。
- 如果子类和父类不在同一个包中:那么在子类中,子类实例可以访问其从父类继承来的protected方法,而不能访问父类实例的protected方法。(也就是表中的说明那里)
(1)protected可以修饰数据成员,构造方法,方法成员,不能修饰类(内部类除外)。
(2)接口以及接口的成员变量和成员方法不能声明为protected。
(3)如果我们只想让该方法对其所在类的子类可见,则将该方法声明为protected。
4.public
(1)被public修饰的类、方法、构造方法和接口能被任何其他类访问。
(2)如果几个相互访问的public类分布在不同的包中,则需要导入public类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类所继承。
二、用于定义类,函数,变量修饰符的关键字:abstract ,final,static,synchronized
1.abstrcat
- abstract可以修饰类的时候,表明该类是抽象类。
(1)抽象类不能用来实例化对象,这样声明的目的是将来对该类进行补充。
(2)一个类不能同时被abstract和final修饰,如果一个类包含了抽象方法,那么该类一定要声明为抽象类,否则会编译错误。
(3)抽象类可以包含不抽象的方法。 - abstract可以修饰方法的时候,表明该方法是抽象方法。
(1)抽象方法是一种没有任何实现的方法,该方法的据图实现由子类提供。
(2)抽象方法不能被声明成final和static。
(3)任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
(4)抽象方法的声明由分号结尾,例如public abstract sample();
2.final
- final修饰变量
(1)final修饰变量后,还变量不能被重新赋值,被final修饰的变量一定要显示指定初始值。
当然也可以这样:
(2)final修饰符通常和static修饰符一起使用来创建类的常量:static写在前边
- final修饰方法
(1)父类的final方法可以被子类继承,但是不能被子类重写。
(2)声明 final 方法的主要目的是防止该方法的内容被修改。
- final修饰类
final修饰类的时候,该类不能被继承,没有类能够继承final类的任何特性。
3.static
- static可以修饰变量、方法,代码块。不管修饰什么都是将它和它所属的类的对象解绑,成为类的一部分。
- static修饰变量
(1)无论一个类实例化多少个对象,它的静态变量只有一份拷贝,静态变量也叫类变量。
(2)被static修饰前,成员变量属于类实例存放在堆中。被static修饰后,成员变量属于类,存放的位置发生变化,放在方法区中。 - static修饰方法
静态方法不能使用类的非静态变量。静态方法是参数列表中得到数据,然后计算这些数据。
4.synchronized
-
对于synchronized声明的方法在同一时间只能被一个线程访问,是一种同步锁。
-
它可以修饰代码块,方法,静态方法,类。
-
修饰代码块
(1)被修饰的代码块称为同步代码块,范围是{括起来的代码},作用的对象是调用这个代码块的对象(它会锁定当前的对象)。
(2)一个线程访问一个对象中的synchronized修饰的同步代码块时,其他访问该对象的线程将会被阻塞;访问该对象的非同步代码块不受影响。
-
修饰方法
(1)当修饰方法时,它的作用范围会改变到整个方法。
(2)synchronized修饰方法的时候并不能被继承:
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
(3)在定义接口方法的时候不能使用synchronized关键字。
(4)构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
(5)我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象,相当于是所有对象用的是同一把锁。 -
修饰类
(1)用法如下:
synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。 -
总结
A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
三、用于定义类与类之间的关键字:extends,implements
1.extends
- 在Java中通过extends继承一个已有的类,被继承的类称为父类(超类、基类),新的类称为子类(派生类),在Java中不允许多继承,但是可以多层继承。
(1)子类继承父类时,可以重写(也称覆盖)类的方法,也可以不重写。重写后就实现了新的功能,不重写就实现父类的功能。
(2)方法的重写发生在子类与父类之间。另外,可用super提供对父类的访问实现父类的功能。 - 不懂重载和重写的区别的老铁点击这里 传送门
2.implements
- implements是实现接口的关键字,用法如下:
- 实现接口的注意点
(1)实现一个接口就要实现该接口的所有方法(如上图)。
(2)接口中的方法都是抽象的。
(3)多个无关的类可以实现同一个接口,一个类可以实现多个无关的接口。
(4)JAVA中不支持多重继承,但是可以用接口来实现,这样就用到了implements,继承只能继承一个类,但implements可以实现多个接口,用逗号分开就行了,比如 class A extends B implements C,D,E {} 。
四、用于定义建立实例及引用实例,判断实例的关键字:new,this,super, instanceof
1.new:实例化对象
- Java中创建一个对象,例如 Person person = new Person(“张三”, 20); 包括几部分
(1) 声明(Declaration):将变量名称与对象类型关联的变量声明。
Person person 就是告诉编译器你将使用person引用一个Person类型的对象。
其初始值将待定,直到有一个对象真正被创造和分配给它。
这里只是简单地声明一个引用变量而并没有创建一个对象。
(2) 实例化(Instantiating):new关键字是一个java运算符,它用来创建对象。
new运算符实例化一个类对象,通过给这个对象分配内存并返回一个指向该内存的引用。new运算符也调用了对象的构造函数。
由new运算符返回的引用可以不需要被赋值给变量。它也可以直接使用在一个表达式中。例如: int age = new Person().age;
(3)初始化(Initialization):new运算符,随后调用构造函数,初始化新创建的对象。
每个构造函数都允许你为矩形的起始值、宽度和高度提供初始值,同时使用原始类型和引用类型。
如果一个类有多个构造函数,它们必须有不同的签名。
java编译器区分构造函数基于参数的数量和类型。
总结:
-
Java关键字new是一个运算符。与 +、-、*、/ 等运算符具有相同或类似的优先级。
-
创建一个Java对象需要三部:声明引用变量、实例化、初始化对象实例。
-
实例化:就是“创建一个Java对象”-----分配内存并返回指向该内存的引用。
-
初始化:就是调用构造方法,对类的实例数据赋初值。
-
Object obj = new Object();
那“Objectobj”这部分的语义将会反映到Java栈的本地变量表中,作为一个reference类型数据出现。而“new Object()”这部分的语义将会反映到Java堆中,形成一块存储了Object类型所有实例数据值(InstanceData,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
2.this
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200318154737829.png)
-
this调用本类属性时:当参数与类中属性同名时,类中属性⽆法被正确赋值。此时我们加上this关键字便可以正确给对象属性赋值。只要在类中方法访问类中属性时,一定要加this关键字。
-
this调用本类构造方法时:
1.this调用构造方法的语句必须在构造方法的首行。 2.使用this调用构造方法时,请留有出口。
-
this表示当前对象时:只要对象调用了本类中的方法,这个this就表示当前执行的对象。
3.super:对某个类的父类的引用
-
访问父类中被覆盖的方法或隐藏的成员变量
super.<成员方法名> super.<成员变量名>
super特指访问父类的成员,使用super首先到直接父类查找匹配成员,如果未找到,再逐层向上到祖先类查找。
-
调用父类的构造方法
super(参数列表) 其中调用参数列表必须和父类的某个构造函数方法的参数列表完全匹配。
子类与其直接父类之间的构造方法存在约束关系,有以下几条重要原则:
(1)按继承关系,构造方法是从顶向下进行调用的。
(2)如果子类没有构造方法,则它默认调用父类无参的构造方法,如果父类中没有无参数的构造方法,则将产生错误。
(3)如果子类有构造方法,那么创建子类的对象时,先执行父类的构造方法,再执行子类的构造方法。
(4)如果子类有构造方法,但子类的构造方法中没有super关键字,则系统默认执行该构造方法时会产生super()代码,即该构造方法会调用父类无参数的构造方法。
(5)对于父类中包含有参数的构造方法,子类可以通过在自己的构造方法中使用super关键字来引用,而且必须是子类构造函数方法中的第一条语句。
(6)Java语言中规定当一个类中含有一个或多个有参构造方法,系统不提供默认的构造方法(即不含参数的构造方法),所以当父类中定义了多个有参数构造方法时,应考虑写一个无参数的构造方法,以防子类省略super关键字时出现错误。
4.instanceof
- instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:boolean result = obj instanceof Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
(1)obj必须为引用类型,不能是基本类型,它只能用作对象的判断
(2)obj为null,将会返回false
(3)obj为class类的实例对象时,返回true
(4)obj为class类的接口的实现类时
(5)obj为class类的直接或间接子类时返回true
(6)问题:
我们来看一段instanceof原理的伪代码:
答案就是:
(1)表达式 obj instanceof T,instanceof 运算符的 obj 操作数的类型必须是引用类型或空类型; 否则,会发生编译时错误。
(2)如果 obj 强制转换为 T 时发生编译错误,则关系表达式的 instanceof 同样会产生编译时错误。 在这种情况下,表达式实例的结果永远为false。
(3)在运行时,如果 T 的值不为null,并且 obj 可以转换为 T 而不引发ClassCastException,则instanceof运算符的结果为true。 否则结果是错误的
总结:简单来说就是:如果 obj 不为 null 并且 (T) obj 不抛 ClassCastException 异常则该表达式值为 true ,否则值为 false 。
五、用于处理异常的关键字:try,catch,finally,throw,throws
1.try,catch,finally放在一起讲解
- 解读
(1)try里边放我们要执行的代码,catch里放如果出现了对应的异常怎么怎么办,最后不管是扫描出了别的异常或者是没有扫描出异常都交给finally解决。
(2)finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally 代码块中的代码总会被执行。
(3)在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。finally 代码块出现在 catch 代码块最后。
- 注意下面事项:
(1)catch 不能独立于 try 存在。
(2)在 try/catch 后面添加 finally 块并非强制性要求的。
(3)try 代码后不能既没 catch 块也没 finally 块。
(4)try, catch, finally 块之间不能添加任何代码。
2.throw,throws放在一起讲解
- 解读
(1)如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部,一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。
(2)也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。
(3)throw关键字通常用在方法体中,并且抛出一个异常对象。程序在执行到throw语句时立即停止,它后面的语句都不执行。通过throw抛出异常后,如果想在上一级代码中来捕获并处理异常,则需要在抛出异常的方法中使用throws关键字在方法声明中指明要跑出的异常;如果要捕捉throw抛出的异常,则必须使用try—catch语句。
(4)throws关键字通常被应用在声明方法时,用来指定可能抛出的异常。多个异常可以使用逗号隔开。当在主函数中调用该方法时,如果发生异常,就会将异常抛给指定异常对象。
六、用于包的关键字:package,import
1.package
-
解读
(1)Java中的一个包就是一个类库单元,包内包含有一组类,它们在单一的名称空间之下被组织在了一起,这个名称空间就是包名。可以使用import关键字来导入一个包。
(2)打个比方,package 名称就像是我们的姓,而 class 名称就像是我们的名字。package 名称有很多的,就好像是复姓。比如说 java.lang.String,就是复姓 java.lang,名字为 String 的类别;java.io.InputStream 则是复姓java.io,名字为 InputStream 的类别。
但是实际中经常会出现重名的情况,这时候就需要用不同的姓作区分,例如A写了一个String类,B也写了个String类,为了区分,可以通过A.String和B.String分别表示他们各自的String类。
(3)按照惯例,java中习惯使用Internet域名的反序作为包名,例如我有一个域名java.oracle.com,那么我写的String类的包名就是com.oracle.java。 -
将类放在包中
(1)基本规则1.在文件的最上方加上一个package语句指定该代码在哪个包中 2.包名尽量指定成一个唯一的名字 3.包名和代码路径相匹配,例如创建com.gao的包,那么会在com/gao的路径来存储代码 4.如果一个类没有package语句,那么这个类会被放到一个默认的包中
-
包的权限控制
如果某个成员不包含 public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用。
2.import
-
import的作用
(1)如果没有import,我们每次使用别的类库的时候就要书写该类的全路径名,例如:
(2)为了方便,我们引入了import关键字,只需要在java文件开头部分导入相应的类的全名一次,之后就只需要写类名就可以引用该类了,例如:
(3)例如使用import java.util.*就可以导入名称空间java.util包里面的所有类。所谓导入这个包里面的所有类,就是在import声明这个包名以后,在接下来的程序中可以直接使用该包中的类。 -
静态导入:使用import static可以导入包中的静态的方法和字段
-
编译后
(1)编译时所有类名都会被替换成相应的完全路径名,如ArrayList倍替换java.util.ArrayList,这与我们自己书写完全路径名是一样的效果。
(2)查找import的类时,按照环境变量CLASSPATH中指定的路径(如有多个,依此执行下面操作),将该路径作为根目录,然后将import后面的完全路径名中的.换成路径分隔符并添加.java后缀,将根目录与替换后的路径拼接成完全路径定位要引用的类文件。例如上面的导入的java.util.ArrayList类文件路径为$CLASSPATH/java/util/ArrayList.java。
七、其他修饰符关键字:native, strictfp,transient,volatile,assert
1.native
- 对于native我也不是很了解,属于不常见的关键字,这里我找了一个大佬写的博客,很详细,供你我学习参考: 传送门
- native不能和abstract一起用:abstract表示该方法没有具体的实现,而native修饰的方法并不是说这个方法没有实现,而是由别人实现的。
2.strictfp
- strictfp, 即 strict float point (精确浮点),如果你想让你的浮点运算更加精确,而且不会因为不同的硬件平台所执行的结果不一致的话,可以用关键字strictfp。
- strictfp 关键字可应用于类、接口或方法。
(1)使用 strictfp 关键字声明一个方法时,该方法中所有的float和double表达式都严格遵守FP-strict的限制,符合IEEE-754规范。
(2)当对一个类或接口 使用 strictfp 关键字时,该类中的所有代码,包括嵌套类型中的初始设定值和代码,都将严格地进行计算。严格约束意味着所有表达式的结果都必须是 IEEE 754 算法对操作数预期的结果,以单精度和双精度格式表示。
例子如下:
不使用strictfp的例子:
public class TestNoStrictfp {
private static double aDouble = 0.0555500333333212d;
private static float aFloat = 0333000000222f;
public static void main(String[] args) {
double cDouble = aDouble / aFloat;
System.out.println("aDouble:" + aDouble);
System.out.println("aFloat:" + aFloat);
System.out.println("cDouble:" + cDouble);
}
}
使用strictfp的例子:
public strictfp class TestStrictfp {
private static double aDouble = 0.0555500333333212d;
private static float aFloat = 0.0333000000222f;
public static void main(String[] args) {
double cDouble = aDouble / aFloat;
System.out.println("aDouble:" + aDouble);
System.out.println("aFloat:" + aFloat);
System.out.println("cDouble:" + cDouble);
}
}
3.transient
- 将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化(需要实现Serilizable接口),也就是说,假设某个类的成员变量是transient,那么当通过ObjectOutputStream把这个类的某个实例保存到磁盘上时,实际上transient变量的值是不会保存的。因为当从磁盘中读出这个对象的时候,对象的该变量会没有被赋值。
- 静态变量不管是不是transient关键字修饰,都不会被序列化
- 这个关键字属于不常见的关键字,这里我找了一个大佬写的博客,很详细,供你我学习参考: 传送门
4.volatile
- 前言:有关变量的原子性,可见性,有序性(保证多线程安全的重要因素)
(1)原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。(变量的自增自减并不是一步操作)
(2)可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
(3)有序性:即程序执行的顺序按照代码的先后顺序执行。 - volatile可以保证变量在多线程的环境下保持可见性和有序性,一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
(1)当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中,因此可以保证可见性。
(2)禁止进行指令重排序。 - volatile保证了变量的可见性,在一定的程度上保证变量的有序性,不保证变量的原子性
(1)下面这段话摘自《深入理解Java虚拟机》:“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”。
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成(一定的程度上保证有序性);
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。 - 参照大佬博客:传送门
5.assert
- assert主要用于断言中,那什么是断言呢:
- assert后边所跟的条件必须满足,必须维护,否则就会出现错误。
(1)assert BooleanCondition; 表示asssert后边跟一个布尔表达式。
1)如果表达式的值为true,那么就认为当前条件符合要求,继续执行业务代码。
2)如果表达式的值为false,那么久认为当前条件不符合要求,立即抛出AssertionError的错误。
3)AssertionError extends Error extends Throwable。Throw这个类,平常使用的相对较少,它还有一个子类叫做Exception。Error和Exception一样,均属于系统不应该试图捕获的严重问题。
(2)assert BooleanCondition:Excepiton; assert后边跟一个布尔表达式,同时再跟一个返回值为基本类型的表达式。
1)当表达式为true时,则继续运行剩余业务代码,不会执行‘:’后边的表达式。
2)当表达式为false时,则会执行‘:’后边的表达式,并将结果放置在AssertionError异常中,并抛出。
- 在assert处加入断点,Debug调试时,发现断点处根本没有停顿,而是直接跳过了。为什么会这样呢?这是因为assert关键字是受java启动项配置的(具体方法看编程软件而定)
- assert的使用,是你知道这个事情在正常的情况下是绝对不会发生的,但是你也知道,OS、jvm中的事情是会偶然出现莫名其妙错误的,同时保不准某个调用你代码的人,和你想的不一样,错误的调用了你的代码。所以:
(1)assert常被放置在用户的核心处理代码中,翻看java源代码,你就会发现源码中有大量的使用assert关键字。
(2)assert处理的是那种正常情况下绝对不会出现的情况,所以在平常的业务流程中使用assert。
(3)assert是不具有继承性的:如果开启父类的assert,则运行到子类的assert方法时,子类是默认不开启的。反之如果开启子类的assert,运行到父类的assert方法时,父类的assert也是不开启的。