【面试题系列】——Java基础
本文主要包括Java基础及面向对象相关面试题。
1,Java科普
1.1 为什么安装包要分JDK和JRE?
JRE主要包含JVM,用于运行Java程序。
JDK包含了JRE,除此之外,包含了比如像javac等程序开发需要用到的工具。
先来说说什么是JDK,JRE。
JDK:Java Development Kit Java开发工具包【开发Java程序用】
JRE:Java Runtiome Environment Java运行环境【运行Java程序用】
【注:下载的JDK包是包含了JDK和JRE的,JDK和JRE是逻辑上的区分,两者在JDK下载的包中都有】
JDK与JRE的关系:以Java代码运行为例,编写好Java代码之后,通过javac将java源文件编译成class字节码文件,然后通过java命令,运行字节码文件。那么运行字节码的环境就是JRE。(JRE的核心就是JVM)
了解完JDK和JRE是什么之后,再聊聊为什么开发者当时要把一个安装包分成两部分呢?
平常使用的软件都是一键安装的,但JDK需要安装两次。JDK的发明者不会这么无聊,故意给开发者增加麻烦。
【我想】:这应该跟生产环境的部署问题有关,关于生产环境部署JDK还是JRE一直饱受争议,具体情况根据项目而定。
出于对性能的考虑,尽可能的使服务器轻,能少装一个软件就少装一个,这样生产环境部署JRE就OK了。【又省了资源】
除了这个方面之外,还有一种可能。JDK的开发也有可能是分团队的,JDK和JRE可能是交由不同团队开发,JDK和JRE的耦合也可能因此而减小,从而加快JDK的迭代版本。(毕竟现在JDK一年更新两次)
1.2 为什么Java语言是跨平台的?
JVM有两个功能
- 将class字节码转换为机器码
- 兼容不同的操作系统
跨平台和Java 虚拟机有关。
JVM有两个主要的功能:
- 适配不同的操作系统的指令集(兼容不同的操作系统)
- 翻译字节码文件为机器码执行
(Oracle官网上下载JDK,不同操作系统的JDK是不一样的,对应不同的虚拟机)
1.3 为什么安装完JDK后要设置环境变量?
在解决这个问题之前,先来了解一些环境变量是干嘛的:
环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。例如Windows和DOS操作系统中的path环境变量,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到path中指定的路径去找。用户通过设置环境变量,来更好的运行进程。
简单来说,运行一个程序(命令),操作系统会从当前目录寻找,或者从环境变量中寻找。
换句话说,如果在java,javac的目录下执行这个两个命令,是没有问题的。但是如果更换了目录,系统在当前目录找不到,就会去环境变量中寻找。所以设置环境变量的根本目的是在电脑的任何一个文件夹下都可以编译运行Java程序。
1.4 Java和C++区别
题外话,Java和C++有什么区别?(据说有些面试官老爱干这种事)
- C++支持多继承,Java支持单继承
- Java有垃圾回收机制
- Java不支持指针,更加安全
2,数据类型&变量
2.1 boolean占几个字节?
表示变量:4个字节(转换为int存储)
表示byte数组:1个字节
2.2 为什么long可以自动转换为float?
float,double采用指数方式存储,能表示的数比long更大
System.out.println(Long.MAX_VALUE);
System.out.println(Float.MAX_VALUE);
System.out.println(Double.MAX_VALUE);
System.out.println(Float.MAX_VALUE>Long.MAX_VALUE);
9223372036854775807
3.4028235E38
1.7976931348623157E308
true
float和double使用指数表示,取值范围在
-3.4*10^38 - 3.4*10^38
顺便看看float和double的精度:7位和16位。
2.3 包装类型和基本数据类型的区别
初始值:包装类型的初始值为null,基本数据类型的初始值基本上是0,char是'u000'
存储方式:包装类型存在堆里,基本数据类型存在栈中
2.4 String,StringBuffer,StringBuilder的区别
- 可变性
String底层使用fina修饰的数组实现,是不可变的,StringBuffer和StringBuilder是可变的
- 安全性
String不可变,自然线程安全,StringBuffer使用synchronized同步锁实现线程安全,StringBuilder是线程不安全的
-
可变性
String是不可变的,StringBuffer和StringBuilder是可变的
String string1 = "111"; string1 = "222"; System.out.println(string1); string1指向222时,创建了一个新的对象并指向它
String 底层使用final修饰的数组实现,故是不变的
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[];
-
安全性
StringBuffer和String是线程安全的,,StringBuilder是线程不安全的。
String 中的对象是不可变的,也就可以理解为常量,线程安全。
StringBuffer 对方法加了synchronized同步锁所以是线程安全的。
-
对于三者使用的总结
- 操作少量的数据: 适用String
- 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer
2.5 String s = “Hello”;s = s + “world!”;这两行代码执行后,原始的String对象中的内容到底变了没有?
没有,只是指向了另外一个对象。
因为String被设计成不可变(immutable)类(final修饰),所以它的所有对象都是不可变对象。
在这段代码中,s原先指向一个String对象,内容是 “Hello”,然后对s进行了+操作
这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为”Hello world!”
原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
2.6 String str =“i”;和 String str = new String(“i”);有区别吗?
前者会被JVM分配到常量池中,常量池中没有重复的元素,如果再次创建一个字符串变量等于i的话,就会直接指向常量池中的i。
后者是new了一个对象,堆中是允许对象重复的。
前者会被JVM分配到常量池中,常量池中没有重复的元素。
String str1 =“i”;
String str2 =“i”;
str2不会重新创建一个常量,而是指向str1。
String str1 = new String("i");
String str2 = new String("i");
str1会在堆内存中创建对象
str2还是会再次创建一个新的对象
2.7 如何将字符串反转?
使用StringBuilder或者StringBuffer的reverse()方法。
StringBuffer str1 = new StringBuffer("12345");
StringBuffer str2 = str1.reverse();
System.out.println(str2);
54321
2.8 Java有几种变量?
-
类变量
独立于方法之外,必须用static修饰
-
实例变量
独立于方法之外,不用static修饰
-
局部变量
方法中的变量
2.9 引用数据类型包含哪几种?
- 类
- 接口
- 数组
3,运算符
3.1 ==和equals的区别
equals用于比较内容是否相同(源自Object类,通常被重写)
==对于基本数据类型比较值相同,对于引用数据类型比较内存地址是否相同(是否是同一个对象)
知乎上非常形象的一张图:
3.2 &和&&区别(|和||同理)
& : 两边都为true时才为true
|:两边有一个为false即为false
短路逻辑运算符
&&:左边为fasle直接返回flase(不计算右边)
||:左边为true直接返回true(不计算右边)
就是为了简化计算量
^表示异或,相同为false,不同为true
4,面向对象
4.1 什么是面向对象?
如果摒弃软件开发的范畴,这是一种通过明确社会分工而提高效率的方法。
在软件开发的范围内,就是通过抽象出系统功能而实现最大化代码复用的开发模式。
4.2 封装相关
-
什么是封装
通过隐藏实现,暴露接口,一来实现代码解耦,二来通过访问修饰符保证数据安全。
4.3 继承相关
-
继承的作用?
实现代码复用
-
继承的规则?
- 子类继承父类非private的属性和方法
- 子类可以扩展自己的属性和方法
-
构造器是否会被继承?
-
构造器不会被继承,但子类对象初始化时会调用父类无参构造器
【为什么。子类和父类有最基本的依赖关系,比如说数据依赖】
-
当父类显式写了有参构造器,且没有无参构造器。子类继承父类的时候必须显式的调用父类的有参构造器。调用的方式可以使用super(a,b)来调用。
-
-
子类父类的初始化顺序
原则:静态优于非静态,父类优于子类 - 父类静态变量,静态语句块 - 子类静态变量,静态语句块 - 父类非静态代码块,构造器 - 子类非静态代码块,构造器
4.4 多态相关
-
什么是多态?
一类事物的多种表现形态。(比如手机有各种各样的品牌,但都属于手机这一类事物)
-
如何体现多态?
方法重载:针对本类的不同方法而言,方法名相同,参数不同(个数,顺序)【返回类型随意】
方法重写:针对继承而言,除了方法体可以自定义外,其他必须与父类保持一致(方法名,返回类型,参数)
-
向上转型&向下转型
up:子类转换为父类,目的是访问父类的公共方法,实现代码的复用和简洁(比如100个类把公共方法写在父类中,就不需要每个类都写一遍了)
down:父类转换为子类,据说是为了调用子类的扩展方法。(为啥不直接new一个对象,已提交知乎问答)
4.5 关键词static
-
修饰变量
称为静态变量,类变量,全局变量
可直接通过类名.变量名访问
-
修饰方法
称为静态方法,类方法
可通过类名.方法名直接访问
- 非static方法可以访问static方法,static方法不能访问非static方法
4.6 关键词final
- 修饰类不能被继承
- 修饰方法不能被重写
- 修饰变量则变量变为常量
4.7 接口和抽象类的区别?
-
继承与实现
类只能单继承,但可以实现多接口
-
方法是否能实现
抽象类不仅可以做方法声明,也可以做方法实现。(接口只能做方法声明)
-
修饰符
接口的变量都默认采用final修饰,方法采用public修饰。
抽象类可自定义。
4.8 为什么重写equals必须重写hashCode?
equals和hashCode位于Object类中,所有的类都会继承Object类。
equals通常被用来比较对象的内容是否相同,hashCode是用来返回对象Hash值的一种方法。
如果不重写hashCode会导致,equals相同但hashCode不相同,equals不相同但hashCode相同。
重写的目的一来为了避免hash冲突,二来提高对象访问速度。