文章目录
一、知识清单
-
static:静态的
-
static可以用来修饰:属性(睁大眼是属性啊)、方法、代码块、内部类
-
使用static修饰的属性:静态变量(或类变量)
- 属性:按是否使用static修饰,由分为静态属性 vs 非静态属性(实例变量)
- 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样属性值的修改。
- 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量是,会导致其他对象调用此静态变量时,是修改过的。
- static修饰属性的其他说明:
- 静态变量随着类的加载而加载。可以通过“类.静态变量”的方式进行调用
- 静态变量的加载要早于对象的创建
- 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中
- 属性:按是否使用static修饰,由分为静态属性 vs 非静态属性(实例变量)
-
使用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
}
}
本博客参考:
参考