java面试问题总结

1.java基本数据类型
数据类型大小字节
byte 字节81
short 短整型162
int 整型(正负20亿)324
long 长整型648
float 浮点型324
double 双精度648
char 字符型162
boolean 布尔型81
2.自动拆装箱

自动装箱:就是自动将基本数据类型转换为包装器类型;

Integer i = 10;//相当于Integer i = Integer.valueOf(10);

自动拆箱:就是自动将包装器类型转换为基本数据类型;

int a = i;//相当于 int a = i.intValue();

3.128陷阱

自动装箱规范要求boolean、byte、char<=127,介于-128-127之间的 short 和 int 类型被包装到固定对象中。Java里面对处在在-128-127之间的Integer值,用的是原生数据类型int,会在内存里供重用,也就是说这之间的Integer值进行双等比较时只是进行int原生数据类型的数值比较,而超出-128-127的范围,进行双等比较时是进行地址及数值比较

4.short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 +=1;有什么错?

前者是s1+1会将s1提升为int类型,所以s1+1的结果是2,再赋值给s1会出现需要强制类型转换的错;
后者s1+=1相当于s1=(short)(s1+1),已经将s1+1强制转换成short类型了,符合语法规则,没错。

5.final,finally,finalize的区别

  • final用于声明属性,方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承。

  • finally是异常处理语句结构的一部分,表示总是执行。

  • finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其

    他资源回收,例如关闭文件等。

    5.堆和栈的区别

    栈是后进先出,队列是先进先出

1.封装、继承、多态

面向过程注重过程,就是要分析出解决问题需要的步骤,然后按步骤一步一步实现,类调用时需要实例化,开销比较大,性能比面向对象高

面向对象最主要的就是把构成问题的事务分解成各个对象,把对象封装成方法,然后调用方法,优点就是易维护,易扩展,提高了可重用性。

  • 封装就是将一个对象的属性隐藏起来,不允许外界直接访问,但是可以通过调用指定的方法进行访问(name域只能通过,get set方法访问)

  • 继承就是子类继承父类的所有的方法,而且可以拥有自己的方法,提高了代码的重用(对扩展是开放的)

  • 多态就是父类引用指向子类对象,就是一个引用在不同的情况下的多种状态,也就是指向父类的指针来调用在不同子类中的实现的方法

    动态绑定静态绑定区别:

    动态绑定是多态性得以实现的重要因素,动态绑定是指:在程序运行过程中,根据具体的实例对象才能具体确定是哪个方法。

    程序在JVM运行过程中,会把类的类型信息、static属性和方法、final常量等元数据加载到方法区,这些在类被加载时就已经知道,不需对象的创建就能访问的,就是静态绑定的内容;需要等对象创建出来,使用时根据堆中的实例对象的类型才进行取用的就是动态绑定的内容。

2.反射机制

是在运行状态中,对任意一个类,能知道这个类的所有方法和属性,对于任意一个对象来说,都能调用他的任意一个方法和属性。这就是Java语言的反射机制,主要就是增加程序的灵活性

应用:spring中通过反射执行Bean方法,依赖注入的时候用到了反射

servlet原理(通过反射来获取servlet接口实现类)

JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序(加载mysql驱动:Class.forName(“com.mysql.jdbc.Driver”)😉

3.代理机制

如果需要在原有类里加上权限,日志等功能,还想不改动原有代码的前提上,实现一些其他功能,可以使用代理写额外的功能,不影响原来的功能。

就是可以通过代理对象访问目标对象,即可以在目标对象实现的基础上,可以对其扩展

静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道

  • 静态代理:在代理类 定义与目标类相同的方法,然后通过修改,执行代理类的方法来达到在原来的方法基础上进行扩充的目的。
  • 动态代理:可以实现AOP编程,代理对象不需要实现接口,生成是利用JDK的API,动态的在内存中构建代理对象。
CGlib代理和JDK代理的区别

JDK的动态代理是通过反射类以及回调函数实现的,但是只能对该类所实现接口中定义的方法进行代理。

CGlib实现动态代理使用字节码技术生成代理类

目标对象生成了接口默认使用JDK,没有实现接口必须使用CGlib,像RPC框架中,消费者调用接口看不到实现类,就是用来CGlib的动态代理。

jdk动态代理:在运行期间使用动态生成字节码形式,通过反射原理动态创建代理类。

Cglib动态代理:CGLIB通过继承方式实现代理。

哪里用到了动态代理 ,spring里面的AOP,mybatis

springAOP中的代理

Spring AOP中的代理使用的默认策略是:

如果目标对象实现了接口,则默认采用JDK动态代理

如果目标对象没有实现接口,则采用CgLib进行动态代理

如果目标对象实现了接口,且强制CgLib代理,则采用CgLib进行动态代理

Java重写和重载:

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

重载 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

4. Java 接口

接口,是抽象方法的集合,可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段

内部类

内部类有一个特征:内部类当中可以调用外部类当中的属性和方法,而外部类却不能调用内部类当中的。

Java 内部类可以使用public ,protect,default,private 修饰符

匿名内部类可以继承两类数据结构:一:抽象类二:接口。

匿名内部类的具体名字不会被我们在程序当众编写出来,因为它已经在主方法当中被实例化了。

5.抽象类和接口的区别

区别主要体现在:
(1)抽象类可以有构造方法,接口中不能有构造方法。

(2)抽象类中可以有静态方法,接口中不能有静态方法。

(3)抽象类中可以有普通方法,接口中所有方法都必须是抽象的。

(4)抽象类中可以有成员变量,接口中没有成员变量。(被final修饰变成了常量)

(5)抽象类中抽象方法的访问类型可以是public,protected,但接口中抽象方法的访问类型只能是public,并且默认为public abstract(省略则自动默认补全)。

数组和链表区别

数组的特点是:查询简单,增加和删除困难;

链表查找数据时间效率低,时间复杂度是o(n),任意位置插入元素和删除元素时间效率较高,时间复杂度是o(1)

6.Java中有了基本数据类型,为什么还需要包装类型
  • Java是一个面向对象的语言,然而基本数据类型不具备面向对象的属性。当我们把基本数据类型包装成包装类型后,基本数据类型就具备了面向对象的属性。
  • 在ArrayList 、HashMap这些容器来传输数据是,,基本类型int和double是传输不进去的,因为容器都是装泛型(object类型)的,所以需要转为包装类型进行传输。
  • 每一个基本类型都有对应的包装类型
7.装箱和拆箱

装箱就是把基本数据类型包装成对应得包装类型
例如:自动装箱 Integer i = 1 ,实际上在编译时会调用Integer .valueOf方法来装箱

拆箱就是把包装类型拆成对应得基本数据类型
例如:int a = i;//相当于 int a = i.intValue();

8.java运行常见的异常

五种常见异常

1、ClassCastException(类转换异常)

2、IndexOutOfBoundsException(数组越界)

3、NullPointerException(空指针)

4、ArrayStoreException(数据存储异常,操作数组时类型不一致)

5、BufferOverflowException异常

9.ArrayList和LinkedList的区别

ArrayList 底层是由数组构建成,默认大小是10 ,,,,,ArrayList 是支持快速访问、复制、序列化的。
LinkedList:底层由双向链表结构实现,通过节点来存储下一个元素的位置,对集合中的元素可以方便的增加与删除,更适合用于大量修改。

  1. 是否保证线程安全: ArrayListLinkedList 都是不同步的,也就是不保证线程安全;

  2. 底层数据结构: Arraylist 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。)

  3. 插入和删除是否受元素位置的影响:

    ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。

    LinkedList 采用链表存储,所以对于a dd(E e)方法的插入,删除元素时间复杂度不受元素位置的影响。

  4. 是否支持快速随机访问:

    LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。

接口子接口
Collection
ListArrayList
LinkedList
Vector
SetAbstractSet
HashSet
TreeSet
MapAbstractMap
HashMap
TreeMap

单链表是由一个头结点开始。然后依次插入新的节点。每个节点包含两个部分一个是数据的引用或者基本类型的数据 和下一个节点的存储地址。这样一个链表向外暴露的只是第一个头结点。所以只要知道头结点就可以直接找到剩下其余的节点。

双向链表的每一个结点都有一条指向其后继结点的next链和一条指向其前结点的pre链

11.HashMap

1.HashMap(线程不安全,多线程同时对其操作时会产生线程安全问题)

HashMap基于Map接口,是一种基于Key-Value的数据结构,允许有一个key为null,多个value为null。

HashMap的默认数组长度为16,扩容因子为0.75,每次扩容成2的n次方数值,也就是扩容两倍,默认第一次扩容阈值16*0.75=12

HashMap的实现原理:
jdk1.7
1.7之前数组加链表,用的是头插法,为什么线程不安全,首先就是·数组扩容的时候,头插会让链表形成闭环, 2.还有一个覆盖的问题
jdk1.8

1.8之后,数组加链表加红黑树的形式,用的尾插法,解决了1.7的(避免老版本hashmap在并发resize时会出现的死循环问题),然后还是有这个覆盖的问题,覆盖就是两个线程,然后hash一样,前一个放入链表后,后一个放入前一个的位置,那么,就会产生覆盖

为什么用红黑树不用二叉搜索树?

在CurrentHashMap中是加锁了的,实际上是读写锁,如果写冲突就会等待,
如果插入时间过长必然等待时间更长,而红黑树相对AVL树他的插入更快

为什么不使用平衡二叉树

2.Hashtable(线程安全,对整个结构加锁,性能不好)
同样为Key-Value数据结构,但不允许key,value的值为null

HashTable是安全的,对整个hashmap加上sychronized,然后效率会低一些,但是多线程下是安全的,默认数组长度为11
实现原理:数组+链表

3.ConcurrentHashMap(线程安全)

ConcurrentHashMap使用分段锁的思想,对于不同的数据段使用不同的锁,可以支持多个线程同时访问不同的数据段,这样线程之间就不存在锁竞争,从而提高了并发效率。
1.7,ConcurrentHashMap由Segment数组结构和HashEntry数组组成。Segment是一种可重入锁,是一种数组和链表的结构,一个Segment中包含一个

HashEntry数组,每个HashEntry又是一个链表结构。正是通过Segment分段锁,ConcurrentHashMap实现了高效率的并发。
1.8 ConcurrentHashMap去除了Segment分段锁的数据结构,主要是基于CAS操作保证保证数据的获取以及使用synchronized关键字对相应数据段加锁实现了主要功能,这进一步提高了并发性。同时同时为了提高哈希碰撞下的寻址性能,Java 8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(long(N)))。

hashSet

Set不能有重复的元素,HashMap不允许有重复的键

HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变,此类允许使用null元素。
在HashSet中,元素都存到HashMap键值对的Key上面,而Value时有一个统一的值private static final Object PRESENT = new Object();,(定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。)

  • 说白了,HashSet就是限制了功能的HashMap,所以了解HashMap的实现原理,这个HashSet自然就通
  • 对于HashSet中保存的对象,主要要正确重写equals方法和hashCode方法,以保证放入Set对象的唯一性
  • 虽说是Set是对于重复的元素不放入,倒不如直接说是底层的Map直接把原值替代了(这个Set的put方法的返回值真有意思)
  • HashSet没有提供get()方法,愿意是同HashMap一样,Set内部是无序的,只能通过迭代的方式获得

HashSet如何去重

由上面的第四点我们可以看到,HashSet是不能添加重复元素的,那么他是如何实现的呢?
一般来说,一个类必要时会同时重写hashCode()和equals()两个方法(如果没有重写,那么就默认调用Object中的hashCode()和equals())。这也是HashSet去重机制的关键。

首先是重写的hashCode()。如果在该方法中,两个对象通过重写后计算出来的哈希码是不同的,那么就直接判定这两个对象不是同一个对象,即可以同时存放进HashSet集合。
当两个对象通过重写后计算出来的哈希码是相同的,那么调用重写后的equals方法。如果返回false,说明这两个对象不同,即可以同时存放进HashSet集合,反之则不可以。

总结问题:

1.为什么将某些类对象放入HashMap一定要重写HashCode方法和equals()方法?
答:这就和HashMap的底层实现原理有关,将对象放入HashMap,首先会判断传入键的hash值是否相同,如果不同则直接放入集合中,如果相同,则进一步进行equals判断,如果equals判断也相同,那么后来传入的键会将前面的键覆盖。
2.ConcurrentHashMap和HashTable有什么区别?能否取代HashMap?
答:主要区别体现在实现线程安全的方式上
①在jdk1.7时,ConcurrentHashMap采用分段锁方式对整个桶数组进行了分割分段(Segment)(其中segment继承自ReentrantLock)0每一把锁只锁容器中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提供了并发访问率;
到了jdk1.8,直接摒弃了Segment的概念,直接采用Node数组+链表+红黑树来实现,并发控制使用synchronized和CAS来操作。
②Hashtable则直接对整个结构加锁(synchronized)来保证线程安全,效率非常低下。当一个线程访问同步方法时,其它线程也访问同步方法,可能会进入阻塞或轮询状态,如果使用put添加元素,另一个线程就不能使用put添加元素,也不能使用get。
③不能取代HashMap,Hashtable的任何操作都会把表锁住,是阻塞的,好处是总能够获取最实时的更新,ConcurrentHashMap为非阻塞的,在更新时会局部锁住某部分数据,但不会把整个表都锁住,同步读取操作是完全非阻塞的,在合理条件下效率非常高,坏处是在大量的读取操作时不能保证数据的实时更新。
3.HashMap的长度为什么是2的倍数?
在HashMap的操作流程中,首先会对key进行hash算法得到一个索引值,这个索引值就是对应哈希桶数组的索引。为了得到这个索引值必须对扰动后的数跟数组长度进行取余运算。即 hash % n (n为hashmap的长度),又因为&比%运算快。n如果为2的倍数,就可以将%转换为&,结果就是 hash & (n-1)。所以这就解释了为什么HashMap长度是2的倍数。
4.HashMap底层是如何实现的?
首先底层数据结构是由数组+链表组成链表散列。HashMap先得到key的散列值,在通过扰动函数(减少碰撞次数)得到Hash值,接着通过hash & (n -1 ),n为table的长度,运算后得到数组的索引值。如果当前节点存在元素,则通过比较hash值和key值是否相等,相等则替换,不相等则通过拉链法查找元素,直到找到相等或者下个节点为null时。
1.8对扰动函数,扩容方法进行优化,并且增加了红黑树的数据结构。

12.什么情况用到了红黑树

TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。

红黑树:
每个节点不是红的就是黑的;
根节点是黑的;
叶节点都是黑色,叶子节点指的是为空的节点;
如果一个节点是红色的,那么子节点必须为黑色;
从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

13.构造器能不能被继承

构造器是无法被继承的,所以不能重写,但是可以重载。

原因:构造器是生产对象的一个途径,假如可以被继承,那么对象就可以被复制了。子类可以通过继承构造器产生父类对象,这样就会出现子类引用指向父类对象,java是不支持向下转型的,只能向上转型。

因为子类继承父类的时候,先运行父类构造函数;具体的说就是运行父类时就会先“调用”父类的构造函数,调用是“自动运行” 继承就是扩展

子类完全没必要扩展父类的构造函数,因为反正每次调子类的时候都会“自动运行”它父类的构造函数,如果真的需要子类构造函数特殊的形式,子类直接修改或重载自己的构造函数就好了。

14.为什么需要序列化

Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。

不序列化也可以传输,但是无法跨平台,安全性也无法保障。我说的是面向服务编程中的作用,在传统编程中,你在表示层实例化一个业务对象,然后调用业务对象中的方法,你想过为什么能这样调用吗?这样做耦合度太高,很不好。如果序列化以后通过特定的协议传输数据就不一样了,表示层通过代理或通道向服务层发送特定的数据格式,这个数据就是序列化以后的,比如XML,服务端接收到以后要进行反序列化,生成服务端可识别的数据格式,比如一个类,然后对数据进行操作,再序列化发送到客户端,客户端再反序列化。这样客户端可以使用和服务端完全不同的开发平台,只要它能够对xml数据进行反序列化,而xml是具有工业标准的数据格式,基本各平台都支持。这也适用于在进程间通信。如果在进程内通信,也可以做到更高的安全性,对象不再通过调用栈交互,而是通过代理或通道。

15.Java的值传递和引用传递

是值传递。Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值