JAVA 面向对象(继承,向上/下转型,子类调用父类的构造函数)
什么是继承
继承其实听名字大概就知道是什么意思了,我们想象一下,现在有一个动物类。他包括名称,年龄的属性,吃饭,睡觉的方法,当然好的大家能想到的肯定有猫和狗,我们现在有了新的要求,我们想要这类有一个新的方法,我们想要听他们的叫声。
可是很明显猫,狗的叫声不一样啊,我们此时又不想重新定义猫类或者狗类,因为这样做太麻烦了。这个时候继承的作用就来了,我们可以定义一个猫类,可以继承他的父类也就是动物类的属性方法,也可以再为子类定义的新方法。
我们把这种可以使用别的类的属性和方法的方式叫做继承。使用者是子类,被使用者是父类。
那么我们总不能直接拿着人家的东西用啊,最起码要喊一声爸爸,而喊爸爸的方式需要extends 关键字如下:
class Dog extends Animal{
void call() {
System.out.println("汪汪汪");
}
}
为了便于理解我把动物类也粘贴出来:
class Animal{
String name;
private int age;
public Animal() {
}
public Animal(String name, int age) {
// TODO Auto-generated constructor stub
this.name=name;
this.age=age;
}
private void eat() {
System.out.println(name+"吃饭了");
}
void sleep() {
System.out.println(name+"睡觉了");
}
}
我们的dog继承与动物类也就是说他有动物类的属性和方法。但是!我们不能继承原本的私有属性和方法。你怎么可以继承你爸爸的年龄呢?
这个时候有的小伙伴要问了“啊,博主,那我可以调用父类的构造函数吗,他应该是公有的!”答案自然是可以的。
在JAVA中子类调用父类的构造函数
我们要知道没有父亲怎么会有儿子,更别提你还用了人家的属性和方法,所以当我们去new一个子类对象的时候,我们首先是去调用它的父类的构造函数,去创建父对象,随后我们在去调用它的构造函数去创建它。但是这个创建父对象的工作不是由我们而是由程序自主完成的,而创建出来的父类对象的位置位于子类对象里边(内存中)。所以子类所占的内存比父类要大。
那么口说无凭我们写个代码看看:
class Anmail{
String name;
private int age;
public Animal() {
System.out.println("我是爸爸");
}
public Animal(String name, int age) {
this.name=name;
this.age=age;
}
private void eat() {
System.out.println(name+"吃饭了");
}
void sleep() {
System.out.println(name+"睡觉了");
}
}
class Dog extends Animal{
void call() {
System.out.println("汪汪汪");
}
}
public class oopjc {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name);
}
}
他的输出是这样的
大家可以看到当我创建子类对象的时候,他先调用了父类的构造方法。
注意:
1、我们在创建子类对象的时候调用了父类的对象,这个是必须的,如果我们不写父类的构造方法,那么编译器会帮我们加上一个默认的构造方法,所谓默认的构造方法,就是没有参数的构造方法,但是如果你自己写了构造方法,那么编译器就不会给你添加了,所以有时候当你new一个子类对象的时候,肯定调用了子类的构造方法,但是在子类构造方法中我们并没有显示的调用基类的构造方法,就是没写,如:super(); 并没有这样写,但是这样就会调用父类没有参数的构造方法,如果父类中没有没有参数的构造方法就会出错。
2、为了解决上述的事情,通常我们需要显示:super(参数列表),来调用父类有参数的构造函数。
代码例子:
class Animal{
String name;
private int age;
public Animal(String name, int age) {
this.name=name;
this.age=age;
}
}
class Dog extends Animal{
}
public class oopjc {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name);
}
}
这种情况下,程序是会报错的,因为你定义了父类的构造函数却没有定义没有参数的父类构造函数,这就导致了我们无法创建子类。
解决办法呢其实很简单:1、在父类加入无参构造函数2、删除父类的有参构造函数3、用关键字super
前两个我感觉应该不会看不懂,那么我们来看看第三个办法的实现方式:
public Dog() {
super("花花",1);
}
在Dog类中加入这个,我们就可以实现赋值了,当然这个初始值可以随意,他就来自与父类(其实就相当于给了父类对象赋值,父类对象再把值初始化给子类对象)。
向上转型
我们一直在说的是一个狗是动物的一种,所以我们把动物作为一个通用或者叫普遍类型,狗作为一个特定类型。
我们现在把这句话反着说 动物是狗。这句话的说法有些片面但是我们在局限性的看法中发现他的说法并不完全错误,因为狗确实有动物的所有特征。这种由特定类型转化为普遍类型的方式就是向上转型。从我们的编程角度看理解为通过子类实例化父类去使用。这种转换其实属于自动转换。
class Animal{
void call() {
System.out.println("叫");
}
void print() {
System.out.println("Animal");
}
}
class Dog extends Animal{
void eat() {
}
void print() {
System.out.println("Dog");
}
}
public class oopjc {
public static void main(String[] args) {
Animal animal = new Dog();
animal.print();
animal.eat();
}
}
我们从上边代码,我们其实会发现他出错了,原因在于你无法调用eat()这个方法,但是很明显print();方法输出的是Dog;
这就是向上转型的特点:
1.向上转型,可以调用父类的对象但不能调用子类的对象。
2.向上转型,如果遇到父类的方法被子类重写了,那么就去调用子类也就是被重写后的方法。
那么向上转型的作用是什么呢?
当我们需要多个由同一个父类创建的对象调用某个方法时,通过向上转换后,则可以确定参数的统一,方便程序设计。
向下转型
向下转型和向上刚好相反,当父对象需要用到子对象的某些功能的时候,我们就可以采取向下转型。但是这个其实并不长用,因为我们知道一个特别的人可以是人,但是不是人人都可以做特别的人。因此 通过父类对象(大范围)实例化子类对象(小范围),这种属于强制转换。
Animal animal = new Animal();
Dog dog = (Dog)animal;
//dog.print();
dog.eat();
强制转换的方式就是这样。
特点:
1、向下转型可以调用子类的特定功能。
这个我的编译器一直报错,因为才学的时间比较短留个坑…我个人感觉这个向下转型还是不用为好。
一些小问题
1.如果子类和父类的方法名相同调用那个?
这里我们采用的是就近原则,子类对象调子类的,父类对象调父类的,大家可以自行尝试。其实这个地方牵扯到函数的重写与重载也就是多态,这个多态本次我们先不多做了解。
注意继承有什么特征呢?
1.喊爸爸必须要通过extends
关键字
2.java只支持单继承(只能有一个爸爸)
3.但是一个类可以有多个子类(爸爸的快乐)
4.继承可以传递(爸爸也有爸爸啊,爸爸的爸爸也是有一部分东西可以传给自己的嘛)
5、 不能继承父类的私有成员
6、 继承多用于功能的修改,子类可以拥有父类的功能的同时,进行功能拓展