java面向对象高级编程学习笔记
面向对象高级编程
类变量和类方法
类变量(静态变量)
- 特点
- 被该类所有对象共享
jdk7以后放在堆,以前放在方法区静态域? - static类变量,在类加载的时候生成。
- 访问
- 类名.类变量名
- 对象名.类变量名
- 细节
- 当某个类的所有对象都共享一个变量时,考虑使用类变量
- 类变量遵循访问修饰符限制的访问权限
- 类变量类加载时就创建了
4.类变量生命周期随着类的加载开始,随着类的消亡而销毁,与对象无关
类方法(静态方法)
使用同上
不创建实例也能使用,当作工具来使用,则可以将方法设计成静态方法,提高开发效率。
- 细节
- 类方法和普通方法 随类加载而加载,将结构信息存储在方法区;类方法中无this的参数,普通方法隐含着this参数
- 类方法中不允许使用和对象相关的关键字,比如 this, super
- 静态方法只能访问静态成员(静态变量和静态方法),普通成员可以访问所有成员。
main()主方法
- main()方法是虚拟机调用
- java虚拟机需要调用类的main()方法,所以该方法权限必须是public
- java虚拟机在执行main()时不必创建对象,所以必须是public
- 该方法一个接收String数组参数,该数组中保存执行java命令时传递给所运行的类的参数
- 比如:java 执行的程序 参数1 参数2 参数3
- 在main()方法中可以直接调用本类中的静态成员,不可以直接调用非静态成员
代码块
代码块又称为初始化块,属于类中的成员,属于类的一部分
和方法不同,它无方法名,无返回值,无参数,只有方法体,不用通过对象或类显式调用,而是加载类时隐式调用。
- 语法
【修饰符】{
代码
};
- 修饰符可选,但写只能写 static
- 代码块分两类
2.1 static修饰:静态代码块
2.2 无static修饰:非静态代码块/普通代码块
3.;可省略
- 相当另一种形式的构造器(堆构造器的补充机制),可以做初始化操作
- 如果多个构造器有重复语句,这时可以将重复语句抽离写在代码块里
细节
- static代码块,对类初始化,随类加载执行,并且只执行一次;
- 无static普通代码块,每创建一个对象执行一次
类什么时候加载(重要!!!!!!!!!!!!)
2.1 创建对象实例
2.2 创建子类对象实例,父类被加载(父类先加载,子类后加载)
2.3 直接使用类的静态成员(静态属性、静态方法)
4. 使用只是使用类的静态变量,不会调用普通代码块,而是创建对象时才调用,创建一次,调用一次。
创建一个对象时,在一个类 调用顺序是:(重点)
第一步
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态属性初始化,则按照定义顺序调用)
calss A{
//静态属性初始化
private static int n1 = getN();
static{//静态代码块
System.out.println("A静态代码块");
}
public static getN1(){
System.out.println("getN1调用");
return 100;
}
}
执行顺序:
getN1调用
A静态代码块
第二步
- 调用普通代码块和普通属性初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按照定义顺序调用)
calss A{
//普通属性初始化
private static int n2 = getN2();
{
System.out.println("A普通代码块");
}
static{//静态代码块 第 1 步
System.out.println("A静态代码块");
}
//静态属性初始化
private static int n1 = getN1(); //第 2 步
public static getN1(){
System.out.println("getN1调用");
return 100;
}
public int getN2(){
System.out.println("getN2调用");
return 200;
}
public A{
System.out.println("构造器调用");
}
}
执行顺序:
A静态代码块
getN1调用
getN2调用
A普通代码块
构造器调用
第三步
- 构造器调用
最后调用构造器
构造器调用
构造器前面隐含了 super()调用和普通代码块调用 (普通)
class A{
public A(){//构造器
//隐藏的执行要求
//隐藏部分(1)
super();
//隐藏部分(2)
//调用普通代码块
System.out.println("ok");
}
}
创建子类对象时,他们的静态代码块、普通代码块、静态变量初始划、普通变量初始化、构造方法调用顺序如下:
删除线格式
静态代码块只能调用静态成员,普通代码块可以使用任意成员
设计模式
单例设计模式
- 就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且只提供一个取得其对象实例的方法
- 饿汉式和懒汉式
- 饿汉式
(1) 构造器私有化(防止直接 new)
(2) 类的内部创建对象,不管用不用都创建
(3) 向外暴露一个公共的static方法返回一个实例对象
public class SingleTon {
public static void main(String[] args) {
Friend f = Friend.getInstance();
System.out.println(f);
}
}
class Friend{
private String name;
// 2. 在类内部直接创建对象
private static Friend f = new Friend("xiaohong");
// 1. 构造器私有化
private Friend(String name){
this.name = name;
}
// 3. 提供一个公共方法获得实例
public static Friend getInstance(){
return f;
}
}
- 懒汉式
(1) 构造器私有化
(2) 类内部创建对象,使用时才创建
(3) 向外暴露一个公共的static方法返回一个实例对象
public class Single01 {
public static void main(String[] args) {
//cat1 == cat2
Cat cat1 = Cat.getInstance();
System.out.println(cat1);
Cat cat2 = Cat.getInstance();
System.out.println(cat2);
}
}
class Cat{
private String name;
//
private static Cat cat;
//步骤
//1. 构造器私有化
//2. 定义一个static类对象
//3. 提供一个public的stati方法,可以返回一个Cat对象
public Cat(String name) {
this.name = name;
}
public static Cat getInstance(){
if(cat == null){
cat = new Cat("tom");
}
return cat;
}
@Override
public String toString() {
return toString();
}
}
- 区别
(1)创建对象时机不同,饿汉式类加载时创建,懒汉式使用时才创建
(2)懒汉式可能有线程安全问题,饿汉式没有
(3)饿汉式可能浪费资源,创建了实例未使用
模板设计模式
抽象类最佳实践
final关键字
- 修饰类,则该类不能被继承
- 修饰方法,该方法不能被重写
- 修饰类属性,该属性不能被修改
- 修饰局部变量,该局部变量不能被修改
- 细节
- final修饰的属性必须定义时进行初始化,且不可修改
- 赋值位置:
(1)定义时
(2)构造器(非静态)
(3)代码块
- 修饰的属性是静态的static时,不能在构造器赋值
- final修饰类可以实例化
- 类中有方法被final修饰,该类可以继承,方法不能重写
- 类是final类了,没必要再写final方法
- final和static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
- 包装类不能被继承
抽象类
- 细节
- abstract 修饰方法,抽象方法不能有方法体
- 抽象类不能实例化
- 抽象类可以没有抽象方法
- 有抽象方法,必须声明为抽象类
- abstract只能修饰类 或者 方法
- 继承抽象类之后必须实现抽象类的所有抽象方法, 或者也声明为抽象类
- 抽象方法不能用private、final、static,因为这些和重写违背
接口
- jdk8接口中可以有属性、抽象方法、默认实现方法、静态方法
public interface A{
public int n1 =1;//属性
public abstract void f();//可以省略关键字abstract
default public void f2();//默认实现方法
public static void f3();//静态方法
}
- 接口不能被实例化
- 一个普通类实现接口必须实现接口的所有方法
- 接口中所有方法是public,抽象方法可以不用abstract修饰
- 一个类可实现多个接口
- 类中的属性只能是final的,而且是public static final
- 访问属性:接口名.属性名
- 接口不能继承类,但可以继承接口
- 接口修饰符只能是public和默认,和类一样
interface A{
int n1 = 10; //等价于public static final int n1 =10;
}
接口与继承类
- 继承:解决代码复用性、可维护性
- 接口: 设计好规范方法,让其它类去实现,灵活,解耦(接口规范性 + 动态绑定机制)
接口的多态性
- 接口引用可以指向,实现了该接口的类的对象
- 多态传递
interface A{}
interface B extends A{}
class C extends B{}
A a = new C();
内部类
最大特点
- 可以直接访问私有属性,是类的第五大成员(属性、方法、构造器、代码块、内部类)
分类
定义在外部类局部位置
局部,相当一个局部变量
局部内部类(有类名)
匿名内部类(无类名)重要!!
简化开发,匿名内部类只能使用一次,
interface A{
public void run();
}
class B{
A tiger = new A(){
@Override
public void run(){
System.out.println("run");
}
}
tiger.run();
}
定义在外部类成员位置上
成员内部类(无static修饰)
不在方法中,在外部类的一个成员位置定义,可以添加任意访问修饰符,与成员一样
静态内部类(有static修饰)
枚举和注解
枚举
- 自定义类实现枚举
- 使用enum
enum类的各种方法
public enum Enumeration02 {
//
SPRING("春"),SUMMER("夏"),AUTUMN("秋"),WIMTER("冬");
private String name;
private Enumeration02(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString(){
return "{name:"+ name + "}";
}
}
public class Enumeration01 {
public static void main(String[] args) {
Enumeration02 spring = Enumeration02.SPRING;
//1. 输出列举对象的名字
System.out.println(spring.name());
//2. ordinal()输出该枚举对象的次序
System.out.println(spring.ordinal());
//3. 反编译可以看到 values方法,返回 Enumeration01[]数组
//含有所有枚举对象
Enumeration02[] allValue = Enumeration02.values();
for(Enumeration02 season: allValue){
//增强for循环
System.out.println(season);
}
//4. 将字符串转换成枚举对象,要求字符串为已有常量,否则报异常
Enumeration02 autumn = Enumeration02.valueOf("AUTUMN");
System.out.println("autumn+" + autumn);
System.out.println(Enumeration02.AUTUMN == autumn); //true
//5. 比较两个枚举常量的序号, 返回的是spring的序号减去autumn的序号值
System.out.println(spring.compareTo(autumn)); // 0-2=-2
//
System.out.println(spring.getDeclaringClass());
System.out.println(spring.getClass());
}
}
反射
异常
两类
- Error错误:java虚拟机无法解决的严重问题
- Exception:一般性问题,可以使用针对性的代码进行处理,分为两大类运行时异常【程序运行时发生的异常】和编译时的异常【编程时,编译器检查出的异常】
- try 多个catch 捕获异常时,先捕获子类异常再父类,只捕获一次
public void f(){public static int f(){
int i = 0;
try {
String[] names = new String[3];
System.out.println(names[0].equals("tom"));
return i;
} catch (NullPointerException e){
System.out.println("null:"+ i);
return ++i; // 这里i的值保存在一个临时变量temp中, 指向完finally最后返回temp保存的这个值
}catch (Exception e) {
e.printStackTrace();
} finally {
++i;
System.out.println("finally: " + i);
}
System.out.println("out of try i:" + i);
return i;
}
}
自定义异常
定义异常类继承Exception或者RuntimeException
- 继承Exception
属于编译异常 - 继承RuntimeException
属于运行异常
意义 | 位置 | 后面跟的东西 | |
---|---|---|---|
throws | 异常处理的一种方式 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
包装类
Serializable接口
所谓序列化即可以保存类型和数据本身,可以在网络中传输,保存到文件
Integer
Object o = true ? new Integer(1): new Double(2.0);
// 三元运算符看作一个整体
System.out.println(o) ;// 输出1.0
常用类
String
实现了Serializable接口,说明String可以串行化,在网络中传输
- 本质还是保存在字符数组里
- value 是一个final类型, 不可以修改(这里不能修改指的是不能指向新的地址,但是可以修改单个字符串内容)
private final char value[];//存放字符串内容
案例分析
1.这里首先在堆中new一个对象,开辟一个空间,空间里有str和ch数组
2. str是new出来的,所以也指向一个String堆空间,在这个空间里有一个value属性,这个value指向常量池中的”hsp“
3. ch是一个final数组,数组默认保存在堆中,ch指向数组[‘j’,‘a’,‘v’,‘a’]
在主方法栈中, ex指向这个对象的引用,ex调用chang方法,产生一个新的栈,在这个栈中有两个参数str he ch[],str指向2中的String堆空间,但是执行 str="java"语句后,这个chang方法栈中的str指向了常量池中的”java“字符串(类成员属性str指向的仍然是String堆空间);chang方法栈中的ch指向的是3中的ch数组,指向同一个数组,ch[0]='h’则直接把堆中数组的第一个变量写成h
4. 输出时是输出类成员属性str指向的内容, 数组ch[]指向的地址不变,内容变化了
-
数组默认保存在堆中
案例
String t1 = “hellp” + name;
两个常量拼接,是创建一个StringBuilder 对象,然后向这个对象里append追加“hello” 和name指向的常量池中的值,然后在堆中开辟一个String堆空间,把这个空间给t1,把StringBuilder里的值放在常量池里,再由空间里的value指向它
StringBuffer
由第3条可知,StringBuffer类型的可变长字符串用char[] value保存,没有final,整个保存在堆中,不会放在常量池
StringBuffer不用每次更新地址,空间不够了才扩展更新
StringBuilder
- 不是线程安全的
- 提供可变字符序列,提供一个与StringBuffer兼容的API
String 、StringBuffer、 StringBuilder对比
Math
//Math常用静态方法
//1.abs绝对值
int a = Math.abs(-1);
System.out.println(a);//1
//2. pow求幂
double b = Math.pow(2,3);
System.out.println(b);//8.0
//3.ceil向上取整
double ceil = Math.ceil(-3.02);
System.out.println(ceil);//-3.0
//4.floor向下取整
double floor = Math.floor(-4.8);
System.out.println(floor);//-5.0
//5. round 四舍五入
double r = Math.round(-4.8);
System.out.println(r);//-5.0
//6.sqrt 开方
double s = Math.sqrt(9.0);
System.out.println(s);//3.0
//7.random随机数 返回的值满足 0 <= x < 1
double x = Math.random();
System.out.println(x);
Arrays类
用于操作数组
BigInteger
不能直接+ -*/,使用相应的方法进行运算
BigDecimal
保存高精度的数,double不够用时使用
使用相应的方法进行运算
除法时可能除不尽,在方法后面指定精度即可解决
BigDecimal bd1 = new BigDecimal("111111111111111111.9999");
BigDecimal bd2 = new BigDecimal(112);
//如果结果是无限循环小数,就会保留分子的精度
System.out.println(bd1.divide(bd2,BigDecimal.ROUND_CEILING));
LocalDate(日期/年月日)、LocalTime(时间/时分秒)、LocalDateTime(日期和时间)
- 第一代 Date
- 第二代Calender
- 第三代 jdk8引入
public static void main(String[] args) {
//1. now()返回日期时间对象
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
//2. 使用DateTimeFormatter对象进行格式化
// 创建DateTimeFormatter对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(localDateTime);
System.out.println(format);
}
集合框架体系
单列集合
- 保存一个元素
Collection接口、子接口及实现类
- Collection 接口遍历元素方式1-使用Iterator(迭代器)
2. Collection 接口遍历对象方式2-for 循环增强
3. for循环
List
List接口实现类的三种遍历方式
ArrayList
- 线程不安全
- 效率高
- 由数组实现数据存储
- 基本等同于Vector
ArrayList底层结构和源码分析
transient(短暂的、瞬时的)修饰,表示该属性不会被序列化
LinkedList
- 线程不安全
- 实现了双端队列和双向链表
- 可以添加任意元素,包括你null
Vector
- 线程安全
ArrayList 和 Vector比较
ArrayList 和LinkedList比较
Set接口
- 常用方法和List一样
- 遍历
- 可以使用迭代器
- 增强for循环
- 不能使用索引的方法获取
HashSet
- 无序
- 元素不能重复
- 添加顺序与取出顺序不同,但是取出顺序固定
- 实现了Set接口
- 实际上是HashMap
- 可以放空值null
- HashSet底层维护的是数组+单向链表
- HashMap底层是数组+链表+红黑树
HashSet底层机制
equals()不能简单的认为是比较内容,每个类都可以重写自己的equals()方法,String是重写了equals()方法比较内容
关于threshold(阈值)扩容的问题,是整个table表中所有元素个数达到12就会扩容;整个table表中每个索引元素后面跟着的链表中的元素都包括在内
LinkedHashSet
- 底层维护的是LinkedHashMap
- LinkedHashMap是HashMap的子类
LinkedHashSet是HashSet的子类
TreeSet
双列集合
- 以键值对的形式保存元素
Map
Map接口特点
Map map = new HashMap();
map.put("01","jack"); // k-v
map.put("02","stion"); // k-v
map.put("01","tom"); // k-v
- k-v 最后是保存在 HashMap$Node node = newNode(hash, key, value, null)
- k-v 为了方便遍历, 还会创建EntrySet 集合, 该集合存放的元素的类型是Entry,而一个Entry对象就有 k,v ,EntrySet<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet;.
- entrySet 中, 定义的类型是 Map.Entry, 但是实际上存放的还是 HasMap$Node, 这是因为 static class Node<K,V> implements Map.Entry<K,V>
右侧一对数据 保存在entry里,再把entry放在entrySet里,
所有的key放在一个keySet里
Set 和entrySet里其实只是建立了简单的引用,指向HashMap$Node,并重新拿一份数据
Map六大遍历方式
第一组 取出所有key,通过key,取出对应value
- Set keySet = map.keySet();
- 增强for循环
- 迭代器
第二组 取出所有values
- Collection values = map.values();
- 增强for
- 迭代器
第三组 通过EntrySet 获取 k-v
- Set entrySet = map.entrySet();
- ** EntrySet<Map.Entry<K,V>>
- 增强for
- 迭代器
Iterator iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next.toString());
// HashMap$Node --实现--> Map.Entry(getKey,getValue)
//这里取出来的next其实是保存在HashMap$Node, 但是这个Node实现了Map.Entry, 所以可向下转型
System.out.println(next.getClass());//class java.util.HashMap$Node
Map.Entry m = (Map.Entry) next;
System.out.println(m.getKey()+"--"+m.getValue());
}
HashMap线程不安全,没有实现同步
HashMap底层机制实现和源码分析
看笔记吧
HashTable线程安全
Properties
TreeMap
这里第一次添加也是调用动态绑定的匿名内部类(也是个对象)的compare方法,方法参数传入两个相同的key,因为第一次添加没有要比较的,然后compare返回值也没有用变量接收,不会对后面有影响
这里compare作用是比较key是否为null,如果为空,会有空指针异常
TreeMap这里使用cpmpare方法比较两个Key是否相等,是否相等决定于自己设计的compare方法,HashMap是用equals方法比较两个Key是否相等
开发中如何选择集合实现类
Collections工具类
TreeSet和HashSet如何实现去重
例题
Person类并没有实现Compareable接口,所以在执行到比较去重的代码就会报错。
泛型
好处
介绍
- 泛型可以理解为表示数据类型的一种数据类型
泛型语法
自定义泛型类
- 基本语法
class 类名<T,R...>{
//...表示可以有许多个泛型成员
}
- 细节
- 普通成员可以使用泛型(属性、方法)
- 使用泛型数组,不能初始化
- 静态方法中不能使用类的泛型
- 泛型类的类型, 是在创建对象时确定的(因为创建对象时,需要指定确定类型)
- 如果在创建对象时候没有指定类型,默认为Object
自定义泛型接口![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/93cafd28c60bfe492a489c5043d11bd2.png)
自定义泛型方法
泛型方法与使用泛型的区别
泛型的继承和通配符
线程
- 单线程
- 多线程
- 并发
同一时刻,多个任务交替进行,造成一种“貌似同时”的错觉,比如单核cpu实现的多任务是并发 - 并行
同一时刻,多个任务同时进行,多核cpu可以实现并行
创建线程
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
run方法是Runna接口里的方法,Thread类里的run方法是实现的Runna接口里的run方法 - 主线程结束,子线程不会立刻结束,会执行完再结束
线程常用方法
线程7大状态
RUNNABLE状态代表可以运行,但是不一定马上运行;在此基础可以细分为两种状态,READY 和RUNNING, 这由线程调度器决定,(操作系统决定)
- 线程状态转换图
Synchronized
互斥锁
上图要求多个线程的锁对象为同一个 解释
如上图,执行注释掉的两条语句,new了两个不同的对象,相当于m1()方法中的this分别是指向着两个对象,锁对象不是同一个了,锁不住,没用了
线程死锁
多个线程占用了对方的资源,双方都无法获得
A线程拿到o1,B线程拿到o2;之后A尝试获取o2失败,同理B也失败,进入死锁
释放锁
例题
public class Thread02 {
public static void main(String[] args) {
Walet walet = new Walet(10000);
Thread thread01 = new Thread(walet);
Thread thread02 = new Thread(walet);
thread01.setName("1号");
thread02.setName("2号");
thread01.start();
thread02.start();
}
}
class Walet implements Runnable {
private int salary;
public Walet(int salary) {
this.salary = salary;
}
@Override
public void run() {
while (true) {
//1.这里使用synchronized对象锁
//2.当线程执行到这里,就会取争夺this对象锁
//3.哪个线程争夺到this对象锁,就执行 synchronized 代码块 ,执行完毕, 释放this对象锁
//4.争取不到this对象锁,进入阻塞状态blocked,准备继续争夺
//5.this对象锁是非公平锁
synchronized (this) {
if (salary <= 0) {
System.out.println("余额不足");
break;
}
salary -= 1000;
System.out.println("线程名" + Thread.currentThread().getName() + "余额" + this.salary);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
文件
IO流
文件字节流
InputStream
OutputStream
//得到一个FileOutputStream对象
fileOutputStream = new FileOutputStream(filePath);
String str = "hello,dscdv";
fileOutputStream.write(str.getBytes("utf-8"),1,3);
//2.追加模式
fileOutputStream = new FileOutputStream(filePath,true);
fileOutputStream.write(str.getBytes(),0,str.length());
文件拷贝
public static void main(String[] args) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
String root = "E:\\javaProjectExercise\\javacode\\test\\src\\file\\a.txt";
String dest = "E:\\javaProjectExercise\\javacode\\test\\src\\file\\b.txt";
try {
fileInputStream = new FileInputStream(root);
fileOutputStream = new FileOutputStream(dest);
//创建一个字节数组用来保存读取的数据
byte[] bytes = new byte[100];
int readLen = 0;
while ((readLen = (fileInputStream.read(bytes)))!= -1) {
//一遍读,一边写
fileInputStream.read(bytes);
//必须根据长度写入
//因为最后一个字节可能读数据不满,比如最后读了50个数据,但是还是写了100个数据进去
//不能用这个fileOutputStream.write(bytes);
fileOutputStream.write(bytes,0,readLen);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件字符流
节点流、处理流
public static void main(String[] args) {
/**
* BufferedReader 和 BufferedWriter 是操作 字符文件
* 不能操作二进制文件, 会造成文件损坏
*/
String srcFilePath = "E:\\javaProjectExercise\\javacode\\test\\src\\file\\b.txt";
String destFilePath = "E:\\javaProjectExercise\\javacode\\test\\src\\file\\a.txt";
BufferedWriter bufferedWriter = null;
BufferedReader bufferedReader = null;
String line;
try {
bufferedReader = new BufferedReader(new FileReader(srcFilePath));
bufferedWriter = new BufferedWriter(new FileWriter(destFilePath));
//按行读取,读不到返回空
//按行原读取,但是没读换行符
while ((line = bufferedReader.readLine()) != null){
bufferedWriter.write(line);
//添加换行符
bufferedWriter.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(bufferedReader!=null) {
bufferedReader.close();
}
if(bufferedWriter != null) {
bufferedWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
序列化
对象流
ObjectInputStream
ObjectOutputStream
标准输入输出流
转换流
InputStreamReader
OutPutStreamWriter
打引流
PrintStream
PrintWriter
Properties类