Java面向对象进阶篇(包装类,不可变类)
一. Java 8的包装类
Java中的8种基本数据类型不支持面向对象的变成机制,也不具备对象的特性:没有成员变量,方法可以调用。为此,Java为这8 种基本数据类型分别提供了对应的
包装类(Byte,Short,Integer,Long,Double,Float,Charater,Boolean)。
从jdk 1.5开始,Java提供了自动装箱和自动拆箱的功能。自动装箱就是可以把一个基本类型变量赋给对应的包装类变量。自动拆箱与之相反。
包装类提供了基本类型变量和字符串之间的转换的方法。有两种方式把字符串类型值转换成基本类型的值
a)利用包装类提供的parseXxx(String s)静态方法(除了Character之外的所有包装类都提供了该方法。)
b)利用包装类提供的Xxx(String s)构造器
String类提供了多个重载valueOf()方法,用于将基本类型变量转换成字符串。
两个128自动装箱后,比较它们的大小并不相等,因此Java 7增强了包装类的功能,为所有的包装类提供了一个静态的compare(xxx val1,xxx val2)方法,来比较两个基本类型值得大小。
Java 8再次增强包装类的功能,可以支持无符号运算。
二.处理对象
2.1 打印对象和toString方法
System.out的println()方法和print()方法只能在控制台输出字符串。
toString()方法是Object类里的一个实例方法,它是一个自我描述方法,所有的Java类都是Object类的子类,因此所有的Java类都有toString()方法。
当程序员直接打印一个对象时,系统会输出该对象的“自我描述”信息,以告诉外界该对象的所有状态信息。当我们使用println()和print()方法打印一个对象时,会自动调用Object类的toString()方 法。因此,下面两行代码的效果完全一样:
System.out.println(person);
System.out.println(person.toString());
Object类提供的toString()方法总是返回该对象实现类的“类名+@+hashCode”值,这个值不能真正实现“自我描述”功能,因此如果用户需要实现自我描述功能,就必须重写Object类的toString()方法。
package com.company; class Apple{
private String color;
private double weight;
public Apple(String color,double weight)
{
this.color = color;
this.weight = weight; } @Override
public String toString() {
return "一个苹果,它的颜色是"+color+",重量是"+weight;
}
} public class ToStringTest {
public static void main(String[] args){
Apple apple = new Apple("红色",5.68);
System.out.println(apple);
} }
2.2 ==和equals方法
== 和 equals 都可以用来测试两个变量是否相等。== 用来判断基本数据类型时,当且仅当变量的数据类型和变量的值都一致时才返回true。== 用来判断引用类型变量时,只有当它们指向同一个对象时才返回true。不可用于比较类型上没有父子关系的两个对象。
可以使用String对象的equals方法判断两个字符串变量的引用字符串的字符序列是否相等,相等就返回true。
下面程序示范了JVM使用常量池管理字符串直接量的情形
package com.company; public class StringCompareTest {
public static void main(String[] args){
//s1直接引用常量池中的“疯狂Java”
String s1 = "疯狂Java";
String s2 = "疯狂";
String s3 = "Java";
String s4 = "疯狂"+"Java";//s4后面的字符串值在编译时确定下来,直接引用常量池中的“疯狂Java”
String s5 = s2+s3;//s5后面的字符串值在编译时确定下来,不能直接引用常量池中的“疯狂Java”
//JVM会使用常量池来管理“疯狂Java”,在调用构造器创建一个新的String对象,一共产生了两个字符串对象
String s6 = new String("疯狂Java");//s6调用构造器创建一个新的String对象,s6引用堆内存中的String对象 System.out.println(s1 == s4);//输出true
System.out.println(s1 == s5);//输出false
System.out.println(s1 == s6);//输出false
System.out.println(s4 == s6);//输出false
}
}
equals方法是Object类提供的一个实例方法,因此所有引用变量都可调用该方法来判断是否等于引用变量,但使用这个
方法与==运算符没有区别,同样要求两个引用变量指向同一个对象才返回true。如果希望采用自定义的相等标准,则可
采用重写equals方法实现。
package com.company; class Person
{
private String name;
private String id; public Person(String name, String id) {
this.name = name;
this.id = id;
} @Override
public boolean equals(Object obj)
{
if(this == obj)
return true;
if(null != obj && obj.getClass() == Person.class)
{
Person person = (Person)obj;
if(person.id == this.id)
{
return true;
}
}
return false;
}
}
public class OverrideEqualsRight {
public static void main(String[] args)
{
Person p1 = new Person("孙悟空","234");
Person p2 = new Person("孙行者","234");
Person p3 = new Person("孙悟饭","567");
System.out.println("p1和p2是否相等?"+p1.equals(p2));
System.out.println("p1和p3是否相等?"+p1.equals(p3)); }
}
三. 类成员
3.1 理解类成员
static关键字修饰的成员就是类成员。类成员可以有4种,包括类变量,类方法,静态初始代码块,内部类等。static不等修饰构造器。类成员只属于类不属于实例。
类变量属于整个类,当系统第一次准备使用该类时,系统会为该类变量分配内存空间,类变量开始生效,直到该类被卸载,该类的类变量所占有的内存才会被系统的垃圾回收机制回收。当类初始化完成后,类变量也初始化完成。
类变量可以通过类来访问,也可以通过类的对象来访问。当通过对象来访问类变量时,系统会在底层转换为通过该类访问变量。
注意类成员不能访问实例成员。因为类成员是属于类的,作用域比实例成员的作用域大。完全可能出现类成员初始化完成,但实例成员还不曾初始化的情况。
3.2 单例(Singleton)类
在一些特殊场景下,不允许自由创建该类的对象,而只允许为该类创建一个对象。为了避免其他类自由创建该类的实例,应该把类的构造器使用private修饰。根据良好的封装原则,一旦把类的构造器隐藏起来,则需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象。为此该类需要使用一个成员变量来保存曾经创建的对象,因为该成员变量需要上面的静态方法访问,故该成员变量必须使用static修饰。
如果一个类始终只能创建一个实例,则这个类被称为单例类。
基于上面介绍,下面程序创建了一个实例类。
package com.company;
class Singleton
{
private static Singleton instance;
private Singleton(){}
public static Singleton getSingleton()
{
if(null == instance)
{
instance = new Singleton();
} return instance;
} } public class SingletonTest {
public static void main(String[] args)
{
Singleton s1 = Singleton.getSingleton();
Singleton s2 = Singleton.getSingleton();
System.out.println(s1 == s2);
} }
输出:true
四. final修饰符
final关键字可用于修饰类、变量和方法。用于表示它修饰的类,方法和变量不可改变。
4.1 final成员变量
final修饰的成员变量必须由程序员显式地指定初始值,归纳如下:
类变量:必须在声明类变量时或静态初始块中指定初始值。而且只能在两个地方的其中之一指定。
实例变量:必须在声明实例变量时,非静态初始化块或构造器中指定初始值,而且只能在三个地方的其中之一指定。
如果打算在构造器、初始化块中对final成员变量进行初始化,则不要在初始化之前就访问成员变量的值。例如下面程序
引发错误。
public class FinalErrorTest {
final int age;
{
System.out.println(age);//访问引发错误
age = 6;
System.out.println(age);
}
}
4.2 final局部变量
局部变量必须由程序员显示指定默认值。因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定
默认值。例如:
public void setDemo()
{
final int a;
a=3;
System.out.println(a);
}
final修饰基本数据类型变量时,保证变量的基本数值不会改变,当final修饰引用类型变量时,final只保证变量引用的
地址不会改变,即一直引用一个对象。
4.3 可执行宏替换的final变量
对于一个final变量来说,不论是类变量,实例变量,还是局部变量,只要满足三个条件,这个final变量不再是一个变量,而是相当于一个直接量。
a)使用final修饰符修饰
b)在定义该final变量时指定了初始值
c)该初始值在编译时确定了下来
4.4 final方法
final修饰的方法不可以被重写
4.5 final类
final修饰的类不可以有子类,不可被继承,例如java.lang.math就是一个final类
4.6 不可变类
不可变类的意思是创建该类的实例后,该实例的实例变量是不可改变的。Java提供的8个包装类和java.lang.String类
都是不可变类,当创建它们的实例后,其实例的实例变量不可改变
创建自定义的不可变类,需满足以下规则:
1.使用private和final修饰符来修饰该类的成员变量
2.提供带参数的构造器,用于根据传入参数初始化类里的成员变量
3.仅为类的成员变量提供getter方法,不提供setter方法
4.如果有必要,重写Object类的equals()方法和hashCode()方法。equals()方法根据关键成员变量来作为两个对象是否相 等的标准,除此之外,还应该保证两个用equals()方法判断为相等的对象的hashCode()也相等。
下面定义一个不可变的Address类。
package com.company; public class Address {
private final String detail;
private final String postCode; public Address()
{
this.postCode = "";
this.detail = "";
} public Address(String detail,String postCode)
{
this.detail = detail;
this.postCode = postCode;
} public String getDetail()
{
return this.detail;
} public String getPostCode()
{
return this.postCode;
} @Override
public int hashCode() {
return detail.hashCode() + postCode.hashCode()*31;
} @Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(null != obj && obj.getClass() == Address.class)
{
Address ad = (Address)obj;
if(this.getDetail().equals(ad.getDetail())
&& this.getPostCode().equals(ad.getPostCode()))
return true;
}
return false;
} }
如果需要设计一个不可变类,尤其要注意其引用类型的成员变量。如果引用类型的成员变量的类是可变的,就必须采取必要的措施来保护该成员变量所引用的对象不会被修改,这样才能创建真正的不可变类。
下面程序试图创建一个不可变的Person类,但因为Person类的一个成员变量是可变类,所以导致Person类也是个可变类。
package com.company2; class Name
{
private String firstName;
private String lastName; public Name(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Name(){} public String getFirstName() {
return firstName;
} public String getLastName() {
return lastName;
} public void setFirstName(String firstName) {
this.firstName = firstName;
} public void setLastName(String lastName) {
this.lastName = lastName;
} }
public class Person { private final Name name; public Person(Name name)
{
this.name = name;
} public Name getName() {
return name;
} public static void main(String[] args)
{
Name name = new Name("悟空","孙");
Person person = new Person(name); System.out.println(person.getName().getFirstName()); name.setFirstName("八戒"); System.out.println(person.getName().getFirstName()); }
}
为了保持Person对象的不可变性,必须保护好Person对象的成员变量:name,让程序无法访问到Person对象的name
成员变量,也无法利用name成员变量的可变性来改变Person对象了。
因此将Person类改为如下:
public class Person { private final Name name;
public Person(Name name)
{
this.name = new Name(name.getFirstName(),name.getLastName());
} public Name getName() {
return name;
}
4.7 缓存实例的不可变类
不可变类的实例状态不可改变,可以很方便的被多个对象所共享。如果程序经常需要使用相同的不可变类的实例,则
应该考虑缓存这种不可变类的实例,毕竟创建重复的对象没有太大的意义,而且加大系统开销。如果可能,应该将不可变类的实例进行缓存。
package com.company2; class CacheImmutale
{
private static int MAX_SIZE = 10;
//使用数组来缓存已有的实例
private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE];
//记录缓存实例在缓存中的位置,cache[pos-1]是最新的实例
private static int pos = 0;
private final String name; private CacheImmutale(String name)
{
this.name = name;
} public String getName() {
return name;
} /**
* 缓存的操作方法
* @param name
* @return 缓存的实例
*/
public static CacheImmutale valueOf(String name){
//遍历已缓存的对象
for(int i = 0 ; i < MAX_SIZE ; i++)
{
//如果已有相同实例,则直接返回该缓存的实例
if(null != cache[i] && cache[i].getName().equals(name))
{
return cache[i];
} }
//如果缓存池已满,
if(pos == MAX_SIZE)
{
//把缓存的第一个对象覆盖,即把刚刚生成的对象放在缓存池的最开始位置
cache[0] = new CacheImmutale(name);
pos = 1;
}
else
{
//把新的对象缓存起来,pos加1
cache[pos++] = new CacheImmutale(name);
}
return cache[pos-1];
} @Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(null != obj && obj.getClass() == CacheImmutale.class)
{
CacheImmutale cache = (CacheImmutale) obj;
return cache.getName().equals(name);
}
return false;
}
public int hashCode()
{
return name.hashCode();
}
}
public class CachelmmutaleTest {
public static void main(String[] args)
{
CacheImmutale c1 = CacheImmutale.valueOf("Hello");
CacheImmutale c2 = CacheImmutale.valueOf("Hello"); System.out.println(c1 == c2);
}
}
Java面向对象进阶篇(包装类,不可变类)的更多相关文章
- Java面向对象进阶篇(抽象类和接口)
一.抽象类 在某些情况下,父类知道其子类应该包含哪些方法,但是无法确定这些子类如何实现这些方法.这种有方法签名但是没有具体实现细节的方法就是抽象方法.有抽象方法的类只能被定义成抽象类,抽象方法和抽象类 ...
- Java面向对象进阶篇(内部类)
一. 概念 大部分时候,类被定义成一个独立的程序单元.有时候把一个类放在另一个类内部定义,这个类被称为内部类,包含内部类的类也被称为外部类. 内部类的主要作用: 内部类提供良好的封装,可以把内部类隐藏 ...
- Python开发【第七篇】:面向对象 和 python面向对象进阶篇(下)
Python开发[第七篇]:面向对象 详见:<Python之路[第五篇]:面向对象及相关> python 面向对象(进阶篇) 上一篇<Python 面向对象(初级篇)> ...
- Java面向对象(概述,构造函数,类与对象的关系,this关键字,成员、局部),匿名对象的调用,构造代码块(5)
Java面向对象(概述,构造函数,类与对象的关系,this关键字,成员.局部),匿名对象的帝爱用,构造代码块(5)
- java中String、包装类、枚举类的引用传递
一般情况下,我们认为Java中了除了八种基本数据类型,其他都是对象,进行引用传递: 但是:String.包装类.枚举类作为参数传递后发现,没有达到引用传递的效果,很多人认为它是值传递! 首先,对象肯定 ...
- Java基本数据类型、包装类与String类之间的转换
一.基本数据类型与包装类之间的转换: import org.junit.Test; public class MainTest { /** * 基本数据类型与包装类之间的转换 */ @Test pub ...
- Java.lang 包 (包装类、String类、Math类、Class类、Object类)
Java 的核心 API(Application Programming Interface)是非常庞大的,这给开发者带来了很大的方便. java.lang 包是 Java 的核心类库,它包含了运行 ...
- ☕Java 面向对象进阶内容
目录 == 和 equals 方法 封装 多态 抽象类和抽象方法 抽象方法 抽象类 抽象类的使用要点 接口 接口使用 内部类 String 字符串常量拼接时的优化 String Pool String ...
- 3.1常用类(java学习笔记)包装类及日期类
一.包装类 java是一门面向对象的语言,秉承一切皆对象的思想. 可java中有一些基本数据类型并不是对象,有时可能需要将它们变为对象. 这时就需要用到我们的包装类了. 基本数据类型 包装类 int ...
随机推荐
- Android 6.0 运行时权限处理问题
序 自从升级到Android M以来,最大的改变就是增加了运行时权限RuntimePermission,6.0以上的系统如果没有做适配,运行了targetSDK=23的App时就会报权限错误.我们知道 ...
- 谈谈Ext JS的组件——布局的使用方法续一
盒子布局 盒子布局主要作用是以水平(Ext.layout.container.HBox)或垂直方式(Ext.layout.container.VBox)来划分容器区域.这也是比较常有的布局方式. 使用 ...
- 【IOS 开发】Object - C 语法 之 类型转换
作者 : 万境绝尘 (octopus_truth@163.com) 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/3913507 ...
- Leetcode_119_Pascal's Triangle II
本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/41851069 Given an index k, retu ...
- 7、Libgdx网络操作
(官网:www.libgdx.cn) Libgdx包含了一些跨平台的网络操作类,这些类在Gdx.net中. 特性 跨平台HTTP请求 多平台TCP C/S Socket支持(可配置) TCP C/S优 ...
- 4.1、Libgdx的生命周期
(原文:http://www.libgdx.cn/topic/32/4-1-libgdx%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) 一个libgdx应 ...
- Swift语言学习路线图
- (二十五)键盘的设置与TextField细节处理
设置Return Key类型为Send并且勾选下面的复选框即可实现没有内容时禁用keyboard,有内容自动启用. 文本框左边框与文本留有间距的方法: //文本框左侧留下间距 UIView *left ...
- AngularJS进阶(二十三)ANGULAR三宗罪之版本陷阱
ANGULAR三宗罪之版本陷阱 坑!碰到个大坑,前面由于绑定日期时将angular版本换为angular-1.3.0-beta.1时,后来午睡后,登录系统,发现无论如何都登陆不进去了,经过调试,发现数 ...
- SpriteBuilder中子节点的相对位置(%百分比定位)
子节点(或在这里确切的为精灵sprites)50%的偏移效果使得其在父节点中居中显示,该父节点的纹理在左下角(锚点为0,0). 这样做好过用父节点的位置的实际值来定位.根据父节点实际位置来定位在早期的 ...