太原企业网站模板建站,上海嘉定区网站建设,高端品牌车,东西湖建设局网站文章目录 1.封装#xff08;掌握#xff09;1.1 封装的理解1.2 不封装存在的问题1.3 怎么封装1.4 难点解惑1.5 练习 2. this 和 static2.1 this#xff08;掌握#xff09;2.1.1 this是什么2.1.2 this 在实例方法中使用2.1.3 this访问实例变量2.1.4 this扩展①2.1.5 this扩… 文章目录 1.封装掌握1.1 封装的理解1.2 不封装存在的问题1.3 怎么封装1.4 难点解惑1.5 练习 2. this 和 static2.1 this掌握2.1.1 this是什么2.1.2 this 在实例方法中使用2.1.3 this访问实例变量2.1.4 this扩展①2.1.5 this扩展②2.1.6 this总结2.1.7 this在构造方法中使用 2.2 static掌握2.2.1 static概述2.2.2 静态变量2.2.2.1 静态变量使用“引用”访问2.2.2.2 静态方法使用“引用”访问 2.2.3 静态代码块2.2.3.1 语法格式与介绍2.2.3.2 静态代码块中访问静态变量 2.2.4 静态方法 2.3 难点解惑2.4 练习2.4.1 选择题2.4.2 判断下面代码的输出结果并说明原因2.4.3 找出下面代码的错误并说明为什么错了 1.封装掌握
1.1 封装的理解
封装从字面上来理解就是包装的意思专业点就是信息隐藏是指利用抽象数据类型将数据和基于数据的操作封装在一起使其构成一个不可分割的独立实体数据被保护在抽象数据类型的内部尽可能地隐藏内部的细节只保留一些对外接口使之与外部发生联系。 系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节但可以通过该对象对外提供的接口来访问该对象。 现实世界事物封装举例
“鼠标”外部有一个壳将内部的原件封装起来至于鼠标内部的细节是什么我们不需要关心只需要知道鼠标对外提供了左键、右键、滚动滑轮这三个简单的操作。对于用户来说只要知道左键、右键、滚动滑轮都能完成什么功能就行了。为什么鼠标内部的原件要在外部包装一个“壳”呢起码内部的原件是安全的。“数码相机”外部也有一个壳将内部复杂的结构包装起来对 外提供简单的按键这样每个人都可以很快的学会照相了因为它的按键很简单另外照相机内部精密的原件也受到了壳儿的保护不容易坏掉。
封装的好处 封装之后就形成了独立实体独立实体可以在不同的环境中重复使用显然封装可以降低程序的耦合度提高程序的扩展性以及重用性或复用性例如“鼠标”可以在 A 电脑上使用也可以在 B 电脑上使用。另外封装可以隐藏内部实现细节站在对象外部是看不到内部复杂结构的对外只提供了简单的安全的操作入口所以封装之后实体更安全了。
1.2 不封装存在的问题
如下代码
class MobilePhone {//电压手机正常电压在 3~5Vdouble voltage;
}
public class MobilePhoneTest {public static void main(String[] args) {MobilePhone phone new MobilePhone();phone.voltage 3.7;System.out.println(手机电压 phone.voltage);phone.voltage 100;System.out.println(手机电压 phone.voltage);}
}
以上程序 MobilePhone 类未进行封装其中的电压属性 voltage 对外暴露在外部程序当中可以对 MobilePhone 对象的电压 voltage 属性进行随意访问导致了它的不安全例如手机的正常电压是 3~5V但是以上程序已经将手机电压设置为 100V这个时候显然是要出问题的但这个程序编译以及运行仍然是正常的没有出现任何问题这是不对的。
1.3 怎么封装
为了保证内部数据的安全这个时候就需要进行封装了封装的第一步就是将应该隐藏的数据隐藏起来起码在外部是无法随意访问这些数据的可以使用 java 语言中的 private 修饰符private 修饰的数据表示私有的私有的数据只能在本类当中访问。
class MobilePhone {//电压手机正常电压在 3~5Vprivate double voltage;
}
public class MobilePhoneTest {public static void main(String[] args) {MobilePhone phone new MobilePhone();phone.voltage 3.7;System.out.println(手机电压 phone.voltage);phone.voltage 100;System.out.println(手机电压 phone.voltage);}
}直接编译报错 通过以上的测试手机对象的电压属性确实受到了保护在外部程序中无法访问了。但从 当前情况来看voltage 属性有点儿过于安全了一个对象的属性无法被外部程序访问自然这 个数据就没有存在的价值了。所以这个时候就需要进入封装的第二步了对外提供公开的访问入口让外部程序统一通过这个入口去访问数据我们可以在这个入口处设立关卡进行安全 控制这样对象内部的数据就安全了。 对于“一个”属性来说我们对外应该提供几个访问入口呢通常情况下我们访问对象的 某个属性不外乎读取get和修改set所以对外提供的访问入口应该有两个这两个 方法通常被称为 set 方法和 get 方法请注意set 和 get 方法访问的都是某个具体对象的属性 不同的对象调用 get 方法获取的属性值不同所以 set 和 get 方法必须有对象的存在才能调用 这样的方法定义的时候不能使用 static 关键字修饰被称为实例方法。实例方法必须使用“引 用”的方式调用。 还记得之前我们接触的方法都是被 static 修饰的这些方法直接采用“类名” 的方式调用而不需要创建对象在这里显然是不行的。请看以下代码
class MobilePhone {//电压手机正常电压在 3~5Vprivate double voltage;public MobilePhone(){}public void setVoltage(double _voltage){if(_voltage 3 || _voltage 5){//当电压低于 3V或者高于 5V时抛出异常程序则终止throw new RuntimeException(电压非法请爱护手机);}//程序如果能执行到此处说明以上并没有发生异常电压值合法voltage _voltage;}public double getVoltage(){return voltage;}
}
public class MobilePhoneTest {public static void main(String[] args) {MobilePhone phone new MobilePhone();phone.setVoltage(3.7);System.out.println(手机电压 phone.getVoltage());phone.setVoltage(100);System.out.println(手机电压 phone.getVoltage());}
}运行结果 通过以上程序可以看出 MobilePhone 的 voltage 属性不能在外部程序中随意访问了只能调用 MobilePhone 的 setVoltage()方法来修改电压调用 getVoltage()方法来读取电压在setVoltage()方法中编写了安全控制代码当电压低于 3V或者高于 5V 的时候程序抛出了异常不允许修改电压值程序结束了。只有合法的时候才允许程序修改电压值。后续可以通过学习异常机制对异常进行处理
总之在 java 语言中封装的步骤应该是这样的
需要被保护的属性使用 private 进行修饰给这个私有的属性对外提供公开的 set 和 get 方法其中 set 方法用来修改属性的值get 方法用来读取属性的值。 set 和 get 方法在命名上也是有规范的 set 方法名是 set 属性名属性名首字母大写get 方法名是 get 属性名属性名首字母大写。set 方法有一个参数用来给属性赋值set 方法没有返回值一般在 set 方法内部编写安全控制程序因为毕竟 set 方法是修改内部数据的。get 方法不需要参数返回值类型是该属性所属类型。【先记住】 set 方法和 get 方法都不带 static 关键字不带 static 关键字的方法称为实例方法这些方法调用的时候需要先创建对象然后通过“引用”去调用这些方法实例方法不能直接采用“类名”的方式调用。
示例代码
class Product{private int no;private String name;private double price;public Product(){}public Product(int _no, String _name, double _price){no _no;name _name;price _price;}public void setNo(int _no){no _no;}public int getNo(){return no;}public String getName() {return name;}public void setName(String _name) {name _name;}public double getPrice() {return price;}public void setPrice(double _price) {price _price;}
}public class ProductTest {public static void main(String[] args) {Product p1 new Product(10000 , 小米 12 , 3599.0);System.out.println(商品编号 p1.getNo());System.out.println(商品名称 p1.getName());System.out.println(商品单价 p1.getPrice());p1.setNo(70000);p1.setName(小米 14);p1.setPrice(5999);System.out.println(商品编号 p1.getNo());System.out.println(商品名称 p1.getName());System.out.println(商品单价 p1.getPrice());}
}运行结果
补充构造方法中给属性赋值是在创建对象的时候完成的当对象创建完毕之后属性可能还是会被修改的后期要想修改属性的值这个时候就必须调用 set 方法了。
1.4 难点解惑
理解为什么在进行封装时提供的 setter 和 getter 方法就不能添加 static 关键字了呢 这块内容在后期学习 static 关键字之后自然就好理解了先简单解释一下带有 static 关键字的方法是静态方法不需要创建对象直接通过“类”来调用。对于没有 static 关键字的方法被称为实例方法这些方法执行时要求必须先创建对象然后通过“引用”的方式来调用。而对于封装来说setter 和 getter 方法都是访问对象内部的属性所以 setter 和 getter 方法在定义时不能添加 static 关键字。
1.5 练习
第一题定义“人”类“人”类包括这些属性姓名、性别、年龄等。使用封装的方式编写 程序要求对年龄进行安全控制合法的年龄范围为[0~100]其他值表示不合法。
class People {private String name;private char sex;private int age;public People() {}public People(String _name, char _sex, int _age) {name _name;sex _sex;age _age;}public String getName() {return name;}public void setName(String _name) {name _name;}public char getSex() {return sex;}public void setSex(char _sex) {sex _sex;}public int getAge() {return age;}public void setAge(int _age) {if(_age 0 || _age 100){//当年龄不合法则抛出异常程序则终止throw new RuntimeException(非法年龄);}age _age;}
}public class PeopleTest{public static void main(String[] args) {People p1 new People(李宗盛, 男, 23);System.out.println(p1.getName());System.out.println(p1.getSex());System.out.println(p1.getAge());p1.setAge(101);}
}2. this 和 static
2.1 this掌握
2.1.1 this是什么
先看一段代码
class Customer {private String name;public Customer(){}public Customer(String _name){name _name;}public void setName(String _name){name _name;}public String getName(){return name;}
}
public class CustomerTest {public static void main(String[] args) {Customer jack new Customer(jack);Customer rose new Customer(rose);}
}以上代码的this内存结构图为 this可以看做一个变量它是一个引用存储在 Java 虚拟机堆内存的对象内部this这个引用保存了当前对象的内存地址指向自身任何一个堆内存的 java 对象都有一个 this也就是说创建 100 个 java 对象则分别对应 100 个this。通过以上的内存图可以看出jack 引用保存的内存地址是0x1111对应的this 引用保存的内存地址也是 0x1111所以jack 引用和this 引用是可以划等号的。也就是说访问对象的时候 jack.name和 this.name 是一样的都是访问该引用所指向对象的 name 属性。
this 指向当前对象也可以说this代表当前对象this可以使用在实例方法中以及构造方法中语法格式分别为this.xx和this(xx)。
2.1.2 this 在实例方法中使用
this 不能出现在带有 static 的方法当中如下
public class ThisInStaticMethod {public static void main(String[] args) {ThisInStaticMethod.method();}public static void method(){System.out.println(this);}
}编译报错
通过以上的测试得知 this不能出现在 static的方法当中原因如下 首先static 的方法在调用的时候是不需要创建对象的直接采用“类名”的方式调用也就是说static 方法执行的过程中是不需要“当前对象”参与的所以 static 的方法中不能使用 this因为 this 代表的就是“当前对象”。
之前的“封装”学习中曾编写属性相关的 set 和 get 方法set 和 get方法在声明的时候不允许带 static 关键字我们把这样的方法叫做实例方法与实例变量一样实例变量和实例方法都是对象相关必须有对象的存在然后通过“引用”去访问。
将 set 和 get 方法设计为实例方法的原因 因为 set 和 get 方法操作的是实例变量“不同的对象”调用 get 方法最终得到的数据是不同的例如 zhangsan 调用 getName()方法得到的名字是zhangsanlisi 调用 getName()方法得到的名字是 lisi显然 get 方法是一个对象级别的方法不能直接采用“类名”调用必须先创建对象再通过“引用”去访问。
this 可以出现在实例方法当中因为实例方法在执行的时候一定是对象去触发的实例方法一定是对象才能去调用的而 this 恰巧又代表“当前对象”所以 “谁”去调用这个实例方法 this 就是“谁” 。请看以下代码
class Customer {private String name;public Customer(){}public Customer(String _name){name _name;}public void setName(String _name){name _name;}public String getName(){return name;}public void shopping(){System.out.println(shopping() -- this);}
}
public class CustomerTest {public static void main(String[] args) {Customer jack new Customer(jack);System.out.println(main() --- jack);jack.shopping();System.out.println();Customer rose new Customer(rose);System.out.println(main() --- rose);rose.shopping();}
}运行结果 分析一下this代表当前对象jack调用shopping()方法this就是jackrose调用shopping方法this就是rose。
由上可知this 可以使用在实例方法当中它指向当前正在执行这个动作的对象。
2.1.3 this访问实例变量
实例变量怎么访问正规的访问方式是采用引用.去访问。请看下面的代码
class Customer {private String name;public Customer(){}public Customer(String _name){name _name;}public void setName(String _name){name _name;}public String getName(){return name;}public void shopping(){System.out.println(name is shopping!);}
}
public class CustomerTest {public static void main(String[] args) {Customer jack new Customer(jack);jack.shopping();Customer rose new Customer(rose);rose.shopping();}
}运行结果 将以上部分代码片段取出来进行分析
class Customer {private String name; //实例变量...public void shopping(){//jack 调用 shopping当前对象是 jack//rose 调用 shopping当前对象是 rose//name 是实例变量不用“引用”可以访问以上结果表示可以System.out.println(name is shopping!);//正规的访问方式应该是“引用.name”比如//System.out.println(jack.name is shopping!);//或者//System.out.println(rose.name is shopping!);//对不起jack 和 rose 在 main 方法当中在这里不可见不能用//难道是这样System.out.println(this.name is shopping!);}
}
public class CustomerTest {public static void main(String[] args) {Customer jack new Customer(jack);jack.shopping();Customer rose new Customer(rose);rose.shopping();}
}完整修改代码
class Customer {private String name;public Customer(){}public Customer(String _name){name _name;}public void setName(String _name){name _name;}public String getName(){return name;}public void shopping(){System.out.println(name is shopping!);System.out.println(this.name is shopping!);}
}
public class CustomerTest {public static void main(String[] args) {Customer jack new Customer(jack);jack.shopping();System.out.println();Customer rose new Customer(rose);rose.shopping();}
}运行结果 通 过 以 上 的 测 试 我 们 得 知 System.out.println(name is shopping!) 等价于System.out.println(this.name is shopping!)。也就是说在 shopping()这个“实例方法”当中直接访问“实例变量”name 就表示访问当前对象的name。换句话说 在实例方法中可以直接访问当前对象的实例变量而this.是可以省略的。
this.什么时候不能省略呢以上name_name这样的代码其实可以修改为更优雅的样子如下
public class Customer {private String name;public Customer(){}public Customer(String name){this.name name;//这里的“this.”不能省略}public void setName(String name){this.name name;//这里的“this.”不能省略}public String getName(){return name; //这里的“this.”可以省略}public void shopping(){//这里的“this.”可以省略System.out.println(name is shopping!);}
}以上代码当中this.name name其中this.name表示实例变量name等号右边的name是局部变量 name此时如果省略this.则变成name name这两个 name 都是局部变量java 遵守就近原则和实例变量 name无关了显然是不可以省略this.的。
2.1.4 this扩展①
如下代码
class Customer {private String name;public Customer(){}public Customer(String name){this.name name;}public void setName(String name){this.name name;}public String getName(){return name;}//实例方法public void shopping(){System.out.println(name is shopping!);System.out.println(name 选好商品了);//pay()支付方法是实例方法实例方法需要使用“引用”调用//那么这个“引用”是谁呢//当前对象在购物肯定是当前对象在支付所以引用是thisthis.pay();//同样“this.”可以省略pay();}//实例方法public void pay(){System.out.println(name 支付成功);}
}
public class CustomerTest {public static void main(String[] args) {Customer jack new Customer(jack);jack.shopping();System.out.println();Customer rose new Customer(rose);rose.shopping();}
}运行结果 通过以上的测试可以看出在一个实例方法当中可以直接去访问其它的实例方法方法是对象的一种行为描述实例方法中直接调用其它的实例方法就表示“当前对象”完成了一系列行为动作。例如在实例方法 shopping()中调用另一个实例方法 pay()这个过程就表示 jack 在选购商品选好商品之后完成支付环节其中选购商品是一个动作完成支付是另一个动作。
2.1.5 this扩展②
public class ThisTest {int i 10;public static void main(String[] args) {System.out.println(i);}
}编译报错 原因分析首先i变量是实例变量实例变量要想访问必须先创建对象然后通过“引用”去访问main方法是 static 的方法也就是说 main 方法是通过“类名”去调用的在 main 方法中没有“当前对象”的概念也就是说 main 方法中不能使用 this所以编译报错了。可以修改如下
public class ThisTest {int i 10;public static void main(String[] args) {//这肯定是不行的因为 main 方法带有 static不能用 this//System.out.println(this.i);//可以自己创建一个对象ThisTest thisTest new ThisTest();//通过引用访问System.out.println(thisTest.i);}
}运行结果 结论在 static 的方法中不能直接访问实例变量要访问实例变量必须先自己创建一个对象通过引用可以去访问不能通过 this 访问因为在 static 方法中是不能存在 this 的。其实这种设计也是有道理的因为 static 的方法在执行的时候是采用“类名”去调用没有对象的参与自然也不会存在当前对象所以 static 的方法执行过程中不存在 this。
在 static 方法中能够直接访问实例方法吗请看以下代码
public class ThisTest {public static void main(String[] args) {doSome();}public void doSome(){System.out.println(do some...);}
}编译报错 原因分析因为实例方法必须先创建对象通过引用去调用在以上的 main 方法中并没有创建对象更没有 this。所以在 main 方法中无法直接访问实例方法。结论就是在 static 的方法中不能直接访问实例方法。修改的话同样是先创建对象
public class ThisTest {public static void main(String[] args) {ThisTest t new ThisTest();t.doSome();}public void doSome(){System.out.println(do some...);}
}运行结果
2.1.6 this总结
this 不能出现在static的方法中可以出现在实例方法中代表当前对象。多数情况下this是可以省略不写的但是在区分局部变量和实例变量的时候不能省略。在实例方法中可以直接访问当前对象实例变量以及实例方法在static方法中无法直接访问实例变量和实例方法。
2.1.7 this在构造方法中使用
this的另外一种用法在构造方法第一行只能出现在第一行这是规定记住就行通过当前构造方法调用本类当中其它的构造方法其目的是为了代码复用。 调用时的语法格式是this(实际参数列表)请看以下代码
class Date {private int year;private int month;private int day;//业务要求默认创建的日期为 1970 年 1 月 1 日public Date(){this.year 1970;this.month 1;this.day 1;}public Date(int year,int month,int day){this.year year;this.month month;this.day day;}public int getYear() {return year;}public void setYear(int year) {this.year year;}public int getMonth() {return month;}public void setMonth(int month) {this.month month;}public int getDay() {return day;}public void setDay(int day) {this.day day;}
}
public class DateTest {public static void main(String[] args) {Date d1 new Date();System.out.println(d1.getYear() 年 d1.getMonth() 月 d1.getDay() 日);Date d2 new Date(2008 , 8, 8);System.out.println(d2.getYear() 年 d2.getMonth() 月 d2.getDay() 日);}
}运行结果 对于无参数构造方法和有参数构造方法观察发现 可以在无参数构造方法中使用this(实际参数列表);来调用有参数的构造方法这样就可以让代码得到复用了请看 最终运行结果同上。
在 this()上一行尝试添加代码请看代码截图以及编译结果 通过以上测试得出this()语法只能出现在构造方法第一行。
2.2 static掌握
2.2.1 static概述
static 是 java 语言中的关键字表示“静态的”它可以用来修饰变量、方法、代码块等修饰的变量叫做静态变量修饰的方法叫做静态方法修饰的代码块叫做静态代码块。在 java语言中凡是用 static 修饰的都是类相关的不需要创建对象直接通过“类名”即可访问即使使用“引用”去访问在运行的时候也和堆内存当中的对象无关。
2.2.2 静态变量
java 中的变量包括局部变量和成员变量。
在方法体中声明的变量为局部变量有效范围很小只能在方法体中访问方法结束之后局部变量内存就释放了在内存方面局部变量存储在栈当中。在类体中定义的变量为成员变量成员变量又包括实例变量和静态变量。 没有使用 static 关键字声明的变量称为实例变量实例变量是对象级别的每个对象的实例变量值可能不同所以实例变量必须先创建对象通过“引用”去访问。使用static 关键字声明的成员变量称为静态变量静态变量访问时不需要创建对象直接通过“类名”访问。实例变量存储在堆内存当中静态变量存储在方法区当中。实例变量在构造方法执行过程中初始化静态变量在类加载时初始化。
对于何时要将成员变量声明为静态变量请看以下代码
class Man {//身份证号int idCard;//性别所有男人的性别都是“男”//true 表示男false 表示女boolean sex true;public Man(int idCard){this.idCard idCard;}
}
public class ManTest {public static void main(String[] args) {Man jack new Man(100);System.out.println(jack.idCard , (jack.sex ? 男 : 女));Man sun new Man(101);System.out.println(sun.idCard , (sun.sex ? 男 : 女));Man cok new Man(102);System.out.println(cok.idCard , (cok.sex ? 男 : 女));}
}运行结果 以上程序的内存结构图 “男人类”创建的所有“男人对象”每一个“男人对象”的身份证号都不一样该属性应该每个对象持有一份所以应该定义为实例变量而每一个“男人对象”的性别都是“男”不会随着对象的改变而变化性别值永远都是“男”这种情况下就没有必要让每一个“男人对象”持有一份否则会浪费大量的堆内存空间所以这个时候建议将“性别男”属性定义为类级别的属性声明为静态变量上升为“整个族”的数据这样的变量不需要创建对象直接使用“类名”即可访问。
修改如下
class Man {//身份证号int idCard;//性别所有男人的性别都是“男”//true 表示男false 表示女static boolean sex true; //声明为静态变量public Man(int idCard){this.idCard idCard;}
}
public class ManTest {public static void main(String[] args) {Man jack new Man(100);System.out.println(jack.idCard , (Man.sex ? 男 : 女));Man sun new Man(101);System.out.println(sun.idCard , (Man.sex ? 男 : 女));Man cok new Man(102);System.out.println(cok.idCard , (Man.sex ? 男 : 女));}
}运行结果同上再看看内存结构图
小结 当一个类的所有对象的某个“属性值”不会随着对象的改变而变化的时候建议将该属性定义为静态属性或者说把这个变量定义为静态变量静态变量在类加载的时候初始化存储在方法区当中不需要创建对象直接通过“类名”来访问。
2.2.2.1 静态变量使用“引用”访问
如果静态变量使用“引用”来访问可以吗如果可以的话这个访问和具体的对象有关系吗来看以下代码
class Man {//身份证号int idCard;//性别所有男人的性别都是“男”//true 表示男false 表示女static boolean sex true; //声明为静态变量public Man(int idCard){this.idCard idCard;}
}
public class ManTest {public static void main(String[] args) {//静态变量比较正式的访问方式System.out.println(性别 Man.sex);//创建对象Man jack new Man(100);//使用“引用”来访问静态变量可以吗System.out.println(性别 jack.sex);//对象被垃圾回收器回收了jack null;//使用“引用”还可以访问吗System.out.println(性别 jack.sex);}
}运行结果 通过以上代码以及运行结果可以看出静态变量也可以使用“引用”去访问但实际上在执行过程中“引用”所指向的对象并没有参与如果是空引用访问实例变量程序一定会发生空指针异常但是以上的程序编译通过了并且运行的时候也没有出现任何异常这说明虽然表面看起来是采用“引用”去访问但实际上在运行的时候还是直接通过“类”去访问的。
2.2.2.2 静态方法使用“引用”访问
静态方法也是如此请看如下代码
class Man {//身份证号int idCard;//性别所有男人的性别都是“男”//true 表示男false 表示女static boolean sex true;public Man(int idCard){this.idCard idCard;}//静态方法public static void printInfo(){System.out.println(----- (Man.sex ? 男 : 女) ------);}
}
public class ManTest {public static void main(String[] args) {//静态变量比较正式的访问方式System.out.println(性别 Man.sex);//创建对象Man jack new Man(100);//使用“引用”来访问静态变量可以吗System.out.println(性别 jack.sex);//对象被垃圾回收器回收了jack null;//使用“引用”还可以访问吗System.out.println(性别 jack.sex);//静态方法比较正式的访问方式Man.printInfo();//访问静态方法可以使用引用吗并且空的引用可以吗jack.printInfo();}
}运行结果 通过以上代码测试得知静态变量和静态方法比较正式的方式是直接采用“类名”访问但实际上使用“引用”也可以访问并且空引用访问静态变量和静态方法并不会出现空指针异常。实际上在开发中并不建议使用“引用”去访问静态相关的成员因为这样会让程序员困惑因为采用“引用”方式访问的时候程序员会认为你访问的是实例相关的成员。
小结
所有实例相关的包括实例变量和实例方法必须先创建对象然后通过“引用”的方式去访问如果空引用访问实例相关的成员必然会出现空指针异常。所有静态相关的包括静态变量和静态方法直接使用“类名”去访问。虽然静态相关的成员也能使用“引用”去访问但这种方式并不被主张。
2.2.3 静态代码块
2.2.3.1 语法格式与介绍
类{//静态代码块static{java 语句;}
}静态代码块在类加载时执行并且只执行一次。其实际上是 java 语言为程序员准备的一个特殊的时刻这个时刻就是类加载时刻如果你想在类加载的时候执行一段代码那么这段代码就有的放矢了。例如我们要在类加载的时候解析某个文件并且要求该文件只解析一次那么此时就可以把解析该文件的代码写到静态代码块当中了。我们来测试一下静态代码块
public class StaticTest {//静态代码块static{System.out.println(2);}//静态代码块static{System.out.println(1);}//main 方法public static void main(String[] args) {System.out.println(main execute!);}//静态代码块static{System.out.println(0);}
}运行结果
通过以上的测试可以得知
一个类当中可以编写多个静态代码块尽管大部分情况下只编写一个并且静态代码块遵循自上而下的顺序依次执行所以有的时候放在类体当中的代码是有执行顺序的大部分情况下类体当中的代码没有顺序要求方法体当中的代码是有顺序要求的方法体当中的代码必须遵守自上而下的顺序依次逐行执行静态代码块当中的代码在 main 方法执行之前执行这是因为静态代码块在类加载时执行并且只执行一次。
2.2.3.2 静态代码块中访问静态变量
再看一下以下代码
public class StaticTest02 {int i 100;public static void main(String[] args) {System.out.println(main execute!);}static{System.out.println(i);}
}编译报错 原因分析 因为 i 变量是实例变量实例变量必须先创建对象才能访问静态代码块在类加载时执行这个时候对象还没有创建呢所以 i 变量在这里是不能这样访问的。可以考虑在 i 变量前添加 static这样 i 变量就变成静态变量了静态变量访问时不需要创建对象直接通过“类”即可访问如下
public class StaticTest02 {static int i 100;public static void main(String[] args) {System.out.println(main execute!);}static{System.out.println(静态变量:ii);}
}运行结果 若代码修改为如下
public class StaticTest02 {static{System.out.println(静态变量 i i);}static int i 100;public static void main(String[] args) {System.out.println(main execute!);}
}编译报错 通过测试可以看到有的时候类体当中的代码也是有顺序要求的类体当中定义两个独立的方法这两个方法是没有先后顺序要求的静态代码块在类加载时执行静态变量在类加载时初始化它们在同一时间发生所以必然会有顺序要求如果在静态代码块中要访问 i 变量那么 i 变量必须放到静态代码块之前。
2.2.4 静态方法
方法在什么情况下会声明为静态的呢方法实际上描述的是行为动作当某个动作在触发的时候需要对象的参与这个方法应该定义为实例方法例如
每个玩篮球的人都会打篮球但是你打篮球和科比打篮球最终的效果是不一样的显然打篮球这个动作存在对象差异化该方法应该定义为实例方法。每个高中生都有考试的行为但是你考试和学霸考试最终的结果是不一样的一个上了“家里蹲大学”一个上了“清华大学”显然这个动作也是需要对象参与才能完成的所以考试这个方法应该定义为实例方法。
以上描述是从设计思想角度出发来进行选择其实也可以从代码的角度来进行判断当方法体中需要直接访问当前对象的实例变量或者实例方法的时候该方法必须定义为实例方法因为只有实例方法中才有 this静态方法中不存在 this。 请看代码
class Customer {String name;public Customer(String name){this.name name;}public void shopping(){//直接访问当前对象的 nameSystem.out.println(name 正在选购商品);//继续让当前对象去支付pay();}public void pay(){System.out.println(name 正在支付);}
}
public class CustomerTest {public static void main(String[] args) {Customer jack new Customer(jack);jack.shopping();Customer rose new Customer(rose);rose.shopping();}
}运行结果 在以上的代码中不同的客户购物最终的效果都不同另外在 shopping()方法中访问了当前对象的实例变量 name以及调用了实例方法 pay()所以 shopping()方法不能定义为静态方法必须声明为实例方法。
在实际的开发中“工具类”当中的方法一般定义为静态方法因为工具类就是为了方便大家的使用将方法定义为静态方法比较方便调用不需要创建对象直接使用类名就可以访问。请看以下工具类为了简化System.out.println();代码而编写的工具类
class U {public static void p(int data){System.out.println(data);}public static void p(long data){System.out.println(data);}public static void p(float data){System.out.println(data);}public static void p(double data){System.out.println(data);}public static void p(boolean data){System.out.println(data);}public static void p(char data){System.out.println(data);}public static void p(String data){System.out.println(data);}}
public class HelloWorld {public static void main(String[] args) {U.p(Hello World!);U.p(true);U.p(3.14);U.p(A);}
}运行结果
2.3 难点解惑
对于 static 来说代码的执行顺序是一个需要大家注意的地方一般来说方法体当中的代码是有执行顺序要求的之前所接触的程序中类体当中的程序没有顺序要求但自从认识了 static 之后我们发现类体当中的代码也有执行顺序的要求了尤其是 static 修饰的静态变量以及static修饰的静态代码块他们是有先后顺序的这里需要记住的是static修饰的静态变量以及静态代码块都是在类加载时执行并且遵循自上而下的顺序依次逐行执行。
2.4 练习
2.4.1 选择题
public class Test {static int value 9;public static void main(String[] args) throws Exception{new Test().printValue();}public void printValue(){int value 69;System.out.println(this.value);}
}A. 编译错误 B. 打印 9 C. 打印 69 D. 运行时抛出异常
运行结果 理解 方法中的int value 69;是局部变量而 static int value 9;是实例变量方法中的System.out.println(this.value);调用的是实例变量
2.4.2 判断下面代码的输出结果并说明原因
class User {private String name;public User(){}public void setName(String name){name name;}public String getName(){return name;}
}
public class UserTest {public static void main(String[] args) {User user new User();user.setName(zhangsan);System.out.println(user.getName());}
}最终的输出结果是null。 原因setName 方法体当中的 name name 是把局部变量 name 赋值给局部变量 name和实例变量 name 无关所以 getName()方法获取的实例变量值是 null。
2.4.3 找出下面代码的错误并说明为什么错了
public class Test {int i;static int j;public void m1(){System.out.println(i);System.out.println(j);m2();m3();}public void m2(){}public static void m3(){System.out.println(i); //报错因为静态方法中无法直接访问实例变量 i System.out.println(j);m2(); //报错因为静态方法中无法直接访问实例方法 m2()m4();}public static void m4(){}
}