目录
1.1 final 关键字
final 的英文意思是“最终”。在 Java 中,可以使用nal 关键字声明类、属性、方法,在声明时需要注意以下几点。
(1)使用 final 修饰的类不能有子类。
(2)使用 final 修饰的方法不能被子类重写
(3)使用 final 修饰的变量(成员变量和局部变量)是常量,常量不可修改
下面将对 fnal 的用法逐一进行讲解。
1.1.1 final关键字修饰类
Java 中的类被 final 关键字修饰后,该类将不可以被继承,即不能派生子类。下面通过一个案例进行验证,如下代码所示。
//使用final关键字修饰Animal类
final class Animal{
}
//Dog类继承Animal类
class Dog extends Animal{
}
//定义测试类
public class Example07{
public static void main (string[] args){
Dog dog = new Dog; //创建 Dog 类的实例对象
}
}
编译上面代码,编译器报错,如下图所示。
上面代码中,第 2 行代码定义了 Animal 类并使用 final 关键字修饰,第 5~6 行代码定义了 Dog 类并继承Animal类。
如上图所示,当 Dog 类继承使用 final 关键字修饰的 Animal类时,编译器报“无法从最终cn.itcast.Animal进行继承”错误,即不能继承使用 fnal 修饰的 Animal 类。由此可见,被 final 关键字修饰的类为最终类,不能被其他类继承。
1.1.2 final关键字修饰方法
当一个类的方法被 final 关键字修饰后,这个类的子类将不能重写该方法。下面通过一个案例进行验证如下代码所示。
//定义Animal类
class Animal {
//使用 final关键字修饰 shout () 方法
public final void shout (){
}
}
//定义 Dog 类继承Animal类
class Dog extends Animal{
//重写 Animal 类的 shout () 方法
public void shout (){
}
}
//定义测试类
public class Example08 {
public static void main (String[] args) {
Dog dog = new Dog ();// 创建 Dog 类的实例对象
}
}
编译以上代码,编译器报错,如下图所示。
在上面代码中,第10行代码在 Dog 类中重写了父类 Animal 中的 shout ( )方法,编译报错。这是因为Animal类的 shout ( )方法被 final 修饰,而被 final 关键字修饰的方法为最终方法,子类不能对该方法进行写。因此,当在父类中定义某个方法时,如果不希望被子类重写,就可以使用 final 关键字修饰该方法。
1.1.3 final关键字修饰变量
Java 中被 final 修饰的变量为常量,常量只能在声明时被赋值一次,在后面的程序中,其值不能被改变。如果再次对该常量赋值,则程序会在编译时报错。下面通过一个案例进行验证,如下代码 所示。
public class Example09{
public static void main (String[]args) {
final int AGE = 18; //第一次可以赋值
AGE = 20; //再次赋值会报错
}
}
编译上面代码,编译器报错,如下图所示。
在上面代码中,当第4行代码对 AGE 进行第二次赋值时,编译器报错。原因在于使用 final定义的常量本身不可被修改。
注意:
在使用 final 声明变量时,要求全部的字母大写。如果一个程序中的变量使用 public static final 声明,则此变量将成为全局常量,如下面代码所示。
public static final String NAME ="哈士奇";
1.2 抽象类和接口
1.2.1 抽象类
当定义一个类时,常常需要定义一些成员方法描述类的行为特征,但有时这些方法的实现方式是无法确定的。例如,前面在定义 Animal 类时,shout ( )方法用于描述动物的叫声,但是不同动物的叫声是不同的,因此在 shout() 方法中无法准确地描述动物的叫声。
针对上面描述的情况,Java 提供了抽象方法来满足这种需求。抽象方法是使用 abstract 关键字修饰的成员方法,抽象方法在定义时不需要实现方法体。抽象方法的定义格式如下:
abstract 返回值类型 方法名称 (参数);
当一个类包含了抽象方法,该类必须是抽象类。抽象类和抽象方法一样,必须使用 abstract 关键字进行修饰。
抽象类的定义格式如下:
abstract class 抽象类名称{
属性;
访问权限 返回值类型 方法名称(参数){ //普通方法
return [返回值];
}
访问权限 abstract 返回值类型 抽象方法名称(参数); //抽象方法,无方法体
}
从以上格式可以发现,抽象类的定义比普通类多了一些抽象方法,其他地方与普通类的组成基本上相同。
抽象类的定义规则如下。
(1)包含抽象方法的类必须是抽象类。
(2)抽象类和抽象方法都要使用 abstract 关键字声明。
(3)抽象方法只需声明而不需要实现。
(4)如果一个非抽象类继承了抽象类,那么该子类必须实现抽象类中的全部抽象方法。
下面通过一个案例学习抽象类的使用,如下代码所示。
//定义抽象类Animal
abstract class Animal{
//定义抽象方法shout()
abstract void shout();
}
//定义 Doq 类继承抽象类Animal
class Dog extends Animal{
//实现抽象方法shout()
void shout (){
System.out.println ("汪汪......");
}
//定义测试类
public class Example10 {
public static void main (Stringl ] args){
Dog dog = new Dog () //创建 Dog 类的实例对象
dog.shout() //调用 dog 对象的 shout () 方法
}
}
上面代码的运行结果如下图所示。
在上面代码中,第 2~5 行代码是声明了一个抽象类Animal,并在 Animal 类中声明了一个抽象方法 shout ();第9~11行代码在子类 Dog 中实现父类 Animal 的抽象方法shout ( );第 17 行代码通过子类的实例化对象用 shout () 方法。
注意:
使用 abstract 关键字修饰的抽象方法不能使用 private 修饰,因为抽象方法必须被子类实现,如果使用private 声明,则子类无法实现该方法。
1.2.2 接口
如果一个抽象类的所有方法都是抽象的,则可以将这个类定义接口。接口是 Java 中最重要的概念之在JDK8 中,接口中除了可以包括抽象方法外,还可以包括默认方法和静态方法(也叫类方法 ,默认方使用 default 修饰,静态方法使用 static 修饰,且这两种方法都允许有方法体。
接口使用interface 关键字声明,语法格式如下:
public interface 接口名 extends 接口1,接口2...{
public static final 数据类型 常量名 = 常量值;
public abstract 返回值类型 抽象方法名称(参数列表);
}
在上述语法中,“extends 接口 1,接口 2..”表示一个接口可以有多个父接口,父接口之间用逗号分隔。Java 使用接口的目的是克服单继承的限制,因为一个类只能有一个父类,而一个接口可以同时继承多个父口。接口中的变量默认使用“public static final”进行修饰,即全局常量。接口中定义的方法默认使用“public abstract”进行修饰,即抽象方法。如果接口声明为 public,则接口中的常量和方法全部为 public。
注意:
在很多Java 程序中,经常看到编写接口中的方法时省略了 public,有很多读者认为它的访问权限是 default 这实际上是错误的。不管写不写访问权限,接口中方法的访问权限永远是 public。与此类似,在接口中定》常量时,可以省略前面的“public static final”,此时,接口会默认为常量添加“public static final”
从接口定义的语法格式可以看出,接口中可以包含三类方法,分别是抽象方法、默认方法、静态方法,其中静态方法可以通过“接口名方法名”的形式来调用,而抽象方法和默认方法只能通过接口实现类的对象来调用。接口实现类的定义方式比较简单,只需要定义一个类,该类使用implements 关键字实现接口,并实现了接口中的所有抽象方法。需要注意的是,一个类可以在继承另一个类的同时实现多个接口,并目多个接口之间需要使用英文逗号 (,)分隔。
定义接口的实现类,语法格式如下:
修饰符 class 类名 implements 接口1,接口2,...{
...
}
下面通过一个案例学习接口的使用,如下面代码所示。
// 定义接口Animal
interface Animal {
int ID = 1; //定义全局常量
string NAME ="牧羊犬";
void shout(); //定义抽象方法 shout ()
static int getID() {
return Animal.ID;
}
public void info(); //定义抽象方法 info ()
}
interface Action {
public void eat (); //定义抽象方法 eat ()
}
//定义 Dog 类实现 Animal 接口和 Action 接口
class Dog implements Animal,Action {
//重写 Action 接口中的抽象方法eat ()
public void eat () {
System.out.printn ("喜欢吃骨头");
}
//重写 Animal 接口中的抽象方法 shout ()
public void shout () {
System.out.println ("汪汪......");
}
//重写 Animal 接口中的抽象方法 info ()
public void info() {
System.out.printIn ("名称:"+NAME);
}
}
//定义测试类
class Example11 {
public static void main (String[] args) {
System.out.println ("编号"+Animal.getID ());
Dog dog = new Dog (); //创建 Dog 类的实例对象
dog.info ();
dog.shout (); // 调用 Dog 类中重写的 shout() 方法
dog.eat (); // 调用 Dog 类中重写的 eat() 方法
}
}
上面代码的运行结果如下图所示。
在上面代码中第2~10行代码定义了一个Animal接口,在Animal接口中定义了全局常量ID和NAME、抽象方法 shout ( )、info()和静态方法 getID ( )。第11~13 行代码定义了一个 Action 接口,在 Action 接口中定义了一个抽象方法 eat ( )。第 15~28 行代码定义了一个 Dog 类,Dog 类通过 implements 关键字实现了Animal 接口和 Action 接口,并重写了这两个接口中的抽象方法。第32行代码使用 Animal 接口名直接访问了Animal 接口中的静态方法 getID ( )。第 33~36行代码创建并实例化了 Dog 类对象 dog,通过 dog 对象访问了 Animal 接口和 Action 接口中的常量以及 Dog类重写的抽象方法。
从上图中的运行结果可以看出,Dog 类的实例化对象可以访问接口中的常量、重写的接口方法和本类内部的方法,而接口中的静态方法则可以直接使用接口名调用。需要注意的是,接口的实现类,必须实现接口中的所有方法,否则程序编译报错。
上面代码演示的是类与接口之间的实现关系,如果在开发中一个类既要实现接口,又要继承抽象类则可以按照以下格式定义类。
修饰符 class 类名 extends 父类名 implements 接口1,接口2,...{
...
}
下面对上面的代码稍加修改,演示一个类既实现接口,又继承抽象类的情况。修改后的代码如下所示。
//定义接口Animal
interface Animal {
public String NAME="牧羊犬";
public void shout(); //定义抽象方法 shout()
public void info(); //定义抽象方法 info()
}
abstract class Action {
public abstract void eat(); //定义抽象方法 eat()
}
//定义 Dog 类继承 Action 抽象类并实现 Animal 接口
class Dog extends Action implements Animal {
//重写 Action 抽象类中的抽象方法eat ()
public void eat() {
System.out.println("喜欢吃骨头");
}
//重写 Anima1 接口中的抽象方法 shout()
public void shout() {
System.out.println("汪汪...");
}
//重写 Animal 接口中的抽象方法 info()
public void info() {
System.out.printIn("名称:"+NAME);
}
}
//定义测试类
public class Example12 {
public static void main(String[] args) {
Dog dog = new Dog(); //创建 Dog类的对象
dog.info(); //调用 Dog类中重写的 info()方法
dog.shout(); //调用 Dog类中重写的 shout ()方法
dog.eat(); //调用 Dog类中重写的 eat()方法
}
}
在上面代码中,Dog 类通过 extends 关键字继承了 Action 抽象类,同时通过 implements 实现了 Animal 接口。因为 Animal 接口和 Action 抽象类本身都有抽象方法,所以 Dog 类中必须重写Animal 接口和 Action 抽象类中的抽象方法。
在 Java 中,接口是不允许继承抽象类的,但是允许一个接口继承多个接口。下面通过一个案例讲解接口的继承,如下面代码所示。
//定义接口 Animal
interface Animal {
public String NAME = "牧羊犬";
public void info () ; //定义抽象方法 info()
}
interface Color {
public void black (); //定义抽象方法 black()
}
interface Action extends Animal,Color {
public void shout (); //定义抽象方法 shout()
}
//定义 Dog 类实现 Action 接口
class Dog implements Action {
//重写 Animal 接口中的抽象方法 info()
public void info () {
System.out.printIn ("名称: "+NAME);
}
//重写 Color 接口中的抽象方法 black()
public void black () {
System.out.println ("黑色");
}
//重写 Action 接口中的抽象方法 shout()
public void shout () {
System.out.println ("汪汗......");
}
}
//定义测试类
class Example13 {
public static void main (String[] args) {
Dog dog = new Dog (); //创建 Dog 类的实例对象
dog.info (); //调用 Dog 类中重写的 info() 方法
dog.shout (); //调用 Dog 类中重写的 shout() 方法
dog.black (); //调用 Dog 类中重写的 eat() 方法
}
}
上面代码的运行结果如下图 所示。
从上面的代码可以发现第9~11代码定义了接口 Action 并继承接口 Animal 和 Color。这样接口 Action 中就同时拥有 Animal 接口中的 info() 方法和 Color 接口中的 black () 方法,以及本类中的 shout () 方法。在第13~26行代码定义了一个 Dog 类并实现了 Action 接口,这样 Dog 类就必须重写这 3 个抽象方法。
2.1 总结
2.1.1 final 关键字总结
-
不可继承:通过使用final关键字修饰类,可以阻止其他类对该类的继承。final类是最终的类,不允许其他类继承它。
-
安全性:final类可以提供更高的安全性,因为它不能被其他类继承和修改。这在一些情况下是非常有用的,特别是当你想确保类的行为和实现不会被修改时。
-
性能优化:final类对编译器来说是已知的,它可以进行一些额外的优化,例如内联方法调用等。这可能导致更好的性能。
-
设计意图:通过将类声明为final,可以清楚地表明这个类的设计意图是不可继承的。这有助于提高代码的可读性和可维护性,其他开发人员在使用该类时,能够更好地理解和遵循设计意图。
2.2.1 抽象类总结
-
无法实例化:抽象类不能直接创建对象,因为它存在抽象方法。只能通过继承抽象类来使用它,子类必须实现抽象类中的所有抽象方法才能被实例化。
-
包含抽象方法:抽象类中可以包含抽象方法,这些方法没有具体的实现,只有方法签名。子类必须实现抽象方法,提供具体的实现逻辑。
-
可以包含非抽象方法:抽象类不仅可以包含抽象方法,还可以包含非抽象方法,这些方法有具体的实现。子类可以继承这些非抽象方法,也可以重写它们。
-
提供了继承的模板:抽象类提供了一个继承的模板,可以定义一些通用的属性和方法,子类可以继承这些通用的部分,并在此基础上进行扩展和定制。
-
部分实现、部分延迟到子类:抽象类可以在其中实现一些通用的方法逻辑,而将一些特定的方法逻辑延迟到子类中去实现。这样可以提高代码的复用性和可维护性。
-
限制了类的层次结构:通过使用抽象类,可以限制类的层次结构,要求子类实现指定的抽象方法。这样可以确保子类在继承时必须满足一定的条件和规范。
2.2.2 接口总结
-
定义接口:接口使用interface关键字来定义,并且所有方法都是抽象的,不包含具体的实现,可以包含常量。
-
实现接口:一个类可以实现一个或多个接口,使用implements关键字来实现接口,并且必须实现接口中的所有方法。
-
多态性:通过实现接口,可以实现多态性。一个对象可以被赋值给其实现的接口类型,然后使用该接口来调用对象的方法。
-
规范性:接口定义了一组规范,所有实现该接口的类都必须遵循这些规范,提供相同的方法和常量。这有助于提高代码的可读性和可维护性。
-
松耦合:接口是松耦合的,因为它们在定义时不需要考虑实现的细节,只需要关注方法的签名和返回类型。这样可以使代码更加灵活和可扩展。
-
扩展性:通过添加新的接口,可以很容易地扩展系统的功能,只需要实现新的接口即可。
-
JDK中的接口:Java中有很多内置的接口,例如Comparable、Iterable、Runnable等。这些接口提供了一些基本的功能和规范,可以在开发中直接使用。