Java温故知新之旅——关于修饰符

来源:互联网 发布:淘宝开放平台开发 编辑:程序博客网 时间:2024/05/18 06:56

一、定义

        修饰符,顾名思义,就是以修饰其他内容为主要目的一组关键字,借用百度百科定义:用于限定类型以及类型成员的申明的一种符号。

二、分类

        为解释方便暂以用途分为三类:访问权限修饰符(public、protected、private)、方法专用修饰符(native、synchronized)、属性专用修饰符(transient、volatile)、其他修饰符(abstract、strictfp、static、final)。这里的属性,指代全局变量,因为局部变量随方法存在,方法使用完,局部变量被垃圾收集器回收,所以绝多数修饰符是无法使用的。

三、说明

1、访问权限修饰符

        访问权限修饰符,主要用于控制所修饰的内容,可以开放到什么程度。以开放程度排序,说明如下:

        public:开放程度最高,无限制,只要调用方法符合规范,就可以使用。

        protected:只对同一个包下的其他类或者子类内部(相当于子类的private)开放使用。

        默认:什么访问权限修饰符都没加的情况。只对同一个包下的其他类开放使用。
        private:只对当前类内部开放使用。

访问权限修饰符使用限制:

:内部类无限制,匿名类等同于变量,非内部/匿名类只能使用public或者默认,且若使用public则只能修饰主类(和Java文件同名的类),主类只能有一个。

方法:无限制

属性:无限制

2、方法专用修饰符

        native:该修饰符的特点是,可以让方法实现跨语言。比如说,程序里需要使用一个方法,而这个方法,因为某些原因,只能使用其他语言来实现。此时,就可以使用这个修饰符来实现,举个例子:

Java部分:

public class Test { // 先写一个java文件public native int getNum(int iFirst, int iSecond);// 需要用其他语言来实现的方法static {System.loadLibrary("Test"); // 其他语言实现完成后,生成的库文件名字}public static void main(String[] args) {Test tt = new Test();System.err.println("result:" + tt.getNum(1, 2));}}
        这里有两个重点:一个是native修饰的方法,不写方法体,其区别于接口的是,接口是不能有实现,而该方法只是声明其实现全在库文件里;二是库文件的引用,这里的库文件指的是其他语言生成的动态链接库,里边包含方法的实现部分,引用库文件时,库文件的名字必须要和动态链接库的名字一致。

        文件写完后,javac Test.java 生成Test.class,然后javah Test 生成对应的.h头文件,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class Test */#ifndef _Included_Test#define _Included_Test#ifdef __cplusplusextern "C" {#endif/* * Class:     Test * Method:    getNum * Signature: (II)I */JNIEXPORT jint JNICALL Java_Test_getNum  (JNIEnv *, jobject, jint, jint);#ifdef __cplusplus}#endif#endif
        这里采用C/C++语言来实现,创建一个dll工程,将这个头文件Test.h添加入项目,然后添加.cpp实现:

#include "Test.h"JNIEXPORT jint JNICALL Java_Test_getNum(JNIEnv *, jobject, jint iFirst, jint iSecond){return iFirst + iSecond;}
        我这里用的环境是VS2010,引用jni.h时会报错,需要将jdk安装路径下include文件夹内jni.h以及win32文件夹下的jawt_md.h和jni_md.h,复制到VS2010安装目录下VC文件夹下的include文件夹内。

        运行正确后,会生成Test.dll也就是动态链接库文件,将其拖放到一开始的Test.class路径下,然后执行java Test,就会看到想要的结果。Object的若干方法就是用该关键字修饰,更加详细的可以搜索了解:JNI。

        synchronized:该修饰符可以用来修饰方法、或者方法体(或称作块)。被修饰的内容,在同一时间,同一访问方式下只允许一个线程进入并执行里边的代码。这里的“同一时间”指的是,任意一个线程进入这个方法开始,到执行完这个方法这段时间内;“同一访问方式”是指,通过同一个对象调用,或者通过类名直接调用(当该内容也被static修饰时)。简单举个例子:
public class Good {       public synchronized void getStr() {try {while (true) {System.err.println(Thread.currentThread().getName());Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {Good good = new Good();TestThread t1 = new TestThread(good);TestThread t2 = new TestThread(good);t1.start();t2.start();}}class TestThread extends Thread {private Good good;public TestThread(Good good) {this.good = good;}@Overridepublic void run() {good.getStr();}}
        while给的是死循环,所以结果会只出现线程一的输出,因为两个线程是通过同一方式调用getStr,所以在线程一进入该方法操作时,线程二会一直等待,直到线程一执行结束。为了加深印象,可以做如下更改(二选一):
1)、创建另一个Good对象good2,将good、good2分别传给t1、t2;
2)、去掉while循环。
        结果会看到两个线程交叉输出。原因参照“同一时间”和“同一访问方式”的解释。
        这里还要特殊强调下static。当在synchronized前或后,加上static时,该方法就会变成静态方法,因为静态方法的特性(说明static修饰符时详解),不论以何种方式调用该方法,始终会满足“同一访问方式”的条件。单例模式,便是一种应用。

3、变量专用修饰符

        transient:该修饰符的作用是,若当前类实现Serializable(即序列化),不序列化被该修饰符修饰的属性。代码示例如下:

import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;class SerializableObject implements Serializable {private static final long serialVersionUID = 8802952604275723536L;public int value = 0;public SerializableObject() {value = 1;}public int getValue() {return value;}}class SerializableContent implements Serializable {private static final long serialVersionUID = -7876526379346277955L;public transient int serializablePublic = 10;private String serializablePrivate = "right";static boolean serializableStatic = true;protected SerializableObject serializableObject = new SerializableObject();public int getSerializablePublic() {return serializablePublic;}public String getSerializablePrivate() {return serializablePrivate;}public SerializableObject getSerializableObject() {return serializableObject;}}public class SerializableTest {public static void writeObject() {try {SerializableContent serializableContent = new SerializableContent();ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:/SerializableContent.txt"));objectOutputStream.writeObject(serializableContent);objectOutputStream.flush();objectOutputStream.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}public static SerializableContent readObject() {try {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("C:/SerializableContent.txt"));SerializableContent serializableContent = (SerializableContent) objectInputStream.readObject();objectInputStream.close();return serializableContent;} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}return null;}public static void main(String[] args) {writeObject();SerializableContent.serializableStatic = false;SerializableContent result = readObject();System.out.println(result.getSerializablePublic());System.out.println(result.getSerializablePrivate());System.out.println(result.serializableStatic);System.out.println(result.getSerializableObject().getValue());}}

输出结果:

0
right
false
1

这里有注意的几点:

1).实现Serializable序列化,如果属性中包含对象,该对象的类也必须实现Serializable;

2).不论是否被何种权限修饰符修饰,即使private,一旦序列化,可以在任意地方反序列化使用;

3).被static修饰的属性无法被序列化,因为其不依托于对象存在,详见后边的static修饰符;

4).被transient修饰的属性不被序列化,也就是该修饰符的作用。

该修饰符一般应用于,某些保密信息,因为一旦序列化,随处反序列化均可调用,安全性会很低。

        volatile:该修饰符的作用是保证当前属性,始终是最新值。何为最新值?比如,

<span style="font-size:10px;">int i = 100;public void setI(int i) {    this.i = i;}</span><span style="font-size: 13.5pt;"></span>

当两个线程,线程一调用setI将i改为200,而在线程一改完的瞬间,线程二进来读取i的值。编译器的优化促使,当编译器发现再次读取数据,若发现数据并未被操作过,便省略掉再次读的操作,直接使用上次读的结果。然后线程二拿到的就会是100。(思想简单,但例子不太好写,所以就不上完整例子了)

添加volatile关键字,类似于线程锁,不过若修饰多个属性,且属性之间存在依赖关系,结果就不能保证安全了。详细可参照百度百科volatile

4、其他修饰符

        abstract:该修饰符可以用在类和方法之前,表示抽象,即抽象方法,抽象类。被该修饰符修饰的类,无法实例化(无法new对象);被修饰的方法,不能有方法体(方法内容)。如此来看,抽象类和接口基本一样。这里来谈一谈,这两者不一样的地方(个人理解):

接口可以实现多继承,而抽象类只能单继承,换言之,一个类只可以继承一个父类,单可以继承多个接口;

接口内所有方法均没有方法体,而抽象类,里边可以有非抽象方法。这一点在多个类存在同一个公共部分时很有用,举例说明:

import java.util.ArrayList;import java.util.List;abstract class CommonContent {public String getResult() {return getBegin() + "y=1 + x" + "\n" + getEnd();}protected abstract String getBegin();protected abstract String getEnd();}class EnglishContent extends CommonContent {@Overrideprotected String getBegin() {return "The equation is:";}@Overrideprotected String getEnd() {return "by Mr.L";}}class ChineseContent extends CommonContent {@Overrideprotected String getBegin() {return "方程式为:";}@Overrideprotected String getEnd() {return "李先生著";}}public class AbstractTest {public static List<CommonContent> contentList = new ArrayList<CommonContent>();static {contentList.add(new EnglishContent());contentList.add(new ChineseContent());}public static void main(String[] args) {List<CommonContent> resultList = contentList;for (CommonContent commonContent : resultList) {System.out.println(commonContent.getResult());}}}
输出:

The equationis:y=1 + x
by Mr.L
方程式为:y=1 + x
李先生著

此处,EnglishContent和ChineseContent两个类有公共部分,可以将公共部分抽出来一个非抽象方法,然后不同的部分以抽象方法存在。这样好处是,一方面体现了多态,另一方面使代码有更好的可维护性和可扩展性。

        strictfp:即 strict float point (精确浮点)。该字段用于修饰方法、接口、类。用该修饰符修饰时,其内部所有使用到的float、double,必须严格依照IEEE-754规范中的单、双精度来。IEEE-754规定了四种浮点数规则:单精确度(32位)、双精确度(64位)、延伸单精确度(43位以上,很少使用)与延伸双精确度(79位元以上,通常以80位元实做)。根据CPU的不同,不同的CPU可能采用不同的规则,不过一般很少遇到采用延伸单、双精度的CPU,且Java本身对浮点数的操作就遵守单双精度规则,所以,如果不是特殊的代码移植(从采用非延伸的CPU,移植代码到采用延伸的CPU),一般不使用该字段,也甚少见到。
        static:该修饰符可以用在类的属性、方法、内部类以及方法体之前,俗称静态X(静态属性、静态方法等),被修饰的内容,在类加载时即分配内存,也就是说,不依托于对象存在。 并且一处修改多处使用。分开来说:

          静态属性:使用方式类似于C语言中全局变量,只要一次声明初始化,可以在任意地方修改,且修改内容直接同步到其他地方。举个简单的例子:

public class Car {public static boolean carStat = false;public static void main(String[] args) {Car.carStat = true;Benz benz = new Benz();benz.getCar();}}class Benz{public void getCar() {if (Car.carStat) {System.out.println("Benz is Comming!");}}}

        在这里carStat属性被修改后,其他使用的地方直接可以同步获取,所以输出Benz is comming!。使用静态属性时要注意访问权限修饰符,直接决定着该属性可以在什么范围内被使用、修改。如若不想被修改,可以再添加一个final修饰符,这样就相当于定义了一个全局通用的常量。(也是出于这一点,为了养成一个好习惯,我们可以将自己所有使用到的常量,都通过这种方式定义在一个专门的类内,比如public static final int RETURN_SUCCESS = 1),这样,我们使用1时,便可以更清晰的明白它所代表的意义。

        静态方法:因为其独立于对象存在,所以,静态方法内的内容,不能直接使用非静态的属性和方法,如要使用,必须声明对象,通过对象调用。其他使用方式上,和静态属性类似,所以,如果多个类同时会使用到某个行为操作,可以将这个方法单独抽取出来,做成静态方法。

        静态内部类:内部类,相当于类的一个属性,所以使用方式类似于静态属性。特殊的在于,静态内部类里的属性的访问范围,受限于静态内部类的访问权限修饰符,比如说,静态内部类是private的,即使内部类里有一条public static 的属性,也是无法在当前类以外使用的。

        静态块:格式为 static {内容} ,类似于方法,所以不能在方法内使用。在加载类时,自动调用。

加载顺序:java存储数据,static 修饰的内容,存储于静态存储区域。当类第一次使用(new 实例化),会优先将static修饰的内容存放于静态存储区域,然后在堆内存里存放对象。若static修饰内容已存在于静态存储区域,则不会再次加载。举例说明:

public class TestClass {ClassAttr classAttr = new ClassAttr();public static void staticFun() {System.out.println("Static Fun");}static ClassStaticAttr classStaticAttr = new ClassStaticAttr();static {System.out.println("static chunk");}public static void main(String[] args) {new TestClass();new TestClass();TestClass.staticFun();}}class ClassAttr {public ClassAttr() {System.out.println("Attr");}}class ClassStaticAttr {public ClassStaticAttr() {System.out.println("Static Attr");}}
输出结果:Static Attr
  static chunk
  Attr
  Attr
  Static Fun
优先加载static修饰内容。当第二次使用new实例化对象时,不会重复加载。

        final:表示被修饰的内容已经完善,不能再次更改。该修饰符类、方法、变量均可以使用。分开来说:
            类(内部类也可)前加final,表示该类的结构已经是最终形态,不能拥有子类。这里说的只是结构,也就是说,类的属性内容是可以被覆盖的,比如全局变量 。因为该类无法被继承,所以方法无法被覆盖。
            方法前加final,表示该方法的结构已经是最终形态,所以不能被子类重载,因为方法内的变量在方法结束后一律会被回收,所以对于final修饰的方法,基本就相当于一块雕刻的模板,内容已完全固定。这里有个例外的方法——构造方法,构造方法只允许有访问权限修饰符,其他修饰符不允许添加。顺便一提,重载不允许,重写却是可以的,因为重写,和原方法的结构,没什么关联性。

            变量前加final,同样表示该变量已经是最终形态,该变量在使用前必须赋初值,变量内所存放的内容不得修改,根据定义变量的类型,变量可分为基本类型变量,和引用形变量;基本类型变量内存放的是变量的值,引用型变量内存放的是指向一个固定位置的索引。所以最终形态也就是说,值不能变,或者索引不能变。根据变量出现的位置又可分为三种,全局变量、局部变量、方法传入参数变量。这里又要分开说:

            全局变量加final,因为在使用前要付初值,所以,要么创建时直接赋值,或者随后赋值(静态变量在     静态块中赋初值,非静态变量,在所有的构造方法内赋初值),也就意味着,一个final非静态全局变量,可以根据构造方法的不同,赋不同的初值。

        局部变量加final一样,必须在使用前赋初值,区别于全局变量的是,如果该变量未在方法体内使用,不赋初值也是可以的。因为永远不会也无法用到。
        最后是方法传入的参数,加final的唯一作用,就是使传入的变量内存放的内容,不的修改。Java的方法传参机制,是复制传参,也就是说,传入的都是原来参数的复制体。而复制体在方法内的任何修改,对原值的内容都是无影响的(也就是说,改变不了实参的值;改变不了形参的引用)。所以,final作用只是保证复制体内容不被修改。

        鉴于final的特性,所以,final是不能和abstract、volatile同时使用的。

四、顺序

        当需要同时用到多个修饰符是,顺序上是没有特殊要求的,只需注意,权限修饰符始终在首位,其他的修饰符,只要在权限修饰符和类型关键字之间即可。


Ps:写的匆忙如有疑问,请喷轻点。。

1 0
原创粉丝点击