前面介绍嵌套类的时候讲到了关键字static,用static修饰类,该类就变成了嵌套类。从嵌套类的用法可知,其它地方访问嵌套类之时,无需动态创建外层类的实例,直接创建嵌套类的实例就行。
其实static不光修饰类,还能用来修饰方法、修饰属性等等,例如大家学习Java一开始就遇到的main方法,便为static所修饰。当一个成员方法被static修饰之后,该方法就成为静态方法;当一个成员属性被static修饰之后,该属性就成为静态属性。静态方法和静态属性,它俩同嵌套类一样不依赖于所在类的实例。外部若要访问某个类的静态方法,只需通过“类名.静态方法名”即可;同理,通过“类名.静态属性名”就能访问该类的静态属性。由于静态方法和静态属性拥有独立调用的特性,因此它们常常出现在一些通用的工具场景,例如系统的数学函数库Math,便提供了大量的静态方法和静态属性。其中常见的静态方法包括四舍五入函数Math.round、取绝对值函数math.abs、求平方根函数Math.sqrt等,常见的静态属性则有圆周率近似值Math.PI等。
那么开发者自己定义一个新类,如何得知哪些属性需要声明为静态属性,哪些方法需要声明为静态方法呢?在多数情况下,静态属性的取值一般要求是固定不变的,而静态方法只允许对输入参数进行加工,不允许操作其它的成员变量(静态属性除外)。以树木类为例,凡是会动态变化着的性状与事情,显然不适合声明为静态成员;只有与生长过程无关的概念,才适合声明为静态成员。譬如树木可分为乔木与灌木两大类,可想而知乔木与灌木的类型取值,与每棵树木的生长情况没有关联,这两种树木类型就适合作为静态属性。根据树木的类型,推断该树木的类型名称是“乔木”还是“灌木”,这个类型名称的判断方法就适合作为静态方法。如此一来,Tree类便可添加下面的静态成员声明代码:

	// static的字面意思是“静态的”,意味着无需动态创建即可直接使用。
// 利用static修饰成员属性,外部即可通过“类名.属性名”直接访问静态属性。
public static int TYPE_ARBOR = 1;
public static int TYPE_BUSH = 2; // 利用static修饰成员方法,外部即可通过“类名.方法名”直接访问静态方法。
public static String getTypeName(int type) {
String type_name = "";
if (type == TYPE_ARBOR) {
type_name = "乔木";
} else if (type == TYPE_BUSH) {
type_name = "灌木";
}
return type_name;
}

外部访问树木类的静态成员,只要按照“类名.静态成员名”的格式就好,具体的调用代码如下所示:

	// 演示静态成员的调用方式
private static void testStaticMember() {
// 使用静态属性无需创建该类的实例,只要通过“类名.静态属性名”即可访问静态属性
System.out.println("类型TYPE_ARBOR的取值为"+TreeStatic.TYPE_ARBOR);
System.out.println("类型TYPE_BUSH的取值为"+TreeStatic.TYPE_BUSH);
// 使用静态方法无需创建该类的实例,只要通过“类名.静态方法名”即可访问静态方法
String arbor_name = TreeStatic.getTypeName(TreeStatic.TYPE_ARBOR);
System.out.println("类型TYPE_ARBOR对应的名称是"+arbor_name);
String bush_name = TreeStatic.getTypeName(TreeStatic.TYPE_BUSH);
System.out.println("类型TYPE_BUSH对应的名称是"+bush_name);
}

神通广大的static不仅可以修饰类、属性、方法,它居然还能修饰一段代码块!被static修饰的代码段样例如下:

	static {
// 这里是被static修饰的代码段内容
}

以上为static所包裹的代码段,又被称作“静态代码块”,其作用是在系统加载该类之时立即执行这部分代码。因为此处的代码被static包括,所以静态代码块内部只能操作同类的静态属性和静态方法,而不能操作普通的成员属性和成员方法。可是这里有个问题,早先提到构造方法才是创建实例之时的初始操作,那么静态代码块与构造方法比起来,它们的执行顺序孰先孰后?倘若从Java的运行机制来解答该问题,不但费口舌而且伤脑筋,都说实践出真知,接下来不如做个实验,看看它们究竟是怎样的先来后到。
首先在树木类中声明一个静态的整型变量leaf_count,之所以添加static修饰符,是因为要给静态代码块使用;接着在静态代码块内部对该变量做自增操作,并将变量值打印到日志;同时在树木类的构造方法里面也进行leaf_count的自增运算,以及往控制台输出它的变量值。修改后的相关代码片段示例如下:

	// 叶子数量,用来演示构造方法与初始静态代码块的执行顺序
public static int leaf_count = 0; // static还能用来包裹某个代码块,一旦当前类加载进内存,静态代码块就立即执行
static {
leaf_count++;
System.out.println("这里是初始的静态代码块,此时叶子数量为"+leaf_count);
} public TreeStatic(String tree_name) {
this.tree_name = tree_name;
leaf_count++;
System.out.println("这里是构造方法,此时叶子数量为"+leaf_count);
}

最后回到外部创建该树木类的新实例,对应代码如下所示:

	// 演示静态代码块与构造方法的执行顺序
private static void testStaticBlock() {
System.out.println("开始创建树木类的实例");
TreeStatic tree = new TreeStatic("月桂");
System.out.println("结束创建树木类的实例");
}

运行以上的演示代码,观察到下列的日志信息:

这里是初始的静态代码块,此时叶子数量为1
开始创建树木类的实例
这里是构造方法,此时叶子数量为2
结束创建树木类的实例

从日志结果可见,静态代码块的内部代码早早就得到执行了,而构造方法的内部代码要等到外部调用new的时候才会执行,这证明了静态代码块的执行时机确实先于该类的构造方法。

静态修饰符一边给开发者带来了便利,一边也带来了不大不小的困惑。为了说明问题的迷惑性,接下来照例做个代码实验。仍旧在树木类中先声明一个静态的整型变量annual_ring,再补充一个成员方法grow,该方法内部对annual_ring自增的同时也打印日志。依据上述步骤给树木类新增了如下代码:

	// 树木年轮,用来演示静态属性的持久性
public static int annual_ring = 0; // 注意每次读取静态属性,得到的都是该属性最近一次的数值
public void grow() {
annual_ring++;
System.out.println(tree_name+"的树龄为"+annual_ring);
}

然后其它地方先后创建这个树木类的两个实例,就像下面代码示范的那样:

	// 演示静态属性的持久性
private static void testStaticProperty() {
TreeStatic bigTree = new TreeStatic("大树");
bigTree.grow();
TreeStatic littleTree = new TreeStatic("小树");
littleTree.grow();
}

继续运行上面的测试代码,发现打印的日志如下:

这里是构造方法,此时叶子数量为3
大树的树龄为1
这里是构造方法,此时叶子数量为4
小树的树龄为2

虽然bigTree和littleTree是新创建的实例,但是从日志结果看它们的annual_ring数值竟然是递增的,这可真是咄咄怪事,两个实例分明都是通过new出来的呀!产生怪异现象的罪魁祸首,原来就是static这个始作俑者,凡是被static修饰的静态变量,它在内存中占据了一块固定的区域,不管所在类被创建了多少个实例,每个实例引用的静态变量依然是最初分配的那个。于是后面创建的树木实例littleTree,其内部的annual_ring与之前实例bigTree的annual_ring保持一致,无怪乎前后两实例的annual_ring数值是依序递增的了。
由此可见,静态属性总是保存最后一次的数值,倘若它的取值每次都发生变化,即使创建新实例也得不到静态属性最初的数值。这种后果显而易见违背了静态变量的设计初衷,在多数时候,开发者定义一个静态属性,原本是想作为取值不变的常量使用,而不希望它变来变去。对于此类用于常量定义的静态属性,可以在static前头再添加修饰符final,表示该属性只允许赋值一次,从而避免了多次赋值导致取值更改的尴尬。下面是联合修饰final和static的属性定义代码例子:

	// 若想静态属性始终如一保持不变,就得给该属性添加final修饰符,表示终态属性只能被赋值一次
public final static int FINAL_TYPE_ARBOR = 1;
public final static int FINAL_TYPE_BUSH = 2;

  

更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(五十五)关键字static的用法的更多相关文章

  1. Java开发笔记(十五)短路逻辑运算的优势

    前面提到逻辑运算只能操作布尔变量,这其实是不严谨的,因为经过Java编程实现,会发现“&”.“|”.“^”这几个逻辑符号竟然可以对数字进行运算.譬如下面的代码就直接对数字分别开展了“与”.“或 ...

  2. Java开发笔记(一百五十)C3P0连接池的用法

    JDBC既制定统一标准兼容了多种数据库,又利用预报告堵上了SQL注入漏洞,照理说已经很完善了,可是人算不如天算,它在性能方面不尽如人意.问题出在数据库连接的管理上,按照正常流程,每次操作完数据库,都要 ...

  3. Java开发笔记(九十五)NIO配套的文件工具Files

    NIO不但引进了高效的文件通道,而且新增了更加好用的文件工具家族,包括路径组工具Paths.路径工具Path.文件组工具Files.先看路径组工具Paths,该工具提供了静态方法get,输入某个文件的 ...

  4. Java开发笔记(一百五十一)Druid连接池的用法

    C3P0连接池自诞生以来在Java Web领域反响甚好,业已成为hibenate框架推荐的连接池.谁知人红是非多,C3P0在大型应用场合中暴露了越来越多的局限性,包括但不限于下列几点:1.C3P0管理 ...

  5. Java开发学习(二十五)----使用PostMan完成不同类型参数传递

    一.请求参数 请求路径设置好后,只要确保页面发送请求地址和后台Controller类中配置的路径一致,就可以接收到前端的请求,接收到请求后,如何接收页面传递的参数? 关于请求参数的传递与接收是和请求方 ...

  6. 【Java学习笔记之十五】Java中的static关键字解析

    Java中的static关键字解析 static关键字是很多朋友在编写代码和阅读代码时碰到的比较难以理解的一个关键字,也是各大公司的面试官喜欢在面试时问到的知识点之一.下面就先讲述一下static关键 ...

  7. Java基础笔记(十五)——封装(续)static关键字

    static 静态的,用static修饰的成员叫静态成员或类成员.类实例化的所有对象都会共用同一块静态空间.一个对象将值改变,其它对象的值也就随之改变了. 如:public static int pr ...

  8. Java学习笔记(十五):import关键字

  9. Java学习笔记(十五)——javadoc学习笔记和可能的注意细节

    [前面的话] 这次开发项目使用jenkins做持续集成,PMD检查代码,Junit做单元测试,还会自动发邮件通知编译情况,会将javadoc生成的文档自动发到一个专门的服务器上面,每个人都可以看,所以 ...

  10. Java学习笔记二十五:Java面向对象的三大特性之多态

    Java面向对象的三大特性之多态 一:什么是多态: 多态是同一个行为具有多个不同表现形式或形态的能力. 多态就是同一个接口,使用不同的实例而执行不同操作. 多态性是对象多种表现形式的体现. 现实中,比 ...

随机推荐

  1. eclipse上的maven,添加依赖后无法自动下载相应的jar包

    报错信息: Failed to read artifact descriptor for org.quartz-scheduler:quartz-jobs:jar:2.2.3  org.eclipse ...

  2. autpmapper映射忽略某个属性

    1.直接加特性[IgnoreMap] 2.映射规则 CreateMap<BaseAccount, BaseAccountListDto>().ForMember(dest => de ...

  3. Vue取消eslint语法限制

    话不多说,先上图: 当然,这里的警告我是知道怎么回事,原来eslint是一个语法检查工具,但是限制很严格,在我的vue文件里面很多空格都会导致红线警告(可以屏蔽),虽然可以屏蔽,但是在编译的时候老是会 ...

  4. 详解AMD规范及具体实现requireJS在工程中的使用

    前面的话 由CommonJS组织提出了许多新的JavaScript架构方案和标准,希望能为前端开发提供统一的指引.AMD规范就是其中比较著名一个,全称是Asynchronous Module Defi ...

  5. Jenkins(Docker容器内)使用宿主机的docker命令

    1.Jenkins镜像 Docker容器内的Jenkins使用容器外宿主机的Docker(即DooD,还有另外的情况就是DioD),google一下有几种说法,但是都没试成功(试过一种就是修改宿主机/ ...

  6. iOS URL Schemes与漏洞的碰撞组合

    iOS URL Schemes与漏洞的碰撞组合 前言 iOS URL Schemes,这个单词对于大多数人来说可能有些陌生,但是类似下面这张图的提示大部分人应该都经常看见: 今天要探究的就是:了解iO ...

  7. git克隆项目出现remote: HTTP Basic: Access denied

    换新电脑,重新装了git,从gitlab上面拉公司项目,出现了remote: HTTP Basic: Access denied错误,说验证失败,百度很多说了很多答案,最后试了这种可以,成功拉下来项目 ...

  8. [Swift]LeetCode157.用Read4来读取N个字符 $ Read N Characters Given Read4

    The API: int read4(char *buf) reads 4 characters at a time from a file.The return value is the actua ...

  9. [Swift]LeetCode897. 递增顺序查找树 | Increasing Order Search Tree

    Given a tree, rearrange the tree in in-order so that the leftmost node in the tree is now the root o ...

  10. spark使用udf给dataFrame新增列

    在 spark 中给 dataframe 增加一列的方法一般使用 withColumn // 新建一个dataFrame val sparkconf = new SparkConf() .setMas ...