Skip to content

Java概念辨析

7604字约25分钟

2024-10-24

1.JDK、JRE、JVM的区别

一、JDK JDK(Java Development Kit) 是整个JAVA的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。 JDK是java开发工具包

二、JRE

JRE(Java Runtime Environment,Java运行环境),包含JVM标准实现及Java核心类库。JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器) JRE是指java运行环境。光有JVM还不能成class的 执行,因为在解释class的时候JVM需要调用解释所需要的类库lib。 (jre里有运行.class的java.exe) JRE ( Java Runtime Environment ),是运行 Java 程序必不可少的(除非用其他一些编译环境编译成.exe可执行文件……)

三、JVM

JVM(Java Virtual Machine),即java虚拟机, java运行时的环境,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。针对java用户,也就是拥有可运行的.class文件包(jar或者war)的用户。里面主要包含了jvm和java运行时基本类库(rt.jar)。rt.jar可以简单粗暴地理解为:它就是java源码编译成的jar包。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

img

查看源图像

JDK>JRE>JVM

扩展:

1.三者联系: JVM不能单独搞定class的执行,解释class的时候JVM需要调用解释所需要的类库lib。在JDK下面的的jre目录里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre。JVM+Lib=JRE。利用JDK(调用JAVA API)开发了属于我们自己的JAVA程序后,通过JDK中的编译程序(javac)将我们的文本java文件编译成JAVA字节码,在JRE上运行这些JAVA字节码,JVM解析这些字节码,映射到CPU指令集或OS的系统调用。

2.三者区别: a.JDK和JRE区别:在bin文件夹下会发现,JDK有javac.exe而JRE里面没有,javac指令是用来将java文件编译成class文件的,这是开发者需要的,而用户(只需要运行的人)是不需要的。JDK还有jar.exe, javadoc.exe等等用于开发的可执行指令文件。这也证实了一个是开发环境,一个是运行环境。 b.JRE和JVM区别:JVM并不代表就可以执行class了,JVM执行.class还需要JRE下的lib类库的支持,尤其是rt.jar。

2.& 与 &&的区别

  1. &和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。

  2. &&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(str != null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。If(x33 & ++y>0) y会增长,If(x33 && ++y>0)不会增长

  3. &还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。

扩展:

在控制语句中由于&&具有短路作用,可以进行空指针保护

3.break、continue、return的区别

1.return关键字并不是专门用于跳出循环的,return的功能是结束一个方法。 一旦在循环体内执行到一个return语句,return语句将会结束该方法,循环自然也随之结束。与continue和break不同的是,return直接结束整个方法,不管这个return处于多少层循环之内。

2.continue的功能和break有点类似,区别是continue只是中止本次循环,接着开始下一次循环。而break则是完全中止循环。

3.break用于完全结束一个循环,跳出循环体。不管是哪种循环,一旦在循环体中遇到break,系统将完全结束循环,开始执行循环之后的代码。 break不仅可以结束其所在的循环,还可结束其外层循环。此时需要在break后紧跟一个标签,这个标签用于标识一个外层循环。Java中的标签就是一个紧跟着英文冒号(:)的标识符。且它必须放在循环语句之前才有作用。

4.int与Integer的区别

1、Integer是int的包装类,int则是java的一种基本数据类型 2、Integer变量必须实例化后才能使用,而int变量不需要 3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 4、Integer的默认值是null,int的默认值是0

延伸: 关于Integer和int的比较 1、由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。

Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false

2、Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)

Integer i = new Integer(100);
int j = 100
System.out.print(i == j); //true

3、非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为 ①当变量值在-128~127之间时,非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同;②当变量值在-128~127之间时,非new生成Integer变量时,java API中最终会按照new Integer(i)进行处理(参考下面第4条),最终两个Interger的地址同样是不相同的)

Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false

4、对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false

Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true
Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false

原因: java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);,而java API中对Integer类型的valueOf的定义如下:

public static Integer valueOf(int i){
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high){
        return IntegerCache.cache[i + (-IntegerCache.low)];
    }
    return new Integer(i);
}

java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了

5.a++与++a的区别

区别:a++先进行其他运算再进行+1操作,++a先进行+1操作再进行其他运算

扩展:a++ , ++a 在底层实现中,不同的就是iinc的时机, 如果的a++的话 , 那么就会先把值加载到操作数栈中,然后再对局部变量表中a值进行自增,++a 则相反。

反编译

虚拟机栈

在java虚拟机中,有内存区域这一块的知识,其中关于虚拟机栈的知识如下:

虚拟机栈表示着java程序方法执行的内存模型,当一个方法执行的时候,则对应的jvm会创建一个栈帧存入虚拟机中,其中栈帧用来存储局部变量表,操作数栈,动态链接,方法出口信息等。当一个方法从执行到执行完毕的时候,则对应,也会有一个栈帧从入栈到出栈的过程。

其中局部变量表用来作为一组变量值的存储空间,即用来存放执行方法的方法入参和方法内定义的局部变量等信息。

操作数栈则是一个栈结构,后入先出,用来执行方法。当方法开始执行的时候,会有各种关于我们编写的程序的字节码指令往整个操作数栈中写入和提取出内容。java的执行引擎被称为“基于栈的执行引擎”,其中的栈就是指的整个操作数栈,因为我们方法内的执行最终靠的还是整个操作数栈来走的。

6.a+=1与a=a+1的区别

byte b=2;
2 b=b+1;
3 System.out.println(b);

运行结果:

错误: 不兼容的类型: 从int转换到byte可能会有损失

​ b=b+1;

​ ^

1 个错误

报错的原因是short变量在参与运算时会自动提升为int类型,b+1运算完成后变为int,int赋值给short报错。

换成+=的情况:

1 byte b=2;
2 b+=1;
3 System.out.println(b);

编译通过,输出结果3。

这是因为b+=1并不是完全等价于b=b+1,而是隐含了强制类型转换,相当于b=(short)(b+1)。

7.类和对象的区别

1,类是一个抽象的概念,它不存在于现实中的时间/空间里,类只是为所有的对象定义了抽象的属性与行为。就好像“Person(人)”这个类,它虽然可以包含很多个体,但它本身不存在于现实世界上。 2,对象是类的一个具体。它是一个实实在在存在的东西。 3,类是一个静态的概念,类本身不携带任何数据。当没有为类创建任何对象时,类本身不存在于内存空间中。 4,对象是一个动态的概念。每一个对象都存在着有别于其它对象的属于自己的独特的属性和行为。对象的属性可以随着它自己的行为而发生改变。

类是对象的抽象,对象是类的具体实例。

类是抽象的,不占用内存,而对象是具体的,占有内存空间。

例如:类就是水果,对象就是苹果。

8.重载与重写的区别

确定方法的方式不同:

重载:通过参数确定(参数个数、参数类型、参数顺序)

重写:通过对象的实际数据类型(调用)

范围不同:

重载:同一个类中

重写:多个继承链上的不同类之间

实现不同

重载:两同三不同

同类、同名方法,方法的参数不同(参数个数、参数类型,参数顺序)

重写:两同一不同,两小一大

同名同参数的方法(参数个数、类型、顺序相同),不同的类(继承链上的不同类)

返回的数据类型(引用类型)可以与超类的不同,子类重写的方法返回类型是超类的派生类,异常数减少

子类重写的方法的权限修饰符可以与超类的不同(子类重写的权限>=超类的方法权限)

扩展:

两者都有多态的作用,都可以让同名方法有不同的实现,

并且可以让Java自动的根据不同情况调用

重载:参数

重写:对象实际数据类型

实现原理:

重载:编译时实现(编译后同名不同参数的方法会变成不同名的方法) 静态多态

重写:运行时实现(运行时动态的判断对象的实际数据类型从而确定调用哪个方法) 动态多态

9.this和super的区别

1.this()代表本类的其他构造方法

只能在本类的构造方法中使用(相当于一个构造方法调用另一个构造方法)

必须在第一句,且只能调用一次

2.this代表当前类的当前对象

在本类中代表当前对象,可在本类的方法、构造方法中使用(类方法不可使用)

3.super()代表本类的父类的构造方法

只能在本类的构造方法中使用(相当于子类构造方法调用父类的构造方法)

必须在第一句,且只能调用一次

无论有无显式调用父类的构造方法在创建子类对象时系统都会自动调用父类的构造方法

如果没有显式调用父类的构造方法在子类的构造方法中会自动调用父类的无参构造方法,若父类无无参构造方法时子类必须显式调用父类的有参构造方法

4.super在本类中代表当前类的父类对象

可以在本类的实例方法、构造方法中调用

10.类变量与实例变量的区别

实例变量

实例变量声明在一个类中,但在方法、构造方法和语句块之外;

当一个对象被实例化之后,每个实例变量的值就跟着确定;

实例变量在对象创建的时候创建,在对象被销毁的时候销毁;

实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;

实例变量可以声明在使用前或者使用后;

访问修饰符可以修饰实例变量;

实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;

实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定;实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:ObejectReference.VariableName。

类变量(静态变量)

类变量也称为静态变量,在类中以static关键字声明,但必须在方法构造方法和语句块之外。

无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。

类变量除了被声明为常量外很少使用。常量是指声明为public/private,final和static类型的变量。常量初始化后不可改变。

类变量储存在静态存储区。经常被声明为常量,很少单独使用static声明变量。

静态变量在程序开始时创建,在程序结束时销毁。

与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为public类型。

默认值和实例变量相似。数值型变量默认值是0,布尔型默认值是false,引用类型默认值是null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。

类变量可以通过:ClassName.VariableName的方式访问。

类变量被声明为public static final类型时,类变量名称一般建议使用大写字母。如果静态变量不是public和final类型,其命名方式与实例变量以及局部变量的命名方式一致。

11.接口与抽象类的区别

1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。

(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。

(3)接口强调特定功能的实现,而抽象类强调所属关系。

(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。

扩展:

相同点:

(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。

抽象类:在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。抽象类的特点:

a、抽象类不能被实例化只能被继承;

b、包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;

c、抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;

d、一个子类继承一个抽象类,则子类必须实现父类抽象方法,否则子类也必须定义为抽象类;

e、抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。

***接口*:**Java中接口使用interface关键字修饰,特点为:

a、接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);

b、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;

c、一个类可以实现多个接口;

d、JDK1.8中对接口增加了新的特性:(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。

接口只能是功能的定义,而抽象类既可以为功能的定义也可以为功能的实现。

12.权限修饰符的区别

Java 中一共有四种访问权限控制,其权限控制的大小情况是这样的:public > protected > default(包访问权限) > private ,具体的权限控制看下面表格,列所指定的类是否有权限允许访问行的权限控制下的内容:

访问权限本类本包的类子类非子类的外包类
public
protected
default
private

1、public: 所修饰的类、变量、方法,在内外包均具有访问权限; 2、protected: 这种权限是为继承而设计的,protected所修饰的成员,对所有子类是可访问的,但只对同包的类是可访问的,对外包的非子类是不可以访问; 3、包访问权限(default): 只对同包的类具有访问的权限,外包的所有类都不能访问; 4、private: 私有的权限,只对本类的方法可以使用;

扩展:

外部类的访问控制只能是这两种:publicdefault

类里面的成员(全局)的访问控制可以是四种,也就是可以使用所有的访问控制权限

普通方法是可以使用四种访问权限的,但抽象方法是有一个限制:不能用private 来修饰,也即抽象方法不能是私有的,否则,子类就无法继承实现抽象方法。

接口由于其的特殊性,所有成员的访问权限都规定得死死的,下面是接口成员的访问权限:

变量: public static final

抽象方法: public abstract

静态方法: public static,JDK1.8后才支持

内部类、内部接口 : public static

构造器的访问权限可以是以上四种权限中的任意一种:

1、采用 private:一般是不允许直接构造这个类的对象,再结合工厂方法(static方法),实现单例模式。注意:所有子类都不能继承它。 2、采用包访问控制:比较少用,这个类的对象只能在本包中使用,但是如果这个类有static 成员,那么这个类还是可以在外包使用;(也许可以用于该类的外包单例模式)。 注意:外包的类不能继承这个类; 3、采用 protected :就是为了能让所有子类继承这个类,但是外包的非子类不能访问这个类; 4、采用 public :对于内外包的所有类都是可访问的;

一旦父类构造器不能被访问,那么子类的构造器调用失败,意味子类继承父类失败!

13.String、StringBuffer、StringBuilder的区别

区别主要是在运行速度和线程安全这两方面。

  1. 运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String

String最慢的原因:

String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。

JVM对于String的操作是下面描述的:

首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

  1. 线程安全

在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的

如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

3. 总结一下   String:适用于少量的字符串操作的情况

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

14.==与equals()方法的区别

1.equals()方法用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断;

2."==" 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。

总结:

== 的作用:   基本类型:比较值是否相等   引用类型:比较内存地址值是否相等

equals 的作用:   引用类型:默认情况下,比较内存地址值是否相等。可以按照需求逻辑,重写对象的equals方法。

15.面向对象的三大特征

1、封装(Encapsulation)

封装是指属性私有化

根据需要提供setter和getter方法来访问属性 隐藏具体属性和实现细节,仅对外开放接口 控制程序中属性的访问级别

目的:增强数据安全性,不能让其他用户随意访问和修改数据,简化编程,使用者不必在意具体实现细节,而只是通过外部接口即可访问类的成员 2、继承(Extend)

继承是指将多个相同的属性和方法提取出来,新建一个父类

java中一个类只能继承一个父类,且只能继承访问权限非private属性和方法 子类可以重写父类中的方法,命名与父类中同名的属性

目的:代码复用 3、多态

多态(Polymiorph)可以分为两种:设计时多态、运行时多态

设计(编译)时多态:即重载(Overload),是指java允许方法名相同而参数不同(返回值可以相同也可以不同),同一个类中允许存在一个或多个以上的同名函数,只要参数类型或参数个数不同即可

运行时多态:即重写(Override)必须是在继承体系中,子类重写父类方法,JVM运行时根据调用该方法的类型决定调用那个方法

目的:增加代码的灵活度

总结:

1、java中应尽量减少继承关系,以降低耦合度

2、使用多态时,父类在调用方法时,优先调用子类的方法,如果子类没有重写父类的方法,则再调用父类的方法

3、java访问控制符作用范围表:

权限修饰符本类本包子类外部包
public
protectedX
defaultXX
privateXXX

✓:可以访问 X:不可访问

扩展:

五大原则

单一职责原则:一个类,最好只做一件事,只有一个引起它变化。每个类应该实现单一的职责,如果不是,那就把类拆分

开放封闭原则:对扩展开放,对修改封闭

里氏替换原则:子类必须能够替换其基类,里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现

依赖倒置原则:依赖于抽象接口,不要依赖于具体实现,简单来说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户于与实现模块间的耦合

接口隔离原则:使用多个小的专门接口,而不是使用一个大的接口

16.异常与错误的区别

错误(Error)一般来说,最常见的错误有程序进入死循环、内存泄漏等。在这种情况下,程序运行时本身无法解决,只能通过其他程序干预,Error类对象由java虚拟机生成并抛弃(通常Java程序不对这类异常进行处理)。

异常(Exception)是程序执行时遇到的非正常情况或者意外行为。如:代码或者调用的代码(如公共库)中有错误,操作系统资源不可用,公共语言运行库遇到意外情况(如无法验证代码)等。常见的有数组下标越界、算法溢出(超出数值表达范围)、除数为零、无效参数、内存溢出等。这种情况,程序运行时本身可以解决,由异常代码调整程序运行方向,可使程序继续运行,直至正常结束。Java 提供了两类主要的异常 :runtime exception 和 checked exception 。运行时异常代表运行时由Java虚拟机生成的异常,原因是编译错误。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过 NullPointerException 异常。非运行时异常有 IO 异常,以及 SQL 异常。

Exception:

1.可以是可被控制(checked) 或不可控制的(unchecked)

2.表示一个由程序员导致的错误

3.应该在应用程序级被处理

Error:

1.总是不可控制的(unchecked)

2.经常用来用于表示系统错误或低层资源的错误

3.如何可能的话,应该在系统级被捕捉

17.受控异常与运行时异常的区别

受控异常就是checked Exception ,这些异常在你写代码时候必须用try{}catch语句抓住,或者throw抛出,不然代码编译时候就通不过。比如IOException ,SqlException,FileNotFoundExcption等等。

运行时异常是写代码的时候不需要catch,或者throw就可以通过编译的异常,一般由于程序员的错误引起的,比如NullPointException异常,数组越界异常,这些都是没法在 try catch中恢复的,异常需要程序员细心检查出错误。 而error是继承throwable接口,但和异常是不同的概念,error基本上就是jvm运行时内存耗尽,系统崩溃等等重大的错误,级别高于Exception,而且没法恢复