【Java面试题】基础知识

1、java语言的三大特性

  • 封装
  • 继承extends
    • 继承后的子类自动拥有父类的属性和方法,但是注意,父类的私有属性和构造方法并不能被继承。
    • 子类也可以写自己特有的属性和方法,目的是实现功能的扩展。
    • 子类也可以重写父类的方法。
  • 多态Parent p = new Child();
    • 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
    • 多态需要向上转型(upcast),其中向上转型是由JVM自动实现的、是安全的的。但向下转型(downcast)是不安全的。需要强制转换。

2、JDK和JRE有什么区别?

  • JDK(Java Development Kit):Java开发工具包,提供了Java的开发环境和运行环境。
  • JRE(Java Runtime Environment):java运行环境,为Java的运行提供了所需环境。

具体来说JDK其实包含了JRE,同时还包含了编译Java源码的编译器Javac,还包含了很多Java程序调试和分析的工具。简单来说:如果你需要运行Java程序,只需安装JRE就可以了,如果你需要编写Java程序,需要安装JDK。

在这里插入图片描述

3、Java基本数据类型及其封装类

基本类型大小(字节)默认值封装类
byte1(byte)0Byte
short2(short)0Short
int40Integer
long80LLong
float40.0fFloat
double80.0dDouble
boolean-falseBoolean
char2\u0000(null)Character
  • boolean单独使用是占4个字节,在数组中又是1个字节。
  • 基本类型放在栈中,直接存储值。
  • 所有数值类型都有正负号,没有无符号的数值类型。

为什么需要封装类,只有基本类型不行吗?

因为泛型类包括预定义的集合,使用的参数都是对象类型,无法直接使用基本数据类型,所以Java又提供了这些基本类型的封装。

基本类型和对应的封装类由于本质的不同,具有一些区别:

  • 基本类型只能按值传递,而封装类按引用传递。
  • 基本类型会在中创建,而对于对象类型,对象在中创建,对象的引用在栈中创建,基本类型由于在栈中,效率会比较高,但是可能存在内存泄漏的问题。

4、说明一下public static void main(String[] args)这段声明里每个关键字的作用

  • public:main方法是Java程序运行时调用的第一个方法,因此它必须对Java环境可见,所以可见性设置为public。
  • static:Java平台调用这个方法时不会创建这个类的一个实例,因此这个方法必须声明为static。
  • void:main方法没有返回值。
  • String是命令行传进参数的类型,args是指命令行传进的字符串数组。

如果main方法被声明为private会怎样?

能正常编译,但运行的时候会提示“main方法不是public的”。在idea中如果不用public修饰,则会自动去掉可运行的按钮。

5、==与equals的区别

  • ==(地址):比较两个对象在内存里是不是同一个对象,就是说在内存里的存储位置是否一致。两个 String 对象存储的值是一样的,但有可能在内存里存储在不同的地方。
  • equals(内容):==比较的是引用,而equals比较的是内容。
    • public boolean equals(Object obj)这个方法是由Object对象提供的,可以由子类进行重写。
    • 默认的实现只有当对象和自身进行比较时才会返回true,这个时候和==是等价的。
    • String、BitSet、Date 和 File 都对 equals 方法进行了重写:对两个 String 对象而言,值相等意味着它们包含同样的字符序列;对于基本类型的包装类来说,值相等意味着对应的基本类型的值一样。

6、Object 有哪些公用方法

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 转换成字符串,一般子类都有重写,否则打印句柄。

7、为什么Java里没有全局变量?

全局变量是指可以全局访问的变量,Java不支持全局变量,原因如下:

  • 全局变量破坏了引用的透明性。
  • 全局变量制造了命名空间冲突。

8、char型变量中能不能存储一个中文汉字?为什么?

可以。Java默认Unicode编码。Unicode码占16位。char两个字节刚好16位。

9、Java访问修饰符

作用域当前类同一package子孙类其他package
public
protected
default
private

10、float f = 3.4;是否正确?

不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换 float f = (float) 3.4; 或者写成 float f = 3.4F;

11、short s1=1; s1=s1 + 1;有错吗?short s1=1; s1 += 1;有错吗?

  • short s1=1; s1=s1 + 1;由于1是int类型,因此s1+1运算结果也是int型,需要强制转换类型才能赋值给short型。
  • short s1=1; s1 += 1;,+=操作符会进行隐式自动类型转换,是Java语言规定的运算符;Java编译器会对它进行特殊处理,因此可以正确编译。因为s1 += 1,相当于s1 = (short) (s1+1)

12、& 与 && 的区别

  • &
    • 按位与: 0&0=0;0&1=0;1&0=0;1&1=1
    • 逻辑与:a==b & b==c即使已经知道a==b是false了,程序还会继续判断 b 是否等于 c
  • &&(短路与):a==b && b==c 如果已经知道a==b是false了,程序不会继续判断 b 是否等于 c

13、IntegerCache

public class IntegerTest {
	public static void main(String[] args) {
		Integer a = 100, b = 100 ,c = 129,d = 129;
		System.out.println(a==b);//结果是true
		System.out.println(c==d);//结果是false
	}
}

通过阅读源码可以发现,Java在-128~127之间做了缓存。考虑到高频数值的复用场景,这样做还是很合理的优化,最大边界可以通过-XX:AutoBoxCacheMax进行配置。

14、Locale类是什么

Locale类用来根据语言环境动态地调整程序的输出。

15、Java 中 final、finally、finalize的区别与用法

1、final

  • final是一个修饰符也是一个关键字。
  • 被final修饰的类无法被继承。(不能有儿子)
  • 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是它指向的对象的内容是可变的。
  • 被final修饰的方法将无法被重写,但允许重载。
    注意:类的private方法会隐式地被指定为finall方法。

2、finally

  • finally是一个关键字。
  • finally在异常处理时提供 finally 块来执行任何清除操作。不管有没有异常被抛出或者捕获,finally块都会执行,通常用于释放资源。
  • finally块正常情况下一定会被执行。但是有至少两个极端情况:
    • 如果对应的try块没有执行,则这个try块的finally块并不会被执行。
    • 如果在try块中JVM关机,例如System.exit(n),则finally块也不会执行(都拔电源了,怎么执行)
  • finally块中如果有return语句,则会覆盖try或者catch中的return语句,导致二者无法return,所以强烈建议finally块中不要存在return关键字。

3、finalize

  • finalize()是Object类的protected方法,子类可以覆盖该方法以实现资源清理工作。
  • GC在回收对象之前都会调用该方法
  • finalize()方法是存在很多问题的:
    • java语言规范并不保证finalize方法会被及时地执行,更根本不会保证它们一定会被执行。
    • finalize()方法可能带来性能问题,因为JVM通常在单独的低优先级线程中完成finalize的执行。
    • finalize()方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的。
    • finalize方法最多由GC执行一次(但可以手动调用对象的finalize方法)

16、hashCode()和equals()的区别

hashCode()方法和equals()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致。

下边从两个角度介绍了他们的区别:一个是性能,一个是可靠性。他们之间的主要区别也基本体现在这里。

1、equals()既然已经能实现对比的功能了,为什么还要hashCode()呢?

因为重写的equals()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高。

2、hashCode()既然效率这么高为什么还要equals()呢?

因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出:

  • equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
  • hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。

拓展

阿里巴巴开发规范明确规定:

  • 只要重写 equals,就必须重写 hashCode;
  • 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法;
  • 如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals;
  • String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象作为 key 来使用;

1、什么时候需要重写?

一般的地方不需要重写hashCode,只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重写hashCode。

2、那么为什么要重写hashCode呢?

如果你重写了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。

这样,当你用其中的一个作为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一个作为键值去查找他们的时候,则根本找不到。

3、为什么equals()相等,hashCode就一定要相等,而hashCode相等,却不要求equals相等?

  • 因为是按照hashCode来访问小内存块,所以hashCode必须相等。
  • HashMap获取一个对象是比较key的hashCode相等和equals为true。

之所以hashCode相等,却可以equal不等,就比如ObjectA和ObjectB他们都有属性name,那么hashCode都以name计算,所以hashCode一样,但是两个对象属于不同类型,所以equals为false。

(也就是说,我们会先比较两个对象的hashCode,如果hashCode相等了才会去比较equals)

4、为什么需要hashCode?

  • 通过hashCode可以很快的查到小内存块。
  • 通过hashCode比较比equals方法快,当get时先比较hashCode,如果hashCode不同,直接返回false。

17、深拷贝和浅拷贝的区别是什么?

  • 浅拷贝
    • 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对"主"对象进行拷贝,但不会复制主对象里面的对象。"里面的对象"会在原来的对象和它的副本之间共享。
    • 简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
  • 深拷贝
    • 深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
    • 简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

18、Java中操作字符串都有哪些类?它们之间有什么区别?

String、StringBuffer、StringBuilder。

String和StringBuffer、StringBuilder的区别在于String声明的是不可变的对象,每次操作都会生成新的String对象,然后将指针指向新的String对象,而StringBuffer、StringBuilder可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用String。

StringBuffer和StringBuilder最大的区别在于,StringBuffer是线程安全的,而StringBuilder是非线程安全的,但StringBuilder的性能却高于StringBuffer,所以在单线程环境下推荐使用StringBuilder,多线程环境下推荐使用StringBuffer。

19、String str="a"String str=new String("a") 一样吗?

不一样,因为内存的分配方式不一样。

  • String str="a"----->常量池
  • String str=new String("a") ----->堆内存

20、抽象类能使用final修饰吗?

不能。定义抽象类就是让其他类继承的,而 final 修饰的类不能被继承。

21、static关键字5连问

(1)抽象的(abstract)方法是否可以同时是静态的(static)?

抽象方法将来是要被重写的,而静态方法不能重写的,所以这个是错误的。

(2)是否可以从一个静态(static)方法内部发出对非静态方法的调用?

不可以,静态方法只能访问静态成员,非静态方法的调用要先创建对象。

(3)static可否用来修饰局部变量?

static不允许用来修饰局部变量。

(4)内部类与静态内部类的区别?

静态内部类相对与外部类是独立存在的,在静态内部类中无法直接访问外部类中变量、方法。如果要访问的话,必须要new一个外部类的对象,使用new出来的对象来访问。但是可以直接访问静态的变量、调用静态的方法;

普通内部类作为外部类一个成员而存在,在普通内部类中可以直接访问外部类属性,调用外部类的方法。

如果外部类要访问内部类的属性或者调用内部类的方法,必须要创建一个内部类的对象,使用该对象访问属性或者调用方法。

如果其他的类要访问普通内部类的属性或者调用普通内部类的方法,必须要在外部类中创建一个普通内部类的对象作为一个属性,外部类可以通过该属性调用普通内部类的方法或者访问普通内部类的属性。

如果其他的类要访问静态内部类的属性或者调用静态内部类的方法,直接创建一个静态内部类对象即可。

(5)Java中是否可以覆盖/重写(override)一个private或者static的方法?

Java中static方法不能被重写,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。

private只能够被自身类访问,子类不能访问private修饰的成员,所有不能override一个private方法。

22、重载(Overload)和重写(Override)的区别。重载方法能否根据返回类型进行区分?

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;

重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。

重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

23、Java的四种引用

1、强引用

最普遍的一种引用方式,如String s = "abc",变量s就是字符串“abc”的强引用,只要强引用存在,则垃圾回收器就不会回收这个对象。

2、软引用(SoftReference)

用于描述还有用但非必须的对象,如果内存足够,不回收,如果内存不足,则回收。一般用于实现内存敏感的高速缓存,软引用可以和引用队列ReferenceQueue联合使用,如果软引用的对象被垃圾回收,JVM就会把这个软引用加入到与之关联的引用队列中。

3、弱引用(WeakReference)

弱引用和软引用大致相同,弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

4、虚引用(PhantomReference)

就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

虚引用与软引用、弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

24、Java中Comparator与Comparable有什么不同?

Comparable接口用于定义对象的自然顺序,是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator通常用于定义用户定制的顺序,是比较接口。我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器“来进行排序。Comparable总是只有一个,但是可以有多个Comparator来定义对象的顺序。

Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。

两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码(重写compareTo()方法);用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。

25、Java序列化,反序列化?

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

26、什么情况需要Java序列化?

对象序列化的两种用途:

  • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  • 在网络上传送对象的字节序列。

为什么需要序列化?

我们可以想想如果没有序列化之前,又是怎样一种情景呢?

举例:

  • Web 服务器中的 Session 会话对象,当有10万用户并发访问,就有可能出现10万个 Session 对象,显然这种情况内存可能是吃不消的。于是 Web 容器就会把一些 Session 先序列化,让他们离开内存空间,序列化到硬盘中,当需要调用时,再把保存在硬盘中的对象还原到内存中。
  • 我们知道,当两个进程进行远程通信时,彼此可以发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。同样的序列化与反序列化则实现了 进程通信间的对象传送,发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

初步总结:Java 序列化和反序列化,其一,实现了数据的持久化,通过序列化可以把数据永久的保存在硬盘上;其二,利用序列化实现远程通信,即在网络上传递对象的字节序列。

27、序列化的实现?

先来介绍JDK中的两个关键类:ObjectOutputStream(对象输出流) 和ObjectInputStream(对象输入流)

  • ObjectOutputStream 类中:通过使用 writeObject(Object object) 方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
  • ObjectInputStream 类中:通过使用 readObject()方法,从输入流中读取二进制流,转换成对象。

只有实现了SerializableExternalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式 。

对象序列化包括如下步骤:

  • 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
  • 通过对象输出流的writeObject()方法写对象。

比如:ObjectOutputStream 对象输出流,将Person对象存储到E盘的Person.txt文件中,完成对Person对象的序列化操作

//关键代码
//1.创建一个对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
		new File("E:/Person.txt")));
//2.通过对象输出流的writeObject()方法写对象。当然Person person是实现了Serializable接口的
oos.writeObject(person);

对象反序列化的步骤如下:

  • 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
  • 通过对象输入流的readObject()方法读取对象。
//关键代码
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
		new File("E:/Person.txt")));
Person person = (Person) ois.readObject();

28、如果某些数据不想序列化,如何处理?

在字段面前加transient关键字,例如:transient private String phone;//不参与序列化

29、Java泛型和类型擦除?

泛型:即参数化类型,在创建集合时,指定集合元素的类型,此集合只能传入该类型的参数。

Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除

如在代码中定义List<Object>List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。

类型擦除的主要过程如下:

  1. 将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
  2. 移除所有的类型参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值