十三、 封装、继承和多态


第1关:什么是封装,如何使用封装


任务描述
本关任务:构造一个类,把对象的属性封装起来,同时提供一些可以被外界访问属性的方法。

相关知识
为了完成本关任务,你需要掌握:1.什么是封装;2.封装的意义;3.实现Java封装的步骤。

什么是封装
封装:就是隐藏对象的属性和实现细节,仅对外提供公共访问方式。
封装时的权限控制符区别如下:
在这里插入图片描述

封装的意义
对于封装而言,一个对象它所封装的是自己的属性和方法,所以它是不需要依赖其他对象就可以完成自己的操作。使用封装有四大好处:

良好的封装能够减少耦合。
类内部的结构可以自由修改。
可以对成员进行更精确的控制。
隐藏信息,实现细节。
封装把一个对象的属性私有化,同时提供一些可以被外界访问属性的方法,如果不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
#####实现Java封装的步骤

修改属性的可见性来限制对属性的访问(一般限制为private),例如:

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

这段代码中,将 nameage 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。

对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:

/*
*封装演示
*/
public class Person {
    /*
     * 对属性的封装 一个人的姓名、性别和年龄都是这个人的私有属性
     */
    private String name;
    private String sex;
    private int age;
    /*
     * setter()、getter()是该对象对外开放的接口
     */
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。

封装可以使我们容易地修改类的内部实现,而无需修改使用了该类的客户代码,就可以对成员变量进行更精确的控制。

public void setAge(int age) {
    if (age > 120) {
        System.out.println("ERROR:error age input...."); // 提示错误信息
    } else {
        this.age = age;
    }
}
public String getSexName() {
    if ("0".equals(sex)) {
        sexName = "女";
    } else if ("1".equals(sex)) {
        sexName = "男";
    } else {
        sexName = "人妖";
    }
    return sexName;
}

编程要求
根据提示,在右侧编辑器Begin-End处补充代码:

声明一个Person类,私有化属性nameage,并将字段封装起来;

Person类中定义一个talk()方法,打印姓名和年龄信息;

main方法中声明并实例化一Person对象p,给p中的属性赋值,调用talk()方法打印

 我是:张三,今年:18岁。

测试说明
测试输入:
预期输出:

我是:张三,今年:18岁

代码实现

package case1;
public class TestPersonDemo {
	public static void main(String[] args) {
		/********* begin *********/
		// 声明并实例化一Person对象p
      Person p=new Person();
		// 给p中的属性赋值
             String name=p.getName();
			 name="张三";
			 p.setName(name);
			 int age=p.getAge();
			 age=18;
			 p.setAge(age);
		// 调用Person类中的talk()方法
             p.talk();
		/********* end *********/

	}
}
// 在这里定义Person类
class Person {
	/********* begin *********/
         private String name;
		 private int age;
		 public String getName(){
			 return name;
		 }
		 public void setName(String name){
			 this.name=name;
		 }
		 public int getAge(){
			 return age;
		 }
		 public void setAge(int age){
			this.age=age;
		 }
		 public void talk(){
			 System.out.print("我是:"+name+",今年:"+age+"岁");
		 }
	/********* end *********/
}

第2关:什么是继承,怎样使用继承


任务描述
本关任务:掌握继承的基本概念以及怎么使用继承。

相关知识
为了完成本关任务,你需要掌握:1.继承的基本概念;2.继承的特性;3.子类对象的实例化过程。

继承的基本概念
所谓继承:是指可以让某个类型的对象获得另一个类型的对象的属性的方法。
在这里插入图片描述

兔子和羊属于食草动物类,狮子和豹属于食肉动物类。
食草动物和食肉动物又是属于动物类。
所以继承需要符合的关系是:is-a,父类更通用,子类更具体。
虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。

在讲解继承的基本概念之前,读者可以先想一想这样一个问题:现在假设有一个Person类,里面有nameage两个属性,而另外一个Student类,需要有nameageschool三个属性,如图所示,从这里可以发现Person中已经存在有name和age两个属性,所以不希望在Student类中再重新声明这两个属性,这个时候就需要考虑是不是可以将Person类中的内容继续保留到Student类中,也就是引出了接下来所要介绍的类的继承概念。
在这里插入图片描述

在这里希望Student类能够将 Person类的内容继承下来后继续使用:

在这里插入图片描述

Java类的继承,可用下面的语法来表示:

class 父类 // 定义父类
{
    ...
}
class 子类 extends 父类 // 用extends关键字实现类的继承
{
    ...
}

范例:

public class TestPersonStudentDemo {
    public static void main(String[] args) {
        Student s = new Student();
        // 访问Person类中的name属性
        s.name = "张三";
        // 访问Person类中的age属性
        s.age = 18;
        // 访问Student类中的school属性
        s.school = "哈佛大学";
        System.out.println("姓名:" + s.name + ",年龄:" + s.age + ",学校:" + s.school);
    }
}
class Person {
    String name;
    int age;
}
class Student extends Person {
    String school;
}

输出结果:

姓名:张三,年龄:18,学校:哈佛大学

由上面的程序可以发现,在Student类中虽然并未定义name与age属性,但在程序外部却依然可以调用name或age,这是因为Student类直接继承自Person类,也就是说Student类直接继承了Person类中的属性,所以Student类的对象才可以访问到父类中的成员。
在这里插入图片描述

继承的特性
子类拥有父类非private的属性和方法;

子类可以拥有自己的属性和方法,即子类可以对父类进行扩展;

子类可以用自己的方式实现父类的方法;

在Java中只允许单继承,而不允许多重继承,也就是说一个子类只能有一个父类,但是Java中却允许多层继承,多层继承就是,例如类C继承类B,类B继承类A,所以按照关系就是类A是类B的父类,类B是类C的父类,这是Java继承区别于C++继承的一个特性;

提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系)。

多重继承:
在这里插入图片描述

class A{
    ...
}
class B{
    ...
}
class C extends A,B{
    ...
}

由上面可以发现类C同时继承了类A与类B,也就是说类C同时继承了两个父类,这在Java中是不允许的。
多层继承:
在这里插入图片描述

class A{
    ...
}
class B extends A{
    ...
}
class C extends B{
    ...
}

由上面可以发现类B继承了类A,而类C又继承了类B,也就是说类B是类A的子类,而类C则是类A的孙子类。

子类对象的实例化过程
既然子类可以继承直接父类中的方法与属性,那父类中的构造方法呢?请看下面的范例:

public class TestPersonStudentDemo1 {
    public static void main(String[] args) {
        Student s = new Student();
    }
}
class Person {
    String name;
    int age;
    // 父类的构造方法
    public Person() {
        System.out.println("1.public Person(){}");
    }
}
class Student extends Person {
    String school;
    // 子类的构造方法
    public Student() {
        System.out.println("2.public Student(){}");
    }
}

输出结果:

1.public Person(){}
2.public Student(){}

从程序输出结果中可以发现,虽然程序第3行实例化的是子类的对象,但是程序却先去调用父类中的无参构造方法,之后再调用了子类本身的构造方法。所以由此可以得出结论,子类对象在实例化时会默认先去调用父类中的无参构造方法,之后再调用本类中的相应构造方法。
实际上在本范例中,在子类构造方法的第一行默认隐含了一个super()语句,上面的程序如果改写成下面的形式,也是可以的:

class Student extends Person{
    String school ;
    // 子类的构造方法
    public Student(){
        super() ; //实际上在程序的这里隐含了这样一条语句
        System.out.println("2.public Student(){}");
    }
}

继承条件下构造方法调用规则如下:

如果子类的构造方法中没有通过super显示调用父类的有参构造方法,也没有通过this显示调用自身的其他构造方法,则系统会默认先调用父类的无参构造方法。在这种情况下写不写super()语句效果都是一样;

如果子类的构造方法中通过super显示调用父类的有参构造方法,那将执行父类相应构造方法,而不执行父类无参构造方法;

如果子类的构造方法中通过this显示调用自身的其他构造方法,在相应构造方法中应用以上两条规则;

特别注意的是,如果存在多级继承关系,在创建一个子类对象时,以上规则会多次向更高一级父类应用,一直到执行顶级父类Object类的无参构造方法为止。

编程要求
根据提示,在右侧编辑器Begin-End处补充代码:

声明一个Animal类,将属性nameage封装起来,提供对外的公共访问方法;

声明一个Cat类和Dog类,都继承Animal类,分别定义各自的voice方法和eat方法;

main方法中分别实例化一个Cat对象和Dog对象,设置各自的属性并调用这两个方法,再打印出名字和年龄信息;

具体具体输出要求请看测试说明。

测试说明
测试输入:
预期输出:

大花猫喵喵叫
大花猫吃鱼
大花猫6岁
大黑狗汪汪叫
大黑狗吃骨头
大黑狗8岁

代码实现

package case2;
public class extendsTest {
	public static void main(String args[]) {
		// 实例化一个Cat对象,设置属性name和age,调用voice()和eat()方法,再打印出名字和年龄信息
		/********* begin *********/
              Cat c=new Cat("大花猫",6);
			  c.voice();
			  c.eat();
			  c.show();
		/********* end *********/
		// 实例化一个Dog对象,设置属性name和age,调用voice()和eat()方法,再打印出名字和年龄信息
		/********* begin *********/
             Dog d=new Dog("大黑狗",8);
			 d.voice();
			 d.eat();
			 d.show();
		/********* end *********/
	}
}
class Animal {
	/********* begin *********/
           String name;
		   int age;
	/********* end *********/
}
class Cat extends Animal {
	// 定义Cat类的voice()和eat()方法
	/********* begin *********/
          Cat(String name,int age){
			  this.name=name;
			  this.age=age;
		  }
			  void voice(){
				  System.out.println(name+"喵喵叫");
			  }
			  void eat(){
				  System.out.println(name+"吃鱼");
			  }
			  void show(){
				  System.out.println(name+age+"岁");
			  }
 	  
	/********* end *********/
}
class Dog extends Animal {
	// 定义Dog类的voice()和eat()方法
	/********* begin *********/
          Dog(String name,int age){
			  this.name=name;
			  this.age=age;
		  }
			  void voice(){
				  System.out.println(name+"汪汪叫");
			  }
			  void eat(){
				  System.out.println(name+"吃骨头");
			  }
			  void show(){
				  System.out.println(name+age+"岁");
			  }
	/********* end *********/
}

第3关:super关键字的使用


任务描述
本关任务:掌握super关键字的使用。

相关知识
为了完成本关任务,你需要掌握:1.super关键字;2.super关键字的使用;3.super与this关键字的比较。

super关键字
在上一节中曾经提到过super的使用,那super到底是什么呢?super关键字出现在子类中,我们new子类的实例对象的时候,子类对象里面会有一个父类对象。怎么去引用里面的父类对象呢?使用super来引用,所以可以得出结论:super主要的功能是完成子类调用父类中的内容,也就是调用父类中的属性或方法。
super关键字的使用

super关键字的用法如下:

super可以用来引用直接父类的实例变量。
super可以用来调用直接父类方法。
super()可以用于调用直接父类构造函数。
1.super用于引用直接父类实例变量

public class TestSuper1 {
    public static void main(String args[]) {
        Dog d = new Dog();
        d.printColor();
    }
}
class Animal {
    String color = "white";
}
class Dog extends Animal {
    String color = "black";
    void printColor() {
        System.out.println(color);// prints color of Dog class
        System.out.println(super.color);// prints color of Animal class
    }
}

输出结果:

black
white

在上面的例子中,AnimalDog都有一个共同的属性:color。 如果我们打印color属性,它将默认打印当前类的颜色。要访问父属性,需要使用super关键字指定。

2.通过super来调用父类方法

public class TestSuper2 {
    public static void main(String args[]) {
        Dog d = new Dog();
        d.work();
    }
}
class Animal {
    void eat() {
        System.out.println("eating...");
    }
}
class Dog extends Animal {
    void eat() {
        System.out.println("eating bread...");
    }
    void bark() {
        System.out.println("barking...");
    }
    void work() {
        super.eat();
        bark();
    }
}

输出结果:

eating...
barking...

在上面的例子中,AnimalDog两个类都有eat()方法,如果要调用Dog类中的eat()方法,它将默认调用Dog类的eat()方法,因为当前类的优先级比父类的高。所以要调用父类方法,需要使用super关键字指定。

3.使用super来调用父类构造函数

public class TestSuper3 {
    public static void main(String args[]) {
        Dog d = new Dog();
    }
}
class Animal {
    Animal() {
        System.out.println("animal is created");
    }
}
class Dog extends Animal {
    Dog() {
        super();
        System.out.println("dog is created");
    }
}

输出结果

animal is created
dog is created

注意:如果没有使用super()this(),则super()在每个类构造函数中由编译器自动添加。

superthis关键字的比较
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
范例:

public class TestAnimalDogDemo {
    public static void main(String[] args) {
        Animal a = new Animal();
        a.eat();
        Dog d = new Dog();
        d.eatTest();
    }
}
class Animal {
    void eat() {
        System.out.println("animal : eat");
    }
}
class Dog extends Animal {
    void eat() {
        System.out.println("dog : eat");
    }
    void eatTest() {
        this.eat(); // this 调用自己的方法
        super.eat(); // super 调用父类方法
    }
}

输出结果:

animal : eat
dog : eat
animal : eat

上表对 thissuper 的差别进行了比较,从上表中不难发现,用 super 或 this 调用构造方法时都需要放在首行,所以superthis 调用构造方法的操作是不能同时出现的。
编程要求
comment: <> (“编程要求”部分介绍本关任务的具体要求,如实现步骤,规则等,最好能给出效果图)
根据提示,在右侧编辑器Begin-End处补充代码:

声明一个名为Person的类,里面有nameage两个属性,并声明一个含有两个参数的构造方法;

声明一个名为Student的类,此类继承自Person类,添加一个属性school,在子类的构造方法中调用父类中有两个参数的构造方法;

实例化一个Student类的对象s,为Student对象s中的school赋值,打印输出姓名:

测试说明
测试输入:
预期输出:

姓名:张三,年龄:18,学校:哈佛大学

代码实现

package case3;

public class superTest {
	public static void main(String[] args) {
		// 实例化一个Student类的对象s,为Student对象s中的school赋值,打印输出信息
		/********* begin *********/
        Student s=new Student();
        s.school="哈佛大学";
        System.out.println("姓名:"+s.name+",年龄:"+s.age+",学校:"+s.school);
		/********* end *********/
	}
}

class Person {
	/********* begin *********/
     String name;
	 int age;
     public Person(String name,int age){
         this.name=name;
         this.age=age;
     }
	/********* end *********/
}

class Student extends Person {
	/********* begin *********/
    String school;
      public Student(){
          super("张三",18);
      }

	/********* end *********/
}

第4关:方法的重写与重载


任务描述
本关任务:掌握方法的重写与重载。

相关知识
为了完成本关任务,你需要掌握:1.方法的重写(override);2.方法的重载(overload);3.重写与重载之间的区别。

方法的重写(override)
方法的重写

子类从父类中继承方法,有时,子类需要修改父类中定义的方法的实现,这称做方法的重写(method overriding)。“重写”的概念与“重载”相似,它们均是Java“多态”的技术之一,所谓“重载”,即是方法名称相同,但却可在不同的场合做不同的事。当一个子类继承一父类,而子类中的方法与父类中的方法的名称、参数个数和类型都完全一致时,就称子类中的这个方法重写了父类中的方法。“重写”又称为“复写”、“覆盖”。

如何使用重写

class Super {
 访问权限 方法返回值类型 方法1(参数1) {
     ...
 }
}
class Sub extends Super{
 访问权限 方法返回值类型 方法1(参数1) —————>复写父类中的方法
 {
     ...
 }
}

注意:方法重写时必须遵循两个原则,否则编译器会指出程序出错。

重写的方法不能比被重写的方法有更严格的访问权限;

重写的方法不能比被重写的方法产生更多的异常(关于异常,在后面会介绍)。

编译器加上这两个限定,是为了与Java语言的多态性(关于方法重写引起的运行时多态,在后面会详细讲述)特点一致而做出的。这样限定是出于对程序健壮性的考虑,为了避免程序执行过程中产生访问权限冲突或有应该捕获而未捕获的异常产生。

方法的重载(overload)
方法的重载

首先回顾一下前面所讲的方法的重载,方法重载是指多个方法可以享有相同的名字,但是参数的数量或类型不能完全相同。
调用方法时,编译器根据参数的个数和类型来决定当前所使用的方法。方法重载为程序的编写带来方便,是OOP多态性的具体变现。在Java系统的类库中,对许多重要的方法进行重载,为用户使用这些方法提供了方便。

重载的规则

被重载的方法必须改变参数列表(参数个数或类型不一样);
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
无法以返回值类型作为重载函数的区分标准。
重写与重载之间的区别
在这里插入图片描述

方法的重写和重载是Java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载;

方法重写是在子类存在方法与父类的方法的名字相同而且参数的个数与类型一样,返回值也一样的方法,就称为方法的重写;

方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

在这里插入图片描述
在这里插入图片描述

编程要求
根据提示,在右侧编辑器补充代码。

声明一个名为Person的类,里面声明nameage两个属性,定义talk()方法返回姓名和年龄信息;

声明一个名为Student的类,此类继承自Person类,添加school属性,声明带三个参数的构造方法,复写talk()方法,在该方法中调用父类的talk()方法,返回姓名、年龄和学校信息;

实例化子类对象s,调用talk()方法打印我是:张三,今年:18岁,我在哈佛大学上学。

测试说明
测试输入:
预期输出:

代码实现

package case4;
public class overridingTest {
	public static void main(String[] args) {
		// 实例化子类对象s,调用talk()方法打印信息
		/********* begin *********/
             Student s=new Student("张三",18,"哈佛大学");
			 s.talk();
		/********* end *********/	
	}
}
class Person {
	/********* begin *********/
    public String name;
	public int age;
	 void talk(){
        System.out.println("我是:"+name+",今年:"+age+"岁");
	 }
	/********* end *********/
}
class Student extends Person {
	/********* begin *********/
       private String school;
	   public Student(String name,int age,String school){
			  this.name=name;
			  this.age=age;
			  this.school=school;
		  }
		  void talk(){
			  System.out.print("我是:"+name+",今年:"+age+"岁,我在"+school+"上学");
		  }
	/********* end *********/
}

第5关:抽象类


任务描述
本关任务:掌握抽象类的定义及用法。

相关知识
为了完成本关任务,你需要掌握:1.抽象类的概念;2.如何定义抽象类。

抽象类的概念
前面对类的继承进行了初步的讲解。通过继承,可以从原有的类派生出新的类。原有的类称为基类或父类,而新的类则称为派生类或子类。通过这种机制,派生出的新的类不仅可以保留原有的类的功能,而且还可以拥有更多的功能。

除了上述的机制之外,Java也可以创建一种类专门用来当作父类,这种类称为“抽象类”。抽象类的作用有点类似“模版”,其目的是要设计者依据它的格式来修改并创建新的类。但是并不能直接由抽象类创建对象,只能通过抽象类派生出新的类,再由它来创建对象。

如何定义抽象类
抽象类的定义规则:

抽象类和抽象方法都必须用abstract关键字来修饰;

抽象类不能被实例化,也就是不能用new关键字去产生对象;

抽象方法只需声明,而不需实现;

含有抽象方法的类必须被声明为抽象类,抽象类的子类必须复写所有的抽象方法后才能被实例化,否则这个子类还是个抽象类。

抽象类的定义格式:

abstract class 类名称 // 定义抽象类
{
    声明数据成员;
    访问权限 返回值的数据类型 方法名称(参数...)//定义一般方法
    {
        ...
    }
    abstract 返回值的数据类型 方法名称(参数...);
    //定义抽象方法,在抽象方法里,没有定义方法体
}

注意:

在抽象类定义的语法中,方法的定义可分为两种:一种是一般的方法,它和先前介绍过的方法没有什么两样;另一种是“抽象方法”,它是以abstract关键字为开头的方法,此方法只声明了返回值的数据类型、方法名称与所需的参数,但没有定义方法体。
抽象类也可以像普通类一样,有构造方法、一般方法、属性,更重要的是还可以有一些抽象方法,留给子类去实现,而且在抽象类中声明构造方法后,在子类中必须明确调用。
编程要求
根据提示,在右侧编辑器补充代码:

声明一个名为Person的抽象类,在Person类中声明了三个属性nameageoccupation和一个抽象方法talk()

声明一个Student类和一个Worker类,都继承自Person类,添加带有三个参数的构造方法;

分别实例化Student类与Worker类的对象,分别调用各自类中被复写的talk()方法打印输出信息;

具体输出要求请看测试说明。

测试说明
测试输入:
预期输出:

学生——>姓名:张三,年龄:20,职业:学生!
工人——>姓名:李四,年龄:30,职业:工人!

代码实现

package case5;
public class abstractTest {
	public static void main(String[] args) {
		/********* begin *********/
		// 分别实例化Student类与Worker类的对象,并调用各自构造方法初始化类属性。
		   Student s=new Student("张三",20,"学生");
		   Worker w=new Worker("李四",30,"工人" );
		// 分别调用各自类中被复写的talk()方法 打印信息。
		     s.talk();
			 w.talk();
	}
}
// 声明一个名为Person的抽象类,在Person中声明了三个属性name age occupation和一个抽象方法——talk()。
abstract class Person {
	/********* begin *********/
     String name;
	 int age;
	 String occupation;
	  abstract  void talk();
	/********* end *********/
}
// Student类继承自Person类,添加带三个参数的构造方法,复写talk()方法 返回姓名、年龄和职业信息
class Student extends Person {
	/********* begin *********/
     public  Student(String name,int age,String occupation){
            this.name=name;
		this.age=age;
		this.occupation=occupation;
	   }
	 void talk(){
	System.out.println("学生——>姓名:"+name+",年龄:"+age+",职业:"+occupation+"!");
		}
	/********* end *********/
}
// Worker类继承自Person类,添加带三个参数的构造方法,复写talk()方法 返回姓名、年龄和职业信息
class Worker extends Person {
	/********* begin *********/
   public Worker(String name,int age,String occupation){
		this.name=name;
		this.age=age;
		this.occupation=occupation;
	}
	   void talk(){
		   System.out.println("工人——>姓名:"+name+",年龄:"+age+",职业:"+occupation+"!");
	   }
	/********* end *********/

}

第6关:final关键字的理解与使用


任务描述
本关任务:理解并能正确使用final关键字。

相关知识
为了完成本关任务,你需要掌握:1.final关键字的使用; 2.final关键字修饰类、成员变量和成员方法。

final关键字的使用
在Java中声明类、属性和方法时,可使用关键字final来修饰。

final标记的类不能被继承;

final标记的方法不能被子类复写;

final标记的变量(成员变量或局部变量)即为常量,只能赋值一次。

final关键字修饰类、成员变量和成员方法
1.final类

final用来修饰一个类,意味着该类成为不能被继承的最终类。出于安全性的原因和效率上的考虑,有时候需要防止一个类被继承。例如,Java类库中的String类,它对编译器和解释器的正常运行有着很重要的作用,不能轻易改变它,因此把它修饰为final类,使它不能被继承,这就保证了String类的惟一性。同时,如果你认为一个类的定义己经很完美,不需要再生成它的子类,这时也应把它修饰为final类。
定义一个final类的格式如下:

final class ClassName{
    ...
}

注意:声明为final的类隐含地声明了该类的所有方法为final方法。

下面的结论是成立的:声明一个类既为abstract,又为final是非法的,因为抽象类必须被子类继承来实现它的抽象方法。下面是一个final类的例子:

final class A{
    ...
}
class B extends A{//错误!不能继承A
    ...
}

2.final修饰成员变量
变量被声明为final后,成为常值变量(即常量),一旦被通过某种方式初始化或赋值,即不能再被修改。通常static与final一起使用来指定一个类常量。例如:

static final int SUNDAY=0;

把final变量用大写字母和下划线来表示,这是一种编码规定。

3.final修饰成员方法
用final修饰的方法为最终方法,不能再被子类重写,可以被重载。
尽管方法重写是Java非常有力的特征,但有时却需要避免这种情况的发生。为了不允许一个方法被重写,在方法的声明中指定final属性即可。例如:

class A{
    final void method(){}
}
class B extends A{//定义A类的一个子类B
    void method(){}//错误,method()不能被重写
}

该例中,因为method()方法在A中被声明为final,所以不能在子类B中被重写。如果这样做,将导致编译错误。
方法被声明为final有时可以提高性能:编译器可以自由地内联调用它们,因为它“知道”它们不会被子类重写。

编程要求
根据提示,在右侧编辑器Begin-End处补充代码:

仔细阅读代码,在右侧编辑器中调整代码使程序能正确编译运行;

具体输出要求请看测试说明。

测试说明
测试输入:
预期输出:

speedlimit=120
running safely with 100kmph
running safely with 100kmph

代码实现

package case6;
public class finalTest {
	public static void main(String args[]) {
		Bike1 obj = new Bike1();
		obj.run();
		Honda honda = new Honda();
		honda.run();
		Yamaha yamaha = new Yamaha();
		yamaha.run();
	}
}
//不可以修改 final 变量的值
// final方法,不可以重写
 //不可以扩展 final 类
//请在此添加你的代码
    /********** Begin *********/
	class Bike1 {
	final int speedlimit = 90;
	void run() {
		System.out.println("speedlimit=120");
	}
}
class Bike2 {
	 void run() {
		System.out.println("running");
	}
}
class Honda extends Bike2 {
	void run() { 
		System.out.println("running safely with 100kmph");
	}
}
 class Bike3 {
}
class Yamaha extends Bike3 { 
	void run() {
		System.out.println("running safely with 100kmph");
	}
}
    /********** End **********/

第7关:接口


任务描述
本关任务:掌握接口相关的知识。

相关知识
为了完成本关任务,你需要掌握:1.接口的定义; 2.接口的实现; 3.接口的扩展。

接口的定义
接口(interface)是Java所提供的另一种重要技术,它的结构和抽象类非常相似,也具有数据成员与抽象方法,但它与抽象类又有以下两点不同:

接口里的数据成员必须初始化,且数据成员均为常量;

接口里的方法必须全部声明为abstract,也就是说,接口不能像抽象类一样保有一般的方法,而必须全部是“抽象方法”。

接口定义的语法如下:

interface 接口名称 // 定义抽象类
{
    final 数据类型 成员名称 = 常量; //数据成员必须赋初值
    abstract 返回值的数据类型 方法名称(参数...);
    //抽象方法,注意在抽象方法里,没有定义方法主体
}

接口与一般类一样,本身也具有数据成员与方法,但数据成员一定要赋初值,且此值将不能再更改,方法也必须是“抽象方法”。也正因为方法必须是抽象方法,而没有一般的方法,所以抽象方法声明的关键字abstract是可以省略的。

相同的情况也发生在数据成员身上,因数据成员必须赋初值,且此值不能再被更改,所以声明数据成员的关键字final也可省略。事实上只要记得:

接口里的“抽象方法”只要做声明即可,而不用定义其处理的方式;

数据成员必须赋初值。

接口的实现
在Java中接口是用于实现多继承的一种机制,也是Java设计中最重要的一个环节,每一个由接口实现的类必须在类内部复写接口中的抽象方法,且可自由地使用接口中的常量。
既然接口里只有抽象方法,它只要声明而不用定义处理方式,于是自然可以联想到接口也没有办法像一般类一样,再用它来创建对象。利用接口打造新的类的过程,称之为接口的实现(implementation)。

接口实现的语法:

class 类名称 implements 接口A,接口B //接口的实现
{
    ...
}

接口的扩展
接口是Java实现多继承的一种机制,一个类只能继承一个父类,但如果需要一个类继承多个抽象方法的话,就明显无法实现,所以就出现了接口的概念。一个类只可以继承一个父类,但却可以实现多个接口。

接口与一般类一样,均可通过扩展的技术来派生出新的接口。原来的接口称为基本接口或父接口,派生出的接口称为派生接口或子接口。通过这种机制,派生接口不仅可以保留父接口的成员,同时也可加入新的成员以满足实际的需要。

同样的,接口的扩展(或继承)也是通过关键字extends来实现的。有趣的是,一个接口可以继承多个接口,这点与类的继承有所不同。

接口扩展的语法:

interface 子接口名称 extends 父接口1,父接口2...
{
    ...
}

编程要求
根据提示,在右侧编辑器补充代码:

声明一Person接口,并在里面声明三个常量:name、age、occupation,并分别赋值;

声明一Student类,此类实现Person接口,并复写Person中的talk()方法;

实例化一Student的对象s,并调用talk()方法,打印信息;

具体输出要求请看测试说明。

测试说明
测试输入:
预期输出:

学生——>姓名:张三,年龄:18,职业:学生!

代码实现

package case7;

public class interfaceTest {
	public static void main(String[] args) {
		// 实例化一Student的对象s,并调用talk()方法,打印信息
		/********* begin *********/
        Student s = new Student();
        System.out.print(s.talk());
		/********* end *********/

	}
}

// 声明一个Person接口,并在里面声明三个常量:name、age和occupation,并分别赋值,声明一抽象方法talk()
interface Person {
	/********* begin *********/
    final String name = "张三";
    final int age = 18;
    final String occupation="学生";
    public abstract String talk();
	/********* end *********/
}

// Student类继承自Person类 复写talk()方法返回姓名、年龄和职业信息
class Student implements Person {
	/********* begin *********/
public String talk(){
  return   "学生——>姓名:"+this.name+",年龄:"+this.age+",职业:"+this.occupation+"!";
	/********* end *********/
}
}

第8关:什么是多态,怎么使用多态


任务描述
本关任务:掌握对象的多态性。

相关知识
为了完成本关任务,你需要掌握:1.什么是多态;2.多态的实现条件;3.多态的实现形式。

什么是多态
所谓多态:就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
在这里插入图片描述

多态性是对象多种表现形式的体现。
现实中,比如我们按下F1键这个动作:

如果当前在Flash界面下弹出的就是AS 3的帮助文档;

如果当前在Word下弹出的就是Word帮助;

在Windows下弹出的就是Windows帮助和支持。

同一个事件发生在不同的对象上会产生不同的结果。

多态的实现条件
多态的三个条件:

继承的存在(继承是多态的基础,没有继承就没有多态);

子类重写父类的方法(多态下调用子类重写的方法);

父类引用变量指向子类对象(子类到父类的类型转换)。

子类转换成父类时的规则:

将一个父类的引用指向一个子类的对象,称为向上转型(upcasting),自动进行类型转换。此时通过父类引用调用的方法是子类覆盖或继承父类的方法,不是父类的方法。 此时通过父类引用变量无法调用子类特有的方法;

如果父类要调用子类的特有方法就得将一个指向子类对象的父类引用赋给一个子类的引用,称为向下转型,此时必须进行强制类型转换。

以下是一个多态实例的演示,详细说明请看注释:

public class TestAnimalDemo {
    public static void main(String[] args) {
        show(new Cat()); // 以 Cat 对象调用 show 方法
        show(new Dog()); // 以 Dog 对象调用 show 方法
        Animal a = new Cat(); // 向上转型
        a.eat(); // 调用的是 Cat 的 eat
        Cat c = (Cat) a; // 向下转型
        c.work(); // 调用的是 Cat 的 work
    }
    public static void show(Animal a) {
        a.eat();
        // 类型判断
        if (a instanceof Cat) { // 猫做的事情
            Cat c = (Cat) a;
            c.work();
        } else if (a instanceof Dog) { // 狗做的事情
            Dog c = (Dog) a;
            c.work();
        }
    }
}
abstract class Animal {
    abstract void eat();
}
class Cat extends Animal {
    public void eat() {
        System.out.println("吃鱼");
    }
    public void work() {
        System.out.println("抓老鼠");
    }
}
class Dog extends Animal {
    public void eat() {
        System.out.println("吃骨头");
    }
    public void work() {
        System.out.println("看家");
    }
}

输出结果:

吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠

可以用 instanceof 判断一个类是否实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。instanceof 的语法格式为:

对象 instanceof 类(或接口)
它的返回值是布尔型的,或真(true)、或假(false)。
#####多态的实现形式
在Java中有两种形式可以实现多态:继承和接口。

基于继承实现的多态
基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。

基于继承实现的多态可以总结如下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。

如果父类是抽象类,那么子类必须要实现父类中所有的抽象方法,这样该父类所有的子类一定存在统一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的统一接口来处理该层次的方法。

基于接口实现的多态
继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可能是通过实现接口并覆盖接口中同一方法的几不同的类体现的。

在接口的多态中,指向接口的引用必须是指定实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。

继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

编程要求
根据提示,在右侧编辑器补充代码:

声明一个Animal类,此类中定义eat()方法;

声明Dog类、Cat类、Lion类,均继承自Animal类,并复写了eat()方法;

运用多态方式实例化子类对象并调用eat()方法打印输出信息;

具体输出要求请看测试说明。

测试说明
测试输入:
预期输出:

eating bread...
eating rat...
eating meat...

代码实现

package case8;
public class TestPolymorphism {
	public static void main(String[] args) {
		// 以多态方式分别实例化子类对象并调用eat()方法
		/********* begin *********/
           Animal a=new Dog();
		   a.eat();
		   Animal b=new Cat();
		   b.eat();
		   Animal c=new Lion();
		   c.eat();
		/********* end *********/
	}
}
// Animal类中定义eat()方法
  abstract class Animal {
	/********* begin *********/
     abstract void eat();
	/********* end *********/
}
// Dog类继承Animal类 复写eat()方法
class Dog extends Animal {
	/********* begin *********/
      public void eat(){
		  System.out.println("eating bread...");
	  }
	/********* end *********/
}
// Cat类继承Animal类 复写eat()方法
class Cat extends Animal {
	/********* begin *********/
   public void eat(){
	   System.out.println("eating rat...");
   }
	/********* end *********/
}
// Lion类继承Animal类 复写eat()方法
class Lion extends Animal {
	/********* begin *********/
   public void eat(){
	   System.out.println("eating meat...");
   }
	/********* end *********/
}
  • 22
    点赞
  • 91
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

请你吃茶去

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值