泛型Generic
定义
- Java在1.5之后加入了泛型的概念。泛型,即“参数化类型”。
- 泛型的本质是为了参数化类型(将类型参数化传递)(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,
- 这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
- 泛型只能是对象类型,比如User,Student,内置对象类型比如String,Integer[一定是包装类型]
//jdk5.0之前
List list = new ArrayList();//集合中可以添加任意类型的数据
//jdk5.0开始~jdk7.0之前
//类型安全的集合框架
List<Integer> list - new ArrayList<Integer>();
//在编译期间确定了集合中添加的数据的类型,只能是Integer类型.
//jdk7.0开始
//结论:泛型只有编译期间的概念,在运行期间将会被擦除.
List<Integer> list = new ArrayList<>();
泛型符号
- E 元素
- K,V - 键值对
- N - 数字
- T - 类型
- ? - 通配符
为何要有泛型呢
对别一下没有泛型,会导致什么结果.
- 加入了泛型之后,可以保证代码的健壮性
- 加入了泛型之后,取值的时候,不需要进行强制类型的转换
- 加入了泛型之后,代码会变得更加简洁
package tech.aistar.day14; import java.util.ArrayList; import java.util.List; /** * 本类用来演示: 泛型的好处 * * @author: success * @date: 2021/8/7 9:05 上午 */ public class GenericDemo { public static void main(String[] args) { //jdk5.0之前 List list = new ArrayList();//集合中可以添加任意类型的数据 list.add(10);//10->java.lang.Integer list.add("ok");//字符串 //没有泛型的时候,获取集合中的元素 - 统计的返回类型都是Object类型 //需要进行强制类型的转换.转换的过程中有可能会抛出java.lang.ClassCastException类型转换失败异常 //在类型转换之前,建议先进行类型的判断,使用instanceof关键字 String ok = (String) list.get(1); System.out.println(ok); //jdk7.0开始 //一旦指定了泛型,add方法中的E成了String类型,返回类型也是String类型 List<String> strList = new ArrayList<>(); strList.add("ok"); strList.add("java"); //根据下标获取 - 不需要进行类型的强制转换了.自动能够识别出返回类型就是你指定的那个泛型. String result = strList.get(1); System.out.println(result); } }
泛型只有编译期间的概念,在运行期间将会被擦除.
结论:泛型是没有多态的
泛型只有编译期间的概念 - 泛型仅仅是在编译期间是有效的
在编译期间一旦确定了泛型,那么在编译期间就只能向这个容器中添加对应类型的数据.
否则编译报错.
运行期间将会被擦除 - 泛型是不存在运行时类型的
package tech.aistar.day14; import tech.aistar.day05.User; import java.util.ArrayList; import java.util.List; /** * 本类用来演示: 泛型只有编译期间的概念,在运行期间将会被擦除. * * @author: success * @date: 2021/8/7 9:16 上午 */ public class GenericRuntimeDemo { public static void main(String[] args) { //多态的语法 - 多态的应用 //面向父类编程/面向接口编程 //编译时类型 对象名 = new 运行时类型(); //父类 对象 = new 子类(); //接口 对象 = new 实现类(); //java.lang.Integer extends java.lang.Number //java.lang.Long extends java.lang.Number Number n1 = new Integer(10); Number n2 = new Long(20L); //jdk5.0~jdk7.0之前 List<Integer> list01 = new ArrayList<Integer>(); //思考 - 编译是错误的 //结论:泛型是没有多态的 //原因:泛型只有编译期间的概念,在运行期间将会被擦除. //List<Number> numberList = new ArrayList<Integer>(); List<Long> list02 = new ArrayList<Long>(); //获取对象的运行时类型 //忘记了 - java.lang.Object中的toString方法 getClass().getName()+"@"+Integer.toHexString(hashCode()); //Class实例 - 别名:对象的运行时类型 Class<?> c1 = list01.getClass(); Class<?> c2 = list02.getClass(); System.out.println(c1 == c2);//true //结论:获取运行时类型始终都是ArrayList,和<Integer>和<Long>是无关的 //所以泛型仅仅是在编译期间有效,但是在运行期间是无效的[将会被擦除,将会失效] System.out.println(c1);//class java.util.ArrayList System.out.println(c2);//class java.util.ArrayList
//结论:一个类无论被实例化多少次,它在JVM中的Class对象/实例永远只有1个
// User user1 = new User();
// System.out.println(user1.getClass());//class tech.aistar.day05.User
//
// User user2 = new User();
// System.out.println(user2.getClass());//class tech.aistar.day05.User
//
// System.out.println(user1.getClass() == user2.getClass());//true
}
}
泛型通配符
**? extends T - 只能是T类型或者T类型的子类 **- 指定类型的上限
package tech.aistar.day14; import java.util.*; /** * 本类用来演示: 问号的通配符 * * @author: success * @date: 2021/8/7 9:45 上午 */ public class WildCardDemo { public static void main(String[] args) { //只能添加添加null // List<?> list = new ArrayList<>(); // //list.add(10);//error // //list.add("ok");//error // list.add(null);//ok List<Integer> intList = new ArrayList<>(); intList.add(10); intList.add(20); intList.add(30); List<Long> longList = new ArrayList<>(); longList.add(100L); longList.add(200L); longList.add(300L); //能否定义一个方法,能够打印上面俩个集合 printList(intList); System.out.println("====="); printList(longList); } //泛型是不存在多态的. //假设认为是List<Number> list = new ArrayList<Long>();//error //Integer和Long都是extends Number //? extends T - 只能是T类型或者T类型的子类 - 指定类型的上限 //? super T - 只能是T类型或者T类型的父类 - 指定类型的下限 public static void printList(List<? extends Number> list){ //推荐使用集合的迭代器 //获取集合的迭代器对象 Iterator<? extends Number> iter = list.iterator(); while(iter.hasNext()){ //Integer,Long -> extends->Number->多态的语法 Number result = iter.next(); System.out.println(result); } } }
? super T - 只能是T类型或者T类型的父类 - 指定类型的下限
泛型类
定义类的时候,给定一个泛型,真正使用的时候,再确定具体的类型
package tech.aistar.day14; /** * 本类用来演示: 泛型类 * * @author: success * @date: 2021/8/7 10:11 上午 */ public class GenericClassDemo<T> { //定义属性 private T type; public GenericClassDemo() { } //将类型参数化传递 public GenericClassDemo(T type) { this.type = type; } //泛型方法 public T getType() { return type; } //将类型参数化传递 public void setType(T type) { this.type = type; } public static void main(String[] args) { //测试泛型类 GenericClassDemo<String> c1 = new GenericClassDemo<>("python"); c1.setType("java"); String result = c1.getType(); System.out.println("result:"+result); System.out.println("===华丽丽的分割线==="); GenericClassDemo<Integer> c2 = new GenericClassDemo<>(); c2.setType(10); System.out.println(c2.getType()); } }
泛型方法
如果泛型加在类上面,对整个类内部的泛型的地方都会有影响
可能某个类中就那么几个方法需要使用到泛型,没有必要定义泛型类,只需要定义泛型方法即可.
如果若干个很多个方法都使用到了泛型,有必要定义一个泛型类.
package tech.aistar.day14; /** * 本类用来演示: 泛型方法 * * @author: success * @date: 2021/8/7 10:17 上午 */ public class GenericMethodDemo { //非静态方法 - 无返回类型 public <T> void test01(T t){ System.out.println("test01..."+t); } //非静态方法 - 带返回类型 public <E> E test02(E e){ System.out.println("e:"+e); return e; } //静态方法 public static <N> N test03(N n){ System.out.println("n:"+n); return n; } public static void main(String[] args) { GenericMethodDemo m = new GenericMethodDemo(); //如何确定到方法的参数T //JVM根据传入的方法的实参 - 拿到这个实参的类型 -> 方法的参数T m.test01(10);//Integer m.test01("ok");//ok //调用带有返回类型 String ok = m.test02("ok"); System.out.println(ok); Integer t = m.test02(100); System.out.println(t); System.out.println("===="); //泛型是类型安全的 Integer s = GenericMethodDemo.test03(12); System.out.println(s); } }
泛型应用
ObjectInputStream/ObjectOuputStream - 操作对象类型的字节文件输入流和对象类型的文件字节输出流
Object in.readObject()/out.writeObject(Object obj);
Jdk没有对readObject方法进行优化,每次调用完之后,进行强制类型的转换操作的.
思考 - 无论是保存/读取User对象或者Book对象 - 用一个方法去实现
package tech.aistar.util;
import tech.aistar.day13.Book;
import tech.aistar.day14.Product;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 本类用来演示: IO流的工具类
*
* @author: success
* @date: 2021/8/7 10:59 上午
*/
public class IOUtil {
//提供一个方法可以保存任意对象
public static <T> void writeList(List<T> list,String path){
try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(path))){
out.writeObject(list);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//提供一个方法可以读取文件中的任意对象
//语法如果定义成了一个泛型类,并且是静态方法的话,那么必须要把这个静态方法也设置成泛型方法
//如果是定义成了一个泛型类,但是是普通方法的话,那么这个普通方法是不需要设置成泛型方法的
public static <T> List<T> readList(String path){
List<T> list = null;
try(ObjectInputStream in = new ObjectInputStream(new FileInputStream(path))){
list = (List<T>) in.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return list;
}
}
class IOUtilTest{
public static void main(String[] args) {
Book b1 = new Book(1,"1001","python",100.0d);
Book b2 = new Book(2,"1002","java",100.0d);
List<Book> books = new ArrayList<>();
books.add(b1);
books.add(b2);
String path = "src/tech/aistar/util/content.txt";
//写入
//IOUtil.writeList(books,path);
//读
// List<Book> list = IOUtil.readList(path);
// if(null!=list && list.size()>0){
// for (Book book : list) {
// System.out.println(book);
// }
// }
System.out.println("======");
Product p1 = new Product(1,"macbook");
Product p2 = new Product(2,"linux");
List<Product> products = new ArrayList<>();
products.add(p1);
products.add(p2);
//IOUtil.writeList(products,path);
List<Product> ps = IOUtil.readList(path);
if(null!=ps && ps.size()>0){
for (Product p : ps) {
System.out.println(p);
}
}
}
}
泛型类的继承
- 子类不指定具体的类型
- 子类指定具体的类型
demo
package tech.aistar.day14;
/**
* 本类用来演示: 泛型类的继承
*
* @author: success
* @date: 2021/8/9 8:34 上午
*/
public class GenericExtendsDemo {
}
//泛型父类
class Sup<T>{
}
//子类继承父类的时候,没有指定具体的类型
class Sub<T> extends Sup<T>{
}
//子类继承父类的时候,指定了具体的类型
class Sub01<Product> extends Sup<Product>{
}
泛型的具体应用
了解一下即可
场景:在接口的制定中.很多接口具有相同的或者类似的功能.比如
//dao层接口 - data access object - 数据访问对象层 - 数据持久层 //这一层专门和数据库[crud增删改查操作][文件_IO流操作]打交道 //教师的业务接口 public interface ITeacherDao{ //保存教师 void save(Teacher teacher); } //学生的业务接口 public interface IStudentDao{ // 保存学生 void save(Student student); } //再去写俩个具体的实现类分别是TeacherDaoImpl.java以及StudentDaoImpl.java //这俩个实现类中 - 具体的代码,现阶段知识点 - 把单个java对象保存到文件中[IO流技术]. //未来 - 肯定是要把java对象保存到db中 - 持久化的操作[java内存中的对象保存到DB中] //关于dao层 - java如何操作数据库 - 原生技术jdbc,dao层框架 - Hibernate框架以及mybatis框架 //比如现在选取的是所谓的Hibernate框架
Hibernate框架来把java的内存对象保存到DB中
session.beginTransaction();//开启一个事务 session.save(user);//具体的调用的是这个框架中的保存方法 session.getTransaction().commit();//提交一个事务
TeacherDaoImpl.java
伪代码
public class TeacherDaoImpl implements ITeacherDao{
@Override
public void save(Teacher teacher){
session.beginTransaction();//开启一个事务
session.save(teacher);//具体的调用的是这个框架中的保存方法
session.getTransaction().commit();//提交一个事务
}
}
StudentDaoImpl.java
public class StudentDaoImpl implements IStudentDao{
@Override
public void save(Student student){
session.beginTransaction();//开启一个事务
session.save(student);//具体的调用的是这个框架中的保存方法
session.getTransaction().commit();//提交一个事务
}
}
发现在未来使用框架的时候,会发现很多步骤都是重复的.可能就涉及到具体的对象那一行的代码才会不一样而已
session.save(具体的java对象);
考虑抽象出一个顶级的业务接口出来-IBaseDao<T>
/**
* 本类用来演示: 顶级的业务接口
*
* @author: success
* @date: 2021/8/9 8:50 上午
*/
public interface IBaseDao<T> {
//顶级的业务接口中应该存储的就是各个子接口中共性的方法
//这些方法都有共同点 - 大部分的代码是一样的,仅仅是操作的对象不一样而已.
void save(T t);
}
制定顶级的接口的实现类BaseDaoImpl.java
package tech.aistar.day14.app.dao;
/**
* 本类用来演示: 顶级接口的实现类
*
* @author: success
* @date: 2021/8/9 8:53 上午
*/
public class BaseDaoImpl<T> implements IBaseDao<T> {
@Override
public void save(T t) {
// session.beginTransaction();//开启一个事务
// session.save(t);//具体的调用的是这个框架中的保存方法
// session.getTransaction().commit();//提交一个事务
System.out.println(t);
}
}
IStudentDao.java继承了顶级的业务接口
package tech.aistar.day14.app.dao;
import tech.aistar.day14.app.Student;
/**
* 本类用来演示: 具体的业务接口去继承这个顶级的业务接口
*
* @author: success
* @date: 2021/8/9 8:52 上午
*/
public interface IStudentDao extends IBaseDao<Student>{
void taoKe();//子接口中特有的方法
}
ITeacherDao.java
继承了顶级的业务接口
package tech.aistar.day14.app.dao;
import tech.aistar.day14.app.Teacher;
/**
* 本类用来演示:
*
* @author: success
* @date: 2021/8/9 8:52 上午
*/
public interface ITeacherDao extends IBaseDao<Teacher>{
void buKe();//子接口中特有的方法
}
俩个具体的实现类 - 需要继承顶级的BaseDaoImpl.java - save方法已经实现好了.同时还需要各自实现自己的接口
package tech.aistar.day14.app.dao;
import tech.aistar.day14.app.Student;
/**
* 本类用来演示: 具体的学生接口的实现类
*
* @author: success
* @date: 2021/8/9 8:55 上午
*/
//泛型继承 - 指定了泛型类
public class StudentDaoImpl extends BaseDaoImpl<Student> implements IStudentDao{
@Override
public void taoKe() {
System.out.println("逃课...");
}
}
package tech.aistar.day14.app.dao;
import tech.aistar.day14.app.Teacher;
/**
* 本类用来演示:
*
* @author: success
* @date: 2021/8/9 9:02 上午
*/
public class TeacherDaoImpl extends BaseDaoImpl<Teacher> implements ITeacherDao{
@Override
public void buKe() {
System.out.println("补课...");
}
}
单元测试
package tech.aistar.day14.app.dao;
import tech.aistar.day14.app.Student;
import tech.aistar.day14.app.Teacher;
/**
* 本类用来演示:
*
* @author: success
* @date: 2021/8/9 8:56 上午
*/
public class TestBase {
public static void main(String[] args) {
Student s = new Student(1,"tom");
IStudentDao studentDao = new StudentDaoImpl();
studentDao.save(s);
Teacher teacher = new Teacher(1,"仓考试");
ITeacherDao teacherDao = new TeacherDaoImpl();
teacherDao.save(teacher);
}
}
枚举类型
枚举常量 - 类型安全的常量 - 公共的静态的常量[final]属性 - 不可变的
jdk5.0开始提供的,以前的作用就是用来替代常量接口的
回忆常量接口 - 管理和维护项目中所有的常量的
public interface IConsts{ //最全的写法 - 接口中只能出现公开的静态的常量属性 //public static final int Car = 1; //精简的写法 //public int CAR = 1; //最精简的写法 int CAR = 1; }
关键字
使用enum关键字来定义一个枚举类型的
switch()中的参数类型可以是byte,short,int,char,enum,String,Byte,Short,Integer
特点
枚举常量,多个枚举常量,使用,隔开.最后一个枚举常量不需要使用逗号了.
如果最后一个枚举常量下面还有代码的话,那么需要使用分号隔开
允许存在构造 - 访问修饰符不能是公开的,protected
枚举类型是不能够被实例化的
枚举类型中可以提供普通属性
每个枚举类型默认都会自动继承java.lang.Enum<E extends Enum
> 枚举类型中是可以存在抽象方法的,但是每个枚举常量必须要实现这个抽象方法
枚举类型不支持再去extends另外一个枚举类型
枚举类型不支持再去手动extends另外一个类
应用
如果某些属性具有固定的一些标识,就可以考虑定义成枚举类型.
比如:User实体类中维护了一个性别属性Gender[性别的枚举类型]
比如:Order实体类也会存在固定的状态 - 未付款,已付款.已下单未付款.取消订单等…
字符串->枚举常量
比如一个注册页面
用户名 tom 性别 单选按钮 男 单选按钮 女 确定按钮 未来.当点击确定按钮,后台接受到M,F - 后台接受到的数据都是字符串数据.
我们是不能够直接将这个字符串数据设置到实体类中的枚举常量属性上的.
每个自定义的枚举类型Gender.java默认都会继承java.lang.Enum<E extends Enum
> public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { }
package tech.aistar.day14.enums; /** * 本类用来演示: String->枚举类型 * * @author: success * @date: 2021/8/9 10:10 上午 */ public class StringToEnumDemo { public static void main(String[] args) { String gender = "F";//接受到页面传过来的数据 - 都是字符串类型 //字符串类型是不能够直接设置给实体类的那个枚举常量属性上的 //System.out.println(FString.class); // System.out.println(Integer.class); //Class<?> c = String.class; //System.out.println(c); //将字符串转换成枚举常量类型 //注意 - 字符串的值一定是和枚举常量的名称是保持一致的. //否则 - java.lang.IllegalArgumentException: // No enum constant tech.aistar.day14.enums.Gender.S Gender g = Enum.valueOf(Gender.class,gender); User user = new User(1,"tom",g); System.out.println(user); } }
枚举单例
它是effective java作者极力推荐的写法 - 枚举实例 - 1. 枚举类型天生就是线程安全的.2. 可以防止序列化或者反射来破坏这种单例的.
单例 - 保证在整个应用程序中,某个类的具体的实例永远只有1个.
什么时候需要把类做成单例的 - 这个类是一个重量级的类[类的创建和销毁的成本比较高.]
枚举常量是类型安全的常量???
public enum Gender{
F,M
}
因为使用枚举常量 - 第一步肯定是先加载枚举类型Gender - JVM通过类加载器[java.lang.ClassLoader]来加载枚举类型
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
//使用到了synchronized关键字
synchronized (getClassLoadingLock(name)) {
//.... 加载类,接口,枚举类型到JVM内存
}
}
很多线程都在调用Gender.M - > 都想尝试加载Gender枚举类型到JVM内存.但是由于loadClass加载的那块代码使用到了synchronized[锁]
只能由一个线程进去执行,并且只会执行一次.
由于每个枚举常量F 本质 就是 public static final Gender F = new Gender();//使用到了static关键字
//意味着当枚举类型一旦被加载了,枚举常量立即就会被分配空间以及初始化,并且机会只有一次.所以在整个内存中F常量也就只会存在一份.
//无论在哪里被调用.无论被多少给线程调用 - 大家拿到的都是同一个/同一份那个枚举常量.
具体的代码的实现
package tech.aistar.design.singleton.version04;
import java.io.Serializable;
/**
* 本类用来演示: 枚举单例实现方式
*
* @author: success
* @date: 2021/8/9 10:45 上午
*/
public class Singleton04 implements Serializable {
private Singleton04(){
System.out.println("私有化构造");
}
//内部的枚举类型
private enum SingletonEnum{
//枚举常量的实例
INSTANCE;//public static final SingletonEnum INSTANCE = new SingletonEnum();
//final修饰的变量要赋值
private final Singleton04 instance;
//枚举类型提供一个空参构造 - 枚举类型是不能new的
SingletonEnum(){
instance = new Singleton04();
}
//提供一个普通方法
public Singleton04 getInstance(){
return instance;
}
}
//外部类肯定是要提供一个方法,返回自己的一个唯一实例的
public static Singleton04 getInstance(){
return SingletonEnum.INSTANCE.getInstance();
}
}
class TestSingle04{
public static void main(String[] args) {
System.out.println(s1 == s2);//true
}
}
枚举单例写法
/**
* 本类用来演示: 枚举单例 - 最精简的写法 - 多线程安全的写法 - 饿汉模式
只有最外层的是枚举类型 - 序列化这个枚举类型的单例,才能阻止序列化破坏单例的模式
*
* @author: success
* @date: 2021/8/9 11:11 上午
*/
public enum Singleton05 {
//public static final Singleton05 INSTANCE = new Singleton05();
INSTANCE;
Singleton05(){
System.out.println("比较繁琐的操作的事情,费时费力的事情");
}
public static Singleton05 getInstance(){
return INSTANCE;
}
}
对比饿汉模式的写法
public class Singleton01 {
//2. 初始化一个变量,该变量就是该类的唯一实例[对象]
private static Singleton01 instance = new Singleton01();
//1. 私有化构造
private Singleton01(){
//比较费时费力的代码,可能需要更多的时间
System.out.println("Singleton01...");
}
//3. 提供一个公开的静态的方法来返回这个类的唯一实例
public static Singleton01 getInstance(){
return instance;
}
}
枚举单例阻止序列化的破坏
- 序列化单例对象s1
- 反序列化封装到单例对象s3
- s1 == s3 ;// 仍然为true
package tech.aistar.design.singleton.version05; import tech.aistar.day14.enums.Gender; import java.io.*; /** * 本类用来演示: 枚举单例 - 最精简的写法 * * @author: success * @date: 2021/8/9 11:11 上午 */ public enum Singleton05 { //public static final Singleton05 INSTANCE = new Singleton05(); INSTANCE; Singleton05(){ System.out.println("比较繁琐的操作的事情,费时费力的事情"); } public static Singleton05 getInstance(){ return INSTANCE; } } class TestSingleton05{ public static void main(String[] args) { Singleton05 s1 = Singleton05.getInstance(); Singleton05 s2 = Singleton05.getInstance(); System.out.println(s1 == s2);//true //序列化 try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("src/tech/aistar/design/ss.txt"))){ //对单例进行序列化操作 out.writeObject(s1); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //反序列化 try(ObjectInputStream in = new ObjectInputStream(new FileInputStream("src/tech/aistar/design/ss.txt"))){ Singleton05 s3 = (Singleton05) in.readObject(); System.out.println(s1 == s3);//true } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
枚举细节 - 抽象方法
枚举类型中是可以存在抽象方法的,但是每个枚举常量必须要实现这个抽象方法
package tech.aistar.day14.enums; /** * 本类用来演示: 季节的枚举类型 * * @author: success * @date: 2021/8/9 1:43 下午 */ public enum Season{ //枚举类型中是可以存在抽象方法的,但是每个枚举常量必须要实现这个抽象方法 SPRING("春天"){ @Override public Season next() { return Season.SUMMER; } }, SUMMER("夏天") { @Override public Season next() { return Season.AUTUMN; } }, AUTUMN("秋天") { @Override public Season next() { return Season.WINTER; } }, WINTER("冬天") { @Override public Season next() { return Season.SPRING; } }; private String sign; Season(){ } Season(String sign) { this.sign = sign; } public String getSign() { return sign; } //提供一个抽象方法 - 但是要求所有的枚举常量都要重写这个抽象方法 public abstract Season next(); } class TestSeason{ public static void main(String[] args) { Season s = Season.SPRING; for(;;){ System.out.println(s+"-"+s.getSign()); try { //让程序睡一秒钟 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } s = s.next(); } } }