JAVA中接口与抽象类的区别
要知道接口与抽象类的区别,首先我们得先知道这两玩意到底是啥。
抽象类是一种专门用来当做父类的类,作用类似于我们所说的“模板”,用关键字abstract声明,其目的是要设计者一句它的格式来修改并创建新的类。但是抽象类并不能创建对象,只能通过抽象类派生出新的类(子类),再由它来创建对象。 抽象类的使用中存在单继承的限制,即一个子类只能继承一个抽象类。
抽象类的使用规则(重要):
- 包含抽象方法的类必须是抽象类。
- 抽象类和抽象方法都必须用“abstract”声明。
- 抽象方法只需要声明,不需要实现。(这句话有点抽象,下面会解释)
- 抽象类必须被继承,子类(如果不是抽象类)必须覆写抽象类中的全部抽象方法。
现在解释一下上面所说的第三条规则“抽象方法只需要声明,不需要实现。”
//实现是针对其子类:例如
public abstract class Father{
//show方法在父类中只申明,不实现
public abstract void show();
}
public class Child extends Father{
//实现
public void show{
System.out.println("show method!");
}
}
你看父类的方法
public abstract void show();
只是做了声明,并没有方法体。
在其子类中:
public void show{
System.out.println(“show method!”);
}
实现了方法内部功能,这里只是输出一句话,当然你可以做其他功能。
(这个例子引用自:百度知道)
接口也被理解为是JAVA中的一种特殊的类,用interface声明,是由全局常量和公共方法所组成。需要注意的是,在接口中定义的抽象方法必须是public访问权限,这一点不允许改变。(如果接口方法中省略了public,它的访问权限依然是public,不是default,这一点需要注意。)与抽象类一样,接口若要使用也必须通过子类,子类通过implements关键字来实现接口。
很关键的一点是一个子类可以同时实现多个接口,这就摆脱了JAVA中单继承的局限。
抽象类与接口的区别汇总:
序号 | 区别点 | 抽象类 | 接口 |
---|---|---|---|
1 | 定义 | 包含一个抽象方法的类 | 抽象方法和全局常量的集合 |
2 | 组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法 |
3 | 使用 | 子类继承抽象类(extends) | 子类实现接口(implement) |
4 | 关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
5 | 常见设计模式 | 模板设计 | 工厂设计、代理设计 |
6 | 对象 | 都通过对象的多态性产生实例化对象 | 都通过对象的多态性产生实例化对象 |
7 | 局限 | 抽象类有单继承的局限 | 接口没有单继承的局限 |
8 | 实际 | 作为一个模板 | 作为一个标准或表示一种能力 |
另外两点:在选择上,如果抽象类和接口都可以被使用,则优先使用接口,避免单继承的局限;特殊的,一个抽象类中可以包含多个接口,一个接口中可以包含多个抽象类。
(上面来自《Java开发实战经典》(李兴华著)与峰哥面经)
简述JVM的内存布局
JVM将内存分为以下若干部分:
-
程序计数器:是一块较小的内存空间,是当前线程在执行的字节码的行号指示器,为线程私有,每个线程都有一个程序计数器,线程之间的程序计数器相互独立,互不干扰。如果线程正在执行的是一个JAVA方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器为空(Undefined)。此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError的区域。
-
Java虚拟机栈:与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同(随线程而生,随线程而死)。
Java虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于储存局部变量表、操作数栈、动态链接、方法出口等信息。每一个从调用直至执行的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
有人把Java内存区分为堆内存(Heap)和栈内存(Stack),虽然这种分发比较粗糙,但是这里的栈内存就是现在指的Java虚拟机栈。
在Java虚拟机规范中,对这个区域规定了两种异常状况:1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;2、如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可以动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。 -
本地方法栈:本地方法栈的作用与虚拟机栈非常类似,他们之间的区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈为虚拟机使用到的Native方法服务,JVM规范并未定义如何实现该区域,但是,在Hotspot JVM中,本地方法栈和JVM栈合二为一,所以,本地方法栈同样会抛出StackOverflowError和OutOfMemoryError。
-
Java堆(Heap):一般来说,这是Java虚拟机所管理的内存中最大的一块。并且Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域存在的唯一目的就是存放对象实例,几乎所有内存实例都在这里分配内存。比如Map<String,String> map = new HashMap<>(); 这个map所引用的对象即位于JVM的堆中。
特点:
1)内存不一定连续分配,只要逻辑上是连续的即可;
2)内存的大小可以通过JVM的参数来控制:如 -Xms = 1024M -Xmx = 2048M; 表示JVM Heap的初始大小为 1GB,最大可自动伸缩到2GB;
3)平时所说的Java内存管理即指的是内存管理器对这部分内存的管理(创建/回收);
4)所有线程共享。
从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代、老年代、永久代。 -
方法区:与Java堆一样,方法区是各个线程共享的区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在Hotspot JVM中,设计者使用“永久代”来实现方法区,这样带来的好处是,JVM可以不用再特别的写代码来管理方法区的内存,而可以像管理JVM 堆一样来管理。带来的弊端在于,这很容易造成内存溢出的问题。我们知道,JVM的永久代使用 --XX:MAXPermSize来设定永久代的内存上限。
值得一提的是,运行时常量池也属于方法区的一部分,所以,它的大小是受到方法区的限制的。运行期间也可以将新的常量放入池中,运用的比较多的就是String的intern()方法。 -
直接内存:即堆外内存,堆外内存能减少IO时的内存复制,
优点:减少了垃圾回收的工作,加快了复制速度;
缺点:堆外内存难以控制,如果造成泄漏,很难排查,而且不适合存储复杂的对象。
上述许多知识引用自博客。
Java对象创建的过程
共有以下6个步骤:
- 检查这个类是否被加载、解析和初始化过;
- 检查通过后,虚拟机为新对象分配内存(一般有两种分配方式:指针碰撞和空闲列表);
- 将分配到的内存空间都初始化为零值(不包括对象头);
- 设置对象头(这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的hash码、对象的GC分代年龄等信息);
- 执行<init>方法,把对象按照程序员的意愿进行初始化。
对象的内存布局
对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对其填充(Padding)。
对象的访问定位一般有两种方式:使用句柄和直接指针。
Java重载和重写的区别
这篇文章写得很好,我就直接搬过来了。
重写(Override)
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
方法的重写规则:
- 参数列表必须完全与被重写方法的相同;
- 返回类型必须完全与被重写方法的返回类型相同;
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
- 父类的成员方法只能被它的子类重写。
- 声明为final的方法不能被重写。
- 声明为static的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个方法,则不能重写这个方法。
重载(Overload)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
重载规则:
- 被重载的方法必须改变参数列表(参数个数或类型或顺序不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准