一、常量与变量
1.1.常量修饰符用final,字母大写,立即赋值,且不允许在修改
1.2变量可以随时修改
二、数据类型
2.1原始数据类型(8个)
2.1.1整数类型 :有符号,分正负
byte:1个字节,8bit, -2^7 ~ 2^7-1 ,-128~ 127
short:2个字节,16bit,-2^15 ~ 2^15-1,-32768~32767
int:4个字节,32bit,-2^31 ~ 2^31-1,-2147....~2147....
long:8个字节,64bit,-2^63 ~ 2^63-1,-2147....~2147....
注:
1.整数取值范围 byte->short->int->long,类型和赋值时设计类型转换
2. 对于short s1 = 1; s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。对于short s1 = 1; s1 += 1;由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
精度小于int的数值运算的时候都回被自动转换为int后进行计算
3. long a=323482942934(超过整型最大值必须加L或l);
2.1.2浮点类型
用于保存小数,分单精度float和双精度double.
1.float a=2.1231f ; double b=12.23d;当为float时,必须加f,因为默认小数值为double类型,而且d或D可省略,同样遵从类型转换规则,整型的可以赋值给浮点类型
2.float为整数+小数=4字节=32bit,double为整数+小数=8字节=64bit
float:整数位数+小数位数=8位;double位 16位长度
float的小数点后6位,double的小数点后16位。
2.1.3字符类型
1.保存字母符号、数字符号、标点符号和其他符号,单个字符占2个字节。字符-->映射成整型-->在转成二进制,才能被计算机识别,这种映射叫字符编码。java中对字符采用unicode编码,使用2个字节标识1个字符,一共可以存储65536个字符,且其前128个字符和ASCII吗字符集兼容,如 a,它的ASCII码的二进制是011000001,它的unicode是00000011000001,都是十进制的97。
转义字符 如\n,\t等
2.1.4布尔类型 boolean
三、类型转换
3.1不同类型转换:自动类型转换和强制类型转换
3.1.1自动类型转换:byte->short (char) ->int -> long ->float ->double (低到高)
a.高位转低位需要强制转换,低位转高位自动转成参与运算的最高类型
byte/char/chort 混合运算会转成int类型相加,最终还是int类型,int/long/float转成float类型或double类型,最终还是最高类型
例子:short s1 = 1; s1 = s1 + 1;中,1 是int 型 s1 short型 通过 + 运算后s1 自动转为int 型 所以错
s1 += 1 复合表达式会自动将运算后的结果进行类型转换成等式左侧类型,不会出错
3.1.2强制类型转换:从高到低类型转换,需要强制类型转换,且是随机截断,不是四舍五入
int a=(int)23.5 ,最终为23,运算时也需要进行强制转换
四、运算符与表达式
4.1算数运算符与算数表达式(二元运算符)
分为加、减、乘、除、余
注:
a. 除法运算符 若两个都是整数,不管是否整除,都为整数,且是去掉小数 print(8/3),为2,除以float或double则为 其最高类型的长度值
b.整数直接求余运算中,结果为余数print(8%3),结果为2
c.除法预算中,0做除数会报错
4.2赋值运算符与赋值表达式(二元运算符,相对于操作数而言)
+=属于复合赋值运算符,会自动强转类型,short s1=1;s1+=1;不会错
4.3自增和自减运算符(一元运算符)
++i,--i:先自增,后赋值,比如int i=0; int m=++i; 输出1
i++,i--:先赋值,后自增,比如int i=0; int m=i++; 输出0
4.4关系运算符和关系表达式
== 和 !=
4.5逻辑运算符合逻辑表达式
与(&,&&)、或(|和||)、异或(^)、非(!)
与:&&当左侧表达式为false,就不在计算后边的表达式,&,左右两侧都要运算
或:||当左侧为true,就不在计算右边的表达式,|,左右两侧都要计算
异或:当两侧为true和false时为true,相同是为false
4.6位运算符
对操作数以二进制为单位进行的运算,运算结果为整数。操作数->二进制,二进制位运算,转整数。
&:按位&与运算 101&110=100 (二元)
|:按位或运算 101|110=111 (二元)
^:按位异或运算 101^110=011(二元)
~:按位取反运算 ~101=010(一元)
<<:左移位运算 (十进制)5<<1 = 101(二进制)<<1=1010(二进制)= 5*2=10(十进制) 相当于乘以2^(n)方
>>:右移位运算(带符号) (十进制)5>>1 = 101(二进制)>>1=(二进制)= 5/2=2(十进制) 相当于除以2^n方,左边空位补0 (待符号位,负数补1,正数补0)
>>>:无符右移位运算:
4.7三元运算符
a > b ?a : b
五、经典试题
5.1 ==号和equals()
区别:==号在比较基本数据类型时比较的是值,而用==号比较两个对象时比较的是两个对象的地址值。
equals是Object的方法,没有重写时,底层调用==,但是大多数进行了重写,用于比较值
比如:String sm =new String("abc"); String sn =new String("abc"); System.out.println(sm.equals(sn)); System.out.println(sm ==sn); 返回了true,false。因为String 类重写了equals 判断两个值,而==判断的是引用。
但对于: String s1 = "abc";String s2 ="abc";System.out.println(s1.equals(s2));System.out.println(s1 == s2); 返回了true,true。因为属于常量池,地址一样。
5.2 Java数据类型的存储位置:
基本数据类型在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上;
引用数据类型在被创建时,首先要在栈上给其引用(句柄)分配一块内存,而对象的具体信息都存储在堆 内存上,然后由栈上面的引用指向堆中对象的地址
5.3 Java数据类型的比较:
基础数据类型的比较只能使用==号,比的是数据的值,而不能使用equals;引用类型两个都可以,当使用 ==比较的是内存地址,使用equals比较的是堆内存里面的内容
拆装箱:
装箱就是把byte ,int ,short, long ,double,float,boolean,char 这些Java的基本数据类型在定义数据类型时不声明为相对应的引用类型,在编译器的处理下自动转化为引用类型的动作就叫做装箱。
拆箱就是把Long,Integer,Double,Float 等将基本数据类型的首字母大写的相应的引用类型转化为基本数据类型的动作就叫拆箱。
拆装箱的性能:
尽管Java编译器能够帮组我们自动进行相应基本数据与引用类型的相互转化,但是不太建议编程中大量使 用,因为存在二次转化,考虑性能。
尽量避免基础类型与引用类型的自动拆装箱,损耗性能
避免 Integer integer = 10;起码得是Integer integer = new Integer(10);
尽量不要用Integer.valueOf(String str);而要用Integer.parseInt(String str)
Integer.valueOf()把String 型转换为Integer对象。因此在int countNum = ? 的情况下用Integer.parseInt() 很好的,直接变成int类型的值,而不用再拆箱变成基础类型了。
Integer.parseInt()把String 型转换为Int型,
就是说Integer.valueOf(S)是针对包装类来说的,而Integer.parseInt(s) 是针对变量而言
5.4 实例
5.4.1
Integer f1=new Integer(3);
Integer f2=3;
int f3=3;
System.out.println(f1 ==f2); //false 两个引用类型,==比较的内存地址,不同
System.out.println(f2 ==f3); //true 引用类型,会将引用类型拆箱成int在和c比较
System.out.println(f1 ==f3); //true 引用类型,会将引用类型拆箱成int在和c比较
5.4.2
Integer f1=127,f2=127,f3=128,f4=128;
System.out.println(f1 ==f2); //true IntegerCache 在
System.out.println(f3 ==f4); //false
IntegerCache : 简单的说, 如果整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的Integer 对象
5.4.3 &和&&的区别?
&运算符有两种用法: (1)按位与; (2)逻辑与。 &&运算符是短路与运算。逻辑与跟短
路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式
的值才是true。 &&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边
的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如
在验证用户登录时判定用户名不是 null 而且不是空字符串,应当写为: username != null
&&!username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果
不成立,根本不能进行字符串的equals 比较,否则会产生 NullPointerException 异常。注意:
逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
5.4.4String理解
java为字符串常量在字符串缓冲池中分配内存:
(1) 池中常量内存空间只读不写
(2)池中字符串常量不重复,相同的字符串为同一内存空间
这也就是有道面试题:String s = new String(“xyz”);产生几个对象?一个或两个,如果常量池中原来没有”xyz”,就是两个。如果常量池中已有“xyz”则只是new String创建一个对象;如果常量池中没有“xyz”,则要在常量池创建一个,new String创建一个,这样就是两个。
例如:
String s1="ab"; //在池中分配一块内存用于存放常量"ab"
String s2="ab"; //由于池中存在"ab",引用变量a指向存在的"ab"内存空间首地址。所以s1和s2指向同一池中"ab"的内存空间
String s3="a";//在池中新分配一块内存用于存放常量"a"
String s4="a"+"b";//在池中新分配用于存放"b"的临时内存,合并将池中的"a"和"b"为"ab",因"ab"已存在,所以不再新分配内存,将已存在的"ab"内存地址赋值给引用变量s4.
String s5=s3+"b"; //引用变量和字符常量"b"连接运行,在堆区分配一块内存存放合并后的"ab",并将其首地址赋值给引用变量d
String e=new String("ab"); //在堆区分配一块内存存放"ab",并将其首地址赋值给引用变量e.
String 类型的引用变量直接判断“==”,比较的是引用变量其在栈内存的内容,即变量指向的字符串常量实例内存的首地址。
若要判断其指向的实例内容是否相等,需要使用equals方法完成。
5.4.5 String s =new String()分析堆与栈 创建了几个对象
5.4.5.1
String str1 = “abc”;
System.out.println(str1 == “abc”);
步骤:
1) 栈中开辟一块空间存放引用str1,
2) String池中开辟一块空间,存放String常量”abc”,
3) 引用str1指向池中String常量”abc”,
4) str1所指代的地址即常量”abc”所在地址,输出为true
5.4.5.2
String str2 = new String(“abc”);
System.out.println(str2 == “abc”);
步骤:
1) 栈中开辟一块空间存放引用str2,
2) 堆中开辟一块空间存放一个新建的String对象”abc”,
3) 引用str2指向堆中的新建的String对象”abc”,
4) str2所指代的对象地址为堆中地址,而常量”abc”地址在池中,输出为false
5.4.5.3
String str3 = new String(”abc”);
System.out.println(str3 == str2);
步骤:
1) 栈中开辟一块空间存放引用str3,
2) 堆中开辟一块新空间存放另外一个(不同于str2所指)新建的String对象,
3) 引用str3指向另外新建的那个String对象
4) str3和str2指向堆中不同的String对象,地址也不相同,输出为false
5.4.5.4
String str4 = “a” + “b”;
System.out.println(str4 == “ab”);
步骤:
1) 栈中开辟一块空间存放引用str4,
2) 根据编译器合并已知量的优化功能,池中开辟一块空间,存放合并后的String常量”ab”,
3) 引用str4指向池中常量”ab”,
4) str4所指即池中常量”ab”,输出为true
5.4.5.5
final String s = “a”;
String str5 = s + “b”;
System.out.println(str5 == “ab”);
步骤:
同4
String s1 = “a”;
String s2 = “b”;
String str6 = s1 + s2; 当有引用类型+时,会new新的对象,如果都是常量"a"+"b"则为常量值
System.out.println(str6 == “ab”);
步骤:
1) 栈中开辟一块中间存放引用s1,s1指向池中String常量”a”,
2) 栈中开辟一块中间存放引用s2,s2指向池中String常量”b”,
3) 栈中开辟一块中间存放引用str5,
4) s1 + s2通过StringBuilder的最后一步toString()方法还原一个新的String对象”ab”,因此堆中开辟一块空间存放此对象,
5) 引用str6指向堆中(s1 + s2)所还原的新String对象,
6) str6指向的对象在堆中,而常量”ab”在池中,输出为false
5.4.5.6
String str7 = “abc”.substring(0, 2);
步骤:
1) 栈中开辟一块空间存放引用str7,
2) substring()方法还原一个新的String对象”ab”(不同于str6所指),堆中开辟一块空间存放此对象,
3) 引用str7指向堆中的新String对象,
String str8 = “abc”.toUpperCase();
步骤:
1) 栈中开辟一块空间存放引用str6,
2) toUpperCase()方法还原一个新的String对象”ABC”,池中并未开辟新的空间存放String常量”ABC”,
3) 引用str8指向堆中的新String对象
注意:字符串的+操作其本质是创建了 StringBuilder 对象进行 append 操
作,然后将拼接后的 StringBuilder 对象用 toString 方法处理成 String 对象,这一点可以用
javap -c StringEqualTest.class 命令获得 class 文件对应的 JVM 字节码指令就可以看出来。
String.intern()方法就是把该对象放进常量池,变成常量池对象
````
//测试String对象创建情况
String aname="a";
String bname=aname;
aname="b";
String lisi=new String("b");
System.out.println(aname);
System.out.println(bname);
System.out.println(bname==aname);
System.out.println(aname==lisi);
System.out.println(aname==lisi.intern());
````
打印结果:false、false、true
1.equals比较的值,因为String对其进行了值比较的重写
2.过程分析: