一、什么是多态?
多态是指在程序中定义的引用变量所指向的具体类型和该引用变量发出的方法在程序编译时并不确定,而是在程序运行时才确定。多态的三个必要条件:继承、重写和向上转型。
继承:在多态中必须存在继承关系的子类和父类
重写:子类必须对父类的方法进行重写,在调用这些方法时才能调用子类的方法
向上转型(父类引用指向子类对象):需要将子类的引用赋给父类,这样引用才能即能调用父类方法又能调用子类方法。
Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但可具有不同的参数列表、返回值类型。调用方法时通过传递的参数类型来决定具体使用哪个方法,这就是多态性。
Java的方法重写,是父类与子类之间的多态性,子类可继承父类中的方法,但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。重写的参数列表和返回类型均不可修改。
public class Figure {
int a;
int b;
Figure(int a, int b){
this.a = a;
this.b = b;
}
int area() {
System.out.print("--父类的area--");
return 0;
}
}
public class Rectangle extends Figure {
Rectangle(int a, int b) {
super(a,b);
}
int area() {
System.out.print("--长方形的area--");
return super.a*super.b;
}
}
public class Triangle extends Figure {
Triangle(int a, int b) {
super(a,b);
}
int area() {
System.out.print("--三角形的area--");
return super.a*super.b/2;
}
}
public class Test {
public static void main(String[] args) {
Figure figure; // 声明Figure类的变量
figure = new Rectangle(9, 9);
System.out.println(figure.area());
System.out.println("===============================");
figure = new Triangle(6, 8);
System.out.println(figure.area());
System.out.println("===============================");
figure = new Figure(10, 10);
System.out.println(figure.area());
}
}
二、接口和抽象类的区别
抽象类:在JAVA中被abstract修饰的类为抽象类,被abstract修饰的方法为抽象方法,抽象方法只有方法的声明,没有方法的实现。抽象类不能被实例化只能被继承;包含抽象方法的一定是抽象类,抽象类不一定含有抽象方法;抽象类中抽象方法的修饰只能是public或protected,默认为public;一个子类继承一个抽象类,则子类必须实现父类的抽象方法,否则子类需定义为抽象类。
接口:在JAVA中使用interface关键字修饰。接口包含变量和方法,变量被隐式指定public static final,方法被隐式指定为public abstract;接口支持多继承,一个接口可以extends多个接口,间接解决了JAVA中的单继承问题。一个类可以实现多个接口。
相同点:都不能被实例化。接口的实现类和抽象类的子类只有实现了接口和抽象类的方法,才能被实例化。
不同点:1:接口只有定义,不能有方法的实现,抽象类可以方法的定义和实现。2:实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。3:接口强调特定功能的实现,而抽象类强调所属关系。4:接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
三、垃圾回收机制
垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。
注意:垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身。换言之,垃圾回收只会负责释放那些对象占有的内存。对象是个抽象的词,包括引用和其占据的内存空间。当对象没有任何引用时其占据的内存空间随即被收回备用,此时对象也就被销毁。但不能说是回收对象,可以理解为一种文字游戏。
发生时间:程序空闲时间不定时回收。
四、JAVA异常处理机制
异常处理机制:抛出异常,异常捕捉
抛出异常:一个方法不处理异常,而是把异常向上传递,谁调用这个这个方法,谁来处理这个异常。
throw: 将产生的异常进行抛出,这个异常可以是异常的引用也可以是异常本身,位置在方法体内。
throws:如果一个方法可能产生异常, 但是没有能力处理异常,可以在方法声明处通过throws抛出异常。用它修饰的方法向调用者表明该方法可能会产生异常(可能是一种,也可能是多种,用逗号隔开)。位置在方法写在方法名 或方法名列表之后 ,在方法体之前。
异常捕获:通常使用关键字try、catch、finally来捕获异常。
try:用于捕获异常,其后可跟零个或多个catch,如果没有catch,其后必须跟finally.
catch:用于处理try捕获到的异常。
finally:无论是否捕获或者处理异常,finally里的代码块都会被处理。当在try或catch中遇到return语句时,finally语句块将在方法返回之前执行。在以下4种特殊情况下,finally块不会被执行:1)在finally语句块中发生了异常。2)在前面的代码中用了System.exit()退出程序。3)程序所在的线程死亡。4)关闭CPU。
五、JAVA多线程
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源利用效率来提供系统的效率。线程是同一时间需要完成多项任务时实现的。一个进程中可以并发多个线程,每条线程并行执行不同的任务。
每一个线程都有优先级,这样有助于操作系统确定线程的调度顺序,JAVA线程的优先级是个整数,取值范围是1-10。默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
- 新建状态: 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态: 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度
- 运行状态: 如果就绪状态的线程获取 CPU 资源,就可以执行 run() ,此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态: 如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去了所使用资源之后,该线程就从运行状态变为阻塞状态,在睡眠时间已到或获取到设备资源后可以重新进入就绪状态。可以分为:
等待阻塞:运行状态中的线程执行wait方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取synchronized同步锁失败(同步锁被其他线程占用。
其他阻塞:通过调用线程的sleep()和join()方法发出I/O请求时,线程就会进入到阻塞状态。当sleep状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
创建线程: 1、通过实现Runnable接口
public class RunnableDemo implements Runnable {
private int name;
private RunnableDemo(int name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println("Thread时间:" + new Date() + "name:" + this.name);
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i=0 ; i<10 ; i++) {
Thread thread = new Thread(new RunnableDemo(i));
thread.start();
}
}
}
2、通过集成Thread类本身
public class ThreadDemo extends Thread {
private int name;
private ThreadDemo(int name) {
this.name = name;
}
@Override
public void run() {
try{
System.out.println("Thread:" + new Date() + "name:" + name);
Thread.sleep(10);
} catch (InterruptedException e){
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i=0 ; i<10 ; i++) {
ThreadDemo threadDemo = new ThreadDemo(i);
threadDemo.start();
}
}
}
3、通过Callable和Future创建
public class CallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 10 ; i++) {
System.out.println("Thread:" + Thread.currentThread().getName() + " " + i);
}
return i;
}
public static void main(String[] args) {
CallableDemo ctt = new CallableDemo();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 5;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==3)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
}
创建线程的三种方式比较:
-
- 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
-
- 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
六、线程池
1、newCachedThreadPool
可创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
通过调用Executor类的静态newCachedThreadPool()方法可以获取缓存的线程池。
public class CacheThreadPoolDemo {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i=0 ; i < 5 ; i++) {
int index = i;
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
SimpleDateFormat sm = new SimpleDateFormat("HH:mm:ss");
System.out.println("Thread" + index + ":运行时间:" + sm.format(new Date()));
System.out.println("Thread" + index + ":运行结束时间:" + sm.format(new Date()));
}
});
}
}
}
2、newFixedThreadPool
可创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程。
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
for (int i =0 ; i<5; i++) {
int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
SimpleDateFormat sm = new SimpleDateFormat("HH:mm:ss");
System.out.println("线程" + index + ":运行时间:" + sm.format(new Date()));
Thread.sleep(20);
System.out.println("线程" + index + ":结束时间:" + sm.format(new Date()));
}catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
fixedThreadPool.shutdown();
}
}
六、说一下死锁的原因,以及如何打破,如何查看死锁进程状态
死锁:是指多个线程同时被阻塞,它们中的一个或多个都在等待某个资源被释放,由于线程被无期限地阻塞,因此程序不可能正常终止。
JAVA死锁产生的四个条件:
- 互斥条件:资源不能被共享,只能被一个线程占有。
- 不可抢占条件:已经分配的资源不能从相应的进程中被强制剥夺。
- 请求与保持条件:已经得到资源的线程可以再次申请新的资源
- 循环等待条件:系统中若干进程形成环路,该环路中每个进程都在等待相邻进程所占用的资源。
上述四个条件都成立的时候,便形成了死锁。死锁的情况下,打破上述任何一个条件,便可打破死锁。
解决死锁的方法:一种是使用synchronized,一种是lock显式锁。如果不恰当的使用了锁,且同时要锁多个对象时,就会出现死锁的情况。
加锁:
public class LockTest implements Runnable{
static int count = 0;
private Lock lock;
private LockTest(Lock lock) {
this.lock = lock;
}
public static void main(String[] args) {
Lock lock = new ReentrantLock();
for (int i=0 ; i<100 ; i++) {
Thread thread = new Thread(new LockTest(lock));
thread.start();
}
}
public void run() {
try {
lock.lock();
count++;
System.out.println(count);
}finally {
lock.unlock();
}
}
}
不加锁:
public class UnLockTest implements Runnable{
static int count = 0;
public void run() {
count++;
System.out.println(count);
}
public static void main(String[] args) {
for (int i=0 ; i<100 ; i++) {
Thread thread = new Thread(new UnLockTest());
thread.start();
}
}
}
七、JVM内存机制
八、volatile
九、Override, Overload的含义与区别
1、Override(重写)
重写是子类对于父类允许访问的方法的实现过程进行重写编码,返回值和形参都不能改变,即外壳不变,核心重写。子类能够根据自己的需要实现父类的方法。
重写的规则:
- 参数列表与被重写的方法的参数列表必须完全相同;
- 返回类型可以与被重写方法的返回类型不一样,但必须是父类返回值的派生类;
- 访问权限不能比父类被重写的方法低;
- 父类的成员方法只能被子类重写;
- 声明为final方法不能被重写;
- 声明为static的方法不能被重写,但是能被再次声明;
- 子类和父类在同一个包中,子类可以重写父类的所有方法,除了声明为private和final的方法;
- 子类和父类不在同一个包中,那么子类只能重写父类中声明为public和protected的非final方法;
- 如果不能继承一个类,则不能重写该类的方法;
- 构造方法不能被重写;
- 重写的方法能够抛出任何非强制异常,无论被重写的方法抛出任何异常。但是重写的方法不能抛出新的强制性异常,或者比被重写的方法声明的更广泛的强制性异常,反之则可以。
2、Overload(重载)
重载是在一个类里边,方法名相同参数不同,返回类型可以相同也可以不相同。
重载规则:
- 被重载的方法必须改变参数列表(参数类型或参数个数不同);
- 被重载的方法可以改变返回值类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法可以在一个类中或者子类中被重载;
- 无法以返回值类型作为重载函数的区分标准。
十、静态内部类与非静态内部类的区别
内部类:在一个类的内部又定义了一个类,这个类就称之为内部类。
内部类有以下几个特点:
- 内部类可以访问其所在类的属性(包括所在类的私有属性),内部类创建自身对象需要先创建其所在类的对象;World world = new Solution().new World();
- 可以定义内部接口,且可以定义另外一个内部类实现这个内部接口;
- 可以在方法体内定义一个内部类,方法体内的内部类可以完成一个基于虚方法形式的回调操作;
- 可以在方法体内定义一个内部类,方法体内的内部类可以完成一个基于虚方法形式的回调操作;
- 内部类中不能定义static的方法和变量,允许static常量;
- 内部类可以多嵌套,内部类再定义个内部类。
静态内部类:静态内部类是内部类中比较特殊的一种情况。一旦内部类使用static修饰,那么此时这个内部类就升级为顶级类。
- 静态内部类只能访问外部类的静态方法和静态属性(如果是private也能访问);
- 静态内部类是可以独立存在的,即使外部类消亡了,静态内部类还是可以存在的;
- 静态内部类的方法可以是静态的方法也可以是非静态方法。
- 静态内部类的创建不需要先创建其所在类的对象Hello hello = new Solution.Hello(); hello.hello();
十一、class和interface的区别
- 接口类似于类,但接口的成员都没有执行方法。接口中不能有字段;
- 不能实例化一个一个接口,接口只包含一个成员的签名,而类可以实例化(abstract类除外);
- 接口没有构造函数,类中有;
- 接口不能进行运算符的重载,类可以;
- 接口的成员没有任何修饰符,其成员都是公共的,而类的成员则可以有修饰符。
- 派生于接口的类必须实现接口中所有成员方法,而从类派生则不然。
十二、Abstract class 与interface的区别
- 实例化:Abstract class不能实例化,接口也不能实例化
- 继承:一个抽象类可以继承一个类或实现多个接口,子类只能继承一个抽象类;接口只能继承接口(一个或多个),子类可以实现多个接口。
- 访问修饰符:抽象方法可以有public,protected和default这些修饰符,接口默认的修饰符为public,你不可以使用其他修饰符。
- 方法实现:可定义构造方法,也可以定义抽象方法和具体方法,接口完全是抽象的,没构造方法,切方法都是抽象的,不存在方法的实现。
- 实现方式:子类使用extends来继承抽象类,如果子类不是抽象类时,它需要提供抽象类中所有声明方法的实现。子类使用implement来实现接口,它需要实现接口中所有生命的方法。
- 作用:抽象类是把相同的东西提取出来,即重用。接口是把程序模块进行固化,为了降低耦合。
十二、Java里内存泄漏和溢出的区别
内存溢出:简单的说就是内存不够用了 内存泄漏:一个对象分配内存之后,在使用结束时未及时释放,导致一直占用内存,没有及时清理,使实际可用内存减少,就好像内存泄露了一样。
常见的内存泄漏:
- 单例造成的内存泄漏:由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
- 非静态内部类创建静态实例造成的内存泄漏:非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,从而导致Activity的内存资源不能被正常回收。
- Handler造成的内存泄漏
- 线程造成的内存泄漏:如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
- 资源未关闭造成的内存泄漏:对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。
- 使用ListView时造成的内存泄漏
- 集合容器中的内存泄露
- WebView造成的泄露
十三、Java里Integer和int的区别,以及如何比较相等
int是基本数据类型,Integer是引用数据类型;int默认是0,Integer默认是null;int类型直接存储数值,Integer需要实例化对象,指向对象的地址。
JAVA能自动拆箱和自动装箱。自动拆箱:将引用数据类型转换为基本数据类型。自动装箱:将基本数据类型转换为引用数据类型。
Equals通常用来比较两个对象的内容是否相等,==用来比较两个对象的地址是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类。
那么基本数据类型和引用数据类型什么时候相等:
- 都是封装类,都是new出来的,肯定不相等,因为指向地址不一样;
- 都是封装类,都不是new的,如果值在-128-127之间那就相等,否则不相等;Java在进行编译时 Integer g = 130会被编成Integer.valueOf(130),Integer.valueOf(130)会在-128~127之间对Integer进行缓存,不会重新new一个对象,当小于-128或大于127时会重新new一个。
- 如果基本类型和封装类进行比较,如果值相等那就相等,否则不相等,因为基本类型和封装类进行比较时,会有一个自动拆箱的功能;
- 都是基本数据类型,如果相等那就相等。 基本数据类型存于栈中,包装类型存在于堆栈中,堆中存地址,栈中存内容。当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这时就需要这些基本类型的包装器类了
十四、List和ArrayList的区别,以及arrayList和HashSet区别
- List是一个接口,而ArrayList是List接口的一个实现类;
- ArrayList是继承AbstractList抽象类和实现List接口的一个实现类;
- List不能被创建实例化对象,可以为List创建一个指向它的对象引用,而ArrayList实现类的实例对象就在这充当了指向List接口的对象引用。
-
List<String> a = new ArrayList<String>(); - HashSet不能存储相等的元素,元素是否相等的判断:重写元素的equals方法,equals方法必须和hashCode方法兼容如:equals方法判断的是用户的名字name,那么hashCode的返回的hashcode必须是name。hashcode();
- HashSet存储是无序的,保存顺序和添加顺序是不一致的,不是线性的。
- HashSet不是线程安全的,不是线程同步的。这需要自己实现线程同步:Collections.synchronizedCollection(),方法实现。
- ArrayList中存放顺序和添加顺序是一致的。并且可重复元素。
- 不是线程安全的,不是线程同步的。
- ArrayList是通过可变大小的数组实现的,允许null在内的所有元素。
- ArrayList适合通过位子来读取元素。
十五、LinkedHashMap和HashMap的比较使用
一 般情况下,我们用的最多的是HashMap,在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列
十六、常用的设计模式
十七、JSON使用
1、JSON.toJSONString(): 将 JSON 对象或 JSON 数组转化为字符串 JSON.parseArray():从字符串解析 JSON 数组 JSON.parseObject():从字符串解析 JSON 对象
十八、一个文件会有多个类吗
可以有多个类,但是只有一个public类,且public类名必须和文件名一样