天天看点

【面试】JAVA程序员面霸之初级知识

该系列文章也是来自于一篇CSDN的gitchat,将其中的答案补全,本篇是最简单的初级知识。

1,面向对象和面向过程的区别和联系。

    网上有个盖浇饭和蛋炒饭的例子比较好。

    面向过程是蛋炒饭,混在一起,一个一个炒。

    面向对象是盖浇饭,饭和菜分开,想要换掉饭或者菜都可以,需要啥加啥,需要啥方法,或者啥其他对象就加上,灵活性高,也更抽象。

    面向对象是高度抽象化,面向过程是一种自顶向下的过程。

2,对象和类的关系。

    对象是类的一个具体。它是一个实实在在存在的东西。

    对象是有真实的存储空间的,类没有,是抽象的。

3,Java的内存布局是怎样的。另外一篇文章有

    JVM运行的时候会自动分配方法区和堆,然后每遇到一个线程,为之分配程序计数器、虚拟机栈、本地方法栈,终止时,后面三个也会释放

    (1)方法区:常量、静态变量、类的方法代码(变量名,方法名,访问权限,返回值等等)

    (2)堆:唯一一个程序员可以管理的内存区域。几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作

    (3)程序计数器: 当前线程执行的字节码的位置指示器。

    (4)虚拟机栈(栈内存):保存局部变量、基本数据类型变量以及堆内存中某个对象的引用变量

    (5)本地方法栈 :为JVM提供使用native 方法的服务

4,Java中的工作内存之间是怎样进行通信的。

    JAVA每一个线程都有自己的工作内存,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。

    线程间变量值的传递均需要在主内存来完成。

    主内存与工作内存之间的具体交互协议,有八种操作:锁定、解锁、读取、载入、使用、赋值、存储、写入

    JMM对上述的交互指令有约束。总之,通过主内存进行线程通信与交互。

5,堆和栈是什么关系,主要放什么东西。

    栈:一些基本类型的变量和对象的引用变量;超过变量的作用域后,被JAVA释放。

    堆:new创建的对象和数组;由Java虚拟机的自动垃圾回收器来管理

6,Java中安全机制是什么。

    (1)类装载器,对Java的沙箱起作用:防止恶意代码区干涉善意的代码,守护了被信任的代码的边界,将代码归于某类(称为保护域),该类确定了代码可以进行哪种操作

    (2)Class文件检查器:保证程序的健壮性。包括Class文件的结构检查、类型数据的语义检查、字节码验证、符号引用的验证。

    (3)内置的安全特性:类型安全的引用转换;结构化的内存访问(无指针算法);自动垃圾手机(不必显式地释放被分配的内存);数组边界检查;空引用检查。

7,String类的修饰符是什么,为什么是它。

    final。为了“效率” 和 “安全性” 的缘故。若 String允许被继承, 由于它的高度被使用率, 可能会降低程序的性能

    这种类是非常底层的,和操作系统交流频繁的,调用的操作系统本地的API,这就是著名的“本地方法调用”。

    那么如果这种类可以被继承的话,

    如果我们再把它的方法重写了,往操作系统内部写入一段具有恶意攻击性质的代码什么的,这不就成了核心病毒了么? 

8,重写和重载的区别和意义。

    (1)都是JAVA多态性的不同表现。重写体现的是子类与父类之间的多态性表现。重载体现的是一个类中多态性一种表现。

    (2)区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

        重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;

        重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。

        重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

    (3)重写多态性起作用,对调用被重载过的方法可以大大减少代码的输入量,同一个方法名只要往里面传递不同的参数就可以拥有不同的功能或返回值。

        重写,使用多态是为了避免在父类里大量重载引起代码臃肿且难于维护

9,Final、Finally和Finalize的区别。

    (1)final修饰类,不能被继承。

        修饰方法,若父类中final方法的访问权限为private,将导致子类中不能直接继承该方法

        因此,此时可以在子类中定义相同方法名的函数,此时不会与重写final的矛盾,而是在子类中重新地定义了新方法。

        final成员变量表示常量,只能被赋值一次,赋值后其值不再改变。

    (2)finally,只有try执行完毕才会执行,如果还没执行就异常了不会执行try,更不会执行finally了

        第二种情况,try里面有System.exit (0),终止JAVA虚拟机的执行。或者Kill,interrrupted

        易错点,如果try,catch,finally都有return语句,finally会撤销之前的return,以他为准。

    (3)Finalize,每个对象都有,被回收的时候调用,一般不用自己调用,而且容易出问题,不推荐。

10,Static代码块、普通代码块和构造代码块之间的调用顺序,以及一些常用场景。

(1)静态代码块是随着类的加载而加载,而构造代码块和构造方法都是随着对象的创建而加载

(2)构造代码块:直接在类中定义且没有加static关键字的代码块称为{}构造代码块。

    构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造函数。

(3)普通代码块就是方法,比如main函数里面的直接用{}包着的代码。

(4)场景:

    构造代码块的调用时机:每生成一个对象就会被调用一次,而且优先于构造函数被调用.

    静态代码块的调用时机:在类加载到内存的时候调用一次!

    现在基本不用构造代码块了,也不推荐。

11,StringBudiler和StringBuffer的区别和联系。

    (1)线程安全与不安全:string的对象是不可变的,安全。StringBuffer也安全,主要方法都加了synchronized。

    安全不安全导致了速度不同,由慢到快:String<StringBuffer<StringBuilder

    举例:String s8 = s6+s7;

    string最慢,因为底层使用StringBuilder首先初始化s6,在用append方法合并s7的字符串cd,在tostring新对象引用给s8.

    经常要操作字符串的情况下:如果字符串不改就用final string。如果可以改变,多线程使用StringBuffer,其他用StringBuilder

    (2)String s1= "abc";"abc"放在常量池。

    String s3 = new String("abc");"abc"常量池,string对象放在堆中(new String("abc")),对象引用s3放在栈中

12,绘制容器继承关系图。

(1)LIST:可以按照序号访问。ArrayList、linkedlist

(2)SET:HashSet、LinkedHashSet、TreeSet

(3)QUEUE:PriorityQueue

    说白了,HashSet就是限制了功能的HashMap,LinkedHashSet也是如此,treeset也是treemap

    数据不重复,底层是依赖hashmap的key值直接替代了新的value值,只不过key值没变罢了。

13,Collections和Collection的区别和联系。

(1)Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。

     Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。

     Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

     比如,排序,混排,反转,拷贝等等

(2)Collection使用,比如a.add(),remove(),isempty(),size(),contains()

     Collections使用,比如Collections.sort(list);

14,ArrayList和Vector的区别和联系。

(1)Vector的方法都是同步的(Synchronized),是线程安全的,而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。 

(2)当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。

15,Set和Map有联系吗。

    看起来,set的实现是依赖map的key值的不重复,来达到set不重复的目的。

(1)set分为hashSet和treeSet,前者无序,无重复,后者实现了sortset接口,二叉树进行排序

    list有arraylist和linkedList,arraylist有个兄弟vector,见上一条。linkedlist链路的,增删改查方便。查询用arraylist,其他用linkedlist

(2)map是键值对.

    两者关系,就是map种的Keys或者values可以生成set和Collection.

    Map中元素,可以将key序列、value序列单独抽取出来。

    使用keySet()抽取key序列,将map中的所有keys生成一个Set。

    使用values()抽取value序列,将map中的所有values生成一个Collection。

16,HashMap和HashTable的区别和联系。

    HashMap和Hashtable,线程安全ConcurrentHashMap结合了两者并且锁是细粒度的,16个桶,只锁一个

    一般来说,Hashtable效率低,而且基本不太维护了,已经被弃用

    ConcurrentHashMap结合两者,多线程可以使用,并引入了分段锁,每一把锁用于锁容器其中一部分数据,

    线程安全,防止脏数据和死锁。

17,类型擦除是什么意思。

(1)泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

    Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。

    在编译之后,List<Object>和List<String>将变成List,Object和String类型信息对于JVM来说是不可见的。

(2)类型擦除可以让泛型兼容JDK1.5版本之前的版本。

18,HashSet和TreeSet的原理。

(1)HashSet的工作原理;计算对象的hashcode后,集合中查找是否包含哈希值相同的元素.有的话,挨个equals对比,都是false的话,插入数据

(2)TreeSet需要排序。二叉树排序,int,string这种基础类型可以直接排序,但是自定义的需要implement Comparable,重写compareTo()方法

19,数组和字符串谁有Length方法,谁有Length属性。

    数组是length属性,字符串有Length的方法。

    数组a.length

    str.length()

20,能比较清楚地简述各个集合类的特点及适用场合。摘自https://blog.csdn.net/qq_22118507/article/details/51576319

(1)线程安全集合类与非线程安全集合类 

    LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;

    HashMap是非线程安全的,HashTable是线程安全的;

    StringBuilder是非线程安全的,StringBuffer是线程安全的。

(2)ArrayList与LinkedList的区别和适用场景

    Arraylist地址连续,内存里连着放的,所以查询效率较高,插入和删除效率低

    LinkedList基于链表的数据结构,地址是任意的,不需要连续的地址,新增和删除占优势。专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

(3)ArrayList与Vector的区别和适用场景

    Vector是多线程安全的,有synchronized进行修饰,所以导致效率比ArrayList低很多。

    如果集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有一定的优势。

    因为Vector可以设置增长因子,所以在集合中使用数据量比较大的数据,用Vector有一定的优势。

(4)HashSet与Treeset的适用场景

    HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。

    为快速查找而设计的Set,我们通常都应该使用HashSe。

    在我们需要排序的功能时,我们才使用TreeSet。

(5)HashMap与TreeMap、HashTable的区别及适用场景

    HashMap是非同步的,效率上比HashTable要高。HashMap允许空键值,而HashTable不允许。

    HashMap:适用于Map中插入、删除和定位元素。 一般不怎么用HashTable。

    Treemap:适用于按自然顺序或自定义顺序遍历键(key)。 

21,线程的几个状态。

(1)新建状态,暂未调用start()之前

(2)就绪状态,调用start()之后。创建线程运行的系统资源,并调度线程运行run()方法。

    当start()方法返回后,线程就处于就绪状态。不一定立即调用run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。

    一个时刻只有一个线程处于运行状态。

(3)运行状态。

    线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

    当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法。

(4)阻塞状态。

    线程仍旧是活的,但是当前没有条件运行。有机会还可以继续运行,或者直接变成就绪状态。

    怎么进入阻塞呢?sleep、调用一个在I/O上被阻塞的操作、试图得到一个锁,而该锁正被其他线程持有、等待某个触发条件

    等于是暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

(5)死亡状态。

    ①run方法正常退出而自然死亡;

    ②一个未捕获的异常终止了run方法而使线程猝死;

    isAlive判断是否或者,true是运行或阻塞,false是就绪或者死亡。