面向对象继承性
一个良好的程序设计结构不仅便于维护,同时还可以提高程序代码的可重用性。例如,从下面定义Person类与Student类就可以发现无重用性代码设计的缺陷
package jicheng;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package jicheng;
public class Student {
private String name;
private int age;
private String school;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
通过以上两段代码的比较,相信读者可以清楚的发现,如果按照之前所学的概念进行开发的话,程序中就会出现重复代码。通过分析发现,学生本来就属于人,但是学生所表示的范围要比人表示的范围更小,也更加的具体。如果想要解决代码问题,就只能依靠继承来完成。
类继承定义
在Java中,如果要实现继承的关系,可以使用以下的语法完成
class 子类 extends 父类{}
在继承结构中,很多情况下会把子类称为派生类,把父类称为超类
范例:继承基本实现
package jicheng;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package jicheng;
class Student extends Person{//Student是子类
}
public class JavaDemo {
public static void main(String[] args) {
Student student=new Student();
student.setName("李无双");
student.setAge(18);
System.out.println("姓名:"+student.getName()+",年龄:"+student.getAge());
}
}
程序执行结果:
姓名:李无双,年龄:18
本程序在定义Student类时并没有定义任何方法,只是让其继承Person父类,而通过执行可以发现,子类可以继续重用父类中定义的属性与方法
继承实现的主要目的是子类可以重用父类中的结构,同时可以根据子类功能的需要进行结构扩充
范例:在子类中扩充父类的功能
package jicheng;
class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Student extends Person{//Student是子类
private String school;
public String getSchool() {//扩充的方法
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
public class JavaDemo {
public static void main(String[] args) {
Student student=new Student();
student.setName("李无双");
student.setAge(18);
student.setSchool("清华大学");
System.out.println("姓名:"+student.getName()+",年龄:"+student.getAge()+",学校:"+student.getSchool());
}
}
程序执行结果:
姓名:李无双,年龄:18,学校:清华大学
子类对象实例化流程
在继承结构中,子类需要重用父类中的结构,所以在进行子类对象实例化之前往往都会默认调用父类中的无参构造方法,为父类对象实例化,而后再进行子类构造调用,为子类对象实例化
package jicheng;
class Person {
public Person(){
System.out.println("[Person父类]调用Person父类构造实例化对象public Person()");
}
}
class Student extends Person{//Student是子类
public Student(){//子类无参构造
System.out.println("[Student子类]调用Person父类构造实例化对象public Student()");
}
}
public class JavaDemo {
public static void main(String[] args) {
Student student=new Student();//实例化子类对象
}
}
程序执行结果:
[Person父类]调用Person父类构造实例化对象public Person()
[Student子类]调用Student子类构造实例化对象public Student()
本程序在实例化Student子类对象时只调用了子类构造,而通过执行结果可以发现,父类构造器会被默认调用,执行完毕后才调用了子类构造,所以可以得出结论:子类对象实例化前一定会实例化父类对象。实际上这个时候就相当于子类的构造方法里面隐含了一个super()的形式
范例:观察子类构造
class Student extends Person{//Student是子类
public Student(){//子类无参构造
super();//明确调用父类构造,不编写时会默认找到父类无参构造
System.out.println("[Student子类]调用Student子类构造实例化对象public Student()");
}
}
子类中的super()的作用表示在子类中明确调用父类的无参构造,如果不写也默认会调用父类构造,对于super()构造调用的语句只能够在子类的构造方法中定义,并且必须放在子类构造方法的首行。
如果父类没有提供无参构造方法时,就可以通过"super(参数,…)"的形式调用指定参数的构造方法
范例:明确调用父类指定构造方法
package jicheng;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person{//Student是子类
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 student=new Student("里无双",18,"清华大学");
}
}
本程序Person父类不再明确提供无参构造方法,这样在子类构造方法中就必须通过super()明确指明要调用的父类构造
例:
package jicheng;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.printf(this.name+"---------"+this.age);
}
}
class Student extends Person{//Student是子类
private String school;
public Student(String name,int age,String school){//子类无参构造
super(name, age);//必须明确调用父类有参构造
this.school=school;
System.out.println("---------"+this.school);
}
}
public class JavaDemo {
public static void main(String[] args) {
Student student=new Student("李无双",18,"清华大学");
}
}
程序执行结果:
李无双---------18---------清华大学
继承是类重用的一种实现手段,而在Java中针对类继承的合理性设置了相关限制
限制1:一个子类只能继承一个父类
限制2:在一个子类继承的时候,实际上会继承父类的所有操作,但是需要注意的是,对于所有的非私有操作属于显式继承(可以直接利用对象操作),而所有的私有操作属于隐式继承(间接完成)
范例:不允许直接访问非私有操作
package jicheng;
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Student extends Person{//Student是子类
private String school;
public Student(String name){//子类无参构造
setName(name);
}
public String getInfo(){
//【ERROR】System.out.println(name);因为父类使用private声明,无法访问
return "姓名:"+getName();//间接访问
}
}
public class JavaDemo {
public static void main(String[] args) {
Student student=new Student("李无双");
System.out.println(student.getInfo());
}
}
本程序中Person父类定义的name属性虽然可以被子类使用,但是由于存在private定义,所以在子类中是无法直接进行私有属性访问的,只能通过getter()方法间接访问,所以该属性属于隐式继承。
覆写
在类继承结构中,子类可以继承父类中的全部方法,当父类某些方法无法满足子类设计需求时,就可以针对已有的方法进行扩充,那么此时在子类中定义与父类中方法名称,返回值类型,参数类型及个数完全相同的方法的时候,称为方法覆写
package jicheng;
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 databaseChannel=new DatabaseChannel();
databaseChannel.connect();
}
}
程序执行结果:
[DatabaseChannel子类]进行资源的连接
当通过子类实例化对象调用方法时所调用的是被覆写过的方法,如果此时需要调用父类已被覆写过的方法,在子类中可以使用“super.方法()”的形式调用
范例:子类调用父类已被覆写过的方法
package jicheng;
class Channel {
public void connect(){
System.out.println("[Channel父类]进行资源的连接");
}
}
class DatabaseChannel extends Channel{
public void connect(){
super.connect();//子类调用父类中被覆写过的方法,如果此时没有使用”super.方法()“的形式定义
System.out.println("[DatabaseChannel子类]进行资源的连接");
}
}
public class JavaDemo {
public static void main(String[] args) {
DatabaseChannel databaseChannel=new DatabaseChannel();//实例化子类对象
databaseChannel.connect();
}
}
程序执行结果:
[Channel父类]进行资源的连接
[DatabaseChannel子类]进行资源的连接
本程序子类覆写了connect()方法,这样在子类中只能通过super.connect()调用父类中已经被覆写过的方法
方法覆写限制
子类利用方法覆写可以扩充父类方法的功能,但是在进行方法覆写时有一个核心的问题:被子类覆写的方法不能拥有比父类更严格的访问控制权限,目前已接触到的3种访问控制权限大小关系为private<default(默认)<public
如果此时父类中的方法是default权限,那么子类覆写的时候只能是default或public权限;而如果父类的方法是public,那么子类中方法的访问权限只能是public
注意:父类方法定义private时,子类无法覆写此方法
按照方法覆写的限制要求,子类方法设置的权限需要大于等于父类的权限,但是如果父类中的方法使用的是private,则子类无法进行覆写该方法,这个时候即便子类定义的方法符合覆写要求,对于子类而言也只是定义了一个新的方法而已。
范例:观察private权限下的方法覆写
package jicheng;
class Channel {
private void connect(){
System.out.println("[Channel父类]进行资源的连接");
}
public void handle(){
//如果子类成功覆写了此方法,那么通过子类实例化对象调用时执行的一定是子类方法
this.connect();//调用connect()
}
}
class DatabaseChannel extends Channel{
public void connect(){
System.out.println("[DatabaseChannel子类]进行资源的连接");
}
}
public class JavaDemo {
public static void main(String[] args) {
DatabaseChannel databaseChannel=new DatabaseChannel();//实例化子类对象
databaseChannel.handle();//父类提供的方法
}
}
程序执行结果:
[Channel父类]进行资源的连接
本程序如果从覆写的要求来讲,子类的结构是属于覆写,但是由于父类中的connect()方法使用了private定义,所以此方法将无法进行覆写。当子类实例化对象调用handle()方法时,发现所调用的并非是覆写过的方法。所以private权限声明的方法无法被子类覆写
提示:方法重载与覆写的区别
NO. | 区别 | 重载 | 覆写 |
---|---|---|---|
1 | 英文单词 | Overloading | Overriding |
2 | 定义 | 方法名称相同,参数的类型及个数不同 | 方法名称,参数的类型及个数,返回值类型完全相同 |
3 | 权限 | 没有权限要求 | 被子类所覆写的方法不能拥有比父类更严格的访问控制权限 |
4 | 范围 | 发生在一个类中 | 发生在继承关系中 |
方法重载时可以改变返回值类型。构造方法Constructor不能被继承,因此不能被覆写,但可以被重载
属性覆盖
子类除了可以对父类中的方法进行覆写外,也可以对非private定义的父类属性进行覆盖,此时只需定义与父类中成员属性相一致的名称即可。
package jicheng;
class Channel {
String info="www.mldn.cn";
}
class DatabaseChannel extends Channel{
int info=11;
public void fun(){
System.out.println(super.info);
System.out.println(this.info);
}
}
public class JavaDemo {
public static void main(String[] args) {
DatabaseChannel databaseChannel=new DatabaseChannel();//实例化子类对象
databaseChannel.fun();
}
}
程序执行结果:
www.mldn.cn
11
本程序在子类中定义了一个与父类名称相同,但是类型不同的成员属性info,所以此时就发生了属性覆盖。
final关键字
final class DatabaseChannel//这个类不能有子类
范例:使用final定义的方法不能被子类所覆写
package jicheng;
class Channel {
String info="www.baidu.com";
public final String fun(){
return "https://"+info;
}
}
final class DatabaseChannel extends Channel{
int info=11;
public void fun(){
System.out.println(super.info);
System.out.println(this.info);
}
}
public class JavaDemo {
public static void main(String[] args) {
DatabaseChannel databaseChannel=new DatabaseChannel();//实例化子类对象
//String fun = databaseChannel.fun();
//System.out.println(fun);
}
}
程序执行结果:
java: jicheng.DatabaseChannel中的fun()无法覆盖jicheng.Channel中的fun()
被覆盖的方法为final
范例:使用final定义常量
class Channel{
private final int ON=1;
}
范例:定义全局常量
public static final int ON=1;
package jicheng;
class Channel {
static String info="www.baidu.com";
public final String fun(){
return "https://"+info;
}
}
final class DatabaseChannel extends Channel{
public static final int INFO=11;
public void fun1(){
System.out.println(super.info);
System.out.println(this.info);
}
}
public class JavaDemo {
public static void main(String[] args) {
DatabaseChannel databaseChannel=new DatabaseChannel();//实例化子类对象
databaseChannel.fun1();
System.out.printf("https://");
String info = Channel.info;
System.out.println(info);
}
}
程序执行结果:
www.baidu.com
www.baidu.com
https://www.baidu.com
Annotation注解
准确覆写
package jicheng;
class Channel {
public void connect(){
System.out.println("【父类Channel】建立连接通道");
}
}
final class DatabaseChannel extends Channel{
@Override
public void connect() {
System.out.println("【子类DatabaseChannel】建立连接通道");
}
}
public class JavaDemo {
public static void main(String[] args) {
new DatabaseChannel().connect();//实例化子类对象并调用方法
}
}
过期声明
package jicheng;
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注解,项目开发者在编写新版本程序代码时就可以清楚地知道此为过期操作,并且可以根据注解的描述更换使用的方法。
面向对象多态性
展现形式1:方法的多态性
方法的重载:同一个方法可以根据传入的参数的类型或个数的不同实现不同功能
方法的覆写:同一个方法可能根据实现子类的不同有不同的实现
展示形式2:对象的多态性
对象向上转型:父类 父类实例=子类实例,自动完成转换
对象向下转型:子类 子类实例=(子类)父类实例,强制完成转换
对象向上转型
package jicheng;
class Message{
public void print(){
System.out.println("www.mldn.cn");
}
}
class DatabaseMessage extends Message{
public void print(){
System.out.println("数据库连接信息");
}
}
class NetMessage extends Message{
@Override
public void print() {
System.out.println("网络信息");
}
}
public class JavaDemo {
public static void main(String[] args) {
Message msgA=new DatabaseMessage();//向上转型
msgA.print();
Message message=new NetMessage();//向上转型
message.print();
}
}
程序执行结果:
数据库连接信息
网络信息
本程序在Message两个子类中分别覆写了print()方法,随后用对象自动向上转型的原则通过子类为Message父类对象实例化,由于print()方法已经被子类所覆写,所以最终调用的方法就是被实例化子类所覆写过的方法
对象向上转型的最大特点在于可以通过父类对象自动接收子类实例,而在实际的项目开发中,就可以利用这一原则实现方法接收或返回参数类型的统一。
package jicheng;
class Message{
public void print(){
System.out.println("www.mldn.cn");
}
}
class DatabaseMessage extends Message{
public void print(){
System.out.println("数据库连接信息");
}
}
class NetMessage extends Message{
@Override
public void print() {
System.out.println("网络信息");
}
}
class Channel{
public static void send(Message message){
message.print();
}
}
public class JavaDemo {
public static void main(String[] args) {
Channel.send(new DatabaseMessage());//向上转型
Channel.send(new NetMessage());
}
}
程序执行结果:
数据库连接信息
网络信息
本程序定义的Channel.send()方法,接收的参数类型为Message,这样就意味着所有的Message及其子类对象都可以接收,相当于统一了方法的参数类型
对象向下转型
package jicheng;
class Person{
public void run(){
System.out.println("奔跑");
}
}
class Superman extends Person{
@Override
public void run() {
System.out.println("jump");
}
public void fly(){
System.out.println("超音速飞行。。。");
}
public void fire(){
System.out.println("喷出三味真火。。。");
}
}
public class JavaDemo1 {
public static void main(String[] args) {
Person per=new Superman();//超人是一个人,向上转型
per.run();
System.out.println("---------------------");
Superman spm= (Superman) per;//强制转为子类实例
spm.fly();
spm.fire();
}
}
执行结果:
jump
---------------------
超音速飞行。。。
喷出三味真火。。。
本程序中Superman子类利用对象向上转型实例化了Person对象,此时Person类只能够调用本类或其父类定义的方法,如果此时需要调用子类中扩充的方法时,就必须强制性地将其转换为指定的子类类型
注意:必须先发生向上转型,之后才可以进行向下转型
在对象向下转型中,父类实例是不可能强制转换为任意子类实例,必须先通过子类实例化,利用向上转型让父类对象与具体子类实例之间发生联系后才可以向下转型,否则将出现ClassCastException异常
范例:错误的向下转型
public class JavaDemo1 {
public static void main(String[] args) {
Person per=new Person();//超人是一个人,向上转型
per.run();
System.out.println("---------------------");
Superman spm= (Superman) per;//强制转为子类实例
spm.fly();
spm.fire();
}
}
程序执行结果:
奔跑
---------------------
Exception in thread "main" java.lang.ClassCastException: jicheng.Person cannot be cast to jicheng.Superman
at jicheng.JavaDemo1.main(JavaDemo1.java:27)
本程序实例化Person类对象时并没有与Superman子类产生联系,所以无法进行强制转换
instanceof关键字
public class JavaDemo1 {
public static void main(String[] args) {
Person perA=new Person();//超人是一个人,向上转型
System.out.println(perA instanceof Person);
System.out.println(perA instanceof Superman);
System.out.println("***************************");
Person perB=new Superman();
System.out.println(perB instanceof Person);
System.out.println(perB instanceof Superman);
}
}
范例:安全的转型操作
package jicheng;
class Person{
public void run(){
System.out.println("奔跑");
}
}
class Superman extends Person{
@Override
public void run() {
System.out.println("jump");
}
public void fly(){
System.out.println("超音速飞行。。。");
}
public void fire(){
System.out.println("喷出三味真火。。。");
}
}
public class JavaDemo1 {
public static void main(String[] args) {
Person perA=new Superman();//超人是一个人,向上转型
perA.run();
if(perA instanceof Superman){
Superman superman = (Superman) perA;
superman.fly();
superman.fire();
}else{
System.out.println("---");
}
}
}
执行结果:
jump
超音速飞行。。。
喷出三味真火。。。
范例:null判断
public class JavaDemo1 {
public static void main(String[] args) {
Person per=null;
Superman man=null;
System.out.println(per instanceof Person);
System.out.println(man instanceof Superman);
}
}
由于null没有对应的堆内存空间,所以无法确定出具体类型,这样判断结果就是false
Object类
package jicheng;
class Person{
public void run(){
System.out.println("奔跑");
}
}
class Superman extends Person{
@Override
public void run() {
System.out.println("jump");
}
public void fly(){
System.out.println("超音速飞行。。。");
}
public void fire(){
System.out.println("喷出三味真火。。。");
}
}
public class JavaDemo1 {
public static void main(String[] args) {
Object obj=new Person();//向上转型
if(obj instanceof Person){
Person person= (Person) obj;//向下转型
person.run();
}
}
}
本程序给出了Object接收子类实例化对象的操作形式,由于所有的对象都可以通过Object接收,这样设计的优势在于:当某些操作方法需要接收任意类型时,那么最合适的参数类型是Object
范例:利用Object接收数组
public class JavaDemo1 {
public 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.printf(temp+" ");
}
}
}
}
//执行结果:1 2 3
获取对象信息
package jicheng;
class Person{
private String name;
private int age;
public Person(String name,int age){
this.age=age;
this.name=name;
}
@Override
public String toString() {
return "姓名:"+this.name+",年龄:"+this.age;
}
}
public class JavaDemo1 {
public static void main(String[] args) {
Person person=new Person("lisi",20);
System.out.println(person);
}
}
//姓名:lisi,年龄:20
本程序在Person子类中根据自已的实际需求覆写了toString()方法,这样当进行对象打印时,就可以直接调用Person子类覆写过的toString()方法获取相关对象信息。