static关键字的使用

一、知识清单

  • static:静态的

  • static可以用来修饰:属性(睁大眼是属性啊)、方法、代码块、内部类

  • 使用static修饰的属性:静态变量(或类变量)

    • 属性:按是否使用static修饰,由分为静态属性 vs 非静态属性(实例变量)
      • 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样属性值的修改。
      • 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量是,会导致其他对象调用此静态变量时,是修改过的。
    • static修饰属性的其他说明:
      • 静态变量随着类的加载而加载。可以通过“类.静态变量”的方式进行调用
      • 静态变量的加载要早于对象的创建
      • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中
        在这里插入图片描述
  • 使用static修饰方法:静态方法

    • 随着类的加载而加载,可以通过“类.静态方法”的方式进行调用
      在这里插入图片描述
    • 静态方法中,只能调用静态的属性或方法;非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
    • 对于类的实例调用属性和方法的说明:
      • 类Field既可以通过类来访问,也可以通过类的对象来访问。当通过类的对象来访问类Field时,实际上并不是访问该对象所拥有的Field,因为当系统创建该类的对象时,系统不会在为类Field分配内存,也不会再次对类Field进行初始化,也就是说,对象根本不拥有对应类的类Field。通过对象访问类Field只是一种假象,通过对象访问的依然是该类的类Field,可以这样理解:当通过对象来访问类Field时,系统会在底层转换为通过类来访问类Field。
      • 对于static关键字而言,有一条非常重要的规则:类成员(包括方法、初始化块、内部类和枚举类)不能访问实例成员(包括Field、方法、初始化块、内部类、枚举类)。因为类成员是属于类的,类成员的作用域比实例成员的作用域更大,完全可能出现类成员已经初始化完成,但实例成员还不曾初始化的情况,如果允许类成员访问实例成员将会引起大量错误。
  • static注意点:

    • 在静态的方法内,不能使用this关键字、super关键字(this和super是引用对象的,而静态方法是在类加载的时候,怎么可能会在静态中调用对象呢)。
    • 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
  • 开发中,如何确定一个属性是否要声明为static的?

    • 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
    • 类中的常量也常常声明为static
  • 开发中,如何确定一个方法是否要声明为static的?

    • 操作静态属性的方法(get/set方法来获得静态属性的值,如果使用自动生成set/get方法,默认就是静态的),通常设置为static的
    • 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections

二、static关键字

1.修饰成员变量
在我们平时的使用当中,static最常用的功能就是修饰类的属性和方法,让他们成为类的成员属性和方法,我们通常将用static修饰的成员称为类成员或者静态成员,这句话挺起来都点奇怪,其实这是相对于对象的属性和方法来说的。请看下面的例子:(未避免程序太过臃肿,暂时不管访问控制)

public class Person {
    String name;
    int age;
    
    public String toString() {
        return "Name:" + name + ", Age:" + age;
    }
    
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "zhangsan";
        p1.age = 10;
        Person p2 = new Person();
        p2.name = "lisi";
        p2.age = 12;
        System.out.println(p1);
        System.out.println(p2);
    }
    /**Output
     * Name:zhangsan, Age:10
     * Name:lisi, Age:12
     *///~
}

上面的代码我们很熟悉,根据Person构造出的每一个对象都是独立存在的,保存有自己独立的成员变量,相互不会影响,他们在内存中的示意如下:
在这里插入图片描述
从上图中可以看出,p1和p2两个变量引用的对象分别存储在内存中堆区域的不同地址中,所以他们之间相互不会干扰。但其实,在这当中,我们省略了一些重要信息,相信大家也都会想到,对象的成员属性都在这了,由每个对象自己保存,那么他们的方法呢?实际上,不论一个类创建了几个对象,他们的方法都是一样的:
在这里插入图片描述
从上面的图中我们可以看到,两个Person对象的方法实际上只是指向了同一个方法定义。这个方法定义是位于内存中的一块不变区域(由jvm划分),我们暂称它为静态存储区。这一块存储区不仅存放了方法的定义,实际上从更大的角度而言,它存放的是各种类的定义,当我们通过new来生成对象时,会根据这里定义的类的定义去创建对象。多个对象仅会对应同一个方法,这里有一个让我们充分信服的理由,那就是不管多少的对象,他们的方法总是相同的,尽管最后的输出会有所不同,但是方法总是会按照我们预想的结果去操作,即不同的对象去调用同一个方法,结果会不尽相同。

我们知道,static关键字可以修饰成员变量和方法,来让它们变成类的所属,而不是对象的所属,比如我们将Person的age属性用static进行修饰,结果会是什么样呢?请看下面的例子:

public class Person {
    String name;
    static int age;
    
    /* 其余代码不变... */

    /**Output
     * Name:zhangsan, Age:12
     * Name:lisi, Age:12
     *///~
}

我们发现,结果发生了一点变化,在给p2的age属性赋值时,干扰了p1的age属性,这是为什么呢?我们还是来看他们在内存中的示意:
在这里插入图片描述
我们发现,给age属性加了static关键字之后,Person对象就不再拥有age属性了,age属性会统一交给Person类去管理,即多个Person对象只会对应一个age属性,一个对象如果对age属性做了改变,其他的对象都会受到影响。我们看到此时的age和toString()方法一样,都是交由类去管理。

虽然我们看到static可以让对象共享属性,但是实际中我们很少这么用,也不推荐这么使用。因为这样会让该属性变得难以控制,因为它在任何地方都有可能被改变。如果我们想共享属性,一般我们会采用其他的办法:

public class Person {
    private static int count = 0;
    int id;
    String name;
    int age;
    
    public Person() {
        id = ++count;
    }
    
    public String toString() {
        return "Id:" + id + ", Name:" + name + ", Age:" + age;
    }
    
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "zhangsan";
        p1.age = 10;
        Person p2 = new Person();
        p2.name = "lisi";
        p2.age = 12;
        System.out.println(p1);
        System.out.println(p2);
    }
    /**Output
     * Id:1, Name:zhangsan, Age:10
     * Id:2, Name:lisi, Age:12
     *///~
}

上面的代码起到了给Person的对象创建一个唯一id以及记录总数的作用,其中count由static修饰,是Person类的成员属性,每次创建一个Person对象,就会使该属性自加1然后赋给对象的id属性,这样,count属性记录了创建Person对象的总数,由于count使用了private修饰,所以从类外面无法随意改变。
2.修饰成员方法
static的另一个作用,就是修饰成员方法。相比于修饰成员属性,修饰成员方法对于数据的存储上面并没有多大的变化,因为我们从上面可以看出,方法本来就是存放在类的定义当中的。static修饰成员方法最大的作用,就是可以使用"类名.方法名"的方式操作方法,避免了先要new出对象的繁琐和资源消耗,我们可能会经常在帮助类中看到它的使用:

public class PrintHelper {

    public static void print(Object o){
        System.out.println(o);
    }
    
    public static void main(String[] args) {
        PrintHelper.print("Hello world");
    }
}

上面便是一个例子(现在还不太实用),但是我们可以看到它的作用,使得static修饰的方法成为类的方法,使用时通过“类名.方法名”的方式就可以方便的使用了,相当于定义了一个全局的函数(只要导入该类所在的包即可)。不过它也有使用的局限,一个static修饰的类中,不能使用非static修饰的成员变量和方法,这很好理解,因为static修饰的方法是属于类的,如果去直接使用对象的成员变量,它会不知所措(不知该使用哪一个对象的属性)。
3.静态块
在说明static关键字的第三个用法时,我们有必要重新梳理一下一个对象的初始化过程。以下面的代码为例:

package com.dotgua.study;

class Book{
    public Book(String msg) {
        System.out.println(msg);
    }
}

public class Person {

    Book book1 = new Book("book1成员变量初始化");
    static Book book2 = new Book("static成员book2成员变量初始化");
    
    public Person(String msg) {
        System.out.println(msg);
    }
    
    Book book3 = new Book("book3成员变量初始化");
    static Book book4 = new Book("static成员book4成员变量初始化");
    
    public static void main(String[] args) {
        Person p1 = new Person("p1初始化");
    }
    /**Output
     * static成员book2成员变量初始化
     * static成员book4成员变量初始化
     * book1成员变量初始化
     * book3成员变量初始化
     * p1初始化
     *///~
}

面的例子中,Person类中组合了四个Book成员变量,两个是普通成员,两个是static修饰的类成员。我们可以看到,当我们new一个Person对象时,static修饰的成员变量首先被初始化,随后是普通成员,最后调用Person类的构造方法完成初始化。也就是说,在创建对象时,static修饰的成员会首先被初始化,而且我们还可以看到,如果有多个static修饰的成员,那么会按照他们的先后位置进行初始化。

实际上,static修饰的成员的初始化可以更早的进行,请看下面的例子:

class Book{
    public Book(String msg) {
        System.out.println(msg);
    }
}

public class Person {

    Book book1 = new Book("book1成员变量初始化");
    static Book book2 = new Book("static成员book2成员变量初始化");
    
    public Person(String msg) {
        System.out.println(msg);
    }
    
    Book book3 = new Book("book3成员变量初始化");
    static Book book4 = new Book("static成员book4成员变量初始化");
    
    public static void funStatic() {
        System.out.println("static修饰的funStatic方法");
    }
    
    public static void main(String[] args) {
        Person.funStatic();
        System.out.println("****************");
        Person p1 = new Person("p1初始化");
    }
    /**Output
     * static成员book2成员变量初始化
     * static成员book4成员变量初始化
     * static修饰的funStatic方法
     * ***************
     * book1成员变量初始化
     * book3成员变量初始化
     * p1初始化
     *///~
}

在上面的例子中我们可以发现两个有意思的地方,第一个是当我们没有创建对象,而是通过类去调用类方法时,尽管该方法没有使用到任何的类成员,类成员还是在方法调用之前就初始化了,这说明,当我们第一次去使用一个类时,就会触发该类的成员初始化。第二个是当我们使用了类方法,完成类的成员的初始化后,再new该类的对象时,static修饰的类成员没有再次初始化,这说明,static修饰的类成员,在程序运行过程中,只需要初始化一次即可,不会进行多次的初始化。

回顾了对象的初始化以后,我们再来看static的第三个作用就非常简单了,那就是当我们初始化static修饰的成员时,可以将他们统一放在一个以static开始,用花括号包裹起来的块状语句中:

class Book{
    public Book(String msg) {
        System.out.println(msg);
    }
}

public class Person {

    Book book1 = new Book("book1成员变量初始化");
    static Book book2;
    
    static {
        book2 = new Book("static成员book2成员变量初始化");
        book4 = new Book("static成员book4成员变量初始化");
    }
    
    public Person(String msg) {
        System.out.println(msg);
    }
    
    Book book3 = new Book("book3成员变量初始化");
    static Book book4;
    
    public static void funStatic() {
        System.out.println("static修饰的funStatic方法");
    }
    
    public static void main(String[] args) {
        Person.funStatic();
        System.out.println("****************");
        Person p1 = new Person("p1初始化");
    }
    /**Output
     * static成员book2成员变量初始化
     * static成员book4成员变量初始化
     * static修饰的funStatic方法
     * ***************
     * book1成员变量初始化
     * book3成员变量初始化
     * p1初始化
     *///~
}

我们将上一个例子稍微做了一下修改,可以看到,结果没有二致。
4.静态导包
相比于上面的三种用途,第四种用途可能了解的人就比较少了,但是实际上它很简单,而且在调用类方法时会更方便。以上面的“PrintHelper”的例子为例,做一下稍微的变化,即可使用静态导包带给我们的方便:

/* PrintHelper.java文件 */
package com.dotgua.study;

public class PrintHelper {

    public static void print(Object o){
        System.out.println(o);
    }
}
/* App.java文件 */

import static com.dotgua.study.PrintHelper.*;

public class App 
{
    public static void main( String[] args )
    {
        print("Hello World!");
    }
    /**Output
     * Hello World!
     *///~
}

上面的代码来自于两个java文件,其中的PrintHelper很简单,包含了一个用于打印的static方法。而在App.java文件中,我们首先将PrintHelper类导入,这里在导入时,我们使用了static关键字,而且在引入类的最后还加上了“.*”,它的作用就是将PrintHelper类中的所有类方法直接导入。不同于非static导入,采用static导入包后,在不与当前类的方法名冲突的情况下,无需使用“类名.方法名”的方法去调用类方法了,直接可以采用"方法名"去调用类方法,就好像是该类自己的方法一样使用即可。
练习

//中国人
class Chinese{
	String name;
	int age;
	static String nation;
	
	public void eat(){
		System.out.println("中国人吃午餐");
		//调用非静态结构
		this.info();
		System.out.println("name:"+name);
		//调用静态结构
		walk();
		//调用静态属性,其实底层执行的是Chinese.nation
		System.out.println("nation:"+nation);
		System.out.println("nation"+Chinese.nation);
	}
	
	public static void show(){
		System.out.println("我是一个中国人");
		//不能调用非静态的结构
//		eat();
//		name="Tom";
		//可以调用静态的结构
		System.out.println(Chinese.nation);
		walk();
	}
	
	public void info(){
		//这里面更不可能被静态方法所调用,
		//如果可以调用的话,结果会输出啥??
		System.out.println("name:"+name+",age"+age);
	}
	
	public static void walk(){
		
	}
}
public class StaticTest {
	public static void main(String[] args) {
		
		Chinese.nation="中国";
		
		Chinese c1=new Chinese();
		c1.name="姚明";
		c1.age=40;
		c1.nation="CHN";
		
		Chinese c2=new Chinese();
		c2.name="马龙";
		c2.age=30;
		c2.nation="CHINA";
		
		System.out.println(c1.nation);
		//编译不通过
//		Chinese.name="张继科";
		c1.eat();
		
		Chinese.show();
		//编译不通过
//		Chinese.eat();
//		Chinese.info();
		
	}
}

2、

public class Circle {
	
	private double radius;
	private int id;//自动赋值
	private static int total;//记录创建的圆的个数
	private static int init=1001;//static声明的属性被所有对象所共享
	
	public Circle(){
		id=init++;
		total++;
	}
	
	public Circle(double radius){
		this();
//		id=init++;
//		total++;
		this.radius=radius;
	}
	
	public double findArea(){
		return 3.14*radius*radius;
	}
	
	public double getRadius(){
		return radius;
	}
	
	public void setRadius(double radius){
		this.radius=radius;
	}
	
	public int getId(){
		return id;
	}

	//如果属性是声明为static的话,自动生成的set、get方法也是静态
	public static int getTotal() {
		return total;
	}

	//生成toString的话里面也没有static修饰的
	@Override
	public String toString() {
		return "Circle [radius=" + radius + ", id=" + id + "]";
	}
	
	
	public static void main(String[] args) {
		
		Circle c1=new Circle();
		
		Circle c2=new Circle();
		
		Circle c3=new Circle(3.4);
		Circle c4=new Circle(3.4);
		System.out.println("c1的id"+c1.getId());//c1的id1001
		System.out.println("c2的id"+c2.getId());//c2的id1002
		System.out.println("c3的id"+c3.getId());//c3的id1003
		System.out.println("创建圆的个数"+c3.total);//创建圆的个数3
	}
	
}

3、Main方法测试

/*
 * main()方法的使用说明:
 * 1.main()方法作为程序的入口
 * 2.main()方法也是一个普通的静态方法
 * 3.main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
 */
public class MainTest {
	public static void main(String[] args) {//入口
		
		Main.main(new String[100]);
		
		MainTest test=new MainTest();
		test.show();
	}

	public void show() {
		
	}
}

class Main{
	
	public static void main(String[] args){
		
		for(int i=0;i<args.length;i++){
			args[i]="全世界拼成首诗,我爱你当做最后一行"+i;
			System.out.println(args[i]);
		}
	}
}
/*
全世界拼成首诗,我爱你当做最后一行0
全世界拼成首诗,我爱你当做最后一行1
全世界拼成首诗,我爱你当做最后一行2
全世界拼成首诗,我爱你当做最后一行3
全世界拼成首诗,我爱你当做最后一行4
		.......
*/

我们看到main方法的String参数数组,可以向String数组传参来打印。其中一个类中的main方法是可以调用另一个类中的mainf方法的,如下代码:

public class MainDemo {
	public static void main(String[] args) {
		
		for(int i=0;i<args.length;i++){
			System.out.println("---"+args[i]);
		}
	}
}

第一步:先跑一遍程序
第二步:
在这里插入图片描述
第三步:
在这里插入图片描述
运行结果:
在这里插入图片描述
4、
创建一个Account

/*
 * 编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,
 * 定义封装这些属性的方法。账号要自动生成。
	编写主类,使用银行账户类,输入、输出3个储户的上述信息。
	考虑:哪些属性可以设计成static属性。
 * 
 */
public class Account {
	
	private int id;
	private String pwd="000";
	private double balance;
	
	private static double interestRate;
	private static double minMoney;
	
	private static int init=1001;//用于自动生成id使用的
	
	public Account(){
		id=init++;
	}
	
	public Account(String pwd,double balance){
//		id=init++;
		this();
		this.pwd=pwd;
		this.balance=balance;
	}

	public String getPwd() {
		return pwd;
	}

	public void setPwd(String pwd) {
		this.pwd = pwd;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

	public static double getInterestRate() {
		return interestRate;
	}

	public static void setInterestRate(double interestRate) {
		Account.interestRate = interestRate;
	}

	public static double getMinMoney() {
		return minMoney;
	}

	public static void setMinMoney(double minMoney) {
		Account.minMoney = minMoney;
	}

	public void setId(int id) {
		this.id = id;
	}

	@Override
	public String toString() {
		return "Account [id=" + id + ", pwd=" + pwd + ", balance=" + balance + "]";
	}
	
	
}

AccountTest

public class AccountTest {
	
	public static void main(String[] args) {
		
		Account acct1 =  new Account();
		Account acct2 =  new Account("qwerty",2000);
		
		Account.setInterestRate(0.012);
		Account.setMinMoney(100);
		
		System.out.println(acct1);//Account [id=1001, pwd=000, balance=0.0]
		System.out.println(acct2);//Account [id=1002, pwd=qwerty, balance=2000.0]
		
		System.out.println(acct1.getInterestRate());//0.012
		System.out.println(acct1.getMinMoney());//100.0
	}
	
}

本博客参考:
参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值