【面试复习】 —— JavaSE
文章目录
一. Java概述
1. JVM、JRE 和 JDK
JVM
Java Virtual Machine是Java虚拟机。Java程序需要在虚拟机上运行,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。
JRE
Java Runtime Environment包括Java虚拟机和Java程序所需要的核心类库等。核心库主要是java.lang 包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包。如果想要运行一个开发好的程序,计算机中只需要安装JRE即可。
JDK
Java Development Kit 是提供给Java开发人员使用的,其中包含了Java的开发工具,也包含了JRE。其中的开发工具:运行工具(java.exe),编译工具(javac.exe),打包工具(jar.exe)等。
2. 什么是Java的跨平台性?原理是什么?
所谓的跨平台,即就是Java语言编写好的程序,一经编译后,可以在多个系统平台上运行。
实现原理:Java程序是通过Java虚拟机在系统平台上运行的,不同的系统平台有自己的虚拟机,只要该系统可以安装相应的Java虚拟机,该系统就可以运行Java程序。
3. 什么是字节码?采用字节码的最大好处
字节码:Java源代码经过虚拟机的编译器编译后产生的文件(即扩展名为.class的文件),它不面向任何特定的处理器,只面向虚拟机。
采用字节码的好处:Java语言通过字节码的方式,在一定程度上解决了传统解释性语言执行效率低的问题,同时又保留了解释性语言可移动的特点。所以Java程序运行时比较高效,且Java语言具有可移植性,因此Java语言无需重新编译即可在多种不同的计算机上运行。
Java语言的执行过程
Java源代码 —— 编译器 —— jvm可执行的Java字节码 —— jvm —— jvm的解释器中 —— 机器可执行的二进制机器码 —— 程序运行
Java程序的主类?应用程序和小程序的主类有何不同?
主类是Java程序执行的入口点,一个程序中可以有多个类,但是只能有一个主类,且类前用public修饰。
在Java的应用程序中,这个主类是指包含 main() 方法的类。Java应用程序从主线程启动,即 main() 方法。
而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。Java小程序是嵌在浏览器页面上运行(调用init()线程或者run() 来启动),嵌入浏览器这点跟flash的小游戏类似。
二. 基础语法
1. 数据类型
Java的数据类型
分类
- 基本数据类型
- 数值型
- 整数类型(byte, short, int, long)
- 浮点类型(float, double)
- 字符型(char)
- 布尔类型(boolean)
- 数值型
- 引用数据类型
- 类(class)
- 接口(interface)
- 数组([])
- 字符串(String)
数据作为成员变量时可以不进行初始化,系统会赋予默认值,但是数据若是作为方法或者其他的局部变量必须赋予初始值。
还有整型变量默认为 int 类型,浮点数变量的默认类型为 double 类型。
switch 语句的判断条件
从Java5 开始,Java的 switch(expr) 中,expr 可以是 enum(枚举)、byte、short、int、char、String。
左移、右移
左移相当于乘<<,例如 2 << 3,2左移3位相当于2 * (2的三次方)。
右移相当于除>>,例如 8 >> 3,8右移3相当于8 / (三次根号下8),即为2。
取两个数的平均数
int a = 7;
int b = 17;
int avg = ?;
- avg = (a + b) / 2;
- avg = a + (b - a) / 2;
- avg = (a + b) >> 1;
求一个数的四舍五入数
Math.round(11.5) —— 返回12;
Math.round(-11.5) —— 返回-11.
四舍五入的原理是在参数上 + 0.5 然后向下取整
2. 访问修饰符
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
private | 可以 | 不可 | 不可 | 不可 |
default | 可以 | 可以 | 不可 | 不可 |
protected | 可以 | 可以 | 可以 | 不可 |
public | 可以 | 可以 | 可以 | 可以 |
上表中体现的是该修饰符对于哪些类是可见的。
3. 运算符
& 和 && 的区别
& 运算符的用法有两个:(1)按位与;(2)逻辑与
&& 运算符的用法:短路与。
虽然两者都要求运算符左右两边的布尔值都为true整个表达式的判断结果才是true,但他们的区别还是很大的。&&之所以被称为是短路与,是因为如果 && 左边的表达式为 false,右边的表达式会被直接短路掉,右边的表达式不会进行运算。同理于 | 和 ||
4. 几个关键字
final 关键字作用
用于修饰类、属性和方法
- 被 final 修饰的类不可以被继承
- 被 final 修饰的方法不可以被重写
- 被 final 修饰的变量不可以被改变,被 final 修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以变的。
final finally finalize的区别
- final 同上个知识点
- finally 一般作用在 try - catch 代码块中,在处理异常的时候,通常将一些必须执行的代码放在 finally 代码块中。
- finalize 是一个方法,属于 Object 类的一个方法,而Object 类是所有类的方法,该方法一般由垃圾回收器来调用。当我们调用 System.gc() 的时候,由垃圾回收器调用 finalize(),回收垃圾。
this 关键字的使用
this 是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。
this 的用法:
- 普通的直接调用,this 相当于指向当前对象本身。可以调用属性、普通方法。
- 形参与成员名重名,用this调用成员名来区分。
- 引用本类的构造函数。可用于构造函数给成员变量逐个赋值的时候。
super 关键字的使用
super 是指向自己父类对象的一个指针(离自己最近的一个父类)。
super 的用法:
- 普通的直接引用,指向当前对象的父类的引用,这样就可以用 super.xxx 来引用父类的成员。
- 子类中的成员变量或方法与父类中的成员变量或方法同名时,用 super 调用来区分。
- 引用父类的构造函数
- super(参数):调用父类中的另一个构造函数(应该为构造函数中的第一句)
- this(参数):调用本类中另一种形式的构造函数(应该为本构造函数的第一句)
this 和 super 的区别
- super:引用当前对象的直接父类中的成员;super是在子类中调用父类的属性、构造方法、普通方法;
- this:代表当前对象名;this是在本类中调用本类的属性 、构造方法、方法;
- super() 和 this() 均需放在构造方法的第一行;
- this 和 super 不可以同时出现在一个构造函数中,因为this会调用其他的构造函数,其他的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
- this() 和 super() 都指的是对象,所以,均不可以在 static 环境中使用(static变量、static方法、static语句块)。
static 关键字
static关键字的主要意义是在于创建独立于具体对象的域变量或者方法,以致于即使没有创建对象也没使用属性和调用方法。
static关键字还有一个比较关键的作用就是用来形成静态代码块以优化程序性能。static代码块可以置于类中的任何地方,类中可以有多个 static 块。在类初次被加载的时候,会按照 static 代码块的顺序来执行每个 static 块,并且只会执行一次。
为什么static代码块可以用来优化程序性能,是因为:它只会在类被加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中被执行。
static 关键字的特点:
- 被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量或者方法不属于任何一个实例化对象,而是被该类的实例化对象所共享。
- 在该类第一次被加载的时候,就会去加载被 static 修饰的部分,而且只在类第一次使用时加载并进行初始化。
- static 变量在类加载的时候分配空间,以后创类对象的时候不会重新分配。
- 被 static 修饰的变量或者方法是优先于对象存在的,即就是当一个类被加载完毕后,即使没有创建对象,也可以访问。
static 应用场景:
- 修饰成员变量;
- 修饰成员方法
- 静态代码块
- 修饰类【只能修饰内部类,也就是静态内部类】
- 静态导包
注意事项:
- 静态只能访问静态;
- 非静态可以访问非静态,也可以访问静态。
三. 面向对象
1. 面向对象概述
面向对象和面向过程的区别
面向过程:
面向过程使具体化的,流程化的,解决一个问题,就需要一步一步的分析,一步一步的实现。
优点:性能比面向对象高,因为面向对象调用时需要实例化,开销较大,比较消耗资源。比如:单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象:
面向对象使模型化的,只需抽象出一个类,需要什么功能,不必一步一步去实现,只需要实例化一个实例,然后去调用相应的方法,不必去考虑这个功能使如何实现的。
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点:系统相比于面向过程较低。
2. 面向对象的三大特性
三大特性基本描述
封装:
对类成员访问权限的设置,将类的属性和方法封装起来。隐藏对象的属性和实现细节,仅对外提供公共访问方式,便于使用,提高复用性和安全性。
继承:
对某一类的类的继承(子类继承父类)
- 子类拥有父类的非private的属性和方法;
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展;
- 子类可以用自己的方式实现父类的方法——重写
- 方法名必须相同
- 子类访问权限不得低于父类
- 子类抛出的异常小于父类抛出的异常
- 参数列表相同
- Java是单继承
- 初始化顺序:父类对象——父类对象属性初始化——父类对象执行构造方法——子类对象——子类对象属性初始化——子类对象构造方法执行
多态:
同一行为具有不同的表现形式。多态就是同一个接口,使用不同的实例而执行不同的操作。
多态存在的三个条件:
- 有继承关系
- 子类重写父类的方法
- 父类引用指向子类对象(动态绑定)
比如:
Parent p = new Child();
当使用多态调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。(若没有,可以通过向下强制转型,将p的类型强转为子类类型)
什么是多态机制?
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法到底调用哪个类中的方法,必须在程序运行期间才能确定。
因为在程序运行时才确定具体的类,这样不用修改源程序代码就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态就是多态性。
3. 类与接口
抽象类与接口的对比
抽象类是用来捕捉子类的通用特性;接口是抽象方法的集合。
从设计层面来说,抽象类是对类的抽象,是一种设计模板;接口是行为的抽象,是一种行为的规范。
相同点:
- 接口和抽象类都不能实例化
- 都位于继承的顶端,用于被其他类实现或继承
- 都包含抽象方法,其子类都必须覆写这些抽象方法。
不同点:
参数 | 抽象类 | 接口 |
---|---|---|
声明 | 抽象类使用abstract class关键字声明 | 接口使用interface声明 |
实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,需要实现抽象类中所有声明的抽象方法 | 子类使用implements关键字来实现接口。子类如果不是抽象类的话需要实现接口中所有的抽象方法 |
构造器 | 抽象类可以有构造器 | 接口没有构造器 |
访问修饰符 | 任意访问修饰符 | 接口方法默认修饰符是public,并且不允许定义为其他的修饰符 |
多继承 | 一个类最多只能继承一个抽象类 | 一个类可以实现多个接口 |
字段声明 | 任意的 | 接口的字段默认都是static 和 final 的 |
普通方法 | 抽象类可以有普通方法 | 接口没有普通方法 |
在接口和抽象类的选择上,必须遵守的原则:
- 行为模型应该总是通过接口而不是抽象类定义,所以通常都是优先选用接口,尽量少用抽象类。
- 选择抽象类的时候的场景通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
抽象类能使用final修饰吗?
不能。定义抽象类就是为了让其他类继承的,而一旦为 final 修饰的类就不能被继承,这样就会彼此矛盾,所以 final 不能修饰抽象类。
实例对象与对象引用有何不同?
new 创建好一个对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在占内存中)。一个对象引用可以指向 0 个 或者 1 个对象(一个绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 个绳子系住一个气球)
4. 变量和方法
成员变量和局部变量
在Java中定义一个没有参数且没有任何语句的构造方法的作用
Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则会在编译时因为在父类中找不到不含参数的构造方法而发生错误。解决办法就是在父类中增加一个不含参数的构造方法。
类的构造方法的作用是什么?若一个类没有声明构造方法,该程序能否正确执行?
构造方法的作用:完成该对象的初始化工作。
可以正常执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
构造方法的特性
- 方法名与类名相同
- 没有返回值,且无需用 void 声明;
- 生成类的对象时自动执行,无序调用。
什么是方法的返回值?返回值的作用是什么?
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果。
作用:接收输出结果,使得它可以继续用于其他操作。
5. 内部类
什么是内部类?
在Java中,可以将一个类的定义放在另一个类的定义内部,这个就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。
内部类的分类
内部类可以分为四种:成员内部类、局部内部类、匿名内部类、静态内部类。
静态内部类:
定义在类内部的静态类,就是静态内部类。
public class Outer{
private static int radius = 1;
static class StaticInner {
public void visit() {
System.out.println("visit outer static variable: " + radius);
}
}
}
静态内部类可以访问外部类所有的静态变量,不可访问外部类的非静态变量。
静态内部类的创建方式,new 外部类.静态内部类(),如下:
Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();
成员内部类
定义在类内部,成员位置上的非静态类,就是成员内部类,
class Outer{
private static int radius = 1;
private int count = 2;
class Inner {
public void visit() {
System.out.println("visit outer static variable: " + radius);
System.out.println("visit outervariable: " + count);
}
}
}
成员内部类可以访问外部类所有的变量和方法,包括静态和非静态、私有和共有。
成员内部类的创建方式外部实例.new 内部类(),如下
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();
局部内部类
定义在方法中的内部类,就是局部内部类。
匿名内部类:
匿名内部类就是没有名字的内部类,日常开发中使用比较多。
publilc class Outer {
private void test(final int i) {
new Service() {
public void method() {
for(int j = 0; j < i; j++){
System.out.println("匿名内部类");
}
}
}.method();
}
}
interface Service{
void method();
}
注意:匿名内部类的特点:
- 没有名字
- 匿名内部类必须继承一个抽象类或实现一个接口
- 匿名内部类不能定义任何静态成员和静态方法
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final
为什么要加final?
因为生命周期不一致,局部变量直接存储在栈中,当方法执行结束后,非 final 的局部变量就会被销毁。而局部变量内部类/匿名类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外部的局部变量区分开,解决这一问题。
内部类的优点
- 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据
- 内部类不为同一包的其他类所见,具有很好的封装性
- 内部类有效实现了“多重继承”,优化Java单继承的缺陷
- 匿名内部类可以很方便你的定义回调。
内部类有哪些应用场景
- 一些多算法场合
- 解决一些非面向对象的语句块
- 适当使用内部类,可以使得代码更加灵活,富有扩展性
- 当某个类除了它的外部类,其他类不需要使用时。
重写与重载
重载(Overload)与重写(Override)的区别。重载的方法能否根据返回值类型进行区分?
方法的重载和方法的重写都是实现多态的方法,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:发生在同一类中;方法名相同,参数列表不同(参数类型不同、参数个数不同、参数顺序不同)被认为是重载;与方法返回值和访问修饰符无关;可以抛出不同的异常。
重写:发生在父子类中;方法名、参数列表、返回值类型都相同;如果父类方法访问修饰符为private,则子类不能进行方法的重写;构造方法、声明为final的方法、声明为static的方法都不可被重写;访问权限不能比父类中被重写的方法的访问权限更低;重写的方法不能抛出新的强制性异常。
为什么函数不能根据返回值类型来区分重载?
因为调用的时候不能制定类型信息,编译器并不知道你要调用的是哪个函数。
函数的返回值只是作为函数运行之后的一个“状态”,他是保持方法的调用者与被调用者之间进行通讯的关键。但并不能作为某个方法的“标识”。
对象相等判断
== 和 equals 的区别是什么?
首先,两者最大的区别,==是运算符,而equals() 是方法
==:它的作用是判断两个对象的地址是否相等。即,判断两个引用所指的对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型比较的是内存地址)
equals():判断两个对象是否相等。但它一般有两种使用情况。
情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”来比较这两个对象。
情况2:类覆盖了 equals() 方法。判断 equals() 比较的两个对象的内容是否相等。
hashCode 与 equals
为什么重写 equals 方法时,,必须重写 hashCode方法?
hashCode() 介绍
hashCode() 的作用是获取散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出相应的“值”。这其中就利用到了散列码。
为何要有hashCode()?
我们以“HashSet如何检查重复”为例来说明为什么要有hashCode
当你把对象加入 HashSet时,HashSet会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他加入的对象的 hashCode 值作对比,如果没有相符的 hashCode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值得对象,这时就会调用 equals() 方法来检查 hashCode 值相等的两个对象是否真的相等。如果二者相等,就会加入操作失败。如果不相等,就会重新散列到其他位置。这样就会大大减少 equals 的次数,从而大大提高了执行速度。
因此 equals 方法被覆写时,hashCode 方法也必须被覆盖。hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该类的两个镀锡无论如何都不会相等。
说明:
- 如果两个对象相等,则 hashCode 一定也是相等的;
- 如果对象相等,两个对象分别调用 equals 方法都返回 true;
- 两个对象有相同的 hashCode 值,他们不一定是相等的。
对象的相等与指向他们的引用相等,两者有什么不同?
对象的相等比的是内存中存放的内容是否相等,引用相等比较的是他们指向的内存是否是同一块。
值传递
Java的传递?
当一个对象被当作参数传递给到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果 —— Java的传递是值传递。
Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象的改变是不会影响到调用者的。
Java为什么只有值传递?
Java 程序设计语言总是采用按值调用。也就是说方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
example1:
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1); // num1 = 10;
System.out.println("num2 = " + num2); // num2 = 20;
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a); // a = 20
System.out.println("b = " + b); // b = 10
}
交换的是num1 和 num2 两个拷贝的值,所以num1 和 num2 的本身并不会改变。
example2:
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]); // 1
change(arr);
System.out.println(arr[0]); // 0
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的是同一个数组对象。因此,外部对引用对象的改变会反应到所对应的对象上。方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。
example3:
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName()); // s1:小张
System.out.println("s2:" + s2.getName()); // s2:小李
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName()); // x:小李
System.out.println("y:" + y.getName()); //y:小张
}
}
未完待续
本文地址:https://blog.csdn.net/weixin_43580746/article/details/107301738
上一篇: react组件之间传值
下一篇: Java基础-程序基础