一、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变量的区别:
static | 非static | |
---|---|---|
份数不同 | 1份 | 1个对象一份 |
存储位置不同 | 方法区中 | 堆中 |
内存分配空间的时间不同 | 第一次类被加载的时候 | 创建对象的时候 |
生命周期不同 | 和类的生命周期相同 | 和所属的对象生命周期相同 |
调用方式不同 | 1.通过类名调用 Student.classRoom 2. 通过对象名调用 stu1.classRoom=“301”(不推荐使用) | 通过对象名调用 stu1.name=“小张” |
1.2 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代码块
局部代码块 | (成员)代码块 | 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");
}
}
二、 package和import
2.1 package包
- 为什么使用包
文件太多,并且会有同名文件,计算机的硬盘需要不同级别的文件夹来存储;
包机制是Java中管理类的重要手段。开发中,我们会遇到大量同名的类,通过包我们很容易对解决类重名的问题,也可以实现对类的有效管理。除了以上考虑外,还和访问权限有密切关系。 - 如何定义包
我们通过package实现对类的管理,package的使用有两个要点:
(1) 包名:域名倒着写即可,再加上模块名,便于内部管理类。
(2) 包名一律小写。 - 如何使用包
(1)通常是类的第一句非注释性语句。
(2)必须以;结尾。 - java常用包
注意事项:
1. 写项目时都要加包,不要使用默认包。 |
---|
2. 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帮助文档。

三、封装性
封装(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 表示私有,只有自己类能访问
- 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 封装练习
以面向对象的思想,编写自定义类描述图书信息。设定属性包括:书名,作者,出版社名,价格;方法包括:信息介绍
要求:
- 设置属性的私有访问权限,通过公有的get,set方法实现对属性的访问
- 限定价格必须大于10,如果无效进行提示
- 设计构造方法实现对属性赋值
- 信息介绍方法描述图书所有信息
- 编写测试类,测试图书类的对象及相关方法(测试数据信息自定)
【示例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());
}
}
四、继承性
继承(Inheritance)是面向对象编程的三大特征之一,它让我们更加容易实现对于已有类的扩展、更加容易实现对于现实世界的建模。
4.1 继承及其作用
继承让我们更加容易实现类的扩展。 比如,我们定义了人类,再定义Boy类就只需要扩展人类即可。实现了代码的重用,不用再重新发明轮子(don’t reinvent wheels)。
从英文字面意思理解,extends的意思是“扩展”。子类是父类的扩展。现实世界中的继承无处不在。比如:

上图中,哺乳动物继承了动物。意味着,动物的特性,哺乳动物都有;在我们编程中,如果新定义一个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+"]";
}
}
总结:
