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()方法总是返回该对象实现类的“类名[email protected]+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);
    }
}

原文地址:https://www.cnblogs.com/yumiaoxia/p/9010721.html

时间: 2024-10-17 11:05:01

Java面向对象进阶篇(包装类,不可变类)的相关文章

java web进阶篇(四) Tomcat数据源

动态web开发的最大特点是可以进行数据库的操作,传统的jdbc操作由于步骤重复性造成程序性能下降. 先来回顾JDBC的操作原理 1.加载数据库驱动程序,数据库驱动程序通过classpath配置. 2.通过DirverManager类取得数据库连接对象. 3.通过Connection实例化PreparedStatement对象,编写sql语句命令操作数据库. 4.数据库属于资源操作,操作完成后要关闭数据库以释放资源. 其实以上操作,1.2.4步骤是重复的,保留3,实际上就是数据源产生的原因. 数据

java基础进阶篇(七)_LinkedHashMap------【java源码栈】

目录 一.概述 二.特点 三.应用场合 四.构造方法 1.参数为空 2.accessOrder 五.源码结构分析 六.常见问题 1.如何实现的元素有序? 2.如何保证顺序的正确以及同步 3.如何实现两种顺序(插入顺序或者访问顺序)? 4.为什么重写containsValue()而不重写containsKey()? 七.常用方法 一.概述 ??LinkedHashMap是HashMap的子类,关于HashMap可以看下前面的章节:java基础进阶篇 HashMap public class Lin

java web进阶篇(三) 表达式语言

表达式语言(Expression Language ,EL)是jsp2.0中新增的功能.可以避免出现许多的Scriptlet代码 格式: ${ 属性名称 },  使用表达式语言可以方便的访问对象中的属性,提交的参数或者进行各种数学运算,而且使用表达式语言最大的特点是如果输出的内容是null,则会自动使用空字符串("")表示. <%request.setAttribute("name", "info");%> <h1>${n

java基础进阶篇(六)_HashTable------【java源码栈】

一.概述 ??前面介绍了HashMap的结构和原理,这里介绍个类似HashMap的结构Hashtable. ??HashTable 官方解释是HashMap的轻量级实现, 和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射. ??所以我们结合HashMap来介绍HashTable, 比较下两者的区别. ??HashTable 使用的很少, 它支持线程安全, 通过内部方法加上 synchronized 实现, 因此同步锁的密度太大了, 在实际情

Java面向对象理论篇(上)总结

面向对象:(学习王伟.传智等教程) 1.1)面向对象 面向对象是基于面向过程的编程思想 1.2)面向对象的思想特点 1.2.1)是一种更符合我们思考习惯的思想 1.2.2)把复杂的事情简单化 1.2.3)让我们从执行者变成了指挥者 2)类与对象 2.1)类(class)和对象(object)是面向对象方法的核心概念.类是对一类事物描述,是抽象的.概念上的定义:对象是实际存在的该类事物的每个个体,因而也称实例(instance). 2.2)Java语言中最基本的单位是类.所以,我们要用类来体现事物

java中String、包装类、枚举类的引用传递

一般情况下,我们认为Java中了除了八种基本数据类型,其他都是对象,进行引用传递: 但是:String.包装类.枚举类作为参数传递后发现,没有达到引用传递的效果,很多人认为它是值传递! 首先,对象肯定是引用传递,为何这三类没有达到引用传递的效果呢? 以包装类Boolean为例: 会发现: 1.Boolean的值都是static.final的,说白了就是不能修改的 2.没有修改值得setter方法 因此,从表象看:枚举值都是值传递(传递副本),根本原因是被限制修改.... 原文地址:https:/

面向对象-进阶篇

本篇主要为Python 类的成员.成员修饰符.类的特殊成员. 类的成员 类的成员可以分为三大类:字段.方法和属性 注:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段.而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份. 一.字段 字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同, 普通字段属于对象 静态字段属于类 1 class Province: 2 3 # 静态字段 4 cou

Python面向对象-进阶篇(类的特殊成员)

Python类,存在着一些具有特殊含义的成员,详情如下: 1.__doc__ 表示类的描述信息 class Foo: """ 描述类信息,这是用于看片的神奇 """ def func(self): pass print Foo.__doc__ #输出:类的描述信息  2.__module__ 和 __class__ __module__ 表示当前操作的对象在那个模块__class__   表示当前操作的对象的类是什么 from lib.aa im

Java语言进阶篇基本概念

一.Object类.常用API 1.Object类 此类事Java语言中的根类,即所有类的父类.如果一个类没有指定父类,那么默认则继承自Object类. 2.日期时间类 (1)Date类(特定的瞬间).format类(日期/时间格式化子类的抽象类) //创建日期对象 Date date = new Date(); //创建日期格式化对象,指定输出格式,注意:y年M月d日H时m分s秒 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss