Java 与 C++ 的区别
Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。
Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。
Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
Java 支持自动垃圾回收,而 C++ 需要手动回收。
Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。
Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。
Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。
基本类型
image.pngshort:
最小值:Short.MIN_VALUE=-32768 (-2的15此方)
最大值:Short.MAX_VALUE=32767 (2的15次方-1)
int:
最小值:Integer.MIN_VALUE= -2147483648 (-2的31次方)
最大值:Integer.MAX_VALUE= 2147483647 (2的31次方-1)
long:
最小值:Long.MIN_VALUE=-9223372036854775808 (-2的63次方)
最大值:Long.MAX_VALUE=9223372036854775807 (2的63次方-1)
float:
最小值:Float.MIN_VALUE=1.4E-45 (2的-149次方)
最大值:Float.MAX_VALUE=3.4028235E38 (2的128次方-1)
double:
最小值:Double.MIN_VALUE=4.9E-324 (2的-1074次方)
最大值:Double.MAX_VALUE=1.7976931348623157E308 (2的1024次方-1)
方法重载 & 方法重写
image.png image.png重载:
方法名一定相同;
方法参数一定不同;
String,StringBuffer 和 StringBuilder
-
可变性
String 不可变
StringBuffer 和 StringBuilder 可变 -
线程安全
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步
String 类中使⽤ final 关键字修饰字符数组来保存字符串, private final char value[] ,所以 String 对象是不可变的。
补充:在 Java 9 之后,String 类的实现改⽤ byte 数组存储字符串 private final byte[] value。
每次对 String 类型进⾏改变的时候,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引⽤。如果没有就在常量池中重新创建⼀个 String 对象,把它赋给当前引⽤。
String 中的对象是不可变的,也就可以理解为常量,线程安全。
StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使⽤字符数组保存字符串 char[]value 但是没有⽤ final 关键字修饰,所以这两种对象都是可变的。
StringBuilder 与 StringBuffer 的构造⽅法都是调⽤⽗类构造⽅法也就是 AbstractStringBuilder 实现的,⼤家可以⾃⾏查阅源码。
AbstractStringBuilder 是StringBuilder 与 StringBuffer 的公共⽗类,定义了⼀些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共⽅法。StringBuffer 对⽅法加了同步锁或者对调⽤的⽅法加了同步锁,所以是线程安全的。StringBuilder 并没有对⽅法进⾏加同步锁,所以是⾮线程安全的。
StringBuffer 每次都会对 StringBuffer 对象本身进⾏操作,⽽不是⽣成新的对象并改变对象引⽤。相同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的⻛险。
== 和 equals
== 基本类型判断值是否相同,引用类型判断引用地址是否相同。
equals 属于 Object 类的实例方法,在 Object 类中用 == 判断,因此如果子类没有重写 equals() 方法,就是用 == 判断。
String 类的equals,字符串字面量、new、intern、+、字符串常量池
- equals
String 类已经重写了equals方法,判断char[] 长度、每个元素的内容是否相同。
- 字符串常量池
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。
String 的 intern() 方法可以在运行过程将字符串添加到字符串常量池中。
当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引⽤。如果没有就在常量池中重新创建⼀个 String 对象。
在 Java 7 之前,字符串常量池String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
- 字符串字面量、new、intern、+
public static void main(String[] args){
String s1="Hello";
String s2="Hello";
String s3=new String("Hello");
String s4 = s1.intern();
System.out.println("s1和s2 引用地址是否相同:"+(s1 == s2));//true
System.out.println("s1和s2 值是否相同:"+s1.equals(s2));//true
System.out.println("s1和s3 引用地址是否相同:"+(s1 == s3));//false
System.out.println("s1和s3 值是否相同:"+s1.equals(s3));//true
System.out.println("s1和s4 引用地址是否相同:"+(s1 == s4));//true
}
字符串字面量方式创建String:String s1 = "Hello";
new方式创建String:String s3 = new String("Hello");
intern方式创建String:String s4 = s1.intern();
字符串字面量方式创建一个字符串时,JVM首先为检查字符串常量池中是否有值相等的字符串,如果有,则不再创建,直接返回该字符串的引用地址,若没有,则创建,然后放到字符串常量池中,并返回新创建的字符串的引用地址。所以上面s1与s2引用地址相同。
当一个字符串调用 intern() 方法时,如果字符串常量池中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回字符串常量池中字符串的引用;否则,就会在字符串常量池中添加一个新的字符串,并返回这个新字符串的引用。
String s3=new String("Hello")
创建了两个对象,一个是在字符串常量池中创建的“Hello”对象,一个是new关键字在堆中创建的“Hello”的拷贝对象,s3指向这个拷贝对象。
public class TestStringDemo {
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
}
}
字符串常量和字符串类型的变量是不同的概念,字符串常量储存于方法区,而字符串类型的变量储存于堆(heap)。
两个或者两个以上的字符串常量相加,在预编译的时候“+”会被优化,相当于把两个或者两个以上字符串常量自动合成一个字符串常量,不会用到StringBuilder对象。如 String s5 = "Program" + "ming"
;在被编译器优化成了String s5 = "Programming"
;
字符串类型的变量相加操作其本质是new了StringBuilder对象进行append操作,拼接后调用toString()返回String对象,toString()方法实际是调用了new String(...)。如s6 = s3 + s4
。
参考链接:
- cs-notes
- String str=“aaa“与 String str=new String(“aaa”)一样吗?
- String解析——String s=new String("a");String s = "a" + "b";String的intern方法等问题
- Java中字符串相加和字符串常量相加区别
拆箱和装箱, 缓存池
int 在装箱的时候自动调用的是 Integer 的 valueOf(int) 方法。
而在拆箱的时候自动调用的是 Integer 的 intValue 方法。
其他的也类似,比如Double、Character。
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
- new Integer(123) 每次都会新建一个对象;
- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在 Java 8 中,Integer 缓存池的大小默认为 -128~127。
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
编译器会在自动装箱过程调用 valueOf() 方法,因此多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,那么就会引用相同的对象。
Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true
基本类型对应的缓冲池如下:
- boolean values true and false
- all byte values
- short values between -128 and 127
- int values between -128 and 127
- char in the range \u0000 to \u007F
在使用这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。
在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax=<size> 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界。
- https://pdai.tech/md/java/basic/java-basic-lan-basic.html
- 深入剖析Java中的装箱和拆箱
- https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20%E5%9F%BA%E7%A1%80.md#%E7%BC%93%E5%AD%98%E6%B1%A0
在 Java 中定义⼀个不做事且没有参数的构造⽅法的作⽤
如果一个类没有手动写构造方法,java默认提供一个没有参数的构造方法。如果手动写了有参数的构造方法,java不会提供没有参数的构造方法。
Java 程序在执⾏⼦类的构造⽅法之前,如果没有⽤ super() 来显式调⽤⽗类特定的构造⽅法,则会默认调⽤⽗类中“没有参数的构造⽅法”。
因此,如果⽗类中只定义了有参数的构造⽅法,⽽在⼦类的构造⽅法中⼜没有⽤ super() 来调⽤⽗类中特定的构造⽅法,则编译时将发⽣错误,因为 Java 程序在⽗类中找不到没有参数的构造⽅法可供执⾏。解决办法是在⽗类⾥加上⼀个不做事且没有参数的构造⽅法。
java中四种修饰符的限制范围
java中四种修饰符的限制范围.pngObject类中的方法
Object类有12个成员方法,按照用途可以分为以下几种
1,构造函数
2,hashCode和equale函数用来判断对象是否相同
Object 的 hashcode ⽅法是本地⽅法,也就是⽤ c 语⾔或 c++ 实现的,该⽅法通常⽤来将对象的 内存地址 转换为整数之后返回。
public native int hashCode();
- 为什么重写 equals 时必须重写 hashCode ⽅法?
如果两个对象相等,则 hashcode ⼀定也是相同的,对两个对象分别调⽤ equals⽅法都返回 true。
但是,两个对象有相同的 hashcode 值,对两个对象分别调⽤ equals⽅法不一定返回 true。
hashCode() 的默认⾏为是对堆上的对象产⽣独特值。如果没有重写hashCode() ,则该class 的两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据)。
因此,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖。
3,wait(),wait(long),wait(long,int),notify(),notifyAll()
4,toString()和getClass
5,clone()
6,finalize()用于在垃圾回收
链接:https://www.jianshu.com/p/51d9ba549172
7、接口和抽象类的区别
- 构造器
抽象类可以有构造器;
而接口不能有构造器。
- 方法
什么是抽象方法?
被abstract修饰,可以有public、protected和default这些修饰符(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!),缺省情况下默认为public。
一个类中含有抽象方法(被abstract修饰),那么这个类必须被声明为抽象类(被abstract修饰)。
-
接⼝
接⼝的抽象方法会被隐式地指定为public abstract方法且只能是public abstract方法。
jdk 7 或更早版本:接⼝只能有抽象⽅法。
jdk 8 :接⼝可以有默认⽅法和静态⽅法功能。
jdk 9 :接⼝可以有私有⽅法和私有静态⽅法。 -
抽象类
抽象类可以有⾮抽象的⽅法。
- 变量
- 接口中的变量都是共有的静态变量,会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),且必须给其初值,所以实现类中不能重新定义,也不能改变其值。
- 抽象类中可以有普通变量
-
继承
⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。
接⼝⾃⼰本身可以通过 extends 关键字扩展多个接⼝。 -
设计
从设计层⾯来说,抽象类是对类的抽象,是⼀种模板设计;
⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。
参考:
《JavaGuide》pdf
《Java 核心基础总结》pdf
https://blog.csdn.net/StackFlow/article/details/80220944
https://blog.csdn.net/weixin_36759405/article/details/82774558
成员变量与局部变量的区别有哪些?
-
从语法形式上看:
成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;
成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。 -
从变量在内存中的存储⽅式来看:
成员变量
- 用 static 修饰的,那么这个成员变量是属于类的,存储在方法区上(jdk 1.8前在永久代,jdk 1.8后在堆上);
- 没有⽤ static 修饰,这个成员变量是属于实例的,存储在堆上。
局部变量:
- 基本数据类型,存储在栈内存;
- 引⽤数据类型,存放的是引⽤地址,指向堆内存对象或者常量池对象。
-
从变量在内存中的⽣存时间上看:
成员变量是对象的⼀部分,它随着对象的创建⽽存在;
⽽局部变量随着⽅法的调⽤⽽⾃动消失。 -
没有被赋初值:
成员变量如果没有被赋初值,则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值);
⽽局部变量则不会⾃动赋值。
Java序列化的方式
Java序列化算法:
-
所有保存到磁盘的对象都有一个序列化编码号。
-
当程序试图序列化一个对象时,会先检查此对象是否已经序列化过,只有此对象从未(在此虚拟机)被序列化过,才会将此对象序列化为字节序列输出。
-
如果此对象已经序列化过,则直接输出编号即可。
注意:
反序列化的顺序与序列化时的顺序一致。
Java序列化同一对象,并不会将此对象序列化多次得到多个对象。
链接:https://juejin.cn/post/6844903848167866375
传值和传引用的区别,Java是怎么样的,有没有传值引用
按值调⽤(call by value)表示⽅法接收的是调⽤者提供的值,⽽按引⽤调⽤(call by reference)表示⽅法接收的是调⽤者提供的变量地址。⼀个⽅法可以修改传递引⽤所对应的变量值,⽽不能修改传递值调⽤所对应的变量值。 它⽤来描述各种程序设计语⾔(不只是 Java)中⽅法参数传递⽅式。
Java 程序设计语⾔总是采⽤按值调⽤。也就是说,⽅法得到的是所有参数值的⼀个拷⻉。
基本数据类型传值:值的拷贝;
引用数据类型传值:引用地址的拷贝,引⽤地址和引用地址的拷贝拷⻉指向同⼀个对象。
final 关键字
final 关键字主要⽤在三个地⽅:变量、⽅法、类。
- final 修饰变量
如果是基本数据类型的变量,则其数值⼀旦在初始化之后便不能更改;
如果是引⽤类型的变量,则在对其初始化之后便不能再让其指向另⼀个对象。 - final 修饰类
表明这个类不能被继承。
final 类中的所有成员⽅法都会被隐式地指定为 final ⽅法。 - final 修饰⽅法
- 父类的 final 方法不能被子类重写。
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。 - 早期版本的java为了提升效率:在早期的 Java 实现版本中,会将 final ⽅法转为内嵌调⽤。但是如果⽅法过于庞⼤,可能看不到内嵌调⽤带来的任何性能提升(现在的 Java 版本已经不需要使⽤final ⽅法进⾏这些优化了)。类中所有的 private ⽅法都隐式地指定为 final。
static
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
存在继承的情况下,初始化顺序为:
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
参考:https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20%E5%9F%BA%E7%A1%80.md#static
Java &、&&、|、||、^、<<、>>、~、>>>等运算符
& 按位与
&& 逻辑与
| 按位或
|| 逻辑或
^ 按位异或
~ 取反
<< 左移
>> 右移
>>> 无符号右移
Java 中的异常处理
image.png在 Java 中,所有的异常都有⼀个共同的祖先 java.lang 包中的 Throwable 类。 Throwable 类有两个重要的⼦类 Exception (异常)和 Error (错误)。
Exception
程序本身可以处理的异常,可以通过 catch 来进⾏捕获。 Exception ⼜可以分为 受检查异常(必须处理)和 不受检查异常(可以不处理)。
-
受检查异常(必须处理)
除了 RuntimeException 及其⼦类以外,其他的 Exception 类及其⼦类都属于受检查异常 。常⻅的受检查异常有: IO 相关的异常、 ClassNotFoundException 、 SQLException,...。
如果受检查异常没有被 catch / throw 处理的话,就没办法通过编译。 -
不受检查异常(可以不处理)
RuntimeException (运行时异常)及其⼦类。
即使不处理不受检查异常也可以正常通过编译。
RuntimeException子类如:NullPointerException, ArithmeticException, BufferOverflowException, ConcurrentModificationException, IllegalArgumentException
参考链接:https://docs.oracle.com/javase/7/docs/api/java/lang/RuntimeException.html
Error
Error 属于程序⽆法处理的错误 ,我们没办法通过 catch 来进⾏捕获 。例如,Java 虚拟机运⾏错误( Virtual MachineError )、虚拟机内存不够错误( OutOfMemoryError )、类定义错误( NoClassDefFoundError )等 。这些异常发⽣时,Java虚拟机(JVM)⼀般会选择线程终⽌。
try-catch-finally
image.pngreturn语句并不是函数的最终出口,如果有finally语句,在return之后还会执行finally(return的值会暂存在栈里面,等待finally执行后再返回)。
- 执行try块或catch块语句
- 执行finally块中的语句
- return(如果需要):
- 如果finally块没有return,最终return try或者catch块的值。
- 如果finally块有return,最终return finally块的值。
参看链接:https://www.jianshu.com/p/ebb13ac70f5d
反射
获取 Class 对象有三种方式:
// 1.通过字符串获取Class对象,这个字符串必须带上完整路径名
Class studentClass = Class.forName("com.test.reflection.Student");
// 2.通过类的class属性
Class studentClass2 = Student.class;
// 3.通过对象的getClass()函数
Student studentObject = new Student();
Class studentClass3 = studentObject.getClass();
通过这三种方式获取到的 Class 对象是同一个,也就是说 Java 运行时,每一个类只会生成一个 Class 对象。
获取字段有两个 API:getDeclaredFields和getFields。他们的区别是:getDeclaredFields用于获取所有声明的字段,包括公有字段和私有字段,getFields仅用来获取公有字段。
获取构造方法同样包含了两个 API:用于获取所有构造方法的 getDeclaredConstructors和用于获取公有构造方法的getConstructors。
获取非构造方法的两个 API 是:获取所有声明的非构造函数的 getDeclaredMethods 和仅获取公有非构造函数的 getMethods。
参考链接:https://zhuanlan.zhihu.com/p/86293659
Java 多态-动态绑定
多态是面向对象程序设计非常重要的特性,它让程序拥有 更好的可读性和可扩展性:
发生在继承关系中。
需要子类重写父类的方法。
父类类型的引用指向子类类型的对象。
自始至终,多态都是对于方法而言,对于类中的成员变量,没有多态的说法。
一个基类的引用变量接收不同子类的对象将会调用子类对应的方法,这其实就是动态绑定的过程。
引用类型的变量具有两种类型:编译时类型和运行时类型。(也分别叫做声明类型和实际类型)举个简单的例子:
//假设Student类是Person类的子类
Person p = new Student();
编译时类型
也叫声明类型,即由声明变量时的类型所决定。
上式的Person即为引用变量p的编译时类型。
运行时类型
也叫实际类型,即由指向对象的实际类型所决定。
上式的Student即为引用变量p的运行时类型。
方法绑定
将方法调用同方法主体关联起来被称为绑定。
静态绑定
在程序执行前进行绑定,叫做静态绑定,也称作前期绑定。在面向过程的语言中是默认的绑定方式。
在Java中,用private、static和final修饰的方法(static和final之后会做出总结)或构造器能够准确地让编译器调用哪个方法,就是静态绑定(static binding)。
动态绑定
在运行时根据对象的运行时类型进行绑定,叫做动态绑定,也叫做后期绑定。当然在Java中,除了静态绑定的那些方法,其他方法的调用方式就是动态绑定啦。
方法表
我们还可以发现,test(new PrimaryStudent());的运行结果是Student,,结果很明显,因为PrimaryStudent类中并没有重写父类的方法,如果采用动态绑定的方式调用方法,虚拟机会首先在本类中寻找适合的方法,如果没有,会一直向父类寻找,直到找到为止。
那么,每次调用时都要向上寻找,时间开销必然会很大。为此虚拟机预先为每个类都创建了方法表,其中列出了所有的方法签名(返回值类型不算)和实际调用的方法,这样子的话,在调用方法时直接查表就可以了。(值得一提的是,如果用super限定调用父类方法,那么将直接在实际类型的父类的表中查找)
public class DynamicBinding {
//Object是所有类的超类,根据向上转型,该方法可以接受任何类型的对象
public static void test(Object x) {
System.out.println(x.toString());
}
public static void main(String[] args) {
test(new PrimaryStudent());//Student
test(new Student());//Student
test(new Person());//Person
test(new Object());//java.lang.Object@1b6d3586
}
}
class Person extends Object {
@Override
public String toString() {
return "Person";
}
public void run(){}
public void count(int a){}
}
class Student extends Person {
@Override
public String toString() {
return "Student";
}
public void jump(){}
}
class PrimaryStudent extends Student {
}
下面是Person类的方法表:
Person:
//下面省略Object方法签名
//xxx()-> Object.xxx()
//方法签名->实际调用的方法
toString()->Person.toString()
run()->Person.run()
count(int)->Person(int)
下面是Student类的方法表:
Student:
//下面省略Object方法签名
//xxx()-> Object.xxx()
//方法签名->实际调用的方法
toString()->Student.toString()
jump()->Student.jump()
run()->Person.run()
count(int)->Person(int)
网友评论