Java基础知识(17)- Java 泛型 (Generics)、序列化(Serialize)
1. 泛型 (Generics)
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型本质上是参数化类型,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
1)泛型方法
可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
(1) 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前;
(2) 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符;
(3) 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符;
(4) 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等);
Java 中泛型标记符:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
?- 表示不确定的 java 类型
2) 泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
3) 类型通配符
(1) 无界通配符 <?>
不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 ),表示可以持有任何类型。
类型通配符一般是使用 ? 代替具体的类型参数。
例如 List<?> 在逻辑上是 List<String>,List<Integer> 等所有 List<具体类型实参> 的父类。
(2) 上界通配符 < ? extends E >
上界:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
a) 如果传入的类型不是 E 或者 E 的子类,编译不成功;
b) 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用;
类型参数列表中如果有多个类型参数上限,用逗号分开。
(3) 下界通配符 < ? super E >
下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object。
在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。
上界通配符主要用于读数据,下界通配符主要用于写数据。
(4) ?和 T 的区别
T 是一个确定的类型,通常用于泛型类和泛型方法的定义,?是一个不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。两者的区别:
a) 通过 T 来 确保 泛型参数的一致性;
b) 类型参数可以多重限定而通配符不行;
c) 通配符可以使用超类限定而类型参数不行;
实例:
1 public class App { 2 3 // 泛型方法 printArray 4 public static < E > void printArray( E[] inputArray ) { 5 6 for (E element : inputArray ) { 7 System.out.printf( "%s ", element ); 8 } 9 System.out.println(); 10 } 11 12 // 泛型方法, 上界通配符 < ? extends E > 13 public static <T extends Comparable<T>> T maximum(T x, T y, T z) { 14 T max = x; 15 if ( y.compareTo( max ) > 0 ) { 16 max = y; 17 } 18 if ( z.compareTo( max ) > 0 ) { 19 max = z; 20 } 21 return max; 22 } 23 24 public static void main( String[] args ) { 25 26 // Generics 27 28 // Generics - Integer, Double, Character 29 Integer[] intArray = { 1, 2, 3, 4, 5 }; 30 Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 }; 31 Character[] charArray = { 'C', 'h', 'a', 'r', 'a', 'c', 't', 'e', 'r' }; 32 33 System.out.println("Integer Array:"); 34 printArray(intArray); 35 System.out.println("Double Array:"); 36 printArray(doubleArray); 37 System.out.println("Character Array:"); 38 printArray(charArray); 39 40 // Generics 41 System.out.println(); 42 System.out.printf( "Max of %d, %d and %d is %d\n", 43 3, 4, 5, maximum( 3, 4, 5 ) ); 44 System.out.printf( "Maxm of %.1f,%.1f and %.1f is %.1f\n", 45 6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) ); 46 System.out.printf( "Max of %s, %s and %s is %s\n","pear", 47 "apple", "orange", maximum( "pear", "apple", "orange" ) ); 48 49 // Generics class 50 System.out.println(); 51 Round<Integer> integerRound = new Round<Integer>(); 52 Round<String> stringRound = new Round<String>(); 53 54 integerRound.add(new Integer(30)); 55 stringRound.add(new String("Round Test")); 56 57 System.out.printf("Integer Value :%d\n", integerRound.get()); 58 System.out.printf("String Value :%s\n", stringRound.get()); 59 } 60 } 61 62 // 泛型类 63 class Round<T> { 64 65 private T t; 66 67 public void add(T t) { 68 this.t = t; 69 } 70 71 public T get() { 72 return t; 73 } 74 75 }
输出:
Integer Array:
1 2 3 4 5
Double Array:
1.1 2.2 3.3 4.4
Character Array:
C h a r a c t e r
Max of 3, 4 and 5 is 5
Maxm of 6.6,8.8 and 7.7 is 8.8
Max of pear, apple and orange is pear
Integer Value :30
String Value :Round Test
2. 序列化 (Serialize)
Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
对象的序列化是将一个Java对象写入IO流(或文件);与此对应的,反序列化则是从IO流(或文件)中恢复一个Java对象。
要将一个java对象序列化,那么对象的类需要是可序列化的。要让类可序列化,那么这个类需要实现接口 Serializable 或 Externalizable。Externalizable 继承了 Serializable,该接口中定义了两个抽象方法:writeExternal() 与 readExternal()。
1) 实现 Serializable 的对象
创建一个 Employee 类,实现 Serializable 接口:
1 class Employee implements Serializable { 2 private String name = null; 3 transient private Integer age = null; 4 5 public Employee(String name, Integer age) { 6 this.name = name; 7 this.age = age; 8 } 9 10 @Override 11 public String toString() { 12 return "[" + name + ", " + age + "]"; 13 } 14 }
(1) 序列化
使用 ObjectOutputStream 类的 writeObject() 方法序列化对象,格式如下:
1 public class App { 2 public static void main( String[] args ) { 3 4 try { 5 File file = new File("employee.out"); 6 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file)); 7 Employee employee = new Employee("Tester", 25); 8 objectOutputStream.writeObject(employee); 9 objectOutputStream.close(); 10 } catch (IOException) { 11 e.printStackTrace(); 12 } 13 } 14 }
(2) 反序列化
使用 ObjectInputStream 类的 readObject() 方法反序列化对象,格式如下:
1 public class App { 2 public static void main( String[] args ) { 3 4 try { 5 File file = new File("employee.out"); 6 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file)); 7 Object newEmployee = objectInputStream.readObject(); // 没有强制转换到Person类型 8 objectInputStream.close(); 9 System.out.println(newEmployee); 10 } catch (IOException | ClassNotFoundException e) { 11 e.printStackTrace(); 12 } 13 } 14 }
输出:
[Tester, null]
(3) transient 关键字
当某个字段被声明为 transient 后,默认序列化机制就会忽略该字段。此处将 Person 类中的 age 字段声明为 transient,age 字段未被序列化。
2) 实现 Externalizable 的对象
当使用 Externalizable 接口来进行序列化与反序列化的时候,需要开发人员重写 writeExternal() 与 readExternal()。
创建一个 Employee2 类,实现 Externalizable 接口:
1 class Employee2 implements Externalizable { 2 private String name = null; 3 transient private Integer age = null; 4 5 public Employee2() { 6 7 } 8 9 public Employee2(String name, Integer age) { 10 this.name = name; 11 this.age = age; 12 } 13 14 @Override 15 public void writeExternal(ObjectOutput out) throws IOException { 16 out.writeObject(name); 17 out.writeInt(age); 18 } 19 20 @Override 21 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 22 name = (String) in.readObject(); 23 age = in.readInt(); 24 } 25 26 @Override 27 public String toString() { 28 return "[" + name + ", " + age + "]"; 29 } 30 }
(1) 序列化
使用 ObjectOutputStream 类的 writeObject() 方法序列化对象,格式如下:
1 public class App { 2 public static void main( String[] args ) { 3 4 try { 5 File file2 = new File("employee2.out"); 6 ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(new FileOutputStream(file2)); 7 Employee2 employee2 = new Employee2("Tester2", 32); 8 objectOutputStream2.writeObject(employee2); 9 objectOutputStream2.close(); 10 } catch (IOException) { 11 e.printStackTrace(); 12 } 13 } 14 }
(2) 反序列化
使用 ObjectInputStream 类的 readObject() 方法反序列化对象,格式如下:
1 public class App { 2 public static void main( String[] args ) { 3 4 try { 5 6 File file2 = new File("employee2.out"); 7 ObjectInputStream objectInputStream2 = new ObjectInputStream(new FileInputStream(file2)); 8 Object newEmployee2 = objectInputStream2.readObject(); 9 objectInputStream2.close(); 10 System.out.println(newEmployee2); 11 12 } catch (IOException | ClassNotFoundException e) { 13 e.printStackTrace(); 14 } 15 16 } 17 }
输出:
[Tester2, 32]
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)