文章目录
抽象类与接口的区别
// 抽象类
abstract class Demo {
private int id;
private int name;
abstract void method1();
abstract void method2();
}
// 接口
interface Demo {
void method1();
void method2();
}
简单概括:
抽象类可以拥有自己的数据成员,即可有抽象方法、也可以有非抽象方法。
接口中只能有静态的、不被修改的成员,(也就是默认都是static final
的 )。所有方法都是抽象方法,但是在Java8 之后,可以定义 default
类型的方法有默认实现。
总结:
- 抽象类和接口都不能直接实例化,必须借助具体实现类进行实例化。
- 抽象类要被子类单继承,接口要被类可以多实现。
- 接口里定义的变量只能是公共的静态的常量(static final),抽象类中的变量是普通变量。
- 抽象类里可以没有抽象方法。
- 接口中没有 this 指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法。
- 抽象类不能在Java 8 的 lambda 表达式中使用。
单继承VS多继承问题
答:(菱形继承)反推
假设存在多继承,A类派生B,C , 则B包含自己的内容和继承过来的内容 a,b
,C包含自己的和继承的a,c
;如果多继承存在 D继承B和C,则D中会包含a,b
a ,c
此时a
被复制两份在同一个作用域中,造成繁琐。所以只能单一继承。
什么是多态
继承是多态得以实现的基础。从字面上理解,多态就是一种类型表现出多种状态。
多态有两种表现形式:
- 继承+重写(动多态)
- 重载(静多态)
重载VS重写
重载和重写都是针对方法的概念,在弄清楚这两个概念之前,我们先来了解一下什么叫方法的型构
(英文名是signature,有的译作“签名”,)。
型构
就是指方法的组成结构,具体包括方法的名称和参数,(参数的数量、类型以及出现的顺序,但是不包括方法的返回值类型,访问权限修饰符,以及abstract、static、final等修饰符)。
比如下面两个就是具有相同型构的方法:
public void method(int i, String s) {
}
public String method(int i, String s) {
}
//只有返回值不同,无法构成不同型构
重载
:在同一个类中,具有相同名称,但是型构不同的方法。
重写
: 在继承情况下,子类中覆盖父类中相同型构的方法,是实现多态必须的步骤。
重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常,譬如父类方法声明了一个检查异常 IOException,在重写这个方法时就不能抛出 Exception,只能抛出 IOException 的子类异常,可以抛出非检查异常。
总结
重载与重写是 Java 多态性的不同表现,重写是父类与子类之间多态性的表现,在运行时起作用(动态多态性,譬如实现动态绑定),而重载是一个类中多态性的表现,在编译时起作用(静态多态性,譬如实现静态绑定)
常见问题
问:Java 构造方法能否被重写和重载?
答:重写是子类方法重写父类的方法,重写的方法名不变,而类的构造方法名必须与类名一致,假设父类的构造方法如果能够被子类重写则子类类名必须与父类类名一致才行,所以 Java 的构造方法是不能被重写的
。而重载是针对同一个的,所以构造方法可以被重载
。
问:下面程序的运行结果是什么,为什么?
public class Demo
{
public boolea equals(Demo other)
{
System.out.println("use Demo equals.");
return true;
}
public static void main (string[] args){
Obeject o1 = new Demo(); //注意类型 Obeject
Obeject o2 = new Demo(); //注意类型 Obeject
Demo o3 = new Demo();
Demo o4 = new Demo();
if(o1.equels(o2)){
System.out.println("o1 is equal with o2");
}
if(o3.equels(o4)){
System.out.println("o3 is equal with o4");
}
}
答:上面程序的运行结果如下。
use Demo equals.
o3 is equal with o4.
因为 Demo 类中的public boolean equals(Demo other)
方法并没有重写 Object 类中的 public boolean equals(Object obj)
方法,原因是其违背了参数规则,其中一个是 Demo 类型而另一个是 Object 类型,因此这两个方法是重载关系(发生在编译时)而不是重写关系;
故当调用 o1.equals(o2)
时,实际上调用了 Object 类中的 public boolean equals(Object obj)
方法,而 Object 类的 equals 方法是通过比较内存地址才返回 false;
当调用 o3.equals(o4)
时,实际上调用了 Demo 类中的 equals(Demo other)
方法,,所以才有上面的打印。
为什么new子类要先执行父类构造方法
其实之前一直不明白构造函数“初始化”的含义。一直以为 “初始化”==“变量赋值”,其实初始化的内涵不是这样的。
class Person{…}
class Student extends Person{…}
对于Student stu = new Student(),更合理的解释是:
1, 加载class文件
5, 父类静态变量加载到方法区
6, 父类静态代码块初始化变量
7, 子类静态变量加载到方法区
8, 子类静态代码块初始化变量
2, 加载main方法在栈内存中(类被加载后,虚拟机再调用静态方法main。类加载会先走静态代码块,注定静态代码块 先于main)
3, 在栈内存中开辟空间给Student stu(对象引用的空间)
4, new申请堆内存空间(对象的空间)
9, 父类变量默认初始化、显示初始化(一般只有默认初始化,创建一个学生类时不会直接写age=20,这样以后每个创建的对象一开始就20岁)
10, 父类构造代码块初始化(如果有赋值语句)
11, 父类构造器初始化(如果有赋值语句)
12, 子类变量默认初始化、显示初始化(一般只有默认初始化,创建一个学生类时不会直接写age=20,这样以后每个创建的对象一开始就20岁)
13, 子类构造代码块初始化(如果有赋值语句)
14, 子类构造器初始化(如果有赋值语句)
最后,把对象地址赋值给stu
详见:
http://bbs.csdn.net/topics/380077372
http://www.jb51.net/article/37881.htm
http://bbs.csdn.net/topics/260013819
继承和多态中成员访问的特点总结
继承是站在子类的角度,多态是站在父类的角度。
继承时,子类为观察者,发起者。不论是成员变量还是成员方法,都是遵照就近原则。
子类变量会被优先调用。
而子类方法会覆盖父类方法。
所以,同名情况下,子类都先使用自己的。
多态中,父类为观察者,发起者。
父类自己的变量会优先调用,而且不存在调用子类变量的情况。因为,调用子类变量说明变量名不同,说明是子类后加上去的。父类中是没有get该变量的方法的!
父类方法会被子类方法覆盖。
所以同名曲情况下,父类使用自己的变量,使用子类的方法。
实际开发中,使用继承一般更多的是为了复用性,为了重写父类方法。很少涉及变量(子类自己的)。
使用多态一般更多的是为了可维护性,为了同一个引用调用不同子类的特有同名方法。很少涉及变量(父类自己的)。