Java的修饰符根据修饰的对象不同,分为类修饰符、方法修饰符、变量修饰符。
其中每种修饰符又分为访问控制修饰符和非访问控制修饰符。
访问控制符:公共类修饰符public
非访问控制符:抽象类修饰符:abstract、最终类修饰符:final
(1)公共类修饰符 public : Java 语言中类的访问控制符只有 public 即公共的。每个 Java 程序的有且只有一个类是 public,它被称为主类 ,其他外部类无访问控制修饰符,具有包访问性。注意:一个类的内部类可以被其他访问控制修饰符protected、default、private修饰,相当于类的成员。
(2)抽象类修饰符 abstract :用 abstract 修饰符修饰的类,被称为抽象类。
(3)最终类修饰符 final :当一个类不能被继承时可用修饰符final修饰为最终类。被定义为 final 的类通常是一些有固定作用、用来完成某种标准功能的类。
(4)类缺省访问控制符:如果一个类没有访问控制符,说明它具有缺省的访问控制符特性。此时,这个类只能被同一个包中的类访问或引用。这一访问特性又称为包访问性。
定义:将一个类的定义放在里另一个类的内部,这就是内部类
分类:成员内部类、静态内部类、局部(方法)内部类、匿名内部类。
成员内部类:
访问控制符:可以被public、protected、default、private修饰,相当于类的成员
非访问控制符:抽象类修饰符:abstract、最终类修饰符:final
静态内部类:
访问控制符:可以被public、protected、default、private修饰,相当于类的成员
非访问控制符:静态修饰符:static
局部(方法)内部类:
访问控制符:类前不能有访问控制符
匿名内部类:
访问控制符:类前不能有访问控制符
访问控制修饰符:公共访问控制符public、保护访问控制符protected、缺省默认default、私有访问控制符private
非访问控制符:抽象方法控制符abstract 、静态方法控制符static 、最终方法控制符final 、本地方法控制符native 、同步方法控制符synchronized
(1)抽象方法控制符 abstract :用修饰符 abstract 修饰的方法称为抽象方法。抽象方法仅有方法头,没有方法体和操作实体。
(2)静态方法控制符 static :用修饰符 static 修饰的方法称为静态方法。静态方法是属于整个类的类方法;而不使用static 修饰、限定的方法是属于某个具体类对象的方法。由于 static方法是属于整个类的,所以它不能操纵和处理属于某个对象的成员变量,而只能处理属于整个类的成员变量,即static方法只能处理static的域。
(3)最终方法控制符 final :用修饰符 final修饰的方法称为最终方法。最终方法是功能和内部语句不能更改的方法,即最终方法不能重写覆盖。final固定了方法所具有的功能和操作,防止当前类的子类对父类关键方法的错误定义,保证了程序的安全性和正确性。所有被private修饰符限定为私有的方法,以及所有包含在final 类 ( 最终类) 中的方法,都被认为是最终方法。
(4)本地方法控制符 native :用修饰符 native 修饰的方法称为本地方法。为了提高程序的运行速度,需要用其它的高级语言书写程序的方法体,那么该方法可定义为本地方法用修饰符 native 来修饰。
(5)同步方法控制符 synchronized :该修饰符主要用于多线程程序中的协调和同步。
访问控制符:公共访问控制符public 、保护访问控制符protected 、缺省默认访问控制符default、私有访问控制符private
非访问控制符:静态域修饰符static 、最终域修饰符 final 、易失 ( 共享 ) 域修饰符volatile 、暂时性域修饰符transient
(1)公共访问控制符public:用public修饰的域称为公共域。由于public修饰符会降低运行的安全性和数据的封装性,所以一般应减少 public 域的使用。
(2)私有访问控制符private:用private修饰的成员变量( 域 ) 只能被该类自身所访问,而不能被任何其它类 ( 包括子类 ) 所引用。
(3)保护访问控制符 protected :用protected修饰的成员变量可以被三种类所引用:①该类自身;②同一个包中的其它类;③其它包中的子类。使用修饰符 protected 的主要作用是允许其它包中的子类来访问父类的特定属性。
(4) 缺省默认修饰符 :没有访问控制修饰符或用修饰符 default修饰的成员变量可以被该类本身或同一包中的其他类访问。
(5)静态域修饰符 static :用 static修饰的成员变量仅属于类的变量,而不属于任何一个具体的对象,静态成员变量的值是保存在类的内存区域的公共存储单元,而不是保存在某一个对象的内存区间。该类的任一对象访问它时取到的都是相同的数据;该类的任一对象修改它时 , 也都是对同一个内存单元进行操作。
(6)最终域修饰符 final :最终域修饰符 final 是用来定义常量的。一个类的域 ( 成员变量 ) 如果被修饰符 final 说明,则它的取值在程序的整个执行过程中都是不变的。
(7)易失 ( 共享 ) 域修饰符 volatile :易失 ( 共享 ) 域修饰符volatile是用来说明这个成员变量可能被几个线程所控制和修改。也就是说在程序运行过程中,这个成员变量有可能被其它的程序影响或改变它的取值。通常 volatile 用来修饰接受外部输入的域。
(8)暂时性域修饰符 transient :暂时性域修饰符 transient 用来定义一个暂时性变量。其特点是:用修饰符transient 限定的暂时性变量,将指定 Java虚拟机认定该暂时性变量不属于永久状态,以实现不同对象的存档功能。否则,类中所有变量都是对象的永久状态的一部分,存储对象时必须同时保存这些变量。
private:只能被同一个类中的对象访问。
default:可以被同一个类的对象,同一个包中的类访问。
protected:可以被同一个包中的所有类访问,可以所有的子类访问,子类没有在同一个包中也可以访问。
public:同包,不同包都可以访问。
package cn.wyu.java;
public class CodeBlockForJava extends BaseCodeBlock {
static {
System.out.println("2.这里是子类的静态代码块");//2
}
{
System.out.println("6.这里是子类的普通代码块");//6
}
Other o = new Other();
public CodeBlockForJava() {
System.out.println("8.这里是子类的构造方法");//8
}
public static void msg2() {
System.out.println("这里是子类的静态方法");
}
@Override
public void msg() {
System.out.println("9.这里是子类的普通方法");//9
}
public static void main(String[] args) {
BaseCodeBlock bcb = new CodeBlockForJava();
bcb.msg();
}
}
class BaseCodeBlock {
Other2 o2 = new Other2();
static {
System.out.println("1.这里是父类的静态代码块");//1
}
{
System.out.println("4.这里是父类的普通代码块");//4
}
public BaseCodeBlock() {
System.out.println("5.这里是父类的构造方法");//5
}
public void msg() {
System.out.println("这里是父类的普通方法");
}
public static void msg2() {
System.out.println("这里是父类的静态方法");
}
}
class Other {
Other() {
System.out.println("7.初始化子类的属性值");//7
}
}
class Other2 {
Other2() {
System.out.println("3.初始化父类的属性值");//3
}
}
运行结果:
1.这里是父类的静态代码块
2.这里是子类的静态代码块
3.初始化父类的属性值
4.这里是父类的普通代码块
5.这里是父类的构造方法
6.这里是子类的普通代码块
7.初始化子类的属性值
8.这里是子类的构造方法
9.这里是子类的普通方法
假设:
将Other o = new Other();变为static Other o = new Other();
将Other2 o2 = new Other2();变为static Other2 o2 = new Other2();
结果?
3.初始化父类的属性值
1.这里是父类的静态代码块
2.这里是子类的静态代码块
7.初始化子类的属性值
4.这里是父类的普通代码块
5.这里是父类的构造方法
6.这里是子类的普通代码块
8.这里是子类的构造方法
9.这里是子类的普通方法
注意:
类的静态属性与静态代码块的执行级别是一样的,谁先执行取决于书写的先后顺序。
类的非静态属性与普通代码块的执行级别是一样的,谁先执行取决于书写的先后顺序。
结论1:初始化父类的静态属性值/父类的静态代码块(自上而下的顺序执行) -> 初始化子类的静态属性值/子类的静态代码块(自上而下的顺序执行) -> 初始化父类的非静态属性值/父类的普通代码块(自上而下的顺序执行) -> 父类的构造方法 -> 初始化子类的非静态属性值/子类的普通代码块(自上而下的顺序执行) -> 子类的构造方法。
注:构造函数最后执行。
2.2.1 下列代码的执行结果是什么?
package cn.wyu.java;
/**
* @author linwillen
* @create 2020-05-01-21:10
*/
public class ClassloadSort1 {
public static void main(String[] args) {
Singleton.getInstance();
System.out.println("Singleton value1:" + Singleton.value1);
System.out.println("Singleton value2:" + Singleton.value2);
Singleton2.getInstance2();
System.out.println("Singleton2 value1:" + Singleton2.value1);
System.out.println("Singleton2 value2:" + Singleton2.value2);
}
}
class Singleton {
static {
System.out.println(Singleton.value1 + "\t" + Singleton.value2 + "\t" + Singleton.singleton);
//System.out.println(Singleton.value1 + "\t" + Singleton.value2);
}
private static Singleton singleton = new Singleton();
public static int value1 = 5;
public static int value2 = 3;
private Singleton() {
value1++;
value2++;
}
public static Singleton getInstance() {
return singleton;
}
int count = 10;
{
System.out.println("count = " + count);
}
}
class Singleton2 {
static {
System.out.println(Singleton2.value1 + "\t" + Singleton2.value2 + "\t" + Singleton2.singleton2);
}
public static int value1 = 5;
public static int value2 = 3;
private static Singleton2 singleton2 = new Singleton2();
private String sign;
int count = 20;
{
System.out.println("count = " + count);
}
private Singleton2() {
value1++;
value2++;
}
public static Singleton2 getInstance2() {
return singleton2;
}
}
运行结果:为什么两次value的值不一样?
0 0 null
count = 10
Singleton value1:5
Singleton value2:3
0 0 null
count = 20
Singleton2 value1:6
Singleton2 value2:4
2.2.2 为什么有上面的结果?
Java类加载分为5个过程,分别为:加载,连接(验证,准备,解析),初始化,使用,卸载。(加链初使卸)
(1)加载
1)通过classloader在classpath中获取XXX.class文件,将其以二进制流的形式读入内存。
2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
(2)链接
① 验证:主要确保加载进来的字节流符合JVM规范。验证阶段会完成以下4个阶段的检验动作:
1)文件格式验证
2)元数据验证(是否符合Java语言规范)
3)字节码验证(确定程序语义合法,符合逻辑)
5)符号引用验证(确保下一步的解析能正常执行)
② 准备:主要为静态变量在方法区分配内存,并设置默认初始值。
③ 解析:主要是虚拟机将常量池内的符号引用替换为直接引用的过程。
(3)初始化:初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值(赋值顺序参考上面2.1)。
(4)使用:程序之间的相互调用。
(5)卸载:即销毁一个对象,一般情况下中有JVM垃圾回收器完成。代码层面的销毁只是将引用置为null。
通过上面的整体介绍后,再来看Singleton2.getInstance()的执行分析:
1)类的加载。运行Singleton2.getInstance(),JVM在首次并没有发现Singleton类的相关信息。所以通过classloader将Singleton.class文件加载到内存中。
2)类的验证。略
3)类的准备。将Singleton2中的静态资源转化到方法区。value1,value2,singleton在方法区被声明分别初始为0,0,null。
4)类的解析。略(将常量池内的符号引用替换为直接引用的过程)
5)类的初始化。执行静态属性的赋值操作。按照顺序先是value1 = 5,value2 = 3,接下来是private static Singleton2 singleton2 = new Singleton2();
这是个创建对象操作,根据 结论1 在执行Singleton2的构造方法之前,先去执行static资源和非static资源。但由于value1,value2已经被初始化过,所以接下来执行的是非static的资源,最后是Singleton2的构造方法:value1++;value2++。
所以Singleton2结果是6和4。结论2:静态资源在类的初始化中只会执行一次。不要与第3个步骤混淆。
有了以上的这个结论,再来看Singleton.getInstance()的执行分析:
1)类的加载。将Singleton类加载到内存中。
2)类的验证。略
3)类的准备。将Singleton2的静态资源转化到方法区。
4)类的解析。略(将常量池内的符号引用替换为直接引用的过程)
5)类的初始化。执行静态属性的赋值操作。按照顺序先是private static Singleton singleton = new Singleton(),根据 结论1 和结论2,value1和value2不会在此层执行赋值操作。所以singleton对象中的value1,value2只是在0的基础上进行了++操作。此时singleton对象中的value1=1,value2=1。
然后, public static int value1 = 5; public static int value2 = 3; 这两行代码才是真的执行了赋值操作。所以最后的结果:5和3。
如果执行的是public static int value1; public static int value2;结果又会是多少?结果: 1和1。要理解初始化和赋值区别注:为什么 Singleton singleton = new Singleton()不会对value1,value2进行赋值操作?因为static变量的赋值在类的初始化中只会做一次。
程序在执行private static Singleton singleton = new Singleton()时,已经是对Singleton类的static变量进行赋值操作了。这里new Singleton()是一个特殊的赋值结论3:非静态资源会随对象的创建而执行初始化。每创建一个对象,执行一次初始化。
package cn.wyu.java;
/**
* @author linwillen
* @create 2020-05-01-23:41
*/
public class TryCatchFinally {
public static void main(String[] args) {
System.out.println("method:x = "+method());//step:1 //step:8 x = 10
}
public static int method(){
int x = 0;//step:2 x = 0
try {
x = 1;//step:3 x = 1
System.out.println("try:x = "+x);//step:4 x = 1
} catch (Exception e) {
x = 2;
System.out.println("catch:x = "+x);
} finally {
x = 10;//step:5 x = 10
System.out.println("finally:x = "+x);//step:6 x = 10
}
return x;//step:7 x = 10
}
}
运行结果:
try:x = 1
finally:x = 10
method:x = 10
总结:按顺序执行,先执行try块,在没有异常的情况下,接下来执行finally块,随后return
package cn.wyu.java;
/**
* @author linwillen
* @create 2020-05-01-23:41
*/
public class TryCatchFinally {
public static void main(String[] args) {
System.out.println("method:x = "+method());//step:1 //step:9 x = 1
}
public static int method(){
int x = 0;//step:2 x = 0
try {
x = 1;//step:3 x = 1
System.out.println("try:x = "+x);//step:4 x = 1
return x;//step:5 x = 1 //step:8 x = 1
} catch (Exception e) {
x = 2;
System.out.println("catch:x = "+x);
return x;
} finally {
x = 10;//step:6 x = 10
System.out.println("finally:x = "+x);//step:7 x = 10
}
}
}
运行结果:
try:x = 1
finally:x = 10
method:x = 1
总结:代码先执行try块,再执行finally块。finally块总会执行。try语句中的return返回的引用变量并不是try语句外定义的引用变量x,而是系统重新定义了一个局部引用x1,这个引用指向了引用x对应的值,也就是1,即使在finally语句中把引用x指向了值10,但是return已经不是x,而是x1,所以引用x的值和try语句中的返回值无关了
package cn.wyu.java;
/**
* @author linwillen
* @create 2020-05-01-23:41
*/
public class TryCatchFinally {
public static void main(String[] args) {
System.out.println("method:x = "+method());//step:1 //step:9 x = 10
}
public static int method(){
int x = 0;//step:2 x = 0
try {
x = 1;//step:3 x = 1
System.out.println("try:x = "+x);//step:4 x = 1
return x;//step:5 x = 1
} catch (Exception e) {
x = 2;
System.out.println("catch:x = "+x);
return x;
} finally {
x = 10;//step:6 x = 10
System.out.println("finally:x = "+x);//step:7 x = 10
return x;//step:8 x = 10
}
}
}
运行结果:
try:x = 1
finally:x = 10
method:x = 10
总结:执行finally块中的return语句,JVM忽略了try块中的return语句。
package cn.wyu.java;
/**
* @author linwillen
* @create 2020-05-01-23:41
*/
public class TryCatchFinally {
public static void main(String[] args) {
System.out.println("method:x = "+method());//step:1 //step:12 x = 10
}
public static int method(){
int x = 0;//step:2 x = 0
try {
x = 1;//step:3 x = 1
Integer.parseInt(null);//step:4
System.out.println("try:x = "+x);
return x;//step:5 x = 1
} catch (Exception e) {
x = 2;//step:6 x = 2
System.out.println("catch:x = "+x);//step:7 x = 2
return x;//step:8 x = 2
} finally {
x = 10;//step:9 x = 10
System.out.println("finally:x = "+x);//step:10 x = 10
return x;//step:11 x = 10
}
}
}
运行结果:
catch:x = 2
finally:x = 10
method:x = 10
总结:执行finally块中的return语句。finally中含return语句,导致try/catch中的异常信息被忽略,而我们最初使用try/catch就是为了捕获异常信息,所以这种情况与之产生矛盾,因此编译器会有警告提示
package cn.wyu.java;
/**
* @author linwillen
* @create 2020-05-01-23:41
*/
public class TryCatchFinally {
public static void main(String[] args) {
System.out.println("method:x = "+method());//step:1 //step:12 x = 10
}
public static int method(){
int x = 0;//step:2 x = 0
try {
x = 1;//step:3 x = 1
Integer.parseInt(null);//step:4
System.out.println("try:x = "+x);
return x;
} catch (Exception e) {
x = 2;//step:5 x = 2
String.valueOf(null); //step 6
System.out.println("catch:x = "+x);//step:7 x = 2
return x;
} finally {
x = 10;//step:7 x = 10
int y = x / 0;//step:8
System.out.println("finally:x = "+x);
}
}
}
运行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at cn.wyu.java.TryCatchFinally.method(TryCatchFinally.java:26)
at cn.wyu.java.TryCatchFinally.main(TryCatchFinally.java:9)
总结:如果finally发生异常,那么try/catch中的异常信息就会被忽略,达不到异常信息处理的目的
综上所述:
- finally语句总会执行。
- 如果try/catch中有return语句,finally中没有return语句,那么finally中修改数据(除集合类、静态变量、全局变量)对try/catch中返回的变量没有任何影响,返回的是try/catch中的变量的值。
- 在finally中使用return,会忽略try/catch中的返回语句,也会忽略try/catch中的异常,返回的是finally中的变量的值。
- finally中如果发生异常,代码执行将会抛出finally中的异常信息。