Java基础(二):继承+多态+抽象类
文章目录
- 一、子类与父类:
- 二、继承中的成员变量隐藏和方法重写
- 1、成员变量的隐藏:
- 2、子类对继承父类方法的重写:
- 3、重载
- 三、super关键字
- ★1、调用父类的构造方法。super必须在第一行。
- 2、使用super访问被子类成员隐藏的父类成员或者方法。
- 四、final关键字
- 1、修饰类:表示该类不能被继承
- 2、修饰方法:它表示该方法不能被覆盖。
- 3、修饰变量:它表示定义一个常量。
- 4、修饰方法参数:表示在整个方法中,不能改变参数的值,说明只能读,不能写,如果final修饰参数是个对象,则不能改变对象的引用地址,但能改变对象的成员
- 5、不能修饰一个抽象类
- 6、修饰一个引用类型变量后,就不能修改变量指向的对象的状态
- 五、对象的上转型对象
- 六、继承与多态
- 1、定义:
- 2、多态的优点
- 3、多态存在的三个必要条件
- 4、示例程序:
- 七、abstract类和abstract方法 :
- 1、抽象方法:
- 2、抽象类:
- 1)、抽象类的定义语法:
- 2)、抽象类的特征:★★★
- 3)、抽象类的作用:
- 八、面向抽象编程和面向接口编程
- 九、面向对象程序设计的基本原则:
- 1)、定义:合成/聚合复用原则经常又叫做合成复用原则。
- 2)、特点:★★★
- 3)、缺点:
- 十、开-闭原则★★★
- 十一、接口interface
- (1)接口(Interface):概念+特点
- (2)实现接口:UML图
- (3)接口回调
- (4)接口与多态:
- (5)接口变量做参数:
- (6)抽象类和接口的区别★★★
- (7)面向接口编程
- 十、练习6.1
- (1)跨包继承
- (2)supper 必须写在第一行
- (3)继承父类方法出现的问题
- (4)java允许单继承一个类,并同时实现接口
- (5)super(x,y)
- (6)finall:
- (7)重写:父类构造方法调用子类重写方法
- (8)重写+static:隐藏父类的静态变量,让子类也有属于自己的静态成员
- (9)接口
- (10)接口
一、子类与父类:
1、继承的概念:
通过必要的说明能够实现某个类无需重新定义就拥有另一个类的某些属性和方法,把这种关系称为继承,先定义的类成为父类,后定义的类称为子类,并且允许多层的继承关系。
子类创建对象也会默认调用父类的无参构造函数
JAVA中实现继承的语法: class 子类名字 extends 父类名字{ }示例:class Person { //Person类称 父类,又称超类,基类protected String name;protected int age;public Person(){ }public Person(String name,int age){this.name = name;this.age = age;}public void setAge(int age){this.age = age;}public void display(){System.out.println("姓名:" + name + " 的年龄是: " + age);}}class Student extends Person{ //Student类是子类,又称派生类private String stuNo;public Student(){ }public Student(String stuNo){name = "张三";age = 18;this.stuNo = stuNo;}public void displayNo(){System.out.println("学号是:" + stuNo);}}
说明:★★★
1)、Java只支持单一继承,即extends后面只能有一个父类名字。
2)、如果要实现多重继承,只有通过接口实现。
3)、所有的类都派生自Object类。即除了类Object外,凡是没有指明扩展关系的类,都认为是省略了“extends Object”,都派生自类Object。
4)、派生具有传递性。如果类A派生了类B,类B又派生了类C,则C不仅继承了B,也继承了A。
2、继承的作用: 提供软件复用功能。就是让子类拥有父类中相关访问权限的成员变量和成员方法。这种做法能减小代码和数据的冗余度,大大增加程序的重用性。
3、继承关系的UML图:
类与类之间的继承关系,又称为泛化关系。泛化关系UML图是采用直线空心三角形连接两个类,Person类和Student类之间的泛化关系图如下:

二、继承中的成员变量隐藏和方法重写
1、成员变量的隐藏:
对于子类可以从父类继承的成员变量,只要子类中声明的成员变量和父类中的成员变量同名时,子类就隐藏了继承父类的成员变量,子类自己声明定义的方法操作,与父类同名的成员变量,则是子类中重新声明定义的这个成员变量。
注意:子类继承父类其实就是copy了一份,当子类重写隐藏父类成员时,可以用super来调用那些被隐藏的
阅读下面的程序并理解:
Goods.java
public class Goods {public double weight;public void oldSetWeight(double w) {weight=w;System.out.println("double型的weight="+weight);}public double oldGetPrice() {double price = weight*10;return price;}}
CheapGoods.java
public class CheapGoods extends Goods {public int weight;public void newSetWeight(int w) {weight=w;System.out.println("int型的weight="+weight);}public double newGetPrice() {double price = weight*10;return price;}}//UseCheapGoods.javapublic class UseCheapGoods{public static void main(String args[]) {CheapGoods cheapGoods=new CheapGoods();cheapGoods.weight=198.98; //是非法的,因为子类对象的weight已经是int型cheapGoods.newSetWeight(198);System.out.println("对象cheapGoods的weight的值是:"+cheapGoods.weight);System.out.println("cheapGoods用子类新增的优惠方法计算价格:"+cheapGoods.newGetPrice());cheapGoods.oldSetWeight(198.987); //子类对象调用继承的方法操作隐藏的double型变量weightSystem.out.println("cheapGoods使用继承的方法(无优惠)计算价格:"+ cheapGoods.oldGetPrice());} }
2、子类对继承父类方法的重写:
子类通过重写可以隐藏已继承的实例方法。
1、重写的语法规则
如果子类可以继承父类的某个实例方法,那么子类就可以对该方法进行重写。
方法重写是指:子类中定义一个方法,这个方法的返回值类型、方法名、参数列表与父类的方法完全相同。
2、重写的目的:
子类通过方法重写可以隐藏继承下来的方法,子类通过方法重写可以把父类的状态和行为改变为自身的状态和行为。
阅读并理解 下面的程序:
//Goods.java
public class Goods {public double weight;public void setWeight(double w) {weight=w;System.out.println("double型的weight="+weight);}public double getPrice() {double price = weight*10;return price;}}
//CheapGoods.java
public class CheapGoods extends Goods {public int weight;public void setWeight(int w) { //由于参数类型与父类不同,故不是重写weight=w;System.out.println("int型的weight="+weight);}public double getPrice() { //重写了父类中的getPrice()方法double price = weight*5;System.out.println("我重写了父类的getPrice方法");return price;}}
//UseCheapGoods.java
public class UseCheapGoods{public static void main(String args[]) {CheapGoods cheapGoods=new CheapGoods();cheapGoods.setWeight(198);System.out.println("对象cheapGoods的weight的值是:"+cheapGoods.weight);System.out.println("cheapGoods用子类新增的优惠方法计算价格:"+ cheapGoods.getPrice());cheapGoods.setWeight(198.987);System.out.println("cheapGoods使用继承的方法(无优惠)计算价格:"+ cheapGoods.getPrice());} }
★★★
特别注意:当子类重写了父类的方法时,要注意以下两点:
1、子类的方法不能比父类方法抛出更多的异常。
2、子类的方法不能缩小父类方法的访问权限。
3、重载
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;(不必要相同的意思)
- 被重载的方法可以改变访问修饰符;(不必要相同)
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
三、super关键字
super的作用:用来连接(使用)当前子类对象中的父类的方法或成员变量。
★1、调用父类的构造方法。super必须在第一行。
调用方法:super([参数]);
class Base{Base(){System.out.println("Base");
}
}
public class Test1 extends Base{Test1(){super();//这才是对的;如果不加他也会默认调用父类无参构造器System.out.println("Checket");}public static void main(String[] arg){Test1 c = new Test1();}}
2、使用super访问被子类成员隐藏的父类成员或者方法。
调用方法:
super.父类的成员变量;
super.父类的方法名();
理解下面的程序:
class Animal{ //定义一个动物类int age=3;//如果我把父类的成员变量 权限改为 private 则 子类不可以访问int weight=5;Animal(int age,int weight){ //构造方法this.age = age;this.weight = weight;}public void meow(){ //叫声System.out.println("Meow......");}
}class Cat extends Animal{ int age; //定义年龄int high; //定义高度Cat(int a,int w,int h){super(a,w); //调用父类构造方法high = h;}public void meow(){ System.out.println("喵喵......");}public void display(){System.out.println("父类的age:"+super.age);System.out.println("父类的weight:"+super.weight);System.out.println("子类的age:" + age);super.meow(); //父类中的meow()方法meow(); //子类中的meow()方法}public static void main(String[] arg){ Cat cat = new Cat(3,4,2);cat.display();}
}
结果:

★★★
特别注意:子类一定要调用父类中的构 式有两种:
1、当子类的构造方法中没有super()调用父类构造方法时,则子类构造方法默认调用父类的缺省构造方法。
2、当子类的构造方法中有super()调用父类构造方法时,则子类调用父类中参数匹配的父类构造方法。
四、final关键字
final 指的是“不可变的”,"最终的"意思。
final关键字可以用来修饰类、成员方法、成员变量 和方法中的局部变量。
final关键字有四种用法:
1、修饰类:表示该类不能被继承
示例:
JDK系统中的String类定义成final的,String类是不能被继承的:
public final class String extends Object implements Serializable, Comparable, harSequence
fianl class People { private String name;public People(String str){name = str;}}class Student extends People{ } //错误
2、修饰方法:它表示该方法不能被覆盖。
这种使用方式主要是从设计的角度考虑,即明确告诉其他可能会继承该类的程序员,不希望他们去覆盖这个方法。
class People { private String name;public People(String str){name = str;}public fianl void setName(Stirng str){name = str;}}class Student extends People{ public void setName(String n){ //错误 :提示不能覆盖People类中的final方法name = n;}}
3、修饰变量:它表示定义一个常量。
final会告诉编译器,这个数据是不会修改的,那么编译器就可能会在编译时期就对该数据进行替换甚至执行计算。两种赋值情况:
★★★
1)、定义时直接赋值 。这种情况一般和static组合使用。
2)、定义时不赋值,在构造方法中赋值。注意:所有的构造方法都要赋值,但赋值可以不相同。
如下面程序:
public class Test{private String name;private final int X = 4+3; // X = 7; 定义时直接赋初值private final int NUM; //定义一个常量,不赋初值。但必须在构造方法中赋值public Test(){NUM = 10; //给常量NUM赋初值 10}public Test(String n){name = n;NUM = 20; //给常量NUM赋初值 20}public void add(int y) {System.out.println(X * y);}public static void main(String[] ar) {new Test().add(5);}}
输出结果:35
4、修饰方法参数:表示在整个方法中,不能改变参数的值,说明只能读,不能写,如果final修饰参数是个对象,则不能改变对象的引用地址,但能改变对象的成员
class Value{int v;}public class FinalTest {public void finalFunc(final int i, final Value value) {// i = 5; 不能改变i的值// value= new Value(); 不能改变value的值value.v = 5; // 可以改变引用对象的值}}
注意:上面的四种方法中,第一种和第二种方法需要谨慎使用,因为在大多数情况下,如果是仅仅为了一点设计上的考虑,我们并不需要使用final来修饰方法和类。类在一般情况是要为了更广泛的使用,而广泛使用的一种方法就继承。
5、不能修饰一个抽象类
6、修饰一个引用类型变量后,就不能修改变量指向的对象的状态
五、对象的上转型对象
1、定义:如果B类是A类的子类或间接子类,当用B类创建对象b并将这个对象b的引用赋给A类对象a时,则称A类对象a是子类B对象b的上转型对象。
class A { }class B extends A { }public class Test{B b = new B();A a = b; // 或者 A a = new B(); }
2、性质:
对象b的上转型a的实体是有子类B创建的,但是上转型对象会失去子类B的一些属性和功能。上转型对象具有以下特点:
★★★
1)、上转型对象不能操作子类新增加的成员变量,不能使用子类新增的方法。即为较子类B失去一些属性和功能,这些属性和功能是新增的。
2)、上转型对象可以操作子类继承或隐藏的成员变量,也可以使用子类继承的或重写的方法。即为上转型对象可以操纵父类原有的属性和功能,无论这些方法是否被重写。
3)上转型对象调用方法时,就是调用子类继承和重写过的方法。而不会是新增的方法,也不是父类原有的方法。
4)、可以将对象的上转型对象再强制转换到一个子类对象,强制转换过的对象具有子类所有属性和功能。
3、示例程序:
class Subject{
public String b="我是父类b";
public void other(){
System.out.println("我是父类没被重写的方法");}
public static void statics(){
System.out.println("我是父类的静态方法");}
public void operation(){
System.out.println("我是父类方法");}
}class MySubject extends Subject{public String a="我是子类新增a";public String b="我是子类b";@Override public void operation(){ System.out.println("我是子类方法"); //我是子类方法System.out.println(b); //我是子类bSystem.out.println(super.b); //我是父类bSystem.out.println(getB1()); //我是子类bSystem.out.println(getB2()); //我是父类b} public String getA(){ return a; } public void setA(String a){ this.a=a;} public String getB1(){ return this.b; } public String getB2(){ return super.b; } public void setB1(String b){ this.b=b; }public void setB2(String b){ super.b=b; }
}public class Test{public static void main(String[] args){Subject sub=new MySubject();sub.operation();MySubject sub1=(MySubject)sub;System.out.println(sub1==sub);//true System.out.println(sub1.equals(sub));}}

六、继承与多态
1、定义:
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作,例如:
多态性是对象多种表现形式的体现。
比如我们按下 F1 键这个动作:
如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
如果当前在 Word 下弹出的就是 Word 帮助;
在 Windows 下弹出的就是 Windows 帮助和支持。
同一个事件发生在不同的对象上会产生不同的结果。
2、多态的优点
★★★
1). 消除类型之间的耦合关系
2). 可替换性
3). 可扩充性
4). 接口性
5). 灵活性
6). 简化性
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
3、多态存在的三个必要条件
1)、继承
2)、重写
3)、父类引用名指向子类对象
4、示例程序:
class Animal { void eat() {System.out.println("我要吃食物");}
}
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("看家"); }
}
public class Test {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(); } } public static void main(String[] args) {show(new Cat()); // 以 Cat 对象调用 show 方法show(new Dog()); // 以 Dog 对象调用 show 方法 Animal a = new Cat(); // 向上转型 a.eat(); // 调用的是 Cat 的 eatCat c = (Cat)a; // 向下转型 c.work(); // 调用的是 Cat 的 work}
}
七、abstract类和abstract方法 :
1、抽象方法:
定义类时,类中的某个方法不能实现,或者没有必要实现,但该类需要有这个方法,则可以将该方法定义成抽象方法。
比如:图形类(Shape),图形是有计算面积的功能,但是不没有具体的计算方法(计算公式),因此Shape类的计算机面积方法要定义成抽象方法。
抽象方法:只声明返回的数据类型、方法名称和所需的参数,没有方法体。比如:
abstract double calArea(); //计算面积
2、抽象类:
如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类要使用关键字abstract来修饰。简单的讲,用abstract来修饰的类就是抽象类。
如果类中定义有抽象方法,那么该类要定义成抽象类。
1)、抽象类的定义语法:
abstract class 类名称{成员变量;返回值类型 方法名(); //一般方法、区别于接口abstract 返回值类型方法名(); //抽象方法}
2)、抽象类的特征:★★★
(1)抽象类不能被实例化,但是可以创建一个抽象类的引用变量。例如 Shape shape;
(2)与抽象类相对应的是实例类。一个抽象类的子类只有把父类中所有抽象方法都重新定义后,才能成为实例类。只有实例类(不含抽象方法的类)才能被实例化——用于生成对象。
(3★)抽象类中可以没有抽象方法,但有抽象方法的类必须定义为抽象类。如果子类没有完全实现父类中的抽象方法,则该子类也必须定义抽象类。
(4)抽象类的构造方法不能定义成抽象的,即要在抽象类中定义构造方法必须是非抽象的,否则将出现编译错误。在创建子类的实例时,会自动调用抽象类的无参构造方法。
(5)抽象类及抽象方法不能被final修饰,即abstract与final不能连用。
(6)由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
定义抽象类和抽象方法是向用户和编译器表明该类的作用和用法,使类体系设计更加清晰,并支持多态。
3)、抽象类的作用:
抽象类不能实现实例化对象,程序中定义抽象类的目的是为同一类对象建立抽象的模板,在同类对象所对应的类体系中,抽象类往往在顶层中。这样可以使类的设计更加清晰,同时也为类的体现提供通用的接口。这些接口反映了同一类对象的共同特征。定义了抽象类后,可以利用Java的多态性,通过抽象类的通用接口处理类体系中的所有类。
简单的讲:抽象类就是为各子类提供一个共同的方法名,然后可以通过父类名来调用,实现多态。
下面用完整的代码说明圆、长方形继承图形。
//Shape类abstract class Shape{public enumColor{red,yellow,blue,white,black};protected Color lineColor; // 线条色protected Color fillColor; // 填充色protected Shape(){ // 无参构造方法lineColor= Color.white;fillColor= Color.black;}protected void setColor(Color lineColor, Color fillColor){this.lineColor= lineColor;this.fillColor= fillColor;}protected abstract void draw(); //抽象方法 画图形protected abstract double calArea(); //抽象方法计算面积
}// Circle类定义class Circle extends Shape{public staticfinal double PI = 3.1415926d; privatedouble radius; Circle(){ } Circle(double radius){this.radius = radius;}public voiddraw(){ System.out.println("画圆。");}public double calArea(){ return PI * radius *radius;}
}class Rectangle extends Shape{private double width; private double height; Rectangle(){ } Rectangle(doublewidth,double height){ this.width = width;this.height = height;}public void draw(){ System.out.println("画矩形。");}public double calArea(){ return width * height;}}//测试(主)类UseShapepublic class UseShape{ public static voidmain(String[] args){Shape sh = null; // 定义父类引用并初始化sh=new Rectangle(10,8); // 指向Rectangle对象System.out.println("矩形面积为:"+sh.calArea()); sh=new Circle(10); // 指向Circle对象System.out.println("圆面积为:"+sh.calArea());}}
八、面向抽象编程和面向接口编程
address
面向抽象编程,是对抽象类(abstract)进行一系列操作的编程。也就是说当设计某种重要的类时,不让该类面向具体的类,而是面向抽象类,即设计类中的重要数据是抽象类的对象,不是具体类声明的对象。
面向抽象编程的目的:是 为了应对用户需求的变化,将某个类中经常因需求变化而需要改变的代码从该类中分离出去。 面向抽象编程的核心是让类中每种可能的变化对应地交给抽象类的一个子类去负责,从而让该类的设计者不去关心具体实现,避免所设计的类依赖于具体的实现。面向抽象编程使设计的类容易应对用户需求的变化。
(我的总结是:多出了一个抽象类或者接口作为中转区,达到无缝衔接)
(关键步骤:上转型对象)

例子:

public abstract class Geometry {//定义抽象类 public abstract double getArea();//只是单纯的构造了一个abstract方法,不用加任何东西。}class Circle extends Geometry{//圆类:实现抽象类double r;Circle(double r){this.r = r;}public double getArea() { //方法的重写return r * r * 3.14;}}class Rectangle extends Geometry{//矩形类:实现抽象类double a,b;Rectangle(double a,double b){this.a = a;this.b = b;}public double getArea() {//方法重写return a*b;}}//柱体体积的类,不再依赖具体类,而是面向Geometry类:public class Pillar {//声明Geometry对象(抽象对象);面向抽象编程的体现之处Geometry bottom;double hight;Pillar(Geometry bottom,double h){//构造函数,获取值this.bottom = bottom;this.hight = h;} public double getVolume(){//返回体积值if(bottom == null){System.out.println("没有底,无法计算面积");return 0;}//这里的调用哪个getArea()函数是根据具体类的上转型对象bottom来确定。return bottom.getArea()*hight;}}// 创建上转型对象,确定其调用函数,输出其值public class Test{public static void main(String[] args) {Pillar pillar;Geometry bottom; bottom = null;pillar = newPillar(bottom,5);System.out.println("体积:" + pillar.getVolume()); bottom = new Circle(2);//上转型对象pillar = newPillar(bottom,1);System.out.println("体积:" + pillar.getVolume());//调用的是Circle里面的getVolume()。 bottom = newRectangle(5,2);//上转型对象pillar = newPillar(bottom,1);System.out.println("体积:" + pillar.getVolume());//调用的是Rectangle里面的getVolume()。}}
九、面向对象程序设计的基本原则:
1、组合/聚合优先原则:
在面向对象的设计中,如果直接继承基类,会破坏封装,因为继承将基类的实现细节暴露给子类;如果基类的实现发生改变,则子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性。于是就提出了组合/聚合复用原则,也就是在实际开发设计中,尽量使用合成/聚合,不要使用类继承。即在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新对象通过向这些对象的委派达到复用已有功能的目的。就是说要尽量的使用合成和聚合,而不是继承关系达到复用的目的。
1)、定义:合成/聚合复用原则经常又叫做合成复用原则。
该原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分:新的对象通过向这些对象的委派达到复用已有功能的目的。
尽量使用合成/聚合,不要使用类继承。
示例:长方体类定义
class Rectangle{ //长方形类private int len;private int width;public Rectangle(int l,int w){len = l; width = w;}public int calArea(){return len* width;}}class Cuboid{ //长方体Rectangle rectangle; //以长方形对象为成员变量,组合int high;public Cuboid(){rectangle = new Rectangle(3,5);high = 1;}public Cuboid(Rectangle r,int h){rectangle =r;high = h;}public int calVolume(){return rectangle.calArea() * high;}}
2)、特点:★★★
(1)、这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的;
(2)、这种复用所需的依赖较少;
(3)、每一个新的类可以将焦点集中在一个任务上;
(4)、这种复用可以在运行时动态进行,新对象可以使用合成/聚合关系将新的责任委派到合适的对象。
3)、缺点:
通过这种方式复用建造的系统会有较多的对象需要管理。
十、开-闭原则★★★
address
开闭原则明确的告诉我们:软件实现应该对扩展开放,对修改关闭,其含义是说一个软件应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的。
一个软件产品只要在生命周期内,都会发生变化,即然变化是一个事实,我们就应该在设计时尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现“拥抱变化”。开闭原则告诉我们应尽量通过扩展软件实体的行为来实现变化,而不是通过修改现有代码来完成变化,它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。
我们举例说明什么是开闭原则,以书店销售书籍为例,如下:
书籍接口:
public interface IBook{public String getName();//书名public String getPrice();//价格public String getAuthor();//作者
}
小说类书籍:
public class NovelBook implements IBook{private String name;private int price; private String author; public NovelBook(String name,int price,String author){ //赋值this.name = name; this.price = price; this.author = author;} public String getAutor(){ return this.author;} public String getName(){return this.name;} public int getPrice(){ return this.price;}
}
Test.java
public class Test{ public static void main(Strings[] args){IBook novel = new NovelBook("笑傲江湖",100,"金庸");System.out.println("书籍名字:"+novel.getName()+"书籍作者:"+novel.getAuthor()+"书籍价格:"+novel.getPrice());}
}
目投入使用后,书籍正常销售,但是我们经常因为各种原因,要打折来销售书籍,这是一个变化,我们要如何应对这样一个需求变化呢?
我们有下面三种方法可以解决此问题:
- 修改接口
在IBook接口中,增加一个方法getOffPrice(),专门用于进行打折处理,所有的实现类实现此方法。但是对于这样的一个修改方式,首先,作为接口,IBook应该稳定且可靠,不应该经常发生改变,否则接口作为契约的作用就失去了。其次,并不是所有的书籍都需要打折销售,仅仅因为NovelBook打折销售就修改接口使所有书都必须实现打折销售的逻辑,显然与实际业务不符。因此,此方案否定。 - 修改实现类
修改NovelBook类的方法,直接在getPrice()方法中实现打折处理。此方法是有问题的,例如我们如果getPrice()方法中只需要读取书籍的打折前的价格呢?这不是有问题吗?当然我们也可以再增加getOffPrice()方法,这也是可以实现其需求,但是这就有二个读取价格的方法,因此,该方案也不是一个最优方案。 - 通过扩展实现变化
我们可以增加一个子类OffNovelBook(继承自NovelBook),重写getPrice()方法。此方法修改少,对现有的代码没有影响,风险少,是最好的办法,同时也符合开闭原则。
打折书类:
public class OffNovelBook implements NovelBook{public OffNovelBook(String name,int price,String author){super(name,price,author);}//覆写价格方法,当价格大于40,就打8析,其他价格就打9析public int getPrice(){if(this.price > 40){ return this.price * 0.8;}else{return this.price * 0.9;} }
}
现在打折销售开发完成了,我们并没有改变原来的程序,而是在原来的系统上增加了一个子类OffNovelBook 而已。这样,既保护了原系统的结构与完整性,又保证了系统的可维护性,同时减少了bug产生的机会。
例二:
当发现:原由接口中的方法:比如 worker 接口:eat,work 两个方法,
但是新增了机器人来工作,他实现worker接口,则eat方法他并不需要,但是又不得不实现,怎么办呢?这时候就最好将eat方法移到新建的接口中
原:Iworker 接口、CommonWorker普通工人类、SuperWorker高效工人类、Mannge管理系统类

当想用机器人代替工人来做服务、则变成下面:

十一、接口interface
(1)接口(Interface):概念+特点
1、概念:
官方解释:Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
大家的理解:接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法(JDK8.0之前版本)所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。
JDK8.0及之后版本中,接口中的方法包含有default或static关键字修饰的方法,这样的方法是实现的。
一个接口可以继承其他接口
2、语法格式:
public interface 接口名称 [extends 其他的接口名] {声明常量抽象方法 default方法 JDK8.0版本新增的。static 方法 JDK8.0版本新增的。}
比如:
public interface Speakable{int NUM = 10; //等价于 public static final int NUM = 10;void speak(); //等价于 public abstract void speak();default void voice(){ //JDK8.0版本新增的System.out.println("正在调节声音大小....");}static void noice(){ //JDK8.0版本新增的System.out.println("讲话的时候会带来噪音.....");}}
3、接口与类相似点:
1)、一个接口可以有多个方法。
2)、接口文件保存在 .java 结尾的文件中,文件名使用接口名。
3)、接口的字节码文件保存在 .class 结尾的文件中。
4)、接口相应的字节码文件必须在与包名称相匹配的目录结构中。
4、接口与类的区别:
1)、接口不能用于实例化对象。
2)、接口没有构造方法。( 抽象类是有的)
3)、接口中所有的方法必须是抽象方法。
4)、接口不能包含成员变量,除了 static 和 final 变量。
5)、接口不是被类继承了,而是要被类实现。
6)、接口支持多继承。
5、接口特性
1)、接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能 是 public abstract,其他修饰符都会报错)。
2)、接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
3)、接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
6、UML图:接口的UML图如下:

第一层:接口名字层,接口的名字必须是斜体字形,而且需要使用<>修饰名字,并且该修饰符和名字分开在2行。
第二层:常量层,列出接口中的常量及类型,格式是“常量名字:类型”。
第三层:方法层:列出接口中的方法及返回值类型,格式“方法名字(参数列表):类型”。
上面的接口如图所示:

(2)实现接口:UML图
类使用implements关键字实现接口。在类声明中,implements关键字放在class声明后面。
1、语法格式:
class 类名 implements 接口1,接口2,接口3...{成员变量成员方法重写各接口中的抽象方法 (如果接口中有,必须重写)重写接口中的static方法 (如果接口中有,可重写、可不重写)重写接口中的default方法 (如果接口中有,可重写、可不重写)}
2、程序示例:
定义人(Person)类实现上面定义的Speakable接口,并编写一个UsePerson类进行测试。
class Person implements Speakable{protected int age; //年龄,隐藏接口常量,立新的变量public void setAge(int age){this.age = age;}public void speak(){System.out.println("我是人,用嘴巴说话的");}public static void noice(){System.out.println("讲话的时候,周围环境会产生噪音");}}
编写一个应用程序UsePerson.java来对上面的程序进行测试。程序代码如下:
public class UsePerson {public static void main(String[] ar) {Person p = new Person();p.speak();p.noice();}}
程序运行的结果是:

例:建动物接口,派生子类Dog,Cow,采用接口回调
package day01;
import java.util.*;interface Animal {String name=null;final int age=0;float weight=0;// 省略 finalpublic abstract void meow() ;void eat(); // 省略public abstractvoid setAge(int i);int getAge();}class Dog implements Animal{int age=10;//接口定义的常量可以重写隐藏,但如果要使用必须 :接口名.常量名public void meow() {// 记得public 写出来,不能降低抽象方法的访问权限System.out.println("456");}public void eat(){System.out.println("狗喜欢吃骨头");}public void setAge(int i) {age=i;}public int getAge() {return age;}
}class Cow implements Animal{int age=10;//接口定义的常量可以重写隐藏,但如果要使用必须 :接口名.常量名public void meow() {System.out.println("789");}public void eat(){System.out.println("奶牛喜欢草");}public void setAge(int i) {age=i;}public int getAge() {return age;}
}
class Test1{public static void main(String[] arg){Animal a;a= new Dog();// 接口回调a.setAge(18);a.meow();a.eat();System.out.println(a.getAge());a= new Cow();a.setAge(21);a.meow();a.eat();System.out.println(a.getAge());}
}
3、重写接口中方法需要注意的规则:
1)、类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
2)、类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
3)、如果实现接口的类是抽象类,那么就没必要实现该接口的方法。
4、实现接口需要注意的规则:
1)、一个类可以同时实现多个接口。
2)、一个类只能继承一个类,但是能实现多个接口。
3)、一个接口能继承另一个接口,这和类之间的继承比较相似。
5、UML实现图:

接口实现采用的是类指向接口,用虚线和空心三角形连接指向。
(3)接口回调
接口回调和上转型有一点像,但是不同的,上转型归根结底是类的范畴,所以他在调用成语变量时有诸多限制,而接口回调则是接口的范畴,比较开放。
java接口回调是指:可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调。
接口与类的形式结构如下:
interface InterTest{void doTest(); //定义抽象方法}class ClaTest implements InterTest{ //定义类实现接口public void doTest(){ //实现接口中的方法,注意前面要加public修饰//方法实现的代码;}}public class UseInterface{public static void main(String[] ar){InterTest it = new ClaTest(); //将实现类的对象赋给接口引用名it.doTest(); //接口回调}}
程序示例:定义一个People接口,然后定义学生类和教师类实现接口,并进行测试。
interface People{ //定义接口void peopleList(); //抽象方法}class Student implements People{ //定义学生类实现接口public void peopleList(){ //实现接口中的方法System.out.println("I’m a student.");}}class Teacher implements People{ //定义教师类实现接口public void peopleList(){ //实现接口中的方法System.out.println("I’m a teacher.");}}public class Example{ //定义应用程序使用接口public static void main(String args[]){People a; //声明接口变量a=new Student(); //实例化,接口变量中存放对象的引用a.peopleList(); //接口回调a=new Teacher(); //实例化,接口变量中存放对象的引用a.peopleList(); //接口回调}}
(4)接口与多态:
接口多态是指不同的类在实现同一个接口时可能具有不同的实现方式,那么接口变量在回调不同类的接口方法时,就体现出了多种形态。
比如:对两个整数a和b进行计算,有人进行算术平均数计算:(a + b) / 2 也有人使用几何平均数计算: 根号内a乘b 。这样计算方法就体现出了多种形态。
程序示例:

interface Shape{ //定义计算面积的接口
double calArea();
}class Circle implements Shape{ //定义Circle类实现Shape接口
private double radius;
public Circle(){
}
public Circle(double r){
radius = r;
}
public double calArea(){ //重写接口方法
return Math.PI *radius * radius;
}
}class Rectangle implements Shape{
private double length;
private double width;
public Rectangle(){
}
public Rectangle(double l,double w){
length = l;
width = w;
}
public double calArea(){
return width * length;
}
}class Triangle implements Shape{
private double sideA;
private double sideB;
private double sideC;
public Triangle() {}public Triangle(double s1,double s2,double s3) {sideA = s1;sideB = s2;sideC = s3;
}
public double calArea() {double l = (sideA + sideB + sideC) / 2;return Math.sqrt(l * (l - sideA)*(l - sideB)*(l - sideC) );}
}public class CalShapeAreaSum {
public static void main(String[] ar) {Shape[] shape = {new Circle(2),new Rectangle(3,4),newTriangle(3,4,5)};double area = 0;for (Shape s : shape) {area +=s.calArea(); //s分别调用了Circle对象,Rectangle对象,Triangle对象//的calArea()方法,表现了 接口的多态}System.out.println("三个图形对象的面积之和为:"+area);}}
(5)接口变量做参数:
如果一个方法的参数是接口类型,就可以将任何实现该接口的类的实例的引用传递给该接口参数,那么该接口参数就可以回调类实现的接口方法。
下面程序说明卖汽车的示例
//汽车接口 Car.java
interface Car{ //要求 接口中有:汽车名称和售价String getName();int getPrice();}//宝马类class BMW implements Car{@Overridepublic String getName() {return "宝马";}@Overridepublic int getPrice() {return 300000;}}//奇瑞QQclass CheryQQ implements Car{@Overridepublic String getName() {// TODO Auto-generated method stubreturn "奇瑞QQ";}@Overridepublic int getPrice() {// TODO Auto-generated method stubreturn 40000;}}class CarShop{//汽车出售店private int money=0; //收入public void sellCar(Car car){ //卖出一部汽车System.out.println("车型:"+car.getName()+"价格:"+car.getPrice());//增加卖出车售价的收入money+=car.getPrice();}public int getMoney(){//售车总收入return money;}}public class jieKouDemo {//测试类,主类public static void main(String[]args){CarShop shop=new CarShop();shop.sellCar(new BMW());//卖出一辆宝马shop.sellCar(new CheryQQ());//卖出一辆奇瑞QQSystem.out.println("总收入:"+shop.getMoney());}}
(6)抽象类和接口的区别★★★
1)、 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行(static,default修饰方法除外)。
2)、 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
3)、 JDK8.0之前版本,接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
4)、一个类只能继承一个抽象类,而一个类却可以实现多个接口。
5)、抽象类是继承关系(本质是一个类),接口是实现关系。抽象类是为各子类提供一个统一的类模板,接口为其他的类增加一个统一的功能。继承是“a is A”的关系,接口实现是 "hava a"的关系。
6)、抽象类和接口都是超类型,抽象类是对事物的抽象,接口是对行为的抽象。
抽象类与接口的选择:
当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
(7)面向接口编程
接口只关心操作,而不关心这些操作的具体细节实现,也就是说我们把主要精力放在程序的设计上。使用接口进行程序设计的核心思想就是使用接口回调,即接口变量存放实现该接口的类对象的引用,从而接口变量就可以回调类实现的接口方法。利用接口也可以体现“开闭原则”。
面向接口编程是开发程序的功能先定义接口,接口中定义约定好的功能方法声明,通过实现该接口进行功能的实现,完成软件或项目的要求.软件或项目随着时间的不断变化,软件的功能要进行升级或完善,开发人员只需要创建不同的新类重新实现该接口中所有方法,就可以达到系统升级和扩展的目的.
精简定义:要针对接口编程,而不是针对实现编程。
面向接口编程的程序设计UML图的结构关系如下:

十、练习6.1
(1)跨包继承
address
(还没理解明白,上面是深入理解这个知识点)
已知类A打包在packageA中,类B打包在packageB中,且类B被声明为public,且有一个protected权限的成员变量x。类C也位于packageA包中,且继承了B类。则下列说法正确的是( c )
A、
类A的实例不能访问类B的实例。
B、
类A的实例能访问类B一个实例的x成员。(相当于:a.b.x但x是protected,不能直接调用)
C、
类C的实例可以访问类B一个实例的x成员。(因为c继承b,除了构造函数和private不能,其他都可以继承过来)
D、
类C的实例不能访问类B的实例。
(2)supper 必须写在第一行
class Base{Base(){System.out.println("Base");}
}public class Checket extends Base{Checket(){//supper()写这里的话才对System.out.println("Checket");super();//写这里的话会编译错误}public static void main(String[] arg){Checket c = new Checket();}}
A、
编译时错误
B、
先输出Base,再输出Checket
C、
先输出Checket,再输出Base
D、
运行无结果输出
(3)继承父类方法出现的问题
对于类定义如下:
public class Parent{public int addValue(int a,int b){ return a + b;}
}
class Child extends Parent{
}
以下哪个方法声明能够加入到Child类中,编译正确。( b )
A、
int addValue(int a,int b){ }// 没把public 写出来,子类继承父类不能降低权限
B、
public void addValue(){ } //子类继承父类,并重载addValue方法
C、
public void addValue(int b,int a){ }// 想重写父类方法,但返回类型不一致
D、
public int addValue(int b,int a) throws Exception { }//没保持跟父类方法一致,无法重写
(4)java允许单继承一个类,并同时实现接口
(5)super(x,y)
有如下类定义:
public class S extends F{S(int x){ }S(int x,int y){super(x,y);}
}
则类F中一定有构造方法( E )
A、
F() { }
B、
F(int x){ }C、
F(int x,int y){ } D、
F(int x,int y,int z){ }E、
F(int x){ } 和 F(int x,int y){ } F、
F(int x){ }或 F(int x,int y){ }
(6)finall:
关于final,下列说法错误的是( )
(5.0分)
A、final 修饰的变量,只能对其赋一次值
B、final修饰一个引用类型变量后,就不能修改变量指向的对象的状态
C、final 不能修饰一个抽象类
D、final修饰的方法,不能被子类覆盖
(final定义了的变量是没法赋值了)
(7)重写:父类构造方法调用子类重写方法
就算通过父类构造方法中,想调用父类中跟子类同名的方法,也是不行的,他还是会去调用子类重写的方法
package day01;
import java.util.*;class Person{public Person(){func();//此时teacher子类已经重写了该方法,所以调用子类的}public void func(){System.out.print("1,");}
}class Teacher extends Person{public Teacher(){super();//调用父类构造方法
//就算通过父类构造方法中,想调用父类中跟子类同名的方法,也是不行的,他还是会去调用子类重写的方法
//不写super显示调用,他也会默认调用父类构造方法}public Teacher(int a){//重载构造方法System.out.println(a);}public void func(){//重写父类方法System.out.print("2,");}
}class Test1{public static void main(String[] arg){Teacher t1 = new Teacher();Teacher t2 = new Teacher(3);}
}
答案:2,2,3
(8)重写+static:隐藏父类的静态变量,让子类也有属于自己的静态成员
一个类自由一个静态变量,子类继承父类,只是用父类的静态变量而已,要想实现子类也有,就必须重写,隐藏父类静态变量
(9)接口
虽然接口和抽象类不能创建对象,但它们的对象引用仍可指向该类型的对象。这种说 法 ( )
A、正确
B、不正确
C、 不能确定
D、接口和抽象类不能说明其对象引用
(10)接口
如果子类是非抽象类,则必须实现接口中的所有方法;
如果子类是抽象类,则可以不实现接口中的所有方法,因为抽象类中允许有抽象方法的存在!
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
