第8章 继承

通过本章可以学习:掌握继承性的主要作用、菜吗实现和相关限制,掌握方法覆盖的操作与相关限制,掌握final关键字的使用,理解常量与全局变量的定义,掌握多态性的概念与应用,并理解对象转型处理中的限制,掌握Object类的主要特点与实际引用。

面向对象程序设计的主要优点是代码的模块化设计以及代码重用,而只是依靠单一的类和对象的概念是无法实现这些设计要求的。所以为了开发出更好的面向对象的程序,还需要进一步学习继承以及多态的概念。本章将为读者详细的讲解买你想对象程序设计中继承雨多泰德相关知识。

8.1 面向对象的继承性

在面向对象的设计过程中,类是基本的逻辑单元。但是对于这些基本的逻辑单位需要考虑到重用的设计问题,所以在面向对象的设计里提供有几成,并利用这一点实现类的可重用定义。

8.1.1 继承问题的引出

一个良好的程序设计结构不仅便于维护,同时还可以提高代码的可重用性。在之前所讲解的面向对象的知识中,只是围绕着单一的类进行的,而这样的类之间没有重用性的描述。例如,从下面定义的Person类与Student类就可以发现无可重用性代码设计的缺陷。

Person.javaStudent.java

class Person
{
private String name;
private int age;
public void setName(String name)
{
this.name=name;
}

public void setAge(int age)
{
this.age=age;
}

public String getName()
{
return this.name;
}

public int getAge()
{
return this.age;
}
}

class Student
{
private String name;
private int age;
private String school;
public void setName(String name)
{
this.name=name;
}

public void setAge(int age)
{
this.age=age;
}

public void setSchool(String school)
{
this.school=school;
}

public String getName()
{
return this.name;
}

public int getAge()
{
return this.age;
}

public String getSchool()

{
return this.school;
}
}

通过以上两端代码的比较,相信读者可以清晰的发现,如果按照之前所学习到的概念进行开发的话,男程序中就会出现重复代码,且可以清晰的发现,学生本来十余人,但是学生所表示的范围要比人表示的范围更小,只能通过继承来完成。

8.1.2 类继承定义

严格来说,继承性是指扩充一个类已有的功能,在Java中,如果要实现集成的关系,可以使用以下的语法完成
class 子类 extends 父类{}在继承结构中,很多情况下会把子类称为派生类,把父类称为超类(SuperClass)
范例:继承基本实现

class Person
{
private String name;
private int age;
}

class Student extends Person{}//Student是子类,子类中不定义任何功能

public class JavaDemo
{
public static void main(String args[])
{
Student stu=new Student():
stu.setName("LLL");
stu.setAge(18);
System.out.println(""+stu.getName()+stu.getAge());
}
}

在本程序中定义Student类时并没有定义任何方法,只是让其继承Person类,而通过执行可以发现,子类可以继续重用父类中定义的属性与方法。继承实现的主要目的是子类可重用父类中的结构,同时可以根据子类功能的需要进行结构扩充,所以子类往往要比父类描述的范围更小,特性更多。

范例:在子类中扩充父类的功能

class Person
{
private String name;
private int age;
}
class Student extends Person
{
private String school;
public void setSchool(String school)
{
this.school=school;
}
public String getSchool()
{
return school;
}
}

public class JavaDemo
{
public static void main(String args[])
{
Student stu=new Student;
stu.setName("LLL");
stu.setAge(18);
stu.setSchool("LLL");

}
}

本程序在Student类在已有的Person类的基础上扩充了新的属性与方法,相比较Perosn类而言,Student类的描述范围更加具体。

8.1.3 子类对象实例化流程

在继承借够钟,子类需要重用父类中的结构,所以在进行子类对象实例化之前往往都会默认调用父类中的无参构造方法,为父类对象实例化(属性初始化),而后在进行子类的调用,为子类对象实例化(属性初始化)

范例:子类对象实例化,观察无参构造调用

class Person
{
public Person()
{
System.out.println("【Person父类】调用Person父类构造实例化对象public Person()");
}
}

class Student exntends Person
{
public Student()
{
System.out.println("【Student 父类】调用Student 子类构造实例化对象public Person()");
}
}

执行结果是先调用父类的后调用子类的,本程序在实例化Student子类对象时只调用了子类构造,而通过执行结果可以发现,父类构造会被默认调用,执行完毕后才调用了子类构造,所以可以得出结论:子类对象实例化钱一定会实例化父类对象,实际上这个时候就相当于子类的构造方法中隐含了一个super()的姓氏。

范例:观察子类构造
class Student extends Person
{
public Student()
{
super();
System.out.println("【Student 父类】调用Student 子类构造实例化对象public Student()");
}
}

子类中的super()的作用表示在子类中明确调用父类的无参构造,如果不写也默认会调用父类构造,对于super()构造调用的语句只能够在子类的构造方法中定义,并且必须放在子类构造方法的首行。如果父类没有提供无参构造方法,也可以通过super(参数)的形势进行调用。

范例:明确调用父类制定构造方法
class Person
{
private String name;
private int age;
public Person(String name,int age)
{
this.name=name;
this.age=age;
}

}
class Student extends Person
{
private String school;
public Student(String name,int age,String school)
{
super(name,age);
this.school=school;
}
}

public class JavaDemo
{
public static void main(String args[])
{
Student stu=new Student("LLL",18,"LLL");
}
}

本程序Person父类不再提供无参构造方法,这样在子类构造方法中就必须通过super()明确指明要调用的父类构造,并且该语句必须放在子类构造的方法的首行。

提问:有没有不让子类去调用父类构造的可能性
既然super()和this都是调用构造方法,而且都要放在构造方法的首行,如果说this()出现了,那么super()就不会出现了,所以编写了一下的程序

范例:疑问的程序

class A{
public A(String msg){System.out.println("msg="+msg);}//父类无参构造
}

class B extends A
{
public B(String msg)//子类构造
{
this("LLL",30);//调用本类构造,无法使用super()
}
public B(String msg,int age)/子类构造
{
this(msg);//调用本类构造,无法使用super()
}
}
public class TestDemo
{
public static void main(String args[])
{
B b=new B(Hello,20)
}
}

在本程序中,子类B的每一个构造方法,都使用了this()调用本类构造方法,那么很专业昂是不是就表示子类无法调用父类的构造呢,
回答:本程序编译有错误,
在之前讲解this关键字的时候强调过一句话:如果一个类中有多个构造方法时间使用this()相互调用的话,那么至少要保留一个构造方法作为出口,而这个出口就一定会去调用父类构造。

8.1.4 继承限制

继承是类重用的一种实现手段,而在Java中针对类继承的合理性设置了相关限制。

限制1:一个子类只能集成一个父类,存在单继承局限

这个概念实际上是相对于其他语言而言的,在其他语言中,一个子类可以同时集成多个父类,这样就可以同时获取多个父类中的方法,在Java中是不允许的,以下为错误的继承代码
class A{}
class B{}
class C extends A,B{}
以上操作称为多重继承,实际上以上的做法就是希望一个子类,可以同时继承多个子类的功能,在Java中并不支持此类语法,但是可以是如下方式:
class A{}
class B extends A{}//B类继承A类
class C exntendsB{}//C类继承B类
C实际上属于孙子类,这样一来就相当于B类继承了A类的全部方法,而C类又继承了A类和B类的方法,这种操作成为多层集成。结论:Java之中只允许多层集成,不允许多重集成,Java存在单继承限制。
注意:继承层次不要过多
类继承虽然可以实现代码的重用,但是如果在编写项目中类的继承结果过多,会导致代码阅读苦难。对于大部分程序编写,不建议超过3层。

限制2:在一个子类继承的时候,实际上会集成父类的所有操作(属性、方法),但是需要注意的是,对于所有的非私有(no private)操作属于显式继承(可以直接利用对象操作),而所有的私有(private)操作属于隐式继承(间接完成)。

范例:不允许直接访问非私有操作

class person
{
private String name;//姓名
public void setName(String name)//构造方法设置姓名
{
this.name=name;
}
public String getName()//获取私有属性
{
return this.name;
}
}

class Student extends Person
{
public Student(String name)//子类构造
{
setName(name);//调用父类构造,设置name属性内容
}
public String getInfo()
{
//"Error System.out.println(name)"因为父类使用private声明,无法访问
return ""+getName()//间接访问
}
}

public static void main(String args[])
{
Student stu=new Student("AA");
System.out.println(stu.getInfo());
}

本程序中Person父类定义的name属性虽然可以被子类使用,但是由于存在private定义,所以在类中无法直接进行私有属性的访问的,只能通过getter()的方式进行直接访问,所以该属性属于隐式继承。

8.2 覆写

在继承关系中,父类作为最基础的类存在,其定义的所有结构都是为了完成本类的需求而设计的,但是在更多的时候由于某些特殊的需要,子类有可能会定义与父类名称相同的方法火属性,子类方法成为覆写。

8.2.1 方法覆写

在类继承结构中,子类可以继承父类中的全部方法,当父类某些方法无法满足子类设计需要时,可以针对已有的方法进行扩充,那么此时在子类中定义与父类中方法名称、返回值类型、参数类型以及个数完全相同方法的时候,称为方法覆写。

范例:方法覆写基本实现

class Channel
{
public void connect()//父类定义方法
{
System.out.println("【channel】父类进行资源的链接");
}
}

class DatabaseChannel extends Channel //要进行书库链接
{
public void connect() //方法覆写,保留已有方法名称
{
System.out.println("【DatabaseChannel 】进行数据库资源的链接");
}
}

public class JavaDemo
{
public static void main(String args[])
{
DatabaseChannel channel=new DatabaseChannel();
channel.connect()
}
}

执行结果:【DatabaseChannel 】进行数据库资源的链接

本程序为channel类定义了一个DatabaseChannel子类,并且在子类中定义了与父类完全相同的connect()方法,这样在利用子类实例化对象调用connect()方法时就是调用的是被覆写后的方法。

当通过子类实例化对象调用方法时所调用的是被覆写后的方法,此时需要调用父类已经覆写后的方法,在子类中可以使用super.方法()的姓氏进行调用。

范例:子类调用父类已经覆写过得方法

class Channel
{
public void connect()
{
System.out.println("【channel】父类进行资源的链接");
}
}
class DatabaseChannel ectends Channel
{
public void connect()
{
super.connect();
System.out.println("【DatabaseChannel 】进行数据库的链接");
}
}

public class JavaDemo
{
public static void main(String args[])
{
DatabaseChannel channel=new DatabaseChannel();
channel.connect();
}
}
程序执行结果:【channel】父类进行资源的链接,【DatabaseChannel 】进行数据库的链接
本子类覆写了connect()方法,这样子类只能通过super.connect()调用父类中已经被覆写后的方法。
提示:关于this与super的调用范围,在本程序DatabaseChannel.connect()方法中如果使用this.connect或者connect()形式的语句,所调用的方法就是被子类覆写过得方法,这样执行中会出现StackOverflowError错误。多以可得出这样一个结论:this调用结构时会先从本类查找,如果没有则去寻找父类中的相应结构,而super()调用时不会查找子类,而是直接调用父类结构。

8.2.2 方法覆写限制

子类利用方法覆写可以扩充父类方法的功能,但是在进行方法覆写时有一个核心的问题:被子类所覆写的方法不能拥有比父类更严格的访问权限控制,目前已经接触到3中访问控制权限大小关系private<default<public
提示:java中一共分为4种访问权限(封装性的视线主要依靠访问限制)。

如果此时父类中的方法是default权限,那么子类覆写的时候只能是default或public权限;而如果父类的方法时public,那么子类中方法的访问权限只能是public

范例:观察错误的方法覆写
public Channel
{
public void connect()

{
System.out.println("【Channel父类】进行资源的链接")
}
}
class DatabaseChannel extends Channel
{
void connect()
{
System.out.println("【DatabaseChannel 父类】进行资数据库的链接")
}
}

本程序中在DatabaseChannel子类中定义了connect()方法,由于子类在进行方法覆写缩小了父类的访问权限,所以此时方法不属于覆写,编译时会发生错误。

注意:父类方法定义private时,子类无法覆写该方法
按照方法覆写的颜值要求,子类方法设置的权限需要大于等于父类的权限,但是如果父类中的方法使用的是private,则子类无法进行覆写该方法,这个时候即使子类定义的方法符合覆写要求,对于子类而言也只是定义了一个新方法

范例:观察private权限下的方法覆写
class Channel
{
private void connect()
{
System.out.println("【Channel父类】进行资源的链接")
}
public void handle()
{
this.connect();
}
}

class DatabaseChannel extends Channel
{
void connect()
{
System.out.println("【DatabaseChannel 父类】进行资数据库的链接")
}
}

public class JavaDemo
{
public static void main(String args[])
{
DatabaseChannel channel=new DatabaseChannel();
channel.handle();
}
}

执行结果:【Channel父类】进行资源的链接
本程序从覆写的要求来讲,子类的结构属于覆写,但是由于父类中的connect()方法使用了private定义,所以此方法将无法被覆写。当子类实例化对象调用handle()方法时,发现所调用的并非覆写过得方法(如果成功覆写,调用一定是子类中的connect()方法)。多以private权限生命的方法无法被子类所覆写

提示:方法重载与覆写的区别
方法重载与覆写严格意义来讲都属于面向对象多态性的一种形式

8.2.3 属性覆盖

子类除了可以对父类中的方法进行覆写意外,也可以对废private定义的父类属性进行覆盖,此时秩序定义域父类中成员属性相一致的名称即可。

范例:属性覆盖
class Channel
{
String info="AAA";
}
class DatabaseChannel extends Channel
{
int info=12;
public void fun()
{
System.out.println("【父类info成员属性】"+super.info");
System.out.println("【子类info成员属性】"+this.info")
}
}

public class JavaDemo
{
public static void main(String args[])
{
DatabaseChannel channel=new DatabaseChannel();
channel.fun();//子类扩充方法
}
}

本程序在子类中定义了一个与父类名称相同,但是类型不同的成员属性info,所以此时就发生了属性覆盖,如果要调用父类的成员属性就必须通过super.info执行

提示:this与super的区别
在程序编写之中,this与super有着类似的语法:
 

No区别thissuper
1定义表示本类对象表示父类对象
2使用本来操作:this属性、this方法()、this()父类操作:super.属性、super.方法()super()
3调用构造调用本来构造,要放在首行子类调用父类构造,要放在首行
4查找范围先从本类查找,找不到查找父类直接由子类查找父类
5特殊表示相亲对象

8.3 final关键字

final在程序中描述为终接器的概念,在Java中使用final关键字可以实现以下功能:定义不能被继承的类,定义不能被覆写的方法,定义常量(全局变量)。
范例:使用final定义的类不能有子类
final class Channel{}//这个类不能有子类
Channel类上由于使用了final关键字,所以该类不允许有子类,实际上String类上也是用了final定义,所以String类也无法定义子类。
        当子类继承了父类之后实际上是可以进行父类中方法的覆写的,但是如果你现在不希望你的某一个方法被覆写,就可以使用final来进行定义
范例:使用final定义的方法不能被子类所覆写
class Channel
{
public final connect();
}
class DatabaseChannel extends Channel{
public void connect();
}

Channel父类中的connect()方法上使用了final关键字定义,这样该方法无法在子类中覆写。在有的系统设计中,可能会使用1表示开关打开的状态,使用0表示开关关闭的状态。如果直接对数字0和1进行操作,则可能造成状态的混乱,在这样的情况下就可以通过一些名称来表示0或者1.在final关键字里有一个重要的应用技术:可以利用其定一场凉,常量的内容一旦定义则不可修改:
范例:使用final定义常量
class Channel
{
private final int ON=1;
private final int OFF=0;
}

常量在定义时就需要为其设置对应的内容,并且其内容一旦定义将不可更改,在Java程序中为了将常量与普通成员属性进行区分,所以要求常量名称字母全部大写。
        大部分的系统设计中,常量往往都会作为一些全聚德标记使用,所以在进行常量定义时《往往会使用public static final组合来定义全局常量。

范例:定义全局常量
class Channel
{
public static final int ON=1;
public static finale int OFF=0;
}

static的主要功能就是进行公共数据的定义,而同时使用final后就表示定义的常量为公共常量,在实际项目开发中会利用次结构定义相关状态码。

提示:关于常量与字符串链接问题

在第7章讲解String类时曾将强调过静态常量池的概念,在进行字符串连接时,会在编译时进行常量的定义,其中的常量也可以通过全局变量来表示。
范例:全局常量与字符串连接

public class JavaDemo
{
public static final String INFO="LLL";
public static void main(String args[])
{
String strA="AAAA";
String strB="BBBB"+INFO+".cn";
System.out.println(strA==strB);
}
}

程序执行结果true,本程序通过INFO常量实现了字符串连接操作,最终的结果就是两个String类都只想同一块堆内存。

8.4 Annotation注解

Annotation是通过注解配置简化程序配置代码的一种技术手段,这时从JDK1.5后兴起的一种新的开发形式,并且在许多的开发框架中都会使用到Annotaion

提示:从Annotation看开发结构的发展
如果要想清楚Annotation的产生一一,就必须了解一下程序开发结构的历史。程序开发一共分为3个过程(以程序开发所需要的服务器信息为例)
阶段1:在程序定义的时候将所有可能用到的资源全部定义在程序代码中。
如果此时服务器的相关地址发生了改变,那么对于程序而言就是修改源代码,维护需要开发人员来完成非常不方便。
阶段2:引入配置文件,在配置文件中定义全部要使用的服务器资源
在配置项不多的情况下,配置文件非常好用,而且简单,但是如果这个时候所有的项目都是采用这种结构进行开发,那么就会出现一种可怕的情景:配置文件过多,维护困难
所有的操作都需要通过配置文件完成,开发的难度明显提升。
阶段3将配置信息重新写到程序里面,利用一些特殊的标记将配置信息与程序代码进行分离,这就是注解的作用,这也是Annotation提出的基本依据。
        但如果全部采用注解,开发难度太高了,而配置文件虽然也有缺点,但也有优势之处,现在的开发基本上采用配置文件+注解的形式完成。

为方便读者理解Annotation注解的作用,接下来先讲解3个基础的Annotation注解:@Override,@Deprecated、@SuppressWarnings

8.4.1 准确覆写

当子类继承某一个子类之后,如果发现父类中的某些方法功能不足,往往会采用覆写的形式来对方法功能进行扩充,此时为了可以再子类中明确表示哪些方法是覆写来的,就可以使用@Overide注解标注,
范例:准确覆写
class Channel
{
public void connect
{
System.out.println("【父类Channel】建立连接通道");
}
}

class DatabaseChannel extends Channel
{
@override
public void connect()
{
System.out.println("【子类DatabaseChannel】建立数据库连接通道");
}
}

public class JavaDemo
{
public static void main(String args[])
{
new DatabaseChannel().connect();
}
}

执行结果:【子类DatabaseChannel】建立数据库连接通道
本程序在子类覆写connect()方法时使用了@Override注解,这样,就可以在不清楚父类结构的情况下立刻分辨出哪些是覆写方法,哪些是子类扩充方法,同时利用@Override注解也可以在编译时检测出因为子类拼写错误所导致的方法续写错误。

8.4.2 过期声明

现代的软件项目开发已经不再是一次编写的过程了,几乎所有的店项目都会出现迭代更新的过程。每一次更新都会设计代码结构、性能与稳定性的替身,所以经常会出现某些程序结构不再适用与新版办的情况。在这样的背景下,如果在新版本更新时对那些不再推荐使用的操作使用@Deprecated注解声明,这样在程序编译时如果发现使用了此类结构会提示警示信息。

范例:过期声明

class Channel
{
@Deprecated
public void connect
{
System.out.println("进行传输过程")
}
public String connection()

{
return "新的连接"
}
}
public class JavaDemo
{
public static void main(String args[])
{
new Channel().connect();//编译时会出现警告信息
}
}

本程序中在Channel.connect()方法上使用了@Deprecated注解,项目开发者在编写新版本程序代码是可以清楚地知道此为过期操作,并且可以根据注解更换使用的方法。

注意:合理开发中不要使用@Deprecated注解定义的结构
在项目开发中,为了保证项目长期的可维护性,所以不要去使用存在有@Deprecated注解的类或者方法,这是项目开发中的一项重要标准。
        另外,需要提醒读者的是,当某些类或方法出现了@Deprectaed注解是一定会有相应的提示文字,告诉开发者替代类是哪一个,可以通过Doc文档获取。

8.4.3 压制警告

为了代码的燕歌行,往往会在编译时给出一些错误的提示信息(非致命错误),但是有些错误提示信息并不是必要的。为了房主这些提示信息的出现,Java提供@SuppressWarnings注解来进行警告信息的压制,在此注解中可以通过value属性设置要压制的警告类型,而value可设置警告信息

范例:压制警告信息

class Channel
{
@Deprecated
public void connect()
{
System.out.println("进行传输通道的链接");
}
public String connection
{
return "获取了通道链接信息";
}
}

public class JavaDemo
{
@SuppressWarnings(value={"deprecation"});
public static void main(String args[])
{
new Channel().connect();
}
}
由于程序使用了过期操作,这样在程序编译时一定会出现警告信息,此时使用@SuppressWarnings阻止在编译时提示警告信息。

提示:不需要去记住可以压制的警告信息。
上表给出的警告信息不需要强行记忆的,从实际开发开奖,往往都是利用IDE集成开发环境开发项目,这些IDE都具有自动提示机制。

8.5 面向对象多态性

在面向对象设计中多态性描述的是同一个结构在执行时会根据不同的形式展示出不同的效果,在Java中多态性可以分为两种不同的展现形式
展现形式1:方法的多态性(同样地方法也有不同的实现)
方法的重载:同一个方法可以根据传入的参数的类型合格书的不同实现不同的功能号
方法的覆写:同一个方法可以根据实现子类的不同有不同的实现

方法重载(Overloading)方法覆写(Overriding)
class Message
{
public void print()
{
System.out.println("AAA");
}
public void print(String str)
{
System.out.println(str);
}
}
class Message
{
public void print()
{

}
}
class DatabaseMessage extends Message
{
public void print()
{

}
}

方法重载多样性的意义在于一个方法名称有不同的实现:方法覆写多态性的实现在于,父亲的一个方法,不同的子类可以有不同的实现
展现形式2:对象的多态性(父类与子类实例之间的转换处理)
对象向上转型:父类 父类实力=子类实例,自动完成转换。
对象向下转型:子类 子类实例=(子类)父类实例,强制完成转换。
对于方法的多态性在之前已经有了详细的阐述,所以本节重点讲述对象的多态性,但是读者一定要记住一点,对象的多态性和方法覆写时紧密联系在一起的。

提示:关于对象多态性的转换说明
在面向对象设计中最难理解的部分就在域对象多态性上,在具体讲解之前,针对向上与乡下转型给出参考意见:从世纪的转型处理上来看,大部分情况下一定是对象的向上转型。

8.5.1 对象向上转型

在对象实例化之前一定会自动实例化父类对象,所以此时将子类对象的实例通过父类进行接受,即可实现对象的自动向上转型。而此时的本质还是子类势力,一旦子类中覆写了父类方法,并且调用该方法时,所调用的一定是被子类覆写过得方法。
范例:对象向上转型
class Message
{
public void print()
{
System.out.println("AAA");
}
}
class DatabaseMessage extends Message
{
public void print()
{
System.out.println("数据库连接信息");
}
}
class NetMessage extends Message
{
public void print()
{
System.out.println("AAAA");
}
}

public class JavaDemo
{
public static void main(String args[])
{
Message msgA=new DatabaseMessage();
msgA.print();
Message msgB=new NetMessage();
msgB.print();
}
}

在本程序中在Message两个子类中分别覆写了print()方法(不同子类对同一方法有不同的实现),随后用对象自动向上转型的原则通过子类为Message父类对象实例化,由于print()方法已经被子类所覆写,所以最终所调用的方法就是被实例化子类所覆写过的方法。

提示:不要看类名称,而是要看实例化对象的类
实际上通过本程序读者一经发现了对象向上转型的特点,整个操作中根本不需要关系对象的生命类型,关键就在于实例化新对象时所调用的那个子类的构造。如果方法被子类覆写,调用的就是被覆写过得方法,否则调用弗雷德方法。
        对象向上转型的最大特点在于其可以通过父类对象自动接受子类实例,而在实际的项目开发中,就可以利用这一原则接收后返回参数类型的通化工艺。

范例:统一方法参数

class Message
{
public void print()
{
System.out.println("AAA");
}
}
class DatabaseMessage extends Message
{
public void print()
{
System.out.println("数据库连接信息");
}
}
class NetMessage extends Message
{
public void print()
{
System.out.println("AAAA");
}
}

class Channel
{
public static void send(Message msg)
{
msg.print();
}
}

public class JavaDemo
{
public static void main(String args[])
{
Channel.send(new DatabaseMessage());
Channel.send(new NetMessage());
}
}

本程序定义的Channel.send()方法,接受的参数类型为Message,这样就意味着所有的Message以及子类都可以接受,相当于统一了方法的参数类型。

提问:可以使用重载解决问题吗
对于以上范例给出的Channel.send()方法,如果采用以下重载的形式:
class Channel
{
public static void send(DatabaseMessage msg)
{
msg.print();
}
public static void send(NetMessage msg)
{
msg.print();
}
}

此时send()方法也可以接受Message子类独享,这种做法可以嘛
回答:需要考虑到子类扩充
如果现在Message只有两个之类,那么以上的做法事完全可以的。但是如果说此时Message会有30万个子类,并且还有可能随时增加,难道要将send()方法重载30万次,并且每增加一个子类都要进行修改Channel类的源代码操作,方案明显不行。
        另外,需要提醒读者的是,一旦发生了对象的向上转型,那么父类对象可以使用的方法只能是本类或其父类定义的方法,是无法直接调用子类扩充方法的,所以在项目设计中,父类的功能设计是最为重要的。

8.5.2 对象向下转型

子类继承父类后可以对已有的父类功能进行扩充,除了采用方法覆写这一机制外,子类也定义了属于自己的新的方法。而对于子类扩充的方法只有具体的子类势力才可以调用。在这样的情况下,如果子类已经发生了向上转型后就需要通过强制性的向下转型实现子类扩充方法调用。

范例:子类对象向下转型
class Person
{
public void run()
{
...
}
}
class Superman extends Person
{
public void fly()
{}
public void fire()
{}
}
public class JavaDemo
{
public static void main(String args[])
{
Person per=new Superman();//超人是一个人向上转型
per.run();//调用人的跑步功能
SuperMan man=(Superman)per;
spm.fly();
spm.fire()
}
}

本程序中Superman子类利用对象向上转型实例化了Person类对象,此时Person类只能够调用本类或其父类定义的方法,如果此时需要调用子类中扩充的方法时,就必须强制性的将其转换为指定的子类类型。

注意:必须先发生向上转型,之后才可以进行向下转型
在对象向下转型中,父类势力是不可能强制转化为任意子类实力,必须先通过子类实例化,利用向上转型让父类对象与具体实例之间发生联系后才可以向下转型,否则会出现ClassCastException异常
范例:错误的向下转型
public class JavaDemo
{
Person per=new Person();
Superman sm=(Superman) per;
}

8.5.3 instanceof关键字

对象的向下转型存在着安全隐患,为了保证转换的安全性,可以再转换前通过instanceof关键字进行对象所属类型的判断,该关键字的使用语法如下
对象 instanceof 类
该判断将返回一个bolean类型数据,如果是true表示实例是指定类对象。

范例:观察instanceof关键字使用
public class JavaDemo
{
public static void main(String args[])
{
Person perA=new Person();
System.out.println(perA instanceof Person);
System.out.println(perA instanceof Superman);
Person perB=new Superman();
System.out.println(perB instanceof Person);
System.out.println(perB instanceof Person);
System.out.println(perB instanceof Superman);
}
}

返回结果是true,false,true,true

通过本程序的执行结果可以发现,如果一个父类对象没有通过子类实例化,则使用instanceof的实力判断结果返回的就是false,在实际开发中采用先判断后转型的方式来回避。

范例:安全的转型操作

public class JavaDemo
{
public static void main(String args[])
{
Person per=new Superman();
per.run();
if(per instanceof Superman)
{
Superman spm=(Superman) per;
spm.fly();
smp.fire();
}else

{
System.out.println("AAA");
}
}
}
本程序在进行对象向下转型前,为了防止可能出现ClassCastException异常,所以通过instanceof进行判断,如果确定为Superman子类实力,则向下汉族啊你选哪个厚底哦啊用子类扩充方法。

提示:null的势力判断会返回false
在使用instanceof进行实例判断时,如果判断的对象内容为null,则返回的内容为false

范例:null判断

public class JavaDemo
{
public static void main(String args[])
{
Person per=null;
Superman man=null;
System.out.println(per instanceof Person);
System.out.println(man instanceof Superman);
}
}

false,false
由于null没有对应的堆内存空间,所以无法确定具体类型,这样的instanceof的判断就是false

8.6 Object类

在Java语言设计过程中,为了方便操作类型的同意,也为了方便每一个类定义一些公共操作,所以专门设计了一个公共的Object父类(此类唯一一个没有父类的泪,但确实所有类的父类),所有利用class关键字定义的类全部都默认继承自Object类,即以下两种类的定义效果是相同的
class Person{}  class Person extends Object{}
既然所有类全部都是Object类的子类,那么也就意味着所有类的对象都可以利用向上汉族啊你选哪个的特点为Object类对象实例化。

范例:对象向上转型为bject类型

class Person{}
public class JavaDemo
{
public static void main(String args[])
{
Object obj=new Person();
if(obj instanceof Person)
{
Peroson per=(Person)obj;
}
}
}

本程序给出了Object接收子类实例化对象的操作形式,由于所有的对象都可以通过Object接收,这样设计的优势在于:当某些操作方法需要接受任意类型是,那么最合适的参数类型就是bject。
提示:Object可以接收子类实例化对象的操作形式,由于所有的对象都可以通过Object接收,这样设计的优势在于:当某些操作方法需要接受任意类型是,男最合适的参数类型就是Object
提示:Object类除可以接受实例之外,也可以进行数组类型的接受

范例:利用Object接受数组

public class JavaDemo
{

publi static void main(String args[])
{
Object obj=new int[]{1,2,3};
if(obj instanceof int[])
{
int data[]=(int[])obj;
for(int temp:data)
{
System.out.println(temp);
}
}
}
}

8.6.1 获取对象信息

在Object类中提供有一个toString()方法,利用此方法可以实现对象信息的获取,而该方法是在直接进行对象输出时默认调用的。
范例:获取对象信息
class Perosn{
private String name;
private int age;
}

public Person(String name,int age)
{
this.name=name;
this.age=age;
}
@Override
public String toString()
{
return ""+this.name+this.age;
}

public class JavaDemo
{
Person per=new Person("lss",20);
System.out.println(per);
}

本程序在Person子类中根据自己的实际需求覆写了toString()方法,这样当进行对象打印室,就可以直接调用Person子类覆写过得toString()方法获取相关对象信息。

提示:Object.toString()方法的默认实现
实际上在本书之前奖结果,当一个对象被直接输出时,默认输出的是对象编码(或李继伟内存地址数据),这是因为Object类是所有类的父类,但是不同的子类可能有不同样式的对象信息获取,为此Object类考虑到公共设计就使用了一个对象编码的形式展示,可以观察toString()源代码
public String toString()
{
return getClass().getName()+"@"+Integer.toHexString(hashCode());
}
此时toString()方法利用相应的反射机制和对象编码获取了一个对象信息,所以当子类不覆写toString()方法是toString()方法回访时类名称@7吧d7fff等信息

8.6.2 对象比较

Object类中另外一个比较重要的方法就在于对象比较的处理上,所谓对象比较的主要功能是比较两个对象的内容是否完全相同。假设有两个Person对象,这两个对象由于分别使用了关键字new开辟堆内存空间,所以要想确认这两个对象是都一致,就需要将每一个成员属性依次进行比较,对于这样的一个比较,在Object类中提供有一个标准的方法。
        对象比较标准方法:public boolean equals(Object obj);
Object类中考虑到涉及的公共性,所以equals()方法中两个对象的比较是基于地址数值判断(对象==对象地址数值判断)实现的,如果子类有对象比较的需求,那么只需覆写此方法即可实现。

提示:String类也是覆写了equals()方法
String类是Object子类,所以在String类中的equals()方法(方法定义:public boolean equals(Object obj))实际上就是覆写了Object类中的euqals()方法

范例:覆写equals()方法
class Person extends Object
{
private String name;
private int age;
public Person(String name,int age)
{
this.name=name;
this.age=age;
}

public boolean equals(Object obj)
{
if(!(obj instanceof Person))
return false;
if(obj==null) return false;
if(this==obj) return true;
Person per=(Person) obj;
return this.name.equals(per.name)&&this.age==per.age;
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值