疯狂Java——学习笔记(三)

枚举类

在某些情况下,一个类的对象是有限而且固定的,比如季节类,它只有4个对象;在比如行星类,目前只有8个对象。这种实例有限而且固定的类,在Java里被称为枚举类。

枚举类入门

Java5新增了一个enum关键字(它与class、interface关键字的地位相同),用以定义枚举类。正如前面看到的,枚举类是一种特殊的类,它一样可以有自己的成员变量、方法,可以实现一个或者多个接口,也可以定义自己的构造器。一个Java源文件中最多只能定义一个public访问权限的枚举类,且该Java源文件也必须和枚举类的类名相同。

但枚举类终究不是普通类,它与普通类有如下简单区别:

  • 枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是默认继承Object类,因此枚举类不能显式继承其他的父类。其中java.long.Enum类实现了java.lang.Serializable和java.lang.Comparable接口。

  • 使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。

  • 枚举类的构造器只能使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。

  • 枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加public static final修饰,无须程序员显式添加。

枚举类默认提供了一个values()方法,该方法可以很方便地遍历所有的枚举值。

下面定义了一个简单枚举类:

 

枚举类的成员变量、方法和构造器

枚举类的实例只能是枚举值,而不是随意地通过new来创建枚举类对象。

public enum Gender {

    MALE,FEMALE;

    //定义一个public修饰的实例变量
    public String name;

    public static void main(String[] args) {

        //通过Enum的valueOf()方法来获取指定枚举类的枚举值
        Gender g = Enum.valueOf(Gender.class, "MALE");

        //直接为枚举值的name实例变量赋值
        g.name = "女";

        //直接访问枚举值的name实例变量
        System.out.println(g + "代表:" + g.name);
    }
}

一旦为枚举类显示定义了带参数的构造器,列出枚举值时就必须对应地传入参数。

public enum Gender {

    //此处的枚举值必须调用对应的构造器来创建
    MALE("男"),FEMALE("女");
    private final String name;

    //枚举类的构造器只能使用private修饰
    private Gender(String name) {
        this.name = name;
    }
}

在枚举类列出枚举值时,实际上就是调用构造器创建枚举类对象,只是这里无须使用new关键字,也无须显式调用构造器。

不难看出,上面程序中红字代码实际上等同于如下两行代码:

private static final Gender MALE = new Gender("男");

private static final Gender FEMALE = new Gender("女");

实现接口的枚举类

枚举类也可以实现一个或多个接口。与普通类实现一个或多个接口完全一样,枚举类实现一个或多个接口时,使用implements实现接口,并实现接口里包含的抽象方法。当然也需要实现该接口所包含的方法。

 

使用匿名内部类的枚举类:

interface GenderDesc{
    void info();
}

public enum Gender implements GenderDesc{

    //此处的枚举值必须调用对应的构造器来创建
    MALE("男"){

        @Override
        public void info() {
            System.out.println("这个枚举类代表男性!");
        }
    },

    FEMALE("女"){

        @Override
        public void info() {
            System.out.println("这个枚举类代表女性!");
        }
    };

    private final String name;

    //枚举类的构造器只能使用private修饰
    private Gender(String name) {
        this.name = name;
    }
}

    橘色花括号部分实际上就是一个类体部分,在这种情况下,当创建MALE、FEMALE枚举值时,并不是直接创建Gender枚举类的实例,而是相当于创建Gender的匿名子类的实例。因为粗体字括号部分实际上是匿名内部类的类体部分,它依然是枚举类的匿名内部子类。

 

枚举类不是用final修饰了吗?怎么还能派生子类呢?

答:并不是所有的枚举类都使用了final修饰!非抽象的枚举类才默认使用了final修饰。对于一个抽象的枚举类而言——只要它包含了抽象方法,它就是抽象枚举类,系统会默认使用abstract修饰,而不是使用final修饰。

 

    枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(因为系统自动会为它添加abstract关键字),但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。

 

对象的引用

对大部分对象而言,程序里会有一个引用变量引用该对象,这是最常见的引用方式。除此之外,java.lang.ref包下提供了3个类:SoftReference、PhantomReference和WeakReference,它们分别代表了系统对对象的3种引用方式:软引用、虚引用和弱引用。因此,Java语言对对对象的引用有如下4种方式:

  • 强引用(StrongReference)

这是Java程序中最常见的引用方式。程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象,对象和数组都采用了这种强引用的方式。当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收。

  • 软引用(SoftReference)

软引用需要通过SoftReference类来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象;当系统内存空间不足时,系统可能会回收它。软引用通常用于对内存敏感的程序中。

  • 弱引用(WeakReference)

弱引用通过SoftReference类实现,弱引用和软引用很像,但弱引用的引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然,并不是说当一个对象只有弱引用时,它就会立即被回收——正如那些失去引用的对象一样,必须等到系统垃圾回收机制运行才会被回收。

  • 虚引用(PhantomReference)

虚引用通过PhantomReference类实现,虚引用完全类似没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时,那么它和没有引用的效果大致相同。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue)联合使用。

 

    引用队列由java.lang.ref.ReferenceQueue类表示,它用于保存被回收后对象的引用。当联合使用软引用、弱引用和引用队列时,系统在回收被引用的对象之后,将把被回收对象对应的引用添加到关联的引用队列中。与软引用和弱引用不同的是,虚引用在对象被释放之前,将把它对应的虚引用添加到它关联的引用队列中,这使得可以在对象被回收之前采取行动。

    软引用和弱引用可以单独使用,但虚引用不能单独使用,单独使用虚引用没有太大的意义。虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队列中是否已经包含了该虚引用,从而了解虚引用所引用的对象是否即将被回收。

 

Java程序的入口——main()方法的方法签名

回忆Java程序的入口——main()方法的方法签名:

public static void main(String[] args){ ... }

下面详细解释main()方法为什么采用这个方法签名。

  • public修饰符:Java类由JVM调用,为了让JVM可以自由调用这个main()方法,所以使用public修饰符把这个方法暴露出来。

  • static修饰符:JVM调用这个主方法时,不会先创建该主类的对象,然后通过对象来调用该主方法。JVM直接通过该类来调用主方法,因此使用static修饰该主方法。

  • void返回值:因为主方法被JVM调用,该方法的返回值将返回给JVM,这没有任何意义,因为此main()方法没有返回值。

  • args数组:这是一个字符创数组形参,根据方法调用的规则:谁调用方法,谁负责为形参赋值。也就是说,main()方法由JVM调用,即args形参应该由JVM负责赋值。

但JVM怎么知道如何为args数组赋值呢?先看下面的程序:

public class ArgsTest{
    public static void main(String[] args){

        //输出args数组的长度
        System.out.println(args.length);

        //遍历args数组的每个元素
        for(String arg : args){
            System.out.println(arg);
        }
    }
}

以上是程序输出args数组的长度,遍历args数组元素的代码。使用java ArgsTest命令运行上面程序,看到程序仅仅输出了一个0,这表明args数组是一个长度为0的数组——这是合理的。因为计算机是没有思考能力的,它只能忠实地执行用户交给它的任务,既然程序没有给args数组设定参数值,那么JVM就不知道args数组的元素,所以JVM将args数组设置成一个长度为0的数组。

改为如下命令来运行上面程序:

java ArgsTes Spring java test

 

将会看到如下效果:

 

如果运行Java程序时在类名后紧跟一个或多个字符串(多个字符串之间以空格隔开),JVM就会把这些字符串依次赋给args数组元素。

 

System类

    System类代表当前Java程序的运行平台,程序不能创建System类的对象,System类提供了一些类变量和类方法,允许直接通过System类来调用这些类变量和类方法。

    除此之外,System类的in、out、err分别代表系统的标准输入(通常是键盘)、标准输出(通常是显示器)和错误输出流,并提供了setIn()、setOut()、setErr()方法来改变系统的标准输入、标准输出和标准错误输出流。

 

Object类

    Java还提供了一个protected修饰的clone()方法,该方法用于帮助其他对象来实现“自我克隆”,所谓“自我克隆”就是得到一个当前对象的副本,而且二者之间完全隔离。由于Object类提供的clone()方法使用了protected修饰,因此该方法只能被子类重写或调用。

自定义类实现“克隆”的步骤如下:

  • 自定义类实现Cloneable接口。这是一个标记性的接口,实现该接口的对象可以实现“自我克隆”,接口里没有定义任何方法。

  • 自定义类实现自己的clone()方法。

  • 实现clone()方法时通过super.clone();调用Object实现的clone()方法来得到该对象的副本,并返回该副本。

class Address{

    String detail;

    public Address(String detail) {
        this.detail = detail;
    }
}

//实现Cloneable接口
class User implements Cloneable{

    int age;
    Address address;

    public User(int age) {
        this.age = age;
        address = new Address("陕西西安");
    }

    //通过调用super.clone()来实现clone()方法
    @Override
    public User clone() throws CloneNotSupportedException {
        return (User)super.clone();
    }
}

public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        User u1 = new User(88);

        //clone得到u1对象的副本
        User u2 = u1.clone();

        //判断u1、u2是否相同
        System.out.println(u1 == u2);

        //判断u1、u2的address是否相同
        System.out.println(u1.address == u2.address);
    }
}

上面程序让User类实现了Cloneable接口,而且实现了clone()方法,因此User对象就可实现“自我克隆”——克隆出来的对象只是原有对象的副本。

    Object类提供的Clone机制只对对象里各实例变量进行“简单复制”,如果实例变量的类型是引用类型,Object的Clone机制也只是简单地复制这个引用变量,这样原有对象的引用类型的实例变量与克隆对象的引用类型的实例变量依然指向内存中的同一个实例。

    Object类提供的clone()方法不仅能简单地处理“复制”对象的的问,而且这种“自我克隆”机制十分高效。比如clone一个包含100元素的int[]数组,用系统默认的clone()方法比静态从copy方法快近2倍。

    需要指出的是,Object类的clone()方法虽然简单、易用,但它只是一种“浅克隆”——它只克隆该对象所有成员变量值,不会对引用类型的成员变量值所引用的对象进行克隆。如果开发者需要对对象进行“深克隆”,则需要开发者自己进行“递归”克隆,保证所有引用类型的成员变量值所引用的对象都被复制了。

 

 

Java7新增的Objects

    Java7新增了一个Objects工具类,它提供了一些工具方法来操作对象,这些工具方法大多是“空指针”安全的。比如你不能确定一个引用变量是否为null,如果贸然地调用该变量的toString()方法,则可能引发NullPointException异常;但如果使用Objects类提供的toString(Object o)方法,就不会引发空指针异常,当o为null时,程序将返回一个“null”字符串。

 

提示:

    Java为工具类的命名习惯是添加一个字母s,比如操作数组的工具类是Arrays,操作集合的工具类时Collections。

import java.util.Objects;

public class ObjectsTest {

    //定义一个obj变量,它的默认值是null
    static ObjectsTest obj;

    public static void main(String[] args) {
        //输出一个null对象的hashCode值,输出0
        System.out.println(Objects.hashCode(obj));

        //输出一个null对象的toString,输出null
        System.out.println(Objects.toString(obj));

        //要求obj不能为null,如果obj为null则引发异常
        System.out.println(Objects.requireNonNull(obj, "obj参数不能是null"));
    }
}

输出结果如下:

 

Java9改进的String、StringBuffer和StringBuilder类

 

    Java9改进了字符串(包括String、StringBuffer和StringBuilder)的实现。在Java9以前采用字符串采用char[]数组来保存字符,因此字符的每个字符占2字节;而Java9的字符串采用byte[]数组再加一个encoding-flag字段来保存字符,因此字符串的每个字符只占1个字节。所以Java9的字符串更加节省空间,但字符串的功能方法没有受到任何影响。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值