Java修饰符主要分为两种,访问权限修饰符和非访问权限修饰符。分别作用于类、方法和域(字段)上面,而且他们将起到不同的作用,甚至有些修饰符还是互斥的。接下来我将粗略的介绍一遍,以便看客朋友们能有个印象,起码知道有这么个东西。
访问权限修饰符
见名知义,访问权限修饰符就是为了控制程序的访问权限。那么为什么需要访问权限修饰符呢?不可以直接所有权限开放,所有类,方法,变量都能访问吗?
个人理解,主要是为了安全考虑,有了访问权限修饰符,研发人员就能自己控制哪些方法、变量可以外部直接访问,对于无需外界访问的可以封装起来形成黑盒,这样对提供者和消费者都很友好,也可以防止不熟悉的开发人员改动到了他不应该改动的值,导致程序崩溃。
简单举个例子:假设现在有个订单表,订单一般有自己的订单状态,支付状态,两者之间会有些不变性约束,比如订单状态为已完成的则支付状态必然是已支付。这种不变性约束是程序在内部控制的,且通过某些行为同时修改对应状态的值。但是如果此时我们可以直接访问单独的某个状态,将已完成订单的支付状态改为未支付,这将破坏订单的不变性条件,导致系统出现异常,这是我们无法容忍的。
访问权限修饰符分为以下几种:
(default):默认权限,不加任何权限修饰符时就是该权限,只有同包可访问。同包可访问意味着在其他包中都不可访问,包括子包。该修饰符可以修饰:类,接口,枚举,方法,域。
public:公开权限,任何地方都可以访问。可修饰:类,接口,枚举,方法,域。
protected:受保护的权限。这个就比较特殊了,它除了同包可访问之外,继承类也可以访问。可修饰:内部类,内部接口,内部枚举,方法,域。
private:私有权限。只有申明的类才可以访问。当前如果有看前一篇文章介绍的话,咱也可以知道,内部类也是可以访问外部类的私有域和私有方法的。可修饰:内部类,内部接口,内部枚举,方法,域。
总结一下:
类 |
接口 |
枚举 |
方法 |
域 |
|
(default) |
✔️ |
✔️ |
✔️ |
✔️ |
✔️ |
public |
✔️ |
✔️ |
✔️ |
✔️ |
✔️ |
protected |
仅内部 |
仅内部 |
仅内部 |
✔️ |
✔️ |
private |
仅内部 |
仅内部 |
仅内部 |
✔️ |
✔️ |
如果将内部类、内部接口、内部枚举类比成类的成员或者方法的话应该就好理解他们的访问权限了。区别在于,内部接口和内部枚举一定是静态的,所以没有成员内部接口或者成员内部枚举的概念。
非访问权限修饰符
与访问权限修饰符类似,这也是Java内置的关键词,用于修饰Java类、方法等。分别都有各自的作用,也有各自的修饰限制条件,接下来我将一一介绍。
final
该关键字意为最终。可以修饰类,方法,域。
修饰类:标明类为最终态的类,不可继承。当我们定义一个不希望别人更改行为的类时可以使用。所以这种类也无法定义它的匿名类。
修饰方法:标明方法不可重写。
修饰域:标明域在初始化之后不可再次变更(可以申明类的成员变量但是不赋值,在构造器或者初始块中再初始化。)
static
该关键字意为静态。可以修饰类,方法,域,块。
修饰类:只能修饰内部类,无法修饰外部类。关于静态内部类,在上一节已经有过说明,感兴趣的看客朋友,可以一阅。
修饰方法:标明是静态方法,独立于类实例对象之外,调用时只需直接使用类.方法
即可。无需实例化对象,再通过对象调用,所以也不能访问类的非静态方法和非静态域。
修饰域:其实和方法是类似的,都标明是独立于类的实例对象之外,可以被静态方法和非静态方法访问,无论实例化多少次类,被static修饰的域都是只存在一份。
修饰块:静态初始块,在类加载时会执行块中的代码,所以会先于构造器执行,一般用于初始化一些资源之类。
abstract
意为抽象。只可以修饰类和方法。
修饰类:标明是抽象类,不可以直接实例化,所以abstract不能和final同时修饰类,另外抽象类也可以有非抽象方法。
修饰方法:标明是抽象方法,必须是抽象类才能有抽象方法。抽象方法在抽象类被继承时必须重写,否则继承类也得是抽象类,所以abstract和final也不能同时修饰方法。
transient
标明临时存储,在默认序列化时将忽略该字段。只能修饰域。
修饰域:标明不需要序列化,在默认序列化时将忽略该字段,如远端传输,或者磁盘读写时,该关键字修饰的域所对应的值将不会传输。
synchronized
用于多线程时同步代码块使用,可以修饰代码块,或者修饰方法。
修饰代码块:不是和初始化块或者静态块那种语法,是在代码块中间需要加上对象用于标识需要抢到哪把锁才能进入该代码块。
// 第一种 synchronized (Object.class) { System.out.println("进入锁"); } // 第二种 Object object = new Object(); synchronized (object) { System.out.println("进入锁"); } // 第三种 synchronized (this) { System.out.println("进入锁"); }
修饰方法:在方法中修饰,所有进入该方法的线程都需要争抢同一把锁。
主要是控制访问被修饰的方法或者代码块在多线程时只能允许一个线程访问。
详细讲解将放后期的同步异步再与大家分享。
volatile
只有两个作用,1、保证内存可见。2、禁止指令重排。只可以修饰域。
保证内存可见:我们应该知道Java的内存模型分为工作内存和主内存,线程访问变量都是与工作内存交互,而工作内存是各线程独立的,所以一个线程操作完变量时其他线程不会立刻感知,由此才会引发线程安全问题。但是如果由volatile修饰则其他线程会立刻感知。但是,这并不能说明它是线程安全的。这里先卖个关子,后续讲解。
禁止指令重排:编译器和处理器为了一些性能优化会对指令做一些排序,所以实际执行的指令顺序和我们代码顺序不一定一致,当然这种优化也会有一些规则保证在单线程下执行不会导致错误的结果。但是在多线程中这种优化就会导致各种莫名其妙的问题,而该关键字可以保证被修饰的变量的代码一定会在他所处的位置执行。
private volatile int i = 2; private int t = 2; private void init () { t = 3; dosometing1(); System.out.println(i); // volatile修饰变量 dosometing2(); t = 4; }
在这个示例中System.out.println(i);
之前的代码一定会在它之前执行,在System.out.println(i);
之后的代码也一定会在他之后执行。
native
只能用于修饰方法,表示需要调用本地方法,主要是为了Java自身没有的能力需要调用其他语言代码实现时使用。例如如果需要访问系统硬件,Java自身不支持,则可以用该修饰符扩展调用其他语言实现。实际语法如下:
private native void init();
strictfp
严格计算,用于修饰类和方法。
修饰类和方法的作用都是相同的,标明类下的所有方法或者修饰的类采用严格计算。那什么是严格计算呢?
这是因为Java虚拟机有各种不同的实现,甚至他们的运行系统也可能不一致,所以他们采用浮点计算时的精度其实并不是一致的。而如果使用该修饰符修饰,所有的浮点运算将会采用浮点规范IEEE-754来执行,这样就能保证精度一致,这就是严格计算。当然实际上无论如何浮点毕竟会导致精度丢失(至于为何,这里也埋个坑,后续补),所以如果想要精度不丢失的话使用BigDecimal即可。
写在最后
大体上也是把这一期的文章给补全了,只是最近实在是忙,只能周天的时候抽点时间补一下,也导致欠的一篇一直没有时间补上,不过我会记着,一定会补的!!!也是自己开始写文章了才知道那些日更的人到底是有多厉害了,如果还能保证质量的话那真的就是高高手了,写文章真的是一个需要精力的活,大脑需要一直思考,不是打字1分钟100就能1小时写6000字文章的。
虽然这期文章完成了,不过限于篇幅而且也怕偏离了主题所以还是埋了几个坑留待后续补充的,当然如果有感兴趣的朋友也可以去搜索阅读下。
- synchronized具体作用及原理?
- volatile既然能保证可见性那为什么不是线程安全的?
- native的实际用法及场景。
- 浮点数为什么会精度丢失?