一、Java基础知识
# 一、Java基础知识
# 1、面向对象概念?面向对象和面向过程的区别?
面向对象是一种思想,能让复杂问题简单化,面向对象思想程序员不需要了解具体的实现过程,只需要使用特定对象去实现功能即可。 面向对象的底层其实还是面向过程,把面向过程抽象成类,然后进行封装,方便我们使用。
面向对象是相对面向过程而言,面向对象和面向过程都是一种思想。
面向对象是基于面向过程的。
面向过程强调的是功能、行为。
面向对象:将功能封装进对象,强调具备了功能的对象本身。
# 2、面向对象的特性?
- 1)封装: 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。
好处:
只能通过规定的方法访问数据。
隐藏类的实例细节,方便修改和实现。
2)继承: A extends B A子类继承B父类,子类将拥有父类对象所有的属性和方法(包括私有属性和私有方法), 但是父类中的私有属性和方法子类是无法访问的,只是拥有。
子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法,即方法的覆写。3) 多态性: 多态就是对象的多种形态。
记住: 编译时期父类引用指向子类对象,在运行时期会动态加载子类实现
java里的多态主要表现在两个方面:
①.引用多态
父类的引用可以指向本类的对象;
父类的引用可以指向子类的对象;
②.方法多态
根据上述创建的两个对象:本类对象和子类对象,同样都是父类的引用,当我们指向不同的对象时,它们调用的方法也是多态的。
创建本类对象时,调用的方法为本类方法;
创建子类对象时,调用的方法为子类重写的方法或者继承的方法;
注意:在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
- 4)抽象: 抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对
象有哪些属性和行为,并不关注这些行为的细节是什么。
# 3、在面向对象设计中,SOLID 是指五个重要的设计原则,它们是?
单一职责原则(Single Responsibility Principle,SRP):
这个原则表明一个类应该只有一个引起它变化的原因。换句话说,一个类应该只有一个职责或功能。这样做有助于使类更加模块化、可维护和易于理解。如果一个类负责过多的事情,那么当需求发生变化时,需要修改这个类的风险就会增加。
开放-封闭原则(Open-Closed Principle,OCP): 这个原则表明软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,当需要改变系统的行为时,应该通过扩展现有代码来实现,而不是通过修改已有的代码。通过遵循这个原则,可以减少因修改现有代码而引入的风险,并使得系统更加稳定和可维护。
里氏替换原则(Liskov Substitution Principle,LSP):
这个原则由芭芭拉·利斯科夫提出,它表明子类型(派生类或实现类)应该能够替换其基类型(基类或接口),而不会影响程序的正确性。换句话说,子类型应该能够在不破坏原有代码的情况下替换其父类型。遵循这个原则有助于保持代码的一致性和可靠性。
接口隔离原则(Interface Segregation Principle,ISP):
这个原则表明一个类不应该强迫其客户端依赖于它们不需要的方法。换句话说,一个类应该将其接口分离成多个专门的接口,而不是一个臃肿的整体。这样做可以降低类与类之间的耦合度,提高代码的灵活性和可维护性。
依赖倒置原则(Dependency Inversion Principle,DIP):
这个原则表明高层模块不应该依赖于低层模块,而是应该依赖于抽象。同时,抽象不应该依赖于细节,而是细节应该依赖于抽象。通过使用接口和抽象类,可以实现这个原则,从而使得系统更加灵活、可扩展和可维护。
# 4、java语言特性以及java是如何实现跨平台的?
跨平台性
支持多线程
支持网络编程等
分布式
(Java包括一个支持HTTP和FTP等基于TCP/IP协议的子库。因此,Java应用程序可凭借URL打开并访问网络上的对象,其访问方式与访问本地文件系统几乎完全相同。)
Unicode
Java使用Unicode作为它的标准字符,这项特性使得Java的程序能在不同语言的平台上都能撰写和执行。
简单的说,你可以把程序中的变量、类别名称使用中文来表示
当你的程序移植到其它语言平台时,还是可以正常的执行。
Java程序并非是直接运行的,Java编译器将Java源程序(.java文件)编译成与平台无关的字节码文件(.class文件),
然后由Java虚拟机(JVM Java Virtual Machine)对字节码文件解释执行。
所以在不同的操作系统下,只需安装不同的Java虚拟机即可实现java程序的跨平台。
(这里需要注意虚拟机并不是跨平台的 Java语言跨平台特性其实质是同一份Java字节码文件可以运行在不同操作系统上的Java虚拟机上)
下图一目了然:
# 5、什么是JDK、JRE、JVM 以及三者的关系是什么样的?
JDK(Java Development Kit)是Java程序开发工具包,包含JRE和开发人员使用的工具。
其中的开发工具:编译工具(javac.exe)和运行工具(java.exe)。
JRE(Java Runtime Environment)是Java程序的运行时环境,包含JVM和运行时所需要的核心类库。
JVM(JVM Java Virtual Machine)是 java虚拟机
开发者写Java代码,编译Java文件: 必须安装JDK
运行编译好的Java代码(.class文件): 安装JRE即可
注意: 安装JDK时,已经包含了JRE。
# 6、java有哪些普通数据类型?分别占几个字节
一共有8个普通数据类型
整型: byte(一个字节) short(两个字节) int(四个字节) long(8个字节)
浮点型:float(4个字节) double(8个字节)
字符型: char(2个字节 可以存一个汉字)
布尔型: boolean ( true false) 它的“大小”并不是精确定义的《Java虚拟机规范》给出了单独的boolean是4个字节,
和boolean数组1个字节的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。
这其实是运算效率和存储空间相互取舍的博弈。
注意:下图中boolean占一个字节说法不够严谨
如何理解 1个字节 的byte数据类型的范围是 -128~127 ?
Java中的byte类型占用8位二进制数来存储数据,这8位中的最高位(最左边的位)被用来表示数的符号,采用二进制补码表示法。 正数的补码和源码相同,负数的补码是负数的绝对值各个位取反后再加1。 补码表示法使得加法和减法运算可以统一处理正数和负数,而不需要对操作数的符号进行特殊处理。
对于8位的二进制数:
如果最高位是0,则表示正数,剩下的7位用于表示数值本身。因此,最大的正数是0111 1111,即
2⁷-1=127
如果最高位是1,则表示负数。
由于补码表示法中,负数是通过对其绝对值的原码取反后加1得到的,所以最小的负数对应于原码的1000 0000(即-0的补码表示),
这实际上是-128(因为-1的补码是1111 1111,而-128是比-1还要小的一个数,其补码不能直接从原码直观看出,需理解补码规则计算得出)。
因此,byte类型的取值范围是从-128(包括所有负数)到127(包括所有正数)。 简而言之,由于符号位的占用以及补码表示法的规则,使得byte类型的最大正值是127,而最小值是-128,而不是-127。
# 7、(Java的自动类型转换) float i=1.0 对吗? short a=1; a=a+1; 对吗? short b=1; b+=1;对吗?
前两个不对,后一个对。
第一个应该写成 float i=1.0F 或者 float i=1.0f
第二个 由于 a是short类型 (注意:当byte short 参与运算时会自动转换成int 类型)
所以第二个相当于 a = (int) (a+1); 结果是int型 赋值给a 而a是short类型 所以编译无法通过
第三种相当于 b=(short)(b+1); 结果为2
# 补充
下面这个对吗?
final byte b1 = 1;
final byte b2 = 1;
byte b3 = b1 + b2;
对。
常量折叠:
final 关键字使得 b1 和 b2 被视为编译时常量。
在编译时,编译器会将 b1 + b2 直接计算为 2,并且知道这个值在 byte 范围内。
类型推断:
因为 b1 和 b2 是 final 变量,且它们的值在编译时是已知的,编译器会进行常量表达式求值并检查结果。
结果 2 在 byte (-128~127)的范围内,编译器允许直接赋值给 byte 类型的变量 b3。
下面这个呢?
final byte b1 = 1;
final byte b2 = 127;
byte b3 = b1 + b2;
编译不通过。
常量折叠:
final 关键字使得 b1 和 b2 被视为编译时常量。
在编译时,编译器会将 b1 + b2 直接计算为 128,128 超出了 byte 类型的范围(-128 到 127)。
类型推断:
编译器会发现结果 128 是 int 类型,而不是 byte 类型,尝试将 int 类型的结果赋值给 byte 类型的变量 b3 会导致编译错误,因为这种赋值操作不安全,可能导致数据丢失。如果强制类型转换 为by
# 常量折叠
常量折叠(Constant Folding)是编译器在编译过程中进行的一种优化技术。它将程序中所有能在编译时确定的常量表达式计算出来,并用计算结果替换原来的表达式。这可以减少运行时的计算量,提高程序执行效率。
常量折叠的工作机制 常量折叠主要涉及以下几个方面:
编译时常量表达式的计算: 编译器在编译过程中会识别出那些可以在编译时计算的表达式,然后将它们计算出结果。例如,final int a = 2 + 3; 在编译时会被计算为 5。
用计算结果替换常量表达式:
编译器会用计算出的常量结果替换掉原来的常量表达式。这样在生成的字节码中直接使用常量值,避免了在运行时进行计算。
对常量变量的处理:
对于使用 final 关键字声明的常量变量,编译器会在编译时将这些变量的值视为常量,并在常量表达式中使用它们的值进行计算。
# 8、(移位运算)如何高效计算2*4 和 8/4 ?
2<< 2
8>>2
左移位 a<<n 相当于 a* 2ⁿ 所以 2<<2 相当于 2* 2²=2* 4
右移位 a>>n 相当于 a* 2﹣ⁿ 所以 8>>2 相当于 8* 2﹣²=8/4
# 9、Java程序的入口函数是什么? Java程序类的公共父类是什么?
main函数
Object类
# 10、类和对象的基本概念?
类是一组具有共同属性和行为的事物的抽象,简单理解,类就是对【事物】的一种【描述】
描述事物,则需要【属性】和【行为】
属性:事物具备的各个特征,例如->手机这个事物的特征(品牌,价格.)
行为:事物能执行的操作,例如->手机能(打电话,发短信)
对象代表客观存在的事物
类是对象的抽象(描述),对象是类的实体
在程序中需要先有类,才能创建出对象!
# 11、Java都有哪些创建对象的方式?
1、通过new关键字创建 Object obj = new Object();
2、通过对象反序列化
ObjectInputStream(InputStream in) 创建从指定的InputStream读取的ObjectInputStream
Object readObject() 从ObjectInputStream读取一个对象
// 假设oos.txt是对象序列化后的文件
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myOtherStream\\oos.txt"));
//Object readObject():从ObjectInputStream读取一个对象
Object obj = ois.readObject();
// 流使用完毕之后一定要关闭
ois.close();
(拓展:如果对象需要序列化 那么对象所属类必须实现Serializable接口, Serializable是一个标记接口,
实现该接口,不需要重写任何方法,这个接口只有标记作用 标记实现此接口的类可以序列化)
serialVersionUID&transient【应用】 具体细节可看这篇文章 (opens new window)
serialVersionUID
用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
会出问题,会抛出InvalidClassException异常
如果出问题了,如何解决呢?
重新序列化 给对象所属的类加一个serialVersionUID 这样类即使被修改之后反序列化也不会出现问题 如果两个类字段不一致 会出现字段丢失的情况但不影响反序列化 例如: private static final long serialVersionUID = 42L;
transient
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
例: private transient int age;
- 3、通过反射
Class<?> c = Class.forName("Student");
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
有时候需要clone 一个对象修改过的属性,然而通过new创建的对象的属性是初始值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。
如果把对象的临时属性一个一个的赋值给我新new的对象,操作起来比较麻烦,并且速度没有底层实现的快。
# 12、Object类里有哪些方法?都有什么作用
其中的registerNatives() 为底层方法 不是Java语言实现的 不做探究
wait()方法重载了两个
综上 Object类中 只需要了解9个方法 分别是
- hashCode()
- equals(Object obj)
- clone()
- toString()
- notify()
- notifyAll()
- wait(long timeout) 还有两个重载
- finalize()
- getClass()
(1) 、hashCode()方法
底层实现 和native相关的方法都是Java的底层实现
public native int hashCode();
该方法的作用是返回对象的hash值
如果该对象没有被修改的前提下多次调用该方法返回的结果是一样的
hashCode默认由对象在内存中的地址值转换而来
并且hashCode()有一个非常重要的特性
如果一个对象的底层是散列表那么使用hashCode可以提升性能
例如:HashMap 在插入元素时 其内部实现 会判断要插入的元素的key 与 容器中已存在的key是否相等
而判断key是否相等先比较key的hash值是否相等 ,如果hash值不等 则直接判定为key不相等,
这样就能提升判断的效率从而提升hashMap的性能
注意:
equals()方法默认是比较对象的地址,使用的是==等值运算符。但是在实际开发中,比较对象地址是没有意义的。
所以实际开发中我们经常重写equals方法来判断对象的内容是否相等,如果内容相等就认为这两个对象是相等的。
例如String类就重写了equals和hashCode方法 ,当两个字符串的内容相等 那么调用equals方法就返回true
(2)、equals(Object obj)方法
源码实现
public boolean equals(Object obj) {
return (this == obj);
}
直接使用==判断两个对象的地址值是都相同 (即默认的equals方法 只有当两个对象的地址值相等 才会判定这两个对象相等)
equals()方法有5个默认的特性:
- 自反性---> 对于任何非null的引用值x, x.equals(x)必须返回true
- 一致性---> 对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致地返回false
- 传递性---> x.equals(y) 和 y.equals(z) 都返回true,那么可以得出:x.equals(z)也返回true
- 对称性---> x.equals(y)和y.equals(x)结果应该是相等的。
- 对于任何非null的引用值x,x.equals(null)必须返回false
需要注意 : 重写equals()方法,就必须重写hashCode()的方法
因为hashCode的源码注释里这样规定:
(主要看第二条 即 根据 equals(Object) 方法,判断两个对象是相等的那么对这两个对象中的每个对象 调用 hashCode 方法都必须生成相同的整数结果)
1.在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。
从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。2.如果根据 equals(Object) 方法,判断两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
3.如果根据 equals(java.lang.Object) 方法,判断两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。 但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。因为如果不相等的对象生成了相同的hashCode 会导致哈希表出现哈希碰撞。
那么:
假设一个类,重写了equals方法,其相等条件是name属性相等,就返回true。
如果不重写hashcode方法,其返回的依然是两个对象的内存地址值,必然不相等。
这就出现了equals方法相等,但是hashcode不相等的情况。这不符合hashcode的规则。
当然这种情况程序并不会强制你重写hashCode方法,但是当我们使用一些哈希表结构时可能会造成错误的判断结果。
(3)、clone()方法
在本文章中创建对象的四种方式一问处 有详细介绍 使用Ctrl+f 搜索clone即可
(4)、toString方法
在源码的注释中这样写道:
该方法以文本的方式来表示一个对象,所有的子类都应该重写这个方法
toString()方法默认打印的是 该对象所属类的全限定名@该对象的hashCode的16进制表示形式
例如:
Object obj1 = new Object();
System.out.println(obj1.hashCode());
System.out.println(obj1.toString());
程序执行结果:
1919892312
java.lang.Object@726f3b58
其中 726f3b58 为 1919892312的16进制值
我们应该重写toString方法来打印我们所希望看到的内容 方面调试
(5)、notify()方法
随机唤醒正在等待的对象(正在等待的对象可能有多个)
该方法需要由监听器(锁)对象的所有者来调用
(6)、notifyAll()方法
唤醒所有正在等待的对象
(7)、wait() 及其重载的方法
调用该方法会使线程进入等待状态,直到该线程被唤醒或指定的等待时间过去或线程中断
该方法需要由监听器(锁)对象的所有者来调用
注意 :
wait()、notify()和notifyAll()这三个方法都需要在同步代码块中调用 否则会抛异常
这里加2个小问题:
①、为什么wait()方法和notify()方法 定义在Object类中而不是定义在Thread类中呢?
因为在Java语言中锁对象可以是任意的,所以wait()方法和notify()方法必须定义在Object类中
详细的锁机制看这里 (opens new window)
②、Thread类中的sleep()方法和Object类中的wait()方法有什么区别?
二者都可以暂停当前线程,释放CPU控制权
主要的区别在于wait()方法在释放CPU的同时,释放了对象锁的控制。而sleep()方法没有释放锁。
(8)、finalize()方法
当一个对象满足垃圾回收的条件,并且被回收的时候,其finalize()方法就会被调用,该方法为JVM自行调用,我们不需要重写这个方法。
当某一个对象,没有任何引用指向它的时候,那么它就满足垃圾回收的条件,在适当的时候,
JVM虚拟机进行GC(Garbage Collection)将其回收,释放空间,以供后续利用
(9)、getClass()方法
返回此 Object 的运行时类的 Class 对象。用于反射编程。
返回的 Class 对象是由所表示类的 static synchronized 方法锁定的对象。
# 13、String是基本数据类型吗? String类能不能被继承? String类为什么要被final修饰?
String不是基本数据类型
因为String类被final修饰, java语法规定 被final修饰的类不能够被继承
String类被final修饰 从两个方面分析
- 1、安全问题
- 2、效率问题
参考这篇文章 (opens new window)
# 14、String、StringBuilder、StringBuffer的区别?
String | 不可变字符串 | 线程安全 |
---|---|---|
StringBuffer | 可变字符串 | 线程安全(内部方法都使用synchronized修饰) |
StringBuilder | 可变字符串 | 线程不安全 |
当涉及大量字符串拼接操作且没有线程安全问题的前提下,建议使用StringBuilder处理字符串拼接
# 15、接口和抽象类的区别?
抽象类
1、抽象类使用abstract修饰;
2、抽象类不能实例化,即不能使用new关键字来实例化对象;
3、含有抽象方法(使用abstract关键字修饰的方法)的类是抽象类,必须使用abstract关键字修饰;
4、抽象类可以含有抽象方法,也可以不包含抽象方法,抽象类中可以有具体的方法;
5、如果一个子类实现了父类(抽象类)的所有抽象方法,那么该子类可以不必是抽象类,否则就是抽象类;
6、抽象类中的抽象方法只有方法体,没有具体实现;接口
1、接口使用interface修饰;
2、接口不能被实例化;
3、一个类只能继承一个类,但是可以实现多个接口;
4、在Java8版本开始,接口可以提供实现方法了,前提是要在方法前加上一个default修饰符
5、接口不能够实现接口,但是接口之间可以多继承
# 16、运行时异常与一般异常有何异同?Error和Exception有什么区别?
运行时异常又叫做非可查异常,在编译过程中,不需要必须进行显示捕捉
比如:
空指针异常:NullPointerException
下标越界异常:ArrayIndexOutOfBoundsException
类找不到异常:ClassNotFoundException等
一般异常又叫做可查异常,在编译过程中,必须进行处理,要么try catch捕捉,要么通过throws抛出去
Error和Exception都实现了Throwable的接口
Error: 指的是JVM层面上的错误,比如,内存不足,OutOfMemoryError 不需要代码进行显式处理
Exception: 指的是代码逻辑的异常,比如下标越界,OutOfIndexException等
RuntimeException: 在编译期是不检查的,出现问题后,需要我们修改代码解决问题
非 RuntimeException: 编译期就必须处理的,否则程序不能通过编译 (比如 IOException)
列举一些常见的运行时异常:
NullPointerException 空指针异常
ArithmeticException 算术异常,比如除数为零
ClassCastException 类型转换异常
ConcurrentModificationException 并发修改异常,遍历一个集合的时候,删除集合的元素,就会抛出该异常
IndexOutOfBoundsException 数组下标越界异常
NegativeArraySizeException 为数组分配的空间是负数异常
NumberFormatException 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常
例如: Integer.parseInt(“abc”);
追加个小问题 throw和throws的区别?
手写一个自定义异常?
自定义异常类
public class ScoreException extends Exception { public ScoreException() {} public ScoreException(String message) { super(message); } }
老师类
public class Teacher { public void checkScore(int score) throws ScoreException { if(score<0 || score>100) { // throw new ScoreException(); throw new ScoreException("你给的分数有误,分数应该在0-100之间"); } else { System.out.println("成绩正常"); } } }
测试类
public class Demo { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入分数:"); int score = sc.nextInt(); Teacher t = new Teacher(); try { t.checkScore(score); } catch (ScoreException e) { e.printStackTrace(); } } }
# 17、&和&&的区别?
&:有两个作用,分别是位与,和逻辑与
&&:就是逻辑与
作为逻辑与,&和&&分别表示为长路与和短路与
长路与 & 两侧都会被运算,短路与 && 只要第一个是false,第二个直接短路不再进行运算
# 18、Java关键字 final,finally,finalize的区别?
final:修饰类,方法,基本类型变量,引用时 分别有不同的意思
修饰类: 表示该类不能被继承
修饰方法: 表示该方法不能被重写
修饰基本类型变量: 表示该变量只能被赋值一次 数据内容不可变
修饰引用: 表示该引用只有一次指向对象的机会 地址不可变 内容可变
修饰成员变量: 成员变量无默认值 必须为成员变量手动赋初值
finally:
适用于异常处理时,无论是否有异常抛出,表示总是执行
finalize:
finalize是Object的方法,所有类都继承了该方法。
当一个对象满足垃圾回收的条件,并且被回收的时候,其finalize()方法就会被调用
# 19、OverLoad和Override的区别,Overloaded的方法是否可以改变返回值的类型?
OverLoad重载
发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
下面是《Java 核心技术》对重载这个概念的介绍:
Override重写
重写是子类对父类允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,
返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
另外,如果父类方法访问修饰符为 private 则子类就不能重写该方法。
# 20、==和equals的区别?
==比较的是两个对象的地址值是否相同
equals()方法也是比较两个对象的地址值是否相同
但是在不同的类中 对该方法有不同的实现(即重写equals方法),如String类重写了equals方法 当两个字符串的内容相同时 即认为这个字符串相同
# 21、在Java中,如何跳出当前的多重嵌套循环?
在最外层循环前加一个标记如 A,然后用 break A;可以跳出多重循环。
例:
A: for(int i=0;i<10;i++){
for(j=0;j<10;j++){
if(j==5){
break A;
}
}
}
# 22、Java的访问修饰符有哪几种?作用范围分别是什么?
缺省 default
# 23、try{}里面有一个return语句,那么紧跟在这个try后面的finally{}里的code会不会被执行,什么时候被执行,在return之前还是之后?
会执行finally里面的code, Try里面的return语句是在finally执行过后才执行的
若finally里和try里都有return语句 则只执行finally里面的return语句
# 24、数组、String、集合、文件 获取长度的方法分别是什么?
数组获取长度: .length属性 记住这个是属性不是方法
String获取长度: length()方法
集合获取长度: size()方法
文件获取长度: length()方法
# 25、Math.round(11.5)等于多少? Math.round(-11.5)等于多少?
Math.round的意思是+0.5取整数部分
所以
Math.round(11.5)即11.5+0.5=12
Math.round(-11.5)即-11.5+0.5=-11
# 26、String s = new String(‘xyz’);创建了几个String对象?
String s = new String(‘xyz’);
首先构造方法 new String(‘xyz’)中的“xyz”;这本身就是一个字符串对象
然后new关键字一定会创建一个对象,所以总共创建了两个String对象
# 27、heap和stack有什么区别?
heap:堆
stack:栈
- 存放的内容不一样:
heap:一般用来存放对象
stack:一般存放基本类型(int,float,boolean等等),引用(对象地址),方法调用等
- 存取方式不一样:
heap:堆是动态分配的,其大小可以根据需要动态增长或减少,因此存取相对较慢。
stack:是固定大小的,并且是先入后出的顺序,并且存取速度比较快,因为栈的分配和释放都是由系统自动管理的
# 28、Java中常用关键字 final,static,this,super 总结
# 29、下面 Integer 类型的数值比较输出的结果为?
如果不清楚常量池概念 很容易认为两个输出要么都是 true 要么都是 false。
首先需要注意的是 f1、f2、f3、f4 四个变量都是 Integer 对象引用,所以下面的==运算比较的不是值而是引用。
装箱的本质是什么呢?
当我们给一个 Integer 对象赋一个 int 值的时候,会调用 Integer 类的静态方法 valueOf,
如果看看 valueOf 的源代码就知道发生了什么。
源码:
IntegerCache 是 Integer 的内部类
如果整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的 Integer 对象,
所以上面的面试题中 f1==f2 的结果是 true,
而 f3==f4 的结果是 false。
# 30、switch 中可以接收的参数有哪些类型?
Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。
从 Java 5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型。
从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
# 31、String a ="aa"与String a = new String("aa")之间的区别?
String a = “aa”; 这段代码执行过程是:
先到常量池中寻找“aa”字符串常量
1.如果常量池中存在"aa",则直接将“aa”对象在常量池中的引用地址传递给a;
2.如果不存在,则在常量池中创建“aa”,然后将地址传递给a
String a = new String(“aa”);执行过程是:
首先在堆内存中创建对象“aa”,然后在常量池中寻找“aa”
1.如果存在,不做任何操作;
2.如果不存在,则在常量池中创建“aa”对象;
总结;直接赋值形式的新建string对象是从常量池中拿数据;最多创建一个string对象,最少创建0个string对象(常量池中已存在该对象的情况)
new形式新建string对象无论怎样会首先在堆内存中创建一个string对象,然后确保常量池中也有一个相同内容的string对象;最多创建2个,最少创建1个。
Sring字符串使用 "+"符号 进行拼接时 编译器是如何处理的?
public static void main(String[] args) {
String str1 = "hello";
String str2 = "world";
String str3 = "hello" + "world";
String str4 = str1 + str2;
String str5 = "helloworld";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
}
运行结果:
为什么 "hello" + "world" 和 "helloworld" 通过 == 比较 会返回 true
说明 这 "hello" + "world" 拼接的字符串 和 "helloworld" 字符串 在内存中的地址一致
这就引出了一个编译优化的概念 叫常量折叠(Constant Folding)
String str3 = "hello" + "world";
编译器会优化成 String str3 = "helloworld";
注意:只有在编译期就可以确定值的常量才可以进行 常量折叠 优化 这也解释了上面的 str3 == str4 结果为false
str4 == str5 结果为false
当我们把 字符串 str1 和str2 前面加上 final 就可以让编译器 把字符串 str1和str2 当做常量来处理
public static void main(String[] args) {
final String str1 = "hello";
final String str2 = "world";
String str3 = "hello" + "world";
String str4 = str1 + str2;
String str5 = "helloworld";
System.out.println(str3 == str4);//true
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//true
}
运行结果:
对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象。
对于需要大量拼接字符串的场景 建议直接使用StringBuilder 如果需要考虑线程安全问题 使用StringBuffer操作。
# 32、对String类中intern()方法的理解?
class StringEqualTest {
public static void main(String[] args) {
String s1 = "Programming";
String s2 = new String("Programming");
String s3 = "Program";
String s4 = "ming";
String s5 = "Program" + "ming";
String s6 = s3 + s4;
System.out.println(s1 == s2); //false
System.out.println(s1 == s5); //true
System.out.println(s1 == s6); //false
System.out.println(s1 == s6.intern()); //true
System.out.println(s2 == s2.intern()); //false
//================================================================
String a5 = new String("A") + new String("A");//在常量池创建A 在堆上创建对象AA
a5.intern();//由于常量池中没有AA 则在常量池上创建引用指向堆中的AA对象
String a6 = "AA";//此时不会再在常量池上创建常量AA,而是将a5的引用返回给a6
System.out.println(a5 == a6); //true
}
总结:
s2.intern();
判断这个常量s2是否存在于常量池。
如果存在判断存在内容是引用还是常量,
如果是引用,返回引用地址指向堆空间对象,
如果是常量,直接返回常量池该常量的地址
如果不存在,将当前对象引用复制到常量池,并且返回的是当前对象的引用
字符串的+操作其本质是创建了 StringBuilder 对象进行 append 操作,然后将拼接后的 StringBuilder 对象用 toString 方法处理成 String 对象
# 33、对象实体与对象引用之间的关系?
new 创建对象实例 (对象实例在堆内存中) ,对象引用指向对象实例 (对象引用存放在栈内存中)。
一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);
一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
# 34、构造方法有哪些特点?
构造方法名与类名相同。
没有返回值,但不能用 void 声明构造函数。
生成类的对象时自动执行,无需调用。
# 35、静态方法和实例方法的区别?
在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。
而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
# 36、为什么Java中只有值传递?
# 37、获取用键盘输入常用的两种方法?
方法 1:通过 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
方法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
# 38、成员变量与局部变量的区别有哪些?
1、从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
2、从变量在内存中的存储方式来看:如果成员变量是使用static修饰的,那么这个成员变量是属于类的,
static修饰的成员变量在内存中只会有一份拷贝,存储在方法区(Java 8 及之前)或者元空间(Java 8 及之后)中。
如果没有使用static修饰,这个成员变量是属于实例的,存在于堆内存,局部变量则存在于栈内存。3、从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
4、成员变量如果没有被赋初值则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
public static void main(String[] args) {
int a;
System.out.println(a); // 局部变量如果不赋初值 当使用这个局部变量时 编译不通过
}
# 39、Java中的自动装箱和拆箱机制?
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;
深入剖析 Java 中的装箱和拆箱 (opens new window)
# 40、equals的正确用法?
String s = null;
"aaa".equals(s); //常量放前面 防止空指针
# 41、阿里巴巴开发手册的一些要求
《阿里巴巴Java开发手册》
【强制】所有的 POJO 类属性必须使用包装数据类型。
【强制】RPC 方法的返回值和参数必须使用包装数据类型。
【推荐】所有的局部变量使用基本数据类型。
比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样.
说明 :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。
正例 : 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
反例 : 比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
# 42、匿名内部类 访问的数据 为什么需要被申明为 final?
从 Java 8 开始,允许匿名内部类访问未显式声明为 final 但在初始化后未被修改的局部变量。 如果在匿名内部类内部修改该变量,还是需要声明为final。
目的是为了避免匿名内部类在外围方法结束后对局部变量的不确定修改,从而确保线程安全和数据一致性。
举个栗子:
编译报错:
public class TestA {
public static void main(String[] args) {
int a = 1;
Thread t = new Thread(() -> {
System.out.println(a);
a = 123;
});
t.start();
}
}
如果把上面的 int a = 1;
改成 final int a = 1;
就正常了。