单例包装其他类 java_Java 面向对象(下)

Java8增强的包装类

753629a0743f156070b60d35b08c9efd.png

自动装箱:把一个基本类型变量直接赋给对应的包装类变量,或者赋给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 + "";

a06d91c894a0bb5c61a20513ffac9aae.png

包装类型的变量是引用数据类型,但包装类的实例可以与数据类型的值进行比较,这种比较是直接取出包装类实例所包装的数值来进行比较的。

静态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修饰没什么意义。

2c1bd05fe7038c056fd58aebb20a0d5c.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值