继承的作用
当我们定义了一个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