Java基础面试题

  1. java的四个基本特性(抽象,封装,继承,多态),对多态的理解(多态的实现方式)以及在项目中哪些地方用到多态?
  • 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,

抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么.

  • 继承:继承是从已有类得到继承信息创建新类的过程,提供继承信息的类被称为父类(超类,基类);得到继承信息的类被称为子类(派生类)继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段
  • 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口.面向对象的本质就是将现实世界描绘成一系列完全自治,封闭的对象.我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装,可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口.
  • 多态:指允许不同子类型的对象对同一消息作出不同的响应.
  • 多态的理解(多态的实现方式)
  • 方法重载(overload)实现的是编译时的多态性(也称为前绑定)
  • 方法重写(override)实现的是运行时的多态性(也称为后绑定)运行时的多态是面向对象最精髓的东西
  • 要实现多态需要做两件事 第一:方法重写(子类继承父类并重写父类中已有的或抽象的方法)第二:对象造型(用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为).
  • 项目中对多态的应用
  • 举一个简单的例子,在物流信息管理系统中,有两种用户:订购客户和卖方客户,两个客户都可以登录系统,他们有相同的方法Login,但登陆之后他们会进入不同的页面,也就是在登录的时候会有不同的操作,两种客户都继承父类的Login方法,但是对于不同的对象,拥有不同的操作.

2.面向对象和面向过程的区别?用面向过程可以实现面向对象么?那是不是不能面向对象?

  • 面向对象和面向过程的区别
  • 面向过程就像是一个细心的管家,事无巨细的都要考虑到.而面向对象就像是个家用电器,你只需要知道他的功能,不需要知道它的工作原理.
  • 面向过程是一种以事件为中心的编程思想,就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用,面向对象是以对象为中心的编程思想.
  • 简单的举个例子:汽车发动,汽车到站
  • 这对于"面向过程"来说,是两个事件,汽车启动是一个事件,汽车到站是另一个事件,面向过程编程的过程中我们关心的是事件,而不是汽车本身.针对上述两个事件,形成两个函数,之后依次调用
  • 然而这对于面向对象来说,我们关心的是汽车这类对象,两个事件只是这类对象所具有的的行为.而且对于这两个行为的顺序没有强制要求

3.重载和重写,如何确定调用哪个函数?

  • 重载:重载发生在同一个类中,同名的方法如果有不同的参数列表(参数类型不同,参数个数或者两者都不相同)则视为重载
  • 重写:重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)根据不同的子类对象确定调用的那个方法.

4.面向对象开发的六个基本原则(单一职责,开放封闭,里式替换,依赖倒置,合成聚合复用,接口隔离)迪米特法则

  • 单一职责:一个类只做它该做的事情(高内聚).在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责.
  • 开放封闭:软件实体应当对扩展开放,对修改关闭,要做到开闭有两个要点:

抽象是关键,一个系统中如果没有抽象类或接口,系统就没有扩展点

封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而乱.

  • 里式替换:任何时候都可以用子类型替换掉父类型.子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用没有任何问题
  • 依赖倒置:面向接口编程(该原则说的直白和具体一些就是声明方法的参数类型,方法的返回类型,变量的引用类型,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代)
  • 合成聚合复用:优先使用聚合或合成关系复用代码
  • 接口隔离:接口要小而专,决不能大而全,臃肿的接口是对接口的污染,既然接口表示功能,那么一个接口只应该描述一种功能,接口也应该是高度内聚的.
  • 迪米特法则:又叫最少知识原则,一个对象应当对其它对象有尽可能少的了解
  • 项目中用到的原则

单一职责:开放封闭,合成聚合复用(最简单的例子就是String类)接口隔离

5.static 和final的区别和用途

static

  • 修饰变量:静态变量随着类加载时被完成初始化,内存中只有一个,且JVM也只会为它分配一次内存,所有类共享静态变量
  • 修饰方法:在类加载的时候就存在,不依赖任何实例,static方法必须实现,不能用abstract修饰
  • 修饰代码块:在类加载完之后就会执行代码块中的内容
  • 父类静态代码块----->子类静态代码块----->父类非静态代码块------>父类构造方法---->子类非静态代码块--->子类构造方法

final

修饰变量:

  • 编译期常量:类加载的过程完成初始化,编译后带入到任何计算式中,只能是基本类型
  • 运行时常量:基本数据类型或引用数据类型,引用不可变,但引用的对象内容可变
  • 修饰方法:不能被继承,不能被子类修改
  • 修饰类:不能被继承
  • 修饰形参:final形参不可变

6.hashMap和hashTable的区别,hashMap中的key可以是任何对象或数据类型么?hashTable是线程安全的么?

hashMap和hashTable的区别

  • hashTable的方法是同步的,hashMap未经同步,所以在多线程场合要手动同步hashMap这个区别就像Vector和ArrayList一样
  • hashTable不允许null值(key和value都不可以),hashMap允许null值(key和value都可以)
  • 两者的遍历方式大同小异,hashTable仅仅比hashMap多一个elements方法
  • hashTable和hashMap都能通过values()方法返回一个Collection,然后进行遍历处理.两者也都可以通过entrySet()方法返回一个set,然后进行遍历处理.
  • hashTable使用Enumeration,hashMap使用Iterator
  • 哈希值的使用不同,hashTable直接使用对象的hashCode.而hashMap重新计算hash值,而且用于代替求模
  • hashTable中hash数组默认大小是11,增加的方式是old*2+1.hashMap中hash数组的默认大小是16,而且一定是2的指数.
  • hashTable基于Dictionary类,而hashMap基于AbstractMap类

hashMap中的key可以是任何对象或数据类型么?

  • 可以为null,但不能是可变对象,如果是可变对象的话,对象中的属性改变,则对象hashCode也进行相应的改变,导致下次无法查找到已存在Map中的数据
  • 如果可变对象在hashMap中被用作键,那就要小心再改变对象状态的时候,不要改变它的哈希值了.我们只需要保证成员变量的改变能保证该对象的哈希值不变即可.

hashTable是线程安全的么?

  • hashTable是线程安全的,其实现是在对应的方法上添加了synchronized关键字进行修饰,由于在执行此方法的时候需要获得对象锁,则执行起来比较慢.所以现在如果为了保证线程安全的话,使用CurrentHashMap

hashMap和ConcurrentHashMap的区别?

  • hashMap是非线程安全的,ConcurrentHashMap是线程安全的.
  • ConcurrentHashMap将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候就应该先找到应该插入到哪一个片段segment,然后在这个片段上面进行插入,而且这里还需要获取segment锁.
  • ConcurrentHashMap让锁的粒度更精细一些,并发性能更好.

,ConcurrentHashMap线程安全么,ConcurrentHashMap如何保证线程安全?

  • hashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问hashTable的线程都必须竞争用一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效地提高并发访问效率,这就是ConcurrentHashMap所使用的的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其它线程访问.
  • get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读.get方法里将要使用的共享变量都定义成volatile,如用于统计当前segment大小的count字段和用于存储值的HashEntry的value.定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖于原值)在get操作里只需要读不需要写共享变量count和value,所以可以不用加锁.
  • put方法首先定位到segment,然后在segment里进行插入操作.插入操作需要经历两个步骤,第一步判断是否需要对segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里

7.因为别人知道源码怎么实现的,故意构造相同的hash的字符串进行攻击,怎么处理?

  • 当客户端提交一个请求并附带参数的时候,web应用服务器会把我们的参数转化成一个HashMap存储,这个HashMap的逻辑结构如下:key1----->value1
  • 但是物理存储结构是不同的,key值会被转化成hashCode,这个hashCode有会被转成数组的下标:0---->value1
  • 不同的String就会产生相同hashcode而导致碰撞,碰撞后的物理存储结构可能如下:0--->value1---->value2
  • 限制post和get的参数个数,越少越好
  • 限制post数据包的大小
  • WAF

8.String ,StringBuffer,StringBuilder以及对String不变性的理解

  • String,StringBuffer,StringBuilder都是final类,都不允许被继承
  • String长度是不可变的,StringBuffer,StringBuilder长度是可变的
  • StringBuffer是线程安全的,StringBuilder不是线程安全的,但它们两个中的所有方法都是相同的,StringBuffer在StringBuilder的方法之上添加了synchronized修饰,保证线程安全
  • StringBuilder比StringBuffer拥有更好的性能
  • 如果一个String类型的字符串,在编译时就可以确定是一个字符串常量,则编译完成之后,字符串会自动拼接成一个常量,此时String的速度比StringBuffer和StringBuilder的性能好得多

String 不变性的理解

  • String类是被final进行修饰的,不能被继承
  • 在用+连接字符串的时候会创建新的字符串
  • String s=new String ("Hello Word")可能创建两个对象也可能创建一个对象.如果静态区中有"HelloWord"字符串常量对象的话,则仅仅在堆中创建一个对象.如果静态区中没有"helloWord"对象,则堆上和静态区中都需要创建对象.
  • 在java中,通过使用"+"符号来串联字符串的时候,实际上底层会转成通过StringBuilder的append()方法来实现

9.String 有重写Object的hashcode和tostring么?

  • String 重写了Object的hashcode和tostring方法
  • 当equals方法被重写时,通常有必要重写hashcode方法,以维护hashcode方法的常规协定,该协定声明相对等的两个对象必须有相同的hashcode

重写equals不重写hashcode会出现什么问题?

  • 在存储散列集合时(如set类)如果原对象.equals(新对象),但没有对hashcode重写,即两个对象拥有不同的hashcode,则在集合中将会存储两个值相同的对象,从而导致混淆.因此在重写equals方法时必须重写hashcode方法

10.java序列化定义

将那些实现了serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象,序列化可以弥补不同操作系统之间的差异.

Java 序列化的作用

Java远程方法调用(RMI)

 

如何实现序列化和反序列化

  • 实现Serializable接口

该接口只是一个可序列化的标志,并没有包含实际的属性和方法

如果不在该方法中添加readObject()和writeObject()方法,则采取默认的序列化机制.如果添加了这两个方法之后还想利用java默认的序列化机制,则在这两个方法中分别调用defaultReadObject()和defaultWriteObject()两个方法

为了保证安全性,可以使用transient关键字进行修饰不必序列化的属性,因为在反序列化时,private修饰的属性也能看到.

  • 实现ExternalSerializable方法

自己对要序列化的内容进行控制,控制哪些属性能被序列化,哪些不能被序列化

 

注意事项:

  • 被static修饰的属性不会被序列化
  • 对象的类名属性都会被序列化,方法不会被序列化
  • 要保证序列化对象所在类的属性也是可以被序列化的
  • 当通过网络,文件进行序列化时,必须按照写入的顺序读取对象
  • 反序列化时必须有序列化对象时的class文件

11.Java实现多线程的方式

  • 继承Thread类,重写run()方法
  • 实现Runnable接口
  • 实现Callable接口

 

三种方式的区别

  • 实现Runable接口可以避免java单继承特性而带来的局限:增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的:适合多个相同程序代码的线程区处理同一资源的情况
  • 继承Thread类和实现Runnable方法启动线程都是使用start方法,然后JVM虚拟机将此线程放到就绪队列中,如果有处理机可用,则执行run方法
  • 实现Callable接口要实现call方法,并且线程执行完毕后会有返回值,其它的两种都是重写run方法,没有返回值

12.线程安全

定义:

  • 某个类的行为与其规范一致
  • 不管多个线程是怎样的执行顺序和优先级,或是wait,sleep,join等控制方式,如果一个类在多线程访问下运转一切正常,并且访问类不需要进行额外的同步处理或者协调,那么我们就认为它是线程安全的.

 

如何保证线程安全?

  • 对变量使用volitate
  • 对程序段进行加锁(synchronized,lock)

注意:

  • 非线程安全的集合在多线程环境下可以使用,但并不能作为多个线程共享的属性,可以作为某个线程独享的属性
  • 例如vector是线程安全的,ArrayList不是线程安全的.如果每一个线程中new一个ArrayList,而这个ArrayList只是在这一个线程中使用,肯定没问题

13.多线程如何进行信息交互

Object中的方法 wait(),notify(),notifyAll()

14.多线程公用一个数据变量需要注意什么

  • 当我们在线程对象(Runnable)中定义了全局变量,run方法会修改该变量时,如果有多个 线程同时使用该线程对象,那么就会造成全局变量的值同时修改,造成错误.
  • ThreadLocal是JDK引入的一种机制,它用于解决线程间共享变量,使用ThreadLocal声明的变量,即使在线程中属于全局变量,针对每个线程来讲,这个变量也是独立的.
  • volatile变量每次被线程访问时,都强迫线程从主内存中重读该变量的最新值,而当该变量发生修改变化时,也会强迫线程将最新的值刷新回主内存中.这样一来,不同的线程都能及时的看到该变量的最新值.

 

15.什么是线程池?

线程池就是是先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销.

 

16.设计一个动态大小的线程池,如何设计,应该有哪些方法?

一个线程池包括以下四个基本组成部分

  • 线程管理器(ThreadPool):用于创建并管理线程池,包括创建线程,销毁线程池,添加新任务;
  • 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
  • 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
  • 任务队列(TaskQueue):用于存放没有处理的任务,提供一种缓冲机制

所包含的方法

  • private ThreadPool()创建线程池
  • public static ThreadPool getThreadPool()获得一个默认线程个数的线程池
  • public void execute(Runnable task):执行任务,其实只是把任务加入任务队列,什么时候执行由线程池管理器决定
  • public void destory()销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
  •  

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值