类的继承
继承在面向对象开发思想中是一个非常重要的概念,它使整个程序架构具有一定的弹性。在程序中复用一些已经定义完善的类,不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性。
在Java语言中,一个类继承另一个类需要使用关键字extends,关键字extends的使用方法如下:
class Child extends Parent{}
因为Java只支持单继承,即一个类只能有一个父类,所以类似下面的代码是错误的:
class Child extends Parent1,Parent2{}
子类在继承父类之后,创建子类对象的同时也会调用父类的构造方法。
【例题7.1】
package Seven;
class Parent{
public Parent(){
System.out.println("调用父类构造方法");
}
}
class Child extends Parent{
public Child(){
System.out.println("调用子类构造方法");
}
}
public class Demo {
public static void main(String[]ages){
new Child();
}
}
运行结果及代码截图如下:
子类继承父类之后可以调用父类创建好的属性和方法
【例题7.2】
package Seven;
class Telephone{//电话类
String button="button:0~9";//成员属性,10个按键
void call(){//拨打电话功能
System.out.println("开始拨打电话");
}
}
class Mobile extends Telephone{//手机类继承电话类
String screen="screen:液晶屏";//成员属性,液晶屏幕
}
public class Demo2{
public static void main (String[]args){
Mobile motto=new Mobile();
System.out.println(motto.button);//子类调用父类属性
System.out.println(motto.screen);//子类调用父类没有的属性
motto.call();//子类调用父类方法
}
}
子类Mobile类仅创建了显示屏属性,剩余的其他属性和方法都是从父类Telephone类中继承的。
Java虽然不允许同时继承两个父类,但不代表没有多继承的关系,可以通过类似“祖父>父亲>儿子>孙子”的方式实现多带继承。
例如,绝大多数动物有眼睛、鼻子和嘴,犬类也有眼睛、鼻子和嘴。哈士奇是犬类的一个品种,犬类有的特性哈士奇都有。但哈士奇的眼睛、鼻子和嘴并不是从犬类继承的而是从动物类继承的。用Java的编码如下:
class Animal{//父类:动物类
Eye eye;
Mouth mouth;
Nose nose;
}
class Dog extends Animal{}//子类:犬类
class Husky extends Dog{}//孙子类:哈士奇类
这三个类的继承关系就是Husky类继承Dog类继承Animal类,虽然Husky类没有直接继承Animal类,但是Husky类可以调用Animal类提供的可被继承的成员变量和方法。
Object类
开始学习使用class关键字定义类时,就应用到了继承原理,因为在Java中所有的类都直接或间接继承了java.lang.Object类。Object类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。用户创建一个类时,除非已经指定要从其他类继承,否则它就是从java.lang.Object类继承而来的。Inreger类等都是继承于Object类。除此之外,自定义的类也都继承于Object。由于所有的类都是Object类的子类,所以在定义类时可以省略extends Object。
由于所有的类都是Object的子类,所以所有的类都是Object类的子类,所以任何类都可以重写Object中的方法。
注意:Object类中的getClass()、notify()、notifyAll()、wait()等方法不能被重写,因为这些方法被定义为final类型。
下面详细讲述Object类中的几个重要方法。
1.getClass()方法
getClass()方法时Object定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称。语法如下:
getClass().getname();
2.toString()方法
toString()方法的功能是将一个对象返回为字符串形式,它会返回对象执行时的String实例。在实际应用中通常重写toString()方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法。
【例题7.3】让学生自我介绍
package Seven;
public class Student {
String name;
int age;
public Student(String name,int age) {
this.name=name;
this.age=age;
}
public String toString() {
return "我叫"+name+",今年"+age+"岁";
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1=new Student("张三",16);
System.out.println(s1);
Student s2=new Student("李四",19);
System.out.println(s2);
}
}
代码及输出结果如下图:
3.equals()方法
在Java语言中,有两种比较对象的方法,分别为“==”运算符与equals()方法。两者的区别在于:“==”比较的是两个对象引用内存地址是否相等,而equals()方法比较的是两个对象的实际内容。
【例题7.4】根据身份证号判断是否为同一人
package Seven;
public class People {
int id;//身份证号
String name;//名字
public People(int id,String name) {
this.id=id;
this.name=name;
}
public boolean equals(Object obj) {//重写Object类的equals()方法
if(this==obj)
return true;
if(obj==null)//如果参数是null
return false;
if(getClass()!=obj.getClass())//如果参数类型与本类类型不同
return false;
People other=(People)obj;//参数强转成本类对象
if(id!=other.id)//如果两者身份证号不相等
return true;
return false;
}
public String toString() {//重写Object的toString方法
return name;//只输出名字
}
public static void main(String[] args) {
People p1=new People(220,"tom");
People p2=new People(220,"汤姆");
People p3=new People(330,"张三");
Object o=new Object();
System.out.println(p1+"与"+p2+"是否为同一人?");
System.out.println("equals()方法的结果:"+p1.equals(p2));
System.out.println("==运算符的结果:"+(p1==p2));
System.out.println();
System.out.println(p1+"与"+p3+"是否为同一人?");
System.out.println(p1.equals(p3));
}
}
运行结果及代码截图如下:
子类重写父类的方法
返回参数相同 方法名相同 传入参数相同 只有方法体不同
传入参数不注重顺序,注重类型!
转型
向上转型:将子类类型的对象转换为父类类型的对象。即把子类对象直接赋值给父类类型的对象。
自动类型转换
Animal a=new Dog();
向下转型:将父类类型的对象转换为子类类型的对象。将父类对象赋值给子类引用。
强制类型转换
Dog a=(Dog)new Animal();
【例题7.5】tom是谁?
使用向上转型模拟如下场景:这里有一个人,他叫tom,他是一名教师
package Seven;
class People1{}
class Teacher extends People1{}
public class Demo3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
People1 tom=new Teacher();
}
}
因为人类(people)是教师类(Teacher)的父类,所以通过向上转型,能够把教师类型的对象(new Teacher();)直接赋值给人类(People)类型的变量(tom)。进行向上转型,父类类型的对象而可以引用子类类型的对象。而且,向上转型是安全的。
【例题7.6】谁是鸽子?
编写代码证明“可以说某只鸽子是一只鸟,却不能说某只鸟是一只鸽子”:鸟类是鸽子类的父类,用Bird表示鸟类,pigeon表示鸽子类。
package Seven;
class Bird{}
class Pigeon extends Bird{}
public class Sevensix {
public static void main(String[] args) {
// TODO Auto-generated method stub
Bird bird=new Pigeon();//某只鸽子是一只鸟
Pigeon pigeon=bird;//某只鸟是一只鸽子
}
}
报错:Type mismatch: cannot convert from Bird to Pigeon
翻译:类型不匹配
截图:
解决方法:强制类型转换
向下转型语法:
子类类型 子类对象 =(子类对象)父类对象;
package Seven;
class Bird{}
class Pigeon extends Bird{}
public class Sevensix {
public static void main(String[] args) {
// TODO Auto-generated method stub
Bird bird=new Pigeon();
Pigeon pigeon=(Pigeon)bird;
}
}
截图:
注意:(1)两个没有继承关系的对象不可以进行向上转型或者向下转型
(2)父类对象可以强制转换为子类对象,但有一个前提:这个父类对象要先引用这个子类对象。
重载
方法名相同 参数不同即为重载
一:返回参数不同
二:传入参数不同
三:传入参数顺序不同
四:传入参数数量不同
顺序不同,也叫重载
【例题7.8】编写不同形式的加法运算方法
package Seven;
public class OverLoadTest {
public static int add(int a,int b) {//定义一个方法
return a+b;
}
public static double add(double a,double b) {//与第一个方法名相同、参数类型不同
return a+b;
}
public static int add(int a) {//与第一个方法参数个数不同
return a;
}
public static int add(int a,double b) {//先int参数,后double参数
return a;//输出int参数值
}
public static int add(double a,int b) {
return b;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("调用add(int,int)方法:"+add(1,2));
System.out.println("调用add(double,double)方法:"+add(2.1,3.3));
System.out.println("调用add(int)方法:"+add(1));
System.out.println("调用add(int,double)方法:"+add(5,8.0));
System.out.println("调用add(double,int)方法:"+add(5.0,8));
}
}
代码截图如下:
总结:参数类型不同,参数顺序不同,参数个数不同都构成重载
不定长参数方法:
返回值 方法名 (参数数据类型...参数名称)
【例题7.9】使用不定长参数重载加法运算方法
package Seven;
public class OverLoadTest2 {
public static int add(int a,int b) {
return a+b;
}
public static double add(double a,double b) {
return a+b;
}
public static int add(int a) {
return a;
}
public static int add(int a,double b) {
return a;
}
public static int add(double a,int b) {
return b;
}
public static int add(int ...a) {//定义不定长参数方法
int s=0;
for(int i=0;i<a.length;i++) {//根据参数个数做循环操作
s+=a[i];//将每个参数累加
}
return s;//将计算结果返回
}
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("调用add(int,int)方法:"+add(1,2));
System.out.println("调用add(double,double)方法:"+add(2.1,3.3));
System.out.println("调用add(int)方法:"+add(1));
System.out.println("调用add(int,double)方法:"+add(5,8.0));
System.out.println("调用add(double,int)方法:"+add(5.0,8));
//调用不定长参数放法
System.out.println("调用不定长方法:"+add(1,2,3,4,5,6,7,8,9));
System.out.println("调用不定长方法:"+add(1));
}
}
运行结果及截图如下:
final
final修饰变量——常量
final修饰方法——该方法不可以被重写
final修饰类——不可以被继承
package Seven;
public class FinalData {
static final double PI=3.1415926;
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("圆周率的值为:"+PI);
System.out.println("半径为·3的圆的周长为:"+(2*3*PI));
//尝试修改PI的值
PI=3.1415927;
}
}
package Seven;
class Dad {
public final void trunOnTheTv() {
System.out.println("爸爸打开了电视");
}
}
class Baby extends Dad{
public final void trunOnTheTv() {
System.out.println("宝宝也要打开电视");
}
}
如上述代码则报错:Cannot override the final method from Dad
翻译:无法覆盖爸爸的最终方法
截图如下:
instanceof关键字
用于判断父类对象是否为子类对象的实例。还可以判断一个类是否实现了某个接口
【例题7.7】分析几何图形之间的继承关系
package Seven;
class Quedrangle{}//四边形类
class Square extends Quedrangle{}//正方形类继承四边形类
class Circular{};
public class Demo5 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Quedrangle q=new Quedrangle();//四边形对象
Square s=new Square();//正方形对象
System.out.println(q instanceof Square);//判断四边形是否为正方形的子类
System.out.println(s instanceof Quedrangle);//判断正方形是否为四边形的子类
System.out.println(q instanceof Circular);//判断正方形是否为圆形的子类
}
}
因为无法判断正方形为圆形的子类所以报错:Incompatible conditional operand types Quedrangle and Circular
翻译:不兼容的条件操作数类型 Quedrangle 和 Circular
去除最后一行代码,代码截图如下:
多态
利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。
【例题7.12】
package six;
class Shape{}//图形类
class Square extends Shape{}//正方形继承图形类
class Circular extends Shape{}//圆形类继承图形类
public class Demo6 {
public static void draw(Shape s) {//绘制方法
if(s instanceof Square) {//如果是正方形
System.out.println("绘制正方形");
}
else if(s instanceof Circular) {//如果是圆形
System.out.println("绘制图形");
}
else {//如果是其他图形
System.out.println("绘制父类图形");
}
}
public static void main(String[] args) {
draw(new Shape());//调用draw方法判断Shape为什么图形
draw(new Square());
draw(new Circular());
}
}
代码及截图如下:
类名 insanceof 接口名
判断该类是否实现了该接口
【例题7.13】
package Seven;
interface Paintable{//可绘制接口
public void draw();//绘制抽象方法
}
class Quadrangle{//四边形类
public void doAnything() {
System.out.println("四边形提供的方法");
}
}
//平行四边形类,继承四边形类,并实现了可绘制接口
class Parallelogram extends Quadrangle implements Paintable{
public void draw() {//由于该类实现了接口,所以需要覆盖draw方法
System.out.println("绘制平行四边形");
}
}
//正方形继承平行四边形,并实现了可绘制接口
class Squareab extends Quadrangle implements Paintable{
public void draw() {
System.out.println("绘制正方形");
}
}
//圆形类,仅实现可绘制接口
class Circularab implements Paintable{//圆形类实现接口
public void draw() {
System.out.println("绘制圆形");
}
}
public class Demo7 {
public static void main(String[] args) {
Squareab q=new Squareab();//正方形对象
q.draw();
q.doAnything();
Parallelogram p=new Parallelogram();//平行四边形对象
p.draw();
p.doAnything();
Circularab c=new Circularab();//圆形对象
c.draw();
}
}
代码及输出截图如下:
从这个结果可以看出,“绘制”这个行为不是四边形独有的,圆形也能绘制,所有draw()方法被独立封装在了可绘制接口中。
正方形类与平行四边形类分别继承了四边形类并实现了可绘制接口,所以正方形类与平行四边形类既可以调用绘制方法,又可以调用四边形提供的do Anything()方法。但是圆形不属于四边形,且可以绘制,所以最后圆形对象只调用了draw()方法。
抽象类与接口
在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是在上方的类越抽象,如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中,并不需要父类初始化为对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化对象。
使用abstract关键字定义的类称为抽象类,而使用这个关键字定义的方法称为抽象方法。抽象方法没有方法体,这个方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类必须被继承,实际上抽象类除了被继承没有任何意义。
定义抽象方法:
修饰符 abstract 返回参数 方法名(传入参数);
public abstract class Patent{
abstract void testAbstract();//定义抽象方法
}
反过来讲,如果声明一个抽象方法,就必须承载这个抽象方法的定义为抽象类,不能在抽象类中获取抽象方法。换句话说,只要类中有一个抽象方法,此类就被标记为抽象类。
抽象类被继承后需要实现其中所有的抽象方法,也就是保证以相同的方法名称、参数列表和返回值类型创建出非抽象方法,当然也可以时抽象方法。
继承抽象类的所有子类需要将抽象类中的抽象方法进行覆盖。这样在多态机制中,就可以将父类修改为抽象类,将draw()方法设置为抽象方法,然后每个子类都会重写这个方法来处理。但这又会出现我们刚探讨多态时讨论的问题,程序中会有太多冗杂的代码,同时这样的父类局限性很大,也许某个不需要draw()的子类也不得不重写draw()方法。如果将draw()方法放置在另外一个类中。让那些需要draw()方法的类继承该类,不需要draw()方法的类继承图形类,又会产生新的问题:所有的子类都需要继承图形类,因为这些类从图形类中导出的,同时某些还需要draw()方法,而Java中规定类不能同时继承多个父类。
有抽象方法的类需要设置为抽象类
修饰符 abstract class 类名{}
抽象方法联合继承:若父类为抽象类,子类要为普通类,那子类必须重写父类所有方法。
接口
重点:因为Java无法实现多继承,可使用接口实现多个接口
接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。因为无法Java无法多继承,所有可以将draw()方法封装到一个接口中,使需要draw()方法的类实现这个接口,同时也继承图形类,这就是接口存在的必要性
接口使用关键字interface关键字进行定义,其语法如下:
public interface Paintable{
void draw();//定义接口方法可省略public abstract关键字
}
public:接口可以像类一样被权限修饰符修饰,但public关键字仅限于接口在与其同名的文件中被定义。
interface:定义接口关键字。
Paintable:接口名
接口 所有对外提供的方法都是抽象方法
修饰符 interface 接口名{}
Java语言每个类可以实现多个接口
一个类继承一个父类同时再实现接口,可以写成如下形式:
public class Paralleogram exrtends Quadrangle implements Paintable{
……
}
注意:(1)在接口中,方法必须被定义为public或abstract形式,其他修饰权限不被Java编译器认可。或者说,即使不将该方法声明为public形式,它也是public形式。
(2)在接口中定义的任何字段都自动是static和final的
实现implements
修饰符 class 类名 inmplements 接口1,接口2……{
}
Java中不允许出现多重继承,但使用接口就可以实现多重继承,一个类可以同时实现多个接口,因此可以将所有需要继承的接口放置在implements关键字后并使用逗号隔开,实现多个接口的语法如下:
class 类名 implements 接口1,接口2……接口n
但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中所有的方法。一个接口可以继承另一个接口,其语法如下:
interface intf{}
interface intf2 extends intf1{}//接口继承接口