java基础总结(基础概念、基础语法、变量)

这里基本上都是我自己根据javaguide总结的,会有拓展,其他部分我都用自己的理解概述了,有任何错误希望大家指出

一、基础概念和常识

1、java语言的特点:

简单易学

面向对象(封装、继承、多态)

跨平台

多线程

可靠性(具有异常处理机制和内存关机机制)

安全性(不能直接操作操作系统的内存资源)

高效性(jit,运行效率还行)

支持网络编程并且很便捷

解释和编译并存

2、JVM、JDK、JRE

JDK 包含 JRE

JDK能够创建和编译java程序,是提供给java开发者使用的,其中包括了JRE和其他一些工具,例如javac编译工具,java运行工具,JDB条hi是工具等。JDK基本上是必须要安装的,因为在编译和编写ava程序的时候需要JDK,在其他情况可能也需要,例如反射等,也需要JDK,因此无论是在开发,编写,调试程序是需要JDK,在编译和运行等也需要JDK。

JRE是运行已编译的java程序所需要的内容的集合,是java程序运行的环境。主要包含了java虚拟机和java所需要的类库。

jvm是运行字节码的虚拟机,可以在不同系统上,根据相同的字节码得出相同的结果,因此就造就了一次编译,随处运行"。

3、什么是字节码?好处是什么?

首先字节码是java虚拟机能够读懂的代码(.class),只面向虚拟机,他在一定程度上解释性语言效率低下的问题,同时又保留了可移植性。字节码可以在不同的操作系统上的电脑无需重新编译即可运行。

Java程序转变为机器代码的过程

而在。class到机器码的过程中,解释器需要一行一行解释再执行,效率会低下,此时引进了jit(运行时编译器)编译器,他会对热点代码(代码和方法进行次数较多的代码)进行一次编译后,会保留机器码,下次会直接运行,这样效率就会变高,这样也就造成了java语言编译和解释并存

Java程序转变为机器代码的过程

4、为什么说java”java语言是编译和解释并存''

在我个人总结看来,第一点是,他会将java程序编译成.class字节码文件,这是编译的过程,其次是在.class到机器码的过程中又解释器解释的过程也有jit编译的过程,因此是编译和解释并存的。

编译型语言:源代码经过编译器一次性编译成可执行的机器码

解释型语言:源代码通过解释器一行一行翻译成机器码再执行

编译型语言和解释型语言

5、AOT有什么优点?为什么不全部使用AOT

首先我们需要知道AOT是什么东西,AOT是一种新的编译模式,他是在程序运行之前就将字节码编译成了机器码,而jit是运行时编译器(也就是即时编译器,是属于动态编译,一种狭义的动态编译)也就是在运行时进行编译,而AOT属于静态编译,即在程序运行前进行编译,翻译成机器码,这样他相对于jit启动速度更开,减少了预热的时间,减少编译中占用的内存,并且增强了安全性。

AOT的优势在于启动的速度更快,占有内存更少,打包的体积更少。而JIT的极限处理能力更强,速度更快,可以降低请求的延时。

那为什么不全部使用AOT呢?

这是因为一些java的高级特性,例如反射、动态代理、动态加载等这些需要动态的生成和加载字节码文件,而AOT提前编译好了,就无法使用相关的类库和框架。

6、Oracle JDK和OPENjdk

Oraclejdk是部分开源的,而openjdk是完全开源的

oraclejdk是部分免费的有些需要付费,而openjdk是完全免费的

前者有一些额外的功能,但是现在基本上差不多

前者提供LTS服务后者不提供,但是很多公司基于OPENjdk也会提供所以差不多

前者使用BCL/OTN协议后者使用GPL V2许可协议

BCL是你可以商用但不能修改,OTN你个人使用可以但是商用要是收费。

7、java和c++的区别

1、java不是用指针

2、java类只能单继承而不能多继承,接口可以多继承

3、java有自动内存垃圾管理回收机制,不用手动释放垃圾

4、java只支持方法重载而不支持操作符重载

二、基本语法

1、移位运算符

<<左移,<<1表示乘2,有符号左移,低位补零即可 >>有符号右移相当于除2,正数高位补零,负数高位补一,>>>无符号右移,正数高位补一,double和float不能进行移位运算(在二进制中表现特殊,符号位指数尾数)。

如果移动的位数超过了二进制本身 则取模本书最大的二进制位数,比如int32位,如果左移42位,相当于42%32=10左移十位。

位运算只支持int,long,所以,byte,char,short都是传换为int进行运算的。

2、基本数据类型

image-20231120105438613

这里需要注意,有符号数的表示范围为:假如位数为n,则-2^n-1~2^n-1-1(这是因为第一位为符号位,剩下的n-1位才为数值位,因为权值是从0开始的,位数位n-1的位置的权值位n-2,所以后面全为1,如果再加上1要进位,变成2^n-1,所以最终结果位2^n-1,至于为什么负数时-2^n-1,这里不用减一时因为,正数有两种方式表示0、1,而负数只有一种)。

还需要注意java中所以的基本类型都是不变的,不随着机器硬件架构的改变而改变

这八种基本类型都有对应的包装类分别为:ByteShortIntegerLongFloatDoubleCharacterBoolean

3、基本类型和包装类型的区别(用途,存储方式,占用空间,默认值,比较)

基本类型 vs 包装类型

区别:

1、用途:基本类型除了一些常量和局部变量之外,很少在方法的参数,对象属性中使用基本类型来定义变量,而且包装类型可以用于泛型,而基本类型不可以

2、存储方式,基本类型的局部变量都存在栈中的局部变量表中,基本数据类型的成员变量(未被static修饰的,此时属于实例)存放在java虚拟机(对象)的堆中(而被static修饰的成员变量,是静态变量,此时是属于类的,而此时他属于类,当一个类加载到 JVM 中时,它的静态成员变量会被分配在类的数据区域,这个区域通常被称为"方法区"或"静态区",而不是存储在每个实例的堆内存中),而包装类型属于对象类型,因此几乎所有的对象实例都是存放在栈中的(jit优化中会进行逃逸分析,某个对象未逃逸到外部,则进行标量替换实现栈上分配,避免栈上分配内存)。

注:这里注意逃逸分析,可以分为不逃逸,参数逃逸,全局逃逸,原理是:分析对象的动态作用域,当一个对象在方法里面被定义后,他可能会被外部方法调用,例如作为参数传递到其他方法中,这称为方法逃逸,甚至可能被外部线程访问到,例如赋值给可以在其他线程中访问的对象。全局逃逸(线程逃逸):对象逃逸出当前的方法和线程,例如:存储在静态字段的对象,存储在转移对象的字段中或者作为当前方法的结果返回的对象。参数逃逸(方法逃逸):对象作为参数传递或由参数引用,但不会发生全局逃逸,这个状态是通过分析被调用方法的字节码。没有逃逸:对象只在方法内部使用,此时可以进行标量替换(直接使用其中的标量来代替对象,而不去直接创建对象,这样可以大大减少堆内存,为栈上分配提供基础。实现栈上分配。

但是这个方法由缺陷,那就是逃逸分析本身需要一定的性能消耗,虽然可以实现锁消除,标量替换,栈上分配,但实际不一定比他小,所以不一定能提高性能。

3、占用的空间:包装类型属于对象类型,因此基本类型的空间更小。

4、默认值:包装类型默认是null,基本类型有默认值且不是null。

5、比较:包装类型中==比较的是在内存中地址,值的比较使用equals(),而基本类型中==就是值的比较

4、包装类型的缓存机制

Byte,Integer,Character,Short都是有缓存机制的,而Double,Float是没有缓存机制的,

Byte、Integer,Short的缓存的范围是【-128,127】,Character的缓存范围是【0,127】,超过了这个范围都要重新创建一个对象。

分析问题:

Integer i1 = 33; Integer i2 = 33; System.out.println(i1 == i2);// 输出 true(这是因为33属于缓存范围,因此i1和i2指向同一个对象,内存地址相同,因此相等。

Float i11 = 333f; Float i22 = 333f; System.out.println(i11 == i22);// 输出 false(这是因为,Float没有实现缓存机制,因此i11在内存中创建了一个对象,i22也在内存中创建了一个对象,两个对象在不同的内存地址上,两者的内存地址不相等,因此不同。)

Double i3 = 1.2; Double i4 = 1.2; System.out.println(i3 == i4);// 输出 false(同上)

Integer i1 = 40; Integer i2 = new Integer(40); System.out.println(i1==i2);

同上,这里i1这里发生了自动装箱,而valueof其中调用了IntegerCache.cache(),会直接复用对象,而i2重新创建了一个对象,两者的内存地址不同。

注意:包装类型值的判定一定要用equals()

当数值处于缓存的范围内是,此时会通过相应的xxxCache.cache(),对象都是在其中创建的(已经创建好了的),此时会直接复用对象,因此在缓存范围内的他们的数值相等,就会复用同一个对象,因此内存地址相等,看似可以通过==进行值的比较,实际上还是内存地址的比较,只是复用了对象,而一旦超过了缓存的范围,此时就会重新创建对象,如果再用==此时用地址判断就不对了,因此值的判断必须要用equals().

5、自动装箱和拆箱和原理

装箱:将数据类型使用他的引用类型包装起来

拆箱:将包装类型转变为基本类型。

原理:装箱实际上是调用了包装类的valueof(),而拆箱则是xxxvalue()方法。

频繁拆装箱会严重影响性能。

6、浮点数会出现精度的丢失

这是因为浮点数在二进制中表示的特殊性,在二进制中表示小数时,宽度是有限的,对于无限的会进行长度截断,因此就造成了精度的丢失。

7、如何解决精度就是的问题呢?

使用BigDemal()方法进行浮点数的运算。

对于超过Long类型的数据类型最终会有BigInteger来运算,之中会使用Int【】数组来存取任意大小的数据类型。

三、变量

1、成员变量和局部变量的区别(语法形式,存储方式,生存时间,默认值)

1)语法形式:局部变量定义在方法体或者代码块中或者方法的参数中,而成员变量则是定义在类中,并且成员变量能被private,public等访问控制符号修饰,成员变量能被final和static修饰,而局部变量不能被static和访问控制符修饰,但是可以被final修饰

补:final可以修饰变量,方法和类,首先说变量,final修饰局部变量和成员变量都需要程序员显示的初始化,当final修饰成员变量时,又分为两种情况(分为类成员变量和对象成员变量,区别在与是否又static修饰),一种是修饰类变量时,初始化必须在静态代码块或者声明中显示赋值(因为类变量在初始化的时候就已经初始化了,不能再非静态代码块中指定初始值),对于对象成员变量,则必须在非静态代码块,声明和构造器中指定初始值(静态代码块无法访问非静态成员变量,因此时非静态代码块赋值)。在修饰局部变量时,也是需要程序员显示初始化。final修饰基本类型变量时候,在获得初始值之后不能再对其赋值,如果时修饰引用类型,例如数组之类的,此时里面的值可以改变,但是引用的地址不能发生改变。修饰方法的时候,会将方法进行锁定,子类无法对父类的方法进行重写。如果是修饰类的化,则这个类无法再被继承,是比较完美的类了。

2、存储方式:对于成员变量,如果是没有被static修饰的话,是属于实例的(因此是存储在堆中的)。如果被static修饰,则此时是属于类的,在类加载的时候就被初始化了,因此是存放在类中的数据区(方法区),而不是传统的实例对象堆中。对于局部变量,则是存放在栈中。

3、生存周期,未被static修饰的成员变量是随着实例对象创建而创建,随着实例对象的销毁而销毁。被static修饰的成员变量,是随着类的创建而创建,随着类的销毁而销毁,只分配一次内存。而局部变量随着方法的调用而自动生成,随着方法调用结束而自动结束。

4、默认值,成员变量如果没有被赋值,则会以自动以类型的默认值赋值(被final修饰的成员变量必须显示的赋值,否则就默认为0,null等,这样就没有意义了),局部变量不会被自动赋值。

2、为什么成员变量会有默认值

1、如果没有默认值的化,则成员变量存放的随机的内存地址,这样访问起来会非常危险。

2、可以手动赋值也可以自动赋值,如果没有手动赋值,则一定会默认赋值。可以在运行中使用反射等方法手动赋值。

3、对于编译器而言,局部变量有没有赋初值很好判断,但是成员变量可以通过反射等方式在运行时赋值,此时很难进行判断,误报没有默认值又会影响用户体验,因此采用自动赋默认值。

3、静态变量有什么用

静态变量被static修饰,静态变量可以被类的所有实例共享,也就是说无论有多少个类的实例,此时都共享一个静态变量,静态变量被创建的时候,只被赋一次值,因为它是属于类的,此时它随着类的加载而被创建,即使创建多个对象,静态只会分配一次内存(类加载的时候),因此可以节省内存。

一般直接通过类名来访问。一般情况静态变量会被final修饰为常量。

4、字符型常量和字符串常量的区别?

表现形式:字符型常量是用单引号(里面是单个字符),字符串常量是用的双引号(里面是0个或者多个字符组成)

占用内存:字符型常量为char,占用两个字节内存,而字符串常量则是根据字符串的长度分配内存。

含义:字符型常量代表着ASCLL值,可以进行运算,而字符串常量则是代表着内存地址(字符串存储在内存中的地址)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值