目录
(1)使用super关键字访问父类的成员变量和成员方法,具体格式如下
(2)使用super关键字访问父类中指定的构造方法,具体格式如下
学习Java面向对象中类的类的继承、方法的重写、super关键字、final关键字、抽象类和接口
一、类的继承
类的概念
类的继承是指在一个现有的类的基础上去构建一个新的类,构建出来的新类称为子类,现有称为父类。子类继承父类的属性和方法,使得子类对象(实例)具有父类的特此和行为。
如果想声明一个类继承另一个类,需要使用 extends 关键字,
语法格式:
class 父类{
......
}
class 子类 extends 父类{
......
}
示例:子类是如何继承父类的
package com.company.example01;
//定义(父类)Animal 类
class Animal{
private String name; //定义name属性
private int age; //定义age属性
//封装
//获取name属性
public String getName() {
return name;
}
//设置name属性
public void setName(String name) {
this.name = name;
}
//获取age属性
public int getAge() {
return age;
}
//设置age属性
public void setAge(int age) {
this.age = age;
}
}
//定义(子类)Cat类继承 Animal 类
class Cat extends Animal{
//此处不写任何代码
}
//定义测试类
public class Example01 {
public static void main(String[] args) {
Cat cat = new Cat(); //创建一个Cat类的实例化对象
cat.setName("波斯猫"); //此时访问的方法是父类中的,子类中并没有定义
cat.setAge(3); //此时访问的方法是父类中的,子类中并没有定义
System.out.println("名称:"+cat.getName()+",年龄:"+cat.getAge());
}
}
/*
结果是:
名称:波斯猫,年龄:3
*/
示例:子类也可以定义自己的属性和方法
package com.company.example02;
//定义(父类)Animal 类
class Animal{
private String name; //定义name属性
private int age; //定义age属性
//封装
//获取name属性
public String getName() {
return name;
}
//设置name属性
public void setName(String name) {
this.name = name;
}
//获取age属性
public int getAge() {
return age;
}
//设置age属性
public void setAge(int age) {
this.age = age;
}
}
//定义(子类)Cat类继承 Animal 类
class Cat extends Animal{
private String color; //定义color属性
//设置color属性
public String getColor() {
return color;
}
//获得olor属性
public void setColor(String color) {
this.color = color;
}
}
//定义测试类
public class Example02 {
public static void main(String[] args) {
Cat cat = new Cat(); //创建一个Cat类的实例化对象
cat.setName("波斯猫"); //此时访问的方法是父类中的,子类中并没有定义
cat.setAge(3); //此时访问的方法是父类中的,子类中并没有定义
cat.setColor("黑色");
//打印
System.out.println("名称:"+cat.getName()+",年龄:"+cat.getAge()+"岁,颜色:"+ cat.getColor());
}
}
/*
结果是:
名称:波斯猫,年龄:3岁,颜色:黑色
*/
在类的继承中,需要注意一些问题,具体如下。
(1)在Java中,类只支持单继承,不允许多继承。也就是说,一个类只能有一个直接父类,例如下面这种情况是不合法的。
class A { }
class B { }
class C extends A,B { } // C类不可以同时继承A类和B类
(2)多个类可以继承一个父类,例如下面这种情况是允许的。
class A { }
class B extends A{ }
class C extends A{ } // B类和C类都可以继承A类
(3)在Java中多层继承也是可以的,即一个类的父类可以再继承另外的父类。例如,C类继承自B类,而B类又可以继承自A类,这时,C类也可称为A类的子类。例如,下面这种情况是允许的。
class A { }
class B extends A{ } // B类继承A类,B类是A类的子类
class C extends B{ } // C类继承B类,C类是B类的子类,同时也是A类的子类
(4)在Java中,子类和父类是一种相对概念,一个类也可以是某个类的父类,也可以是另一个类的子类。例如,在第(3)种情况,B类是A类的子类,同时又是C类的父类。
在继承中,子类不能直接访问父类中的私有成员,子类可以调用父类的非私有方法,但是不能调用父类的私有成员。
二、方法的重写
在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表和返回值类型,且在子类重写的方法不能拥有比父类方法更加严格的访问权限。
示例:方法的重写
package com.company.example03;
//定义Phone类
class Phone{
//定义打电话
void call(){
System.out.println("打电话");
}
}
//定义NewPhone类
class NewPhone extends Phone{
//重写父类Phone 中的call()方法
void call(){
System.out.println("开启语音");
System.out.println("关闭语音");
}
}
//测试类
public class Example03 {
public static void main(String[] args) {
System.out.println("------------重写前-----------");
Phone phone = new Phone(); //实例化Phone类的对象
phone.call(); //调用Phone对象call方法
System.out.println("------------重写后-----------");
NewPhone newPhone = new NewPhone(); //实例化NewPhone类的对象
newPhone.call(); //调用NewPhone对象call方法
}
}
/*
结果是:
------------重写前-----------
打电话
------------重写后-----------
开启语音
关闭语音
*/
三、super关键字
当子类重写父类的方法后,子类对象将无法访问父类被重写的方法,为了解决这个问题,Java提供了super关键字,super关键字可以在子类中调用父类的普通属性、方法和构造方法。
(1)使用super关键字访问父类的成员变量和成员方法,具体格式如下
super.成员变量
super.成员方法 (参数1, 参数2...)
示例:使用关键字super关键字访问父类的shout( ) 方法
package com.company.example05;
//定义Animal类
class Animal{
String name = "牧羊犬";
//定义动物叫的方法
void shout(){
System.out.println("动物发出叫声");
}
}
//定义 Dog类继承 Animal类
class Dog extends Animal{
//重写父类 Animal中的 shout( ) 方法,扩大了访问权限
public void shout(){
super.shout(); //调用父类 Animal类中的shout( ) 方法
System.out.println("汪汪汪......");
}
public void printName(){
System.out.println("名字:"+super.name); //调用父类 Animal类中的name属性
}
}
//定义测试类
public class Example05 {
public static void main(String[] args) {
Dog dog = new Dog(); //创建 Dog类的实例对象
dog.shout(); //调用dog重写的 shout( )方法
dog.printName(); //调用 Dog类中的 printName()方法
}
}
(2)使用super关键字访问父类中指定的构造方法,具体格式如下
super (参数1 ,参数2...)
示例:如何使用super关键字调用父类的构造方法
package com.company.example06;
//定义父类 Animal类
class Animal{
private String name;
private int age;
//有参数构造方法
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
//获取name属性
public String getName() {
return name;
}
//设置name属性
public void setName(String name) {
this.name = name;
}
//获取age属性
public int getAge() {
return age;
}
//设置age属性
public void setAge(int age) {
this.age = age;
}
//info( ) 方法
public String info(){
return "名称:"+this.getName()+",年龄:"+this.getAge();
}
}
//定义 Dog类继承 Animal类
class Dog extends Animal{
private String color;
//有参数构造方法
public Dog(String name, int age, String color) {
super(name, age); //调用父类Animal类 属性
/* //也可以这样写
super.setName(name);
super.setAge(age);*/
this.color = color;
}
//获取color属性
public String getColor() {
return color;
}
//设置color属性
public void setColor(String color) {
this.color = color;
}
//重写父类的info( ) 方法
public String info(){
return super.info() +",颜色:"+this.getColor(); // 扩充父类中的方法
}
}
//定义测试类
public class Example06 {
public static void main(String[] args) {
Dog dog = new Dog("牧羊犬",3,"黑色"); //创建 Dog类的实例对象
System.out.println(dog.info());
}
}
/*结果是:
名称:牧羊犬,年龄:3,颜色:黑色
*/
注意:
通过super( ) 调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次。
super 与 this 关键字的作用非常相似,都可以调用构造方法、普通方法和属性,但是两者之间还是有区别的。
示例:super 与 this 的区别
需要注意的是,this 和 super 两者不可以同时出现,因为 this 和 super在调用构造方法时都要求必须放在构造方法的首行。
四、final关键字
final的英文意思是“最终”。在Java中,可以使用final关键字声明类、属性、方法,在声明时需要注意以下几点。
(1)使用fnal修饰的类不能有子类。
(2)使用final修饰的方法不能被子类重写。
(3)使用final修饰的变量(成员变量和局部变量)是常量,常量不可修改。
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 进行继承 ” 错误,即不能继承使用final修饰的Animal类。由此可见,被final关键字修饰的类为最终类,不能被其他继承。
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关键字修饰该方法。
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、抽象类
当定义一个类时,常常需要定义一些成员方法描述类的行为特征,但有时这些方法的实现方式是无法确定的。例如,前面在定义Animal类时,shout( )方法用于描述动物的叫声,但是不同动物的叫声是不同的,因此在shout( )方法中无法准确地描述动物的叫声。
针对上面描述的情况,Java提供了抽象方法来满足这种需求。抽象方法是使用abstract关键字修饰的成员方法,抽象方法在定义时不需要实现方法体。
抽象方法的定义格式如下
abstract 返回值类型 方法名称 (参数);
当一个类包含了抽象方法,该类必须是抽象类。抽象类和抽象方法一样,必须使用abstract 关键字进行修饰。
抽象方法的定义格式如下
abstract class 抽象类名称{
属性;
访问权限 返回值类型 方法名称(参数) { //普通方法
return [返回值];
}
访问权限 abstract 返回值类型 抽象方法名称(参数); //抽象方法,无方法体
}
从以上格式可以发现,抽象类的定义比普通类多了一些抽象方法,其他地方与普通类的组成基本上相同。
抽象类的定义规则如下。
(1)包含抽象方法的类必须是抽象类。
(2)抽象类和抽象方法都要使用abstract关键字声明。
(3)抽象方法只需声明而不需要实现。
(4)如果一个非抽象类继承了抽象类,那么该子类必须实现抽象类中的全部抽象方法。
示例:抽象类的使用
package com.company.example10;
//定义抽象类 Animal
abstract class Animal{
//定义抽象方法shout()
abstract void shout();
}
//定义 Dog类继承抽象类Animal
class Dog extends Animal{
//实现抽象方法shout()
void shout(){
System.out.println("汪汪汪.......");
}
}
//定义 Cat类继承抽象类Animal
class Cat extends Animal{
//实现抽象方法shout()
void shout(){
System.out.println("喵喵喵.......");
}
}
//定义测试类
public class Example10 {
public static void main(String[] args) {
Dog dog = new Dog(); //创建 Dog类的实例对象
dog.shout(); //调用 dog对象的shout()方法
Cat cat = new Cat(); //创建 Cat类的实例对象
cat.shout(); //调用 cat对象的shout()方法
}
}
/*
结果是:
汪汪汪.......
喵喵喵.......*/
注意:
使用abstract关键字修饰的抽象方法不能使用private修饰,因为抽象方法必须被子类实现,如果使用了private声明,则子类无法实现该方法。
2、接口
如果一个抽象类的所有方法都是抽象的,则可以将这个类定义接口。接口是Java中最重要的概念之一。在JDK8中,接口中除了可以包括抽象方法外,还可以包括默认方法和静态方法(也叫类方法),默认方法使用default修饰,静态方法使用static修饰,且这两种方法都允许有方法体。
接口使用interface关键字声明,语法格式如下
public interdace 接口名 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,...{
...
}
示例:接口的使用
package com.company.example11;
//定义接口 Animal
interface Animal{
int ID =1; //定义全局变量
String NAME ="牧羊犬"; //定义全局变量
void shout(); //定义抽象方法shout()
//定义静态方法getID()
static int getID(){
return Animal.ID;
}
public void info(); //定义抽象方法info()
}
//定义抽象方法eat()
interface Action{
public void eat();
}
//定义 Dog类实现 Animal接口和 Action接口
class Dog implements Animal,Action{
//重写Action接口中的抽象方法 eat()
public void eat(){
System.out.println("喜欢吃骨头"); //方法体
}
//重写Animal接口中的抽象方法 shout()
public void shout(){
System.out.println("汪汪......");
}
//重写Animal接口中的抽象方法 info()
public void info(){
System.out.println("名称:"+NAME);
}
}
//定义测试类
public class Example11 {
public static void main(String[] args) {
System.out.println("编号"+Animal.getID()); //使用Animal接口直接访问了Animal接口中的静态方法getId()
//抽象、接口(接口是更加抽象的抽象类)不能直接实例化对象
Dog dog = new Dog(); //创建Dog类的实例对象 Ctrl+Alt+v
dog.info();
dog.shout(); //调用Dog类中重写的shout()方法
dog.eat(); //调用Dog类中重写的eat()方法
}
}
/*结果是:
编号1
名称:牧羊犬
汪汪......
喜欢吃骨头
*/
上述代码是类与接口之间的实现关系,如果在开发中一个类既要实现接口,又要继承抽象类,则可以按照以下格式定义类。
修饰符 class 类名 extends 父类名 implements 接口1, 接口2,...{
...
}
示例:演示一个类既实现接口,又继承抽象类的情况
package com.company.example12;
//定义接口 Animal
interface Animal{
public String NAME ="牧羊犬"; //定义全局变量
public void shout(); //定义抽象方法shout()
public void info(); //定义抽象方法info()
}
//定义抽象方法eat()
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("喜欢吃骨头"); //方法体
}
//重写Animal接口中的抽象方法 shout()
public void shout(){
System.out.println("汪汪......");
}
//重写Animal接口中的抽象方法 info()
public void info(){
System.out.println("名称:"+NAME);
}
}
//定义测试类
public class Example12 {
public static void main(String[] args) {
//抽象、接口(接口是更加抽象的抽象类)不能直接实例化对象
Dog dog = new Dog(); //创建Dog类的实例对象 Ctrl+Alt+v
dog.info(); //调用Dog类中重写的info()方法
dog.shout(); //调用Dog类中重写的shout()方法
dog.eat(); //调用Dog类中重写的eat()方法
}
}
/*
结果是:
名称:牧羊犬
汪汪......
喜欢吃骨头
*/
接口是不允许继承抽象类的,但是允许一个接口继承多个接口。
示例:接口的继承
package com.company.example13;
//定义接口 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抽象类并实现 Animal接口
class Dog implements Action {
//重写Animal接口中的抽象方法 info()
public void info(){
System.out.println("名称:"+NAME);
}
//重写Color接口中的抽象方法 shout()
public void black(){
System.out.println("黑色");
}
//重写Action接口中的抽象方法 shout()
public void shout(){
System.out.println("汪汪......");
}
}
//定义测试类
public class Example13 {
public static void main(String[] args) {
//抽象、接口(接口是更加抽象的抽象类)不能直接实例化对象
Dog dog = new Dog(); //创建Dog类的实例对象 Ctrl+Alt+v
dog.info(); //调用Dog类中重写的info()方法
dog.shout(); //调用Dog类中重写的shout()方法
dog.black(); //调用Dog类中重写的black()方法
}
}
/*
结果:
名称:牧羊犬
汪汪......
黑色
*/