合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
# 继承 [TOC] ## 导学 在本章节内容中,我们将继续来学习面向对象三大特性之一的继承。之前的封装,实际上是针对一个类进行隐藏和访问控制操作,而即将要学习的继承,其实是在描述两个类之间的关系。 >[success]继承是软件实现复用的重要手段之一 首先,我们来想一想生活中的继承,比如儿子继承父亲的外貌与性格,徒弟继承师傅的手艺等等。这些都是继承,在两个主体之间有着传承的关系。 对于面向对象程序而言,它的编程思想同样来自于生活。我们在程序的开发中同样也会有和生活中一样的感受。比如,我们使用两个类来描述猫和狗: ![](https://img.kancloud.cn/fc/d8/fcd84c8b9b7686e35858830d5e4f9c28_863x467.png) 我们发现在描述过程中,它们有一些相同的属性和方法,也有一些不同的属性和方法。那么,如果我们接着描述其他的动物,是不是也会产生大量的重复的代码呢? 最好能有一种方法能把这些重复代码收集起来,然后我每次要使用的时候,就直接调用这个方法,进行重复利用就可以了。而,这种方法就是我们今天要学习的继承了。 比如我们可以将猫类和狗类中的共同属性和方法抽取出来,组成一个动物类: ![](https://img.kancloud.cn/1c/48/1c48d007d74b90da7bdc9eef0576fc85_397x447.png) 当猫狗类和动物类实现继承关系时,猫狗类就可以直接使用动物类中属性和方法了,而不必在写那些重复的代码了。即使我们再写如企鹅类,狮子类,乌龟类等,也可以去继承动物类,不必再写那些重复的代码。 >[info]将一些具有相似逻辑的类中的公共的**属性**和**方法**抽取出来,组成一个类,这个类我们称之为**父类**。**父类**和**子类**是一种一般和特殊的关系。例如水果和苹果,苹果是一种特殊的水果 所以,我们发现继承其实有着如下的特点: * 利于代码复用 * 缩短开发周期 那么,说了这么多,我们再来对继承做个总结吧: * 一种类与类之间的关系 * 使用已存在的类的定义作为基础建立新类 * 新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类的功能! ## 继承的实现 在Java中使用`extends`关键字实现类与类之间的继承关系 ![](https://img.kancloud.cn/a1/d0/a1d0ac793d68b1c659c4e54e3d858ea9_779x442.png) 在Java中,**继承只能是单继承**,即子类只能有一个父类,而父类可以被多个子类继承。就像现实生活中孩子只能有一个亲爹,而父亲可能有多个子女一样。 但是Java中存在**多层继承**,常用的比如每个子类只有一个直接父类,但是该父类依然存在其自身的父类。**注意**:虽然类继承可以实现代码的复用,但是如果继承的结构过多,也会造成代码段的阅读困难,一般不建议继承的结构超过3层。 >[warning]当实现继承时,子类可以获得父类**非私有**属性和方法的使用权 接下来就结合就结合具体的案例来看看吧 父类: ~~~java public class Animal { private String name;//名称 private int month;//月份 private String species;//品种 //动物共有方法吃东西 public void eat() { System.out.println(this.getName() + "在吃东西"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public Animal() { } public Animal(String name, int Month, String species) { super(); this.setName(name); this.setMonth(Month); this.setSpecies(species); } } ~~~ 子类-猫: ~~~java public class Cat extends Animal{ //猫自己的属性-体重 private double weight; //猫自己的方法-跑动 public void run() { //子类可以使用父类的非私有成员,这里可以使用父类的getName()方法,不能使用父类的name属性 System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快乐地奔跑"); } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } public Cat() { } public Cat(double weight) { this.setWeight(weight); } } ~~~ 子类-狗: ~~~java public class Dog extends Animal { //自有的属性 private String sex; //自有的睡觉方法 public void sleep() { System.out.println(this.getName() + "现在" + this.getMonth() + "个月大,它在睡觉~"); } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Dog() { } } ~~~ 测试: ~~~java public class Test { public static void main(String[] args) { Cat one = new Cat(); one.setName("花花"); one.setSpecies("中华田园猫"); one.eat(); one.run(); //sleep()方法不属于父类Animal,也不属于子类Cat中定义的方法 //one.sleep(); System.out.println("====================="); Dog two = new Dog(); two.setName("田田"); two.setMonth(1); two.eat(); two.sleep(); System.out.println("====================="); Animal three = new Animal(); //three.run();无法访问 //three.sleep(); } } ~~~ **总结:** * 一个子类只能有一个父类 * 子类可以访问父类非私有成员 * 子类自己自有成员其他兄弟类无法访问 * 父类不可以访问子类自有成员 ## 方法的重写 在上述案例中,子类都调用了父类的`eat()`方法。但是对于这样的方法,猫与狗都需要吃东西,但可能各自都有自己不同的想法。比如猫吃鱼,狗吃肉,具体的表现形式不同,那么各自类中的方法体描述也不应该相同。 对于这样的问题,我们可以使用方法的重写来完成这样的操作。对于方法的重写,指的是**在子类中重新描述父类中的方法**。 >[danger]方法的重写,要求返回值类型,方法名,参数类型、顺序、个数都要与父类继承的方法完全一致。 ~~~ public class Cat extends Animal{ //猫自己的属性-体重 private double weight; //猫自己的方法-跑动 public void run() { //子类可以使用父类的非私有成员,这里可以使用父类的getName()方法,不能使用父类的name属性 System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快乐地奔跑"); } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } public Cat() { } public Cat(double weight) { this.setWeight(weight); } @Override//注解,表示方法的重写,方法前面加上@Override 系统可以帮你检查方法的正确性 public void eat() { System.out.println(this.getName() + "爱吃鱼"); } } ~~~ ~~~ public class Dog extends Animal { //自有的属性 private String sex; //自有的睡觉方法 public void sleep() { System.out.println(this.getName() + "现在" + this.getMonth() + "个月大,它在睡觉~"); } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Dog() { } @Override public void eat() { System.out.println(this.getName() + "爱吃肉"); } } ~~~ ~~~ public class Test { public static void main(String[] args) { Cat one = new Cat(); one.setName("花花"); one.setSpecies("中华田园猫"); one.eat(); System.out.println("====================="); Dog two = new Dog(); two.setName("田田"); two.setMonth(1); two.eat(); } } ~~~ 输出结果: ~~~ 花花爱吃鱼 ===================== 田田爱吃肉 ~~~ **当子类重写父类方法之后,子类对象调用的是重写之后的方法。** ### 方法重载与方法重写 在学习中,我们有可能会把方法重载和方法重写混淆。所以我们来通过一个表格认识一下方法重载与方法重写的区别 >[info]方法的签名是由方法的方法名和形参列表(注意:包含方法的参数和类型)组成 | | 方法重载 |方法重写 | | :--- | :--- | :--- | | **类关系** | 发生在同一个类中 | 发生在有继承关系的子类与父类中 | | **方法签名** | 方法名相同,参数列表不同(参数顺序、个数、类型) | 方法名相同,参数列表相同(参数顺序、个数、类型)| | **返回值** | 返回值任意 | 返回值相同或返回值呈现父子关系 | | **访问修饰符** | 访问修饰符任意 | 访问修饰符的访问范围需要大于等于父类的访问范围 | | **参数名** | 与方法的参数名无关 | 与方法的参数名无关 | ### 关于属性 对于重写,在Java中只有针对方法的重写,没有针对属性的重写。但是,我们在子类中也是可以定义与父类同名的属性的,此时子类对象调用的是子类的属性。 ~~~ public class Animal { public int temp = 15; } public class Cat extends Animal { public int temp = 30; } public class Test { public static void main(String[] args) { Cat one = new Cat(); System.out.println(one.temp);//30 } } ~~~ ## 访问修饰符 访问控制修饰符能修饰的对象包括:**属性**、**方法**、**构造器** 访问修饰符可以用来限制成员被访问的范围,即用来控制被修饰的成员可以在哪里(包)被使用。 | | private | default | protected | public | | :---: | :---: | :---: | :---: | :---: | | 同一个类 | √ | √ | √ | √ | | 同一个包 | | √ | √ | √ | | 子类 | | | √ | √ | | 全局范围 | | | | √ | * private: 只允许在本类中进行访问(访问权限最小的) * public: 允许在任意位置访问(访问权限最大的) * protected: 允许在当前类、同包子类/非子类、跨包子类调用;跨包非子类不允许 * default: 允许在当前类、同包子类/非子类调用;跨包子类/非子类不允许调用 >[success]在实际的开发中,大部分的场景使用 private 定义属性,public 定义方法。 ### 访问修饰符对方法重写的影响 子类中重写父类方法时,子类方法的访问修饰符的访问范围需要大于等于父类方法的访问范围 ~~~ public class Animal { protected void eat() { } } public class Cat extends Animal { public void eat() { } /*void eat() { 有问题 }*/ } ~~~ ## super关键字 在上节内容的学习中,我们知道子类可以继承父类的方法,也可以使用自己重写的方法。那么该如何判定子类调用的方法是继承自父类,还是自己重写的呢。 ~~~ public class Animal { public String name; //动物共有方法吃东西 public void eat() { System.out.println(this.getName() + "在吃东西"); } } public class Cat extends Animal{ //猫自己的方法-跑动 public void run() { eat();//调用的是子类重写的方法 System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快乐地奔跑"); } @Override public void eat() { System.out.println(this.getName() + "爱吃鱼"); } } ~~~ 如果我想要使用父类的方法,则需要使用`super`关键字。`super`关键字代表着对父类对象的引用。 ~~~ public void run() { super.eat();//调用的是子类重写的方法 System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快乐地奔跑"); } ~~~ 当然也可以使用`super`访问父类中允许被子类访问到的任意成员。 ~~~ public void run() { super.eat();//调用的是子类重写的方法 super.name = "猫猫"; System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快乐地奔跑"); } ~~~ 1. 子类重写父类的方法,则父类的方法会被隐藏,隐藏的方法或者成员变量可以通过super关键字访问 2. 引入super关键字的原因是可以使用被隐藏的成员变量和方法,而且super只能在子类的方法中定义使用 ### 继承的初始化顺序 **父类的构造方法不允许被继承,不允许被重写。** 那么父类的构造器除了构建父类对象是否有其他作用呢? ~~~java public class Animal { private String name = "妮妮";//名称 protected int month = 2;//月份 String species = "英短";//品种 public int temp = 15; private static int st1 = 22; private static int st2 = 23; static { System.out.println("我是父类的静态代码块"); } { System.out.println("我是父类的构造代码块"); } //动物共有方法吃东西 public void eat() { System.out.println(this.getName() + "在吃东西"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public Animal() { System.out.println("我是父类的无参构造方法"); } public Animal(String name, int Month, String species) { super(); this.setName(name); this.setMonth(Month); this.setSpecies(species); } } ~~~ ~~~java public class Cat extends Animal{ //猫自己的属性-体重 private double weight; private static int st3 = 44; static { System.out.println("我是子类的静态代码块"); } { System.out.println("我是子类的构造代码块"); } //猫自己的方法-跑动 public void run() { System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快乐地奔跑"); } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } public Cat() { System.out.println("我是子类的无参构造方法"); } public Cat(double weight) { this.setWeight(weight); } @Override public void eat() { System.out.println(this.getName() + "爱吃鱼"); } } ~~~ ~~~java public class Test { public static void main(String[] args) { Cat one = new Cat(); System.out.println(one.temp); } } ~~~ 继承后的初始化顺序(实例化子类对象的时候):先加载父类静态成员,然后加载子类静态成员,然后父类对象构造(构造代码块-->属性-->方法),最后子类对象构造。 ### super() 在上节内容中,我们提到子类对象在实例化的时候会先去调用父类对象的构造器。那么子类构造器是否有权利去选择具体使用父类的哪个构造器呢? 在父类中添加一个有参构造器 ~~~ public Animal(String name, int month, String species) { System.out.println("我是父类的有参构造器"); } ~~~ 在子类中也使用三参构造器 ~~~ public Cat(String name, int Month, String species) { System.out.println("我是子类的有参构造器"); } ~~~ 测试: ~~~ public class Test { public static void main(String[] args) { Cat one = new Cat("花花",3,"英短"); System.out.println(one.temp); } } ~~~ 最终的运行结果表明,子类应用带参构造器实例化对象的时候,同样调用的是父类无参的构造器。 **这也就是在实际的开发过程中,我们都会保证一个类中有一个无参的构造器存在的原因。** 这也是Java继承的一个特点,如果在子类的构造器中,没有显式的调用父类的构造器,会**默认的调用父类的构造器**。 那么,问题又来啦,如何在子类的构造器中调用父类的构造器呢? ~~~ public Cat(String name, int month, String species) { //super();调用的是父类的无参构造方法 super(name, month,species);//调用的是父类对应参数的构造方法 System.out.println("我是子类的有参构造器"); } ~~~ >[warning]使用`super()`关键字,可以完成对父类构造器的调用。 **总结:** 1. 子类默认调用父类无参构造方法; 2. 可以通过`super()`调用父类允许访问的其他构造方法; 3. `super()`必须放在子类构造方法的有效方法的第一行,而且只能出现一次。 4. `this()`和`super()`在同一构造器中只可能出现一个。 ### super,this,super()和this() ||super | this | super() | this() | | --- | --- | --- | --- | ---| | 用处 | super可以用在子类的成员方法中 | this可以用在本类中的成员方法中 | super()可以用在子类的构造器中 | this()可以用在本类的构造器中 | | 目的 | 调用父类非私有(no private)的成员变量或方法 | 调用本类的成员变量或方法 | 调用父类的构造器 | 调用本类的构造器 | >[danger]super和this可以同时出现的,而super()和this()不能同时出现在一个构造器中 ### 子类调用父类的构造器情况 **子类构造器调用父类的构造器可能出现的情况有** 1. 子类构造器执行体的第一行显式的使用super调用父类的构造器,此时系统将根据super(params)去调用对应的构造器。 2. 子类构造器在执行体的第一行显式的使用this调用重载的构造器,系统将会根据this(params) 调用对应 的重载构造器,本类中的对应的构造器再去调用父类的构造器 3. 子类构造器中既没有super有没有this,那么子类在执行构造器语句的时候会去执行父类的无参构造器。 4. 无论如何子类都会调用一次父类的构造器 ## Object类 `Object`类是所有类的老祖宗,如果有一个类没有显式的说明继承自哪个类,那么该类就默认的继承Object类。 对于`Object`类,它有着如下的特点 1. `Obejct`类是所有类的父类 2. 一个类没有使用`extends`关键字明确标识继承关系,则默认继承Object类(包括数组) 3. Java中每个类都可以使用`Ojbect`类中定义的方法 接下来,我们就来结合Java API(JDK文档)来看看Java中对于`Object`类的介绍 文档地址:[http://www.matools.com/api/java8](http://www.matools.com/api/java8) ### equals()方法 在`Object`类中,我们首先要学习的就是`equals()`方法。 >[info]**当直接继承Object类中的`equals()`方法时**,它的作用是判断调用`equals()`方法的对象的内存空间引用与传入参数对象的内存空间引用是否一致(是否指向的是同一块内存地址)。 在Java中,`==`不但可以判断两个基本数据类型的数据是佛相等,也可以用来判断两个对象的内存地址是否一致。本章节中所讲的,equals()方法如果不对其进行重写,则作用和`==`没有什么太大的差异。 ~~~Java public class Test { public static void main(String[] args) { Animal one = new Animal("花花",2,"英短"); Animal two = new Animal("花花",2,"英短"); System.out.println(one == two); System.out.println(one.equals(two)); } } ~~~ 上述代码的运行结果都为`false`。这是因为`one`对象通过`new`关键字开辟了一块堆内存空间,`two`对象通过new关键字开辟了另一块内存空间。`one`对象和`two`对象各自指向的内存地址不一样,即使对象中的属性值一致,也被判定为`false`。 在String类中,Java开发人员重写了`equals()`方法。那我们来看看在String类中重写后的`equals()`方法的作用。 ~~~ public static void main(String[] args) { String str = "abc"; //String作为一个类,同样有其构造方法 String str1 = new String("abc"); String str2 = new String("abc"); System.out.println(str == str1); System.out.println(str == str2); System.out.println(str1 == str2); System.out.println("================"); System.out.println(str.equals(str1)); System.out.println(str.equals(str2)); System.out.println(str1.equals(str2)); } ~~~ 在这次的运行中,我们可以看到分割线下`equals()`方法的返回值都为`true`,这是因为String类中重写的`equals()`方法会用来比较保存的字符串内容。 ### 重写equals()方法 ~~~Java public class Test { public static void main(String[] args) { Animal one = new Animal("花花",2,"英短"); Animal two = new Animal("花花",2,"英短"); System.out.println(one == two); System.out.println(one.equals(two)); } } ~~~ 回到刚刚的代码,如果只想去比较`one`和`two`这两个对象属性值是否相同怎么办呢?这时候就需要我们去Animal类中重写`equals()`方法了。 ~~~ public boolean equals(Object obj) { // 1. 首先判断传入的对象是否为null if (obj == null) { // 如果是null则直接返回false return false; } else { // 2. 接着判断两个对象中的属性值是否一致 // 此时是引用数据类型的强制类型转换,形式也是类似于之前学习过的基本数据类型强制类型转换 Animal animal = (Animal) obj; if (this.getName().equals(animal.getName()) && this.getMonth() == animal.getMonth() && this.getSpecies().equals(animal.getSpecies())) { return true; } else { return false; } } } ~~~ 完成重写后,我们会发现再去运行测试类中的方法,结果已经发生了改变。 当然,上述代码还是有问题的,最大的问题在于强制类型转换的时候,如果传入的参数不能匹配`Animal`类,则会发生一个类型转换错误的异常。 ~~~ /** * 此方法就不是对equals()方法的重载了,而是对Animal类中已经存在的equals()方法的重载 * @param ani * @return */ public boolean equals(Animal ani) { // 1. 首先判断传入的对象是否为null if (ani == null) { // 如果是null则直接返回false return false; } else { // 2. 接着判断两个对象中的属性值是否一致 // 此时是引用数据类型的强制类型转换,形式也是类似于之前学习过的基本数据类型强制类型转换 if (this.getName().equals(ani.getName()) && this.getMonth() == ani.getMonth() && this.getSpecies().equals(ani.getSpecies())) { return true; } else { return false; } } } ~~~ 针对于,有可能发生的类型转换异常,我们可以强制限定传入的参数为`Animal`类型,避免异常的发生。所以,我们重载了一个参数为`Animal`类型的方法,在测试类中代码运行的时候,会自动根据参数类型定位到类型匹配的重载方法上。 **总计:** 1. 继承Object中的equals()方法时,比较的是两个引用是否指向同一个对象 2. 子类可以通过重写equals()方法的形式,改变比较的内容 >[danger]需要注意的是,需要针对传入的参数进行非空判断,避免空指针异常 ### toString()方法 toString()方法也是使用频率比较高的一个方法。 >[info]在没有重写toString()方法之前,对象调用toString()方法会返回一个类的字符串表现形式,这个表现形式就是一个类的类名 + @ + 对象在内存中位置表现的哈希值。 ~~~ public class Test { public static void main(String[] args) { Animal one = new Animal("花花",2,"英短"); Animal two = new Animal("花花",2,"英短"); System.out.println(one); System.out.println(one.toString()); String str = new String("Hello,world!"); System.out.println(str); System.out.println(str.toString()); } } ~~~ 输出结果 ~~~ com.dodoke.animal.model.Animal@15db9742 com.dodoke.animal.model.Animal@15db9742 Hello,world! Hello,world! ~~~ 由此可见,当直接打印对象的对象名时,会默认调用对象继承自`Object`类的`toString()`方法 ### 在类中重写toString()方法 可以通过eclipse完成重写toString()方法 ~~~ @Override public String toString() { return "Animal [name=" + name + ", month=" + month + ", species=" + species + "]"; } ~~~ 总结: * 直接输出对象名时,默认会直接调用类中的toString方法 * 继承Object中的`toString()`方法时,输出对象的字符串表现形式:类型信息+@+地址信息 * 子类可以通过重写`toString()`方法的形式,改变输出的内容及其表现形式 ## final关键字 通过继承,我们可以提高代码的复用性和灵活性。但是在有些时候,我们并不希望这个类被继承,这个方法被重写和这个变量的值被修改,那么这个时候`final`关键字就起了大作用了。 1. `final`修饰类,则该类不允许被继承 ~~~ public final class Animal {} final public class Animal {} ~~~ 我们常用的`String`类和`System`类等,都是使用final去修饰的。 2. `final`修饰方法,则该方法不允许被重写,但可以正常被子类继承使用 ~~~ public final void eat() {} ~~~ 3. `final`修饰变量 * `final`修饰局部变量,初始化(赋值)后不允许被修改 ~~~ public void eat() { int temp = 10; final int temp1 = 12; temp1 = 14;//不可修改 final int temp2; temp2 = 13; temp2 = 14;//不可修改 System.out.println(this.getName() + "在吃东西"); } ~~~ * `final`修饰属性变量,同样复制后不可以修改 ~~~ public class Animal { public final int temp = 15; public final int temp1; public final int temp2; public final int temp3; public Animal() { super(); temp1 = 20; } { temp2 = 20; } static { temp3 = 20;//不可以赋值 } } ~~~ 被final定义的成员属性,只有三种方式进行赋值: * 定义时直接赋值; * 在构造代码块中赋值; * 在构造方法中赋值。 ### final其他应用 之前我们使用final修饰变量的时候,实验的都是基本数据类型,如果使用final修饰引用数据类型会发生什么样的变化呢? ~~~ public void eat() { final Animal ani = new Animal(); ani = new Animal();//不允许重新赋值,也就是ani所代表的引用地址不允许被修订 ani.setMonth(3);//对象中的属性值是允许被修改的 ani.setMonth(2); System.out.println(this.getName() + "在吃东西"); } ~~~ **总结:** `final `当修饰的变量为引用类型时,对象不允许再被重新实例化(不允许再被new),但是此对象里的属性的值依然可以更改。 >[info]在实际的开发中, `final `用的最多的场景是结合 static 关键字定义类变量,即静态变量。定义的这个变量我们也称之为常量。 定义为` final `另一个意图就是将变量的值保护起来。 ~~~ public class Animal { public static final int TEMP = 200; public final static int TEMP1 = 200; } ~~~ `final`修饰的这个常量需要字母全部大写 >[danger]`final`不能修饰构造方法 ## 注解 在之前的的学习中,我们提到可以适应`@Override`来添加对于重写方法的约束,同样的在eclipse中采用快捷方式完成重写方法时也会出现`@Override`。这样一个事物,我们称之为注解。在本章节中,我们就来简单的学习使用注解。 注解是JDK1.5版本引入的一个特性,可以声明在包、类、属性、方法、局部变量、方法参数等的前面,用来对这些元素进行说明、注释。简单来说,注解就相当于一个标记,在程序中使用了注解就相当于给这个程序打上了某种标签。被打过标签的一些程序,以后Java编辑器、开发工具、以及其他的程序就会借由这个标签来了解你所写的程序。 按照运行机制分: - 源码注解:注解只在源码中存在,编译成.class文件就不存在了。如`@Override` - 编译时注解:注解在源码和.class文件中都存在。 - 运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解。如以后要学习到的框架的注解`@Autowired` 按照来源分: - 来自JDK的注解 - 来自第三方的注解 - 我们自己定义的注解 >[warning]还有特殊的注解:元注解:对注解进行注释的 ## 练习 一、选择 1. 在Java中,以下程序的输出结果是 ![](https://img.kancloud.cn/4b/1f/4b1f0952f409d76c31ebb846ffd5ac63_451x322.png) ~~~ A. Super-->print B. Test-->print C. Super-->print Test-->print D. 编译错误 ~~~ 2. 在Java中,以下关于方法重载和方法重写描述正确的是 ~~~ A. 方法重载和方法重写实现的功能相同 B. 方法重载出现在父子关系中,方法重写是在同一类中 C. 方法重载的返回类型必须一致,参数项必须不同 D. 方法重写需要出现在满足继承关系的子类中 ~~~ 3. 哪个选项中的方法插入到(1)处可以正确实现方法重写 ![](https://img.kancloud.cn/d0/8e/d08e965d8105e22259ae0849412f3674_448x494.png) ~~~ A. public static void bark(){} B. public final void display(){} C. public void eat(String food){} D. public boolean eat(String food){} ~~~ 4. 在下面程序的注释1处补充上下列()方法,会导致在编译过程中发生错误 ~~~ A. public float getNum() { return 4.0f; } B. private float getNum() {return 4.0f;} C. public void getNum(double d){} D. public double getNum(float d){ return 4.0f; } ~~~ 5. 如下Java源文件,编译并运行Child.java后,以下结果描述正确的是 ![](https://img.kancloud.cn/00/97/00979f37a9c1af3ed68fe517be275a5a_327x306.png) ~~~ A. 编译错误:没有找到构造器Child() B. 编译错误:没有找到构造器Parent1() C. 正确运行,没有输出值 D. 正确运行,输出结果为:parent2 ~~~ 6. 分析如下所示的Java代码,则选项中的说法正确的是 ![](https://img.kancloud.cn/91/40/9140badaafd48ea43c98496a877cacdd_447x345.png) ~~~ A. 第2行错误,Test类的构造函数中参数名称应与其父类构造函数中的参数名相同 B. 第3行错误,应使用super关键字调用父类的name属性,改为super.name="hello" C. 第4行错误,调用父类构造方法的语句必须放在子类构造方法中的第一行 D. 程序编译通过,无错误 ~~~ 7. 关于super的说法正确的是 ~~~ A. 是指当前子类的对象 B. 是指当前类的对象 C. 是指当前父类的对象 D. 可以用在main()方法中 ~~~ 8. 阅读下面JAVA代码片段,正确的选项是 ![](https://img.kancloud.cn/13/cb/13cb69ea018453c8d487f088a111feae_978x570.png) ~~~ A. 第1行编译错误,但能输出正确结果 B. 第2行编译错误,但能输出正确结果 C. 第3行编译错误,不能输出正确结果 D. 第4行编译错误,不能输出正确结果 ~~~ 9. 下列关于super和this的说法正确的是(多选) ~~~ A. this关键字通常指当前对象 B. super关键字则指父类对象 C. 在一个类中this只可以调用当前类中公有属性和方法 D. 在一个类中super可以调用父类中允许被访问的属性和方法 ~~~ 10. 下列关于Object类的叙述错误的是 ~~~ A. Object类是所有类的父类 B. 所有类都可以继承Object中允许被继承的方法 C. 一个类没有使用extends关键字明确标识继承关系,则默认继承Object类 D. 要想继承Object类,必须使用extends关键字标识继承关系,否则不会实现继承 ~~~ 11. 该段代码的运行结果为 ![](https://img.kancloud.cn/f6/75/f67561ddfbaa3fea7893bd8b4de96129_456x286.png) ~~~ A. true B. 相等 C. 不相等 D. false ~~~ 12. 在Java中,关于继承的说法错误的是 ~~~ A. 使用extends关键字实现一个类继承另一个类 B. 所有的Java类都直接或间接地继承了java.lang.Object类 C. 在子类的构造方法中,必须显式调用父类的构造方法 D. 在子类的构造方法中,可以通过super关键字调用父类的构造方法 ~~~ 13. 下列关于final的说法错误的是 ~~~ A. final修饰的变量值不允许被修改 B. final修饰的方法不可被重写 C. final可以修饰所有方法 D. final不可以修饰构造方法 ~~~ 二、编程 1. 编程练习:某公司要开发“XX车行管理系统”,请使用面向对象的思想,设计自定义类描述自行车、电动车和三轮车。 程序参考运行效果图如下: ![](https://img.kancloud.cn/d9/6b/d96b039a1d96f5293cfe9fe566b393de_991x149.png) 任务 任务分析; **第一步:分析自行车、电动车和三轮车的共性:** 1. 都是非机动车,具有非机动车的基本特征 2. 都有运行的方法 **第二步:根据共性,定义非机动车** 属性:品牌、颜色、轮子(默认2个)、座椅(默认   1个) 方法: 1. 编写无参构造方法、双参构造方法和四参构造方法,其中,在双参构造方法中,完成对品牌和颜色的赋值;在四参构造方法中,完成对所有属性的赋值 2. 编写运行的方法,描述内容为:这是一辆\*\*颜色的,\*\*牌的非机动车,有\*\*个轮子,有\*\*个座椅的非机动车。其中\*\*的数据由属性提供 **第三步:定义自行车、电动车和三轮车分别继承自行车类,要求:** * **自行车类:** 1. 在构造方法中调用父类多参构造,完成属性赋值 2. 重写运行方法,描述内容为:这是一辆\*\*颜色的,\*\*牌的自行车。其中\*\*的数据由属性提供 * **电动车:** 1.  增加“电池品牌”属性 2.  重写运行方法,描述内容为:这是一辆使用\*\*牌电池的电动车。其中\*\*的数据由属性提供 * **三轮车:** 1. 在无参构造中实现对轮子属性值进行修改 2. 重写运行方法,描述内容为:三轮车是一款有\*\*个轮子的非机动车。其中\*\*的数据由属性提供 2. 请使用面向对象的思想,设计自定义类Person继承Object类,重写toString方法实现对象信息输出。 运行效果如下图所示: ![](https://img.kancloud.cn/3a/a9/3aa9afb36fee7ffe1854f5b161936c45_521x91.png) 思路分析 * 创建一个 Person 类继承自 Object,其中类的结构要求为: 属性:name(姓名)、age(年龄)、sex(性别) 方法: * 创建带参(name、age、sex为参数)构造方法 * 重写 toString 方法,输出信息格式为:姓名:\*\* 年龄:\*\* 性别:\*\*(其中,\*\*为对象对应属性值) * 创建测试类,在测试方法中,实例化 Person对 象,并传入三个属性值。然后,分别通过直接打印Person对象以及利用重写的 toString 方法,打印输出2行对象信息。 ~~~ public class Person{ //私有属性:name(姓名)、age(年龄)、sex(性别) //带参构造方法(name、age、sex为参数) //通过封装实现对属性的get/set方法设定 //重写toString方法,表示形式为:姓名:+**+ 年龄:+**+ 性别:+** } ~~~ 3. 请使用面向对象的思想,实现杨梅和仙人蕉的信息描述。 程序参考运行效果图如下: ![](https://img.kancloud.cn/8b/ca/8bcaf2f6fe6a9db4b2f3b16af9846585_772x305.png) 思路分析: **1、根据杨梅和香蕉的共性,抽取父类水果(Fruits)** 私有属性:水果的形状(shape)和口感(taste) 方法: * 带参构造函数(参数为shape和taste) * 创建无参无返回值得方法eat(描述内容为:水果可供人们食用!) * 重写equals方法,比较两个对象是否相等(比较shape,taste) **2、子类Waxberry** 私有属性:颜色(color) 方法: * 调用父类的构造方法,完成属性赋值 * 创建不允许重写的face方法,描述为:杨梅:\*\*、\*\*,果味酸甜适中。 * 重写父类eat方法,描述为:杨梅酸甜适中,非常好吃! * 重写toString方法,输出的表现形式不同(输出shape,color,taste) * 要求Waxberry类不允许有子类 **3、子类:Banana** 私有属性:品种(variety) 方法: * 带参构造方法为所有属性赋值 * 创建无参无返回值的advantage方法,描述为:\*\*果形\*\*,果肉香甜,可供生食。 * 重载要求(2)中的advantage方法(带参数color),描述为:\*\*颜色为\*\* **4、测试,运行效果参照效果图:** * 实例化2个父类对象,并传入两组相同的参数值 * 调用父类eat方法 * 测试重写equals方法,判断两个对象是否相等 * 实例化子类Wacberry对象,并传入相关参数值 * 调用子类face方法和重写父类eat方法后的eat方法 * 测试重写toString方法,输出子类对象的信息 * 实例化Banana类对象,并传入相关参数值 * 调用子类的advantage和它的重载方法