目录
主要内容
- static变量
- static方法
- static代码块
- package和import
- 封装性
- 继承性
- 方法重写
学习目标
节数 |
知识点 |
要求 |
第一节(static关键字) |
static变量 |
理解 |
static方法 |
理解 |
|
static代码块 |
理解 |
|
第二节(package和import) |
package包 |
理解 |
import导入 |
掌握 |
|
使用文档注释生成API |
了解 |
|
第三节(封装性) |
封装的引入和作用 |
掌握 |
权限修饰符 |
掌握 |
|
第四节(继承性) |
继承的引入和作用 |
掌握 |
方法重写 |
掌握 |
第一节 static关键字
static是Java中的一个关键字,单词本身是静态的含义。一个类的成员包括变量、方法、构造方法、代码块和内部类,static可以修饰除了构造方法以外的所有成员。
使用static修饰的成员成为静态成员,是属于某个类的;而不使用static修饰的成员成为实例成员,是属于类的每个对象的。
1.1. static变量
在类中,用static声明的成员变量为静态成员变量,也称为类变量。 类变量的生命周期和类相同,在整个应用程序执行期间都有效。它有如下特点:
- 为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显式初始化。
- 对于该类的所有对象来说,static成员变量只有一份。被该类的所有对象共享!!
- 一般用“类名.类属性/方法”来调用。(也可以通过对象引用或类名(不需要实例化)访问静态成员。)
- 在static方法中不可直接访问非static的成员。
【示例1】static变量
public class Student { //成员变量 private int sno;//学号 private String name;//姓名 private String sex;//性别 private double score;//分数 private String cup;//杯子 //private static String waterDispenser;//饮水机 private static String classRoom;//教室的编号 //private static String teachName;//讲师的名字 //构造方法 public Student(){ } public Student(int sno, String name, String sex ){ this.sno = sno; this.name = name; this.sex = sex; } public void show(){ System.out.println(sno +" "+this.name+" "+sex+" " +this.score+" "+classRoom); } public static void main(String[] args) { System.out.println(Student.classRoom); Student.classRoom = "503"; System.out.println(Student.classRoom); Student stu = new Student(7,"田七","男",77); stu.sno = 5; //Student.sno = 6; stu.classRoom = "507"; stu.show(); Student stu2 = new Student(); stu2.show(); } }
内存分配图如下:
总结:static变量和非static变量的区别:
- 份数不同:静态变量:1份;非静态变量:1个对象一份
- 存储位置不同:静态变量:方法区;非静态变量:堆中
- 内存分配空间的时间不同:静态变量:第一次加载类的时候;非静态变量:创建对象的时候
- 生命周期不同。静态变量和类的生命周期相同;非静态变量的生命周期和所属对象相同
- 调用方式不同
- 静态变量: 通过类名调用 Student.classRoom,也可以通过对象名调用stu1.classRoom ="301" 不推荐
- 非静态变量:通过对象名调用 stu1.name ="小张";
1.2. static方法
- static方法的作用
访问static变量和static方法
- static方法的调用方式
通过类名调用 Student.showClassRoom(); 推荐该方式
通过对象名访问 stu1.showClassRoom();
- 不可以
静态方法中不可以访问非静态变量
静态方法中不可以访问非静态方法
静态方法中不可以访问this
理解:加载类的时候就加载静态变量和静态方法,此时可能还没有创建对象,所以非静态变量和非静态的方法还没有分配空间,无法访问
- 可以
非静态方法中可以访问静态变量
非静态方法中可以访问静态方法
理解:加载类的时候就已经加载静态变量和静态方法,创建对象后,非静态变量和非静态的方法才分配空间,此时静态变量和静态方法已经存在,可以访问
【示例2】static方法
public class Student { //成员变量 String name; int age; String sex; double score; static String classRoom;//103 !!! public static void showClassRoom(){ System.out.println(classRoom); //System.out.println(name);//非静态变量 //shout();//非静态方法 //System.out.println(this); } public static void setClassRoom(String classRoom){ Student.classRoom = classRoom; } //成员方法 public void introduce(){ System.out.println(this.name+" "+this.age+" " +sex+" "+score+" "+classRoom); showClassRoom(); } public static void main(String[] args) { Student.showClassRoom(); Student.setClassRoom("r301"); Student.showClassRoom(); //Student stu1 = new Student("小王",20,"男",98); Student stu1 = new Student("小王",20,"男"); stu1.name ="小张"; stu1.classRoom ="r301"; stu1.showClassRoom(); } }
1.3. static代码块
- 总结1:局部代码块
- 位置:方法中
- 数量:多个
- 执行顺序:依次执行
- 局部代码块中定义的变量作用范围只限于当前代码块
- 总结2:(成员)代码块
- 位置:类中
- 数量:多个
- 执行顺序:依次执行
- 执行时间:每次创建对象的时候都执行;先执行代码块,再执行构造方法
- 作用:实际开发中很少用; 可以将各个构造方法中公共的代码提取到代码块;匿名内部类不能提供构造方法,此时初始化操作放到代码块中
- 总结3:static代码块
- 位置:类中
- 数量:多个
- 执行顺序:依次执行
- 执行时间:第一次加载类的时候执行,只执行一次
- 作用:给静态变量赋初始值。实际开发中使用比较多,一般用于执行一些全局性的初始化操作,比如创建工厂、加载数据库初始信息
【示例3】static代码块
public class Student { static { System.out.println("static code block 1"); } String name; static String classRoom;//103 !!! static { //name = "sdf"; classRoom = "r301"; System.out.println("static code block 2"); } public Student(){ System.out.println("-----Student()---------"); } public static void main(String[] args) { int n = 4;//局部变量 // { //局部代码块 // int m = 5; // System.out.println(m); // } System.out.println(classRoom); new Student(); new Student(); } { //classRoom = "r301"; System.out.println(" code block 3"); } }
本节作业
- static变量和非static变量的区别和联系
- static方法为什么不可以访问非static的变量和方法
- static代码块的执行时机和执行次数
第二节 package和import
2.1. package包
- 为什么使用包
文件太多,并且会有同名文件,计算机的硬盘需要不同级别的文件夹来存储;
包机制是Java中管理类的重要手段。开发中,我们会遇到大量同名的类,通过包我们很容易对解决类重名的问题,也可以实现对类的有效管理。除了以上考虑外,还和访问权限有密切关系。
- 如何定义包
我们通过package实现对类的管理,package的使用有两个要点:
- 包名:域名倒着写即可,再加上模块名,便于内部管理类。
- 包名一律小写。
com.bjsxt.oop.object
cn.com.sina.video....
com.bjsxt.stumgr.dao
com.bjsxt.stumgr.dao.impl
- 如何使用包
通常是类的第一句非注释性语句。
必须以;结尾。
- Java常用包
Java中的常用包 |
说明 |
java.lang |
包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。 |
java.awt |
包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 |
java.net |
包含执行与网络相关的操作的类。 |
java.io |
包含能提供多种输入/输出功能的类。 |
java.util |
包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。 |
注意事项
- 写项目时都要加包,不要使用默认包。
- com.gao和com.gao.car,这两个包没有包含关系,是两个完全独立的包。只是逻辑上看起来后者是前者的一部分。
2.2. import导入
如果我们要使用其他包的类,需要使用import导入,从而可以在本类中直接通过类名来调用,否则就需要书写类的完整包名和类名。import后,便于编写代码,提高可维护性。
注意要点
- 默认是当前包的类和接口
- Java会默认导入java.lang包下所有的类,因此这些类我们可以直接使用。
- 可以使用通配符,比如import com.bjsxt.oop.object.*; 会导入该包下所有类和接口(但不包括下级包)
- 如果导入两个同名的类,只能用包名+类名来显示调用相关类:
java.util.Date date = new java.util.Date();
静态导入(static import)是在JDK1.5新增加的功能,其作用是用于导入指定类的静态属性和静态方法,这样我们可以直接使用静态属性和静态方法。
【示例4】导入和静态导入
import java.lang.reflect.Constructor; import java.util.Date; import java.util.*; import static java.lang.Math.PI; import static java.lang.Math.*; public class TestImport { public static void main(String[] args) { Date date = new Date(); java.sql.Date date2 = java.sql.Date.valueOf("19990-12-23"); List list; Set set; Map map; Constructor constructor; System.out.println(PI); System.out.println(sqrt(64)); System.out.println(pow(7, 2)); } }
2.3. 使用文档注释生成API文档
IntelliJ IDEA 本身提供了很好的 JavaDoc 生成功能,以及标准 JavaDoc 注释转换功能,其实质是在代码编写过程中,按照标准 JavaDoc 的注释要求,为需要暴露给使用者的类、方法以及其他成员编写注释。然后使用 IDEA 的功能自动调用 javadoc.exe(JDK 自带的工具)根据源代码中的注释内容自动生成 JavaDoc 文档(超文本格式)。
【示例5】使用文档注释
/** * 学生类 */ public class Student { /** * 姓名 */ String name; /** * 年龄 */ int age; /** * 性别 */ String sex; /** * 学习 */ public void study(){ } /** * 考试 * @param site 地点 * @return 分数 */ public double test(String site){ return 100; } }
在idea中主要操作步骤如下:
最终可以生成如图所示的API帮助文档。
本节作业
- 理解package包的作用
- Java中常用包及其作用
- import导入的注意事项
第三节 封装性
封装(encapsulation)是面向对象三大特征之一。对于程序合理的封装让外部调用更加方便,更加利于写作。同时,对于实现者来说也更加容易修正和改版代码。
3.1. 引入封装
我要看电视,只需要按一下开关和换台就可以了。有必要了解电视机内部的结构吗?有必要碰碰显像管吗?制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口,比如:电源开关。具体内部是怎么实现的,我们不需要操心。同理,汽车暴露离合、油门、制动和方向盘,驾驶员不需要懂原理和维修也可以驾驶。
需要让用户知道的才暴露出来,不需要让用户知道的全部隐藏起来,这就是封装。说的专业一点,封装就是把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
我们程序设计要追求“高内聚,低耦合”。 高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。
编程中封装的具体优点:
- 提高代码的安全性。
- 提高代码的复用性。
- “高内聚”:封装细节,便于修改内部代码,提高可维护性。
- “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。
【示例6】未进行封装的代码演示
class Person { String name; int age; public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Test { public static void main(String[ ] args) { Person p = new Person(); p.name = "小红"; p.age = -45;//年龄可以通过这种方式随意赋值,没有任何限制 System.out.println(p); } }
我们都知道,年龄不可能是负数,也不可能超过130岁,但是如果没有使用封装的话,便可以给年龄赋值成任意的整数,这显然不符合我们的正常逻辑思维。
3.2. 权限修饰符
Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。 Java中4种“访问控制符”分别为private、默认、protected、public,它们说明了面向对象的封装性,所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性。
下面详细讲述它们的访问权限问题。其访问权限范围如表所示。
访问权限修饰符 |
||||
修饰符 |
同一个类 |
同一个包中 |
子类 |
所有包的所有类 |
private |
* |
|||
默认 |
* |
* |
||
protected |
* |
* |
* |
|
public |
* |
* |
* |
* |
- private 表示私有,只有自己类能访问
- default表示没有修饰符修饰,只有同一个包的类能访问
- protected表示可以被同一个包的类以及其他包中的子类访问
- public表示可以被该项目的所有包中的所有类访问
类的成员的处理:
- 一般使用private访问权限。
- 提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作(注意:boolean变量的get方法是is开头!)。
- 一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。
类的处理:
- 类只能使用public和默认来修饰
- 默认:当前包
- public:当前项目的所有包
- public类要求类名和文件名相同,一个java文件中至多一个public类
【示例7】封装后的代码演示
class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; setAge(age); } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { //在赋值之前先判断年龄是否合法 if (age > 130 || age < 0) { this.age = 18;//不合法赋默认值18 } else { this.age = age;//合法才能赋值给属性age } } public int getAge() { return age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Test2 { public static void main(String[ ] args) { Person p1 = new Person(); //p1.name = "小红"; //编译错误 //p1.age = -45; //编译错误 p1.setName("小红"); p1.setAge(-45); System.out.println(p1); Person p2 = new Person("小白", 300); System.out.println(p2); } }
3.3. 封装练习
以面向对象的思想,编写自定义类描述图书信息。设定属性包括:书名,作者,出版社名,价格;方法包括:信息介绍
要求:
1) 设置属性的私有访问权限,通过公有的get,set方法实现对属性的访问
2) 限定价格必须大于10,如果无效进行提示
3) 设计构造方法实现对属性赋值
4) 信息介绍方法描述图书所有信息
5) 编写测试类,测试图书类的对象及相关方法(测试数据信息自定)
【示例8】对Book进行封装
public class Book { private String bookName;//书名 private String author;//作者 private String publisher;//出版社 private double price;// public Book(){ //super(); } public Book(String bookName,String author,String publisher,double price){ this.bookName = bookName; this.author = author; this.publisher = publisher; //this.price = price; this.setPrice(price); } public void setBookName(String bookName){ this.bookName = bookName; } public String getBookName(){ return this.bookName; } public void setAuthor(String author){ this.author = author; } public String getAuthor(){ return author; } public String getPublisher(){ return publisher; } public void setPublisher(String publisher){ this.publisher = publisher; } public void setPrice(double price){ if(price < 15){ this.price = 10; System.out.println("价格必须大于10元,默认10元"); }else{ this.price = price; } } public double getPrice(){ return this.price; } public void show(){ System.out.println("bookName="+bookName+",author=" +author+",price="+price+",publisher="+publisher); } public static void main(String[] args) { //购买第一本书 Book book1 = new Book(); //book1.price = 3; book1.setBookName("鹿鼎记"); book1.setAuthor("金庸"); book1.setPrice(7); book1.setPublisher("清华大学出版社"); book1.show(); //购买第二本书 Book book2 = new Book("倚天屠龙记","金庸","清华大学出版社",30); book2.show(); System.out.println(book1.getBookName()); } }
本节作业
- private、默认、protected、public四个权限修饰符的作用?
- 使用面向对象的思想,编写自定义描述狗的信息。设定属性包括:品种,年龄,心情,名字;方法包括:叫,跑。要求:
- 设置属性的私有访问权限,通过公有的get,set方法实现对属性的访问
- 限定心情只能有“心情好”和“心情不好”两种情况,如果无效输入进行提示,默认设置“心情好”。
- 设置构造函数实现对属性赋值
- 叫和跑的方法,需要根据心情好坏,描述不同的行为方式。
- 编写测试类,测试狗类的对象及相关方法(测试数据信息自定义)
运行效果图:
第四节 继承性
继承(Inheritance)是面向对象编程的三大特征之一,它让我们更加容易实现对于已有类的扩展、更加容易实现对于现实世界的建模。
4.1. 继承及其作用
继承让我们更加容易实现类的扩展。 比如,我们定义了人类,再定义Boy类就只需要扩展人类即可。实现了代码的重用,不用再重新发明轮子(don’t reinvent wheels)。
从英文字面意思理解,extends的意思是“扩展”。子类是父类的扩展。现实世界中的继承无处不在。比如:
图5-1 现实世界中的继承
上图中,哺乳动物继承了动物。意味着,动物的特性,哺乳动物都有;在我们编程中,如果新定义一个Student类,发现已经有Person类包含了我们需要的属性和方法,那么Student类只需要继承Person类即可拥有Person类的属性和方法。
【示例9】实现继承
public class Animal {//extends Object private String color;//颜色 private int age;//年龄 public Animal() { super(); } public Animal(String color, int age) { //alt+insert this.color = color; this.age = age; } public void eat(){ System.out.println("eating .........."); } public void sleep(){ System.out.println("sleeping............"); } public void introduce(){ System.out.println(color+" "+age); } } public class Dog extends Animal{ private String nickName;//昵称 public Dog() { } public Dog(String color,int age){ } public Dog(String color,int age,String nickName){ // this.color = color; // this.age = age; super(color,age); this.nickName = nickName; } public void guard(){ System.out.println("Dog guarding......"); } } public class Cat extends Animal { private int eysSight;//视力 public Cat() { super();//默认调用父类的无参数构造方法 } public Cat(String color, int age, int eysSight) { super(color,age); this.eysSight = eysSight; } public void grabMouse(){ System.out.println("Cat grab mouse.........."); } } public class Test { public static void main(String[] args) { Dog dog = new Dog("黑色",3,"旺财"); dog.eat();//从父类继承的方法 dog.sleep();//从父类继承的方法 dog.introduce();//从父类继承的方法 dog.guard();//子类特有的方法 Cat cat = new Cat("花色",3,5);//alt+enter cat.eat(); cat.sleep(); cat.introduce(); cat.grabMouse(); } }
继承使用要点
- 父类也称作超类、基类。子类:派生类等。
- Java中只有单继承,没有像C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
- 子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
- 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
4.2. 方法重写
父类的方法introduce()已经无法满足子类的需求,怎么办?同理,Object类的toString()已经无法满足Animal类、Dog类的需求,怎么办?可通过方法重写(override)解决,或者称为方法覆盖。
【示例10】实现方法重写
public class Animal {//extends Object protected String color;//颜色 private int age;//年龄 public Animal() { super(); } public Animal(String color, int age) { this.color = color; this.age = age; } public void introduce(){ System.out.println(color+" "+age); } @Override public String toString() { //return super.toString(); return "Animal[color="+color+",age="+age+"]"; } } public class Dog extends Animal{ private String nickName;//昵称 public Dog() { } public Dog(String color, int age){ } public Dog(String color, int age, String nickName){ // this.color = color; // this.age = age; super(color,age); this.nickName = nickName; } public void introduce(){ //this.introduce(); //super.introduce(); System.out.println(color+" "+this.getAge()+" "+nickName); } public void guard(){ //this.guard(); //super.guard(); System.out.println("Dog guarding......"); } @Override public String toString() { //return super.toString()+" "+nickName; return "Dog[name="+color+",age="+getAge()+ ",nickName="+this.nickName+"]"; } }
总 |
方法重载和方法重写(覆盖)是面向对象中两个重要概念,其实这两个概念之间没有什么关系,但是毕竟都是关于方法的,毕竟容易引起混淆。对此我也做了一些归纳,感觉能够把这两个概念很好的区分开。我打算从总体区别、细节区别两个方面来说明。 |
||||||||||||||||||||||||||||||
分 |
总体的区别:最主要的区别,是解决的问题不同,即作用不同。
细节的区别:一个方法的声明自左向右包括权限修饰符、方法返回值、方法名、参数列表、抛出的异常类型等。下面从这几方面说明区别
|
||||||||||||||||||||||||||||||
总 |
重载实例:构造方法重载、println()方法重载 重写实例:Object类的toString()、equals()、hashCode()等都可以被子类重写 |
||||||||||||||||||||||||||||||
可选 |
|
本节作业
- 继承的作用
- 定义父类Animal、定义Dog、Cat类继承Animal类
- 方法重写和方法重载的比较
- 重写Animal、Dog、Cat的toString()