Java面试基础问题

Java基础面试常考问题汇总~~~

面向过程和面向对象

两者区别:

面向过程:分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;
面向对象:把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,为是为了描述某个事务在解决问题的步骤中的行为。

面向过程的优缺点:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展。

面向对象的优缺点:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点:性能比面向过程低。

接口与抽象类的区别

抽象类:
使用abstract修饰符修饰的类。官方点的定义就是:如果一个类没有包含足够多的信息来描述一个具体的对象,这样的类就是抽象类。

抽象类在实际应用中,更多的是因为类中有抽象方法。抽象方法:只声明,不实现。具体的实现由继承它的子类来实现。

实际点就是:被abstract修饰的方法,只有方法名没有方法实现,具体的实现要由子类实现。方法名后面直接跟一个分号,而不是花括号。例如:public abstract int A();一个类中含有抽象方法(被abstract修饰),那么这个类必须被声明为抽象类(被abstract修饰)。

接口:
官方定义:接口在java中是一个抽象类型,是抽象方法的集合。一个类通过继承接口的方式,从而继承接口的抽象方法。

接口是个集合,并不是类。类描述了属性和方法,而接口只包含方法(未实现的方法)。接口和抽象类一样不能被实例化,因为不是类。但是接口可以被实现(使用 implements 关键字)。实现某个接口的类必须在类中实现该接口的全部方法。虽然接口内的方法都是抽象的(和抽象方法很像,没有实现)但是不需要abstract关键字。

接口中没有构造方式(因为接口不是类)
接口中的方法必须是抽象的(不能实现)
接口中除了static、final变量,不能有其他变量
接口支持多继承(一个类可以实现多个接口)

共同点:
都不能被实例化,它们都为与继承树的顶端,用于被其他类实现和继承。
都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

不同点:

Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:
接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
类可以实现很多个接口,但是只能继承一个抽象类
类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
抽象类可以在不提供接口方法实现的情况下实现接口。
Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。
接口是绝对抽象的,不可以被实例化,抽象类也不可以被实例化。

string、stringbuffer、stringbuilder

String:字符串常量,字符串长度不可变。
StringBuffer 与 StringBuilder是字符缓冲变量。
StringBuffer:字符串变量(Synchronized,即线程安全)
StringBuilder:字符串变量(非线程安全)。在内部,StringBuilder对象被当作是一个包含字符序列的变长数组。

String 类型和StringBuffer的主要性能区别:String是不可变的对象, 因此在每次对String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。

StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而StringBuilder没有这个修饰,可以被认为是线程不安全的。

String类型的字符串对象是不可变的,一旦String对象创建后,包含在这个对象中的字符系列是不可以改变的,直到这个对象被销毁。

StringBuilder和StringBuffer类型的字符串是可变的,不同的是StringBuffer类型的是线程安全的,而StringBuilder不是线程安全的

效率:三者在执行速度方面的比较:StringBuilder > StringBuffer > String。

知道使用StringBuffer等无非就是为了提高java中字符串连接的效率,因为直接使用+进行字符串连接的话,jvm会创建多个String对象
(1)基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。
(2)不要使用String类的”+”来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。

==、equals、hashcode

==:操作符,比较两个对象之间的数值关系,返回boolean类型

equals:Object类的方法,比较两个对象内容关系,返回boolean类型

hashCode:Object类的方法,返回对象的hash值

重写equals需要注意些什么??

• 自反性:对于任何非空引用x,x.equals(x)必须返回true。
• 对称性:对于任何非空引用x和y,如果且仅当y.equals(x)返回true时x.equals(y)必须返回true。
• 传递性:对于任何非空引用x、y、z,如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)必须返回true。
• 一致性:对于任何非空引用x和y,如果在equals比较中使用的信息没有修改,则x.equals(y)的多次调用必须始终返回true或始终返回false。
• 非空性: 对于任何非空引用x,x.equals(null)必须返回false。
当重写equals方法时,同时也要重写hashCode方法。

sql注入

黑客的填空游戏。
是发生于应用程序与数据库层的安全漏洞。简而言之,是在输入的字符串之中注入SQL指令,在设计不良的程序当中忽略了字符检查,那么这些注入进去的恶意指令就会被数据库服务器误认为是正常的SQL指令而运行,因此遭到破坏或是入侵。
受到的伤害:
1.数据表中的数据外泄,例如企业及个人机密数据,账户数据,密码等。
2.数据结构被黑客探知,得以做进一步攻击(例如SELECT * FROM sys.tables)。

避免的方法:
在设计应用程序时,完全使用参数化查询(Parameterized Query)来设计数据访问功能。
在组合SQL字符串时,先针对所传入的参数加入其他字符(将单引号字符前加上转义字符)

红黑树

是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它在1972年由鲁道夫·贝尔发明,被称为”对称二叉B树”,

平衡二叉树(Balanced BinaryTree)又被称为AVL树。它具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

HashMap

最常用的Map;
Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。

多线程

进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。

地址空间和其它资源的区别:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。

你了解守护线程吗?它和非守护线程有什么区别?

程序运行完毕,jvm会等待非守护线程完成后关闭,但是jvm不会等待守护线程.守护线程最典型的例子就是GC线程。

什么是多线程上下文切换?

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

创建两种线程的方式?他们有什么区别?

通过实现java.lang.Runnable或者通过扩展java.lang.Thread类.相比扩展Thread,实现Runnable接口可能更优.原因有二:

1.Java不支持多继承.因此扩展Thread类就代表这个子类不能扩展其他类.而实现Runnable接口的类还可能扩展另一个类.

2.类可能只要求可执行即可,因此继承整个Thread类的开销过大.

乐观锁和悲观锁

乐观锁顾名思义就是在操作时很乐观,认为操作不会产生并发问题(不会有其他线程对数据进行修改),因此不会上锁。但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS(compare and swap)算法实现。

乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。

传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

RPC

RPC (Remote Procedure Call)即远程过程调用,是分布式系统常见的一种通信方法。

RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
RPC会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯)。
客户端发起请求,服务器返回响应(类似于Http的工作方式)RPC在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。

hashmap存在哪些问题?

Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据。

HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

HashTable

底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化。

HashMap

底层数组+链表实现,可以存储null键和null值,线程不安全。

HashMap和Hashtable都是用hash算法来决定其元素的存储,因此HashMap和Hashtable的hash表包含如下属性:

容量(capacity):hash表中桶的数量;
初始化容量:创建hash表时桶的数量,HashMap允许在构造器中指定初始化容量;
尺寸(size):当前hash表中记录的数量;
负载因子(load factor):负载因子等于“size/capacity”。

ConcurrentHashMap

  • 底层采用分段的数组+链表实现,线程安全;

  • 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是volatile的,也能保证读取到最新的值。)

  • Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术

  • 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。

  • 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容 。

两次hash,一次hash确定那个片段,第二次hash确定hashmap中的位置

final、finally、finalize

1、final 修饰符(关键字)
final用于控制成员、方法或者是一个类是否可以被重写或者继承等功能,如果要学好java,那么这个关键字很重要,必须掌握。如果类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。将变量或者方法声明为final,可以保证他们在使用中不被改变。其初始化可以在两个地方:一是其定义处,也就是说,在final变量定义时直接给其赋值;二是构造函数中。这2个地方只能选其一,要么在定义处直接给其赋值,要么在构造函数中给值,并且在以后的引用中,只能读取,不可修改。被声明为final的方法也同样只能使用,不能重写。

2、finally(用于异常处理)

一般是用于异常处理中,提供finally块来执行任何的清楚操作,try{} catch(){} finally{}。finally关键字是对java异常处理模型的最佳补充。finally结构使代码总会执行,不关有无异常发生。使得finally可以维护对象的内部状态,并可以清理非内存资源。 finally在try,catch中可以有,可以没有。如果trycatch中有finally则必须执行finally快中的操作。一般情况下,用于关闭文件的读写操作,或者是关闭数据库的连接等等。

3、finalize(用于垃圾回收)

finalize这个是方法名。在java中,允许使用finalize()方法在垃圾收集器将对象从内存中清理出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用是对这个对象调用的。它是Object类中定义的,因此,所有的类都继承了它。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。