1,好的编码习惯
- 用Integer.valueOf()替代new Integer(); Double同理;
java9已经取消了new Integer(),因为效率低。 - 数字字面量里使用下划线以方便阅读。
java7新增:
double d = 432_112.555_12;
int c = 1111_2221_1235;
- 用Objects.equals()替代a.equals(b);
- 不使用魔法数。
2,基本数据类型(primitive type)
java在不同的机器上数值范围和字节长度都是固定的。
以下为8种基本类型及对应的包装类型:
分类 | 基本类型 | 默认值 | 字节大小 | 包装类型 | 默认值 |
---|---|---|---|---|---|
布尔型 | boolean | false | 1bit | Boolean | null |
字符型 | char | '\u0000' (Unicode 0000即null) | 16bit | Character | null |
整型 | byte | (byte)0 | 8bit | Byte | null |
short | 0 | 16bit | Short | null | |
int | 0 | 32bit | Integer | null | |
long | 0L | 64bit | Long | null | |
浮点型 | float | 0.0f | 32bit | Float | null |
double | 0.0d | 64bit | Double | null |
- 低级向高级是隐式类型转换,高级向低级必须强制类型转换(可能会丢失数据,除非必须且确定否则不推荐此操作)。
- 基本类型是值传递(call by value),
虽然他们的包装类型是引用传递(call by reference),但是因为它们都是immutable类型,没有提供自身修改的函数,每次操作都是新生成一个对象,传参后也无法修改自身的值。 - 为什么有了值类型还需要包装类型?
java面向对象的语言,许多时候必须使用对象才可以(如List等集合的容器要求元素是对象),通过包装类型让基本类型有了对象的性质,并扩展了基本类型的功能。 - 存储的区别
基本类型保存在堆,包装类型作为对象保存在堆,其引用保存在栈。
引用类型:
变量将引用(或“指向”)原始值。不创建任何副本。引用类型包括类、接口、委托和装箱值类型。
值类型:
只复制变量的值。也就是基本数据类型(四类八种)
四类:整型、浮点型、字符型、逻辑型。
1)char(16bit)
1>计算
字符型与整型运算:自动转换成整型。如:’a’ % 3
中,a的ASCII码为97。
2>比较
基本类型都不能equals比较,只能==
比较
3>char型变量存储一个中文汉字
能实现。因为java中以unicode编码,一个char占2个字节(16位),所以放一个中文是没问题的。
2)float
1> float f=3.4是否正确?
不正确。精度不准确,应该用强制类型转换,如下所示:
float f=(float)3.4 或f = 3.4f。
原因:
在java里面,没小数点的默认是int,有小数点的默认是 double。
编译器可以自动向上转型,如int 转成 long 系统自动转换没有问题,因为后者精度更高。double 转成 float 就不能自动做了,所以后面的加上个f。
2> 控制精度输出
System.out.println(100%3);//输出1
System.out.println(100%3.0);//输出1.0
3)int(32bit,4字节)
1> java整型的字节序是Big-Endian(大端)
a.字节序
指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
通常有Little-Endian(小端)和Big-Endian两种方式。
b.Little-Endian(小端)
指低位字节存放在内存的低地址端,高位字节存放在内存的高地址端。
如:0x12 34 56 78存储为: 0x78 | 0x56 | 0x34 | 0x12(低地址---->高地址)
c.Big-Endian(大端)
如:0x12 34 56 78存储为: 0x12 | 0x34 | 0x56 | 0x78(低地址---->高地址)
2> 计算
byte和short型在计算时会自动转换为int型计算,结果也是int 型
4)double
整数型默认为int,带小数的默认为double.
1> 计算
/**
* double型转化成int型
*/
//不进行四舍五入操作:
(int)x
//进行四舍五入操作:
Integer.parseInt(new java.text.DecimalFormat("0").format(x))
5)byte(8bit)
1> 计算
计算单位转换:
8bit(位)=1Byte(字节)
1024Byte(字节)=1KB
1024KB=1MB 1024MB=1GB 1024GB=1TB
一个中文字是占两个字节的。
byte和short型在计算时会自动转换为int型计算,结果也是int 型。
2> 实现了GBK编码字节流到UTF-8编码字节流的转换:
byte[] src,dst;
dst=new String (src,"GBK").getbytes("UTF-8");
//String (byte[] bytes, String charsetName) 通过使用指定的 charset 解码指定的 byte 数组,构造一个新的
//String.getBytes(Charset charset) 使用给定的 charset 将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
举例:
short a =128;
byte b =(byte) a; //-128
1.short类型,a的二进制是:0000 0000 1000 0000;
2.强制转换的截后8位,正数用源码表示,负数用补码表示,第一位是符号。
3.因此,a截取后8位的二进制是:1000 0000,第一位是1,表示是一个负数,二进制的值是128,所以结果是 -128。
2,引用数据类型
String(不可变性);
包装类:Byte,Short,Integer,Long,Character,BigInteger,BigDecimal。
指针大小
对于某个数据类型的指针:
指针指向数据的起始地址,指针大小就是起始地址的大小,取决于计算机的字长。对于32位机是4字节,64位机是八字节。
- java和c++对比,C++存在指针是引用传递,而java是值传递:即使是引用数据类型,传递时也是复制了对象的引用(共享对象调用)。修改方法入参引用指向的内容,会影响原数据(基本包装类型除外,涉及常量池)。
1) String
java.lang
//好的编码习惯,在做字符串操作时确定字符串非null或者非空串
//StringUtils.isEmpty() 已经废弃 代替方案:
ObjectUtils.isEmpty(Object)
//可检测对象:array Collection CharSequence
2) Interger
java.lang
1>Integer++ 相当于重新创建了新的对象并赋值引用
Integer a = 1;
Integer b = a;
b++;//相当于 b = Integer.valueOf(b.intValue() + 1); 重新创建了新对象,所以 a 与 b 指向不同的对象。
System.out.println(a);
System.out.println(b);
2>常量池(java5)
常量池缓存使用条件:
- 整型 且范围在
-128~127
之间的数字。
可以通过-XX:AutoBoxCacheMax=size修改最大值127;
java6中可以通过java.lang.Integer.IngtegerCache.high设置最大值。 - 自动装箱拆箱时使用缓存。
直接new不会使用缓存,会在heap中开辟新的内存。
第一次使用数值时需要通过一定时间就行常量池初始化。
//当我们给一个Integer赋予一个int类型的时候会调用Integer的静态方法valueOf,而valueOf()函数会对-128到127之间的数进行缓存。
Integer i01=59;
Integer i02=59;
Integer i03=Integer.valueOf(59);
Integer i04=new Integer(59);
//非new出来的Integer,如果数在-128到127之间,因为常量池的关系两者相等。
System.out.println(i01==i02); //true
System.out.println(i01 == i03);//true
//对于有new出来的Integer,在heap中开辟了一块新内存放置值为59的Integer对象在内存中。所以不相等
System.out.println(i01 == i04);//false
Integer i05=128;
Integer i06=128;
//对于不在(-128~127)这个范围的数,不能直接从cache中获取对象,则每次对象的地址是新的。
System.out.println(i05 == i06);//false
3)BigInteger(大整数类)
java.math.BigInteger
java提供的用于高精度计算的类,没有对应的基本类型。
BigInteger是不可变的,也就是说,一旦创建就不能改变其数值。同时也决定了BigInteger提供的运算方法都是非本地算法,不能直接使用运算符。
4)BigDecimal
java提供的用于高精度计算的类,没有对应的基本类型。用法和float类似,但是必须以方法来代替运算符。
对于浮点数,不管是float还是double都近似值,对于高精度的运算会产生资损问题,推荐使用BigDecimal。BigDecimal也无法解决计算机无法精确表示小数的问题,只是精度非常非常高。
使用时注意:
- 不能用equals进行比较
精度不同 1.0==1结果为false
BigDecimal构造函数:4中scale是不同的。
BigDecimal(int) scale=0
BigDecimal(double) scale=double近似值的位数
BigDecimal(long) scale=0
BigDecimal(String) scale=String的精度,如0.0001的scale=4
对于0.1来说,BigDecimal(0.1)是0.1的近似值0.1000...625;BigDecimal("0.1")的值才是真正的0.1.
2. 不能用==
进行比较 ==》
引用类型比较的是地址
3. 结果值的比较用compareTo()方法,忽略精度进行比较
A.compareTo(B) 如果相等输出是0。
4.
BigDecimal(double)
和BigDecimal(String)
区别?
double是一个近似值,即使是new BigDecimal(0.1)
,其创建出来的值也不是正好等于0.1,可能是0.1000……00055111……;但是BigDecimal("0.1")
创建的值标度是1,值也是0.1。
6)Number
java.math.BigInteger
是一个抽象类,也是一个超类(即父类)。Number 类属于 java.lang 包,所有的包装类(如 Double、Float、Byte、Short、Integer 以及 Long)都是抽象类 Number 的子类。
在不确定参数是什么类型时(Integer、Long、BigInteger),可以用Number接收参数。
3,数据比较与计算
1)“==”
1>包装类型(即对象)
“==”比较的是地址。
比较时用Objects.equals(a, b)
替代a.equals(b);
2>基本型和基本型封装型进行“==”运算符的比较
基本型封装型将会自动拆箱变为基本型后再进行比较。
如:Integer(0)和int i = 0比较,Integer(0)会自动拆箱为int类型再进行比较,结果为相等。
Integer i = 0;
System.out.printf(i == 1); ==>反编译:i.intValue() == i
3>String和char[]
String s = "he";
char c[] = {'h','e'};
System.out.println(s.equals(c));//s为字符串,c为数组对象,故结果为false
2)equals
①两个包装型进行equals()比较
首先比较类型,如果类型相同,则继续比较值,如果值也相同,返回true;
②基本型封装类型调用equals(),但是参数是基本类型
先自动装箱,基本型转换为其封装类型,再进行①的比较。
3)按位操作符
在计算机中,位运算是非常快速的,因为它们可以直接在硬件层面上执行。
java是32位机。
原码
第一位为符号位,其它为值。0为正,1为负。
反码
正数反码为其本身;
负数反码:符号位不变,其它各位取反。
补码
正数补码:本身;
负数补码:原码的符号位不变,其它各位取反,最后加1。
即:从后数第一个1之后,各位取反。
注意:计算机用补码存储。
计算举例:变量a、b是64位有符号正数,a:0X 7FFF FFFF FFFF FFFF;b:0X 8000 0000 0000 0000;则a+b = ?
解答:a+b = 0X FFFF FFFF FFFF FFFF FFFF;是-1的补码。
1>按位与(&
)
基本计算:有0为0,否则为1.
1 & 1 = 1
0 & 1 = 0
0 & 0 = 0
高效率计算:
- 奇数判断:
//常规:取模
//原理:将被除数按位分解得到的每一位与除数相比较;如果大于除数,则将除数不断左移一位(乘2),直到它大于被除数为止,然后将被除数减去这个数,再进行下一次比较。如果小于除数,则将对应的商位为0,然后考虑下一位。
n % 2 == 1;
//位运算优化:
(n & 1) == 0;
- n & (n - 1):将n的二进制表示中的最后一个1变成0,其他位保持不变;
统计1bit的个数k:对一个操作数做k次n & (n - 1)操作,就可以将二进制表示中的所有1bit都消除;
判断一个数是否是2的幂次方:如果是2的幂次方,那么只有一个1。
2>按位或(|
)
基本计算:有1为1,否则为0.
1 | 1 = 1
0 | 1 = 1
0 | 0 = 0
3>按位异或(^
)
基本计算:相同为0,不同为 1。
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
公式3.1:任何一个数异或0都不变;
公式3.2:任何一个数异或-1(1,1111111b)都等于该数取反。
异或运算在计算机中有广泛应用,例如数据加密、压缩、校验等领域。其中,数据校验往往采用奇偶校验或者循环冗余校验(CRC)算法,而这些算法都需要用到异或运算。此外,在编程语言中,异或运算也经常用于实现位操作和状态判断等功能。
- 可逆性:A xor B等于C,那么C xor B等于A,C xor A等于B。也就是说,对于任意两个值进行异或运算所得的结果,只要其中有一个值和结果以及另外一个值已知,就可以得到另外一个值。
- 结合律和交换律:异或运算可以任意交换和结合,例如(A xor B) xor C = A xor (B xor C),A xor B = B xor A。
- 自反性:一个数和自己异或的结果为0,即A xor A = 0。
- 零值:任何数和0进行异或的结果都等于这个数本身,即A xor 0 = A。
高效率计算:
- 交换两个数
//常规运算:通过临时变量
int temp = a;
a = b;
b = temp;
//常规运算:通过加法或者减法省去临时变量
a += b;
b -= a;
a -= b;
//位运算优化:利用异或的性质交换两个数
a ^= b;
b ^= a;
a ^= b;
- 输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n。
比如10 = 1010(b) 13=1101(b)
,需要改变3位。
思路:首先,求这两个数的异或。第二步,统计异或结果中1的位数。
4>按位取反(~
)
~10
表示10各位取反。
公式4.1:~n = -(n + 1)
计算举例:
int i = 5;
int j = 10;
System.out.println(i + ~j); //5+(-11)=-6
//i :101
//j : 原码为(10)b = 0000 0000 0000 0000, 0000 0000 0000 1010;
//各位取反:(~10) = 1111 1111 1111 1111,1111 1111 1111 0101;
//因为计算机以补码存储,在读取时,补码为(~10)的数原码为:1000 0000 0000 0000,0000 0000 0000 1011;即-11
5>右移(>>
)
右边移出位被丢弃,左边移出的空位补符号位。
右移n位相当于原操作上除以2n.
>>
带符号右移
>>>
无符号右移,左边空缺补充为0
公式5.1:n>>31 取得n的符号(32位机);
0为正数,-1为负数。
a = 5; // 00000000000000000000000000000101
b = 2; // 00000000000000000000000000000010
a >> b; // 00000000000000000000000000000001
高效率计算:用右移运算符代替除以2。
6>左移(<<
)
左移n位相当于原操作数*2n。
高效率计算:用左移运算符代替乘以2。
获得int型最大值(32位机): (1 << 31) - 1
7>位运算举例
- 取绝对值:
int mask = n >> 31; return (n + mask) - mask;
//解析:mask为符号位,正数为0,负数为-1;
//公式3.1 3.2:n ^ 0 = n; n^-1=~n
//负数绝对值是-n = ~n + 1 = (n^-1) + 1
//正数绝对值是n
//整理得到:(n + mask) - mask
- 获取一个数二进制的某一位
int getBit(int a, int b) { return (a >> b) & 1; }
4)逻辑运算符
a&&b
如果a假就不再计算b,存在短路计算。||
,存在短路计算;- 逻辑运算符的优先级 ()> not > and > or
5)Math类
1>取整
①static double ceil(double a);
返回>=a的min整数
②static double floor(double a);
返回<=a的max整数,类型未double
③static double rint(double a);
四舍五入,返回与a最相近的整数
④static long round(double a);
四舍五入,返回与a最相近的长整型整数。将原来的数字加上0.5后再向下取整。例:
Math.round(12.5) = 13;
Math.round(-12.5) = -12
⑤static int round(float a);
四舍五入,返回与a最相近的整型整数
4,拆箱与装箱
1)装箱
把基本数据类型赋给对应的包装类,都是通过Integer.valueOf()
来实现的。
比如:把int赋值给Integer,对他的操作就需要用它的方法了。
是值类型转换为引用类型的过程。
2)拆箱
把一个包装类赋给基本数据类型,都是通过Integer.initValue()
来实现的。
由引用类型转换为值类型的过程。
3)demo
Integer i = 1;//装箱
int j = i;//拆箱
//典型应用
List list = new ArrayList();
list.add(1);//装箱
list.add(new Integer(1));//没有引入装箱概念之前
Iterator it = list.iterator();
while (it.hasNext()) {
int x = (Integer)it.next();//拆箱
}
4)频繁的装箱和拆箱很浪费效率
装箱时,生成的是全新的引用对象,这会有时间损耗,效率降低。
在代码中应避免装箱和拆箱的操作。
可以使用泛型来减少这样的操作。
5,泛型
泛型其实就是一个语法糖,在编译的时候会进行泛型擦除,JVM中没有泛型。在javac转换为class字节码的时候,List<String>
会编程List。
1)泛型
用于解决强转的安全问题,是一个安全机制。
在编译的时候检查类型安全,确保只能把正确类型的对象放入集合中;消除强制类型转换。
2)优缺点
优点:
- 方便
提高代码复用率 - 安全
在编译期间做类型检查,增加了程序安全性。
java5泛型出来之前,Object实现的类型转换要在运行时检查,如果出现了问题会直接报classCastException,泛型的出现避免了强制转换(显式转换)。
缺点:
在性能上不如数组快。好的编码习惯就是:能用array不用list泛型。
3)使用
①泛型方法:
public <T> T getA(T a){
//此处<T>先声明泛型T,然后使用时,传进来是什么类型,返回什么类型,同样避免了强制转换
return a;
}
②泛型类
泛型类中,泛型只作用在非静态成员,静态成员需要单独声明泛型。
使用注意:
1,泛型类型必须是引用类型
2,<> 念 typeof,表示处理的类型
public class RequestDataBean<T>{
private int code;
private String message;
private T data;
}
使用的时候,RequestDataBean<ClassName>
,就可以为data使用不同的类型。
4)JVM与泛型 擦除
泛型 擦除
在编译阶段,所有泛型类的类型参数都会被Object或者它们的限定边界来替换,处理成普通类和普通方法。
所以在创建泛型对象时请指明类型,让编译器尽早的做参数检查,而不是让JVM运行时抛出类不匹配的异常。
不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。
①JVM中没有泛型,只有普通类和普通方法。
比如并不存在List<String>.class
或是List<Integer>.class
,而只有List.class。
②泛型数组初始化时不能声明泛型类型
如下代码编译时通不过:
List<String>[] list = new List<String>[];
在这里可以声明一个带有泛型参数的数组,但是不能初始化该数组,因为执行了类型擦除操作后,List[] 与 List[] 就是同一回事了,编译器拒绝如此声明。
③instanceof 不允许存在泛型参数
以下代码不能通过编译,原因一样,泛型类型被擦除了。
List<String> list = new ArrayList<String>();
System.out.println(list instanceof List<String>)
④静态变量是被泛型类的所有实例所共享的
对于声明为MyClass<T>
的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。
不管是通过new MyClass<String>
还是new MyClass<Integer>
创建的对象,都是共享一个静态变量。
⑤泛型的类型参数不能用在Java异常处理的catch语句中
因为异常处理是由JVM在运行时刻来进行的。
由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>
和MyException<Integer>
的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。
5)<? extends E>
①概念
是Upper Bound(上限) 的通配符,用来限制元素的类型的上限。表示元素类型上限为E类型。
<?>是<? extends Object>的简写②赋值
List<? extends Fruit> fruits;
fruits = new ArrayList<Fruit>();
fruits = new ArrayList<Apple>();
fruits = new ArrayList<Object>();//编译不通过(有上限限制)
③写入
不能写入,因为不知道fruits具体是什么类型,为了类型安全,编译器只能阻止添加元素了。
fruits.add(new Fruit());//编译不通过
fruits.add(new Apple());//添加子类,编译不通过
④读取
无论fruits指向什么,编译器都可以确定获取的元素是Fruit类型,所有读取集合中的元素是允许的。
Fruit fruit = fruits.get(0);//编译通过
6)<? super E>
①概念
是 Lower Bound(下限) 的通配符 ,用来限制元素的类型下限。
②赋值
List<? super Apple> apples;
apples = new ArrayList<Apple>();
apples = new ArrayList<Fruit>();
apples = new ArrayList<Object>();
apples = new ArrayList<RedApple>();//子类不可赋值,编译不通过
③写入
允许写入。
因为apples中装的元素是Apple或Apple的某个父类,我们无法确定是哪个具体类型,但是可以确定的是Apple和Apple的子类是和这个“不确定的类”兼容的,因为它肯定是这个“不确定类型”的子类,也就是说我们可以往集合中添加Apple或者Apple子类的对象,所以对于下面的添加是允许的:
apples.add(new Apple());
apples.add(new RedApple());
apples.add(new Fruit());//无法添加父类,编译不通过
④读取
编译器允许从apples中获取元素的,但是无法确定的获取的元素具体是什么类型,只能确定一定是Object类型的子类,因此我们想获得存储进去的对应类型的元素就只能进行强制类型转换了
Apple apple = (Apple)apples.get(0);//获取的元素为Object类型
7)PECS法则
①PECS法则
生产者(Producer)使用extends,消费者(Consumer)使用super。
②生产者
如果你需要一个提供E类型元素的集合,使用泛型通配符<? extends E>。它好比一个生产者,可以提供数据。
③消费者
如果你需要一个只能装入E类型元素的集合,使用泛型通配符<? super E>。它好比一个消费者,可以消费你提供的数据。
③既是生产者也是消费者
既要存储又要读取,那就别使用泛型通配符。
8)泛型和可变参数
public class HelloWorld {
public static void main(String[] args) {
// 传递可变参数,参数是泛型集合
display(10, 20, 30);
// 传递可变参数,参数是非泛型集合
display("10", 20, 30); // 没有@SafeVarargs会有编译警告
}
@SafeVarargs
public static <T> void display(T... array) {
for (T arg : array) {
System.out.println(arg.getClass().getName() + ":" + arg);
}
}
}