前面介绍嵌套类的时候讲到了关键字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. nginx连接数优化

    一.一般来说nginx 配置文件中对优化比较有作用的为以下几项: 1. worker_processes 8; nginx 进程数,建议按照cpu 数目来指定,一般为它的倍数 (如,2个四核的cpu计 ...

  2. window server 2008 安装Oracle10g

    oracle安装都大同小异. 开始安装步骤 输入完之后点击下一步 这时候稍等一会儿. 这时候也要稍等一会儿. 直接安装. 设置口令管理,设置SCOTT的密码为tiger就好了. 这时候稍等一会儿. o ...

  3. truffle unbox react 出坑指南

    最近几天差点就被这鬼东西给逼疯了,truffle init .truffle unbox webpack 不管我怎么运行都是对的,唯独truffle unbox react 不管在哪个windows都 ...

  4. Java面试题:Hibernate的二级缓存与Hibernate多表查询

    我们来看两个有关Java框架之Hibernate的面试题,这是关于Hibernate的常考知识点. 1.请介绍一下Hibernate的二级缓存 解题按照以下思路来回答: (1)首先说清楚什么是缓存: ...

  5. FFmpeg 学习(四):FFmpeg API 介绍与通用 API 分析

    一.FFmpeg 相关术语 1. 容器/文件(Container/File):即特定格式的多媒体文件,比如MP4,flv,mov等. 2. 媒体流(Stream):表示在时间轴上的一段连续的数据,比如 ...

  6. 微信小程序快速开发上手

    微信小程序快速开发上手 介绍: 从实战开发角度,完整系统地介绍了小程序的开发环境.小程序的结构.小程序的组件与小程序的API,并提供了多个开发实例帮助读者快速掌握小程序的开发技能,并能自己动手开发出小 ...

  7. Kubernetes 笔记 06 豌豆荚之旅(一)

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. Hi,大家好, ...

  8. 【机器学习】--Kmeans从初识到应用

    一.前述 Kmeans算法一般在数据分析前期使用,选取适当的k,将数据分类后,然后分类研究不同聚类下数据的特点. Kmeans算法是一种无监督的算法. 常用于分组,比如用户偏好. 二.概念及原理 Km ...

  9. AspNetCoreMvc使用MongoDB,快来get一下吧。

    看这篇文章之前请耐心看完MongoDb入门,如果还是坚持不看,那我也没有办法. MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. ...

  10. asp.net core系列 32 EF查询数据 必备知识(1)

    一.查询的工作原理 Entity Framework Core 使用语言集成查询 (LINQ) 来查询数据库中的数据. 通过 LINQ 可使用 C#(或你选择的其他 .NET 语言)基于派生上下文和实 ...