static 概述
static 是 java 语言中的关键字,表示“静态的”,它可以用来修饰变量、方法、代码块等,修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块。在 java语言中凡是用 static 修饰的都是类相关的,不需要创建对象,直接通过“类名”即可访问,即使使用“引用”去访问,在运行的时候也和堆内存当中的对象无关。
静态变量
java 中的变量包括:局部变量和成员变量,在方法体中声明的变量为局部变量,有效范围很小,只能在方法体中访问,方法结束之后局部变量内存就释放了,在内存方面局部变量存储在栈当中。在类体中定义的变量为成员变量,而成员变量又包括实例变量和静态变量,当成员变量声明时使用了 static 关键字,那么这种变量称为静态变量,没有使用 static 关键字称为实例变量,实例变量是对象级别的,每个对象的实例变量值可能不同,所以实例变量必须先创建对象,通过“引用”去访问,而静态变量访问时不需要创建对象,直接通过“类名”访问。实例变量存储在堆内存当中,静态变量存储在方法区当中。实例变量在构造方法执行过程中初始化,静态变量在类加载时初始化。代码举例
/* static: 1、static翻译为“静态” 2、所有static关键字修饰的都是类相关的,类级别的。 3、所有static修饰的,都是采用“类名.”的方式访问。 4、static修饰的变量:静态变量 5、static修饰的方法:静态方法 变量的分类: 变量根据声明的位置进行划分: 在方法体当中声明的变量叫做:局部变量。 在方法体外声明的变量叫做:成员变量。 成员变量又可以分为: 实例变量 静态变量 */ class VarTest{ // 以下实例的,都是对象相关的,访问时采用“引用.”的方式访问。需要先new对象。 // 实例相关的,必须先有对象,才能访问,可能会出现空指针异常。 // 成员变量中的实例变量 int i; // 实例方法 public void m2(){ // 局部变量 int x = 200; } // 以下静态的,都是类相关的,访问时采用“类名.”的方式访问。不需要new对象。 // 不需要对象的参与即可访问。没有空指针异常的发生。 // 成员变量中的静态变量 static int k; // 静态方法 public static void m1(){ // 局部变量 int m = 100; } }
那么变量在什么情况下会声明为静态变量呢?
- 当一个类的所有对象的某个“属性值”不会随着对象的改变而变化的时候,建议将该属性定义为静态属性(或者说把这个变量定义为静态变量),静态变量在类加载的时候初始化,存储在方法区当中,不需要创建对象,直接通过“类名”来访问。
静态变量的调用
- 静态的:建议使用“类名.”来访问,但使用“引用.”也行(不建议使用"引用.")。静态的如果使用“引用.”来访问会让程序员产生困惑:程序员以为是实例的呢。
- 空指针异常只有在什么情况下才会发生呢?只有在“空引用”访问“实例”相关的,都会出现空指针异常。
静态方法
方法在什么情况下会声明为静态的呢?
- 方法实际上描述的是行为动作,我认为当某个动作在触发的时候需要对象的参与,这个方法应该定义为实例方法,例如:每个高中生都有考试的行为,但是你考试和学霸考试最终的结果是不一样的,一个上了“家里蹲大学”,一个上了“清华大学”,显然这个动作也是需要对象参与才能完成的,所以考试这个方法应该定义为实例方法。实例成员必须要创建对象才能访问。
- 当方法体中需要直接访问当前对象的实例变量或者实例方法的时候,该方法必须定义为实例方法,因为只有实例方法中才有 this,静态方法中不存在 this。
- 在实际的开发中,“工具类”当中的方法一般定义为静态方法,因为工具类就是为了方便大家的使用,将方法定义为静态方法,比较方便调用,不需要创建对象,直接使用类名就可以访问。
被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
非静态的成员方法的访问特点
- 能访问静态的成员变量
- 能访问非静态的成员变量
- 能访问静态的成员方法
- 能访问非静态的成员方法
静态的成员方法
- 能访问静态的成员变量
- 能访问静态的成员方法
总结成一句话就是:
- 静态成员方法只能访问静态成员
静态代码块
静态代码块的语法格式是这样的:
特点
- 静态代码块在类加载时执行,并且只执行一次。
- 态代码块实际上是 java 语言为程序员准备的一个特殊的时刻,这个时刻就是类加载时刻,如果你想在类加载的时候执行一段代码,那么这段代码就有的放矢了。例如我们要在类加载的时候解析某个文件,并且要求该文件只解析一次,那么此时就可以把解析该文件的代码写到静态代码块当中了。
- 一个类当中可以编写多个静态代码块(尽管大部分情况下只编写一个),并且静态代码块遵循自上而下的顺序依次执行,所以有的时候放在类体当中的代码是有执行顺序的(大部分情况下类体当中的代码没有顺序要求,方法体当中的代码是有顺序要求的,方法体当中的代码必须遵守自上而下的顺序依次逐行执行),另外静态代码块当中的代码在 main 方法执行之前执行,这是因为静态代码块在类加载时执行,并且只执行一次。
/* 1、使用static关键字可以定义:静态代码块 2、什么是静态代码块,语法是什么? static { java语句; java语句; } 3、static静态代码块在什么时候执行呢? 类加载时执行。并且只执行一次。 静态代码块有这样的特征/特点。 4、注意:静态代码块在类加载时执行,并且在main方法执行之前执行。 5、静态代码块一般是按照自上而下的顺序执行。 6、静态代码块有啥作用,有什么用? 第一:静态代码块不是那么常用。(不是每一个类当中都要写的东西。) 第二:静态代码块这种语法机制实际上是SUN公司给我们java程序员的一个特殊的时刻/时机。 这个时机叫做:类加载时机。 具体的业务: 项目经理说了:大家注意了,所有我们编写的程序中,只要是类加载了,请记录一下 类加载的日志信息(在哪年哪月哪日几时几分几秒,哪个类加载到JVM当中了)。 思考:这些记录日志的代码写到哪里呢? 写到静态代码块当中。 */ public class StaticTest06{ // 静态代码块(特殊的时机:类加载时机。) static { System.out.println("A"); } // 一个类当中可以编写多个静态代码块 static { System.out.println("B"); } // 入口 public static void main(String[] args){ System.out.println("Hello World!"); } // 编写一个静态代码块 static{ System.out.println("C"); } } /* A B C Hello World! */
注意:
静态代码块在类加载时执行,静态变量在类加载时初始化,它们在同一时间发生,所以必然会有顺序要求,如果在静态代码块中要访问 i 变量,那么 i 变量必须放到静态代码块之前。
实例代码块
/* 1、除了静态代码块之外,还有一种语句块叫做:实例语句块 2、实例语句在类加载是并没有执行。 3、实例语句语法? { java语句; java语句; java语句; } 4、实例语句块在什么时候执行? 只要是构造方法执行,必然在构造方法执行之前,自动执行“实例语句块”中的代码。 实际上这也是SUN公司为java程序员准备一个特殊的时机,叫做对象构建时机。 */ public class InstanceCode{ //入口 public static void main(String[] args){ System.out.println("main begin"); new InstanceCode(); new InstanceCode(); new InstanceCode("abc"); new InstanceCode("xyz"); } //实例语句块 { System.out.println("实例语句块执行!"); } // Constructor public InstanceCode(){ System.out.println("无参数构造方法"); } // Constructor public InstanceCode(String name){ System.out.println("有参数的构造方法"); } }
代码的执行顺序练习题
//判断以下程序的执行顺序 public class CodeOrder{ // 静态代码块 static{ System.out.println("A"); } // 入口 // A X Y C B Z public static void main(String[] args){ System.out.println("Y"); new CodeOrder(); System.out.println("Z"); } // 构造方法 public CodeOrder(){ System.out.println("B"); } // 实例语句块 { System.out.println("C"); } // 静态代码块 static { System.out.println("X"); } }
知识框架
this 是什么
- this 可以看做一个变量,它是一个引用,存储在 Java 虚拟机堆内存的对象内部,this 这个引用保存了当前对象的内存地址指向自身,任何一个堆内存的 java 对象都有一个 this,也就是说创建 100 个 java 对象则分别对应 100 个 this。
- this 指向“当前对象”,也可以说 this 代表“当前对象”,this 可以使用在实例方法中以及构造方法中,语法格式分别为“this.”和“this(..)”。
- this不能出现在 static的方法当中,这是为什么呢?首先static 的方法,在调用的时候是不需要创建对象的,直接采用“类名”的方式调用,也就是说static 方法执行的过程中是不需要“当前对象”参与的,所以 static 的方法中不能使用 this,因为 this 代表的就是“当前对象”。
代码举例
/* this: 1、this是一个关键字,全部小写。 2、this是什么,在内存方面是怎样的? 一个对象一个this。 this是一个变量,是一个引用。this保存当前对象的内存地址,指向自身。 所以,严格意义上来说,this代表的就是“当前对象” this存储在堆内存当中对象的内部。 3、this只能使用在实例方法中。谁调用这个实例方法,this就是谁。 所以this代表的是:当前对象。 4、“this.”大部分情况下是可以省略的。 5、为什么this不能使用在静态方法中?????? this代表当前对象,静态方法中不存在当前对象。 */ public class ThisTest01{ public static void main(String[] args){ Customer c1 = new Customer("张三"); c1.shopping(); Customer c2 = new Customer("李四"); c2.shopping(); Customer.doSome(); } } // 顾客类 class Customer{ // 属性 // 实例变量(必须采用“引用.”的方式访问) String name; //构造方法 public Customer(){ } public Customer(String s){ name = s; } // 顾客购物的方法 // 实例方法 public void shopping(){ // 这里的this是谁?this是当前对象。 // c1调用shopping(),this是c1 // c2调用shopping(),this是c2 //System.out.println(this.name + "正在购物!"); // this. 是可以省略的。 // this. 省略的话,还是默认访问“当前对象”的name。 System.out.println(name + "正在购物!"); } // 静态方法 public static void doSome(){ // this代表的是当前对象,而静态方法的调用不需要对象。矛盾了。 // 错误: 无法从静态上下文中引用非静态 变量 this //System.out.println(this); } } class Student{ // 实例变量,怎么访问?必须先new对象,通过“引用.”来访问。 String name = "zhangsan"; // 静态方法 public static void m1(){ //System.out.println(name); // this代表的是当前对象。 //System.out.println(this.name); // 除非你这样 Student s = new Student(); System.out.println(s.name); } //为什么set和get方法是实例方法? public static void setName(String s){ name = s; } public String getName(){ return name; } // 什么时候方法定义为实例方法,什么时候定义为静态方法? // 如果方法中直接访问了实例变量,该方法必须是实例方法。 }
this 的使用
在实例方法中,或者构造方法中,为了区分局部变量和实例变量,这种情况下:this. 是不能省略的。
/* 1、this可以使用在实例方法中,不能使用在静态方法中。 2、this关键字大部分情况下可以省略,什么时候不能省略呢? 在实例方法中,或者构造方法中,为了区分局部变量和实例变量, 这种情况下:this. 是不能省略的。 */ public class ThisTest03{ public static void main(String[] args){ Student s = new Student(); s.setNo(111); s.setName("张三"); System.out.println("学号:" + s.getNo()); System.out.println("姓名:" + s.getName()); Student s2 = new Student(2222, "李四"); System.out.println("学号:" + s2.getNo()); System.out.println("姓名:" + s2.getName()); } } // 学生类 class Student{ //学号 private int no; //姓名 private String name; //构造方法无参 public Student(){ } // 上面的有参构造方法也增强以下可读性 public Student(int no, String name){ this.no = no; this.name = name; } public void setNo(int no){ //no是局部变量 //this.no 是指的实例变量。 this.no = no; // this. 的作用是:区分局部变量和实例变量。 } public int getNo(){ return no; //return this.no; } public void setName(String name){ this.name = name; } public String getName(){ // getName实际上获取的是“当前对象”的名字。 //return this.name; // 严格来说,这里是有一个 this. 的。只不过这个 this. 是可以省略的。 return name; } }
this 还有另外一种用法,使用在构造方法第一行(只能出现在第一行,这是规定,记住就行),通过当前构造方法调用本类当中其它的构造方法,其目的是为了代码复用。调用时的语法格式是:this(实际参数列表)
/* 1、this除了可以使用在实例方法中,还可以用在构造方法中。 2、新语法:通过当前的构造方法去调用另一个本类的构造方法,可以使用以下语法格式: this(实际参数列表); 通过一个构造方法1去调用构造方法2,可以做到代码复用。 但需要注意的是:“构造方法1”和“构造方法2” 都是在同一个类当中。 3、this() 这个语法作用是什么? 代码复用。 4、死记硬背: 对于this()的调用只能出现在构造方法的第一行。 */ public class ThisTest04{ public static void main(String[] args){ // 调用无参数构造方法 Date d1 = new Date(); d1.detail(); // 调用有参数构造方法 Date d2 = new Date(2008, 8, 8); d2.detail(); } } /* 需求: 1、定义一个日期类,可以表示年月日信息。 2、需求中要求: 如果调用无参数构造方法,默认创建的日期为:1970年1月1日。 当然,除了调用无参数构造方法之外,也可以调用有参数的构造方法来创建日期对象。 */ class Date{ // 以后写代码都要封装,属性私有化,对外提供setter and getter //年 private int year; //月 private int month; //日 private int day; // 构造方法无参 // 调用无参数构造方法,初始化的日期是固定值。 public Date(){ //错误: 对this的调用必须是构造器中的第一个语句 //System.out.println(11); this(1970, 1, 1); } // 构造方法有参数 public Date(int year, int month, int day){ this.year = year; this.month = month; this.day = day; } // 提供一个可以打印日期的方法 public void detail(){ //System.out.println(year + "年" + month + "月" + day + "日"); System.out.println(this.year + "年" + this.month + "月" + this.day + "日"); } //setter and getter public void setYear(int year){ // 设立关卡(有时间可以设立关卡) this.year = year; } public int getYear(){ return year; } public void setMonth(int month){ // 设立关卡(有时间可以设立关卡) this.month = month; } public int getMonth(){ return month; } public void setDay(int day){ // 设立关卡(有时间可以设立关卡) this.day = day; } public int getDay(){ return day; } }