华为大佬万字长文总结,梳理的Java入门所有基础知识点,快收藏

今日分享开始啦,请大家多多指教~

1.1 Java 语言概述。

Java 是一种高级编程语言,而且是面向对象的编程语言。Java 语言是美国 Sun 公司(Stanford University Network),在 1995 年推出的高级的编程语言。Java 语言共同创始人之一:詹姆斯·高斯林 (James Gosling),被称为 Java之父。Java 语言的版本:1.0-1.4,5.0…8.0…13.0,本文笔记用的是 jdk 版本为 8.0。

1.2 Java 语言能做什么

Java 语言主要应用在互联网程序的开发领域,网上购物商城、物流、金融、各行各业的门户网站。

1.3 Java 语言的跨平台实现原理

(1) JVM: Java 虚拟机,是专门用来运行 Java 程序的。

(2) 平台:指的就是操作系统,比如 Windows、linux、MacOS 等。

(3) 跨平台: 我们编写的一个 Java 程序,可以做多个操作系统上运行一次编译,到处运行。

(4) 问题思考,如下:

  • Java 程序是跨平台的? ⇒ 正确的 ⇒ 一次编译到处运行。
  • JVM 是跨平台的? ⇒ 错误的 ==> JVM 是实现 Java 程序跨平台的基石。针对不同的操作系统提供不同的 JVM。而程序在 JVM 中运行。
  • Java 程序的跨平台是依靠 JVM 的不跨平台实现的。正确的

1.4 JDK、JRE、JVM 的组成和作用

  • JVM: Java 虚拟机,是专门用来运行 Java 程序的,但是不能单独安装。
  • JRE: Java 运行环境,包含 JVM(Java 虚拟机,是专门用来运行 Java 程序的)和核心类库。
  • JDK: Java 开发工具包,包含 JRE 和开发工具。
  • 三者关系: JDK > JRE > JVM

1.1 Java 语言开发环境搭建

JDK 安装,注意事项:

  • 注意操作系统是 Windows、linux、MacOS
  • 注意操作系统的位数是 32 位还是 64 位
  • 安装 java 相关软件的时候: 安装路径中不允许出现中文和空格(任何开发软件都最好不要安装在中文路径下)

1.2常用 DOS 命令的使用

如何进入DOS 命令操作窗口?

1.开始/命令提示符

2.开始/搜索程序和文件 输入 cmd

3.Windows键 + R –> 输入 cmd

4.窗口空白处/按住 shift 键 + 鼠标右键单击 /在此处开命令窗口

5.常用命令如下表所示:

1.3环境变量 JAVA_HOME 的配置

记事本软件的启动方式?

1.开始/程序/附件/记事本

2.C:/windows/找到notepad.exe命令,双击启动

3.如果在DOS窗口的命令中:

C:\\windows> notepad.exe回车 运行这个命令

首先在C:\\windows路径下,寻找是否存在notepad.exe,发现有,直接运行

D:\\abc> notepad.exe回车 运行这个命令

首先:在 D:\\abc 路径下,寻找是否存在 notepad.exe,发现没有

其次: 如果发现在当前路径 D:\\abc 没有要运行的 notepad.exe 命令,到系统环境变量 path 中寻找

path:… C:\\Windows;…,发现path中配置的路径 C:\\Windows 有该命令,直接运行.

如果path中配置的所有的路径中都没有要运行的命令,运行报错了.

给Java配置环境变量的意义/目的/作用?

让我们可以在任意路径下运行java开发的相关工具(javac: 编译工具,java: 运行工具)

比如jdk的安装路径:C:\\develop\\Java\\jdk1.8.0_162

配置步骤:

1.创建名称为 JAVA_HOME 的环境变量,取值是 C:\\develop\\Java\\jdk1.8.0_162

2.把步骤1中创建的名称为 JAVA_HOME 的环境变量,添加到系统环境变量 path 中

找到系统环境变量path, 在前面添加: %JAVA_HOME%\\bin;…

3.如果在DOS窗口的命令中:

C:\\develop\\Java\\jdk1.8.0_162\\bin> javac.exe回车 运行这个命令

首先在C:\\develop\\Java\\jdk1.8.0_162\\bin路径下,寻找是否存在javac.exe,发现有,

直接运行

D:\\abc> javac.exe回车 运行这个命令 首先:在D:\\abc路径下,寻找是否存在javac.exe,发现没有

其次: 如果发现在当前路径 D:\\abc 没有要运行的 javac.exe 命令,到系统环境变量path中寻找

path:… %JAVA_HOME%\\bin;…, 发现 path 中配置的名称为 JAVA_HOME 的环境变量,对应的路径

C:\\develop\\Java\\jdk1.8.0_162\\bin 中有要运行的 javac.exe 命令,直接运行,

如果 path 中配置的所有路径中,都没有要运行的 javac.exe 命令,运行报错了

寻找名称为JAVA_HOME的环境变量,找到后,使用其配置的具体路径进行替换:

path:… C:\\develop\\Java\\jdk1.8.0_162\\bin;…,

替换后的路径: C:\\develop\\Java\\jdk1.8.0_162\\bin 中有 javac 命令,就可以直接运行

2.1 程序开发的步骤

1. 源程序:

  • 程序员写的程序;
  • 程序员在自己可以看得懂得程序;
  • 程序:字母、数字、其他符号;

源程序是程序员编写的,程序员自己可以看得懂得程序,本质就是一个文本文件,但是扩展名不是 .txt,而是 .java。

2. 生产JVM可以执行的字节码(.class)文件

  • JVM:叫做 Java 虚拟机,是专门用来运行 Java 程序的。但是 JVM 是一个二货,只能识别 0 和 1,而存储 0 和 1 的文件叫做 字节码文件(.class文件)
  • 如何把源文件(程序)翻译成JVM能够执行的字节码文件(程序)呢? 使用 javac 命令(编译命令), 使用格式:javac 文件名.java 例如:编译HelloWorld.java 源文件: javac HelloWorld.java,生成一个字节码文件:HelloWorld.class

3. 把字节码文件交给JVM执行

不管是源文件(程序)还是字节码文件(程序)都存储在硬盘中? 不会自动执行,如何把字节码文件交给 JVM 执行呢? 使用 java 命令(运行命令)。 使用格式: java 文件名 例子:java HelloWorld。

2.2 HelloWorld 案例的编写编译运行

1. 编写源文件。 创建一个名称为 HelloWorld.txt 的文本文件,把扩展名修改为 .java,打开 HelloWorld.java 源文件,输入以下内容,并保存(ctrl+s)。

2. 编译: javac命令。 根据 .java源文件 生产对应的 .class文件(字节码文件)。 使用 javac 命令的格式:javac 文件名.java。javac HelloWorld.java。注意:

  • 保证当前路径下 javac命令 可以使用。
  • 保证当前路径下有要进行编译的 源(.java)文件。
  • 使用编译javac命令时,文件名后面必须写 扩展名.java。

3.运行: java命令。 把 字节码(.class)文件 交给 jvm 执行。使用 java 命令的格式:java 文件名,java HelloWorld,同样要注意:

  • 保证当前路径下 java 命令 可以使用。
  • 保证当前路径下有要进行运行的 字节码(.class)文件。
  • 使用运行 java 命令 时,文件名后面不能写 扩展名.class。

2.3 初学者编写 HelloWorld 常见问题

  • 非法字符问题。Java 中的符号都是英文格式的。
  • 大小写问题。Java 语言对 大小写敏感(区分大小写)。
  • 在系统中显示文件的扩展名,避免出现 HelloWorld.java.txt 文件。
  • 编译命令后的 java文件名需要带文件后缀 .java。
  • 运行命令后的 class文件名(类名) 不带文件 后缀.class。
  • 不要把 main 写成 mian。

2.4 Notepad++ 软件的安装和配置

3.1 注释

概念: 在代码中添加注释可提高代码的可读性。注释中包含了程序的信息,可以帮助程序员更好地阅读和理解程序。在 Java 源程序文件 的任意位置都可以添加注释,且 Java 编译器不编译代码中的注释,也就是说代码中的注释对程序不产生任何影响。所以开发者不仅可以在注释中编写代码的说明文字、设计者的个人信息,还可以使用注释来屏蔽某些不希望执行的代码。

分类: Java 提供了 3 种代码注释,分别为单行注释、多行注释和文档注释。

1、单行注释:// 为单行注释标记,从符号 // 开始直到换行为止的所有内容均作为注释而被编译器忽略。语法格式如下:

// 注释内容

int age ; // 声明int型变量,用于保存年龄信息

2、多行注释: /* */ 为多行注释标记,符号 /* 与 */ 之间的所有内容均为注释内容。注释中的内容可以换行。语法格式如下:

3、文档注释: /**…*/ 为文档注释标记。符号 /** 与 */ 之间的内容均为文档注释内容。当文档注释出现在声明(如类的声明、类的成员变量声明、类的成员方法声明等)之前时,会被 Javadoc 文档工具 读取作为 Javadoc 文档内容。文档注释的格式与多行注释的格式相同。对于初学者而言,文档注释并不是很重要,了解即可。示例如下:

说明:一定要养成良好的编码习惯。软件编码规范中提到 可读性第一,效率第二,所以程序员必须要在程序中添加适量的注释来提高程序的可读性和可维护性。建议程序中的注释总量要占程序代码总量的 20%~50%。

3.2 关键字

引入:邮箱: @前面是用户名,@后面是使用的是哪家的邮箱。

换而言之,关键字是 Java 中已经被赋予特定意义的一些单词,不可以把这些字作为标识符来使用。关键字中的所有字母都是小写的,或者在高级编辑器中彩色显示。

Java中的关键字如下表所示:

3.3 标识符

标识符可以简单地理解为一个名字,用来标识类名、变量名、方法名、数组名等的有效字符序列。Java 规定标识符由 任意顺序的字母、下划线(_)、美元符号($)和数字组成,并且第一个字符不能是数字。标识符不能是 Java 中的保留关键字。 示例:

常见错误:用中文命名标识符是非常不好的编码习惯。当编译环境的字符编码集发生改变后,代码中所有的中文标识符全部会显示成乱码,程序将无法维护。因为 Java 是一种可以跨平台的开发语言,所以发生中文标识符显示成乱码这种情况的概率非常大。编写 Java 代码有一套公认的命名规范:

  • 类名:通常使用名词,第一个单词字母必须大写,后续单词首字母大写。(大驼峰式)
  • 方法名:通常使用动词,第一个单词首字母小写,后续单词首字母大写。(小驼峰式)
  • 变量:第一个单词首字母小写,后续单词首字母大写。(小驼峰式)
  • 常量:所有字母均大写。
  • 单词的拼接:通常使用 userLastName 方式拼接单词,而不是 user_last_name。

4.1 常量的概念和分类

引入:

数学中的常数,对应到 java 中叫常量,数学中的常数有分类,java 中的常量也有分类,而且比数学中的分类更加丰富。

1、概念: 在程序的执行过程中,其值不可以发生改变的量。

2、分类:

  • 整数常量:1314、520
  • 小数常量:13.14、5.20
  • 字符常量:java 中规定字符常量必须使用单引号 \’\’ 引起来,而且单引号 \’\’ 中只能写一个字符(不能不写,也不能写2个以上) 举例:
  • 布尔常量:只有两个值 true 和 false。true:表示肯定的,对的,是的,正确的,成立的。false:表示否定的,错的,不是的,却无的,不成立的。
  • 字符串常量:java 中规定字符串常量必须使用双引号 \”\” 引起来,而且双引号 \”\” 中可以写多个字符(0个、1个、2个…), 举例:
  • 空常量:null

4.2 打印不同类型的常量

同 C语言 一样,Java 程序想要在控制台输出文字,需要调用一个已有的方法,方法如下:

System.out.print(\”Hello!\”); // 此方法输出\”Hello\”后不会自动换行,光标停留同一行的末尾

但与 C语言 不同的是,Java 又提供了一个输出文字后自动换行的方法,这个方法在原有的 print 后面加上了 ln 后缀,方法如下:

System.out.println(\”Hello!\”); // 此方法输出\”Hello\”后会自动换行,光标停留下一行的开头

打印不同类型的常量,示例如下:

4.3 变量和数据类型【重要】

引入:

1、变量概念: 在程序的执行过程中,其值可以在一定范围内发生改变的量。可以把变量理解成为一个 容器,例如一个空烧杯,给变量赋值就相当于给烧杯倒水。如下图所示的那样,变量可以不断更换值,就像烧杯可以反复使用一样:

2、分类:

3、变量定义格式图解分析:

变量的理解:

  • 变量的本质就是内存中的一块空间,空间的大小由数据类型决定。
  • 要想找到变量对应的内存空间的数据,需要给变量对应的内存空间起个名字,叫做变量名称。对于变量的命名并不是任意的。

应遵循以下几条规则:

a.变量名必须是一个有效的标识符。

b.变量名不可以使用 Java 中的关键字。

c.在同一个大括号范围内,变量名不能重复。

d.应选择有意义的单词作为变量名。

说明:在 Java 中允许使用汉字或其他语言文字作为变量名,如 **int 年龄 = 21;** 在程序运行时不会出现错误,但建议尽量不要使用

这些语言文字作为变量名。

  • 变量对应的内存空间中必须有数据才能使用,这种向变量内存空间中,存储数据的过程叫做初始化或者赋值。

图解:

为什么要声明变量呢?简单地说,就是要告诉编译器这个变量属于哪一种数据类型,这样编译器才知道需要分配多少空间给它,以及它可以存放什么样的数据。

4、定义8种变量1:

5、定义8种变量2:

变量的注意事项:定义的变量,不赋值不能使用。定义 long 类型的变量时,需要在整数的后面加 L(大小写均可,建议大写)。同理,定义 float 类型的变量时,需要在小数的后面加 F(大小写均可,建议大写)。

4.4 数据类型转换

类型转换是将一个值从一种数据类型更改为另一种数据类型的过程。例如,可以将 String 类型数据 457 转换为一个数值型,而且可以将任意类型的数据转换为 String 类型。数据类型转换有两种方式,即 隐式转换与显式转换。 如果从低精度数据类型向高精度数据类型转换,则永远不会溢出,并且总是成功的;

而把高精度数据类型向低精度数据类型转换则必然会有信息丢失,甚至有可能失败。这种转换规则就像下图所示的两个场景,高精度相当于一个大水杯,低精度相当于一个小水杯,大水杯可以轻松装下小水杯中所有的水,但小水杯无法装下大水杯中所有的水,装不下的部分必然会溢出。

从低级类型向高级类型的转换,系统将自动执行,程序员无须进行任何操作。这种类型的转换称为 隐式转换,也可以称为自动转换。 下列基本数据类型会涉及数据转换(不包括逻辑类型),这些类型按精度从 “低”到“高” 排列的顺序为 byte < short < int < long < float < double,可对照下图,其中 char 类型比较特殊,它可以与部分 int 型数字兼容,且不会发生精度变化。

隐式转换具体分析如下:

Java 程序中要求参与的计算的数据,必须要保证数据类型的一致性,如果数据类型不一致将发生类型的转换。

int + int

int + long ==> long + long (把int转换成long: 从小到大,自动类型转换,不需要代码的干预)

int + long ==> int + int (把long转成int: 从大到小,强制类型转换,必须手动代码完成)

总结:

1.隐式转换(自动类型转换)概念:

取值范围小的数据或者变量可以直接赋值给取值范围大的变量(小萝卜可以直接放入大坑中)

2.特点:

(1)自动类型转换是自动完成的,不需要代码的干预

(2)byte/short/char类型数据,只要参加运算会自动转换为int类型

(3)byte、short、char–>int–>long–>float–>double

举例:有一个byte类型(1个字节)的数字5: 00000101

byte类型自动类型转换成short类型(2个字节):在左侧补充1个字节的0,因为左侧补充的都是0,对原有数据是没有影响的,仍然是5,

00000000 00000101

byte类型自动类型转换成int类型(4个字节):

在左侧补充3个字节的0,因为左侧补充的都是0,对原有数据是没有影响的,仍然是5

00000000 00000000 00000000 00000101

byte类型自动类型转换成long类型(8个字节):

在左侧补充7个字节的0,因为左侧补充的都是0,对原有数据是没有影响的,仍然是5

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101

总结:根据需求,在数据前面补充若干字节的0,因为补充的都是0,对原有数据大小是没有影响的(打肿脸充胖子)

示例代码,如下:

显式转换具体分析如下:

当把高精度的变量的值赋给低精度的变量时,必须使用显式类型转换(又称强制类型转换),

当执行显式类型转换时可能会导致精度缺失。语法如下:

(类型名) 要转换的值

取值范围大的数据或者变量不能直接赋值给取值范围小的变量(大萝卜不能直接放入小坑中),解决方案:

(1) 把坑变大

(2) 把萝卜变小(强制类型转换)

2.格式:

转后类型 变量名称 = (转后类型) 转前数据或者变量;

long类型(8个字节)的数字5:

long num = 5L;

long类型强制类型转换成int类型(4个字节):

int a = (int)num;//把num中的数据强制类型转换成int类型,并把结果赋值给int变量a

举例: 有一个long类型(8个字节)的数字5:

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101

long类型强制类型转换成int类型(4个字节):

砍掉左侧的四个字节的内容,因为砍掉的都是数字0,所以对最终的结果数据没有影响仍然是5

00000000 00000000 00000000 00000101

long类型强制类型转换成short类型(2个字节):

砍掉左侧的六个字节的内容,因为砍掉的都是数字0,所以对最终的结果数据没有影响仍然是5

00000000 00000101

long类型强制类型转换成byte类型(1个字节):

砍掉左侧的七个字节的内容,因为砍掉的都是数字0,所以对最终的结果数据没有影响仍然是5

00000101

总结: 根据需求,砍掉数据左侧的若干字节的数据,只要砍掉的都是0,对原数据没有影响

但是只要砍掉的数据中包含1,就会对原数据产生影响(可能会损失精度)

示例代码2,如下:

图解(其它案例):

4.5 ASCII 码表

计算机是一个二货,只能存储 0和1,所以存储到计算机中的所有内容都会转换成 0和1 进行存储。所以我们在计算机中存储的字符也不例外,也需要把字符转换成 0和1 进行存储,问题: 如何把字符转换成 0和1 呢? 通过 ASCII 编码表: 存储字符和数字对应关系的一张表格。 存储字符时:需要查找 ASCII 码表,找到字符对应的数字,将数字转换为二进制数存放到计算机中。

ASCII 码表如下图所示:

int 类型和 char 类型的运算原理,如下:

计算机中的存储单位(2的10次方就是1024)

务必记住:1个字节是8位

运算符:对常量或者变量进行操作的符号。表达式:用运算符把常量或者变量连接起来符合 java 语法的式子就可以称为表达式。

6.1 算术运算符

Java 中的算术运算符主要有 +(加号)、-(减号)、*(乘号)、/(除号)、%(求余),它们都是二元运算符。Java 中算术运算符的功能及使用方式如下表所示:

其中,“+”和“-” 运算符还可以作为数据的正负符号,如 +5、-7。说明:“+”运算符也有拼接字符串的功能。下面分别演示算术运算符的用法。

【示例1】算术运算符加减乘除。示例代码如下:

6.2 赋值运算符

赋值运算符用符号 “=” 表示,它是一个二元运算符(对两个操作数作处理),其功能是将右方操作数所含的值赋给左方的操作数。例如:

int a = 100; // 该表达式是将100赋值给变量a

/*左方的操作数必须是一个量,而右边的操作数则可以是变量(如a、number)、

常量(如123、\”book\”)、有效的表达式(如45*12)。*/

和其他主流编程语言一样,Java 中也有复合赋值运算符。所谓的复合赋值运算符,就是将赋值运算符与其他运算符合并成一个运算符来使用,从而同时实现两种运算符的效果。Java 中的复合运算符如下表所示:

以“+=”为例,虽然“a += 1”与“a = a + 1”二者最后的计算结果是相同的,但是在不同场景下,两种运算符都有各自的优势和劣势。

【示例2】赋值运算符的使用场景。示例代码如下:

6.3 自增自减运算符

自增、自减运算符是单目运算符,可以放在变量之前,也可以放在变量之后。自增、自减运算符的作用是使变量的值加1或减1。

例如下图所示:

6.4 关系运算符

关系运算符属于二元运算符,用来判断一个操作数与另外一个操作数之间的关系。不管关系表达式多么复杂或者多么简单,返回值一定是布尔类型的结果,要么是 true,要么是 false,如下表所示:

【示例3】关系运算符。示例代码如下:

6.5 逻辑运算符

假定某面包店,在每周二的下午7点至8点和每周六的下午5点至6点,对生日蛋糕商品进行折扣让利活动,那么想参加折扣活动的顾客,就要在时间上满足这样的条件,(周二并且7:00 PM~8:00 PM)或者(周六并且5:00 PM~6:00 PM),这里就用到了逻辑关系。逻辑运算符是对 true(真) 和 false(假) 这两种逻辑值进行运算,运算后的结果仍是一个逻辑值。

用来连接多个条件(布尔表达式的: 结果为true/false的式子),最终的结果也必须是一个布尔类型的数据,要么是true,要么是false

不管逻辑运算符连接的式子有多么简单或者多么复杂,最终结果要么是true,要么是false

分类:

(1) & (shift+7): 逻辑与,表示并且的意思,多个条件同时成立的意思,就是只有多个条件都是true,最终的结果才是true

特点:【有false,则false】: 只要有一个条件不成立(false),结果就是false

(2) |(shift+\\): 逻辑或,表示或者的意思,多个条件,只要有一个成立,最终的结果就是true

特点:【有true,则true】:只要有一个条件是true,结果就是true

(2) !(shift+1): 逻辑取反,!true 就是false,!false 就是true

逻辑运算符的短路效果,如下表所示:

逻辑运算符的运算结果如下表所示:

说明如下:

6.6 位运算符

位运算的操作数类型是整型,可以是有符号的,也可以是无符号的。位运算符可以分为位逻辑运算符和位移运算符两大类。

位逻辑运算符包括 &、|、^和 ~,前三个是双目运算符,第四个是单目运算符。这四个运算符的运算结果如下表所示:

参照上表来看一下这四个运算符的实际运算过程:

1)位逻辑与实际上是将操作数转换成二进制表示方式,然后将两个二进制操作数对象从低位(最右边)到高位对齐,每位求与,

若两个操作数对象同一位都为1,则结果对应位为1,否则结果中对应位为0。例如,12和8经过位逻辑与运算后得到的结果是8。

0000 0000 0000 1100 (十进制12原码表示)

& 0000 0000 0000 1000 (十进制8原码表示)

0000 0000 0000 1000 (十进制8原码表示)

2)位逻辑或实际上是将操作数转换成二进制表示方式,然后将两个二进制操作数对象从低位(最右边)到高位对齐,每位求或,

若两个操作数对象同一位都为0,则结果对应位为0,否则结果中对应位为1。例如,4和8经过位逻辑或运算后的结果是12。

0000 0000 0000 0100 (十进制4原码表示)

| 0000 0000 0000 1000 (十进制8原码表示)

0000 0000 0000 1100 (十进制12原码表示)

3)位逻辑异或实际上是将操作数转换成二进制表示方式,然后将两个二进制操作数对象从低位(最右边)到高位对齐,每位求异或,

若两个操作数对象同一位不同时,则结果对应位为1,否则结果中对应位为0。例如,31和22经过位逻辑异或运算后得到的结果是9。

0000 0000 0001 1111 (十进制31原码表示)

^ 0000 0000 0001 0110 (十进制22原码表示)

0000 0000 0000 1001 (十进制9原码表示)

4)取反运算符,实际上是将操作数转换成二进制表示方式,然后将各位二进制位由1变为0,由0变为1。

例如,123取反运算后得到的结果是-124。

~ 0000 0000 0111 1011 (十进制123原码表示)

1111 1111 1000 0100 (十进制-124原码表示)

&、| 和 ^ 也可以用于逻辑运算,运算结果如下表所示:

【示例10】位逻辑运算符的使用场景。示例代码如下:

移位运算有三个,分别是左移 <<、右移>> 和无符号右移 >>>,这三个运算符都属于双目运算符。左移是将一个二进制操作数对象按指定的移动位数向左移,左边(高位端)溢出的位被丢弃,右边(低位端)的空位用0补充。左移相当于乘以2的幂,如下图所示:

例如,short 型整数 9115 的二进制是 0010 0011 1001 1011,左移一位变成 18230,左移两位变成 -29076。

右移是将一个二进制的数按指定的位数向右移动,右边(低位端)溢出的位被丢弃,左边(高位端)用符号位补充,正数的符号位为0,负数的符号为1。右移位运算相当于除以2的幂,如下图所示:

例如 short 型整数 9115 的二进制是 0010 0011 1001 1011,右移一位变成 4557,右移两位变成 2278,运行过程如下图所示:

short 型整数 -32766 的二进制是 0010 0011 1001 1011,右移一位变成 -16383,右移两位变成 -8192,运行过程如下图所示:

无符号右移是将一个二进制的数按指定的位数向右移动,右边(低位端)溢出的位被丢弃,左边(高位端)一律用0填充,运算结果相当于除以2的幂。例如 int 型整数 -32766 的二进制是 1111 1111 1111 1111 1000 0000 0000 0010,右移一位变成 2147467265,右移两位变成 1073733632,运行过程如下图所示:

【示例11】位移运算符的使用场景。示例代码如下:

常见错误:byte、short 类型做 >>> 操作时,可能会发生数据溢出,结果仍为负数。从二进制的实现机制来说,byte 类型和 short 类型不适用于 >>> 操作。

6.7 三元运算符

1.格式:

数据类型 变量名称 = 布尔表达式1 ? 表达式2 : 表达式3;

2.执行流程:

(1)计算布尔表达式1的结果,看是true还是false

(2)如果布尔表达式1的结果为true,就把表达式2的结果赋值给左侧的变量

(3)如果布尔表达式1的结果为false,就把表达式3的结果赋值给左侧的变量

执行流程图解:

【示例13】用三元运算符完成判断一个数字的奇偶性。示例代码如下:

【示例14】使用条件表达式判断一个数是否是3和5的公倍数。示例代码如下:

图解分析:

6.8 圆括号

圆括号可以提升公式中计算过程的优先级,在编程中非常常用。如下图所示,使用圆括号更改运算的优先级,可以得到不同的结果。

圆括号还有调整代码格式,增强阅读性的功能。比如下面的这个公式:

6.9 运算符优先级

Java 中的表达式就是使用运算符连接起来并且符合 Java 规则的式子。运算符的优先级决定了表达式中运算的先后顺序。通常优先级由高到低的顺序依次是:自增和自减运算、算术运算、位运算、逻辑运算、赋值运算。如果两个运算有相同的优先级,会以从左到右的方式进行运算。下表显示 Java 中的运算符的优先级。

今日份分享已结束,请大家多多包涵和指点!

Java 基础常见知识点&面试题总结,2022 最新版

原文地址:https://mp.weixin.qq.com/s/PQA_sB5J2nK05ilKUDz0mQ侵权联系删除

两者的主要区别在于解决问题的方式不同:

  • 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
  • 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。

另外,面向对象开发的程序一般更易维护、易复用、易扩展。

相关 issue : 面向过程 :面向过程性能比面向对象高??

  • 语法形式 :从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
  • 存储方式 :从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
  • 生存时间 :从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
  • 默认值 :从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。

new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。

一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。

  • 对象的相等一般比较的是内存中存放的内容是否相等。
  • 引用相等一般比较的是他们指向的内存地址是否相等。

构造方法是一种特殊的方法,主要作用是完成对象的初始化工作。

如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。

构造方法特点如下:

  • 名字与类名相同。
  • 没有返回值,但不能用 void 声明构造函数。
  • 生成类的对象时自动执行,无需调用。

构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。

封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法 ,这里只是为了举例子)。

不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。

关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。(以后介绍)。

多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。

多态的特点:

  • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
  • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
  • 多态不能调用“只在子类存在但在父类不存在”的方法;
  • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

共同点

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。

区别

  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系(比如说我们抽象了一个发送短信的抽象类,)。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。

关于深拷贝和浅拷贝区别,我这里先给结论:

  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
  • 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

上面的结论没有完全理解的话也没关系,我们来看一个具体的案例!

浅拷贝

浅拷贝的示例代码如下,我们这里实现了 Cloneable 接口,并重写了 clone() 方法。

clone() 方法的实现很简单,直接调用的是父类 Object 的 clone() 方法。

测试 :

从输出结构就可以看出, person1 的克隆对象和 person1 使用的仍然是同一个 Address 对象。

深拷贝

这里我们简单对 Person 类的 clone() 方法进行修改,连带着要把 Person 对象内部的 Address 对象一起复制。

测试 :

从输出结构就可以看出,虽然 person1 的克隆对象和 person1 包含的 Address 对象已经是不同的了。

那什么是引用拷贝呢? 简单来说,引用拷贝就是两个不同的引用指向同一个对象。

我专门画了一张图来描述浅拷贝、深拷贝、引用拷贝:

Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

Object 类 equals() 方法:

equals() 方法存在两种使用情况:

  • 类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
  • 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

举个例子(这里只是为了举例。实际上,你按照下面这种写法的话,像 IDEA 这种比较智能的 IDE 都会提示你将 == 换成 equals() ):

String 中的 equals 方法是被重写过的,因为 Object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。

当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。

String类equals()方法:

hashCode() 的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。

hashCode()定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是:Object 的 hashCode() 方法是本地方法,也就是用 C 语言或 C++ 实现的,该方法通常用来将对象的内存地址转换为整数之后返回。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode?

下面这段内容摘自我的 Java 启蒙书《Head First Java》:

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

其实, hashCode() 和 equals()都是用于比较两个对象是否相等。

那为什么 JDK 还要同时提供这两个方法呢?

这是因为在一些容器(比如 HashMap、HashSet)中,有了 hashCode() 之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HashSet的过程)!

我们在前面也提到了添加元素进HashSet的过程,如果 HashSet 在对比的时候,同样的 hashCode 有多个对象,它会继续使用 equals() 来判断是否真的相同。也就是说hashCode 帮助我们大大缩小了查找成本。

那为什么不只提供 hashCode() 方法呢?

这是因为两个对象的hashCode 值相等并不代表两个对象就相等。

那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?

因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode )。

总结下来就是 :

  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
  • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
  • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。

相信大家看了我前面对 hashCode() 和 equals() 的介绍之后,下面这个问题已经难不倒你们了。

因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。

思考 :重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题。

总结

  • equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
  • 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)。

更多关于 hashCode() 和 equals() 的内容可以查看:Java hashCode() 和 equals()的若干问题解答

可变性

String 是不可变的(后面会详细分析原因)。

StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。

线程安全性

String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结:

  1. 操作少量的数据: 适用 String
  2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

String 类中使用 final 关键字修饰字符数组来保存字符串,所以String 对象是不可变的。

修正 :我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。

String 真正不可变有下面几点原因:

保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。

String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。

相关阅读:如何理解 String 类型值的不可变?- 知乎提问

补充(来自issue 675):在 Java 9 之后,String 、StringBuilder 与 StringBuffer 的实现改用 byte 数组存储字符串。

public final class String implements java.io.Serializable,Comparable<String>, CharSequence {// @Stable 注解表示变量最多被修改一次,称为“稳定的”。@Stableprivate final byte[] value;}abstract class AbstractStringBuilder implements Appendable, CharSequence {byte[] value;}

Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[] ?

新版的 String 其实支持两个编码方案:Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),byte 相较 char 节省一半的内存空间。

JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。

如果字符串中包含的汉字超过 Latin-1 可表示范围内的字符,byte 和 char 所占用的空间是一样的。

这是官方的介绍:https://openjdk.java.net/jeps/254 。

Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的元素符。

上面的代码对应的字节码如下:

可以看出,字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象

StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对象。

如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。

如果你使用 IDEA 的话,IDEA 自带的代码检查机制也会提示你修改代码。

String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。Object 的 equals 方法是比较的对象的内存地址。

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

更多关于字符串常量池的介绍可以看一下 Java 内存区域详解 这篇文章。

会创建 1 或 2 个字符串对象。

1、如果字符串常量池中不存在字符串对象“abc”的引用,那么会在堆中创建 2 个字符串对象“abc”。

示例代码(JDK 1.8):

对应的字节码:

ldc 命令用于判断字符串常量池中是否保存了对应的字符串对象的引用,如果保存了的话直接返回,如果没有保存的话,会在堆中创建对应的字符串对象并将该字符串对象的引用保存到字符串常量池中。

2、如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。

示例代码(JDK 1.8):

对应的字节码:

这里就不对上面的字节码进行详细注释了,7 这个位置的 ldc 命令不会在堆中创建新的字符串对象“abc”,这是因为 0 这个位置已经执行了一次 ldc 命令,已经在堆中创建过一次字符串对象“abc”了。7 这个位置执行 ldc 命令会直接返回字符串常量池中字符串对象“abc”对应的引用。

String.intern() 是一个 native(本地)方法,其作用是将指定的字符串对象的引用保存在字符串常量池中,可以简单分为两种情况:

  • 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
  • 如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。

示例代码(JDK 1.8) :

先来看字符串不加 final 关键字拼接的情况(JDK1.8):

注意 :比较 String 字符串的值是否相等,可以使用 equals() 方法。String 中的 equals 方法是被重写过的。Object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是字符串的值是否相等。如果你使用 == 比较两个字符串是否相等的话,IDEA 还是提示你使用 equals() 方法替换。

对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。

在编译过程中,Javac 编译器(下文中统称为编译器)会进行一个叫做 常量折叠(Constant Folding) 的代码优化。《深入理解 Java 虚拟机》中是也有介绍到:

常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中,这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)。

对于 String str3 = \”str\” + \”ing\”; 编译器会给你优化成 String str3 = \”string\”;。

并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:

  • 基本数据类型( byte、boolean、short、char、int、float、long、double)以及字符串常量。
  • final 修饰的基本数据类型和字符串变量
  • 字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )

引用的值在程序编译期是无法确定的,编译器无法对其进行优化。

对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

我们在平时写代码的时候,尽量避免多个字符串对象拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。

不过,字符串使用 final 关键字声明之后,可以让编译器当做常量来处理。

示例代码:

被 final 关键字修改之后的 String 会被编译器当做常量来处理,编译器在程序编译期就可以确定它的值,其效果就相当于访问常量。

如果 ,编译器在运行时才能知道其确切值的话,就无法对其优化。

示例代码(str2 在运行时才能确定其值):

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。