1.类的继承
类的继承是面向对象程序设计的一个重要特点,通过继承可以实现代码的复用,被继承的类称为父类或超类,由继承而得到的类称为子类。
一个父类可以同时拥有多个子类,但由于Java语言不支持多重继承,所以一个类只能有一个直接父类。父类实际上是所有子类的公共成员的集合。,
而每一个子类则是父类的特殊化,是对公共成员变量和方法在功能、内涵方面的扩展和延伸。
在Java语言中有一个名为 java.lang.Object 的特殊类,所有的类都是直接或间接地继承该类而得到的。
①子类的创建
Java语言中类的继承是通过 extends 关键字来实现的,在定义时若使用 extends 关键字指出新定义类的父亲,就是在两个类之间建立了继承关系。
新定义的类称为子类,它可以从父类那里继承所有非私有成员作为自己的成员。通过在类的声明时使用 extends 关键字来创建一个类的子类,其格式如下:
class SubClass extends SuperClass{ ....... }
上述语句把 SubClass 声明为类 SuperClass 的直接子类,如果 SuperClass 又是某个类的子类,则 SubClass 同时也是该类的间接子类。
②子类的构建方法
例子:类的继承,创建个人类 Person ,再以该类为父类创建一个学生子类 Student。
class Person{ private String name; private int age; public Person(){ System.out.println("调用了个人类构造方法 Person()"); } public void setNameAge(String name,int age){ this.name = name; this.age = age; } public void show(){ System.out.println("姓名:"+name +" 年龄+:"+age); } } class Student extends Person(){ //定义 Student 类,继承自 Person 类 prinvate String department; public Student(){ //定义 Student 类的构造方法 System.out.println("调用了学生类的构造方法 Student()"); } public void setDepartment(String dep){ department = dep; System.out.println("我是"+ departement + "的学生"); } } public class { Student stu = new Student(); //创建 Student 对象 stu.setNameAge("张小三",21); //调用父类的 setNameAge() 方法 stu.show(); //调用父类的 show() 方法 stu.setDepartment("计算机系"); //调用子类setDepartment() 方法 }
说明:Java 程序在执行子类的构造方法前,会先调用父类中没有参数的构造方法,其目的是为了帮助继承自父类的成员做初始化的操作。
③调用父类中特定的构造方法
通过 super() 语句来调用父类特定的构造方法
例子:以 Person 作为父类,创建学生子类 Student,并在子类中调用父类中某指定的构造方法。
class Person{ private String name; private int age; //定义 Person 的无参构造方法 public Person(){ System.out.println("调用了 Person 类的无参构造方法"); } public Person(String name,int age){ //定义 Person 类的有参构造方法 System.out.println("调用了 Person 类的有参构造方法"); this.name = name; this.age = age; } public void show(){ System.out.println("姓名: " + name " 年龄:"+ age); } } class Student extends Person{ //定义继承来自 Person 类的子类 Student private String department; public Student(){ //定义 Student 类的无参构造方法 System,out.println("调用了学生了的无参构造方法 Student()"); } public Student(String name,int age,String dep){ //定义 Student 类的有参构造方法 super(name,age); //调用父类的有参构造方法,在第10行定义的 departement = dep; System.out.println("我是"+department + "的学生"); System.out.printlin("调用了学生类的有参构造方法 Stunden(String name,int age,String dep)") } } public class{ public static void main(String[] args){ Student stu1 = new Student(); //创建对象,并调用无参构造方法 Student stu2 = new Student("李小四",23,“信息系”); //创建对象,并调用有参构造方法 stu1.show(); stu2.show(); } }
注意:1.super() 与 this() 的功能相似,但 super() 是从子类的构造方法调用父类的构造方法,而 this() 则是在同一个类内调用其他的构造方法。
2.super () 与 this() 均必须放在构造方法内的第一行,正因如此,super() 与 this() 无法同时存在同一个构造方法内。
2.在子类中访问父类成员
在子类中使用 super 不但可以访问父类的构造方法,还可以访问父类的成员变量和成员方法,但 super 不能访问在子类中添加的成员。在子
类中访问父类成员的格式如下:super.变量名;super.方法名();
另外,由于在子类中不能继承父类中的 private 成员,所以无法在子类中(类外)访问父类中的这种成员。但如果将在父类中的成员声明为 protected(保护)
成员的,而非 private 成员,则 protected 成员不仅可以在父类中直接访问,同时也可以在其子类中访问。
例子:在学生子类 Student 中访问父类 Person 的成员。
class Person{ protected String name; //声明被保护的成员变量 protected int age; public Person(){} //定义 Person 类的“不做事”的无参构造方法 public Person (String name,int age){ //定义 Person 类的有参构造方法 this.name = name; this.age = age; } protected void show(){ //定义被保护的成员方法 System.out.println("姓名:"+name+" 年龄:"+age); } } class Student extends Person{ //定义子类 Student ,其父类为 Person private String departement; //声明私有数据成员 int age = 20; //新添加了一个与父类的成员变量 age 同名的成员变量 public Student (String xm,String dep){ //定义 Student 类的有参构造方法 name = xm; //在子类中直接访问父类 protected 成员 name departement = dep; super.age = 25; //利用 super 关键字将父类的成员变量 age 赋值为 25 System.out.println("子类 student 中的成员变量 age = "+age); super.show(); //去掉 super 而只写 show()也可 System.out.println("系别:" +department); } } public class { public static void main(String[] args){ Student stu = new Student("李小四",“信息系”); } }
3.覆盖 (子类中新增与父类同名的成员)
①覆盖子类的方法
在子类中重新定义已有的方法时,应保持与父类中完全相同的方法头声明,即应与父类中被覆盖的方法有完全相同的方法名、
返回值类型和参数列表,否则就不是方法的覆盖,而是子类定义自己的父类无关的方法,父类的方法未被覆盖,所以仍然存在。
也就是说,子类继承父类中所有可被访问的成员方法时,如果子类的方法头与父类中的方法头完全相同,则不能继承,此时子类
的方法就是覆盖父类的方法。
注意:子类中不能覆盖父类中声明为final或static的方法。
例子:以个人类 Person 为父类,创建学生子类 Student,并用子类中的方法覆盖父类的方法。
class Person{ protected String name; protected int age; public Person(String name,int age){ //定义 Person 类的构造方法 this.name = name; this.age = age; } protected void show(){ System.out.println("姓名:"+ name +"年龄:"+ age); } } class Student extends Person{ //定义子类 Student ,其父类为 Person private String department; public Student(String name,int age,String dep){ super(name,age); departement = dep; } protected void show(){ //覆盖父类 Person 中的同名方法 System.out.println("系别:"+ department); } } public class{ public static void main(String[] args){ Student stu = new Student("王永涛",24,“电子”); stu.show(); } } 系别:电子
②不可被继承的成员最终类
如果父类成员不希望被子类的成员所覆盖可以将他们声明为 final.如果用 final 来修饰成员变量,
则说明成员变量是最终变量,即常量,程序中的其他部分都可以访问,但不能修改。
③Object类
如果某个类没有使用 extends 关键字,则该类默认为 java.lang.Object 类的子类。
4.抽象类
在java中创建专门的类作为父类,这种类被称为抽象类(abstract class)。抽象类的作用有点类似“模板”,其目的是根据它的格式
来创建和修改新的类。但并不能直接由抽象类创建对象,只能通过抽象类派生出新的子类,再由其子类来创建对象。
抽象类是以修饰符 abstract 修饰的类。定义抽象类的语法格式如下:
abstract class 类名{ 声明成员变量; 返回值的数据类型 方法名(参数表){ ...... } ..... abstract 返回值的数据类型 方法名(参数表);———抽象方法。在抽象方法里,不能定义方法体。 }
注意:abstract 不能与 private、static、final或native并列修饰同一个方法。
例子:在抽象类 Base,基础上派生出一个矩形类 Rectangle,新增成员变量 width和 height ,分别代表矩形的长和宽,采用构造方法给成员变量赋值,
并实现抽象类中的抽象方法,在声明一个主类,创建一个矩形类的对象,该对象的数据成员(color,width,height)的取值为(“red”,8,3),在屏幕上
显示出该矩形的成员变量值,并求矩形的面积和周长。
class Rectangle extends Base{ double width; double height; public Rectangle(String s,double w,double h){ super(s); width = w; height = h; } public double area(){ return width*height; } public double perimeter(){ return(width + height)*2; } public void display(){ System.out.pritln(color +" "+width+","+height); } public static void main(String[] args){ Base m = new Rectangle("red",8,3); m.display(); System.out.println("面积="+m.area()); System.out.println("周长="+m.perimeter()); } }
5.接口
接口是(interface)是java语言提供的另一种重要功能,它的结构与抽象类相似。本身也具有数据成员、抽象方法、默认方法和静态方法,
但它与抽象类有下列不同。
(1)接口的数据成员都是静态的且必须初始化,即数据成员必须是静态常量。
(2)接口中除了声明抽象方法外,还可以定义静态方法和默认方法,但不能定义一般方法。
①接口的定义:
②接口的实现与引用
接口实现的语法格式:
class 类名称 implement 接口名表{ ........ }
一个类要实现一个接口时,应注意一下问题:
(1)如果实现某接口的类不是 abstract 的抽象类,则在类的定义部分必须实现指定接口的所有抽象方法,即非抽象类中不存在抽象方法。
(2)一个类在实现某接口的抽象方法时,必须使用完全相同的方法头,否则只是在定义一个新方法,而不是实现已有的抽象方法。
(3)接口中抽象方法的访问控制修饰符都已指定为 pulic ,所以类在实现方法时,必须显式地使用 public 修饰符,否则将被系统警告为缩小了接口中
定义方法的访问控制范围。
(4) 与类一样,每个接口都被编译成独立的扩展名为.class 的字节码文件。
例子:计算圆柱的底面积和体积,通过类MainClass 继承父类 Cylinder 并实现接口 Action 来达到目的。
interface Action{ static final double P = 3.14; abstract double s(); //求底面积 abstract double v(); //求体积 } class Cylinder{ protected double radius; //圆柱底面半径 protected double height; public Cylinder(double r,double h){ radius = r; height = h; } }
③利用接口实现类的多重继承(只能继承一个类,但可以实现多个接口)
例子:利用接口实现类的多重继承
package ch08; interface Face1{ final double PI=3.14; abstract double area(); } interface Face2{ abstract void setColor(String c); } interface Face3{ abstract void volume(); } public class Cylinder implements Face1,Face3{ //Cylinder 类实现并继承两个接口 private double radius; private int height; protected String color; public Cylinder(double r,int h){ radius=r; height=h; } public double area(){ return PI*radius*radius; } public void setColor(String c){ color=c; System.out.println("颜色:"+color); } public void volume(){ System.out.println("圆柱体体积="+area()*height); } public static void main(String[] args){ Cylinder volu=new Cylinder(3.0,2); volu.setColor("红色"); volu.volume(); } }
④接口中静态方法和默认方法
接口中的静态方法与普通类中静态方法的定义相同。接口中的静态方法不能被子类接口继承,也不能被实现该接口的类继承。
对接口中静态方法的访问,可以通过接口名直接访问,可以通过接口名进行访问,即“接口名.静态方法名()”的形式进行调用。接口中的
默认方法虽然有方法体,可是不能通过接口名直接调用,但可以通过接口实现类的实例进行访问,即“对象名.默认方法名()”形式。
例子:在接口 Face 中定义默认方法、静态方法和抽象方法,在接口的实现类中进行相应的方法调用。
interface Face{ final static double PI = 3.14; //定义常量 public default double area(int r){ //定义默认方法 return r * r * PI; } abstract double volume(int r,double h){ //声明抽象方法 public static show(){ //定义静态方法 return "我是 Face 接口中的静态方法"; } } public class App8_14 implements Face{ //定义主类App8_14 并实现接口 Face public double volume(int r,double h){ //实现接口中的方法 return area(r) * h; //调用接口中的默认方法 area() } public static void main(String[] args){ System.out.println(Face.show()); App8_14 ap = new App8_14; //直接使用接口名调用接口的静态方法 System.out.println("圆柱体体积为:"+ap.volume(1,2.0)); } } } 我是 Face 接口中的静态方法 圆柱体体积为:6.28
⑤解决接口中多重继承中名字冲突问题
如果子接口中定义了与父接口同名的常量或者相同名称的方法,则父接口中的常量被隐藏,方法被覆盖。但在接口的多重继承中可能存在常量名或方法名称重复的问题,
即名字冲突问题。对于常量若名称不冲突,子接口可以继承多个父接口中的常量,如果多个父接口中有同名的常量,则子接口不能继承,但子接口中可以定义一个同名的常量。
对于多个父接口中存在同名的方法时,此时必须通过特殊的方式加以解决。
如果一个类实现了两个接口,其中一个接口中有默认方法,另一接口中也有一个名称和参数都相同的方法(默认或抽象方法),此时发生方法名冲突。如果出现这种情况,
编译器就不知道该继承哪个方法,所以编译报错。要解决方法名冲突问题,可以在接口的实现类中提供同名方法的一个新实现,引用其中一个父接口中的默认方法,
这种引用方式称为委托某父接口中的默认方法,委托方式为:接口名. super .默认方法名 () 。