Extend继承

继承的作用

当我们定义了一个Person类:

class Person{
    private Stirng name;
    private int age;
    public String getName(){....}
    public int getAge(){...}
    public void setName(String name){...}
    public void setAge(int age){...}
}

现在,假设还需要定义一个Student类:

class Student{
    private Stirng name;
    private int age;
    private int score;

    public String getName(){....}
    public int getAge(){...}
    public int getScore(){...}

    public void setName(String name){...}
    public void setAge(int age){...}
    public void setScore(int score){...}
}

        通过观察我们发现这两个类相似,其中Student类包含了Person类中已有的字段和方法,只是多了一个score字段和相应的set、get方法。那能不能不用在Student类中写重复的代码呢?这时候我们就需要用继承(Extends)来解决这个问题。

        继承是面向对象编程中非常强大的一种机制,首先它可以实现代码的复用,当Student类继承Person类时,Student类就获得了Person的所有功能,我们只需要为Student类添加一些其他我们想实现的功能。在Java中我们用关键字extends来实现继承:

class Person{
    private Stirng name;
    private int age;
    public String getName(){....}
    public int getAge(){...}
    public void setName(String name){...}
    public void setAge(int age){...}
}
class Student extends Person{
    private int score;
    
    public int getScore(){...}
    public void setScore(int score){...} 
}

注意:子类自动获得父类的所有字段,严谨定义与父类重名的字段

        在我们定义Person的时候,没有写extends。在java中,没有明确写extend的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类,如下图:

        java只允许一个类继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,他没有父类。

protected关键字

        继承有一个特点,就是子类无法继承父类的private字段或者private方法。例如,Student类无法访问Person类的name和age字段,这样,继承的作用就会被削弱。为了让子类可以访问父类的字段,我们需要把修饰符从private改为protected。用protected修饰的字段可以被父类访问

super关键字

        super关键字表示父类(超类),子类引用父类的字段时,可以用super.fieldName,例如:

public class Person {
	protected String name;
	private int age;

}
class Student extends Person{
	public String hello() {
	//子类允许访问父类protected修饰的成员变量和方法
	return "hello"+super.name;

	}
}

        实际上,这里使用super.name,或者this.name ,或者直接使用name的效果都是一样的。编译器会自动定位到父类的name字段。但是在某些时候,必须使用super:

public Main{
    public static void main(String[] args){
        Student s=new Student("Wei",18,100);
     }
}
class Person{
    protected String name;
    protected int age;
    
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
}
class Student extends Person {
    protected int score;
    public Student(String name,int age,int score){
        this.score=score;
    }
}

        运行此代码,会得到一个编译错误,大意是在Student的构造方法中无法调用Person的构造方法。

        因为在Java中,任何子类的构造方法,第一句语句必须是调用父类的构造方法。如果没有明确的调用父类的构造方法,编译器会帮我们自动加一句super(),所以,Student的构造方法实际应该是这样的:

public Main{
    public static void main(String[] args){
        Student s=new Student("Wei",18,100);
     }
}
class Person{
    protected String name;
    protected int age;
    
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
}
class Student extends Person {
    protected int score;
    public Student(String name,int age,int score){
        super();//自动调用无参构造方法
        this.score=score;
    }
}

        但是,在这个实例中,我们没有在父类中定义一个无参的构造方法,因此依然编译失败。解决方法是:手动调用Person类存在的某个构造方法,这样就可以正常编译了:

public Main{
    public static void main(String[] args){
        Student s=new Student("Wei",18,100);
     }
}
class Person{
    protected String name;
    protected int age;
    
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
}
class Student extends Person {
    protected int score;
    public Student(String name,int age,int score){
        super(name,age);//手动调用有参构造方法
        this.score=score;
    }
}

        由此,我们可以得出结论:如果父类没有默认的构造方法,子类必须显式的通过super关键字,让编译器定位到某个合适的构造方法。

        这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,而不是继承父类的。

简单应用

我们用一个有更完整功能的实例来加深一下对继承的理解:

//商品类(父类)
clas Product{
    //成员变量
    private double price;
    private int stock;
    //无参构造方法
    public Product(){
     }
    //有参构造方法
    public Product(double price,int stock){
        this.price=price;
        this.stock=stock;
    }
   //getter和Setter方法
	public double getPrice() {
		return price;
	}
	public void setPrice(double price){
        if(price<=0){
            this.price=0.01;
        }else{
            this.price = price;}
    }
    public int getStock() {
		return stock;
	}
	public void setStock(int stock) {
		this.stock = stock;
	}

}
//图书类(子类)
class Book extends Product{
    private String bookName;
	private String author;
    //构造方法
    public Book(String bookName,double price,String author,int 
                                                         stock)){
        this.bookName=bookName;
        this.author=author;
        super.setPrice(price);
        super.setStock(stock);
    }
    @Override
	public String toString() {
		return String.format("书名:《%s》,
                              作者:%s,价格:¥%f,
                              库存:%d",				
                              this.bookName,
                              this.author,super.getPrice(),
                              super.getStock());
	}
}

//手机类(子类)
class Phone extends Product{
	private int memory;
	private String model;
	public Phone(String model,int memory,double price,int stock) {                                                                                                                                                                                                                                                                                                                                 
    //方法一:通过父类的set和get方法,保存库存和价格
//  super.setPrice(price);
//  super.setStock(stock);

    //方法二:通过父类有参的构造方法,保存库存和价格
    super(price,stock){
    this.memory=memory;
    this.model=model;
    
}
    @Override
    public String toString() {
	    return String.format("型号:%s,价格:¥%f,内存:%dG,库存:%d"			            
                             this.model,super.getPrice(),
                             this.memory,super.getStock());
	}
}
public class Test {
	public static void main(String[] args) {
		Phone phone1=new Phone("Mate60",8888,128,67);
		Book book1=new Book("皮皮鲁和鲁西西",12.5,"李",80);
		System.out.println(book1);
		System.out.println(phone1);
	}

}

输出结果:

向上转型

        如果引用变量的类型是Student,它可以指向一个Student类型的实例:

Student s=new Student();

         如果引用变量的类型是Person,它可以指向一个Person类型的实例:

Person p=new Person();

        如果Student是从Person继承下来的,一个引用类型为Person的变量,它可以直接指向Student类型的实例:

Person p=new Student();

        因为Student继承自Person,因此,它拥有Person的全部功能。所以Person类型的变量,如果指向Student类型的实例,对他进行操作是没有问题的。这种把一个子类类型安全地变为父类类型的赋值,称为向上转型(upcasting)

        向上转型实际上是把一个子类安全地变为更抽象的父类类型,继承树的顺序是:Student->Person->Person->Object。所以可以把Student转换为Person类型,或者更高层次的Object。

Student s=new Student();
Person p=s;
Object o1=p;
Object o1=s;

向下转型

        和向上转型相反,如果把一个父类类型强制转换为子类类型,就是向下转型(downcasting)。例如:

Person p1=new Student();//向上转型
Person p2=new Person();
Student s1=(Student)p1;//ok
Student s2=(Student)p2;//runtime error! ClassCastException!

        运行时,Person类型实际上指向Student实例,p2指向Person实例,所以在进行向下转型时,p1转型为Student会成功,是因为p1本身指向Student实例p2转为Student会失败,是因为p2没有指向Student,而是指向Person,不能将父类变为子类,因为子类的方法可能比父类方法多,多的方法不能凭空变出来,因此向下转型会失败,会报ClassCastException异常。

        为了避免向下转型失败,Java提供了instanceof操作符,可以先判断这个实例是不是某种类型,再进行向下转型:

Person p=new Person();
System.out.println(instanceof Person);//true
System.out.println(instanceof Student);//false

Student s-new Student();
System.out.println(instanceof Student);//true
System.out.println(instanceof Person);//false

Person p=new Student();
if(p.instanceof Student){
    //只有判断成功才会向下转型
    Student s=(Student)p;//一定转型成功
}

综合应用

//父类
class Computer{
    private String cpu;//中央处理器
    private int ssd;//固态硬盘
    
    //无参构造方法
    public Compuetr(){
    }
    //有参构造方法
    public Compuetr(String cpu,int ssd){
        this.cpu=cpu;
        this.ssd=ssd;
    }
}
//子类:PC机
class PersonComputer extends Computer{
    private String displayCard;//显卡
    //构造方法
    public PersonComputer(String cpu,int ssd,String displayCard) {
        //手动调用父类的有参构造方法
        super(cpu,ssd);
        this.displayCard=displayCard;
    }
    public String getDisplayCard() {
		return displayCard;
	}
	public void setDisplayCard(String displayCard) {
		this.displayCard = displayCard;
	}
	
}
//子类:笔记本电脑
class NotebookComputer extends Computer{
	private String brand;//品牌
	public NotebookComputer(String cpu,int ssd,String brand) {
		//手动调用父类的有参构造方法
		super(cpu,ssd);
		this.brand=brand;
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	
}
public class Computer_test {
	public static void main(String[] args) {
		//对象数组
		//类型如果是父类,代表数组内可以保存任意一种子类的对象
        Computer[] array={
                          new Computer("Intel i8",127),
                          new PersonComputer("Intel i9",128,"8090Ti")
                          new NotebookComputer("AMD R9",512,"联想"),
			              new NotebookComputer("AMD R9",512,"小米"),
                          new PersonComputer("Intel i9",128,"4090T")
                          };
    for(int i=0;i<array.length;i++){
        //获取数组中的元素
        Computer comp=array[i];
        //向下转型
		//instanceof运算符:
        //判断当前引用comp指向的对象类型是否是PersonComputer
        if(comp instanceof PersonComputer){
            PersonComputer pc=(PersonComputer)cmp;
            System.out.println("台式机的显卡型号是:"
                                +pc.getDisplayCard());
        }else if(comp instanceof NotebookComputer){
            NotebookComputer nb=(NotebookComputer)comp;
            System.out.println("笔记本的品牌是:"+nb.getBrand());
        }
    }
}

输出结果:

台式机的显卡型号是:8090Ti
笔记本的品牌是:联想
笔记本的品牌是:小米
台式机的显卡型号是:4090Tipluse

  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值