枚举及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()方法基础概念
- 方法定义与作用
clone()是Java中Object类的一个protected方法,其定义为:
protected native Object clone() throws CloneNotSupportedException;
核心作用:创建并返回当前对象的一个副本(浅拷贝)。这是Java提供的对象复制机制的基础实现。
- 方法特性
- native实现:实际克隆操作由JVM本地代码完成
- protected访问权限:限制直接外部调用,需子类显式重写
- 可能抛出CloneNotSupportedException:当对象不支持克隆时抛出
二、克隆机制实现原理
- 浅拷贝(Shallow Copy)与深拷贝(Deep Copy)
| 类型 | 特点 | 示意图 |
|---|---|---|
| 浅拷贝 | 复制对象本身及所有基本类型字段,引用类型字段复制引用地址 | [原始对象]--->[引用对象] |
| 深拷贝 | 完全复制对象及其引用的所有对象,创建全新对象图 | [原始对象]-->[新对象]-->[新引用对象] |
- 实现克隆的必要条件
- 实现Cloneable接口:标记接口,无方法定义
- 重写clone()方法:通常提升为public访问权限
- 调用super.clone():确保正确初始化
三、克隆实现的具体方式
- 基本实现模板
class MyClass implements Cloneable {
private int value;
private Object ref;
@Override
public MyClass clone() throws CloneNotSupportedException {
return (MyClass) super.clone(); // 浅拷贝
}
}
- 深拷贝实现方案
class DeepCloneExample implements Cloneable {
private int[] data;
@Override
public DeepCloneExample clone() throws CloneNotSupportedException {
DeepCloneExample copy = (DeepCloneExample) super.clone();
copy.data = data.clone(); // 数组单独克隆
return copy;
}
}
四、克隆使用的关键问题
- 典型问题与解决方案
| 问题类型 | 表现 | 解决方案 |
|---|---|---|
| 可变对象共享 | 克隆对象与原对象共享可变引用 | 实现深拷贝 |
| final字段冲突 | final字段无法在clone后重新赋值 | 重新设计或放弃final |
| 继承链问题 | 父类clone可能破坏子类约束 | 谨慎设计继承关系 |
- 性能考量
- 浅拷贝:性能高,适合简单对象
- 深拷贝:递归克隆开销大,复杂对象需谨慎
- 替代方案:序列化/反序列化、拷贝构造器、静态工厂方法
五、克隆最佳实践与替代方案
-
使用建议
-
优先考虑不可变对象:避免克隆需求
-
明确文档说明:注明克隆行为(浅/深拷贝)
-
考虑线程安全:克隆过程不应破坏原子性
-
现代替代方案
// 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();
}
}
六、特殊场景处理
- 数组克隆
Java数组天然支持clone():
int[] arr = {1, 2, 3};
int[] copy = arr.clone(); // 独立副本
- 集合克隆
标准集合类需特殊处理:
List<String> original = new ArrayList<>();
List<String> copy = new ArrayList<>(original); // 构造器方案
总结:Object.clone()是Java对象复制的底层机制,正确使用需要理解其浅拷贝本质及实现要求。在现代Java开发中,往往推荐使用更直观的替代方案(如拷贝构造器),除非有明确的性能需求或遗留系统兼容要求。
getClass
一、基础定义与核心特性
- 方法原型
public final native Class<?> getClass();
- 关键特性
- final方法:禁止子类重写,确保所有Java对象行为一致
- native实现:由JVM直接提供底层支持
- 运行时类型识别(RTTI):动态获取对象实际类型信息
- 泛型返回:返回
Class<?>类型,需配合强制类型转换使用
二、运行时类型系统原理
-
JVM层实现机制
-
对象头存储:每个Java对象头部包含指向方法区中Class元数据的指针
-
方法区结构:
- 类型继承关系表
- 方法代码指针
- 字段偏移量表
-
动态链接:通过
invokevirtual指令实现多态调用 -
类型信息生命周期
| 阶段 | 行为 | 示例 |
|---|---|---|
| 类加载 | Class对象创建 | Class.forName() |
| 对象实例化 | 对象头写入Class指针 | new MyClass() |
| 方法调用 | 通过Class指针定位方法 | obj.toString() |
| GC回收 | Class对象作为GC Roots | 类卸载时 |
三、核心使用场景与示例
- 基础类型判断
Object obj = "Hello";
Class<?> clazz = obj.getClass(); // 返回java.lang.String
- 反射操作入口
Method[] methods = "".getClass().getDeclaredMethods();
Field field = obj.getClass().getField("value");
- 类型安全比较
void process(Object input) {
if(input.getClass() == Expected.class) {
// 精确类型匹配
}
}
- 泛型类型擦除补偿
List<String> list = new ArrayList<>();
Class<?> actualType = list.getClass(); // 返回ArrayList
四、高级特性与特殊案例
- 数组类型处理
int[] arr = new int[10];
Class<?> arrayClass = arr.getClass(); // 返回int[].class
System.out.println(arrayClass.getComponentType()); // 输出int
- 匿名类与Lambda
Runnable r = () -> {};
Class<?> lambdaClass = r.getClass(); // 返回JVM生成的合成类
- 原始类型与包装类
Integer i = 1;
Class<?> boxedClass = i.getClass(); // 返回java.lang.Integer
// int.class 需通过Integer.TYPE获取
五、性能考量与最佳实践
- 性能对比
| 操作 | 耗时(相对值) | 适用场景 |
|---|---|---|
getClass() |
1x | 需要精确类型时 |
instanceof |
0.8x | 类型兼容判断 |
| 反射操作 | 100x+ | 动态功能扩展 |
- 设计建议
- 避免过度反射:编译时类型优于运行时类型检查
- 类型系统设计:优先使用接口/抽象类而非具体类判断
- null安全处理:
Class<?> clazz = (obj != null) ? obj.getClass() : null;
六、底层扩展与JVM规范
- HotSpot实现细节
- Klass指针:对象头中压缩指针优化
- 类元数据:方法区(Metaspace)中的Klass结构体
- 快速路径:JIT编译时的去虚拟化优化
- JVMS规范要求
- 一致性保证:同一类的所有对象返回相同Class引用
- 初始化触发:
getClass()不会导致类初始化 - 数组特殊性:数组类型的Class对象由JVM自动创建
总结:getClass()是Java类型系统的基石方法,其设计体现了Java"编译时严格,运行时灵活"的类型哲学。正确理解其实现原理和适用场景,对编写健壮的类型敏感代码至关重要。在Java 17+的模块化环境中,还需注意Class对象访问与模块权限的交互。
浙公网安备 33010602011771号