抽象类
概述
-
抽象定义
抽象就是从多个事物中将共性的、本质的内容抽取出来。
例如:狼和狗共性都是犬科,犬科就是抽象出来的概念。 -
抽象类
Java中可以定义没有方法体的方法,该方法的具体实现由子类完成,该方法称为抽象方法,包含抽象方法的类就是抽象类。 -
抽象方法的由来
多个对象都具备相同的功能,但是功能具体内容有所不同,那么在抽取过程中,只抽取了功能定义,并未抽取功能主体,那么只有功能声明,没有功能主体的方法称为抽象方法。
例如:狼和狗都有吼叫的方法,可是吼叫内容是不一样的。所以抽象出来的犬科虽然有吼叫功能,但是并不明确吼叫的细节。
特点
- 抽象类和抽象方法必须用
abstract
关键字来修饰。 - 抽象方法只有方法声明,没有方法体,定义在抽象类中。
格式:修饰符 abstract 返回值类型 函数名(参数列表)
; - 抽象类不可以被实例化,也就是不可以用new创建对象。
原因如下:- 抽象类是具体事物抽取出来的,本身是不具体的,没有对应的实例。
例如:犬科是一个抽象的概念,真正存在的是狼和狗。 - 而且抽象类即使创建了对象,调用抽象方法也没有意义。
- 抽象类是具体事物抽取出来的,本身是不具体的,没有对应的实例。
- 抽象类通过其子类实例化,而子类需要覆盖掉抽象类中所有的抽象方法后才可以创建对象,否则该子类也是抽象类。
相关问题
-
抽象类中是否有构造函数?
有,用于给子类对象进行初始化。 -
抽象类中可不可以没有抽象方法?
可以,但很少见,目的是不让该类创建对象。AWT的适配器对象就是这种类。
通常这个类中的方法有方法体,但没有内容。(没有大括号才是没有方法体,有大括号就表示有方法体)abstract class Demo { void show1() {} void show2() {} }
-
抽象关键字abstract不可以和哪些关键字共存?
private:修饰方法时,子类不知道父类的方法,因此父类的方法不能被子类覆盖;修饰类时更不用说了。static:修饰方法,可直接用抽象类的类名调用,不用对象,但是没有方法体,所以没有意义。
final:修饰类时,子类不能继承父类;修饰方法时,子类不能覆盖掉父类的方法。 -
抽象类和一般类的异同点
相同点:抽象类和一般类都是用来描述事物的,都在内部定义了成员。
不同点:
一般类有足够的信息描述事物;抽象类描述事物的信息有可能不足。
一般类中不能定义抽象方法,只能定义非抽象方法;抽象类中可定义抽象方法,同时也可定义非抽象方法。
一般类可以被实例化;抽象类不可以被实例化。 -
抽象类一定是个父类吗?
是的。因为需要子类覆盖其方法后才可以对子类实例化。
示例代码
abstract class Employee
{
private String name;
private String id;
private double salary;
Employee(String name, String id, double salary)
{
this.name = name;
this.id = id;
this.salary = salary;
}
public String getName(){return name;}
public void setName(String name){this.name = name;}
public String getId(){return id;}
public void setId(String id){this.id = id;}
public double getSalary(){return salary;}
public void setSalary(double salary){this.salary = salary;}
public abstract void work();
}
class Programmer extends Employee
{
Programmer(String name, String id, double salary)
{
super(name, id, salary);
}
public void work()
{
System.out.println("code...");
}
}
class Manager extends Employee
{
private double bonus;
Manager(String name, String id, double salary, double bonus)
{
super(name, id, salary);
this.bonus = bonus;
}
public void work()
{
System.out.println("manage...");
}
}
接口
概述
1. 定义
当一个抽象类中的方法都是抽象的时候,可以将该抽象类用另一种形式定义和表示,就是接口interface(表面上是这样,但实质却很不相同)。
abstract class Demo
{
abstract void show1();
abstract void show2();
}
2. 格式interface {}
interface Demo
{
abstract void show1();
abstract void show2();
}
3. 接口中的成员修饰符是固定的。
成员常量: public static final
,成员函数: public abstract
interface Demo
{
public static final int NUM = 4;
public abstract void show1();
public abstract void show2();
}
由于是固定的,所以可以省略,写成下面的形式(但是阅读性差).
interface Demo
{
int NUM = 4;
void show1();
void show2();
}
4. 接口不可以实例化。
只能由实现了接口的子类覆盖接口中所有的抽象方法后,该子类才可以实例化,否则这个子类就是一个抽象类。
class DemoImpl implements Demo
{
public void show1()//注意必须写public
{}
public void show2()
{}
}
class InterfaceDemo
{
DemoImpl d = new DemoImpl();
System.out.println(d.NUM);
System.out.println(DemoImpl.NUM);
System.out.println(Demo.NUM);
// 以上三种都对,但是不能写d.NUM = 3;
}
5. 接口的出现将“多继承”通过另一种形式体现出来,即“多实现”。
在java中不直接支持多继承,因为会出现调用的不确定性,所以java将多继承机制进行改良,变成了多实现。
Example1:一个类可以实现多个接口
interface A
{
public abstract void show();
}
interface B
{
public abstract void show();
}
class Test implements A,B
{
public void show()
{}
}
class Demo
{
public static void main(String[] args)
{
Test t = new Test();
t.show();//不会有不确定性,因为在接口中的方法没有方法体
}
}
注意,以下这种情况不允许多实现。因为若只写1,则不能覆盖到B中的函数,而如果1和2都写,则会冲突。
interface A
{
public abstract void show();
}
interface B
{
public abstract int show();
}
class Test implements A,B
{
/*------1------*/
public void show()
{}
/*------2------*/
public int show()
{...}
}
Example2:一个类在继承另一个类的同时,还可以实现多个接口
继承自Q,属于Q的体系,但通过实现接口,扩展功能。接口的出现避免了单继承的局限性。
class Q
{
public void method()
{}
}
class Test2 extends Q implements A,B
{}
Example3:接口之间可以多继承
类与类之间是继承关系,类与接口之间是实现关系,接口与接口之间是继承关系。
多继承的问题主要在于方法体。
interface CC
{
void show();
}
interface MM
{
void method();
}
interface QQ extends CC,MM
{
void function();
}
class WW implements WW
{
//需要覆盖三个方法
public void show(){}
public void method(){}
public void funcion(){}
}
特点
- 接口是对外暴露的规则。
凡是对外暴露的内容,全都可以理解为接口,不要把接口仅理解为interface。 - 接口是程序的功能扩展。
- 接口的出现降低耦合性。
- 接口可以用来多实现。
- 类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口。
- 接口与接口之间可以有继承关系。
例如,笔记本的接口(好好体会)
笔记本的接口(如USB接口)是提前定义的规则【接口】,外围设备(如U盘、鼠标)的生产产商只需按照该规则生产设备【实现类】即可,人们在使用时【使用类】,只需插拔不同的设备(即不同情况使用不同的实现类),而无需去更改笔记本内部的结构,以后若还有其他外围设备生产,也只用实现该接口就可。
笔记本的接口在于三部分:定义规则、实现规则、使用规则。而规则在java中即是interface。
注意:(引用指向的是对象)接口型引用指向的是其子类的对象
接口与抽象类的异同点
相同点:都是不断抽取出来的抽象的概念。
不同点:
- 抽象类体现继承关系,一个类只能单继承;
接口体现实现关系,一个类可以多实现。 - 抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法;
接口中只能定义抽象方法,必须由子类实现,接口中的成员都有固定修饰符。 - 抽象类是继承,是"is a"关系,在定义该体系的基本共性内容;
接口是实现,是"like a"关系,在定义体系额外功能。
多态
概述
1. 定义:某一类事物的多种存在形态。
例:动物中猫,狗。
猫这个对象对应的类型是猫类型:猫 x = new 猫();
同时猫也是动物中的一种,也可以把猫称为动物:动物 y = new 猫();
(动物是猫和狗具体事物中抽取出来的父类型。)
猫这类事物既具备猫的形态,又具备着动物的形态,这就是对象的多态性(一个对象,两种形态)。简单说,就是一个对象对应着不同类型。
网上的另一种解释——不同类的对象对同一消息作出不同的响应就叫做多态。就像上课铃响了,上体育课的学生跑到操场上站好,上语文课的学生在教室里坐好一样。
2. 多态在代码中的体现:父类或者接口的引用指向或者接受自己的子类对象。
abstract class Animal
{
abstract void eat();
}
class Dog extends Animal
{
void eat()
{
System.out.println("啃骨头");
}
}
class Cat extends Animal
{
void eat()
{
System.out.println("吃鱼");
}
}
class Demo
{
public static void main(String[] args)
{
Cat c = new Cat();
Dog d = new Dog();
method(c);
method(d);
}
public static void method(Animal a) //用父类作为参数,这个函数可以接收其各个子类的对象
{
a.eat();
}
}
3. 多态的好处:提高了代码的扩展性(重用性)和后期可维护性,前期定义的代码可以使用后期的内容。
网上的资料——简单讲多态的作用就是解耦。再详细点讲就是,多态是设计模式的基础,不能说所有的设计模式都使用到了多态,但是23种中的很大一部分,都是基于多态的。
class Pig extends Animal //新加的一个Pig类
{
void eat()
{
System.out.println("吃饲料");
}
}
class Demo
{
public static void main(String[] args)
{
Cat c = new Cat();
Dog d = new Dog();
method(c);
method(d);
method(new Pig()); //新加代码
}
public static void method(Animal a)
{
a.eat();
}
}
4. 多态的弊端:前期定义的内容不能使用(调用)后期子类的特有内容。
abstract class Animal
{
abstract void eat();
}
class Dog extends Animal
{
void eat()
{
System.out.println("啃骨头");
}
void guardHouse()
{
System.out.println("看家");
}
}
class Cat extends Animal
{
void eat()
{
System.out.println("吃鱼");
}
void catchMice()
{
System.out.println("抓老鼠");
}
}
class Pig extends Animal
{
void eat()
{
System.out.println("吃饲料");
}
void digEarth()
{
System.out.println("拱地");
}
}
class Demo
{
public static void main(String[] args)
{
/* 第一种方式*/
Animal a = new Cat();
a.eat();
a.catchMice(); //编译错误,在类Animal中找不到方法catchMice()
/* 第二种方式*/
Cat c = new Cat();
method(c);
}
public static void method(Animal a)
{
a.eat();
a.catchMice(); //编译错误,在类Animal中找不到方法catchMice()
}
}
5. 多态的前提:
1)存在继承或者实现关系
2)子类重写父类方法
3)父类引用指向子类对象
6. 转型:
Animal a = new Cat(); //自动类型提升,猫对象提升到了动物类型。
a.eat();
向上转型(自动):猫一旦提升成动物,访问上就有了局限性,不能再访问猫的特有功能了。其作用就是提高扩展性(可以接收不同类型)和限制对特有功能的访问。
如果还想调用具体动物——猫的特有功能,还可将该对象进行向下转型。
Cat c = (Cat)a;
c.eat();
c.catchMice();
向下转型(需要强转):目的是为了使用子类中的特有方法。
Animal a = new Dog();
Cat c = (Cat)a; //运行报错:java.lang.ClassCastException: Dog cannot be cast to Cat
Son s = (Son)new Father(); //注意这种写法也可以
注意,对于转型,自始至终都是子类对象在做着类型的变化,一会变成父类型,一会变成本类型。
7. instanceof关键字
用于判断对象的具体类型,只能用于引用数据类型判断。
public static void method(Animal a)
{
a.eat();
if(a instanceof Cat)
{
Cat c = (Cat)a;
c.catchMice();
}else if(a instanceof Dog)
{
Dog d = (Dog)d;
d.guardHouse();
}
}
通常在向下转型前使用,增强程序的健壮性(因为如果不是猫的类型,并且程序没有对其判断的话,直接强转,编译时不会报错,但运行会报错)。
8.多态的分类
1)编译时多态,即方法的重载,从JVM的角度来讲,这是一种静态分派(static dispatch)【函数的多态】
2)运行时多态,即方法的重写,从JVM的角度来讲,这是一种动态分派(dynamic dispatch)【对象的多态】
特点
1. 成员变量
编译时和运行时均只看引用变量所属的类。
简单说,编译和运行都参考等号的左边。
class Father
{
int num = 3;
}
class Son extends Father
{
int num = 4;
}
class Demo
{
public static void main(String[] args)
{
Father f = new Son();
System.out.println(f.num); //输出3,覆盖只发生在函数上;若Father中没有num变量,会编译失败
}
}
2. 成员函数(非静态)
编译时:要查看引用变量所属的类中是否有所调用的成员(有->编译通过;没有->编译失败)。
运行时:要查看对象所属的类中是否有所调用的成员。
简单说:编译看等号左边,运行看等号右边。
class Father
{
void show()
{
System.out.println("father show");
}
}
class Son extends Father
{
void show()
{
System.out.println("son show");
}
}
class Demo
{
public static void main(String[] args)
{
Father f = new Son();
f.show();
//Father和Son中都有show(),调用Son的方法,输出son show;
//Father中没有show(),Son中有,编译失败;
//Father中有show(),Son中没有,调用Father的方法,输出father show
}
}
内存图解:
非静态方法需要依赖对象。
动态绑定:在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程称为动态绑定。
在Java中重写可以被认为是动态绑定的最佳示例,因为父类和子类具有相同的方法—— 也就是说,它不决定要调用的方法。
3. 静态函数
编译和运行都参考等号的左边。
class Father
{
static void method(){
System.out.println("father static method");
}
}
class Son extends Father
{
static void method(){
System.out.println("son static method");
}
}
class Demo
{
public static void main(String[] args)
{
Father f = new Son();
f.method();//输出father static method
}
}
静态绑定:static
,final
和private
方法的绑定是静态绑定,都在编译时完成,因为它们无法被覆盖,所以将始终由某个本地类的对象访问,静态绑定提供了更好的性能。
以下三种类型的方法是没有办法表现出多态特性的(因为不能被重写):
1)static方法,因为被static修饰的方法是属于类的,而不是属于实例的。
2)final方法,因为被final修饰的方法无法被子类重写。
3)private方法和protected方法,前者是因为被private修饰的方法对子类不可见,后者是因为尽管被protected修饰的方法可以被子类见到,也可以被子类重写,但是它是无法被外部所引用的,一个不能被外部引用的方法,怎么能谈多态呢。
网友评论