包装类
Java是面向对象编程语言,但也包含了八种基本的数据类型,这八种基本的数据类型不支持面向对象的编程机制,基本的数据类型也不具备对象的特性:没有成员变量、方法被调用。所有类型的变量都继承Object类,都可以当成Object类型,但是基本的数据类型就不可以。为了解决这个问题,Java提供了包装类的概念,成为基本数据类型的包装类。
基本的数据类型 包装类 byte Byte short Short int Integer long Long char Character float Float double Double boolean Boolean
自动拆装箱:自动装箱就是把一个基本的数据类型变量直接赋给对应的包装类变量,或者赋给Object变量;自动拆箱与之相反,允许直接把包装类对象直接赋给一个对应的基本类型变量。自动拆装箱时必须注意类型匹配。
包装类还可以实现基本类型变量和字符串之间的转换,把字符串类型的值转换为基本数据类型由两种方式:包装类提供的parseXxx(String s);利用包装类提供的Xxx(String s)构造器
String提供了多个重载的valueOf()方法,用于将基本的数据类型转换成字符串。如果希望把基本的数据类型转换成字符串,更简单的方式:与""进行连接运算,系统会自动把基本的数据类型转换成字符串。
int i = 100;String s = i + "";
包装类型的变量是引用数据类型,但包装类的实例可以与数值类型进行比较,这种比较是直接取出包装类实例所包装的数值类比较的。
Integer a = new Integet(6);System.out.println(a > 5);
两个包装类的实例进行比较的情况比较复杂,因为包装类的实例实际上是引用类型,两个包装类指向同一块内存才会返回true。
(new Integer(100)) == (new Integer(100)) //返回false
自动装箱就是可以直接把一个基本类型赋给一个包装类实例
Integer ina = 12;Integer inb = 12;(ina == inb) //返回trueInteger inc = 128;Integer ind = 128;(inc == inb) //返回false
这里可以看Integer的源码
static final Integer[] cache = new Integer[-(-128) + 127 + 1];static { for(int i = 0; i < cache.length; i++){ cache[i] = new Integer(i - 128); }}
可以看出系统把一个-128到127之间的整数自动装箱成Integer实例,并放入一个cache数组中缓存起来,如果之后自动装箱一个-128 到127之间的整数实际指向cache数组中。如果不在这个范围,系统则会创建一个Integer实例。
处理对象
打印对象和toString()方法
class Person{ private String name; public Person(String name){ this.name = name; }}public class PrintObject{ public static void main(String[] args){ Person p = new Person(); //这里打印的是对象p的类名 + @ + hashCode,这个是Object类默认的 System.out.println(p); }}
重写toString()方法
class Person{ private String name; public Person(String name){ this.name = name; } public String toString(){ return "name的值为: " + name; }}public class PrintObject{ public static void main(String[] args){ Person p = new Person("李雷"); //重写了toString()方法之后可以直接打印对象 //输出的格式为重写return中的 System.out.println(p); }
Java测试两个变量是否相等:一种是==运算符,一种是equals()方法。当使用==来判断两个变量是否相等时,如果是基本的数据类型,且都是数值型,则两个变量的值相等就将返回true。
public class EqualsClass{ public static void main(String[] args){ int i = 65; float f = 65.0f; System.out.println(i == f); //返回true }}
"hello"直接量和new String("hello")有什么区别?当Java程序直接使用形如hello的字符串直接量时,JVM将会使用常量池来管理这些字符串,再调用String类的构造器来创建一个新的String对象,新建的String对象被保存在堆内存中,也就是new String("hello")产生了两个字符串对象。
常量池专门用于管理在编译时被确定并被保存已编译的.class文件中的一些数据,它包括了关于类、方法、接口中的常量,还包括字符串常量。
public class EqualsClass{ public static void main(String[] args){ String s1 = "abcd"; String s2 = "ab" + "cd"; String s3 = "abc"; String s4 = "d"; String s5 = s3 + s4; String s6 = new String("abcd"); System.out.println(s1 == s2); //返回true System.out.println(s1 == s5); //返回false System.out.println(s1 == s6); //返回false }}
JVM常量池保证相同的字符串常量只有一个,不会产生多个副本,上面程序中s1和s2的字符串在编译时就可以确定下来,因此他们都将引向同一个字符串对象
使用new创建出来的字符串对象是运行时创建出来的,被保存在运行时内存区。
有时候只是要求它们引用字符串里包含的字符串序列相同即可认为相等,此时就可以利用equals()方法来进行判断,如:
System.out.println(s1.equals(s6)); //返回true
equals()方法是Object类提供的一个实例方法,因此所有引用变量都可调用该方法来判断是否与其他引用相等。但使用这个方法判断两个对象是否相等和==没有区别,同样要求两个引用变量指向同一个对象才会返回true。因此上面这里没有实际意义,可采用重写equals方法来实现。
String已经重写了equals()方法,通过equals()比较返回true。
重写equals()方法
public class EqualsClass{ public boolean equals(Object obj){ //如果两个对象为同一对象 if(this == obj){ return true; } //只有当obj是this类对象 if(obj != null && obj.getClass() == EqualsClass.class){ EqualsClass eqc = (EqualsClass)obj; if(this.getIdStr().equals(eqc.getIdStr())){ return true } return false; } } public static void main(String[] args){ }}
正确写equals应该满足一下条件:
自反省:任意x,x.equals(x)一定返回true
对称性:任意x、y,x.equals(y)结果等于y.equals(x)
传递性:x.equals(y), y.equals(z),x.equals(z)结果意义
对于任何不是null,x.equals(null)返回true;
类成员
static修饰的成员就是类成员,static修饰的类成员属于整个类。当通过对象类访问类变量时,系统会在底层转换为通过该类来访问类变量。
对象实际上并不持有类变量,类变量是由该类持有的,同一个类的所有对象访问类变量时,实际上访问的都是该类所持有的变量。当通过对象访问类成员时,实际上依然是委托给该类来访问类成员。因此即使某个实例为null,它也可以访问它所属类的类成员。
public class NullStatic{ private static void test(){ System.out.println("null变量调用"); } public static void main(String[] args){ NullStatic ns = null; ns.test(); }}
对象访问类成员实际还是委托类来访问该成员,所以即使是对象的值为null,依然可以调用类成员。
静态初始化块也是类成员的一种,静态初始化块用于执行类初始化动作,系统会调用静态初始化类对类进行初始化。一旦初始化结束后,静态初始化块将永远不会获得执行的机会。
对static关键字而言,类成员不能访问实例成员。
final关键字可以用来修饰类、变量和方法。用final修饰表示一旦赋初值就不可改变。final修饰的成员变量必须由程序猿显式指定初始化。
final修饰的类变量、实例变量能指定初始值的地方:
类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,只能在三个地方中的一个指定;
实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初值。
必须注意的是,不管在哪里指定初值,都必须遵循先赋值再引用的规则,没有指定初值的变量不能被访问。
系统不会对局部变量初始化,局部变量必须由程序猿显式初始化。因此使用final修饰局部变量既可以在定义时指定,也可以不指定。如果不指定则必须在后面的代码中指定,只能一次,不能重复赋值。
使用final修饰基本的数据类型,不能对基本的数据类型重新赋值,因此基本的数据类型不能改变。
修饰的是引用类型,final保证这个引用类型变量所引用的地址不能改变,即一直引用统一对象,但这个对象完全可以改变。
class Person{ String name; int age; public Person(String name, int age){ this.name = name; this.age = age; } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; }}public class FinalClass{ public static void main(String[] args){ final Person p = new Person("lilei", 18); System.out.println(p.name + "---" + p.age); //无法改变引用地址,但可以改变对象 p.setName("hanmeimei"); p.setAge(81); System.out.println(p.name + "---" + p.age); }}
对于final变量来说,不管是类变量、实例变量还是局部变量,只要满足1、使用final修饰;2、定义该final变量时指定了初值;3、该初始值可在编译时确定下来,这种情况下变量相当于一个直接量。
final修饰符的一个重要用于是定义宏变量,当定义final变量时就为该变量指定了初始值,而且该初始值可以在编译时确定下来,那么合格final变量本质上是一个宏变量,编译器会在程序中所有用到该变量的地方替换成宏变量。
public static void main(String[] args){ final int a = 5 + 2; final String str = "abc" + 99.0; final String str2 = "abc" + String.valueOf(99.0); //str与str2不相等,str在编译时就可以确定值,所以是宏变量 //str2必须在程序运行才能确定值,所以不是宏变量 }
final修饰的方法不可被重写,final修饰的类不可以有子类,String是不可变类。
定义不可变类需要遵循:
1、使用private和final修饰该类的成员变量
2、提供带参数构造器,用于根据传入参数来初始化类里的成员变量
3、仅为该类成员变量提供getter方法,不为该类挺setter方法,普通方法无法修改final修饰的成员变量
4、有必要重写hashcode和equals方法