1、面向对象可以解释下吗?都有那些特性?
面向对象是对现实的理解和抽象的一种软件开发方法,具有封装、继承与多态的特性。
封装:将事物封装成一个类,较少耦合,隐藏细节。保留特定的接口与外界联系,当接口内部发生改变时,不会影响外部调用方法。
继承:已知类的派生新的类,新类拥有已知类的属性或方法,可以通过覆写来增强已知类的功能。
多态:一个程序中存在多个同名的不同方法,通过三种方式实现:
|- 重载:在一个类中对方法进行重载实现。
|- 覆写:子类对父类的覆写实现。
|- 转型:将子类对象作为父类对象使用来实现。
知识点:
覆写:是指子类和父类之间方法的一种关系,比如说父类拥有方法A,子类扩展了方法A并且添加了丰富的功能。那么我们就说子类覆盖或者重写了方法A,也就是说子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。
重载:指在一个类中(包括父类)存在多个同名的不同方法,这些方法的参数个数,顺序以及类型不同均可以构成方法的重载。如果仅仅是修饰符、返回值、抛出的异常不同,那么这是2个相同的方法。
多态:
把不同的子类对象都当作父类对象来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。这样操作之后,父类的对象就可以根据当前赋值给它的子类对象的特性以不同的方式运作。
对象的引用型变量具有多态性,因为一个引用型变量可以指向不同形式的对象,即:子类的对象作为父类的对象来使用。在这里涉及到了向上转型和向下转型。
向上转型:
子类对象转为父类,父类可以是接口。
公式:Father f = new Son(); Father是父类或接口,Son是子类。
向下转型:
父类对象转为子类。公式:Son s = (Son) f;
在向上转型的时候我们可以直接转,但是在向下转型的时候我们必须强制类型转换。并且,如案例中所述,该父类必须实际指向了一个子类对象才可强制类型向下转型,即其是以这种方式Father f = new Son()创建的父类对象。若以Father f = new Father()这种方式创建的父类对象,那么不可以转换向下转换为子类的Son对象,运行会报错,因为其本质还是一个Father对象。
2、只有方法返回值不同可以构成重载吗?
不可以,因为我们调用某个方法,有时候并不关心其返回值,这个时候编译器根据方法名和参数无法确定我们调用的是哪个方法。
1、JDK、JRE和JVM的联系?
- JVM(Java Virtual Machine)Java虚拟机的规范,独立于硬件和操作系统,具有平台无关性,而这也是Java程序可以一次编写,多处执行的原因。比如:Hotspot VM。
- JRE(Java Runtime Environment)JVM 标准加上实现的一大堆基础类库,就组成了 Java 的运行时环境。JRE包含了JVM,但是不包含Java编译器/调试器之类的开发工具
- JDK(Java Development Kit)是一个开发工具包,除了 JRE,JDK 还提供了一些非常好用的小工具,比如 javac、java、jar 等。。
2、Java跨平台性是怎么实现的?
因为Java文件执行过程是:Java 文件->编译器>字节码->JVM->机器码,机器码调用操作系统的相关函数。在这个过程中,JVM 上承开发语言,下接操作系统,它的中间接口就是字节码,这样相当于屏蔽了操作系统和底层硬件的差异。
3、Java语言是编译型还是解释型语言?
Java的执行经历了编译和解释的过程,是一种先编译,后解释执行的语言,不可以单纯归到编译性或者解释性语言的类别中。
1、抽象类与接口的区别?
- 接口中的方法在JDK8之前只能是抽象的,JDK8版本开始提供了接口中方法的default实现
- 抽象类和类一样是单继承的;接口可以实现多个父接口
- 抽象类中可以存在普通的成员变量;接口中的变量必须是static final类型的,必须被初始化,接口中只有常量,没有变量
抽象类 | 接口 | |
定义 | abstract class 抽象类的名称 { } | interface 接口名称 { } |
组成 | 构造方法、抽象方法、普通方法、static方法、全局常量、成员 | 抽象方法、defaullt + 普通方法、static方法、全局常量 |
权限 | 可以使用各种权限定义 | 只能够使用public |
子类使用 | 子类可以通过extends关键字继承一个抽象类 | 子类使用implements关键字可以实现多个接口 |
两者关系 | 抽象类可以实现若干个接口 | 接口不许继承抽象类,但是允许继承多个父接口 |
使用 | 1、抽象类和接口必须定义子类 2、子类一定要覆写抽象类和接口中的抽象方法 3、通过子类的向上是实现抽象类或接口的对象实例化 |
2、接口与抽象类应该怎样选择?分别在什么情况下使用呢?(*)
仅仅需要定义一些抽象方法而不需要其余额外的具体方法或者变量的时候,我们可以使用接口。
反之,使用抽象类。
那么JDK8中为什么会出现默认方法呢?
使用接口,使得我们可以面向抽象编程,但是其有一个缺点就是当接口中新增或者更改一个方法的时候,需要修改所有的实现类。在接口路中如果有默认方法就不需要更改所有的实现类,只需要在接口中实现就行。
1、Java中的8种基本数据类型及其占用空间
Java种的8种基本数据类型分别是:byte,short,int,long,float,double,char以及boolean。boolean类型的取值为true和false两种,其余每一种基本类型都占有一定的字节,并且拥有着最大值和最小值。比如int的取值范围为 Integer.MIN_VALUE 到 Integer.MAX_VALUE。这里给出每种基本类型所占用的字节数:
- byte:1字节
- short:2字节
- char:2字节
- int:4个字节
- float:4字节
- long:8字节
- double:8字节
- boolean:Java规范中并没有规定boolean类型所占字节数
1、Java中的元注解有哪些?(*)
Java中提供了4个元注解,元注解的作用是负责注解其它注解。
@Target:说明注解所修饰的对象范围;
@Retention:留策略定义了该注解被保留的时间长短。其中,SOURCE:表示在源文件中有效(即源文件保留);CLASS:表示在class文件中有效(即class保留);RUNTIME:表示在运行时有效(即运行时保留)。例如,@Retention(RetentionPolicy.RUNTIME)标注表示该注解在运行时有效。
@Documented:这个注解只是用来标注生成javadoc的时候是否会被记录。使用@Documented标注了,在生成javadoc的时候就会把@Documented注解给显示出来。
@Inherited:该注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
1、说说Java中反射机制?(*)
反射机制是指在运行中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法和属性。即动态获取信息和动态调用对象方法的功能称为反射机制。
如何获取Class类,获取Class类有三种基本方式:
通过类名称.class来获取Class类对象:
通过对象.getClass( )方法来获取Class类对象:
通过类名称加载类Class.forName( ),只要有类名称就可以得到Class:
知识点:
反射机制的作用:
- 在运行时判断任意一个对象所属的类
- 在运行时构造一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法,生成动态代(dai)理
与反射相关的类:
- Class:表示类,用于获取类的相关信息
- Field:表示成员变量,用于获取实例变量和静态变量等
- Method:表示方法,用于获取类中的方法参数和方法类型等
- Constructor:表示构造器,用于获取构造器的相关参数和类型等
1、Java中的Exception和Error有什么区别?
Throwable中提供两个子类:
- Error:此时程序还未执行出现的错误。
- Exception:程序中出现的异常,可以被捕获并进行相应的处理;
2、请解释RuntineException与Exception的区别?请例举出几个你常见的RuntimeException?
-
RuntimeException是Exception的子类,
-
RuntimeException标注的异常可以不需要进行强制性处理,而Exception异常必须强制性处理;
-
常见的RuntimeException异常:NumberFormatException、
ClassCastException、
NullPointerException等。
1、JIT编译器有了解吗?(*)
答:前面我们谈到了Java是一种先编译,后解释执行的语言。那么我们就来说下何为JIT编译器吧。
JIT编译器全名叫Just In Time Compile 也就是即时编译器,把经常运行的代码作为"热点代码"编译成与本地平台相关的机器码,并进行各种层次的优化。JIT编译除了具有缓存的功能外,还会对代码做各种优化,包括逃逸分析、锁消除、 锁膨胀、方法内联、空值检查消除、类型检测消除以及公共子表达式消除等。
知识点:
逃逸分析:
逃逸分析的基本行为就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。JIT编译器的优化包括如下:
- 同布省略:也就是锁消除,当JIT编译器判断不会产生并发问题,那么会将同步synchronized去掉
- 标量替换
我们先来解释下标量和聚合量的基本概念。
-
标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。
-
聚合量(Aggregate)是还可以分解的数据。Java中的对象就是聚合量,因为他可以分解成其他聚合量和标量。
在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换。标量替换的好处就是对象可以不在堆内存进行分配,为栈上分配提供了良好的基础。
那么逃逸分析技术存在哪些缺点呢?
技术不是特别成熟,分析的过程也很耗时,如果没有一个对象是不逃逸的,那么就得不偿失了。
1、Java中的值传递和引用传递可以解释下吗?
值传递和引用传递的解释可以概括如下。
- 值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。
- 引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。
因为如果参数是基本数据类型,那么是属于值传递的范畴,传递的其实是源对象的一个copy副本,不会影响源对象的值。
1、String、StringBuffer与StringBuilder的区别?
- String 类是字符串的首选类型,其最大的特点内容不可修改;
- StringBuffer与Stringbuilder类的内容允许修改;
- StringBuffer是在JDk1.0时候提供的,属于线程安全的操作;而StringBuilder是在JDK1.5提供的属于非线程安全的操作;
2、Java中的泛型的理解(*)
- 泛型的本质在于,类中的属性或方法的参数与返回值的类型,可以由对象的实例化的时候决定。
- 泛型之中只允许设置引用类型,如果现在要操作基本类型,则必须使用包装类,比如:使用Integer,不用int;
- 泛型是在编译期间有效,在运行阶段就会去泛型化,也就是将泛型信息抹掉,这也是不支持泛型数组的原因。
- 编译器可以在编译期提供一定的类型安全检查,过滤掉大部分因为类型不符而导致的运行时异常。
3、Java序列化与反序列化的过程
一个对象只要实现了 Serilizable接口,这个对象就可以被序列化。
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
JDK类库中序列化的步骤
步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\\objectfile.obj”));
步骤二:通过对象输出流的writeObject()方法写对象:
out.writeObject(“Hello”);
out.writeObject(new Date());
JDK类库中反序列化的步骤
步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
ObjectInputStream in = new ObjectInputStream(new fileInputStream(“D:\\objectfile.obj”));
步骤二:通过对象输出流的readObject()方法读取对象:
String obj1 = (String)in.readObject();
Date obj2 = (Date)in.readObject();
4、equals和hashCode方法的关系?
hashCode()方法和equals()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致。
一个是性能,一个是可靠性。他们之间的主要区别也基本体现在这里。
equals()既然已经能实现对比的功能了,为什么还要hashCode()呢?
因为重写的equals()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高。
hashCode()既然效率这么高为什么还要equals()呢?
- equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
-
hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。
5、Java和C++的区别有哪些?
- 所有代码(包括函数、变量等)必须在类中实现,除基本数据类型 (包括int、float等)外,所有类型都是类。此外,Java语言中不存在全局变量或全局函数,而C++兼具面向过程和面向过程编程的特点,可以定义全局变量和全局函数。
- 与C/C++语言相比,Java语言中没有指针概念,这有效防止了C/C++语言中操作指针可能引起的系统问题,从而使程序变得更加安全
- 与C++语言相比,Java语言不支持多重继承,但是Java语言引入了接口的概念,可以同时实现多个接口。由于接口也具有多态多态特性,因此在Java语言中可以通过实现多个接口来实现与C++语言中多重继承类似的目的。
- 在C++语言中,需要开发人员去管理对内存的分配(包括申请与释放),而Java语言提供了垃圾回收器来实现垃圾的自动回收,不需要程序显示地管理内存的分配。在C++语言中,通常都会把释放资源的代码放到析构函数中,Java语言中虽然没有析构函数,但却引入了一个 finalize() 方法,当垃圾回收器将要释放无用对象的内存时,会首先调用该对象的 finalize() 方法,因此,开发人员不需要关心也不需要知道对象所占的内存空间何时会被释放。
6、Java中equals方法和==的区别?
-
equals()方法用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断;
-
"==" 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。
7、静态与非静态的区别?(*)
这里的静态,指以static关键字修饰的,包括类,方法,块,字段。静态变量和静态方法都属于静态对象
非静态,指没有用static 修饰的。
静态对象 | 非静态对象 | |
拥有属性 | 是类共同拥有的 | 是类各对象独立拥有的 |
内存分配 | 内存空间上是固定的 | 空间在各个附属类里面分配 |
分配顺序 | 先分配静态对象的空间 | 继而再对非静态对象分配空间,也就是初始化顺序是先静态再非静态. |
静态有一些特点:
- 全局唯一,任何一次的修改都是全局性的影响
- 只加载一次,优先于非静态
- 使用方式上不依赖于实例对象。直接用 类名.静态方法名 或者 类名.静态变量名就可引用并且直接可以修改其属性值,不用get和set方法。
- 生命周期属于类级别,从JVM 加载开始到JVM卸载结束。
- static final用来修饰成员变量和成员方法,可简单理解为“全局常量”。对于变量,表示一旦给值就不可修改;对于方法,表示不可覆盖。
- 静态方法和静态变量创建后始终使用同一块内存
- 静态方法优点是效率高,缺点是不自动进行销毁
1、Map,List和Set都是Collection的子接口吗?
不是,list与set是Collection的子接口。map是与Collection并列的接口。
(1)、说说常见的集合?(*)
- Map接口和Collection接口是所有集合框架的父接口
- Collection接口的子接口包括:Set接口和List接口
- Map接口的实现类主要有:HashMap、TreeMap、Hashtable、LinkedHashMap、ConcurrentHashMap以及Properties等
- Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
- List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
(2)HashMap和Hashtable的区别有哪些?
- HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的;
- HashMap允许null作为Key;Hashtable不允许null作为Key,Hashtable的value也不可以为null
(3)HashMap是线程不安全的是吧?你可以举一个例子吗?
- HashMap线程不安全主要是考虑到了多线程环境下进行扩容可能会出现HashMap死循环
- Hashtable线程安全是由于其内部实现在put和remove等方法上使用synchronized进行了同步,所以对单个方法的使用是线程安全的。但是对多个方法进行复合操作时,线程安全性无法保证。 比如一个线程在进行get操作,一个线程在进行remove操作,往往会导致下标越界等异常。
知识点:
快速失败是Java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast。
例如:
假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就可能会抛出 ConcurrentModificationException异常,从而产生fast-fail快速失败。
(4)那么快速失败机制底层是怎么实现的呢?
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedModCount值,是的话就返回遍历;否则抛出异常,终止遍历。
2、HashMap底层实现结构有了解吗?
(1)HashMap的初始容量,加载因子,扩容增量是多少?
(2)HashMap的长度为什么是2的幂次方?
知识点:
HasMap的存储和获取原理:
当调用put()方法传递键和值来存储时,先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象,也就是找到了该元素应该被存储的桶中(数组)。当两个键的hashCode值相同时,bucket位置发生了冲突,也就是发生了Hash冲突,这个时候,会在每一个bucket后边接上一个链表(JDK8及以后的版本中还会加上红黑树)来解决,将新存储的键值对放在表头(也就是bucket中)。
当调用get方法获取存储的值时,首先根据键的hashCode找到对应的bucket,然后根据equals方法来在链表和红黑树中找到对应的值。
(3)HasMap的扩容步骤
(4)解决Hash冲突的方法有哪些?
- 拉链法 (HashMap使用的方法)
- 线性探测再散列法
- 二次探测再散列法
- 伪随机探测再散列法
(5)哪些类适合作为HashMap的键?
String和Interger这样的包装类很适合做为HashMap的键,因为他们是final类型的类,而且重写了equals和hashCode方法,避免了键值对改写,有效提高HashMap性能。
为了计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashCode的话,那么就不能从HashMap中找到你想要的对象。
2、ConcurrentHashMap和Hashtable的区别?
ConcurrentHashMap结合了HashMap和Hashtable二者的优势。HashMap没有考虑同步,Hashtable考虑了同步的问题。但是Hashtable在每次同步执行时都要锁住整个结构。
ConcurrentHashMap锁的方式是稍微细粒度的,ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常用操作只锁上当前需要用到的桶。
Java1.7ConcurrentHashMap的具体实现方式(分段锁):
3、TreeMap有哪些特性?
TreeMap底层使用红黑树实现,TreeMap中存储的键值对按照键来排序。
- 如果Key存入的是字符串等类型,那么会按照字典默认顺序排序
- 如果传入的是自定义引用类型,比如说User,那么该对象必须实现Comparable接口,并且覆写其compareTo方法;或者在创建TreeMap的时候,我们必须指定使用的比较器。
(1) 请解释Comparable与Comparator的区别?
- java.lang.Comparable是在类定义的时候实现的父接口,主要用于定义排序的规则,里面只有一个ComparaTo()方法;重新定义比较规则的时候,必须修改源代码。
- java.util.Comparator是挽救的比较器操作,需要单独设计比较器规则类实现排序,里面有Compare()方法
(2) ArrayList和LinkedList有哪些区别?
常用的ArrayList和LinkedList的区别总结如下。
- ArrayList底层使用了动态数组实现,实质上是一个动态数组
- LinkedList底层使用了双向链表实现,可当作堆栈、队列、双端队列使用
- ArrayList在随机存取方面效率高于LinkedList
- LinkedList在节点的增删方面效率高于ArrayList
- ArrayList必须预留一定的空间,当空间不足的时候,会进行扩容操作
- LinkedList的开销是必须存储节点的信息以及节点的指针信息
(3)HashSet和TreeSet有哪些区别?
HashSet和TreeSet的区别总结如下。
- HashSet底层使用了Hash表实现。
保证元素唯一性的原理:判断元素的hashCode值是否相同。如果相同,还会继续判断元素的equals方法,是否为true
- TreeSet底层使用了红黑树来实现。
保证元素唯一性是通过Comparable或者Comparator接口实现
(4)LinkedHashMap和LinkedHashSet有了解吗?
LinkedHashMap在面试题中还是比较常见的。LinkedHashMap可以记录下元素的插入顺序和访问顺序,具体实现如下:
- LinkedHashMap内部的Entry继承于HashMap.Node,这两个类都实现了Map.Entry<K,V>
- LinkedHashMap的Entry不光有value,next,还有before和after属性,这样通过一个双向链表,保证了各个元素的插入顺序。
- 通过构造方法public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder), accessOrder传入true可以实现LRU缓存算法(访问顺序)
- LinkedHashSet 底层使用LinkedHashMap实现,两者的关系类似与HashMap和HashSet的关系,大家可以自行类比。
(5) 什么是LRU算法?LinkedHashMap如何实现LRU算法?
LRU(Least recently used,最近最少使用)算法根据局部性原理,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
由于LinkedHashMap可以记录下Map中元素的访问顺序,所以可以轻易的实现LRU算法。只需要将构造方法的accessOrder传入true,并且重写removeEldestEntry方法即可。
package pak2;
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUTest {
private static int size = 5;
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<String, String>(size, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
return size() > size;
}
};
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");
map.put("5", "5");
System.out.println(map.toString());
map.put("6", "6");
System.out.println(map.toString());
map.get("3");
System.out.println(map.toString());
map.put("7", "7");
System.out.println(map.toString());
map.get("5");
System.out.println(map.toString());
}
}
(6)List和Set的区别?
List和Set的区别可以简单总结如下。
- List是有序的并且元素是可以重复的
- Set是无序(LinkedHashSet除外)的,并且元素是不可以重复的
(此处的有序和无序是指放入顺序和取出顺序是否保持一致)
(7)Iterator和ListIterator的区别是什么?
常见的两种迭代器的区别如下。
- Iterator可以遍历list和set集合;ListIterator只能用来遍历list集合
- Iterator前者只能前向遍历集合;ListIterator可以前向和后向遍历集合
- ListIterator其实就是实现了前者,并且增加了一些新的功能。
(8)数组和集合List之间的转换:
数组和集合Lis的转换在我们的日常开发中是很常见的一种操作,主要通过Arrays.asList以及List.toArray方法来搞定。
Object[] arr = list.toArray();
List<String> asList = Arrays.asList(arr2);