static 关键字
static关键字是我们从”hello world“开始,就频繁使用的一个关键字
例如我们的main方法,例如我们一直在写的static方法
- 但是我们经常用,却不知道为什么这么用
- 那么今天就正式学习static关键字
- 引例
练习:
创建一个学生类,用来描述我们班全体同学,要求
属性:姓名,性别,年龄,学号,学校信息
行为:吃饭,学习
- 建议性别用gender,禁止使用布尔类型的isMale,isFemale这种形式
- 创建多个学生对象,想一想会有什么问题?
对于我们班级的同学来说,学校名字都是固定的“训练营”
我们虽然知道这一点,但是我们还是要,不厌其烦的在每次创建对象的时候,给schoolName赋值
这显然是没有必要的,浪费时间的同时也浪费内存空间
-
怎么改进这个设计?
- 可以把成员变量schoolName中直接初始化赋值为“训练营”,这样可以实现功能
- 但是每个对象中都存有一个成员变量,浪费空间
- 最重要的,我们在设计类的时候其实已经知道这个属性,是全体对象都有的
- 我们希望这个属性不是属于某一个对象的,而是一种类的特征,是属于类的
-
上面这个改进不是我们最希望的样子
- 那么能不能在内存中找一块区域,每个对象都共用这片区域,把schoolName放进去?
- 这样做既节省时间,又节省空间
- 最重要的体现了该属性属于类——只要是这个类的对象都该有这个属性
在JVM内存的设计当中,确实在方法区中开辟了一块单独的空间,称之为静态区
用于存放这些 “所有对象共享,整个类的对象都只有一份的数据”
在Java当中把存放在这个区域的成员,称之为静态成员,包括静态成员变量和静态成员方法
语法:
1,成员变量在数据类型前加static,静态成员变量或者简称静态变量
2,成员方法在返回值类型前加static,是为静态成员方法或者简称静态方法
- 修改一下schoolName为static修饰,验证一下该成员是否成为所有对象共享
public class Demo {
public static void main(String[] args) {
//创建几个学生类对象
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
/*s1.schoolName = "训练营";
s2.schoolName = "训练营";
s3.schoolName = "训练营";*/
//写了很多重复代码,怎么改进?
/*不推荐的访问方式
System.out.println(s1.schoolName);
System.out.println(s2.schoolName);
System.out.println(s3.schoolName);*/
System.out.println(Student.schoolName);
//Static member 'com.cskaoyan.javase.oop.statickeyword.introduction.Student.schoolName' accessed via instance reference
//被访问 通过 实例=对象 创建对象的过程也被称之为实例化 引用
//警告:一个静态的成员通过一个对象引用去访问了
//所以不应该用对象访问静态成员
System.out.println("-----------------------------");
Student.schoolName = "C++训练营";
/*
不推荐的访问方式
System.out.println(s1.schoolName);
System.out.println(s2.schoolName);
System.out.println(s3.schoolName);*/
System.out.println(Student.schoolName);
}
}
class Student {
//定义属性 姓名
String name;
//定义性别
String gender; //sex可以表示性别,开发中使用gender
int age;
int id;
//String schoolName = "训练营";
static String schoolName = "Java训练营";
//定义行为,成员方法
//eat
public void eat() {
System.out.println("吃饭的时候也是要想着学习!");
}
//学习
public void study() {
System.out.println("疯狂的学习");
}
//睡觉
public void sleep() {
System.out.println("梦中学习大法");
}
public Student() {
}
}
Java训练营
-----------------------------
C++训练营
- 很容易就可以验证出这一结果,那么接下来,我们结合我们的内存图去深入的学习static关键字
接下来我们通过一个案例来深入了解static关键字
宠物公司新进了一批同样颜色的宠物,创建一个类描述这批宠物
- 属性:颜色,性别
- 行为:(static)跑,跳
public class Demo {
public static void main(String[] args) {
//创建Rabbit对象
Rabbit r = new Rabbit();
r.jump();//对于没有用static关键字的成员方法,必须new一个对象来调用
Rabbit.run();//对于有static关键字的成员方法,可用类直接调用
}
}
class Rabbit{
//定义属性
static char color;
String gender; //isMale/isFemale的布尔类型不要使用
//定义行为
public static void run(){
System.out.println("和乌龟一起跑!");
}
public void jump(){
System.out.println("蹦蹦跳跳真可爱!");
}
}
输出:
蹦蹦跳跳真可爱!
和乌龟一起跑!
r.jump();//对于没有用static关键字的成员方法,必须new一个对象来调用
这种访问方式不推荐
> 总结static
- static修饰成员,称之为静态成员,包括静态成员变量和静态成员方法
- 随着类加载完毕,静态成员就存在,并且能够使用了
- 静态成员变量在类加载过程中完成初始化,并且具有默认值
- 静态成员方法的二进制指令集合在类加载过程也准备完毕,可以调用了
- 静态成员领先于对象存在,不依赖于对象而存在
- 静态成员变量在创建对象之前就已经创建了,只有一份且被类所有对象共享
- 静态方法被所有类对象共享,且无需创建对象就能调用
- 仍然可以创建对象去调用静态成员,但是规范的Java代码只建议通过类名直接访问
- 静态成员变量:类名.变量名
- 静态成员方法:类名.方法名
- 对象调用的方式,好像该成员属于对象,但显然静态成员属于全体对象,属于类
- 在很多书籍博客中,由于静态成员的随着类加载而存在的特点
- 静态成员是该类全体对象共有,属于类
- 所以也称类成员,类属性,类变量,类方法
- 知道这种称呼即可,但是大家还是习惯叫静态成员
>比较静态成员变量和普通成员变量
- 所属不同
- 静态成员变量属于类,所以也称为为类变量
- (普通)成员变量属于对象,所以也称为对象变量(对象变量)
- 在内存中的位置不同
- 静态变量存储于方法区的静态区,被所有对象共享
- 成员变量存储于堆内存,每个对象独享自己的成员变量
- 在内存中出现时间不同
- 静态变量随着类的加载而加载,比成员变量出现的要早
- 成员变量随着对象的创建而存在
- 调用不同
- 静态变量可以通过类名调用,也可以通过对象调用(不推荐/ 不合理的方式)
- 成员变量只能通过对象名调用,必须创建对象
静态成员方法和普通成员方法的区别和上述类似
> 那么我们使用static的场景是什么?
- static的使用场景
- 静态成员变量:当存在需要所有对象共享的变量时,应该使用static
- 静态成员方法:当不需要对象只是需要便捷的调方法时,使用static,广泛应用于工具类中,方便访问调用
- 数组工具类
//使用工具方法
public class Tools {
public static void main(String[] args) {
int[] a = {2, 7, 8, 5, 43, 3, 76, 8, 4};
int max = ArrayTool.max(a);
ArrayTool.max(a);
ArrayTool.min(a);
System.out.println("最大值为:" + max);
int min = ArrayTool.min(a);
System.out.println("最小值为:" + min);
int firstindex = ArrayTool.find(a, 3);
System.out.println("找到第一个位置为:" + firstindex);
int endindex = ArrayTool.findEnd(a, 8);
System.out.println("找到最后一个位置为;" + endindex);
ArrayTool.reverse(a);
System.out.println("逆转后排序:");
System.out.println(Arrays.toString(a));
}
}
class ArrayTool {
public static int max(int[] a) {
int max = a[0];
for (int i = 0; i < a.length; i++) {
if (max < a[i]) {
max = a[i];
}
}
return max;
}
public static int min(int[] a) {
int min = a[0];
for (int i = 0; i < a.length; i++) {
if (min > a[i]) {
min = a[i];
}
}
return min;
}
//逆值数组元素
public static void reverse(int[] a) {
int temp;
for (int i = 0; i < a.length / 2; i++) {
temp = a[i];
a[i] = a[a.length - i - 1];
a[a.length - i - 1] = temp;
}
}
//查表(第一个元素出现的位置)
public static int find(int a[], int value) {
for (int i = 0; i < a.length; i++) {
if (a[i] == value) {
return i;
}
}
return -1;
}
//查表(最后一个元素出现的位置)
public static int findEnd(int a[], int value) {
for (int i = a.length - 1; i >= 0; i--) {
if (a[i] == value) {
return i;
}
}
return -1;
}
}
输出:
最大值为:76
最小值为:2
找到第一个位置为:5
找到最后一个位置为;7
逆转后排序:
[4, 8, 76, 3, 43, 5, 8, 7, 2]
> 注意事项:
- 一个类中,静态方法无法直接调用非静态的方法和属性,也不能使用this,super关键字
- 经典错误:Non-static field/method xxx cannot be referenced from a static context
- 原因:静态方法调用的时候,可能还没有对象,直接访问属于对象的成员变量和成员方法显然不合适
- 反过来,一个非静态的方法,可以访问静态的成员
- 因为有对象的时候,一定有静态成员
- 建议采用这种访问形式的时候,使用类名.变量名的形式访问,以示区别,增加代码可读性
- 只存在静态成员变量,不存在“静态局部变量”
- 局部变量只有在调用的时候才有意义
- 而静态变量在类加载时就初始化,就存在了
- 如果我一直不调用这个方法,这个“静态局部变量”就一直占着空间,没有意义
- 静态方法是类所有,那么静态方法的局部变量就也是类所有,为什么静态方法中也不能有静态局部变量?
- 局部变量一定是方法所有
- 静态方法也是方法,不调用其中的局部变量也没意义