继承的作用
当我们定义了一个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);}@Overridepublic 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;}@Overridepublic 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);//falseStudent s-new Student();
System.out.println(instanceof Student);//true
System.out.println(instanceof Person);//falsePerson 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指向的对象类型是否是PersonComputerif(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