文章目录
一. 组合 vs 继承
继承
- is-a 的关系,具有共同数据成员和方法,派生类在父类基础上有一定的扩展或区别
- 优缺点:
- 优点:
- 子类能自动继承父类的接口
- 创建子类的对象时,无须创建父类的对象
- 缺点:
- 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
- 支持扩展,但是往往以增加系统结构的复杂度为代价 - 不支持动态继承。在运行时,子类无法选择不同的父类
- 子类不能改变父类的接口
- 优点:
- 使用场景:
- 父类只是给子类提供服务,并不涉及子类的业务逻辑
- 层级关系明显,功能划分清晰,父类和子类各做各的。
- 父类的所有变化,都需要在子类中体现,也就是说此时耦合已经成为需求
- 新类需要向基类进行向上转型
组合
- has-a 的关系,强调一种包含关系,类与类构成整体和部分
- 优缺点:
- 优点:
- 不破坏封装,整体类与局部类之间松耦合,彼此相对独立
- 具有较好的可扩展性 - 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象
- 整体类可以对局部类进行包装,封装局部类的接口,提供新的接口
- 缺点:
- 整体类不能自动获得和局部类同样的接口
- 创建整体类的对象时,需要创建所有局部类的对象
- 优点:
- 使用场景:
- 优先考虑组合
- 对象间需要互发消息
- 一个对象需要使用另一个对象的功能或数据
二. 两个对象间互发消息
消息的实质:引用向对象发出的服务请求,是对数据成员和方法的调用
- 方式:类的组合,对象通过成为另一个对象的数据成员从而具备发送消息的条件,就可以向另一个对象发送消息
- 举例:
class FighterPlane {
String name;
int missileNum;
public Message(String _name,int _missileNum){
name = _name;
missileNum = _missileNum;
}
public void fire(){
if(missileNum > 0){
System.out.println("now fire a missile!");
missileNum -= 1;
}else{
System.out.println("No missile left!");
}
}
}
class A{
FighterPlane fp;
public A(FighterPlane fpp){
this.fp = fpp; // A对象中有了 FighterPlane 对象的引用
}
public void invoke(){
//A 对象发送消息给 FighterPlane 的对象
System.out.println(fp.name);
}
}
public class Run{
public static void main(String[] args){
FighterPlane ftp = new FighterPlane ("su35",10);
// 产生A对象,并将 ftp 作为对象引用传入
A a = new A(ftp);
// 发送消息,产生调用关系
a.invoke();
}
三. 多态
运行时多态
一种动态绑定,使用父类引用指向子类对象,再调用某一父类中的方法时,不同子类会表现出不同结果
实际上就是方法覆盖
- 作用:具有极好的扩展性,可维护性高
- 举例:
// 父类
class Bird{
public void moo(){
System.out.println("鸟叫声");
}
}
// 子类,鹦鹉类
class Parrot extends Bird{
public void moo(){
System.out.println("鹦鹉的叫声");
}
}
// 子类,麻雀类
class Sparrow extends Bird{
public void moo(){
System.out.println("麻雀的叫声");
}
}
public class Test {
public static void main(String[] args) {
Bird b1 = new Parrot(); // 父类引用指向子类对象
b1.moo(); // 调用父类的方法,实际上调用子类覆写的方法
Bird b2 = new Sparrow();// 父类引用指向子类对象
b2.moo(); // 调用父类的方法,实际上调用子类覆写的方法
}
}
/*
运行结果:
鹦鹉的叫声
麻雀的叫声
*/
编译时多态
一种静态绑定,根据参数的数据类型、个数和次序,在编译时就能够确定执行重载方法中的哪一个
实际上就是方法重载
四. 抽象类 vs 接口
抽象类
- 概念:
abstract
修饰符修饰的类,不包含足够的信息来描绘具体的对象 - 特点:
- 可以有零个或多个抽象方法,也可以包含非抽象方法
- 不能被实例化,即不能创建对象,但可以有声明,声明能引用所有具体子类对象
- 抽象类和具体子类的关系只能是被继承与继承关系
- 抽象方法没有方法体,抽象类子类可以不实现抽象类的所有抽象方法,但此时必须也作为抽象类,且不能和父类有同名的抽象方法
- 在抽象类中,非抽象方法可以调用抽象方法
abstract
不能和final
修饰同一个类,也不能和static
、private
、native
修饰同一个方法
- 使用场景
- 在继承方面应用:简化子类定义,增加系统结构关系的清晰度
- 在引用具体子类对象方面应用:抽象类的声明可以引用具体子类对象
接口
-
概念:一种是可以被引用调用的方法;另一种是功能方法说明的集合
interface
-
特点:
- 接口中数据成员全是
public fianl static
,即静态常量 - 接口没有构造方法,所有成员都是抽象方法,默认是
public abstract
修饰 - 接口也具有继承性,可以多重继承,用关键字
extends
声明 - 在类中,用implements关键字来实现接口。一个类可以实现多个接口,在implements后用逗号隔开多个接口的名字。一个接口也可被多个类来实现
- 接口和子接口都不能有自己的实例对象,但可以有声明,可以引用实现类对象
- 如果实现某接口的类不是abstract修饰的抽象类,则在类的定义部分必须实现接口的所有抽象方法,而且方法头部分应该与接口中的定义完全一致
- 如果实现接口的类是abstract类,则它可以不实现该接口的所有方法。但对于抽象类的任何一个非抽象的子类而言,接口中的所有抽象方法都必须实现
- l类在实现接口的抽象方法时,必须显式使用public修饰符,否则将被警告为缩小了接口中定义的方法的访问控制范围。
注意:在JDK8中,接口也可以定义静态方法和默认非静态方法,可以直接用接口名调用静态方法,实现类可以调用默认非静态方法。如果同时实现两个接口,接口中定义了一样的默认方法,则实现类必须重写默认方法,不然会报错。
- 接口中数据成员全是
-
使用场景:
- 约束多个实现类具有统一的行为,但是不在乎每个实现类如何具体实现
- 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识
- 实现类需要具备很多不同的功能,但各个功能之间可能没有任何联系
- 使用接口的引用调用具体实现类中实现的方法(多态)
异同
-
共同点:二者都可具有抽象方法,都不能实例化,但都可以有自己的声明,并能引用子类或实现类对象
-
不同点:
特性 接口 抽象类 组合 新类可以组合多个接口 只能继承单一抽象类 状态 不能包含属性(除了静态属性,不支持对象状态) 可以包含属性,非抽象方法可能引用这些属性 默认方法和抽象方法 不需要在子类中实现默认方法,默认方法可以引用其他接口的方法 必须在子类中实现抽象方法 构造器 没有构造器 可以有构造器 可见性 隐式 public 可以是protected 或 友元
五. 接口应用
求矩形、三角形、圆面积和周长
import java.applet.Applet;
import java.awt.*;
// Shapes 接口
interface Shapes {
public abstract double getArea();
public abstract double getPerimeter();
}
// 矩形
class Rect implements Shapes{
int x,y;
double width,height;
public Rect(int x,int y,double width,double height) {
this.x=x;
this.y=y;
this.width=width;
this.height=height;
}
public double getArea() {
return width*height;
}
public double getPerimeter() {
return 2*(width+height);
}
}
// 三角形
class Triangle implements Shapes {
int baseA,baseB,baseC;
double m;
public Triangle(int x,int y,int z) {
baseA=x;
baseB=y;
baseC=z;
m=(baseA+baseB+baseC)/2.0;
}
public double getArea() {
return (Math.sqrt(m*(m-baseA)*(m-baseB)*(m-baseC)));
}
public double getPerimeter() {
return (double)(baseA+baseB+baseC);
}
}
// 圆
class Circle implements Shapes{
int x,y;
double d,r;
public Circle(int x,int y,int width) {
this.x=x;
this.y=y;
r=width/2.0;
d=(double)width;
}
public double getArea() {
return (r*r*Math.PI);
}
public double getPerimeter() {
return (d*Math.PI);
}
}
public class RunShape extends Applet {
Rect rect=new Rect(5,15,25,25);
Triangle tri=new Triangle(5,5,8);
Circle cir=new Circle(13,90,25);
private void drawArea(Graphics g,Shapes s,int a,int b) {
g.drawString(s.getClass().getName()+"Area"+s.getArea(), a,b);
}
private void drawPerimeter(Graphics g,Shapes s,int a,int b) {
g.drawString(s.getClass().getName()+"Perimeter"+s.getPerimeter(),a,b);
}
public void paint(Graphics g) {
g.drawRect(rect.x, rect.y,(int)rect.width, (int)rect.height);
drawArea(g,rect, 50, 35);
drawPerimeter(g,rect,50,55);
drawArea(g,tri,50,75);
drawPerimeter(g,tri,50,95);
g.drawOval(cir.x-(int)cir.r,cir.y-(int)cir.r ,(int)cir.d,(int)cir.d);
drawArea(g,cir,50,115);
drawPerimeter(g,cir,50,135);
}
}
六. 运算符instanceof
用于确定一个实例对象是否属于一个特定的类,返回 Boolean 值
二目运算符,左边操作元为对象,右边操作元为类
class Uncle{}
class Pare{}
class Pare1 extends Pare{}
class Pare2 extends Pare1{}
public class Test {
public static void main(String[] args) {
Uncle u = new Uncle();
Pare p = new Pare();
Pare1 p1 = new Pare1();
Pare2 p2 = new Pare2();
//本类对象是本类的实例
if ( p instanceof Pare) {
System.out.println("p instanceof Pare");
}
//子类对象是父类的实例
if (!( p1 instanceof Pare)) {
System.out.println("p1 not instanceof Pare");
} else {
System.out.println("p1 instanceof Pare");
}
//父类对象不是子类的实例
if (p1 instanceof Pare2) {
System.out.println("p1 instanceof Pare2");
} else {
System.out.println("p1 not instanceof Pare2");
}
/* 报错:
java: 不兼容的类型: Pare无法转换为Uncle
if (p instanceof Uncle) {
System.out.println("p instanceof Uncle");
} else {
System.out.println("p not instanceof Uncle");
}
*/
if (null instanceof String){
System.out.println("null instanceof String");
}else{
System.out.println("null not instanceof String");
}
}
}
/*
运行结果:
p instanceof Pare
p1 instanceof Pare
p1 not instanceof Pare2
null not instanceof String
*/
七. equals 方法覆写应用
Object类的方法,比较本引用和参数指明的某个引用是否相等,即是否指向同一对象,返回Boolean值
class Student{
private int id;
private String name;
public Student(int _id, String _name){
id = _id;
name = _name;
}
public int getId(){
return id;
}
@Override
public boolean equals(Object o) {
return this.getId()==((Student)o).getId();
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student(2020001, "小明");
Student s2 = new Student(2020001,"小王");
System.out.println(s1.equals(s2)); // true
}
}
八. debug调试
例6.4
class AddClass {
public int x = 0, y = 0, z = 0;
AddClass(int x) {
this.x = x;
}
AddClass(int x, int y) {
this(x);
this.y = y;
}
AddClass(int x, int y, int z) {
this(x, y);
this.z = z;
}
public int add() {
return x + y + z;
}
}
public class SonAddClass extends AddClass {
int a = 0, b = 0, c = 0;
SonAddClass(int x) {
super(x);
a = x + 7;
}
SonAddClass(int x, int y) {
super(x, y);
a = x + 5;
b = y + 5;
}
SonAddClass(int x, int y, int z) {
super(x, y, z);
a = x + 4;
b = y + 4;
c = z + 4;
}
public int add() {
System.out.println("super:x+y+z=" + super.add());
return a + b + c;
}
public static void main(String[] args) {
SonAddClass p1 = new SonAddClass(2, 3, 5); // debug调试处
SonAddClass p2 = new SonAddClass(10, 20);
SonAddClass p3 = new SonAddClass(1);
System.out.println("a+b+c=" + p1.add());
System.out.println("a+b=" + p2.add());
System.out.println("a=" + p3.add());
}
}
步骤总结:
- 先声明父类的x,y,z并赋初值0
- 通过super()方法调用父类构造函数 AddClass(int x, int y, int z),再通过this()方法调用构造函数 AddClass(int x, int y) ,最后调用 AddClass(int x),然后执行
this.x = x
语句,完成x的赋值;接着回退执行this.y = y
和this.z = z
,完成父类构造函数的全部调用 - 接着声明本类的成员a,b,c并赋初值0
- 执行本类构造函数内部代码,完成a,b,c的赋值,对象初始化结束
例6.5
class Pare {
int i;
Pare() {
i = 6;
}
}
public class Construct extends Pare {
Construct() {}
Construct(int num) {}
public static void main(String[] args) {
Construct ct = new Construct(9); // debug调试处
System.out.println(ct.i); // 6
}
}
步骤总结:
- 执行子类构造函数 Construct(int num) 前先对父类的 i 声明并赋默认初值0,然后隐式调用父类构造函数,i 赋值为6
- 然后执行子类构造函数,完成对象初始化
总结
这一次的整理侧重于明晰一些具体理论知识的使用场景,以及重点知识的应用,从而能使抽象的概念更生动具体,而唯有自己亲身体验过了才能加深印象,帮助自己更好理解和掌握
在整理博客的过程中参考了一些资料以及许多他人优秀的文章,就不一一列举,在此表示感谢。