java基础面试常见问题汇总
- 面向对象的特征有哪些方面?
- Object类中的方法
- ==和equals方法的区别
- 八种基本数据类型的大小,以及他们的封装类
- 关于hashCode方法
- String、StringBuffer与StringBuilder的区别
- int和Integer有什么区别?
- 阐述final、finally、finalize的区别
- 重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
- 八种基本数据类型的大小,以及他们的封装类
- Error和Exception有什么区别?
- java中如何实现比较器
- 面向对象的特征有哪些方面?
1)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
2)继承:是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类,复用父类基类的信息(包括方法和属性)
3)封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口
4)多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。
- 关于Object类中的方法
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
public final native Class> getClass();
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }
}
Object是所有类的父类,任何类都默认继承Object
- clone 保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
- equals 在Object中与==是一样的,子类一般需要重写该方法。
- hashCode 该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
- getClass final方法,获得运行时类型
- wait 使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。 wait() 方法一直等待,直到获得锁或者被中断。 wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。调用该方法后当前线程进入睡眠状态,直到以下事件发生1、其他线程调用了该对象的notify方法。 2、其他线程调用了该对象的notifyAll方法。 3、其他线程调用了interrupt中断该线程。 4、时间间隔到了。 5、此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
- notify 唤醒在该对象上等待的某个线程。
- notifyAll 唤醒在该对象上等待的所有线程。
- toString 转换成字符串,一般子类都有重写,否则打印句柄。
- == 和equals的区别
1)基本类型
== 比较的是值
基本类型不存在equals方法,equals方法是Object类中的方法
2)引用类型
== 比较的是地址,equals方法底层使用的是==
是否覆盖Object类中的equals方法,如果覆盖,以覆盖的为准,一般是比较值,没重写,就是Object类中的方法
例如:String的equals方法
- 八种基本数据类型的大小,以及他们的封装类
八种基本数据类型:int、short、float、double、long、boolean、byte、char。
封装类分别是:Integer、Short、Float、Double、Long、Boolean、Byte、Character
- 关于hashCode方法
1)HashCode的特性
- HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储地址。
- 如果两个对象相同,equals方法一定返回true,并且这两个对象的HashCode一定相同。
- 两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能够说明这两个对象在一个散列存储结构中。
- 如果对象的equals方法被重写,那么对象的HashCode也尽量重写。
2)HashCode作用:加快查找速度
Java中的集合有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
equals方法可用于保证元素不重复,但如果每增加一个元素就检查一次,若集合中现在已经有1000个元素,那么第1001个元素加入集合时,就要调用1000次equals方法。这显然会大大降低效率。于是,Java采用了哈希表的原理。
哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。
这样一来,当集合要添加新的元素时,先调用这个元素的HashCode方法,就一下子能定位到它应该放置的物理位置上。
(1)如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了。
(2)如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了。
(3)不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同HashCode的对象放到这个单链表上去,串在一起(很少出现)。
这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
如何理解HashCode的作用:
从Object角度看,JVM每new一个Object,它都会将这个Object丢到一个Hash表中去,这样的话,下次做Object的比较或者取这个对象的时候(读取过程),它会根据对象的HashCode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。若HashCode相同再去调用equal。
3)为什么需要重写了equals方法也要重写hashCode方法
一般的地方不需要重载hashCode,只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode,那么为什么要重载hashCode呢?
如果你重写了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。
这样,当你用其中的一个作为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一个作为键值去查找他们的时候,则根本找不到。
为什么equals()相等,hashCode就一定要相等,而hashCode相等,却不要求equals相等?
1、因为是按照hashCode来访问小内存块,所以hashCode必须相等。 2、HashMap获取一个对象是比较key的hashCode相等和equal为true。
之所以hashCode相等,却可以equal不等,就比如ObjectA和ObjectB他们都有属性name,那么hashCode都以name计算,所以hashCode一样,但是两个对象属于不同类型,所以equal为false。
为什么需要hashCode?
1、通过hashCode可以很快的查到小内存块。 2、通过hashCode比较比equal方法快,当get时先比较hashCode,如果hashCode不同,直接返回false。
-
String、StringBuffer与StringBuilder的区别
1)String 不可变字符串,线程安全,对字符串的操作都是创建新的字符串对象,效率低
适用于少量的字符串操作的情况
不能被继承
其中value[] 数组 使用final修饰,不可变
2)StringBuffer 线程安全,
适用多线程下在字符缓冲区进行大量操作的情况。
定义
对本字符串进行操作,返回本字符串
方法都是使用synchronized修饰
3)StringBuilder 线程不安全
适用于单线程下在字符缓冲区进行大量操作的情况
定义
方法并没有synchronized修饰
这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String。
String最慢的原因
String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。
再来说线程安全
在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的。
7.int和Integer有什么区别?
1)Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:
原始类型:boolean,char,byte,short,int,long,float,double
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
2)Integer缓存
Integer定义
public static void main(String[] args) {
Integer a1 = 100, a2 = 100, a3 = 150, a4 = 150;
System.out.println(a1 == a2); //true
System.out.println(a3 == a4); //false
}
自动装箱:
给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf
范围:-128---127
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象
8.阐述final、finally、finalize的区别
1)final:修饰符(关键字)有三种用法:
java.lang.Integer
如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。
将变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。
被声明为final的方法也同样只能使用,不能在子类中被重写。
java.lang.AbstractStringBuilder
2)finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。 流、连接、锁等资源的释放
3)finalize:
Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。
finalize方法是Object提供的的实例方法,使用规则如下:
- 当对象不再被任何对象引用时,GC会调用该对象的finalize()方法
- finalize()是Object的方法,子类可以覆盖这个方法来做一些系统资源的释放或者数据的清理
- 可以在finalize()让这个对象再次被引用,避免被GC回收;但是最常用的目的还是做cleanup
- Java不保证这个finalize()一定被执行;但是保证调用finalize的线程没有持有任何user-visible同步锁。
- 在finalize里面抛出的异常会被忽略,同时方法终止。
- 当finalize被调用之后,JVM会再一次检测这个对象是否能被存活的线程访问得到,如果不是,则清除该对象。也就是finalize只能被调用一次;也就是说,覆盖了finalize方法的对象需要经过两个GC周期才能被清除。
java.io.FileInputStream 重写finalize方法
9.重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性
1)overload 重载
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载
重载对返回类型没有特殊的要求。
构造函数重载
普通方法重载
java.lang.String
2)override 重写
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常
3)为什么不能根据返回类型来区分重载?
在Java语言中,要重载一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名;
特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里面是无法仅仅依靠返回值的不同来对一个已有方法进行重载。
但在Class文件格式之中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法也可以共存。
也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法存于同一个Class文件中的。
Class文件中同方法名、同参数、不同返回值可以,那为什么Java文件中不行呢?
因为Java语言规范的规定,所以编译时会出现错误。
那为什么Class文件可以呢?因为Java虚拟机规范和Java语言规范不同,两者是分开的...
10.Error和Exception有什么区别?
顶级对象
1)error 错误
Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况
工作中遇到的error
StackOverflowError 栈内存错误,可能遇到递归未退出的情况
如栈内存错误,递归调用未退出则栈内存溢出。
OutOfMemoryError 内存溢出, tomcat启动的时候报错,调大omcat jvm的内存
2)Exception
Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。
3)RuntimeException 非检查异常 运行时异常
可以直接抛出,可以进行处理,也可以不进行处理
11.java中如何实现比较器
1)Comparable---接口(集合中元素实现此接口,元素具有可比性)
compareTo(Object o)方法的返回值是int,且此方法只有一个参数,返回值有三种情况:
1、返回正整数
2、返回0
3、返回负整数
可以这么理解:返回1表示当前元素排在与之对比的元素后面,返回-1表示当前元素排在与之对比的元素前面,返回0表示不排序(按其原顺序排列)。(其实并不是1,-1,0;只要是正数负数和0就可以进行区分)。
元素自身可以理解为基准,而参数上的obj可以理解为与之对比的元素。
package com.zengqingfa.base;
/*
* @ author zengqingfa
* @ created in 2019/8/13 22:17
*
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Person implements Comparable {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int compareTo(Person o) {
if (this.age > o.getAge()) {//返回负数表示比他大的排在他前面
return 1;
}
if (this.age < o.getAge()) {//返回整数表示比他小的排在他后面
return -1;
}
return 0; //表示相等
}
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + "]";
}
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Person(20, "张三"));
list.add(new Person(22, "李四"));
list.add(new Person(19, "王五"));
list.add(new Person(19, "张是"));
list.add(new Person(17, "开发"));
list.add(new Person(29, "看"));
Collections.sort(list);
for (Person p : list) {
System.out.println(p);
}
/**
* Person [age=17, name=开发]
Person [age=19, name=王五]
Person [age=19, name=张是]
Person [age=20, name=张三]
Person [age=22, name=李四]
Person [age=29, name=看]
*/
}
}
2) Comparator---接口(可以理解为比较器,给集合传递比较器集合具有可比性)
Comparator相当于外部比较器,其作为参数传给具有可比性的集合,使集合具有可比性。比如: TreeSet ts = new TreeSet(new MyComparator()); 比较器需要重写compareTo(Object o1,Object o2)方法,返回值也是下面三个值:
1、返回正整数
2、返回0
3、返回负整数
可以这么理解:返回1表示当前元素排在与之对比的元素后面,返回-1表示当前元素排在与之对比的元素前面,返回0表示不排序(按其原顺序排列)。(其实并不是1,-1,0;只要是正数负数和0就可以进行区分)
compareTo(Object o1,Object o2)方法的第一个参数可以理解为基准,而参数上的第二个参数可以理解为与之对比的元素。
package com.zengqingfa.base;
/*
* @ author zengqingfa
* @ created in 2019/8/13 22:25
*
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class PersonComparator implements Comparator {
/**
* 第一个参数可以理解为基准,第二个是与之比较多元素 。返回负数表示排在其前面,返回正数表示排在其后面
*/
public int compare(Person o1, Person o2) {
if (o1.getAge() > o2.getAge()) {// 返回负数表示比他大的排在他前面
return -1;
}
if (o1.getAge() < o2.getAge()) {//返回正数表示比他大的排在他后面
return 1;
}
return 0;
}
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Person(20, "张三"));
list.add(new Person(22, "李四"));
list.add(new Person(19, "王五"));
list.add(new Person(19, "张是"));
list.add(new Person(17, "开发"));
list.add(new Person(29, "看"));
Collections.sort(list, new PersonComparator());
for (Person p : list) {
System.out.println(p);
}
/**
* Person [age=29, name=看]
Person [age=22, name=李四]
Person [age=20, name=张三]
Person [age=19, name=王五]
Person [age=19, name=张是]
Person [age=17, name=开发]
*/
}
}