Java8增强的包装类
自动装箱:把一个基本类型变量直接赋给对应的包装类变量,或者赋给Object变量(Object是所有类的父类,子类对象可以直接赋给父类变量);
自动拆箱:允许直接把包装类对象直接赋给一个对应的基本类型变量。
包装类实现基本类型变量和字符串之间的转换。把字符串类型的值转换为基本类型的值有两种方式:
利用包装类提供的parseXxx(String s)静态方法(除了Character之外的所有包装类都提供了该方法)。
利用包装类提供的Xxx(String s)构造器。
String intStr = "123";
int it1 = Integer.parseInt(inStr);
int it2 = new Integer(intStr);
String类提供了多个重载valueOf()方法,用于将基本类型变量转换成字符串:
String ftStr = String.valueOf(2.345f);
String boolStr = String.valueOf(true);
将基本类型转换成字符串的更简单方法:将基本类型变量和""进行连接运算,系统会自动把基本类型变量转换成字符串:
//intStr的值为"5"
String intStr = 5 + "";
包装类型的变量是引用数据类型,但包装类的实例可以与数据类型的值进行比较,这种比较是直接取出包装类实例所包装的数值来进行比较的。
静态compare(xxx val1, xxx val2)方法,比较两个基本类型值的大小,输出1、0、-1。
两个boolean类型值进行比较时,true>false。
Java7为Character包装类增加了大量的工具方法来对一个字符进行判断,Java8再次增强了这些包装类的功能,其中一个重要的增强就是支持无符号算术运算。
处理对象
打印对象和toString方法
toString()方法是Object类里的一个实例方法,所有的Java类都是Object类的子类,因此所有的Java对象都具有toString()方法。所有的Java对象都可以和字符串进行连接运算,当Java对象和字符串进行连接运算时,系统自动调用Java对象toString()方法的返回值和字符串进行连接运算。
toString()方法是一个非常特殊的方法,它是一个“自我描述”方法,该方法通常用于实现这样一个功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象具有的状态信息。
Object类提供的toString()方法总是返回该对象实现类的“类名+@+hashCode”值。
==和equals方法
当使用==来判断两个变量是否相等时,如果两个变量是基本类型变量,且都是数值类型(不一定要求数据类型严格相同),则只要两个变量的值相等,就将返回true。
但对于两个引用类型变量,只有它们指向同一个对象时,==判断才会返回true。==不可用于比较类型上没有父子关系的两个对象。
public class EqualTest
{
public static void main(String[] args)
{
int it = 65;
float f1 = 65.0f;
//将输出true
System.out.println("65和65.0f是否相等?" + (it == f1));
char ch= 'A';
//将输出true
System.out.println("65和'A'是否相等?" + (it == ch));
String str1 = new String("hello");
String str2 = new String("hello");
//将输出false
System.out.println("str1和str2是否相等?" + (str1 == str2));
//将输出true
System.out.println("str1是否equals str2?" + (str1.equals(str2)));
//由于java.lang.String与EqualTest类没有继承关系
//所以下面语句导致编译错误
System.out.println("hello" == new EqualTest());
}
}
当Java程序直接使用形如"hello"的字符串直接量(包括可以在编译时就计算出来的字符串值)时,JVM将会使用常量池来管理这些字符串;当使用new String("hello")时,JVM会先使用常量池来管理"hello"直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中。换句话说,new String("hello")一共产生了两个字符串对象。
常量池(constant pool)专门用于管理在编译时被确定并被保存在已编译的.class文件中的一些数据。包括了关于类、方法、接口中的常量,还包括字符串常量。
下面程序示范了JVM使用常量池管理字符串直接量的情形
public class StringCompareTest
{
public static void main(String[] args)
{
//s1直接引用常量池的"克利夫兰骑士"
String s1 = "克利夫兰骑士";
String s2 = "克利夫兰";
String s3 = "骑士";
//s4后面的字符串值可以在编译时就确定下来
//s4直接引用常量池中的"克利夫兰骑士"
String s4 = "克利" + "夫兰" + "骑士";
//s5后面的字符串值可以在编译时就确定下来
//s5直接引用常量池中的"克利夫兰骑士"
String s5 = "克利夫兰" + "骑士";
//s6后面的字符串值不能在编译时就确定下来
//不能引用常量池中的字符串
String s6 = s2 +s3;
//使用new调用构造器将会创建一个新的String对象
//s7引用堆内存中新创建的String对象
String s7 = new String("克利夫兰骑士");
System.out.println(s1 == s4); //输出true
System.out.println(s1 == s5); //输出true
System.out.println(s1 == s6); //输出false
System.out.println(s1 == s7); //输出false
}
}
JVM常量池保证相同的字符串直接量只有一个,不会产生多个副本。例子中的s1、s4、s5所引用的字符串可以在编译期就确定下来,因此它们都将引用常量池中的同一个字符串对象。
使用new String()创建的字符串对象是运行时创建处理的,它被保存在运行时内存区(即堆内存)内,不会放入常量池中。
equals()方法是Object类提供的一个实例方法,因此所有引用变量都可调用该方法来判断是否与其他引用变量相等。String类以及重写了Object的equals()方法,String的equals()方法判断两个字符串相等的标准是:只要两个字符串所包含的字符序列相同,通过equals()比较将返回true,否则将返回false。
class Person
{
private String name;
private String idStr;
public Person(){}
public Person(String name , String idStr)
{
this.name = name;
this.idStr = idStr;
}
// 此处省略name和idStr的setter和getter方法。
// name的setter和getter方法
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
// idStr的setter和getter方法
public void setIdStr(String idStr)
{
this.idStr = idStr;
}
public String getIdStr()
{
return this.idStr;
}
// 重写equals()方法,提供自定义的相等标准
public boolean equals(Object obj)
{
// 如果两个对象为同一个对象
if (this == obj)
return true;
// 只有当obj是Person对象
if (obj != null && obj.getClass() == Person.class)
{
Person personObj = (Person)obj;
// 并且当前对象的idStr与obj对象的idStr相等才可判断两个对象相等
if (this.getIdStr().equals(personObj.getIdStr()))
{
return true;
}
}
return false;
}
}
public class OverrideEqualsRight
{
public static void main(String[] args)
{
Person p1 = new Person("孙悟空" , "12343433433");
Person p2 = new Person("孙行者" , "12343433433");
Person p3 = new Person("孙悟饭" , "99933433");
// p1和p2的idStr相等,所以输出true
System.out.println("p1和p2是否相等?"
+ p1.equals(p2));
// p2和p3的idStr不相等,所以输出false
System.out.println("p2和p3是否相等?"
+ p2.equals(p3));
}
}
重写equals()方法应该满足下列条件:
1.自反性:对任意x,x.equals(X)一定返回true。
2.对称性:对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true。
3.传递性:对任意x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)也返回true。
4.一致性:对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一直是true,要么一直是false。
5.对任何不是null的x,x.equals(null)一定返回false。
Object默认提供的equals()只是比较对象的地址,即Object类的equals()方法比较的结果与==运算符比较的结果完全相同。在实际应用中常常需要重写equals()方法,重写equals()方法时,相等条件是由业务要求决定的,因此equals()方法的实现也是由业务要求决定的。
类成员
理解类成员
在Java类里只能包含成员变量、方法、构造器、初始化块、内部类(包括接口、枚举)5种成员。static可以修饰成员变量、方法、初始化块、内部类(包括接口、枚举),以static修饰的成员就是类成员。类成员属于整个类,而不属于单个对象。
类变量属于整个类,当系统第一次准备使用该类时,系统会为该类变量分配内存空间,类变量开始生效,直到该类被卸载,该类的类变量所占有的内存才被系统的垃圾回收机制回收。类变量生存范围几乎等同于该类的生存范围。当类初始化完成后,类变量也被初始化完成。
当通过对象来访问类变量时,系统会在底层转换为通过该类来访问类变量。
当使用实例来访问类成员时,实际上依然是委托给该类来访问类成员,因此即使某个实例为null,它也可以访问它所属类的类成员。
如果一个null对象访问实例成员(包括实例变量和实例方法),将会引发NullPointException异常,因为null表明该实例根本不存在,既然实例不存在,那么它的实例变量和实例方法自然也不存在。
单例(Singleton)类
如果一个类始终只能创建一个实例,则这个类被称为单例类。
在一些特殊场景下,要求不允许自由创建该类的对象,而只允许为该类创建一个对象。为了避免其他类自由创建该类的实例,应该把该类的构造器使用private修饰,从而把该类的所有构造器隐藏起来。
根据良好封装的原则:一旦把该类的构造器隐藏起来,就需要一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。
除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象,也就无法保证只创建了一个对象。为此该类需要使用一个成员变量来保存曾经创建的对象,因为该成员变量需要被上面的静态方法访问,故该成员变量必须使用static修饰。
class Singleton
{
// 使用一个类变量来缓存曾经创建的实例
private static Singleton instance;
// 将构造器使用private修饰,隐藏该构造器
private Singleton(){}
// 提供一个静态方法,用于返回Singleton实例
// 该方法可以加入自定义的控制,保证只产生一个Singleton对象
public static Singleton getInstance()
{
// 如果instance为null,表明还不曾创建Singleton对象
// 如果instance不为null,则表明已经创建了Singleton对象,
// 将不会重新创建新的实例
if (instance == null)
{
// 创建一个Singleton对象,并将其缓存起来
instance = new Singleton();
}
return instance;
}
}
public class SingletonTest
{
public static void main(String[] args)
{
// 创建Singleton对象不能通过构造器,
// 只能通过getInstance方法来得到实例
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // 将输出true
}
}
final修饰符
final关键字可用于修饰类、变量和方法
final成员变量
final修饰的成员变量必须由程序员显式地指定初始值。
final修饰的类变量、实例变量能指定初始值的地方如下:
1.类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定。
2.实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。
与普通成员变量不同的是,final成员变量(包括实例变量和类变量)必须由程序员显式初始化,系统不会对final成员进行隐式初始化。如果打算在构造器、初始化块中对final成员变量进行初始化,则不要在初始化之前就访问成员变量的值。
final局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不知道默认值。如果final修饰的局部变量在定义时已经指定默认值,则后面代码中不能再对该变量赋值。
final修饰基本类型变量和引用类型变量的区别
当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用联系不了所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。
可执行“宏替换”的final变量
对一个final变量来说,不管它是类变量、实例变量,还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。
使用final修饰符修饰
在定义该final变量时指定了初始值
该初始值可以在编译时就被确定下来
final修饰符的一个重要用途就是定义“宏变量”。当定义final变量时就为该变量指定了初始值,而且该初始值可以在编译时就确定下来,那么这个final变量本质上就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。
如果被赋的表达式只是基本的算术表达式或字符串连接运算,没有访问普通变量,调用方法,Java编译器同样会将这种final变量当成“宏变量”处理。
Java会使用常量池来管理曾经用过的字符串直接量,例如执行String a = "java";语句之后,常量池中就会缓存一个字符串"java";如果程序再次执行String b = "java";,系统将会让b直接指向常量池中的"java"字符串,因此a==b将会返回true。
final方法
final修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用final修饰该方法。
如果子类中定义一个与父类private方法用相同方法名、相同形参列表、相同返回值类型的方法,不是方法重写,只是重新定义了一个新方法。因此,即使使用final修饰一个private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。private final
final修饰的方法不能被重写,可以被重载
final类
final修饰的类不可被继承。
不可变类
不可变(immutable)类的意思是创建该类的实例后,该实例的实例变量是不可改变的。Java的8个包装类和java.lang.String类都是不可变类,当创建它们的实例后,其实例的实例变量是不可改变。
如果需要创建自定义的不可变类,可遵守如下规则
使用private和final修饰符来修饰该类的成员变量
提供带参数构造器,用于根据传入参数来初始化类里的成员变量
仅为该类的成员变量提供getter方法,不要为该类的成员变量提供setter方法,因为普通方法无法修改final修饰的成员变量
如果有必要,重写Object类的hashCode()和equals()方法。equals()方法根据关键成员变量来作为两个对象是否相等的标准,除此之外,还应该保证两个用equals()方法判断为相等的对象的hashCode()也相等。
缓存实例的不可变类
class CacheImmutable
{
private static int MAX_SIZE = 10;
//使用数组来缓存已有的实例
private static CacheImmutable[] cache = new CacheImmutable[MAX_SIZE];
//记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例
private static int pos = 0;
private final String name;
private CacheImmutable(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static CacheImmutable ValueOf(String name)
{
//遍历已缓存的对象,
for (int i = 0; i < cache.length; i++)
{
//如果已有相同实例,则直接返回该缓存的实例
if (cache[i] != null && cache[i].getName().equals(name))
{
return cache[i];
}
}
//如果缓存池已满
if (pos == MAX_SIZE)
{
//把缓存的第一个对象覆盖,即把刚刚生成的对象放在缓存池的最开始位置
cache[0] = new CacheImmutable(name);
//把pos设为1
pos = 1;
}
else {
//把新创建的对象缓存起来,pos加1
cache[pos++] = new CacheImmutable(name);
}
return cache[pos-1];
}
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj !=null && obj.getClass() == CacheImmutable.class)
{
CacheImmutable ci = (CacheImmutable)obj;
return name.equals(ci.getName());
}
return false;
}
public int hashCode()
{
return name.hashCode();
}
}
public class CacheImmutableTest {
public static void main(String[] args)
{
CacheImmutable c1 = CacheImmutable.ValueOf("hello");
CacheImmutable c2 = CacheImmutable.ValueOf("hello");
//下面代码将输出true
System.out.println(c1 == c2);
System.out.println(c1.equals(c2));
}
}
修饰符的适应范围
4个访问控制符是互斥的,最多只能出现其中之一
abstract和final永远不能同时使用
abstract和static不能同时修饰方法,可以同时修饰内部类
abstract和private不能同时修饰方法,可以同时修饰内部类
private和final同时修饰方法虽然语法是合法的,但没有太大的意义——由于private修饰的方法不可能被子类重写,因此使用final修饰没什么意义。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。