Java核心机制精讲:深入理解 static 关键字与引用传递

本节学习目标

  • 掌握关键字static的特征以及使用;
  • 掌握代码块的概念及使用;

1️⃣ static 关键字

相信大家对于 static关键字并不陌生,从第一个Java程序到现在,我们一直都可以看到这个关键字的使用(例如 public static void main(String args[]) )。在Java中,static关键字可以用于定义属性和方法。接下来将为大家详细讲解 static关键字的使用。

1.1 static定义静态属性

一个类的主要组成就是属性和方法(构造方法与普通方法),而每一个对象都分别拥有各自的属性内容(不同对象的属性保存在不同的堆内存中),如果类中的某个属性希望定义为公共属性(所有对象都可以使用的属性),则可以在声明属性前加上 static关键字。

复制代码
//	范例 1: 定义程序
class Book{
   
      
	private	String title;	//普通属性
	private double price;
	static String pub = "清华大学出版社"; 	//定义一个描述出版社信息的属性,为操作方便,暂不使用 private封装
	
	public Book(String title, double price){
   
     
		this.title = title;
		this.price = price;
	}
	public String getInfo(){
   
     
		return "图书名称:"+this.title+",价格:"+this.price+",出版社:"+this.pub;
	}
}

public class TestDemo  {
   
     
	public static void main(String args[]){
   
     
		Book ba = new Book("Java开发",10.2);   		//实例化Book 类对象
		Book bb = new Book("Android 开发",11.2);
		Book bc = new Book("Oracle开发",12.2);
		ba.pub = "北京大学出版社";               //修改了一个属性的内容
		System.out.println(ba.getInfo());
		System.out.println(bb.getInfo());
		System.out.printin(bc.getlnfo());
	}
}     

程序执行结果:

复制代码
图书名称:Java 开发,价格:10.2,出版社:北京大学出版社
图书名称:Android 开发,价格:11.2,出版社:北京大学出版社
图书名称:Oracle开发,价格:12.2,出版社: 北京大学出版社

此程序在定义 Book 类的时候提供了一个 static 属性,而这个属性将成为公共属性,也就是说有任何一个对象修改了此属性的内容都将影响其他对象。此程序在主方法中利用 ba 对象修改了 pub 属性, 所以其他两个对象的 pub 内容都发生了改变。此程序的内存关系如下图所示。

图1程序的内存关系

既然static 是一个公共属性的概念,那么如果只是简单地由一个对象去修改 static 属性的做法显然很不合适,最好的做法是由所有对象的公共代表来进行访问,这个公共代表就是类。所以利用 static 定义的属性是可以由类名称直接调用的:

复制代码
Book.pub = "北京大学出版社";

此时Book 类中定义了 pubstatic 属性(没有封装),所以可以利用 "Book" 类直接调用 staticpub 属性。

static 属性与非 static 属性还有一个最大的区别,所有的非 static 属性必须产生实例化对象才可以访问,但是 static 属性不受实例化对象的控制,也就是说,在没有实例化对象产生的情况下,依然可以直接使用 static 属性。

复制代码
//	范例 2: 在没有实例化对象产生时直接操作 static 属性
public class TestDemo{
   
     
	public static void main(String args[]){
   
     
		System.out.println(Book.pub);	//在没有实例化对象的情况下直接输出属性内容
		Book.pub = "北京大学出版社";	//修改static属性内容
		Book ba = new Book("Java开发",10.9);	//实例化Book 类对象
		System.out.println(ba.getInfo());	//输出Book 类对象信息
	}
}

程序执行结果:

复制代码
清华大学出版社
图书名称: Java开发,价格:10.9,出版社:北京大学出版社

通过本程序可以发现,在没有实例化对象产生时,依然可以直接利用 *Book 类输出或设置 static 属性的内容,并且此修改的结果将影响以后的 Book 类对象。

需要注意的是,在编写代码时,static 定义的属性出现几率并不是很高,一般只有在描述共享属性概念或者是不受实例化对象限制的属性时才会使用 static 定义属性,而大部分情况下依然都使用非static属性。

在实际应用中,选择是否使用 static属性主要取决于需求和设计考虑。

当一个属性需要被所有该类的实例对象所共享并且对整个类来说具有全局性质时,可以考虑使用 static 属性。这样的属性可以被所有实例对象访问和修改,并且只占用一份内存空间,节省了资源。典型的例子是用于计数或跟踪某个类的实例数量的属性。

而如果一个属性是独立于每个实例对象的,每个实例都应该有自己的值,那么该属性应该是非static的。每个对象对于非static属性都会拥有自己的副本,它们的取值可以相互独立。典型的例子是每个对象的名称、颜色等个性化属性。

1.2 static定义静态方法

在定义类的普通方法时可以使用 static 进行定义,很明显使用 static 定义的方法也可以在没有实例化对象产生的情况下由类名称直接进行调用。

复制代码
//	范例 3: 使用 static 定义方法
class Book  {
   
       	//描述的是同一个出版社的信息
	private String title;
	private double price;
	private static String pub = "清华大学出版社";        //定义一个描述出版社信息的属性
	
	public Book(String title, double price){
   
     
		this.title = title;
		this.price = price;
	}

	public static void setPub(String p){
   
     		//定义static方法可以由类名称直接调用
		pub = p;
	}

	public String getInfo(){
   
     
		return "图书名称:"+this.title+",价格:"+this.price+",出版社:"+this.pub;
	}
}

public class TestDemo{
   
     
	public static void main(String args[]){
   
     
		Book.setPub("北京大学出版社");		//在没有对象产生的时候进行调用
		Book ba = new Book("Java 开发",10.2);	//实例化Book 类对象
		Book bb = new Book("Android 开发",11.2); 
		Book bc = new Book("Oracle 开发",12.2);
		System.out.println(ba.getInfo());
		System.out.println(bb.getInfo());
		System.out.println(bc.getInfo());
	}
}

程序执行结果:

复制代码
图书名称:Java 开发,价格:10.2,出版社:北京大学出版社
图书名称:Android开发,价格:11.2,出版社:北京大学出版社
图书名称:Oracle开发,价格:12.2,出版社:北京大学出版社

此程序对 Book 类中的 pub 属性使用了private封装,所以专门定义了一个 setPub()static 方法,这样就可以通过类名称直接调用 setPub() 方法来修改属性内容。

从上面讲解可以发现 static 定义的属性和方法都不受实例化对象的控制,也就是说都属于独立类的功能。但是这个时候会出现一个问题:此时类中的方法就变为 static 方法和非 static 方法两组,这两组方法间的访问也将受到如下限制。

  • static 方法不能直接访问非 static 属性或方法,只能调用 static 属性或方法;
  • 非 static 方法可以访问 static的属性或方法,不受任何的限制。

之所以会存在上述操作限制,原因有以下两点。

  • 所有的非static定义的结构,必须在类已经明确产生实例化对象时才会分配堆空间,才可以使用;
  • 所有的 static定义的结构,不受实例化对象的控制,即可以在没有实例化对象的时候访问。

那么类中可以定义static方法,也可以定义非static方法,除了能用类名称直接访问外, 还有什么情况是需要考虑static方法呢?

1、 工具方法或辅助功能 :如果某个方法在整个应用程序中都具有通用的功能,并且不需要访问非静态成员(例如非静态属性),那么将该方法定义为static可以更加清晰地表示其作用和无需依赖于对象状态;
2、 纯函数 :纯函数是指在相同的输入条件下,始终产生相同的输出结果,并且不产生副作用的函数这样的方法可以被定义为静态方法,因为它们不需要依赖于任何特定的实例状态,只根据参数传递的数据进行计算;
3、 辅助工具类方法:如果一个方法提供一些通用的、与业务无关的操作,比如数据转换、校验等,而且这些方法不需要访问或修改类的实例属性,那么可以将它们定义为静态方法;

需要注意的是,在使用static方法时要小心滥用。如果方法需要访问或修改实例属性,或者需要多态性行为,那么它可能更适合定义为非静态方法,以便让每个实例都能够独立地执行方法。所以在选择是否使用 static方法时,要权衡需求、设计考虑和面向对象的原则。

复制代码
//	范例 4:定义一个数学的加法操作
class MyMath {
   
          	//数学操作类,类中没有属性
	public static int add(int x, int y){
   
      	//只是一个加法操作
		return x+y;
	}
}
public class TestDemo {
   
     
	public static void main(String args[]){
   
     
		System.out.println(MyMath.add(10, 20));//直接调用
	}
}

程序执行结果:

复制代码
30

此时MyMath 类没有属性,产生对象完全没有任何意义,所以就使用 static关键字定义方法。

1.3 主方法

Java 语言最大的特点就在于主方法,因为在 Java 中的主方法的组成单元很多。下面就概括一下前面所学知识,来介绍每一个组成单元的含义。

复制代码
public static void main(String args[]){
   
     ...}
  • public:主方法是程序的开始,所以这个方法对任何操作都一定是可见的,既然是可见的就必须使用 public (公共)。(权限修饰符这块还没介绍到,将在后面文章中详细介绍)

  • static:此方法由类名称调用;

  • void:主方法是一切执行的开始点,既然是所有的开头,就没有返回值;

  • main:是一个系统规定好的方法名称,不能修改;

  • String args[]:指的程序运行时传递的参数,格式为: "java 类名称 参数 参数..."。

    // 范例 5: 得到参数
    public class TestDemo {

    复制代码
      public static void main(String args[]){
     
       
      	for(int x=0; x<args.length; x++){
     
        //循环输出参数
      		System.out.println(args[x]);
      	}
      }

    }

程序执行输入:java TestDemo hello world xiaoshan

程序执行结果:

复制代码
hello
world  
xiaoshan

通过本程序可以发现,如果要为程序运行输入参数,只需要在执行时配置即可,不同的参数之间使用空格分隔,而如果配置的参数本身就包含空格,则可以使用"""声明,例如: java TestDemo "Hello world" "Hello everybody"

1.4 实际应用

通过之前的讲解,可以发现 static关键字具备如下特点:

  • 不管有多少个对象,都使用同一个 static 属性;
  • 使用 static方法可以避免实例化对象调用方法的限制。

下面依照这两个特点来研究 static 的实际应用。

功能一:实现类实例化对象个数的统计

希望每当实例化一个类对象时都可以打印一个信息:产生第 x 个实例化对象。

因为只要是新的实例化对象产生了,就一定会去调用类中的构造方法,所以可以在构造方法里面增加一个统计数据的操作,每当新对象产生后统计的内容就自增一个。

复制代码
//	范例 6: 实现类实例化对象个数的统计
class Book {
   
     
	private static int num=0;	//保存统计个数
	public Book(){
   
     		//只要是新对象实例化就执行此构造
		num++;			//保存个数自增
		System.out.println("这是第"+ num+" 个产生的对象!");
	}
}
	
public class TestDemo{
   
     
	public static void main(String args[]){
   
     
		new Book();
		new Book();
		new Book();
	}
}

程序执行结果:

复制代码
这是第1个产生的对象!
这是第2个产生的对象!
这是第3个产生的对象!

此程序在构造方法中进行了实例化对象个数的统计操作,所以每当调用构造方法时都会相应地进行统计个数的修改。

在本程序中构造方法主要是创建新对象时调用的,所以可以通过构造方法实现对象个数的统计,但是当某一个对象不再使用时,应该进行实例化对象个数的减少,而这一执行操作,可以通过学习后续文章中将会介绍到的 finalize() 方法后实现。

功能二:实现属性的自动设置

例如,某一个类有一个无参构造方法, 一个有参构造方法。有参构造的主要目的是传递一个 title 属性,但是希望不管调用的是无参的还是有参的构造方法,都可以为 title 设置内容(尽量不使用重复的内容设置)。

复制代码
//	范例 7: 实现属性的自动设置
class Book{
   
     
	private String title;		//title 属性
	private static int num = 0;	//自动命名索引号
	
	public Book(){
   
     	//没有设置title属性内容
		this("NOTITLE-" + num++);	//自动命名
	}
	public Book(String title){
   
     	//设置title 属性内容
		this.title = title;
	}

	public String getTitle(){
   
     
		return this.title;
	}
}

public class TestDemo {
   
     
	public static void main(String args[]){
   
     
		System.out.printIn(new Book("Java").getTitle());
		System.out.printIn(new Book().getTitle());
		System.out.println(new Book().getTitle());
	}
}

程序执行结果:

复制代码
Java
NOTITLE-0
NOTITLE-1

此程序提供了两个构造方法,如果调用有参构造方法则会将指定的内容赋值给 title 属性,而如果调用的是无参构造, title 属性会采用自动命名的方式配置,所以就算是没有设置 title 属性的内容,最终的结果也不会是 null

2️⃣ 代码块

在程序编写中可以直接使用"{}"定义一段语句,根据此部分定义的位置以及声明的关键字的不同,代码块一共可以分为4种:普通代码块、构造块、静态块、同步代码块(用于多线程编程)。

需要注意的是,代码块本身有许多破坏程序结构的操作,所以在编写实际代码的过程中,不太建议使用代码块。这里只是对这个语法结构做一个大致介绍。

2.1 普通代码块

如果一个代码块写在方法里,就称它为普通代码块。

复制代码
//	范例 8: 编写普通代码块
public class TestDemo{
   
     
	public static void main(String args[]){
   
     
		// 普通代码块
		{
   
     
			int num = 10;	//局部变量
			System.out.println("num = " + num);
		}
		int num = 100;		//全局变量
		System.out.println("num = " + num);
	}
}

程序执行结果:

复制代码
num = 10
num = 100

此程序首先在普通代码块中定义了一个 num 变量,然后在普通代码块的外部定义了一个 num 变量。由于第一个 num 变量定义在代码块中,所以是个局部变量;第二个 num 变量定义在普通代码块外,所以相对而言就属于全局变量,这两个变量彼此不会互相影响。 一般而言普通代码块可以实现较大程序的分隔,这样可以更好地避免重名变量的问题。

2.2 构造块

如果将一个代码块写在一个类里,这个代码块就称为构造块。

复制代码
//	范例 9: 定义构造块
class Book {
   
     
	public Book(){
   
     	//构造方法
		System.out.println("【A】Book类的构造方法");
	}
	//将代码块写在类里,所以为构造块 
	{
   
     
		System.out.println("【B】Book类中的构造块");
	}
}	
public class TestDemo {
   
     
	public static void main(String args[]){
   
      
		new Book();	//实例化类对象
		new Book();
	}
}

程序执行结果:

复制代码
【B】Book类中的构造块
【A】Book类的构造方法
【B】Book类中的构造块
【A】Book类的构造方法

此程序为了说明问题,特别将构造方法放在构造块之前定义,可以发现代码的结构与顺序无关。同时也可以发现,构造块在每一次实例化类对象时都会被调用,而且优于构造方法执行。

2.3 静态块

如果一个代码块使用 static 进行定义,就称其为静态块,静态块有时需要分为两种情况。

复制代码
//	范例 10: 在非主类中使用
class Book {
   
     
	public Book(){
   
     		//构造方法
		System.out.println("【A】Book类的构造方法");
	}
	//将代码块写在类里,所以为构造块 
	{
   
     
		System.out.println("【B】Book类中的构造块");
	}
	//定义静态块
	static{
   
     	
		System.out.println("【C】Book类中的静态块");
	}
}

public class TestDemo {
   
     
	public static void main(String args[]){
   
     
		new Book();	//实例化类对象
		new Book();
	}
}

程序执行结果:

复制代码
【C】Book 类中的静态块
【B】Book 类中的构造块
【A】Book 类的构造方法
【B】Book 类中的构造块
【A】Book 类的构造方法

此程序在 Book 类中增加了静态块的定义,通过运行结果可以发现当有多个实例化对象产生时,静态块会优先调用,而且只调用一次。静态块的主要作用一般可以为 static 属性初始化。

复制代码
//	范例 11: 利用静态块为 static 属性初始化
class Book{
   
     
	static String msg;	// static 属性,暂不封装
	
	public Book(){
   
     		//构造方法
		System.out.println("【A】Book类的构造方法");
	}
	//将代码块写在类里,所以为构造块
	{
   
     
		System.out.println("【B】Book类中的构造块");
	}
	static {
   
      			//定义静态块
		msg = "Hello".substring(0,2);
		System.out.println("【C】Book类中的静态块");
	}
}

public class TestDemo {
   
     
	public static void main(String args[]){
   
     
		new Book();   	//实例化类对象
		new Book();       
		System.out.println(Book.msg);
	}
}

程序执行结果:

复制代码
【C】Book 类中的静态块
【B】Book 类中的构造块
【A】Book 类的构造方法
【B】Book 类中的构造块
【A】Book 类的构造方法
He

此程序利用静态块为 static 属性进行赋值操作,但是这样做一般意义不大,所以有个了解即可。

复制代码
//	范例 12: 在主类中定义静态块
public class TestDemo {
   
     
	public static void main(String args[]){
   
     
		System.out.println("Hello World!");
	}
	static{
   
        	//静态块
		System.out.println("更多文章请访问:https://blog.csdn.net/LVSONGTAO1225");
	}
}

程序执行结果:

复制代码
更多文章请访问:https://blog.csdn.net/LVSONGTAO1225
Hello World!

通过本程序的执行可以发现,静态块将优先于主方法执行。

* 总结

本文详细介绍了Java中的static关键字以及代码块的使用。通过学习static关键字,我们了解到它可以用于定义静态属性和静态方法。静态属性是类级别的变量,在整个类中只有一份副本,并且被所有实例对象所共享。静态方法则是类级别的方法,可以通过类名直接调用,而无需创建对象实例。

同时,我们也探索了代码块的不同类型:普通代码块、构造块和静态块。普通代码块用于在方法中限定变量的作用域,构造块在创建对象时执行,用于初始化对象的特定属性,而静态块在类加载时执行,常用于初始化静态成员和执行其他预处理操作。

在实际应用中,static关键字和代码块具有很多用途。静态成员可以用来跟踪全局状态或提供公用的工具方法;静态方法可以在不创建对象实例的情况下实现某些功能;代码块则可以用来进行额外的初始化操作或执行一些特定的逻辑。

相关推荐
枫叶落雨2221 天前
ShardingSphere 介绍
java
花花鱼1 天前
Spring Security 与 Spring MVC
java·spring·mvc
言慢行善1 天前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星1 天前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟1 天前
操作系统之虚拟内存
java·服务器·网络
Tong Z1 天前
常见的限流算法和实现原理
java·开发语言
凭君语未可1 天前
Java 中的实现类是什么
java·开发语言
He少年1 天前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新1 天前
myeclipse的pojie
java·ide·myeclipse
迷藏4941 天前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构