在学习面向对象之前,我们需要了解面向过程的编程思维,如果你学习过C语言和Python就会很轻松!
变量就是一个可变的量,例如定义一个int类型的变量(int就是整数类型):
int a = 10;
a = 20;
a = 30;
我们能够随意更改它的值,也就是说它的值是随时可变的,我们称为变量。变量可以是类的变量,也可以是方法内部的局部变量(我们现阶段主要用局部变量,类变量在面向对象再讲解)
变量和C语言中的变量不同,Java中的变量是存放在JVM管理的内存中,C语言的变量存放在内存(某些情况下需要手动释放内存,而Java会自动帮助我们清理变量占据的内存)Java和C++很类似,但是没有指针!Java也叫C+±-
Java是强类型语言,只有明确定义了变量之后,你才能使用!一旦被指定某个数据类型,那么它将始终被认为是对应的类型(和JS不一样!)
定义一个变量的格式如下:
[类型] [标识符(名字)] = [初始值(可选)]
int a = 10;
注意:标识符不能为以下内容:
包括基本数据类型、流程控制语句等,了解就行,不用去记,后面我们会一点一点带大家认识!
常量就是无法修改值的变量,常量的值,只能定义一次:
final int a = 10;
a = 10; //报错!
常量前面必须添加final关键字(C语言里面是const,虽然Java也有,但是不能使用!)
这只是final关键字的第一个用法,后面还会有更多的用法。
养成注释的好习惯,不然以后自己都看不懂自己的代码!注释包括单行注释和多行注释:
//我是单行注释
/**
* 我是
* 多行注释
*/
//TODO 待做标记
Java中的数据类型分为基本数据类型和引用类型两大类,引用类型我们在面向对象时再提,基本数据类型是重点中的重点!首先我们需要了解有哪些类型。然后,我们需要知道的,并不是他们的精度如何,能够表示的范围有多大,而是为什么Java会给我们定义这些类型,计算机是怎么表示这些类型的,这样我们才能够更好的记忆他们的精度、表示的范围大小。所以,我们从计算机原理的角度出发,带领大家走进Java的基本数据类型。
这一部分稍微有点烧脑,但是是重中之重,如果你掌握了这些,任何相关的面试题都难不倒你!(如果你学习过计算机组成原理就很好理解了)
在计算机中,所有的内容都是二进制形式表示。十进制是以10为进位,如9+1=10;二进制则是满2进位(因为我们的计算机是电子的,电平信号只有高位和低位,你也可以暂且理解为通电和不通电,高电平代表1,低电平代表0,由于只有0和1,因此只能使用2进制表示我们的数字!)比如1+1=10=2^1+0,一个位也叫一个bit,8个bit称为1字节,16个bit称为一个字,32个bit称为一个双字,64个bit称为一个四字,我们一般采用字节来描述数据大小。
十进制的7 -> 在二进制中为 111 = 2^2 + 2^1 + 2^0
现在有4个bit位,最大能够表示多大的数字呢?
在Java中,无论是小数还是整数,他们都要带有符号(和C语言不同,C语言有无符号数)所以,首位就作为我们的符号位,还是以4个bit为例,首位现在作为符号位(1代表负数,0代表正数):
现在,我们4bit能够表示的范围变为了-7~+7,这样的表示方式称为原码。
虽然原码表示简单,但是原码在做加减法的时候,很麻烦!以4bit位为例:
1+(-1) = 0001 + 1001 = 怎么让计算机去计算?(虽然我们知道该去怎么算,但是计算机不知道!)
我们得创造一种更好的表示方式!于是我们引入了反码:
经过上面的定义,我们再来进行加减法:
1+(-1) = 0001 + 1110 = 1111 => -0 (直接相加,这样就简单多了!)
思考:1111代表-0,0000代表+0,在我们实数的范围内,0有正负之分吗?
根据上面的问题,我们引入了最终的解决方案,那就是补码,定义如下:
其实现在就已经能够想通了,-0其实已经被消除了!我们再来看上面的运算:
1+(-1) = 0001 + 1111 = (1)0000 => +0 (现在无论你怎么算,也不会有-0了!)
所以现在,4bit位能够表示的范围是:-8~+7(Java使用的就是补码!)
以上内容是重点, 是一定要掌握的知识,这些知识是你在面试中的最终防线!有了这些理论基础,无论面试题如何变换,都能够通过理论知识来破解
整数类型是最容易理解的类型!既然我们知道了计算机中的二进制数字是如何表示的,那么我们就可以很轻松的以二进制的形式来表达我们十进制的内容了。
在Java中,整数类型包括以下几个:
long都装不下怎么办?BigInteger!
数字已经达到byte的最大值了,还能加吗?为了便于理解,以4bit为例:
0111 + 0001 = 1000 => -8(你没看错,就是这样!)
整数还能使用8进制、16进制表示:
在Java中,存在字符类型,它能够代表一个字符:
字符要用单引号扩起来!比如 char c = ‘淦’;
字符其实本质也是数字,但是这些数字通过编码表进行映射,代表了不同的字符,比如字符'A'
的ASCII码就是数字65
,所以,char类型其实可以转换为上面的整数类型。
Java的char采用Unicode编码表(不是ASCII编码!),Unicode编码表包含ASCII的所有内容,同时还包括了全世界的语言,ASCII只有1字节,而Unicode编码是2字节,能够代表65536种文字,足以包含全世界的文字了!(我们编译出来的字节码文件也是使用Unicode编码的,所以利用这种特性,其实Java支持中文变量名称、方法名称甚至是类名)
既然char只能代表一个字符,那怎么才能包含一句话呢?(关于数组,我们这里先不了解,数组我们放在面向对象章节讲解)
String就是Java中的字符串类型(注意,它是一个类,创建出来的字符串本质是一个对象,不是我们的基本类型)字符串就像它的名字一样,代表一串字符,也就是一句完整的话。
字符串用双引号括起来!比如:String str = “一日三餐没烦恼”;
小数类型比较难理解(比较难理解指的是原理,不是使用)首先来看看Java中的小数类型包含哪些:
思考:小数的范围该怎么定义呢?我们首先要了解的是小数在计算机里面是如何存放的:
根据国际标准 IEEE 754,任意一个二进制浮点数 V 可以表示成下面的形式:
V = (-1)^S × M × 2^E
(1)(-1)^S 表示符号位,当 S=0,V 为正数;当 S=1,V 为负数。
(2)M 表示有效数字,大于等于 1,小于 2,但整数部分的 1 不变,因此可以省略。(例如尾数为1111010,那么M实际上就是1.111010,尾数首位必须是1,1后面紧跟小数点,如果出现0001111这样的情况,去掉前面的0,移动1到首位;题外话:随着时间的发展,IEEE 754标准默认第一位为1,故为了能够存放更多数据,就舍去了第一位,比如保存1.0101 的时候, 只保存 0101,这样能够多存储一位数据)
(3)2^E 表示指数位。(用于移动小数点)
比如: 对于十进制的 5.25 对应的二进制为:101.01,相当于:1.0101*2^2。所以,S 为 0,M 为 1.0101,E 为 2。所以,对于浮点类型,最大值和最小值不仅取决于符号和尾数,还有它的阶码。我们在这里就不去计算了,想了解的可以去搜索相关资料。
思考:就算double有64bit位数,但是依然存在精度限制,如果我要进行高精度的计算,怎么办?BigDecimal!
布尔类型(boolean)只有true
和false
两种值,也就是要么为真,要么为假,布尔类型的变量通常用作流程控制判断语句。(C语言一般使用0表示false,除0以外的所有数都表示true)布尔类型占据的空间大小并未明确定义,而是根据不同的JVM会有不同的实现。
隐式类型转换支持字节数小的类型自动转换为字节数大的类型,整数类型自动转换为小数类型,转换规则如下:
问题:为什么long比float大,还能转换为float呢?小数的存储规则让float的最大值比long还大,只是可能会丢失某些位上的精度!
所以,如下的代码就能够正常运行:
byte b = 9;
short s = b;
int i = s;
long l = i;
float f = l;
double d = f;
System.out.println(d);
//输出 9.0
显示类型转换也叫做强制类型转换,也就是说,违反隐式转换的规则,牺牲精度强行进行类型转换。
int i = 128;
byte b = (byte)i;
System.out.println(b);
//输出 -128
为什么结果是-128?精度丢失了!
在参与运算时(也可以位于表达式中时,自增自减除外),所有的byte型、short型和char的值将被提升到int型:
byte b = 105;
b = b + 1; //报错!
System.out.println(b);
这个特性是由 Java虚拟机规范 定义的,也是为了提高运行的效率。其他的特性还有:
赋值运算符=
是最常用的运算符,其实就是将我们等号右边的结果,传递给等号左边的变量,例如:
int a = 10;
int b = 1 + 8;
int c = 5 * 5;
算术运算符也就是我们在小学阶段学习的+
-
*
/
%
,分别代表加减乘除还有取余,例如:
int a = 2;
int b = 3;
int c = a * b;
//结果为6
需要注意的是,+
还可以用作字符串连接符使用:
System.out.println("lbw" + "nb"); //lbwnb
当然,字符串可以直接连接其他类型,但是会全部当做字符串处理:
int a = 7, b = 15;
System.out.println("lbw" + a + b); //lbw715
算术运算符还包括++
和--
也就是自增和自减,以自增为例:
int a = 10;
a++;
System.out.println(a); //输出为11
自增自减运算符放在变量的前后的返回值是有区别的:
int a = 10;
System.out.println(a++); //10 (先返回值,再自增)
System.out.println(a); //11
int a = 10;
System.out.println(++a); //11 (先自增,再返回值)
System.out.println(a); //11
int a = 10;
int b = 2;
System.out.println(b+++a++); //猜猜看结果是多少
为了使得代码更简洁,你还可以使用扩展的赋值运算符,包括+=
、-=
、/=
、*=
、%=
,和自增自减类似,先执行运算,再返回结果,同时自身改变:
int a = 10;
System.out.println(a += 2); //等价于 a = a + 2
关系运算符的结果只能是布尔类型,也就是要么为真要么为假,关系运算符包括:
> < == //大于小于等于
>= <= != //大于等于,小于等于,不等于
关系运算符一般只用于基本类型的比较,运算结果只能是boolean:
int a = 10;
int b = 2;
boolean x = a > b;
System.out.println(x);
//结果为 true
逻辑运算符两边只能是boolean类型或是关系/逻辑运算表达式,返回值只能是boolean类型!逻辑运算符包括:
&& //与运算,要求两边同时为true才能返回true
|| //或运算,要求两边至少要有一个为true才能返回true
! //非运算,一般放在表达式最前面,表达式用括号扩起来,表示对表达式的结果进行反转
实际案例来看看:
int a = 10;
int b = 2;
boolean x = a > b && a < b; //怎么可能同时满足呢
System.out.println(x); //false
int a = 10;
int b = 2;
boolean x = a > b || a <= b; //一定有一个满足!
System.out.println(x); //true
int a = 10;
int b = 2;
boolean x = !(a > b); //对结果进行反转,本来应该是true
System.out.println(x); //false
& //按位与,注意,返回的是运算后的同类型值,不是boolean!
| //按位或
^ //按位异或 0 ^ 0 = 0
~ //按位非
按位运算实际上是根据值的二进制编码来计算结果,例如按位与,以4bit为例:
0101 & 0100 = 0100 (只有同时为1对应位才得1)
int a = 7, b = 15;
System.out.println(a & b); //结果为7
三目运算符其实是为了简化代码而生,可以根据条件是否满足来决定返回值,格式如下:
int a = 7, b = 15;
String str = a > b ? "行" : "不行"; // 判断条件(只能是boolean,或返回boolean的表达式) ? 满足的返回值 : 不满足的返回值
System.out.println("汉堡做的行不行?"+str); //汉堡做的行不行?不行
理解三目运算符,就很容易理解后面的if-else语句了。
我们的程序都是从上往下依次运行的,但是,仅仅是这样还不够,我们需要更加高级的控制语句来帮我进行更灵活的控制。比如,判断用户输入的数字,大于1则输出ok,小于1则输出no,这时我们就需要用到选择结构来帮助我们完成条件的判断和程序的分支走向。学习过C语言就很轻松!
选择结构包含if和switch类型,选择结构能够帮助我们根据条件判断,再执行哪一块代码。
就像上面所说,判断用户输入的数字,大于1则输出ok,小于1则输出no,要实现这种效果,我们首先可以采用if语句:
if(判断条件){
//判断成功执行的内容
}else{
//判断失败执行的内容
}
//if的内容执行完成后,后面的内容正常执行
其中,else
语句不是必须的。
现在,又来了一个新的需求,用户输入的是1打印ok,输入2,打印yes,其他打印no,那么这样就需要我们进行多种条件的判断了,当然if能进行多分支判断:
if(判断条件1){
//判断成功执行的内容
}else if(判断条件2){
//再次判断,如果判断成功执行的内容
}else{
//上面的都没成功,只能走这里
}
同样,else
语句不是必须的。
现在,又来了一个新的需求,用户输入1之后,在判断用户下一次输入的是什么,如果是1,打印yes,不是就打印no,这样就可以用嵌套if了:
if(判断条件1){
//前提是判断条件1要成功才能进来!
if(判断条件2){
//判断成功执行的内容
}else{
//判断失败执行的内容
}
}
我们不难发现,虽然else-if
能解决多分支判断的问题,但是效率实在是太低了,多分支if采用的是逐级向下判断,显然费时费力,那么有没有一直更专业的解决多分支判断问题的东西呢?
switch(判断主体){
case 值1:
//运行xxx
break; //break用于跳出switch语句,不添加会导致程序继续向下运行!
case 值2:
//运行xxx
break;
case 值3:
//运行xxx
break;
}
在上述语句中,只有判断主体等于case后面的值时,才会执行case中的语句,同时需要使用break来跳出switch语句,否则会继续向下运行!
为什么switch效率更高呢,因为switch采用二分思想进行查找(这也是为什么switch只能判断值相等的原因),能够更快地找到我们想要的结果!
小明想向小红表白,于是他在屏幕上打印了520个 “我爱你”,我们用Java该如何实现呢?
for语句是比较灵活的循环控制语句,一个for语句的定义如下:
for(初始条件;循环条件;更新){
//循环执行的内容
}
//循环结束后,继续执行
初始条件、循环条件、更新条件不是缺一不可,甚至可以都缺!
for(int i = 0;i < 520;i++){
System.out.println("我爱你");
}
for(;;){
//这里的内容将会永远地进行下去!
}
增强for循环在数组时再讲解!
while循环和for循环类似,但是它更加的简单,只需要添加维持循环的判断条件即可!
while(循环条件){
//循环执行的内容
}
和for一样,每次循环开始,当循环条件不满足时,自动退出!那么有时候我们希望先执行了我们的代码再去判断怎么办呢,我们可以使用do-while语句:
do{
//执行内容
}while(循环条件);
一定会先执行do里面的内容,再做判断!
思考:
for(;;){
}
while(true){
}
//它们的性能谁更高?
简单:将九九乘法表打印到控制台。
中等:打印1000以内所有满足水仙花的数,“水仙花数”是指一个三位数其各位数字的立方和等于该数本身,例如153是“水仙花数”,因为:153 = 1^3 + 5^3 + 3^3
困难:一共有n个台阶,一只青蛙每次只能跳一阶或是两阶,那么一共有多少种跳到顶端的方案?例如n=2,那么一共有两种方案,一次性跳两阶或是每次跳一阶。
动态规划:其实,就是利用,上次得到的结果,给下一次作参考,下一次就能利用上次的结果快速得到结果,依次类推
不对啊,别的教程都讲了数组、方法,怎么我们还没讲就进入面向对象了呢?