枚举及Object类详解

枚举

英文单词:enumeration
枚举是一组常量的集合
可这样理解:枚举类属于一种特殊的类,里面只包含一组有限的特定的对象

创建一个季节类,来描述春夏秋冬

package javaSEStudy.enum_;

public class enumDemo1 {
    public static void main(String[] args) {
        Season spring = new Season("春天","温暖");
        Season summer = new Season("夏天","炎热");
        Season autumn = new Season("秋天","凉爽");
        Season winter = new Season("冬天","寒冷");
        //对于季节而言,他的值为固定的,不会有更多的
        //按照如此方式设计,无法体现季节是固定的四个对象,该设计不好
        //因此这样设计不好---->枚举类:把具体的对象一个一个的列举出来的类称为枚举类
        Season other = new Season("啥也不是天","啥也不是");
    }
}
class Season{
    private String name;
    //desc 描述
    private String desc;

    public Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

我们将其优化一下

package javaSEStudy.enum_;

//实现自定义枚举
public class SeasonMax {
    public static void main(String[] args) {
        System.out.println(Season1.AUTUMN);
        System.out.println(Season1.SUMMER);
    }
}
//1.先将构造器私有化,防止直接被new出来
//2.去掉set方法,防止属性被修改
//3.在Season1内部创建固定的对象
//4.优化:可以加入final
class Season1{
    private String name;
    //desc 描述
    private String desc;
    //定义了四个对象,固定
    public static final Season1 SPRING = new Season1("春天","温暖");
    public final static Season1 SUMMER = new Season1("夏天","炎热");
    public final static Season1 AUTUMN = new Season1("秋天","凉爽");
    public static final Season1 WINNER = new Season1("冬天","寒冷");

    private Season1(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "Season1{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

总结:

  • 首先就是枚举类的对象名要大写,它是常量
  • 不需要提供set方法,因为它是常量
  • 对枚举的对象/属性使用static和final共同修饰,实现底层优化
  • 枚举对象根据需要,也可以有多个属性

将一个普通类转换为枚举类->
1.先将构造器私有化,防止直接被new出来
2.去掉set方法,防止属性被修改
3.在Season1内部创建固定的对象
4.优化:可以加入final

enum关键字

enum
类名原来是class,改为enum

package javaSEStudy.enum_;

//使用enum关键字实现枚举
public class SeasonKeywords {
    public static void main(String[] args) {
        System.out.println(SeasonEnum.SPRING);
    }
}

/*
    1.使用enum关键字替代class
    2.new 对象的改变
        public static final Season1 SPRING = new Season1("春天","温暖");
        SPRING("春天","温暖")

        常量名(实参列表)
    3.如果有多个常量(对象),使用逗号间隔即可
    4.如果使用enum来创建枚举类,要求将定义常量对象写在最前面
 */

enum SeasonEnum{

    SPRING("春天","温暖"),
    SUMMER("夏天","炎热"),
    AUTUMN("秋天","凉爽"),
    WINNER("冬天","寒冷");

    private String name;
    private String desc;

    SeasonEnum(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "SeasonEnum{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

使用eunm关键字注意事项:

  • 当我们使用enum关键字开发一个枚举类的时候,默认继承Enum类,而且是一个final类
  • 传统的public static final SeasonEnum = new SeasonEnum("春天","温暖");简化为SPRING("春天","温暖"),这里必须知道它调用的是哪个构造器
  • 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略
  • 当有多个枚举对象时,使用逗号间隔,最后一个才是分号结尾
  • 枚举对象必须放在枚举类的首行
  • enum的关键字默认为private,enum 的实例是固定的,枚举常量(如 SPRING, SUMMER 等)必须在 enum 类的内部定义,不能由外部代码通过 new 创建。如果允许 public 构造器,外部代码就可以随意创建新的枚举实例,破坏枚举的单例性和不可变性。因此,Java 强制要求 enum 的构造器只能是 private(即使不写,默认也是 private)。

可以用javap实现反编译,javac是编译

使用enum关键字时,由于是继承的父类,当他采用sout输出对象的时候,例如sout(boy),enum中为BOY,他会使用父类的toString方法,在源码中它返回的是名字,所以这里会输出BOY

enum常用方法

首先是一个一览表

然后我们来举个栗子

package javaSEStudy.enum_;

//Enum类的各种方法的使用
public class EnumMethod {
    public static void main(String[] args) {
        SeasonEnum1 spring = SeasonEnum1.SPRING;

        //输出枚举对象的名称
        System.out.println(spring.name());

        //ordinal() 输出该枚举对象的次序/编号,从0开始
        System.out.println(spring.ordinal());

        //从反编译才能看到,返回一个数组
        //含有定义的所有枚举对象
        SeasonEnum1[] values = SeasonEnum1.values();
        for (SeasonEnum1 value : values) {
            System.out.println(value);
        }

        //valueOf:将你输入的字符串转换为枚举对象并去已有的枚举对象中去匹配,如果没找到,就报异常
        SeasonEnum1 spring1 = SeasonEnum1.valueOf("SPRING");
        System.out.println("spring1: " + spring1);
        //用于验证它是一个静态的常量,所以此处的spring1和上面的spring为同一个对象
        System.out.println(spring1 == spring);

        //比较两个枚举常量,比较的是位置的编号
        /*源码
         public final int compareTo(E o) {
            return self.ordinal - other.ordinal;
         }
         */
        //比较的就是两个位置的编码,然后拿前一个位置数减去后一个位置数,得到这个值
        //在这里 1 - 0 = -1,输出-1
        System.out.println(SeasonEnum1.SPRING.compareTo(SeasonEnum1.SUMMER));
    }
}

enum SeasonEnum1 {

    SPRING("春天", "温暖"),
    SUMMER("夏天", "炎热"),
    AUTUMN("秋天", "凉爽"),
    WINNER("冬天", "寒冷");

    private String name;
    private String desc;

    SeasonEnum1(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }

    @Override
    public String toString() {
        return "SeasonEnum{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';

    }
}

来个小练习

package javaSEStudy.enum_;

public class WeekTest {
    public static void main(String[] args) {
        Week[] values = Week.values();
        System.out.println("==所有星期的信息如下==");
        for(Week week: values) {
            System.out.println(week);
        }
    }
}

enum Week {
    MONDAY("星期一"),
    TUESDAY("星期二"),
    WEDNESDAY("星期三"),
    THURSDAY("星期四"),
    FRIDAY("星期五"),
    SATURDAY("星期六"),
    SUNDAY("周末");

    private String name;

    Week(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}

enum实现接口

注意事项点:

  • 使用enum关键字后,就不能在继承其他类了,因为enum会隐式继承Enum,而java是单继承机制
  • 枚举类和普通类一样,可以实现接口,语法如下
enum 类名 implements 接口1,接口2{}

//我们来举个栗子

package javaSEStudy.enum_;

//enum接口
public class Enum_interface {
    public static void main(String[] args) {
        Music.CLSSICMUSIC.play();
    }
}

interface Play{
    public void play();
}
enum Music implements Play{
    CLSSICMUSIC;

    @Override
    public void play() {
        System.out.println("播放音乐");
    }
}

//使用enum关键字之后就不能继承其他类了,因为enum已经继承了Enum类,而java是单继承机制
//class A {
//
//}
//enum Season2 extends A{
//
//}

Object类详解

equals方法

首先就是和 == 的比较

  • ==:既可以判断基本类型,又可以判断引用类型
  • ==:如果判断基本类型,判断的就是值是否相等
  • ==:如果判断的是引用类型,判断的是地址是否相等,即判定是不是同一个对象

我们上个代码看看

package javaSEStudy.enum_Object.ObjectTest;

public class Equals1 {
    public static void main(String[] args) {
        A a = new A();
        A b = a;
        A c = b;
        //那么a==c真假?
        System.out.println(a == c);
        System.out.println(b == c);
        //他们指向的地址一样,判定是不是同一个对象,所以为true

        B b1 = a;
        System.out.println(b1 == c);
        //只要满足地址对象相同,那么在这里他们就是true,即同一个对象   
    }
}
class B {}
class A extends B {}

equals 是Object中的方法,只能判断引用类型
默认判断的是地址是否相等,子类往往重写方法

我们添加几行示例

System.out.println( "hello".equals("abc"));
        //在这里equals会将字符串转换为值进行比较,所以为false

        String s1 = new String("abcd");
        String s2 = new String("abcd");
        //输出true
        System.out.println(s1.equals(s2));
        //输出false
        System.out.println(s2 == s1);

如何重写equals方法

我们直接上代码

package javaSEStudy.enum_Object.ObjectTest;

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

        Person p1 = new Person("Jason", 11, '男');
        Person p2 = new Person("Jason", 11, '男');

        //未加下面重写方法输出False
        //加上了下面重写方法后,就会输出true
        System.out.println(p1.equals(p2));
        //False
        System.out.println(p1 == p2);
        /*
        为什么此处equals为False
            此处的equals来自于Person类,在这里默认继承默认继承Object类,在Object类里默认比较两个对象
            此处并没有重写方法,所以这里比较的就是对象,这里new了两次,就不是同一个对象,输出False
         */

    }
}

//Person 默认继承Object类,在Object类里默认比较两个对象
class Person {
    private String name;
    private int age;
    private char gender;

    //重写Object类的equals方法
    @Override
    public boolean equals(Object obj){
        //如果比较的两个对象是同一个对象,则直接返回true
        if(this == obj){
            return true;
        }
        //进行类型判断
        if(obj instanceof Person){
            //进行类型转换,向下转型
            //因为我需要得到obj的各个属性
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
        }
        //如果不是Person则直接返回False
        return false;
    }

    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

为什么此处未重写equals输出False?
此处的equals来自于Person类,在这里默认继承默认继承Object类,在Object类里默认比较两个对象
此处并没有重写方法,所以这里比较的就是对象,这里new了两次,就不是同一个对象,输出False

hashCode方法

六个小结:

  • 提高具有哈希结构的容器的效率
  • 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
  • 两个引用指向的是不同对象,则哈希值是不一样的
  • 哈希值主要根据地址号来,不能完全将哈希值等价于地址
  • 后面在集合中的hashCode如果需要的话,也会重写,哈希值的重写在集合的学习再说明

话不多说,我们举个栗子

package javaSEStudy.enum_Object.ObjectTest;

public class HashCode {
    public static void main(String[] args) {
        A1 a1 = new A1();
        A1 a2 = new A1();
        A1 a3 = a1;
        System.out.println("a1的哈希值"+a1.hashCode());
        System.out.println("a2的哈希值"+a2.hashCode());
        System.out.println("a3的哈希值"+a3.hashCode());

        //可以发现,a1和a3是同一个对象,所以他们指向同一个对象,哈希值也就是相同的
        //a1和a2则是new的两个不同的对象,因此他们的哈希值也就是不相同的 
    }
}

class A1{ }

可以发现,a1和a3是同一个对象,所以他们指向同一个对象,哈希值也就是相同的
a1和a2则是new的两个不同的对象,因此他们的哈希值也就是不相同的

toString方法

基本介绍:
默认返回:全类名+@+哈希值的十六位进制,子类往往会重写toString方法,用于返回对象属性信息
重写toString方法,打印对象或拼接对象时,都会自动调用该对象的听toString方法

当直接输出一个对象的时候,toString方法会被默认的调用
例如:sout(m1),等价于--->m1.toString()

我们直接来举个栗子

package javaSEStudy.enum_Object.ObjectTest;

public class ToString {
    public static void main(String[] args) {
        /*
        Object类toString方法的源码
        getClass().getName():全类名,包名+类名
        Integer.toHexString(hashCode()):将对象的哈希值转换为十六进制的字符串
            public String toString() {
                 return getClass().getName() + "@" + Integer.toHexString(hashCode());
            }
         */

        Monster m1 = new Monster("小旋风","巡山",1000.00);
        System.out.println(m1.toString());
        System.out.println("=========================");
        System.out.println(m1);
        //以上两种输出方法输出结果是相同的
    }
}

class Monster{
    private String name;
    private String job;
    private double salary;

    public Monster(String name, String job, double salary) {
        this.name = name;
        this.job = job;
        this.salary = salary;
    }
    //重写toString方法,输出对象的属性
    //使用快捷键即可 alt+ ins


    //重写后,一般是把对象的属性输出
    //当然也可以自己定制
    @Override
    public String toString() {
        return
                "name='" + name + '\'' +
                ", job='" + job + '\'' +
                ", salary=" + salary;
    }
}

Finalize方法

PS:在 JDK9+ 中,该方法已被废弃
使用 Cleaner 和 PhantomReference (Java 9+)

  • 1.当对象被回收时,系统会自动调用该对象的finalize方法,子类可以重写该方法,做出一些释放资源的操作
  • 2.什么时候被回收:当某个对象没有被任何引用时,则JVM就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象之前,会先调用finalize方法
  • 3.垃圾回收机制的调用,是由系统决定的,也可以通过system.gc()主动触发垃圾回收机制
package javaSEStudy.enum_Object.ObjectTest;

public class Finalize {
    public static void main(String[] args) {
        Car car = new Car("宝马");
        //这个时候car就是一个垃圾对象,因为他没有任何的引用
        //垃圾回收器就会开始销毁这个对象,堆的空间就会被释放,别人可以使用了
        //在销毁对象前,就会调用该对象的finalize方法
        //此时就可以在finalize写自己的代码(释放资源:数据库连接,打开文件)
        //如果未重写finalize,就会调用Object的finalize,即默认处理
        //如果重写了finalize方法,就可以实现自己的逻辑
        car = null;

        //主动调用垃圾回收处理器,但是可不是百分百,得看系统处理逻辑
        System.gc();

        System.out.println("程序退出了");

        /*
        垃圾回收机制由系统控制,什么时候得等系统控制,所以这里看不到重写的方法输出
        使用system.gc主动触发垃圾回收机制
         */
    }
}

class Car{
    private String name;
    //属性也是资源

    public Car(String name) {
        this.name = name;
    }


    //重写finalize
    @Override
    protected void finalize() throws Throwable {
        System.out.println("我们销毁汽车" + name);
        System.out.println("释放资源");
    }
}

clone

一、clone()方法基础概念

  1. 方法定义与作用
    clone()是Java中Object类的一个protected方法,其定义为:
protected native Object clone() throws CloneNotSupportedException;

核心作用:创建并返回当前对象的一个副本(浅拷贝)。这是Java提供的对象复制机制的基础实现。

  1. 方法特性
  • native实现:实际克隆操作由JVM本地代码完成
  • protected访问权限:限制直接外部调用,需子类显式重写
  • 可能抛出CloneNotSupportedException:当对象不支持克隆时抛出

二、克隆机制实现原理

  1. 浅拷贝(Shallow Copy)与深拷贝(Deep Copy)
类型 特点 示意图
浅拷贝 复制对象本身及所有基本类型字段,引用类型字段复制引用地址 [原始对象]--->[引用对象]
深拷贝 完全复制对象及其引用的所有对象,创建全新对象图 [原始对象]-->[新对象]-->[新引用对象]
  1. 实现克隆的必要条件
  2. 实现Cloneable接口:标记接口,无方法定义
  3. 重写clone()方法:通常提升为public访问权限
  4. 调用super.clone():确保正确初始化

三、克隆实现的具体方式

  1. 基本实现模板
class MyClass implements Cloneable {
    private int value;
    private Object ref;
    
    @Override 
    public MyClass clone() throws CloneNotSupportedException {
        return (MyClass) super.clone(); // 浅拷贝
    }
}
  1. 深拷贝实现方案
class DeepCloneExample implements Cloneable {
    private int[] data;
    
    @Override 
    public DeepCloneExample clone() throws CloneNotSupportedException {
        DeepCloneExample copy = (DeepCloneExample) super.clone();
        copy.data = data.clone(); // 数组单独克隆
        return copy;
    }
}

四、克隆使用的关键问题

  1. 典型问题与解决方案
问题类型 表现 解决方案
可变对象共享 克隆对象与原对象共享可变引用 实现深拷贝
final字段冲突 final字段无法在clone后重新赋值 重新设计或放弃final
继承链问题 父类clone可能破坏子类约束 谨慎设计继承关系
  1. 性能考量
  • 浅拷贝:性能高,适合简单对象
  • 深拷贝:递归克隆开销大,复杂对象需谨慎
  • 替代方案:序列化/反序列化、拷贝构造器、静态工厂方法

五、克隆最佳实践与替代方案

  1. 使用建议

  2. 优先考虑不可变对象:避免克隆需求

  3. 明确文档说明:注明克隆行为(浅/深拷贝)

  4. 考虑线程安全:克隆过程不应破坏原子性

  5. 现代替代方案

// 1. 拷贝构造器
public MyClass(MyClass original) { /*...*/ }
 
// 2. 静态工厂方法 
public static MyClass newInstance(MyClass original) { /*...*/ }
 
// 3. 序列化方案 
public static <T> T deepClone(T obj) throws IOException, ClassNotFoundException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try(ObjectOutputStream oos = new ObjectOutputStream(baos)) {
        oos.writeObject(obj);
    }
    try(ObjectInputStream ois = new ObjectInputStream(
        new ByteArrayInputStream(baos.toByteArray()))) {
        return (T) ois.readObject();
    }
}

六、特殊场景处理

  1. 数组克隆
    Java数组天然支持clone():
int[] arr = {1, 2, 3};
int[] copy = arr.clone(); // 独立副本 
  1. 集合克隆
    标准集合类需特殊处理:
List<String> original = new ArrayList<>();
List<String> copy = new ArrayList<>(original); // 构造器方案 

总结:Object.clone()是Java对象复制的底层机制,正确使用需要理解其浅拷贝本质及实现要求。在现代Java开发中,往往推荐使用更直观的替代方案(如拷贝构造器),除非有明确的性能需求或遗留系统兼容要求。

getClass

一、基础定义与核心特性

  1. 方法原型
public final native Class<?> getClass();
  1. 关键特性
  • final方法:禁止子类重写,确保所有Java对象行为一致
  • native实现:由JVM直接提供底层支持
  • 运行时类型识别(RTTI):动态获取对象实际类型信息
  • 泛型返回:返回Class<?>类型,需配合强制类型转换使用

二、运行时类型系统原理

  1. JVM层实现机制

  2. 对象头存储:每个Java对象头部包含指向方法区中Class元数据的指针

  3. 方法区结构:

    • 类型继承关系表
    • 方法代码指针
    • 字段偏移量表
  4. 动态链接:通过invokevirtual指令实现多态调用

  5. 类型信息生命周期

阶段 行为 示例
类加载 Class对象创建 Class.forName()
对象实例化 对象头写入Class指针 new MyClass()
方法调用 通过Class指针定位方法 obj.toString()
GC回收 Class对象作为GC Roots 类卸载时

三、核心使用场景与示例

  1. 基础类型判断
Object obj = "Hello";
Class<?> clazz = obj.getClass();  // 返回java.lang.String 
  1. 反射操作入口
Method[] methods = "".getClass().getDeclaredMethods();
Field field = obj.getClass().getField("value");
  1. 类型安全比较
void process(Object input) {
    if(input.getClass() == Expected.class) {
        // 精确类型匹配 
    }
}
  1. 泛型类型擦除补偿
List<String> list = new ArrayList<>();
Class<?> actualType = list.getClass();  // 返回ArrayList 

四、高级特性与特殊案例

  1. 数组类型处理
int[] arr = new int[10];
Class<?> arrayClass = arr.getClass();  // 返回int[].class 
System.out.println(arrayClass.getComponentType());  // 输出int 
  1. 匿名类与Lambda
Runnable r = () -> {};
Class<?> lambdaClass = r.getClass();  // 返回JVM生成的合成类 
  1. 原始类型与包装类
Integer i = 1;
Class<?> boxedClass = i.getClass();  // 返回java.lang.Integer 
// int.class 需通过Integer.TYPE获取 

五、性能考量与最佳实践

  1. 性能对比
操作 耗时(相对值) 适用场景
getClass() 1x 需要精确类型时
instanceof 0.8x 类型兼容判断
反射操作 100x+ 动态功能扩展
  1. 设计建议
  2. 避免过度反射:编译时类型优于运行时类型检查
  3. 类型系统设计:优先使用接口/抽象类而非具体类判断
  4. null安全处理:
    Class<?> clazz = (obj != null) ? obj.getClass() : null;
    

六、底层扩展与JVM规范

  1. HotSpot实现细节
  • Klass指针:对象头中压缩指针优化
  • 类元数据:方法区(Metaspace)中的Klass结构体
  • 快速路径:JIT编译时的去虚拟化优化
  1. JVMS规范要求
  • 一致性保证:同一类的所有对象返回相同Class引用
  • 初始化触发:getClass()不会导致类初始化
  • 数组特殊性:数组类型的Class对象由JVM自动创建

总结:getClass()是Java类型系统的基石方法,其设计体现了Java"编译时严格,运行时灵活"的类型哲学。正确理解其实现原理和适用场景,对编写健壮的类型敏感代码至关重要。在Java 17+的模块化环境中,还需注意Class对象访问与模块权限的交互。

posted @ 2025-05-14 17:40  sprint077  阅读(23)  评论(0)    收藏  举报