文章目录
一、什么是类和对象?
说到类和对象,用一句话概括就是:
类就是类别的意思,人是人类这个类别,小猫,小狗是动物这个类别,苹果、香蕉是水果这个类别,我们知道每个类别里面会有很多的个体,比如说:人类里面有黄种人、白人、黑人、中国人、外国人等,他们都是人类这个类别,动物里面有小猫、小狗还有老虎、狮子等,它们都是动物这个类别,水果里面有苹果、香蕉还有梨等,它们都是水果这个类别。这样分析来看,它们为什么会被各自分为一类呢?因为它们有相同的特征,比如说人,我们有相同的属性和方法,我们每个人都有身高、体重、年龄、血型等属性,人会劳动、会直立行走、会用自己的头脑去创造工具等方法。人之所以能区别于其他类型的动物,是因为每个人都具有“人”这个群体的属性与方法。那么人类这个类别下的黄种人、白人、黑人、中国人、外国人等,它们就被称为对象,对象就是类的一个个体,那我们怎么区分每个对象呢?就是用属性和方法。比如下图:
这里类是:人,对象是:正在清洁的环卫工人小刘,分析一下,
对象的属性:
- 职业:环卫工人
- 工作:清洁工
对象的方法是:他能够打扫卫生。
这里的类是:汽车,对象是:一辆黄色的宝马跑车,分析一下,
对象的属性是:
- 颜色:黄色
- 类别:宝马牌
方法:能跑能开
概括一下就是:类的具体化就是对象,对象的抽象就是类
1.类的定义及可用关键字
在 Java 中定义一个类,需要使用 class 关键字、一个自定义的类名和一对表示程序体的大括号。完整语法如下:
[public][abstract|final]class<class_name>[extends<class_name>]
[implements<interface_name>]
{
//定义属性部分
<property_type><property1>;
<property_type><property2>;
<property_type><property3>;
…
//定义方法部分
function1();
function2();
function3();
…
}
[ ]中括号,括住的部分表示可加可不加
上述语法中各关键字的描述如下:
- public:表示“共有”的意思。如果使用 public 修饰,则可以被其他类和程序访问。每个 Java 程序的主类都必须是 public 类,作为公共工具供其他类和程序使用的类应定义为 public 类。
- abstract:如果类被 abstract 修饰,则该类为抽象类,抽象类不能被实例化,但抽象类中可以有抽象方法(使用 abstract 修饰的方法)和具体方法(没有使用 abstract 修饰的方法)。继承该抽象类的所有子类都必须实现该抽象 类中的所有抽象方法(除非子类也是 抽象类)。
- final:如果类被 final 修饰,则不允许被继承。
- class:声明类的关键字。
- class_name:类的名称。
- extends:表示继承其他类。
- implements:表示实现某些接口。
- property_type:表示成员变量的类型。
- property:表示成员变量名称。
- function():表示成员方法名称。
类名应该以下划线()或字母开头,最好以字母开头;第一个字母最好大写,如果类名由多个单词组成,则每个单词的首字母最好都大写;类名不能为 Java 中的关键字,例如 boolean、this、int 等;类名不能包含任何嵌入的空格或点号以及除了下划线()和美元符号($)字符之外的特殊字符。
下面来定义一个简单的 Person 类:
1. public class Person
2. {
3. private String name; // 姓名
4. private int age; // 年龄
5. public void teli()
6. { //定义说话的方法
7. System.out.println(name+"今年"+age+"岁!");
8. }
9. }
如上述代码,在 Person 类中首先定义了两个属性,分别为 name 和 age,然后定义了一个名称为 tell() 的方法。
2.成员变量的定义和声明
在 Java 中类的成员变量定义了类的属性。例如,一个学生类中一般需要有姓名、性别和年龄等属性,这时就需要定义姓名、性别和年龄 3 个属性。声明成员变量的语法如下:
[public|protected|private][static][final]<type><variable_name>
注:这里[ ]中表示可加可不加的内容
各参数的含义如下:
- public、protected、private:用于表示成员变量的访问权限。
- static:表示该成员变量为类变量,也称为静态变量。
- final:表示将该成员变量声明为常量,其值无法更改。
- type:表示变量的类型。
- variable_name:表示变量名称。
可以在声明成员变量的同时对其进行初始化,如果声明成员变量时没有对其初始化,则系统会使用默认值初始化成员变量。
初始化的默认值如下:
- 整数型(byte、short、int 和 long)的基本类型变量的默认值为 0。
- 单精度浮点型(float)的基本类型变量的默认值为 0.0f。
- 双精度浮点型(double)的基本类型变量的默认值为 0.0d。
- 字符型(char)的基本类型变量的默认值为 “\u0000”。
- 布尔型的基本类型变量的默认值为 false。
- 数组引用类型的变量的默认值为 null。如果创建了数组变量的实例,但没有显式地为每个元素赋值,则数组中的元素初始化值采用数组数据类型对应的默认值。
定义类的成员变量的示例如下:
1. public class Student
2. {
3. public String name; //姓名
4. final int sex=0; //性别:0表示女孩,1表示男孩
5. private int age; //年龄
6. }
上述示例的 Student 类中定义了 3 个成员变量:String 类型的 name、int 类型的 sex 和 int 类型的 age。其中,name 的访问修饰符为 public,初始化值为 null;sex 的访问修饰符为 friendly(默认),初始化值为 0,表示性别为女,且其值无法更改;age 的访问修饰符为 private,初始化值为 0。
下面以一个简单的例子来介绍成员变量的初始值,代码如下所示:
1. public class Counter
2. {
3. static int sum;
4. private int i;
5. private void setI(int i){
6. this.i=i;
7. }
8. private int getI(){
9. return i;
10. }
11. public static void main(String[] args)
12. {
13. Counter c=new Counter();
14. System.out.println(sum);
15. System.out.println(c.i);
16. System.out.println(c.getI());
17. }
18. }
在这里用静态的方法来修饰变量 sum,输出结果是 int 类型的初始值,即:0。
这里我要说一下,静态static,被static修饰的字段为,静态字段,他的范围为整个类,所以可以直接调用输出,而如果是实例变量的话,需要先new一个对象,通过对象调用get方法或者对象.字段,才能得到他的值。
3.面向对象的三大基本特征
面向对象开发模式更有利于人们开拓思维,在具体的开发过程中便于程序的划分,方便程序员分工合作,提高开发效率。面向对象程序设计有以下优点。
该开发模式之所以使程序设计更加完善和强大,主要是因为面向对象具有封装、继承、多态 3 个核心特性。
3.1封装性
类的基本作用就是封装代码。封装将类的一些特征和行为隐藏在类内部,不允许类外部直接访问。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
我们可以通过类提供的方法来实现对隐藏信息的操作和访问。隐藏了对象的信息,留出了访问的接口。
在我们日常生活中,封装与我们息息相关,智能手机就是一个拥有良好封装的例子,我们不需要关心其内部复杂的逻辑电路设计,可以通过手机的屏幕、按键、充电口、耳机接口等等外部接口来对手机进行操作和使用。复杂的逻辑电路以及模块被封装在手机的内部,而留出的这些必要接口,让我们更加简便地使用手机的同时也保护了手机的内部细节。
①封装有两个特点:
- 只能通过规定的方法访问数据;
- 隐藏类的实例细节,方便修改和实现。
②封装具有以下优点:
- 封装有利于提高类的内聚性,适当的封装可以让代码更容易理解与维护;
- 良好的封装有利于降低代码的耦合度;
- 一些关键属性只允许类内部可以访问和修改,增强类的安全性;
- 隐藏实现细节,为调用方提供易于理解的接口;
- 当需求发生变动时,我们只需要修改我们封装的代码,而不需要到处修改调用处的代码。
③在 Java 语言中,如何实现封装呢?需要 2个步骤。
- 修改属性的可见性为private,让其只在类的内部可以访问:
- 创建公开的 getter 和 setter 方法,留出接口方法,分别用于属性的读写,这样在类的外部就可以通过调用其方法对属性进行操作了。
3.2 继承性
继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。这是类之间的一种关系;继承只能是单继承。
①特点
- Java 中的继承为单一继承,也就是说,一个子类只能拥有一个父类,一个父类可以拥有多个子类。
- 另外,所有的 Java 类都继承自 Java.lang.Object,所以 Object 是所有类的祖先类,除了 Object 外,所有类都必须有一个父类。我们在定义类的时候没有显示指定其父类,它默认就继承自 Object 类。
- 子类一旦继承父类,就会继承父类所有开放的特征,不能选择性地继承父类特征。
- 继承体现的是类与类之间的关系,这种关系是 is a 的关系,也就是说满足 A is a B 的关系就可以形成继承关系。
②实现继承
定义父类 SuperClass:
// 父类
class SuperClass {
...
}
在 Java 语言中,我们通过 extends 关键字声明一个类继承自另一个类:
// 子类
class SubClass extends SuperClass {
...
}
例如,宠物猫和宠物狗都是宠物,都有昵称、年龄等属性,都有吃东西、叫喊等行为。我们可以定义一个父类:宠物类。并且宠物猫和宠物狗类都继承宠物类,继承树形图如下:
代码实现:
public class Pet {
private String name; // 昵称
private int age; // 年龄
// getter 和 setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 吃东西
public void eat() {
System.out.println(this.getName() + "在吃东西");
}
// 叫喊
public void shout() {
System.out.println("宠物会叫");
}
}
父类宠物类中 name 和 age 都是私有属性,而对应的 getter、setter 方法,eat 和 shout 方法都是公有方法。
宠物狗类:
public class Dog extends Pet {
// 特有属性体重
private float weight;
// getter和setter
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
// 特有的方法 run
public void run() {
System.out.println("胖成了" + this.getWeight() + "斤的狗子在奔跑");
}
}
宠物狗类有一个自己特有的属性 weight,还有一个特有的方法 run。
宠物猫类:
public class Cat extends Pet {
public void sleep() {
System.out.println(this.getName() + "睡大觉zzz");
}
}
宠物猫类有一个特有的方法 sleep,在方法中可以调用其父类 Pet 的 getName 方法。
调用类的方法:
// 实例化一个宠物狗
Dog dog = new Dog();
dog.setName("欢欢");
dog.setWeight(30f);
// 调用继承自父类的公有方法
dog.eat();
// 调用其特有方法
dog.run();
// 实例化一个宠物猫
Cat cat = new Cat();
cat.setName("豆豆");
// 调用继承自父类的公有方法
cat.eat();
// 调用其特有方法
cat.sleep();
运行结果:
欢欢在吃东西
胖成了30.0斤的狗子欢欢在奔跑
豆豆在吃东西
豆豆睡大觉zzz
③方法重写
如果一个类从它的父类继承了一个方法,如果这个方法没有被标记为 final 或 static,就可以对这个方法进行重写。重写的好处是:能够定义特定于子类类型的行为,这意味着子类能够基于要求来实现父类的方法。
实例
在上述父类 Pet 中有一个 shout 方法,我们知道小猫和小狗的叫声是不同的,此时可以使用方法重写,在 Dog 类和 Cat 类中重写 shout 方法。
Dog 类:
class Dog extends Pet{
// 重写 shout 方法
public void shout() {
System.out.println(this.getName() + "汪汪汪地叫~");
}
}
Cat 类:
class Cat extends Pet{
@Override // 使用注解
public void shout() {
System.out.println(this.getName() + "喵喵喵地叫~");
}
}
注意:
在要重写的方法上面,可以选择使用 @Override 注解,让编译器帮助检查是否进行了正确的重写。如果重写有误,编译器会提示错误。虽然这个注解不是必须的,但建议日常编码中,在所有要重写的方法上都加上 @Override 注解,这样可以避免我们由于马虎造成的错误。
可以使用对象实例调用其重写的方法:
// 实例化一个宠物狗
Dog dog = new Dog();
dog.setName("欢欢");
// 调用重写方法
dog.shout();
// 实例化一个宠物猫
Cat cat = new Cat();
cat.setName("豆豆");
// 调用重写方法
cat.shout();
运行结果:
欢欢汪汪汪地叫~
豆豆喵喵喵地叫~
④方法重写规则
关于方法重写,有以下规则:
- 重写方法的参数列表应该与原方法完全相同;
- 返回值类型应该和原方法的返回值类型一样或者是它在父类定义时的子类型;
- 重写方法访问级别限制不能比原方法高。例如:如果父类方法声明为公有的,那么子类中的重写方法不能是私有的或是保护的。具体限制级别参考访问修饰符;
- 只有被子类继承时,方法才能被重写;
- 方法定义为 final,将不能被重写;
- 一个方法被定义为 static,将使其不能被重写,但是可以重新声明;
- 一个方法不能被继承,那么也不能被重写;
- 和父类在一个包中的子类能够重写任何没有被声明为 private 和 final 的父类方法;
- 和父类不在同一个包中的子类只能重写 non-final 方法或被声明为 public 或 protected 的方法;
- 一个重写方法能够抛出任何运行时异常,不管被重写方法是否抛出异常。然而重写方法不应该抛出比被重写方法声明的更新更广泛的已检查异常。重写方法能够抛出比被重写方法更窄或更少的异常;
- 构造方法不能重写。
⑤方法重写和方法重载的区别
Java 中的方法重写(Overriding)是说子类重新定义了父类的方法。方法重写必须有相同的方法名,参数列表和返回类型。覆盖者访问修饰符的限定大于等于父类方法。
而方法重载(Overloading)发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。
⑥super 关键字
super 是用在子类中的,目的是访问直接父类的变量或方法。注意:
- super 关键字只能调用父类的 public 以及 protected 成员;
- super 关键字可以用在子类构造方法中调用父类构造方法;
- super 关键字不能用于静态 (static) 方法中。
⑦super 与 this 的对比
this 关键字指向当前类对象的引用,它的使用场景为:
- 访问当前类的成员属性和成员方法;
- 访问当前类的构造方法;
- 不能在静态方法中使用。
super 关键字指向父类对象的引用,它的使用场景为:
- 访问父类的成员属性和成员方法;
- 访问父类的构造方法;
- 不能在静态方法中使用。
另外,需要注意的是,在构造方法调用时,super 和 this 关键字不能同时出现。
3.3多态性
多态顾名思义就是多种形态,是指对象能够有多种形态。在面向对象中最常用的多态性发生在当父类引用指向子类对象时。在面向对象编程中,所谓多态意指相同的消息给予不同的对象会引发不同的动作。换句话说:多态意味着允许不同类的对象对同一消息做出不同的响应。
例如,火车类和飞机类都继承自交通工具类,这些类下都有各自的run()方法,交通工具的run()方法输出交通工具可以运输,而火车的run()方法输出火车会跑,飞机的run()方法则输出飞机会飞,火车和飞机都继承父类的run()方法,但是对于不同的对象,拥有不同的操作。
任何可以通过多个IS-A测试的 Java 对象都被视为多态的。在 Java 中,所有 Java 对象都是多态的,因为任何对象都能够通过IS-A测试以获取其自身类型和 Object 类。
①实现多态
在 Java 中实现多态有 3 个必要条件:
- 满足继承关系
- 要有重写
- 父类引用指向子类对象
例如,有三个类Pet、Dog、Cat:
父类Pet:
class Pet {
// 定义方法 eat
public void eat() {
System.out.println("宠物吃东西");
}
}
子类Dog继承Pet
class Dog extends Pet { // 继承父类
// 重写父类方法 eat
public void eat() {
System.out.println("狗狗吃狗粮");
}
}
子类Cat继承Pet
class Cat extends Pet { // 继承父类
// 重写父类方法 eat
public void eat() {
System.out.println("猫猫吃猫粮");
}
}
在代码中,我们看到Dog和Cat类继承自Pet类,并且都重写了其eat方法。
现在已经满足了实现多态的前两个条件,那么如何让父类引用指向子类对象呢?我们在main方法中编写代码:
public void main(String[] args) {
// 分别实例化三个对象,并且保持其类型为父类Pet
Pet pet = new Pet();
Pet dog = new Dog();
Pet cat = new Cat();
// 调用对象下方法
pet.eat();
dog.eat();
cat.eat();
}
运行结果:
宠物吃东西
狗狗吃狗粮
猫猫吃猫粮
在代码中,Pet dog = new Dog();、Pet cat = new Cat();这两个语句,把Dog和Cat对象转换为Pet对象,这种把一个子类对象转型为父类对象的做法称为向上转型。父类引用指向了子类的实例。也就实现了多态。
②向上转型
向上转型又称为自动转型、隐式转型。向上转型就是父类引用指向子类实例,也就是子类的对象可以赋值给父类对象。例如:
Pet dog = new Dog();
这个是因为Dog类继承自Pet类,它拥有父类Pet的全部功能,所以如果Pet类型的变量指向了其子类Dog的实例,是不会出现问题的。
向上转型实际上是把一个子类型安全地变成了更加抽象的父类型,由于所有类的根类都是Object,我们也把子类类型转换为Object类型:
Cat cat = new Cat();
Object o = cat;
③向下转型
向上转型是父类引用指向子类实例,那么如何让子类引用指向父类实例呢?使用向下转型就可以实现。向下转型也被称为强制类型转换。例如:
// 为Cat类增加run方法
class Cat extends Pet { // 继承父类
// 重写父类方法 eat
public void eat() {
System.out.println("猫猫吃猫粮");
}
public void run() {
System.out.println("猫猫跑步");
}
public static void main(String[] args) {
// 实例化子类
Pet cat = new Cat();
// 强制类型转换,只有转换为Cat对象后,才能调用其下面的run方法
Cat catObj = (Cat)cat;
catObj.run();
}
}
运行结果:
猫猫跑步
我们为Cat类新增了一个run方法,此时我们无法通过Pet类型的cat实例调用到其下面特有的run方法,需要向下转型,通过(Cat)cat将Pet类型的对象强制转换为Cat类型,这个时候就可以调用run方法了。
使用向下转型的时候,要注意:不能将父类对象转换为子类类型,也不能将兄弟类对象相互转换。以下两种都是错误的做法:
// 实例化父类
Pet pet = new Pet();
// 将父类转换为子类
Cat cat = (Cat) pet;
// 实例化Dog类
Dog dog = new Dog();
// 兄弟类转换
Cat catObj = (Cat) dog;
④instanceof 运算符
instanceof运算符用来检查对象引用是否是类型的实例,或者这个类型的子类,并返回布尔值。如果是返回true,如果不是返回false。通常可以在运行时使用 instanceof 运算符指出某个对象是否满足一个特定类型的实例特征。其使用语法为:
<对象引用> instanceof 特定类型
例如,在向下转型之前,可以使用instanceof运算符判断,这样可以提高向下转型的安全性:
Pet pet = new Cat();
if (pet instanceof Cat) {
// 将父类转换为子类
Cat cat = (Cat) pet;
}
二、创建对象的几种方法
对象是对类的实例化。对象具有状态和行为,变量用来表明对象的状态,方法表明对象所具有的行为。Java 对象的生命周期包括创建、使用和清除,接下来详细介绍对象的创建,在 Java 语言中创建对象分显式创建与隐含创建两种情况。
1.显式创建对象
对象的显式创建方式有 4 种。
- 使用 new 关键字创建对象
这是常用的创建对象的方法,语法格式如下:
类名 对象名=new 类名();
- 调用 java.lang.Class 或者 java.lang.reflect.Constuctor 类的 newlnstance() 实例方法
在 Java 中,可以使用 java.lang.Class 或者 java.lang.reflect.Constuctor 类的 newlnstance() 实例方法来创建对象,代码格式如下:
Class 类对象名称=Class.forName(要实例化的类全称);
类名 对象名=(类名)Class类对象名称.newInstance();
调用 java.lang.Class 类中的 forName() 方法时,需要将要实例化的类的全称(比如 com.mxl.package.Student)作为参数传递过去,然后再调用 java.lang.Class 类对象的 newInstance() 方法创建对象。
- 调用对象的 clone() 方法
该方法不常用,使用该方法创建对象时,要实例化的类必须继承 java.lang.Cloneable 接口。 调用对象的 clone() 方法创建对象的语法格式如下:
类名对象名=(类名)已创建好的类对象名.clone();
- 调用 java.io.ObjectlnputStream 对象的 readObject() 方法
下面创建一个示例演示常用的前三种对象创建方法。示例代码如下:
public class Student implements Cloneable {
// 实现 Cloneable 接口
private String Name; // 学生名字
private int age; // 学生年龄
public Student(String name, int age) { // 构造方法
this.Name = name;
this.age = age;
}
public Student() {
this.Name = "name";
this.age = 0;
}
public String toString() {
return "学生名字:" + Name + ",年龄:" + age;
}
public static void main(String[] args) throws Exception {
System.out.println("---------使用 new 关键字创建对象---------");
// 使用new关键字创建对象
Student student1 = new Student("小刘", 22);
System.out.println(student1);
System.out.println("-----------调用 java.lang.Class 的 newInstance() 方法创建对象-----------");
// 调用 java.lang.Class 的 newInstance() 方法创建对象
Class cl = Class.forName("Student");
Student student2 = (Student) cl.newInstance();
System.out.println(student2);
System.out.println("-------------------调用对象的 clone() 方法创建对象----------");
// 调用对象的 clone() 方法创建对象
Student student3 = (Student) student2.clone();
System.out.println(student3);
}
}
程序执行结果如下:
--------使用 new 关键字创建对象---------
学生名字:小刘,年龄:22
-----------调用 java.lang.Class 的 newInstance() 方法创建对象-----------
学生名字:name,年龄:0
-------------------调用对象的done()方法创建对象----------
学生名字:name,年龄:0
2.隐含创建对象
除了显式创建对象以外,在 Java 程序中还可以隐含地创建对象,例如下面几种情况。
①String strName=“strValue”,其中的“strValue”就是一个 String 对象,由 Java 虚拟机隐含地创建。
②字符串的“+”运算符运算的结果为一个新的 String 对象,示例如下:
1. String str1="Hello";
2. String str2="Java";
3. String str3=str1+str2; //str3引用一个新的String对象
③当 Java 虚拟机加载一个类时,会隐含地创建描述这个类的 Class 实例。
提示:类的加载是指把类的 .class 文件中的二进制数据读入内存中,把它存放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。
无论釆用哪种方式创建对象,Java 虚拟机在创建一个对象时都包含以下步骤:
- 给对象分配内存。
- 将对象的实例变量自动初始化为其变量类型的默认值。
- 初始化对象,给实例变量赋予正确的初始值。
注意:每个对象都是相互独立的,在内存中占有独立的内存地址,并且每个对象都具有自己的生命周期,当一个对象的生命周期结束时,对象就变成了垃圾,由 Java 虚拟机自带的垃圾回收机制处理。
三、访问对象属性和行为
每个对象都有自己的属性和行为,这些属性和行为在类中体现为成员变量和成员方法,其中成员变量对应对象的属性,成员方法对应对象的行为。
在 Java 中,要引用对象的属性和行为,需要使用点(.)操作符来访问。对象名在圆点左边,而成员变量或成员方法的名称在圆点的右边。语法格式如下:
对象名.属性(成员变量) //访问对象的属性
对象名.成员方法名() //访问对象的方法
例如,定义一个 Student 类,创建该类的对象 stu,再对该对象的属性赋值,代码如下:
1. Student stu=new Student(); //创建 Student 类的对象 stu
2. stu.Name="李子文"; //调用stu对象的Name属性并赋值
3. stu.Sex=true; //调用stu对象的Sex属性并赋值
4. stu.Age=15; //调用stu对象的Age属性并赋值
四、对象的销毁
对象使用完之后需要对其进行清除。对象的清除是指释放对象占用的内存。在创建对象时,用户必须使用 new 操作符为对象分配内存。不过,在清除对象时,由系统自动进行内存回收,不需要用户额外处理。这也是 Java 语言的一大特色,某种程度上方便了程序员对内存的管理。
java 语言的内存自动回收称为垃圾回收(Garbage Collection)机制,简称 GC。垃圾回收机制是指 JVM 用于释放那些不再使用的对象所占用的内存。
java 语言并不要求 JVM 有 GC,也没有规定 GC 如何工作。不过常用的 JVM 都有 GC,而且大多数 GC 都使用类似的算法管理内存和执行回收操作。具体的垃圾回收实现策略有好多种,在此不再赘述。
在 Java 的 Object 类中还提供了一个 protected 类型的 finalize() 方法,因此任何 Java 类都可以覆盖这个方法,在这个方法中进行释放对象所占有的相关资源的操作。
五、成员方法的声明和调用
声明成员方法可以定义类的行为,行为表示一个对象能够做的事情或者能够从一个对象取得的信息。类的各种功能操作都是用方法来实现的,属性只不过提供了相应的数据。一个完整的方法通常包括方法名称、方法主体、方法参数和方法返回值类型,其结构如图 1 所示。
成员方法一旦被定义,便可以在程序中多次调用,提高了编程效率。声明成员方法的语法格式如下:
1. public class Test
2. {
3. [public|private|protected][static]<void|return_type><method_name>([paramList])
4. {
5. //方法体
6. }
7. }
上述代码中一个方法包含 4 部分:方法的返回值、方法名称、方法的参数和方法体。其中 return_type 是方法返回值的数据类型,数据类型可以是原始的数据类型,即常用的 8 种数据类型,也可以是一个引用数据类型,如一个类、接口和数组等。
除了这些,一个方法还可以没有返回值,即返回类型为 void,像 main() 方法。method_name 表示自定义的方法名称,方法的名称首先要遵循标识符的命名约定,除此之外,方法的名称第一个单词的第一个字母是小写,第二单词的第一个字母是大写,依此类推。
paramList 表示参数列表,这些变量都要有自己的数据类型,可以是原始数据类型,也可以是复杂数据类型,一个方法主要依靠参数来传递消息。方法主体是方法中执行功能操作的语句。其他各修饰符的含义如下。
- public、private、protected:表示成员方法的访问权限。
- static:表示限定该成员方法为静态 方法。
- final:表示限定该成员方法不能被重写或重载。
- abstract:表示限定该成员方法为抽象方法。抽象方法不提供具体的实现,并且所属类型必须为抽象类。
例 1
为上一节创建的学生类 Student 添加一个可以返回学生信息字符串的方法。代码如下:
1. public class Student
2. {
3. public StringBuffer printInfo(Student st)
4. {
5. StringBuffer sb=new StringBuffer();
6. sb.append("学生姓名:"+st.Name+"\n 学生年龄:"+st.Age+"\n 学生性别:"+st.isSex());
7. return sb;
8. }
9. }
上述代码创建了一个名称为 printInfo 的方法,其返回值类型为 StringBuffer(引用数据类型)。该方法需要传递一个 Student 类型的参数,最后需要将一个 StringBuffer 类型的数据返回。
1. 成员方法的返回值
若方法有返回值,则在方法体中用 return 语句指明要返回的值,其格式如下所示。
return表达式
或者
return(表达式)
其中,表达式可以是常量、变量、对象等。表达式的数据类型必须与声明成员方法时给出的返回值类型一致。
2. 形参、实参及成员方法的调用
一般来说,可以通过以下方式来调用成员方法:
methodName({paramList})
关于方法的参数,经常会提到形参与实参,形参是定义方法时参数列表中出现的参数,实参是调用方法时为方法传递的参数。
例 2
下面 retumMin() 方法中的 m 和 n 是形参,调用 retumMin() 方法时的 x 和 y 是实参。
1. public int returnMin(int m,int n)
2. {
3. return Math.min(m,n); //m和n是形参
4. }
5. public static void main(String[] args)
6. {
7. int x=50;
8. int y=100;
9. Test t=new Test();
10. int i=t.returnMin(x,y); //x和y是实参
11. System.out.println(i);
12. }
方法的形参和实参具有以下特点:
- 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在方法内部有效,方法调用结束返回主调 方法后则不能再使用该形参变量。
- 实参可以是常量、变量、表达式、方法等,无论实参是何种类型的量,在进行方法调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值、输入等办法使实参获得确定值。
- 实参和形参在数量、类型和顺序上应严格一致,否则会发生“类型不匹配” 的错误。
- 方法调用中发生的数据传送是单向的,即只能把实参的值传送绐形参,而不能把形参的值反向地传送给实参。因此在方法调用过程中,形参的值发生改变,而实参中的值不会变化。
例 3
下面的示例演示了调用 add() 方法前后形参 x 的变化。
1. public int add(int x)
2. {
3. x+=30;
4. System.out.println("形参 x 的值:"+x);
5. return x;
6. }
7. public static void main(String[] args)
8. {
9. int x=150;
10. System.out.println("调用 add() 方法之前 x 的值:"+x);
11. Test t=new Test();
12. int i=t.add(x);
13. System.out.println("实参 x 的值:"+x);
14. System.out.println("调用 add() 方法的返回值:"+i);
15. }
运行上述程序,输出结果如下:
调用 add() 方法之前 x 的值:150
形参 x 的值:180
实参 x 的值:150
调用 add() 方法的返回值:180
从输出结果可以看出,形参 x 值的改变,并没有影响实参 x。
在调用成员方法时应注意以下 4 点:
- 对无参成员方法来说,是没有实际参数列表的(即没有 paramList),但方法名后的括号不能省略。
- 对带参数的成员方法来说,实参的个数、顺序以及它们的数据类型必须与形式参数的个数、顺序以及它们的数据类型保持一致,各个实参间用逗号分隔。实参名与形参名可以相同,也可以不同。
- 实参也可以是表达式,此时一定要注意使表达式的数据类型与形参的数据类型相同,或者使表达式的类型按 Java 类型转换规则达到形参指明的数据类型。
- 实参变量对形参变量的数据传递是“值传递”,即只能由实参传递给形参,而不能由形参传递给实参。程序中执行到调用成员方法时,Java 把实参值复制到一个临时的存储区(栈)中,形参的任何修改都在栈中进行,当退出该成员方法时,Java 自动清除栈中的内容。
3. 方法体中的局部变量
在方法体内可以定义本方法所使用的变量,这种变量是局部变量。它的生存期与作用域是在本方法内,也就是说,局部变量只能在本方法内有效或可见,离开本方法则这些变量将被自动释放。
在方法体内定义变量时,变量前不能加修饰符。局部变量在使用前必须明确赋值,否则编译时会出错。另外,在一个方法内部,可以在复合语句中定义变量,这些变量只在复合语句中有效。
六、构造方法和析构方法
1.构造方法
构造方法是类的一种特殊方法,用来初始化类的一个新对象。Java 中的每个类都有一个默认的构造方法,它必须具有和类名相同的名称,而且没有返回类型。构造方法的默认返回类型就是对象类型本身,并且构造方法不能被 static、final、synchronized、abstract 和 native 修饰。
提示:构造方法用于初始化一个新对象,所以用 static 修饰没有意义;构造方法不能被子类继承,所以用 final 和 abstract 修饰没有意义;多个线程不会同时创建内存地址相同的同一个对象,所以用 synchronized 修饰没有必要。
构造方法的语法格式如下:
1. class class_name
2. {
3. public class_name(){} //默认无参构造方法
4. public ciass_name([paramList]){} //定义构造方法
5. …
6. //类主体
7. }
在一个类中,与类名相同的方法就是构造方法。每个类可以具有多个构造方法,但要求它们各自包含不同的方法参数。
例 1
构造方法主要有无参构造方法和有参构造方法两种,示例如下:
1. public class MyClass
2. {
3. private int m; //定义私有变量
4. MyClass()
5. {
6. //定义无参的构造方法
7. m=0;
8. }
9. MyCiass(int m)
10. {
11. //定义有参的构造方法
12. this.m=m;
13. }
14. }
该示例定义了两个构造方法,分别是无参构造方法和有参构造方法。在一个类中定义多个具有不同参数的同名方法,这就是方法的重载。这两个构造方法的名称都与类名相同,均为 MyClass。在实例化该类时可以调用不同的构造方法进行初始化。
注意:类的构造方法不是要求必须定义的。如果在类中没有定义任何一个构造方法,则 Java 会自动为该类生成一个默认的构造方法。默认的构造方法不包含任何参数,并且方法体为空。如果类中显式地定义了一个或多个构造方法,则 Java 不再提供默认构造方法。
例 2
要在不同的条件下使用不同的初始化行为创建类的对象,这时候就需要在一个类中创建多个构造方法。下面通过一个示例来演示构造方法的使用。
(1) 首先在员工类 Worker 中定义两个构造方法,代码如下:
1. public class Worker
2. {
3. public String name; //姓名
4. private int age; //年龄
5. //定义带有一个参数的构造方法
6. public Worker(String name)
7. {
8. this.name=name;
9. }
10. //定义带有两个参数的构造方法
11. public Worker(String name,int age)
12. {
13. this.name=name;
14. this.age=age;
15. }
16. public String toString()
17. {
18. return"大家好!我是新来的员工,我叫"+name+",今年"+age+"岁。";
19. }
20. }
在 Worker 类中定义了两个属性,其中 name 属性不可改变。分别定义了带有一个参数和带有两个参数的构造方法,并对其属性进行初始化。最后定义了该类的 toString() 方法,返回一条新进员工的介绍语句。
提示:Object 类具有一个 toString() 方法,该方法是个特殊的方法,创建的每个类都会继承该方法,它返回一个 String 类型的字符串。如果一个类中定义了该方法,则在调用该类对象时,将会自动调用该类对象的 toString() 方法返回一个字符串,然后使用“System.out.println(对象名)”就可以将返回的字符串内容打印出来。
(2) 在 TestWorker 类中创建 main() 方法作为程序的入口处,在 main() 方法中调用不同的构造方法实例化 Worker 对象,并对该对象中的属性进行初始化,代码如下:
1. public class TestWorker
2. {
3. public static void main(String[] args)
4. {
5. System.out.println("-----------带有一个参数的构造方法-----------");
6. //调用带有一个参数的构造方法,Staff类中的sex和age属性值不变
7. Worker worker1=new Worker("张强");
8. System.out.println(worker1);
9. System.out.println("-----------带有两个参数的构造方法------------");
10. //调用带有两个参数的构造方法,Staff类中的sex属性值不变
11. Worker worker2=new Worker("李丽",25);
12. System.out.println(worker2);
13. }
14. }
在上述代码中,创建了两个不同的 Worker 对象:一个是姓名为张强的员工对象,一个是姓名为李丽、年龄为 25 的员工对象。对于第一个 Worker 对象 Worker1,并未指定 age 属性值,因此程序会将其值采用默认值 0。对于第二个 Worker 对象 Worker2,分别对其指定了 name 属性值和 age 属性值,因此程序会将传递的参数值重新赋值给 Worker 类中的属性值。
运行 TestWorker 类,输出的结果如下:
-----------带有一个参数的构造方法-----------
大家好!我是新来的员工,我叫张强,今年0岁。
-----------带有两个参数的构造方法------------
大家好!我是新来的员工,我叫李丽,今年25岁。
通过调用带参数的构造方法,在创建对象时,一并完成了对象成员的初始化工作,简化了对象初始化的代码。
2.析构方法
析构方法与构造方法相反,当对象脱离其作用域时(例如对象所在的方法已调用完毕),系统自动执行析构方法。析构方法往往用来做清理垃圾碎片的工作,例如在建立对象时用 new 开辟了一片内存空间,应退出前在析构方法中将其释放。
在 Java 的 Object 类中还提供了一个 protected 类型的 finalize() 方法,因此任何 Java 类都可以覆盖这个方法,在这个方法中进行释放对象所占有的相关资源的操作。
对象的 finalize() 方法具有如下特点:
- 垃圾回收器是否会执行该方法以及何时执行该方法,都是不确定的。
- finalize() 方法有可能使用对象复活,使对象恢复到可触及状态。
- 垃圾回收器在执行 finalize() 方法时,如果出现异常,垃圾回收器不会报告异常,程序继续正常运行。
例如:
1. protected void finalize()
2. {
3. //对象的清理工作
4. }
例 1
下面通过一个例子来讲解析构方法的使用。该例子计算从类中实例化对象的个数。
(1) Counter 类在构造方法中增值,在析构方法中减值。如下所示为计数器类 Counter 的代码:
1. public class Counter
2. {
3. private static int count=0; //计数器变量
4. public Counter()
5. {
6. //构造方法
7. this.count++; //创建实例时增加值
8. }
9. public int getCount()
10. {
11. //获取计数器的值
12. return this.count;
13. }
14. protected void finalize()
15. {
16. //析构方法
17. this.count--; //实例销毁时减少值
18. System.out.println("对象销毁");
19. }
20. }
(2) 创建一个带 main() 的 TestCounter 类对计数器进行测试,示例代码如下:
1. public class TestCounter
2. {
3. public static void main(String[] args)
4. {
5. Counter cnt1=new Counter(); //建立第一个实例
6. System.out.println("数量:"+cnt1.getCount()); //输出1
7. Counter cnt2=new Counter(); //建立第二个实例
8. System.out.println("数量:"+cnt2.getCount()); //输出2
9. cnt2=null; //销毁实例2
10. try
11. {
12. System.gc(); //清理内存
13. Thread.currentThread().sleep(1000); //延时1000毫秒
14. System.out.println("数量:"+cnt1.getCount()); //输出1
15. }
16. catch(InterruptedException e)
17. {
18. e.printStackTrace();
19. }
20. }
21. }
执行后输出结果如下:
数量:1
数量:2
对象销毁
数量:1
技巧:由于 finalize() 方法的不确定性,所以在程序中可以调用 System.gc() 或者 Runtime.gc() 方法提示垃圾回收器尽快执行垃圾回收操作。
一般开发中用不到这个析构方法,因为java会自动回收。
七、可变参数的方法
在具体实际开发过程中,有时方法中参数的个数是不确定的。为了解决这个问题,在 J2SE 5.0 版本中引入了可变参数的概念。
声明可变参数的语法格式如下:
methodName({paramList},paramType…paramName)
其中,methodName 表示方法名称;paramList 表示方法的固定参数列表;paramType 表示可变参数的类型;… 是声明可变参数的标识;paramName 表示可变参数名称。
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。 可变参数在方法中被当作数组来处理
注意:可变参数必须定义在参数列表的最后。
例 1
每次参加考试的人数是不固定的,但是每次考试完之后都需要打印出本次考试的总人数以及参加考试的学生名单。下面编写程序,使用方法的可变参数实现该功能,具体的代码如下:
1. public class StudentTestMethod
2. {
3. //定义输出考试学生的人数及姓名的方法
4. public void print(String...names)
5. {
6. int count=names.length; //获取总个数
7. System.out.println("本次参加考试的有"+count+"人,名单如下:");
8. for(int i=0;i<names.length;i++)
9. {
10. System.out.println(names[i]);
11. }
12. }
13. public static void main(String[] args)
14. {
15. //TODO Auto-generated method stub
16. StudentTestMethod student=new StudentTestMethod();
17. student.print("张强","李成","王勇"); //传入3个值
18. student.print("马丽","陈玲");
19. }
20. }
在 Student TestMethod 类中定义了 print() 方法和 main() 方法。print() 方法声明了一个 String 类型的可变参数,方法体打印可变参数的总个数以及参数值。在 main() 方法中创建了 StudentTestMethod 类的实例,然后分别传入不同个数的参数调用 print() 方法。
运行 StudentTestMethod 类,输出结果如下:
本次参加考试的有3人,名单如下:
张强
李成
王勇
本次参加考试的有2人,名单如下:
马丽
陈玲
八、访问控制修饰符( public、private、protected、friendly)
在 Java 语言中提供了多个作用域修饰符,其中常用的有 public、private、protected、final、abstract、static、transient 和 volatile,这些修饰符有类修饰符、变量修饰符和方法修饰符。本文将详细介绍访问控制修饰符。
在实际生活中,如果要获取某件物品,与其直接穿过堡垒的墙壁,从而导致墙壁毁灭和破坏,不如通过门口的警卫请求进入堡垒的许可。一般而言,这对对象同样适用:没有对象的许可(即对象的属性是私有的),不能直接访问该对象的私有属性。
信息隐藏是 OOP 最重要的功能之一,也是使用访问修饰符的原因。在编写程序时,有些核心数据往往不希望被用户调用,需要控制这些数据的访问。
对类成员访问的限制是面向对象程序设计的一个基础,这有利于防止对象的误用。只允许通过一系列定义完善的方法来访问私有数据,就可以(通过执行范围检查)防止数据赋予不正当的值。例如,类以外的代码不可能直接向一个私有成员赋值。同时,还可以精确地控制如何以及何时使用对象中的数据。
当正确实现对类成员的方法控制后,类就可以创建一个可用的“黑箱”,其内部动作不会被打开而任意篡改。
通过使用访问控制修饰符来限制对对象私有属性的访问,可以获得 3 个重要的好处。
- 防止对封装数据的未授权访问。
- 有助于保证数据完整性。
- 当类的私有实现细节必须改变时,可以限制发生在整个应用程序中的“连锁反应”。
访问控制符是一组限定类、属性或方法是否可以被程序里的其他部分访问和调用的修饰符。类的访问控制符只能是空或者 public,方法和属性的访问控制符有 4 个,分别是 public、 private、protected 和 friendly,其中 friendly 是一种没有定义专门的访问控制符的默认情况。访问控制修饰符的权限如表 1 所示。
访问控制在面向对象技术中处于很重要的地位,合理地使用访问控制符,可以通过降低类和类之间的耦合性(关联性)来降低整个项目的复杂度,也便于整个项目的开发和维护。在 Java 语言中,访问控制修饰符有 4 种。
1. private
用 private 修饰的类成员,只能被该类自身的方法访问和修改,而不能被任何其他类(包括该类的子类)访问和引用。因此,private 修饰符具有最高的保护级别。例如,设 PhoneCard 是电话卡类,电话卡都有密码,因此该类有一个密码域,可以把该类的密码域声明为私有成员。
2. friendly(默认)
如果一个类没有访问控制符,说明它具有默认的访问控制特性。这种默认的访问控制权规定,该类只能被同一个包中的类访问和引用,而不能被其他包中的类使用,即使其他包中有该类的子类。这种访问特性又称为包访问性(package private)。
同样,类内的成员如果没有访问控制符,也说明它们具有包访问性,或称为友元(friend)。定义在同一个文件夹中的所有类属于一个包,所以前面的程序要把用户自定义的类放在同一个文件夹中(Java 项目默认的包),以便不加修饰符也能运行。
3. protected
用保护访问控制符 protected 修饰的类成员可以被三种类所访问:该类自身、与它在同一个包中的其他类以及在其他包中的该类的子类。使用 protected 修饰符的主要作用,是允许其他包中它的子类来访问父类的特定属性和方法,否则可以使用默认访问控制符。
4. public
当一个类被声明为 public 时,它就具有了被其他包中的类访问的可能性,只要包中的其他类在程序中使用 import 语句引入 public 类,就可以访问和引用这个类。
类中被设定为 public 的方法是这个类对外的接口部分,避免了程序的其他部分直接去操作类内的数据,实际就是数据封装思想的体现。每个 Java 程序的主类都必须是 public 类,也是基于相同的原因。
例 1
下面来创建一个示例,演示 Java 中访问控制修饰符的使用。
(1) 新建 Student.java 文件,在该文件中定义不同修饰符的属性和方法,代码如下:
1. class Student
2. {
3. //姓名,其访问权限为默认(friendly)
4. String name;
5.
6. //定义私有变量,身份证号码
7. private String idNumber;
8.
9. //定义受保护变量,学号
10. protected String no;
11.
12. //定义共有变量,邮箱
13. public String email;
14.
15. //定义共有方法,显示学生信息
16. public String info()
17. {
18. return"姓名:"+name+",身份证号码:"+idNumber+",学号:"+no+",邮箱:"+email;
19. }
20. }
(2) 新建 StudentTest.java 文件,在该文件中定义 main() 方法,访问 Student 类中的属性并赋值,打印出用户的信息。代码如下:
1. public class StudentTest
2. {
3. public static void main(String[] args)
4. {
5. //创建Student类对象
6. Student stu=new Student();
7. //向Student类对象中的属性赋值
8. stu.name="zhht";
9.
10. //stu.idNumber="043765290763137806";
11. //这是不允许的。提示stu.idNumber是不可见的,必须注释掉才可运行
12. stu.no="20lil01637";
13. stu.email="zhht@qq.com";
14. System.out.println(stu.info());
15. }
16. }
在 StudentTest 类中,“stu.idNumber=“043765290763137806”;”代码行将提示 “The field User.password is not visible”错误信息。将该代码行注释掉再运行 StudentTest.java 文件,输出的内容如下:
姓名:zhht,身份证号码:null,学号:20lil01637,邮箱:zhht@qq.com
在源文件中创建了两个类,分别为主类 StudentTest 和辅助类 Student,二者在同一个包中。
在辅助类 Student 中,创建了 4 个属性,其访问控制分别为默认的、私有的、受保护的和共有的,除了私有控制符修饰的变量之外,其他的都可以被主类访问,同时创建了一个共有的方法——info(),用于打印用户信息。
在主类 StudentTest 中,创建类 Student 的实例化对象 stu,通过对象 stu 来访问该对象中的属性并赋值,因为 idNumber 属性的修饰符为 private(私有的,私有的话,只能被该类自身的方法访问和修改,而不能被任何其他类(包括该类的子类)访问和引用),因此,在 StudentTest 类中的 main() 方法中无法访问该属性。
从上面的例子中可以看出,范围控制修饰符成功地限制了访问者访问不同修饰符的属性(成员变量),从而实现了数据的隐藏。
九、main()方法
main() 方法定义的语法如下所示。
1. public static void main(String[] args)
2. {
3. //方法体
4. }
其中,使用 main() 方法时应该注意如下几点:
- 访问控制权限是公有的(public)。
- main() 方法是静态的。如果要在 main() 方法中调用本类中的其他方法,则该方法也必须是静态的,否则需要先创建本类的实例对象,然后再通过对象调用成员方法。
- main() 方法没有返回值,只能使用 void。
- main() 方法具有一个字符串数组参数,用来接收执行 Java 程序的命令行参数。命令行参数作为字符串,按照顺序依次对应字符串数组中的元素。
- 除了形参变量名可以任意设置以外,main() 方法中的其他内容都是固定不变的。
下面的示例代码演示了如何在 main() 方法中调用本类的静态和非静态方法。
1. public class Student
2. {
3. public void Speak1()
4. {
5. System.out.println("你好!");
6. }
7. public static void Speak2()
8. {
9. System.out.println("Java!");
10. }
11. public static void main(String[] args)
12. {
13. //Speak1(); //错误调用
14. Speak2(); //可以直接调用静态方法Speak2()
15. Student t=new Student();
16. t.Speak1(); //调用非静态方法,需要通过类的对象来调用
17. }
18. }
例 1
创建一个 Java 程序,编写代码实现程序执行时统计传递参数的数量及每个参数值。示例代码如下:
1. public class TestMain
2. {
3. //@param args
4. public static void main(String[] args)
5. {
6. //TODO Auto-generated method stub
7. int n=args.length; //获取参数数量
8. System.out.println("一共有 "+n+" 个参数");
9. if(n>0)
10. {
11. //判断参数个数是否大于0
12. for(int i=0;i<n;i++)
13. {
14. System.out.println(args[i]);
15. }
16. }
17. }
18. }
(1) 将代码保存到 TestMain.java 文件中,然后用如下 Java 命令对程序进行编译:
javac TestMain.java
(2) 程序编译成功后用如下 Java 命令执行 Test 程序:
java TestMain 参数列表 //多个之间用空格隔开
这里使用如下三个语句执行程序:
java TestMain
java TestMain apple banana
java TestMain one two three four five six
(3) 执行结果如下所示:
D:\2HHT\ShuGao\ch08\bin>java TestMain
一共有 0 个参数
D:\2HHT\ShuGao\ch08\bin>java TestMain apple banana
一共有 2 个参数
apple
banana
D:\ZHHT\ShuGao\ch08\bin>java TestMain one two three four five six
一共有 6 个参数 one
three
four
five
six
D:\ZHHT\ShuGao\ch08\bin>
由此可见,main() 方法可以以字符串的形式接收命令行参数,然后在方法体内进行处理。
十、final修饰符、static静态修饰符、this关键字
1.final修饰符
final 关键字表示对象是最终形态的,对象是不可改变的意思。final 应用于类、方法和变量时意义是不同的,但本质是一样的:final 表示不可改变。
final 用在变量的前面表示变量的值不可以改变,此时该变量可以被称为常量;final 用在方法的前面表示方法不可以被重写;final 用在类的前面表示类不可以被继承,即该类是最终形态,如常见的 java.lang.String 类。
final 修饰符使用在如下方面:
1. final 修饰类中的属性
表示该属性一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对对象属性来说其引用不可再变。其初始化可以在两个地方:一是其定义处,也就是说在 final 属性定义时直接给其赋值;二是在构造函数中。这两个地方只能选其一,要么在定义时给值,要么在构造函数中给值,不能同时既在定义时赋值,又在构造函数中赋予另外的值。
2. final 修饰类中的方法
说明这种方法提供的功能已经满足当前要求,不需要进行扩展,并且也不允许任何从此类继承的类来重写这种方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。在声明类中,一个 final 方法只被实现一次。
3. final 修饰类
表示该类是无法被任何其他类继承的,意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。
对于 final 类中的成员,可以定义其为 final,也可以不是 final。而对于方法,由于所属类为 final 的关系,自然也就成了 final 型。也可以明确地给 final 类中的方法加上一个 final,这显然没有意义。
例 1
下面创建一个示例演示 final 修饰符的具体使用。首先新建 FinalTest.java 文件,在该文件中创建 FinalTest 类,定义一个声明为 final 的 count 属性和一个声明为 final 的 sum() 方法。
FinalTest 类的代码如下:
1. public class FinalTest
2. {
3. final int count=1;
4. public int updateCount()
5. {
6. count=4; //修改final属性值,提示错误
7. return count;
8. }
9. public final int sum()
10. {
11. int number=count+10;
12. return number;
13. }
14. }
创建 FinalTest 类的子类 FinalExtendTest,在该类中重写父类 FinalTest 中的 sum() 方法,并继承父类中的 sum() 方法。代码如下:
1. public class FinalExtendTest extends FinalTest
2. {
3. public int sum(){}; //重写父类 FinalTest 中的 sum()方法,出错
4. int count=sum(); //继承父类 FinalTest 中的 sum()方法
5. }
最后在 FinalExtend 类中新建程序主方法——main() 方法,实例化 FinalExtendTest 类并访问该类的 count 属性,代码如下所示。
1. public static void main(String[] args)
2. {
3. FinalExtendTest fet=new FinalExtendTest();
4. System.out.println(fet.count);
5. }
在编译该文件时,会出现两处错误:一是代码“count=4;”处,此处试图给 count 变量重新赋值,会产生错误,因为 final 变量只能被初始化一次。第二处是代码“public int sum(){};”,此处试图重写 final 修饰的方法,会出现错误,因为 final 修饰的方法可以被继承但不能被任何类重写。
将两处的错误语句都注释掉,程序运行后输出的结果如下:
11
2.static静态修饰符
使用 static 修饰符修饰的属性(成员变量)、常量和成员方法称为静态变量、常量和方法,它们统称为静态成员,归整个类所有,不依赖于类的特定实例,被类的所有实例共享。只要这个类被加载,Java 虚拟机就可以根据类名在运行时数据区的方法区内找到它们。
调用静态成员的语法形式如下:
类名.静态成员
1.静态变量
实际上类的成员变量可以分为两种:静态变量(或称为类变量),指被 static 修饰的成员变量;实例变量,指没有被 static 修饰的成员变量。
静态变量与实例变量的区别如下:
- 对于静态变量,运行时,Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配。在类的内部,可以在任何方法内直接访问静态变量;在其他类中,可以通过类名访问该类中的静态变量。
- 对于实例变量,每创建一个实例,Java 虚拟机就会为实例变量分配一次内存。在类的内部,可以在非静态方法中直接访问实例变量;在本类的静态方法或其他类中则需要通过类的实例对象进行访问。
静态变量在类中的作用如下:
- 静态变量可以被类的所有实例共享,因此静态变量可以作为实例之间的共享数据,增加实例之间的交互性。
- 如果类的所有实例都包含一个相同的常量属性,则可以把这个属性定义为静态常量类型,从而节省内存空间。
例如,在类中定义一个静态常量 PI。
1. public static double PI=3.14159256;
例 1
创建一个带静态变量的类,然后在 main() 方法中访问该变量并输出结果。
1. public class StaticVar
2. {
3. public static String str1="Hello";
4. public static void main(String[] args)
5. {
6. String str2="World!";
7. //直接访问str1
8. String accessVar1=str1+str2;
9. System.out.println("第 1 次访问静态变量,结果为:"+accessVar1);
10. //通过类名访问str1
11. String accessVar2=StaticVar.str1+str2;
12. System.out.println("第 2 次访问静态变量,结果为:"+accessVar2);
13. //通过对象svt1访问str1
14. StaticVar svt1=new StaticVar();
15. svt1.str1=svt1.str1+str2;
16. String accessVar3=svt1.str1;
17. System.out.println("第3次访向静态变量,结果为:"+accessVar3);
18. //通过对象svt2访问str1
19. StaticVar svt2=new StaticVar();
20. String accessVar4=svt2.str1+str2;
21. System.out.println("第 4 次访问静态变量,结果为:"+accessVar4);
22. }
23. }
运行该程序后的结果如下所示。
第 1 次访问静态变量,结果为:HelloWorld!
第 2 次访问静态变量,结果为:HelloWorld!
第 3 次访向静态变量,结果为:HelloWorld!
第 4 次访问静态变量,结果为:HelloWorld!World!
从运行结果可以看出,在类中定义静态的属性(成员变量),在 main() 方法中可以直接访问,也可以通过类名访问,还可以通过类的实例对象来访问。
注意:静态变量是被多个实例所共享的。
2.静态方法
与成员变量类似,成员方法也可以分为两种:静态方法(或称为类方法),指被 static 修饰的成员方法;实例方法,指没有被 static 修饰的成员方法。
静态方法与实例方法的区别如下:
- 静态方法不需要通过它所属的类的任何实例就可以被调用,因此在静态方法中不能使用 this 关键字,也不能直接访问所属类的实例变量和实例方法,但是可以直接访问所属类的静态变量和静态方法。另外,和 this 关键字一样,super 关键字也与类的特定实例相关,所以在静态方法中也不能使用 super 关键字。
- 在实例方法中可以直接访问所属类的静态变量、静态方法、实例变量和实例方法。
例 2
创建一个带静态变量的类,添加几个静态方法对静态变量的值进行修改,然后在 main() 方法中调用静态方法并输出结果。
1. public class StaticMethod
2. {
3. public static int count=1; //定义静态变量count
4. public int method1()
5. { //实例方法method1
6. count++; //访问静态变量count并赋值
7. System.out.println("在静态方法 method1()中的 count="+count); //打印count
8. return count;
9. }
10. public static int method2()
11. { //静态方法method2
12. count+=count; //访问静态变量count并赋值
13. System.out.println("在静态方法 method2()中的 count="+count); //打印count
14. return count;
15. }
16. public static void PrintCount()
17. { //静态方法PrintCount
18. count+=2;
19. System.out.println("在静态方法 PrintCount()中的 count="+count); //打印count
20. }
21. public static void main(String[] args)
22. {
23. StaticMethod sft=new StaticMethod();
24. //通过实例对象调用实例方法
25. System.out.println("method1() 方法返回值 intro1="+sft.method1());
26. //直接调用静态方法
27. System.out.println("method2() 方法返回值 intro1="+method2());
28. //通过类名调用静态方法,打印coimt
29. StaticMethod.PrintCount();
30. }
31. }
运行该程序后的结果如下所示。
在静态方法 method1()中的 count=2
method1() 方法返回值 intro1=2
在静态方法 method2()中的 count=4
method2() 方法返回值 intro1=4
在静态方法 PrintCount()中的 count=6
在该程序中,静态变量 count 作为实例之间的共享数据,因此在不同的方法中调用 count,值是不一样的。从该程序中可以看出,在静态方法 method2() 和 PrintCount() 中是不可以调用非静态方法 method1() 的,而在 method1() 方法中可以调用静态方法 method2() 和 PrintCount()。
在访问非静态方法时,需要通过实例对象来访问,而在访冋静态方法时,可以直接访问,也可以通过类名来访问,还可以通过实例化对象来访问。
3.静态代码块
静态代码块指 Java 类中的 static{} 代码块,主要用于初始化类,为类的静态变量赋初始值。静态代码块的特点如下:
- 静态代码块类似于一个方法,但它不可以存在于任何方法体中。
- Java 虚拟机在加载类时会执行静态代码块,如果类中包含多个静态代码块,则 Java 虚拟机将按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次。
- 静态代码块与静态方法一样,不能直接访问类的实例变量和实例方法,而需要通过类的实例对象来访问。
例 3
编写一个 Java 类,在类中定义一个静态变量,然后使用静态代码块修改静态变量的值。最后在 main() 方法中进行测试和输出。
1. public class StaticCode
2. {
3. public static int count=0;
4. {
5. count++;
6. System.out.println("非静态代码块 count="+count);
7. }
8. static
9. {
10. count++;
11. System.out.println("静态代码块 count="+count);
12. }
13. public static void main(String[] args)
14. {
15. System.out.println("*************** StaticCode1 执行 ***************");
16. StaticCode sct1=new StaticCode();
17. System.out.println("*************** StaticCode2 执行 ***************");
18. StaticCode sct2=new StaticCode();
19. }
20. }
如上述示例,为了说明静态代码块只被执行一次,特地添加了非静态代码块作为对比,并在主方法中创建了两个类的实例对象。上述示例的执行结果为:
静态代码块 count=1
*************** StaticCode1 执行 ***************
非静态代码块 count=2
*************** StaticCode2 执行 ***************
非静态代码块 count=3
3.this关键字
this 关键字是 Java 常用的关键字,可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用。
例 1
假设有一个教师类 Teacher 的定义如下:
1. public class Teacher
2. {
3. private String name; //教师名称
4. private double salary; //工资
5. private int age; //年龄
6. }
在上述代码中 name、salary 和 age 的作用域是 private,因此在类外部无法对它们的值进行设置。为了解决这个问题,可以为 Teacher 类添加一个构造方法,然后在构造方法中传递参数进行修改。代码如下:
1. //创建构造方法,为上面的3个属性赋初始值
2. public Teacher(String name,double salary,int age)
3. {
4. this.name=name; //设置教师名称
5. this.salary=salary; //设置教师工资
6. this.age=age; //设置教师年龄
7. }
在 Teacher 类的构造方法中使用了 this 关键字对属性 name、salary 和 age 赋值,this 表示当前对象。this.name=name 语句表示一个赋值语句,等号左边的 this.name 是指当前对象具有的变量 name,等号右边的 name 表示参数传递过来的数值。
创建一个 main() 方法对 Teacher 类进行测试,代码如下:
1. public static void main(String[] args)
2. {
3. Teacher teacher=new Teacher("王刚",5000.0,45);
4. System.out.println("教师信息如下:");
5. System.out.println("教师名称:"+teacher.name+"\n教师工资:"+teacher.salary+"\n教师年龄:"+teacher.age);
6. }
运行该程序,输出的结果如下所示。
教师信息如下:
教师名称:王刚
教师工资:5000.0
教师年龄:45
提示:当一个类的属性(成员变量)名与访问该属性的方法参数名相同时,则需要使用 this 关键字来访问类中的属性,以区分类的属性和方法中的参数。
总结
各位小主,如果感觉有一点点帮助到你的话,麻烦留下赞赞一枚哦~