Java 初级面试题及答案.docx
《Java 初级面试题及答案.docx》由会员分享,可在线阅读,更多相关《Java 初级面试题及答案.docx(11页珍藏版)》请在冰豆网上搜索。
Java初级面试题及答案
Java初级面试题及答案
1、Java中的重载与重写有什么区别
重载(Overload)是让类以统一的方式处理不同类型数据的一种手段,实质表现就是多个具有不同的参数个数或者类型的同名函数(返回值类型可随意,不能以返回类型作为重载函数的区分标准)同时存在于同一个类中,是一个类中多态性的一种表现(调用方法时通过传递不同参数个数和参数类型来决定具体使用哪个方法的多态性)。
重写(Override)是父类与子类之间的多态性,实质是对父类的函数进行重新定义,如果在子类中定义某方法与其父类有相同的名称和参数则该方法被重写,不过子类函数的访问修饰权限不能小于父类的;若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,如需父类中原有的方法则可使用super关键字。
重载:
必须具有不同的参数列表;
可以有不同的返回类型;
可以有不同的访问修饰符;
可以抛出不同的异常。
重写:
参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载;返回类型必须一直与被重写的方法相同,否则不能称其为重写而是重载;访问修饰符的限制一定要大于等于被重写方法的访问修饰符;
重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。
重载与重写是Java多态性的不同表现,重写是父类与子类之间多态性的表现,在运行时起作用(动态多态性,譬如实现动态绑定),而重载是一个类中多态性的表现,在编译时起作用(静态多态性,譬如实现静态绑定)。
2、Java中final、finally、finalize的区别
final是一个修饰符,如果一个类被声明为final则其不能再派生出新的子类,所以一个类不能既被声明为abstract又被声明为final的;将变量或方法声明为final可以保证它们在使用中不被改变(对于对象变量来说其引用不可变,即不能再指向其他的对象,但是对象的值可变),被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改,被声明为final的方法也同样只能使用不能重载。
使用final关键字如果编译器能够在编译阶段确定某变量的值则编译器就会把该变量当做编译期常量来使用,如果需要在运行时确定(譬如方法调用)则编译器就不会优化相关代码;将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计并进行优化;接口中的变量都是publicstaticfinal的。
finally用来在异常处理时提供块来执行任何清除操作,如果抛出一个异常,则相匹配的catch子句就会执行,然后控制就会进入finally块。
finalize是一个方法名,Java允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作,这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,它是在Object类中定义的,因此所有的类都继承了它,子类覆盖finalize()方法以整理系统资源或者执行其他清理工作,finalize()方法在垃圾收集器删除对象之前对这个对象调用的。
3、Java中hashCode()的作用
hashCode()的作用是为了提高在散列结构存储中查找的效率,在线性表中没有作用;
只有每个对象的hash码尽可能不同才能保证散列的存取性能,事实上Object类提供的默认实现确实保证每个对象的hash码不同(在对象的内存地址基础上经过特定算法返回一个hash码)。
在Java有些集合类(HashSet)中要想保证元素不重复可以在每增加一个元素就通过对象的equals方法比较一次,那么当元素很多时后添加到集合中的元素比较的次数就非常多了,会大大降低效率。
于是Java采用了哈希表的原理,这样当集合要添加新的元素时会先调用这个元素的hashCode方法就一下子能定位到它应该放置的物理位置上(实际可能并不是),如果这个位置上没有元素则它就可以直接存储在这个位置上而不用再进行任何比较了,如果这个位置上已经有元素了则就调用它的equals方法与新元素进行比较,相同的话就不存,不相同就散列其它的地址,这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次,而hashCode的值对于每个对象实例来说是一个固定值。
4、抽象类(abstractclass)和接口(interface)有什么区别
含有abstract修饰符的class为抽象类,abstract类不能创建实例对象,含有abstract方法的类必须定义为abstractclass,abstractclass类中的方法不必是抽象的。
abstractclass类中定义的抽象方法必须在具体的子类中实现,所以不能有抽象构造方法或抽象静态方法,如果子类没有实现抽象父类中的所有抽象方法则子类也必须定义为abstract类型。
对于接口可以说是抽象类的一种特例,接口中的所有方法都必须是抽象的(接口中的方法定义默认为publicabstract类型,接口中的成员变量类型默认为publicstaticfinal)。
具体的区别如下:
抽象类可以有构造方法;接口中不能有构造方法。
抽象类中可以有普通成员变量或者常量或者静态变量;接口中没有普通成员变量和静态变量,只能是常量(默认修饰符为publcistaticfinal)。
抽象类中可以包含非抽象的普通方法和抽象方法及静态方法;接口中的所有方法必须都是抽象的,不能有非抽象的普通方法和静态方法(默认修饰符为publicabstract)。
抽象类中的抽象方法访问类型可以是public、protected的;接口中的抽象方法只能是public的(默认修饰符为publicabstract)。
一个子类可以实现多个接口,但只能继承一个抽象类。
5、为什么ArrayList的增加或删除操作相对来说效率比较低
ArrayList在小于扩容容量的情况下其实增加操作效率是非常高的,在涉及扩容的情况下添加操作效率确实低,删除操作需要移位拷贝,效率是低点。
因为ArrayList中增加(扩容)或者是删除元素要调用System.arrayCopy这种效率很低的方法进行处理,所以如果遇到了数据量略大且需要频繁插入或删除的操作效率就比较低了,具体可查看ArrayList的add和remove方法实现,但是ArrayList频繁访问元素的效率是非常高的,因此遇到类似场景我们应该尽可能使用LinkedList进行替代效率会高一些。
6、LinkedList工作原理和实现
LinkedList是以双向链表实现,链表无容量限制(但是双向链表本身需要消耗额外的链表指针空间来操作),其内部主要成员为first和last两个Node节点,在每次修改列表时用来指引当前双向链表的首尾部位。
所以LinkedList不仅仅实现了List接口,还实现了Deque双端队列接口(该接口是Queue队列的子接口),故LinkedList自动具备双端队列的特性,当我们使用下标方式调用列表的get(index)、set(index,e)方法时需要遍历链表将指针移动到位进行访问(会判断index是否大于链表长度的一半决定是首部遍历还是尾部遍历,访问的复杂度为O(N/2)),无法像ArrayList那样进行随机访问。
(如果i>数组大小的一半,会从末尾移起),只有在链表两头的操作(譬如add()、addFirst()、removeLast()或用在iterator()上的remove()操作)才不需要进行遍历寻找定位。
具体感兴趣可以去看下LinkedList的源码。
7、介绍HashMap的底层原理
所以HashMap的数据结构是数组和链表的结合,此外HashMap中key和value都允许为null,key为null的键值对永远都放在以table[0]为头结点的链表中。
之所以HashMap这么设计的实质是由于数组存储区间是连续的,占用内存严重,故空间复杂度大,但二分查找时间复杂度小(O
(1)),所以寻址容易而插入和删除困难;而链表存储区间离散,占用内存比较宽松,故空间复杂度小,但时间复杂度大(O(N)),所以寻址困难而插入和删除容易;
所以就产生了一种新的数据结构叫做哈希表,哈希表既满足数据的查找方便,同时不占用太多的内容空间,使用也十分方便,哈希表有多种不同的实现方法,HashMap采用的是链表的数组实现方式。
对于JDK1.8开始HashMap实现原理变成了数组+链表+红黑树的结构,数组链表部分基本不变,红黑树是为了解决哈希碰撞后链表索引效率的问题,所以在JDK1.8中当链表的节点大于8个时就会将链表变为红黑树。
区别是JDK1.8以前碰撞节点会在链表头部插入,而JDK1.8开始碰撞节点会在链表尾部插入,对于扩容操作后的节点转移JDK1.8以前转移前后链表顺序会倒置,而JDK1.8中依然保持原序。
8、Hashtable与HashMap的区别
Hashtable算是一个过时的集合类,因为JDK1.5中提供的ConcurrentHashMap是HashTable的替代品,其扩展性比HashTable更好。
由于HashMap和Hashtable都实现了Map接口,所以其主要的区别如下:
HashMap是非synchronized的,而Hashtable是synchronized的。
HashMap可以接受null的键和值,而Hashtable的key与value均不能为null值。
HashMap的迭代器Iterator是fail-fast机制的,而Hashtable的Enumerator迭代器不是fail-fast机制的(历史原因)。
单线程情况下使用HashMap性能要比Hashtable好,因为HashMap是没有同步操作的。
Hashtable继承自Dictionary类且实现了Map接口,而HashMap继承自AbstractMap类且实现了Map接口。
HashTable的默认容量为11,而HashMap为16(安卓中为4)。
Hashtable不要求底层数组的容量一定是2的整数次幂,而HashMap则要求一定为2的整数次幂。
Hashtable扩容时将容量变为原来的2倍加1,而HashMap扩容时将容量变为原来的2倍。
Hashtable有contains方法,而HashMap有containsKey和containsValue方法。
9、java类加载器的理解及加载机制
通过java命令运行java程序的步骤就是指定包含main方法的完整类名以及一个classpath类路径,类路径可以有多个,对于直接的class文件路径就是class文件的根目录,对于jar包文件路径是jar包的完整路径,包含jar包名字;
java运行时会根据类的完全限定名寻找并加载,寻找的方式基本就是在系统类和指定的路径中寻找,如果是class文件的根目录则直接查看是否有对应的子目录及文件,如果是jar包则首先在内存中解压文件,然后再查看是否有对应的类;负责类加载的类就是ClassLoader类加载器,它的输入是完全限定的类名,输出是Class对象,java虚拟机中可以安装多个类加载器,系统默认主要有三个类加载器,每个类负责加载特定位置的类,也可以自定义类加载器,自定义的加载器必须继承ClassLoader,如下:
启动类加载器(BootstrapClassLoader):
此加载器为虚拟机实现的一部分,不是java语言上层实现的,一般为C++实现,主要负责加载java基础类(譬如/lib/rt.jar,常用的String、List等都位于此包下),启动类加载器无法被java程序直接引用。
扩展类加载器(ExtensionClassLoader):
此加载器实现类为sun.misc.Launcher$ExtClassLoader,负责加载java的一些扩展类(一般为/lib/ext目录下的jar包),开发者可直接使用。
应用程序类加载器(ApplicationClassLoader):
此加载器实现类为sun.misc.Launcher$AppClassLoader,负责加载应用程序的类,包括自己写的和引用的第三方类库,即classpath类路径中指定的类,开发者可直接使用,一个程序运行时会创建一个这个加载器,程序中用到加载器的地方如果没有特殊指定一般都是这个加载器,所以也被称为System系统类加载器。
这三个加载器具备父子委派关系(非继承父子关系),在java中每个类都是由某个类加载器的实体来载入的,所以在Class类的实体中都会有字段记录载入它的类加载器的实体(当为null时,其指BootstrapClassLoader),在java类加载器中除了引导类加载器(既BootstrapClassLoader)。
所有的类加载器都有一个父类加载器(因为他们本身自己就是java类),子ClassLoader有一个变量parent指向父ClassLoader,在子ClassLoader加载类时一般会先通过父ClassLoader加载,所以在加载一个class文件时首先会判断是否已经加载过了,加载过则直接返回Class对象(一个类只会被一个ClassLoader加载一次)。
没加载过则先让父ClassLoader去加载,如果加载成功返回得到的Class对象,父没有加载成功则尝试自己加载,自己加载不成功则抛出ClassNotFoundException,整个加载流程就是双亲委派模型,即优先让父ClassLoader加载;双亲委派可以从优先级的策略上避免Java类库被覆盖的问题。
例如类java.long.Object存放在rt.jar中,无论哪个类加载器要加载这个类最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类,相反如果我们自己写了一个类名为java.long.Object且放在了程序的classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,所以一般遵循双亲委派的加载器就不会存在这个问题。
类加载机制中的双亲委派模型只是一般情况下的机制,有些时候我们可以自定义加载顺序(不建议)就不用遵守双亲委派模型了,同时以java开头的类也不能被自定义类加载器加载,这是java安全机制保证的;
ClassLoader一般是系统提供的,不需要自己实现,不过通过自定义ClassLoader可以实现一些灵活强大的功能,譬如热部署(不重启Java程序的情况下动态替换类实现)、应用的模块化和隔离化(不同ClassLoader可以加载相同的类,但是互相隔离互不影响,tomcat就是利用这个特性管理多web应用的)、灵活加载等,通过自定义类加载器我们可以加载其它位置的类或jar,自定义类加载器主要步骤为继承java.lang.ClassLoader然后重写父类的findClass方法。
之所以一般只重写这一个方法是因为JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时loadClass方法会主动调用findClass方法来搜索类,所以我们只需重写该方法即可,如没有特殊的要求,一般不建议重写loadClass搜索类的算法。
JVM在判定两个Class是否相同时不仅会判断两个类名是否相同而且会判断是否由同一个类加载器实例加载的,只有两者同时满足的情况下JVM才认为这两个Class是相同的,就算两个Class是同一份class字节码文件,如果被两个不同的ClassLoader实例所加载JVM也会认为它们是两个不同Class。
而对于JVM来说它们是两个不同的实例对象,但它们确实是同一份字节码文件,当试图将这个Class实例生成具体的对象进行转换时就会抛运行时异常java.lang.ClassCaseException提示这是两个不同的类型。
此外一个ClassLoader创建时如果没有指定parent则parent默认就是AppClassLoader。
10、Java中sleep()与wait()方法的区别
sleep()方法使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用,目的是不让当前线程独自霸占该进程所获的CPU资源。
该方法是Thread类的静态方法,当在一个synchronized块中调用sleep()方法时,线程虽然休眠了,但是其占用的锁并没有被释放;当sleep()休眠时间期满后,该线程不一定会立即执行,因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
wait()方法是Object类的,当一个线程执行到wait()方法时就进入到一个和该对象相关的等待池中,同时释放对象的锁(对于wait(longtimeout)方法来说是暂时释放锁,因为超时时间到后还需要返还对象锁),其他线程可以访问。
wait()使用notify()或notifyAll()或者指定睡眠时间来唤醒当前等待池中的线程。
wait()必须放在synchronized块中使用,否则会在运行时抛出IllegalMonitorStateException异常。
由此可以看出它们之间的区别如下:
sleep()不释放同步锁,wait()释放同步锁。
sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间没到则只能调用interreput()方法来强行打断(不建议,会抛出InterruptedException),而wait()可以用notify()直接唤起。
sleep()是Thread的静态方法,而wait()是Object的方法。
wait()、notify()、notifyAll()方法只能在同步控制方法或者同步控制块里面使用,而sleep()方法可以在任何地方使用。
11、对ClassLoader的理解
ClassLoader的作用是根据一个指定的类名称找到或者生成其对应的字节代码,然后把字节码转换成一个Java类(即java.lang.Class实例),除此之外还负责加载Java应用所需的资源、Nativelib库等。
Java的类加载器大致可以分成系统类加载器和应用开发自定义类加载器。
系统类加载器主要有如下几个:
引导类加载器(bootstrapclassloader):
用来加载Java核心库,是虚拟机中用原生代码实现的,没有继承自ClassLoader。
扩展类加载器(extensionsclassloader):
用来加载Java的扩展库,虚拟机的实现会提供一个默认的扩展库目录,该类加载器在此目录里面查找并加载Java类。
系统类加载器(systemclassloader):
用来加载应用类路径(CLASSPATH)下的class,一般来说Java应用的类都是由它来完成加载的,可以通过ClassLoader.getSystemClassLoader()来获取它。
除了引导类加载器之外,所有的其他类加载器都有一个父类加载器(可以通过ClassLoader的getParent()方法得到)。
系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器。
开发自定义的类加载器的父类加载器是加载此类加载器的Java类的类加载器。
所以类加载器在尝试自己去加载某个类时会先通过getParent()代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推,从而形成了双亲委派模式。
类加载机制是通过loadClass方法触发的,查找类有没有被加载和该代理给哪个层级的加载器加载是由findClass方法实现的,而真正完成类加载工作是defineClass方法实现的。
12、ArrayList和Vector有何异同点?
ArrayList和Vector在很多时候都很类似。
(1)两者都是基于索引的,内部由一个数组支持。
(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。
(3)ArrayList和Vector的迭代器实现都是fail-fast的。
(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。
以下是ArrayList和Vector的不同点。
(1)Vector是同步的,而ArrayList不是。
然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因为有同步,不会过载。
(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
13、SpringMVC运行原理
客户端请求提交到DispatcherServlet
由DispatcherServlet控制器查询HandlerMapping,找到并分发到指定的Controller中。
Controller调用业务逻辑处理后,返回ModelAndView
DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
视图负责将结果显示到客户端
14、说说熟悉的排序算法(很可能手写伪代码)
基数排序
堆排序
归并排序
选择排序
拓扑排序之Java详解
希尔排序
直接插入排序
快速排序
冒泡排序