| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [View Raw Code] [Original HTTPS Page] |
| isOriginal | true |
|---|---|
| category | 求职面试 |
| tag | 面试 |
| date | 2024-02-01 |
作者:村雨遥
不要哀求,学会争取,若是如此,终有所获
最近,公众号之外,建立了微信交流群,不定期会在群里分享各种资源(影视、IT 编程、考试提升……)&知识。如果有需要,可以扫码或者后台添加小编微信备注入群。进群后优先看群公告,呼叫群中【资源分享小助手】,还能免费帮找资源哦~
一个 Java 文件中是可以存在多个类的,但是一个 Java 文件中只能存在一个 public 所修饰的类,而且这个 Java 文件的文件名还必须和 public 所修饰类的类名保持一致,一个简单的实例如下。
public class Person{
private String name;
}
class Student{
private Integer age;
}我们编译的源代码(xxx.java)经 JDK 中的 javac 命令编译后,成为 JVM 能够理解的 Java 字节码(xxx.class),然后经由 JVM 加载,通过解释器 逐行解释执行,这就是为什么能经常听见说 Java 是一门编译和解释共存的语言。
其中 JVM 是解释 Java 字节码(xxx.class) 的虚拟机,针对不同系统均有特定实现,方便一次编译,多次运行,即 Java 语言的平台独立性;
Java 中,数据类型主要包括 8 大基本数据类型和引用数据类型两大类。
| 数据类型 | bit | 字节 | 封装类 | 数据范围 | 默认值 |
|---|---|---|---|---|---|
| byte | 8 | 1 | Byte | $-2^7 \text{~} 2^7-1$ | 0 |
| short | 16 | 2 | Short | $-2^{15} \text{~} 2^{15}-1$ | 0 |
| char | 16 | 2 | Character | \\u0000 | |
| ~ \\uffff | |||||
| ($0 \text{~} 65535$) | u0000 | ||||
| int | 32 | 4 | Integer | $-2^{31} \text{~} 2^{31}-1$ | 0 |
| long | 64 | 8 | Long | $-2^{63} \text{~} 2^{63}-1$ | 0L |
| float | 32 | 4 | Float | $3.4e^{-45} \text{~} 1.4e^{38}$ | 0.0f |
| double | 64 | 8 | Double | $4.9e^{-324} \text{~} 1.8e^{308}$ | 0.0D |
| boolean | 不确定 | 不确定 | Boolean | true | |
| 或 false | false |
注意:
| 数据类型 | 默认值 |
|---|---|
| 数组 | null |
| 类 | null |
| 接口 | null |
基本数据类型都有其对应的封装类,两者之间的赋值通过 自动装箱 和 自动拆箱 来完成;
// 实际调用 Integer.valueOf(12)
Integer x = 12;Integer x = 12;
// 实际调用 x.intValue()
int y = x;以 new Integer(123) 和 Integer.valueOf(123) 为例:
Integer x = new Integer(123);
Integer y = new Integer(123);
// false,通过 new 的方式,每次都会创建一个新对象,指向不同对象
System.out.println(x == y);
Integer m = Integer.valueOf(123);
Integer n = Integer.valueOf(123);
// true,通过 valueOf() 的方式,先到缓存池中查找,存在时则多次调用也是指向同一对象
System.out.println(m == n);| 数据类型 | 默认缓存池 |
|---|---|
| Byte | $-2^7 \text{~} 2^7-1$ |
| Character | \\u0000 |
| ~ \\u007F | |
| Short | $-2^7 \text{~} 2^7-1$ |
| Integer | $-2^7 \text{~} 2^7-1$ |
| Boolean | true & false |
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}上述代码为 Java 8 中 String 的定义,其底层实际上使用的是字符(char)数组,而且由于被声明为 final,代表着它 不能被继承。而且一旦初始化之后就不能再去引用其他数组,这样就保证了 String 的不可变性,也因此 String 是线程安全的。
由于 String 的 hash 值被频繁使用,它的不可变性使得 hash 值也不可变,此时只需要进行一次计算;
如果一个 String 对象已经被创建过,那么就会优先从字符串常量池中获取其引用,其不可变性确保了不同引用指向同一 String 对象;
我们经常用 String 作为我们方法的参数,其不变性能够保证参数不可变;
String 的不可变性让它天生 具备线程安全,能够在多个线程中方便使用而不用考虑线程安全问题。
主要从三个方面对三者进行对比:
| 可变性 | 线程安全 | 适用场景 | |
|---|---|---|---|
| String | 不可变 | 安全 | 操作少量的数据 |
| StringBuffer | 可变 | 安全,内部使用 synchronized 进行同步 | 多线程操作字符串缓冲区下操作大量数据 |
| StringBuilder | 可变 | 不安全 | 单线程操作字符串缓冲区下操作大量数据,性能高于 StringBuffer |
String Pool 位于 方法区,通常保存着所有 字符串字面量(literal strings),在编译期间就被确定。此外,还可以用 String 中的 intern() 方法在运行过程中添加到 String Pool 中。当一个字符串调用 intern() 时,如果 String Pool 中已经存在字面量相同的字符串,则会返回 String Pool 中的引用;如果不存在,则向 String Pool 中添加一个新的字符串,同时返回新字符串的引用。
String s1 = new String("aaa");
String s2 = new String("aaa");
// false 两个字符串指向不同对象
System.out.println(s1 == s2);
String s3 = s1.intern();
String s4 = s1.intern();
// true,常量池中存在字面量相同的字符串,直接取出
System.out.println(s3 == s4);在下面的代码中,内存分析如下图:
String str1 = "村雨遥";
String str2 = "村雨遥";
String str3 = new String("村雨遥");
String str4 = new String("村雨遥");
// true,两个引用指向常量池中的同一对象
System.out.println(str1 == str2);
// false,两个引用指向堆中不同对象
System.out.println(str3 == str4);使用 new 的方式创建字符串对象,会有两种不同的情况:
此时会创建两个字符串对象,“xxx” 属于字符串字面量,因此在编译期会在 String Pool 中创建一个字符串对象,用于指向该字符串的字面量 “xxx”;然后 new 会在堆中创建一个字符串对象;
此时只需要创建一个字符串对象,由于 String Pool 中已经存在指向 “xxx” 的对象,所以直接在堆中创建一个字符串对象;
// 这是单行注释
String name = "村雨遥";/*
* 这是多行注释
* name,公众号
*/
String name = "村雨遥";/**
* @author : 村雨遥
* @param : name,公众号
*/
String name = "村雨遥";
Java 主要提供了 3 中访问修饰符 public、protected、private,但实际使用过程中,一共可以形成 4 种访问权限,分别是 public、protected、private、default,其中 default 是不加任何修饰符时的访问权限。关于 4 种访问权限的对比如下表,✅ 表示可以访问,❌ 表示不可访问。
| 作用域 | 当前类 | 同一 package 的类 | 子类 | 其他 package 的类 |
|---|---|---|---|---|
| public | ✅ | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ✅ | ❌ |
| default | ✅ | ✅ | ❌ | ❌ |
| private | ✅ | ❌ | ❌ | ❌ |
static 主要有如下 4 中使用场景:
用于引用类的当前实例,比如我们最常用的构造方法中,注意不能用在 static 方法中;
public class User{
int age;
public User(int age){
this.age = age;
}
}其中 this.age 说明访问的是 User 类中的成员变量,而后面的 age 则代表传入的形参;
用于从子类访问父类中的变量和方法,注意不能用在 static 方法中。
public class Father{
String name;
public Father(String name){
this.name = name;
}
public Father(){
}
}public class Son extends Father{
public Son(String name){
super();
this.name = name + ".jr";
}
}| 关键字 | 说明 |
|---|---|
| continue | 用于循环结构,指跳出当前循环,进入下一次循环 |
| break | 用于循环结构,指跳出整个循环体,继续执行循环下面的语句 |
| return | 1. return ;进行同步:直接用 return 结束方法执行,用于没有返回值函数的方法; 2. return value; :return 一个特定值,用于有返回值函数的方法 |
while 循环结构在循环开始前会判断下一个迭代是否应该继续,可能一次循环体都不执行。
do……while 会在循环的结果来判断是否继续下一轮迭代,至少会执行一次循环体。
final 既是一个修饰符,也是一个关键字,修饰不同对象时,表示的意义也不一样。
finallize() 是 Object 类的 protected 方法,子类能够覆盖该方法以实现资源清理工作;
GC 在回收前均会调用该方法,但是 finalize() 方法存在如下问题:
| 操作符 | 描述 | 例子 |
|---|---|---|
| + | 加法 - 相加运算符两侧的值 | A + B 等于 30 |
| - | 减法 - 左操作数减去右操作数 | A – B 等于 -10 |
| * | 乘法 - 相乘操作符两侧的值 | A * B等于200 |
| / | 除法 - 左操作数除以右操作数 | B / A等于2 |
| % | 取余 - 左操作数除以右操作数的余数 | B%A等于0 |
| ++ | 自增: 操作数的值增加1 | B++ 或 ++B 等于 21 |
| -- | 自减: 操作数的值减少1 | B-- 或 --B 等于 19 |
注意:++ 和 -- 可以放在操作数之前,也可以放在操作数之后;位于操作数之前时,先自增/减,再赋值;位于操作数之后,先赋值,再自增/减;总结起来就是 符号在前就先加/减,符号在后就后加/减。
| 运算符 | 描述 | 例子 |
|---|---|---|
| == | 检查如果两个操作数的值是否相等,如果相等则条件为真。 | (A == B)为假。 |
| != | 检查如果两个操作数的值是否相等,如果值不相等则条件为真。 | (A != B) 为真。 |
| > | 检查左操作数的值是否大于右操作数的值,如果是那么条件为真。 | (A> B)为假。 |
| < | 检查左操作数的值是否小于右操作数的值,如果是那么条件为真。 | (A <B)为真。 |
| >= | 检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。 | (A> = B)为假。 |
| <= | 检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。 | (A <= B)为真。 |
| 操作符 | 描述 | 例子 |
|---|---|---|
| & | 如果相对应位都是 1,则结果为 1,否则为 0 | $(A\text{&}B)$,得到 12,即 0000 1100 |
| ` | ` | 如果相对应位都是 0,则结果为 0,否则为 1 |
| ^ | 如果相对应位值相同,则结果为 0,否则为1 | $(A ^ B)$得到 49,即 0011 0001 |
| 〜 | 按位取反运算符翻转操作数的每一位,即 0 变成 1,1 变成 0。 | $( \text{~} A)$ 得到 -61,即1100 0011 |
| << | 按位左移运算符。左操作数按位左移右操作数指定的位数。 | $A << 2$ 得到 240,即 1111 0000 |
| >> | 按位右移运算符。左操作数按位右移右操作数指定的位数。 | $A >> 2$ 得到 15 即 $1111$ |
| >>> | 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。 | $A >>> 2$ 得到 15 即 $0000 1111$ |
| 操作符 | 描述 | 例子 |
|---|---|---|
| && | 称为逻辑与运算符。当且仅当两个操作数都为真,条件才为真。 | (A && B) |
| ` | ` | 称为逻辑或操作符。如果任何两个操作数任何一个为真,条件为真。 |
| ! | 称为逻辑非运算符。用来反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将得到false。 | !(A && B) |
| 为真。 |
| 操作符 | 描述 | 例子 |
|---|---|---|
| = | 简单的赋值运算符,将右操作数的值赋给左侧操作数 | C = A + B将把A + B得到的值赋给C |
| += | 加和赋值操作符,它把左操作数和右操作数相加赋值给左操作数 | C + = A等价于C = C + A |
| -= | 减和赋值操作符,它把左操作数和右操作数相减赋值给左操作数 | C - = A等价于C = C - A |
| *= | 乘和赋值操作符,它把左操作数和右操作数相乘赋值给左操作数 | C _ = A等价于C = C _ A |
| /= | 除和赋值操作符,它把左操作数和右操作数相除赋值给左操作数 | C / = A,C 与 A 同类型时等价于 C = C / A |
| %= | 取模和赋值操作符,它把左操作数和右操作数取模后赋值给左操作数 | C%= A等价于C = C%A |
| << = | 左移位赋值运算符 | C << = 2等价于C = C << 2 |
| >>= | 右移位赋值运算符 | C >> = 2等价于C = C >> 2 |
| &= | 按位与赋值运算符 | C&= 2等价于C = C&2 |
| ^= | 按位异或赋值操作符 | C ^ = 2等价于C = C ^ 2 |
| |= | 按位或赋值操作符 | C | = 2等价于C = C | 2 |
也叫作三元运算符,共有 3 个操作数,且需要判断布尔表达式的值;
variable x = (expression) ? value if true : value if false用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型);
( Object reference variable ) instanceof (class/interface type)基本数据类型用 == 比较的是值,用于引用数据类型时判断两个对象的内存地址是否相等,即两对象是否是同一个对象;
本质来讲,由于 Java 中只有值传递,所以不管是基本数据类型还是引用数据类型,比较的其实都是值,只不过引用类型变量存的值是对象的地址;
作用也是判断两个对象是否相等,但是 不能用于基本数据类型变量的比较。存在于 Object() 类中,所以所有类都具有 equals() 方法存在两种使用情况:
重载就是同样方法能够根据输入的不同,做出不同的处理。重载发生在 编译期,而且在同一个类中,方法名必须相同,参数类型、参数个数、参数顺序不同,返回值和访问修饰符可以不同。 总的而言:重载就是同一类中多个同名方法根据不同传参来执行不同的逻辑处理。
重写是当子类继承自父类的相同方法,输入数据一样,但最终响应不同于父类。重写发生在 运行期,是子类对父类允许访问的方法的实现逻辑进行改写。重写方法的方法名、参数列表以及返回值必须相同,抛出的异常范围不超出父类,访问修饰符的范围也不能小于父类。此外,若父类方法别 private/final/static 修饰,则子类无法重写父类方法,但 static 修饰的方法能被再次声明。构造方法是个特例,不能被重写。总结起来就是:重写即子类对父类方法的改造,外部样子不能改变,但能够改变内部逻辑。
| 不同点 | 重载 | 重写 |
|---|---|---|
| 参数列表 | 必须不同 | 必须相同 |
| 返回类型 | 可不同 | 必须相同 |
| 访问修饰符 | 可不同 | 不能比父类更严格 |
| 发生范围 | 同一类中 | 父子类 |
| 异常范围 | 可修改 | 可以减少或删除,不能抛新异常或范围更广的异常 |
| 发生阶段 | 编译期 | 运行期 |
浅拷贝是 按位拷贝对象,会创建一个新对象,该对象具有原始对象属性值的精确拷贝。 若属性是基本类型,则拷贝的是基本类型的值;若属性是引用类型(内存地址),则拷贝的是内存地址。因此,一旦其中任一对象改变了该引用类型属性,均会影响到对方;
深拷贝会 拷贝所有属性,同时拷贝属性指向的动态分配的内存。当对象和它引用的对象一起拷贝是即发生深拷贝,相比于浅拷贝,深拷贝速度较慢同时花销更大。
浅拷贝后,改变其中任一份值都会引起另一份值的改变;而深拷贝后,改变其中任何一份值,均不会对另一份值造成影响;
推荐阅读:https://juejin.im/post/5bce68226fb9a05ce46a0476
public static void func(String name){
System.out.println(name);
}
public static void main(String[] args) {
//实参
String name = "村雨遥";
func(name);
}方法被调用时,实参通过形参将其内容副本传入方法内部,此时形参接收的内容实际上是实参的一个拷贝,因此在方法内对形参的任何操作均只针对于实参的拷贝,不会影响到实参原始值的内容。即 值传递的是实参的一个副本,对副本的操作不会影响实参原始值,也即无论形参如何变化,都不会影响到实参的内容。
public static void valueCrossTest(int age,float weight){
System.out.println("传入的age:"+age);
System.out.println("传入的weight:"+weight);
age=33;
weight=89.5f;
System.out.println("方法内重新赋值后的age:"+age);
System.out.println("方法内重新赋值后的weight:"+weight);
}
public static void main(String[] args) {
int a=25;
float w=77.5f;
valueCrossTest(a,w);
// a = 25,原始值不收影响
System.out.println("方法执行后的age:"+a);
// w = 77.5,原始值不收影响
System.out.println("方法执行后的weight:"+w)
}引用即指向真实内容的地址值,在方法调用时,实参的地址被传递给相应形参,在方法体内,形参和实参指向同一个地址内存,因此此时操作形参也会影响到实参的真实内容。
但 Java 中并 不存在引用传递,因为 无论是基本类型还是引用类型,在实参传入形参时,均为值传递,即传递的都是一个副本,而非实参内容本身。
如果是对基本数据类型的数据进行操作,由于实参原始内容和副本都是存储实际值,并且处于不同栈区,因此对形参的操作,实参原始内容不受影响。
如果是对引用类型的数据进行操作,分两种情况,
public static void PersonCrossTest(Person person){
System.out.println("传入的person的name:" + person.getName());
person.setName("我是张小龙");
System.out.println("方法内重新赋值后的name:" + person.getName());
}public static void PersonCrossTest(Person person){
System.out.println("传入的person的name:" + person.getName());
person=new Person();
person.setName("我是张小龙");
System.out.println("方法内重新赋值后的name:" + person.getName());
}推荐阅读:https://www.zhihu.com/question/27468564/answer/757537214
面向过程是一种 对现实世界理解和抽象的方法,更容易维护、复用、扩展。最主要的特点就是 继承、封装、多态,所以 设计出的系统耦合性较低,但比起面向过程性能要低。
面向过程是一种 以过程为中心 的编程思想,以正在发生为主要目标进行编程,不同于面向的的是谁受影响。最主要的不同就在于 封装、继承、多态,其性能比面向对象更高。
面向对象的方式使得每个类都各司其职,最后整合到一起来共同完成一个项目,而面向过程则是让一个类中的功能越来越多,就像一个全栈工程师能够一个人搞定所有事。
将客观事物封装为抽象的类,同时类能把自己的数据和方法只让可信的类或对象进行操作,对不可信的类进行信息隐藏。即把属于同一类事物的共性(属性与方法)归到一个类,从而方便使用。
通过 封装,实现了 专业分工,将能实现特定功能的代码封装为独立实体,供我们在需要时调用。此外,封装还 隐藏了信息以及实现细节,使得我们通过访问权限权限符就能将想要隐藏的信息隐藏起来。
可以使用现有类的所有功能,且无需重写现有类来进行功能扩展,即个性对共性的属性与方法的接受,并加入特性所特有的属性与方法。通过继承的新类叫做 子类/派生类,被继承的类叫做 父类/基类/超类,具有如下特点:
多态是允许 将父对象设置为和一个或多个其子对象相等的技术,赋值后,父对象能够根据指向的子类对象的特性以不同方式运作,即 父类引用指向子类对象实例,有 重载和重写 两种实现方式。具有如下特点:
| 不同 | 语法 | 存储位置 | 生命周期 | 初始化值 | 调用方式 | 别名 |
|---|---|---|---|---|---|---|
| 成员变量 | 1、 属于类 | |||||
| 2、能被访问控制符、static、final | ||||||
| 等修饰 | 堆 | 与对象共存亡 | 有,基本数据类型为对应默认值,而对象统一为 null | 对象调用 | 实例变量 | |
| 局部变量 | 1、属于方法(方法中的变量或参数) | |||||
| 2、不能被访问控制符及 static | ||||||
| 修饰,但可以被 final | ||||||
| 修饰 | 栈 | 与方法共存亡 | 无,必须定义赋值后使用 | |||
| 静态变量 | 1、属于类 | |||||
| 2、被 static | ||||||
| 修饰,被所有类对象共用 | 方法区 | 与类共存亡 | 同成员变量初始化值 | 类名调用(推荐)、对象调用 | 类变量 |
| 方法 | 说明 |
|---|---|
| public final native Class<?> getClass() | 用于返回当前运行时对象的 Class 对象,使用了final 关键字修饰,故不允许子类重写 |
| public native int hashCode() | 用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的 HashMap |
| public boolean equals(Object obj) | 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写用户比较字符串的值是否相等 |
| protected native Object clone() throws CloneNotSupportedException | 用于创建并返回当前对象的一份浅拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为 true。Object 本身没有实现 Cloneable 接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException 异常 |
| public String toString() | 返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法 |
| public final native void notify() | 不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个 |
| public final native void notifyAll() | 不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程 |
| public final native void wait(long timeout) throws InterruptedException | 不能重写。暂停线程的执行注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间,调用该方法后当前线程进入睡眠状态,知道如下时间发生: |
推荐阅读:https://juejin.im/post/5a4379d4f265da432003874c
| 准则 | 说明 |
|---|---|
| 自反性 | 对任意非空引用值 x |
| ,x.equals(x) | |
| 应该返回 true | |
| 对称性 | 对于任何非空引用值 x |
| 和 y | |
| ,当 y.equals(x) | |
| 返回 true | |
| 时,x.equals(y) | |
| 也应返回 true | |
| 传递性 | 对于任何非空引用值x、y |
| 和 z | |
| ,如果 x.equals(y) | |
| 返回 true | |
| , 并且 y.equals(z) | |
| 返回 true | |
| ,那么 x.equals(z) | |
| 也应返回 true | |
| 一致性 | 对于任何非空引用值 x |
| 和 y | |
| ,多次调用 x.equals(y) | |
| 始终返回 true | |
| 或始终返回 false | |
| , 前提是对象上 equals | |
| 比较中所用的信息没有被修改 | |
| 非空性 | 对于任何非空引用值 x |
| ,x.equals(null) | |
| 都应返回 false |
hashCode 用于返回对象 hash 值,主要是为了加快查找的快捷性,因为 hashCode() 是 Object 类中的方法,所以所有 Java 类均有 hashCode(),在 HashTable 和 HashMap 这类的散列结构中,均是通过 hashCode() 来查找在散列表中位置,通过 hashCode 能够较快的茶道小内存块。
当 Java 对象需要在网络上传输或者持久化存储到文件中时,我们就需要对象进行序列化;
要实现序列化,只需要让类实现 Serializable 接口即可,此时就标注该类对象能够被序列化;
针对类中某些数据不想序列化时,可以使用 transient 关键字来实现,例如:
// 通过关键字 transient 修饰,表明不参与序列化
transient private String telephone;| Back | FazBrowse Home | New Git URL |