1.整形数据
byte、short、int、long,分别是1248个字节的存储量,取值范围也是依次增大的,其中int是正负21亿多;
long a = 1111222233334444L;记住后面要加上L,否则因为1111222233334444为int型,且超出int范围;
在这里插入一个面试题:
short c = 1;c = c +1;对错与否?
因为定义c为short型,c+1计算结果是int型(short型的c与int型的1相加,结果为int型),所以要强转
c=(short)c+1;
然而,对于c+=1;这个算法和(c=c+1)效果一毛一样,但是,却存在一个隐式的类型转换;
c+=1实际上是c=(short)c+1;
所以对于上面提到的:c=c+1错;c+=1对。
那么问题来了,在上面自增运算中我们可以看到,int型数据赋值给short型变量时,要进行类型强转,
按照long a = 1111222233334444L定义是看到的那样,不加L,数值默认是int型,那么short c = 1,这里
的1也应当是int型,将int型的1赋值给short型变量却不用强转,
思索再三,只能给出这样一个解释:在变量初始化赋值的时候,只要赋的值符合变量类型要求(取值范围)
就不用进行类型强转,在进行变量操作之后再进行赋值时要检查等号两边数据类型。
2.浮点型数据
float、double;分别48字节存储量,带小数点float7位小数,double15位,精度更高;
又是面试题:
float a =3.4;double b =2.4;
其实前面的float型变量的赋值时错误的,因为上面的3.4和2.4没有后缀默认是double型的,所以对于
float a = 3.4,这里初始化时将double型数据赋值给float型变量,虽然3.4满足float型数据范围要求;
但是还是要强转类型 float a = (float) 3.4;
这是浮点型数据初始化时与整形数据初始化时的区别。
3.两数运算返回值类型
只要有一个是double类型,最终返回double类型;否则,
只要有一个是float类型,最终返回float类型;否则,
只要有一个是long类型,最终返回long类型;
否则,最终返回int型。
4.Integer包装类
这里讲一个自动装箱/拆箱的问题:
自动装箱:
Integer a = 100;Integer b = 100;a==b?
这里的自动装箱,Integer a=Integer.valueOf(100);
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
我们可以看到,自动装箱实际上是调用了Integer类的valueOf方法,当传进来的值在一个区间[-128,127]时,
直接返回一个数值;所以上述a和b都是指向同一个数值IntegerCache.cache[100-IntegerCache.low];
a==b返回true。
但是当Integer c = 150;Integer d = 150;情况就不一样了;
Integer c = Integer.valueOf(150)=new Integer(150);同理d也是等于new Integer(150);
在这里补充一下,就算数值相同,使用new和运算符算出来的结果,都会占据内存中的一块新地址,所以,这里
c、d虽然数值上相同,但是它们各自指向内存中截然不同的地址,
所以c==d返回false。
自动拆箱:
Integer a = 12;------------自动装箱
int b = a;--------------------自动拆箱
我们来看看Integer类的源码:
public final class Integer extends Number implements Comparable<Integer> { private final int value; public Integer(int value) { this.value = value; } public int intValue() { return value; } }
不难发现,自动装箱的时候实现了Integer a = new Integer(12);而自动拆箱的时候实际上是执行了intValue()方法;
int b = a.intVaule();
5.值/引用传递以及堆栈的概念
java开辟了两类存储区域---栈、堆:
栈:基本类型的变量(8大基本数据类型,int b 这里的b)以及对象的引用变量(String a 这里的a);
堆:new等指令创建的对象和数组(new A()---一个A类的实例化对象);
在性能上,栈的存取速度要比堆快,仅次于寄存器,并且由于栈数据的可共享性,我们看到的几乎都是对栈数据
进行操作,很少见到人对new出来的对象进行直接操作。
A a = new A();
这句代码应作如下解析:
1.调用A类的无参构造器来实例化一个A的对象new A();
2.在堆内存中开辟一块空间存放上面实例化的A对象new A();
3.在栈内存中开辟一块空间给A类的引用变量a,此时a=null;
4.最后将对象new A()在堆内存中的地址传递给引用变量a。
这就是A类的引用(变量)指向A类的(实例化)对象。
引用传递:传递的变量指向内存中的地址,所以对该变量操作的结果会直接影响内存中的数据;
值传递:传递变量的是值的拷贝,所以操作的对象也只是源值的副本,所以源值是不会发生改变的。
下面来通过一个小程序来检测一下值/引用传递:
package com.eco.factory; public class Test { /** * 检测不同类型参数的传值方式--基本类型变量和引用变量 */ // 引用变量作为参数,对数据修改 public void change(A a) { a.age = 20; } // 基本类型变量作为参数,对数据修改 public void change(int b) { b = 20; } public static void main(String[] args) { Test test = new Test(); int a = 5; int b = a; System.out.println("int型数据处理之前:" + a); test.change(a); System.out.println("int型数据处理之后:" + a); if (b == a) { System.out.println("数据处理前后没发生变化,所以基本类型参数是值传递"); } else System.out.println("数据处理前后发生变化,所以基本类型参数是引用传递"); A c = new A(); c.age = 5; System.out.println("A型数据处理之前:" + c.age); int d = c.age; test.change(c); System.out.println("A型数据处理之后:" + c.age); if (d == c.age) { System.out.println("数据处理前后没发生变化,所以引用变量型参数是值传递"); } else System.out.println("数据处理前后发生变化,所以引用变量型参数是引用传递"); } } class A { public int age; }
控制台打印输出:
int型数据处理之前:5 int型数据处理之后:5 数据处理前后没发生变化,所以基本类型参数是值传递 A型数据处理之前:5 A型数据处理之后:20 数据处理前后发生变化,所以引用变量型参数是引用传递
注:
在《Java核心技术》这本书中第四章第5节提到,java只有值传递,并且用了一个swap来验证这一论
点,在我看来是有待商榷的,我们先来看看这个swap方法:
package com.eco.factory; public class Test { public void swap(A x, A y) { A temp = x; x = y; y = temp; } public static void main(String[] args) { Test test = new Test(); A a = new A("Jack"); A b = new A("Tom"); test.swap(a, b); System.out.println("***********************************"); System.out.println("a的姓名:" + a.name); System.out.println("b的姓名:" + b.name); } } class A { public int age; public String name; public A(String name) { this.name = name; } }
该书提出,如果java对对象采用的是引用调用(传递),那么这个swap方法就能实现数据交换的结果,
结果就该显示a的姓名是Tom,b的姓名是Jack,实际结果a 姓名依然是Jack,b依然是Tom,所以作者
得出结论:java对对象采用的不是引用传递。
那么现在,我们来对这个程序稍作修改:
package com.eco.factory; public class Test { public void swap(A x, A y) { A temp = x; x = y; y = temp; System.out.println("a的姓名:" + x.name); System.out.println("b的姓名:" + y.name); } public static void main(String[] args) { Test test = new Test(); A a = new A("Jack"); A b = new A("Tom"); test.swap(a, b); System.out.println("***********************************"); System.out.println("a的姓名:" + a.name); System.out.println("b的姓名:" + b.name); } } class A { public int age; public String name; public A(String name) { // super(); this.name = name; } }
控制台打印:
a的姓名:Tom b的姓名:Jack *********************************** a的姓名:Jack b的姓名:Tom
我们可以看到在swap方法内部实际上是真的实现了作者所要求的数据交换的,这一点我认为可以反驳
作者的数据没有进行交换的论点,但是到了主方法上,a的姓名又回来了,我认为这是因为在swap方法
内部定义的x,y由于是局部变量,在方法结束之后这两个变量是会被销毁的。而主方法所访问的是a、b
这两个引用变量,而非x、y这两个引用变量(因为在全局作用域也没用定义过,所以实际上也是访问不
了的)
所以我认为,java对对象采用的是引用传递,对象并不能够反映一个数据的本质,而swap方法实现了对
a对象的复制---x,方法体内部也可以看到自始至终是对这个克隆体x的各种操作,丝毫没有涉及到a对象,
这就和基本数据类型的值传递一样,自始至终都是对克隆体的操作。
值传递:以源数据的复制品为操作对象,不会改变原数据;
引用传递:以引用变量的复制品为操作对象,不会改变原引用变量,但会同步改变两个引用变量共同指向
的数据。
6.getter方法的返回值类型问题
我们通常在写一个类的getter、setter方法时都会直接用IDE工具提供的一键生成getter、setter方法,但是
当返回值类型不是基本数据类型的话,会产生一些问题:
package com.eco.factory; import java.util.Date; public class Test2 { public static void main(String[] args) { Date date = new Date(); Father f = new Father("桔子桑", date); System.out.println(f.getHirday()); f.getHirday().setHours(16); System.out.println(f.getHirday()); } } class Father { private String name; private Date hirday; public Father(String name, Date hirday) { this.name = name; this.hirday = hirday; } public String getName() { return name; } public Date getHirday() { return hirday; } }
控制台打印输出:
Mon Dec 25 00:24:33 CST 2017 Mon Dec 25 16:24:33 CST 2017
我们可以看到,就算Father类的成员变量hirday是private声明,还是可以不通过setter方法来改变hirday的
值,在Java核心技术这本书中提供了一个方法:
package com.eco.factory; import java.util.Date; public class Test2 { public static void main(String[] args) { Date date = new Date(); Father f = new Father("桔子桑", date); System.out.println(f.getHirday()); Date obj =(Date)f.getHirday(); obj.setHours(17); System.out.println(f.getHirday()); } } class Father { private String name; private Date hirday; public Father(String name, Date hirday) { this.name = name; this.hirday = hirday; } public String getName() { return name; } public Object getHirday() { return hirday.clone(); } }
控制台:
Mon Dec 25 00:34:16 CST 2017
Mon Dec 25 00:34:16 CST 2017
可以看到,当返回的是hirday.clone()的时候,就算对返回值如何操作,都不会对实例化对象的hirday值
作任何改变,因为红字操作的始终只是hirday的副本,对真实值丝毫没影响。
7.