抽象、继承、封装、多态(允许不同类的对象对同一消息做出响应)
1. 继承
- Java不支持多重继承,即子类至多只能有一个父类。但是可以通过实现多个接口达到多重继承的目的。
- 子类只能继承父类的非私有成员变量与方法。
- 当子类中定义的成员变量与父类中定义的成员变量同名时,子类中的成员变量会覆盖父类的成员变量,而不会继承。
- 当子类中的方法与父类中的方法有相同的函数签名(相同的方法名,相同的参数个数与类型)时 ,子类将会覆盖父类的方法,而不会继承。
继承与组合
继承是一种is-a的关系 比如汽车是交通工具的一种 car extends Verhicle
组合是一种has-a的关系 比如汽车有多个轮胎
继承
class Verhicle{
}
class Car extends Verhicle{
}
组合
class Tire{
}
class Car extends Verhicle{
private Tire t=new Tire();
}
选取原则:
- 除非两个类之间是is-a的关系否则不要轻易使用继承。当父类被修改时,会影响到所有继承它的子类,增加程序的维护难度与成本
- 不要仅仅为了实现多态而使用继承,如果类之间没有is-a的关系,可以通过接口与组合的方式实现多态。
2. 多态
多态表示当同一个操作作用在不同对象时会有不同的语义,从而产生不同的结果。
比如同样是执行“+”操作,3+4实现整数相加,而“3”+“4”却实现了字符串的连接。
多态表现方式:
- 方法的重载。重载是指同一个类中有多个同名的方法,但这些方法有不同的参数,因此在编译时就可以确定到底调用哪一个方法,它是一种编译时的多态。重载可以被看做一个类中的方法多态性。
- 方法的覆盖。子类可以覆盖父类的方法。因此同样的方法会在父类与子类中有不同的表现形式。Java中父类的引用变量不仅可以指向父类的实例对象,也可以指向子类的实例对象。同样接口的引用变量也可以指向其实现类的实例对象。而程序调用的方法在运行期才动态绑定。由于只有在运行时才能确定调用哪个方法,因此通过方法覆盖实现的多态也可以被称为运行时多态。
Java提供了哪两种用于多态的机制?
答:编译时多态和运行时多态。编译时多态是通过方法重载实现的;运行时多态是通过方法的覆盖(子类覆盖父类方法)实现的。
注意:只有类中的方法有多态的概念,类中成员变量没有多态的概念。
成员变量是无法实现多态的,成员变量的值取父类还是子类并不取决于创建对象的类型,而是取决于所定义变量的类型,这是在编译期间确定的。
3. 重载和覆盖的区别
重载
- 重载通过不同的方法参数来区分(不同的参数个数、不同的参数类型、不同的参数顺序),不能通过方法的访问权限、返回值类型和抛出的异常类型来进行重载。如果函数名相同、参数列表相同、返回值不同会报错。
- 对于继承来说,如果父类方法的访问权限是private,那么就不能再子类中对其重载。如果子类中也定义了一个同名的函数,这只是一个新方法,不会达到重载的效果。
覆盖指子类函数覆盖父类函数
- 子类中的覆盖方法必须和父类中的被覆盖方法有相同的函数名和参数。
- 子类的覆盖方法返回值必须和父类中的被覆盖方法的返回值相同。如果不同会出现编译错误,编译器无法区分应该调用哪个函数。
- 子类的覆盖方法抛出的异常必须和父类被覆盖方法抛出的异常一致。
- 父类中被覆盖的方法不能是private,否则子类只是定义了一个方法,并没有覆盖。
重载与覆盖区别
- 覆盖是子类和父类的关系,重载是同一个类中方法之间的关系。
- 覆盖要求参数列表相同;重载要求参数列表不同。
- 覆盖关系中调用方法体是根据对象的类型来决定;而重载是根据调用时实参和形参列表来选择。
4. 抽象类接口异同
- 只要包含一个抽象方法的类就必须被声明为抽象类,被声明为抽象的方法不能包含方法体,(abstract只能用来修饰类与方法,不能修饰属性)。
- 抽象类的子类为父类中的所有抽象方法提供具体的实现,否则他们也是抽象类。在实现时必须包含相同的或者更低的访问级别(public->protected->private)
- 抽象类不能被实例化,但可以创建一个对象使其指向子类的一个实例。
- 接口中所有的方法都没有方法体,默认就是public权限,成员变量都是public static final类型。抽象类中可以提供部分方法的实现。
相同点
- 都不能被实例化
- 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能被实例化
不同点
- 接口只有定义,其方法不能在接口中实现;而抽象类可以有定义与实现;
- 接口强调特点功能的实现,has-a的关系;抽象类是is-a的关系
- 接口中定义的成员变量默认为public static final,只能够有静态的不能被修改的数据成员而且必须赋初值,其所有的方法都是public、abstract的,而且只能被这两个关键字修饰。而抽象类可以有自己的成员变量,也可以有非抽象的成员方法。抽象类中的成员变量默认为default(本包可见),也可以被定义为public/private/protected,这些成员变量可以在子类中被重新定义重新赋值。抽象类中的抽象方法(有abstract修饰)不能用private、static、synchronized、native修饰,方法必须以分号结尾,并且不带花括号。
- 接口被用于实现比较常用的功能,便于日后的维护或者添加删除方法;抽象类倾向于充当公共类的角色,不适用于日后重新对立面的代码修改。
- 接口可以继承接口,抽象类可以实现接口,抽象类也可以继承具体类,抽象类也可以有静态的main方法。
4. 内部类
静态内部类
不依赖于外部类实例而被实例化,不能与外部类有相同的名字,不能访问外部类的普通成员变量,只能访问外部类中的静态成员和静态方法(包括私有类型)。
成员内部类
只有在外部的类被实例化后,这个内部类才能被实例化。非静态内部类中不能有静态的属性和方法。可以自由的引用外部类的属性和方法(无论静态or非静态)。
局部内部类
作用范围为其所在的代码块。和局部变量一样,不能被public、private、protected、static修饰,只能访问方法中定义为final类型的局部变量。
对于一个静态内部类,去掉static,将其定义移入外部类的静态方法或静态初始化代码段中就成为了局部静态内部类。局部静态内部类与静态内部类的基本特性相同。
对一个成员类,将其移入外部类的实例方法或实例初始化代码中就称为局部内部类。
匿名内部类
不能有构造函数;匿名内部类不能定义静态成员、方法和类;不能是public、private、protected、static。只能创建匿名内部类的一个实例。必须继承一个父类或实现一个接口。因为匿名内部类为局部内部类,所以对局部内部类的限制都对其生效。
5. 如何获取父类的类名
class A{}
public class Test extends A{
public void test(){
System.out.println(super.getClass().getName());
}
public static void main(String[] args){
new Test().test();
}
}
程序输出Test 为什么不是A呢?
由于getClass方法在Object类中被定义为final与native,子类不能覆盖,因此this.getClass()和super.getClass()最终调用的都是Object中的getClass方法。
而Object中的getClass方法释义是返回此Object的运行时类。由于上述实际运行时的类时Test而不是A,因此输出Test。
改为System.out.println(this.getClass().getSuperclass().getName());输出A
6. this与super区别
this指示当前实例对象。
super可以访问父类的方法或成员变量。当子类的方法或成员变量与父类有相同名字时会覆盖父类的方法或成员变量,要想访问父类的方法或成员变量可以通过super关键字。
注意:当子类构造函数需要显示调用父类的构造函数时,super()必须为构造函数中的第一条语句。