java学习第14天
序列化
java在运行时,如果需要保存对象的状态(下次程序在运行时,也能还原当前对象的状态)就需要使用序列化操作。本质是把对象保存为一个文件存到磁盘上,下次运行时从磁盘上读取文件,恢复对象(反序列化)。
举例:Student对象,在运行时给姓名赋值为张三,年龄赋值为20岁,这个对象在程序运行结束就消失了,下次程序运行时又要重新创建对象并赋值。要想下一次运行时这个对象还存在,这种情况就需要使用序列化。
网络程序,如果想把一个对象从一台机器(虚拟机)发送到另外一台机器(虚拟机),这种情况也需要把对象序列化为二进制的内容,如何再通过网络发送,对象收到二进制内容,再反序列化为对象。
ObjectOutputStream(序列化)
把运行的对象保存为文件。
要被序列化的对象的类,要实现Serializable接口
-
Student类,实现节后Serializable
public class Student implements Serializable { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "学生:{" + "姓名是" + name + ", 年龄为" + age + '}'; }
-
SerializableDemo
利用ObjectOutputStream的writeObject方法做序列化
public static void main(String[] args) throws Exception { ObjectOutputStream oot = new ObjectOutputStream(new FileOutputStream("D:\\idea\\javaseprojects\\java_Hqyj_Day14\\src\\abc.txt")); Student student = new Student("张三",23); oot.writeObject(student); oot.close(); }
ObjectInputStream(反序列化)
调用readObject()反序列化,该方法返回的类型是Object,所以要强制转换
读取的序列化文件一定是正常序列化的文件,并且类型要相同。
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\idea\\javaseprojects\\java_Hqyj_Day14\\src\\abc.txt"));
//读取序列化的文件,还原为原来的对象(对象的属性都还在)
Student o = (Student) ois.readObject();
System.out.println(o);
ois.close();
复习
构造方法在什么时候使用?有参和无参的区别在哪里?
构造方法在创建对象(实例化)的时候会被调用(也就是使用new Xxx() ),还有一种情况就是子类创建对象时会先调用父类的构造方法,然后在调用子类的构造方法。在子类的构造方法中没有明确的调用父类的构造方法,自动调用父类的无参构造方法。
new的关键字使用的时候,类名后面的小括号里面有几个参数,就会调用对应参数数量的构造方法。
例如:new Student(),调用无参构造方法;
new Student(String name),调用一个参数的构造方法。
new Student(String name,int age),调用两个参数的构造方法。
用哪一个构造方法是程序员自己决定的。调用有参数的构造方法的目的是创建对象的同时给属性赋值。
-
Emp
package com.Hqyj.ObjectOutputStream; public class Emp { protected int empNum;//员工编号 protected String name;//员工姓名 protected int age;//员工年龄 public Emp() { System.out.println("父类的无参构造方法被执行"); } public Emp(int empNum) { this.empNum = empNum; } public Emp(int emp, String name, int age) { this.empNum = emp; this.name = name; this.age = age; System.out.println("父类的有参构造方法被执行"); } public int getEmpNum() { return empNum; } public void setEmpNum(int empNum) { this.empNum = empNum; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Emp{" + "emp=" + empNum + ", name='" + name + '\'' + ", age=" + age + '}'; } }
-
EmpHqyj
package com.Hqyj.ObjectOutputStream; public class EmpHqyj extends Emp{ public EmpHqyj() { System.out.println("子类的无参构造方法被执行"); } public EmpHqyj(int emp, String name, int age) { //super(emp, name, age);//调用父类的构造方法 this.empNum = emp; this.name = name; this.age = age; System.out.println("子类的有参构造方法"); } }
-
EmpTest
package com.Hqyj.ObjectOutputStream; public class EmpTest { public static void main(String[] args) { //用无参的构造方法创建对象,属性都是默认值 Emp emp = new Emp(); //要给属性赋值,使用setXxx()方法 emp.setEmpNum(1001); emp.setName("张三"); emp.setAge(25); System.out.println(emp); //用有参(三个)构造方法创建对象,它会在创建的同时给属性赋值。 Emp emp1 = new Emp(1002,"李四",26); System.out.println(emp1); //用一个参数的构造方法创建对象。 Emp emp2 = new Emp(1003); System.out.println(emp2); /* 测试EmpHqyj*/ System.out.println("=======EmpHqyj======"); EmpHqyj empHqyj = new EmpHqyj(); System.out.println(empHqyj); //子类的有参构造方法如果没有明确调用父类的构造犯法,会自动调用父类的无参构造方法 EmpHqyj empHqyj1 = new EmpHqyj(1004,"王五",24); System.out.println(empHqyj1); } }
常量池中存的是一种对象吗?
常量池中存的也是对象,主要正对String类型,如果用字面量赋值的String变量,就会在常量池中创建一个对象,下一次再用相同的字符串给变量赋值的时候,就会共用常量池中的一个对象。
基本类型不会赋值给对象。
package com.Hqyj.ObjectOutputStream;
public class StringTest {
public static void main(String[] args) {
String str = "abc";
//判断一个对象是否是某个类型
System.out.println(str instanceof String);
String str2 = "abc";
System.out.println(str == str2);
System.out.println(str.equals(str2));
String str3 = new String("abc");
System.out.println("str == str3?"+(str == str3));
System.out.println("str.equals(str3)?"+ str.equals(str3));
}
}
对于泛型List,如果进行引用,虚拟机不能自动垃圾回收吗?
运行参数设置,虚拟机最大和最小都设为20M
测试对象不被回收,导致内存溢出
package com.Hqyj.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class GcTest {
public static void main(String[] args) {
List<Emp> emps = new ArrayList<>();
while (true){
// 因为emp对象被list引用,所以emp不能被垃圾回收。
//当内层1超过上限就会报OutofMemaryError
//emps.add(new Emp());
//emp对象知赋给局部变量,这个变量下一次循环就消失了(不在被引用)所以能被回收
//所以内存不容易超过极限
Emp emp2 = new Emp();
System.out.println(System.currentTimeMillis());
}
}
}
关于方法的调用
方法用来执行某一个特定功能的一段代码,方法可以重复使用。使main里面的代码尽量简洁。
方法也是类的一种组成部分
方法的定义:例如
public int fun1(int i, int j){
//方法体,方法内的代码
return 0;
}
方法的调用:
int m = obj.fun1(5, 20);
package com.Hqyj.ObjectOutputStream;
public class MethodDemo {
public static void main(String[] args) {
MethodDemo methodDemo = new MethodDemo();
int sum = methodDemo.sum(25,63);
System.out.println(sum);
}
public int sum(int a, int b) {
return a + b;
}
}
-
重写和重载的区别
-
可变长的参数
定义方法的时候,参数类型后面跟上三个点…,这个参数就叫可变长参数,调用该方法时可以传多个参数。
可变参数只能是最后一个参数。
int void fun2(String …str);
-
方法里面的变量都是局部变量,方法里面的引用类型的变量只保存对象的地址,实际对象存在堆里面的。
方法被调用时在栈里面会有一段内存(方法栈帧)保存局部变量,方法调用时入站,方法调用结束出栈,出栈后释放栈帧的内存,局部变量不可再访问。
如果方法里面调用另一个方法(如A调用B),A方法暂停运行,先执行被调用的B方法,B方法入栈,等被B方法执行完成后,A方法在继续执行。
try语句块中出现return语句,finally还会执行吗?
会执行,即便出现Error,finally也会执行,
抽象 abstract
抽象可以修饰类,方法。
抽象方法,就是这个方法没有方法体(没有代码),抽象方法的目的是为了统一规范方法的定义,具体的实现交给子类去实现,一般来说抽象方法在每个子类中实现都不一样。例如:形状类计算面积的方法,父类中就没有办法去实现这个方法,因为不同的形状面积的计算公式不一样,所以只能让长方形,三角形,梯形等这些子类自己去实现面积计算方法。如果不统一方法名,那每个子类可能会写出不同的方法名,不方便向上造型之后的统一调用。
一个类只要有一个方法是抽象方法,这个类就必须定义为抽象类。抽象类中可以只有抽象方法,也可以有抽象方法和具体方法,也可以只有具体方法,抽象类不能创建对象。
抽象类可以有构造方法,子类在创建对象时,抽象类的构造方法也会被调用。
所有的方法都是抽象类,这个抽象类就可以定义为接口(interface),interface的子类就用implements关键字实现接口,可以实现多个接口,接口可以相互继承。
接口里面的方法不写访问修饰符,默认就是Public,如果属性不写修饰符,默认就是public static final(常量)
- 抽象类Shape
public abstract class Shape {
public abstract void area(int a, int b);
}
- 子类Rectangle
public class Rectangle extends Shape {
@Override
public void area(int a, int b) {
System.out.println(a * b);//长方形的面积等于长乘宽
}
}
- 子类Triangle
public class Tritangle extends Shape {
@Override
public void area(int a, int b) {
System.out.println(1.0 / 2 * a * b);//三角形的面积底乘高乘1/2.
}
}
- 测试代码
public class ShapeTest {
public static void main(String[] args) {
Shape shapeRect = new Rectangle();//多态的向上造型
System.out.println("长方形的面积");
shapeRect.area(6,4);
Shape shapeTrit = new Tritangle();
System.out.println("三角形的面积");
shapeTrit.area(4,8);
}
}
Static静态
static可以修饰类,方法,属性,代码块
static是属于类的,不属于对象:
- static在类加载的时候执行(静态代码块最先执行),不是在创建对象时执行。
- static是所有对象共享的,不属于对象独有的。
- static的访问可以直接用类名访问。static的方法只能访问static的属性或方法
序列化时不会序列化类的静态成员
-
staticDemo
package com.Hqyj.ObjectOutputStream; import java.text.SimpleDateFormat; import java.util.Date; public class StaticDemo { public static int count = 0; private int i; private String str; public static void showTime(){ Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(date)); } public StaticDemo() { System.out.println("static静态代码块的无参构造器"); } static { System.out.println("static静态代码块"); } public int getI() { return i; } public void setI(int i) { this.i = i; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } public static int getCount() { return count; } @Override public String toString() { return "StaticDemo{" + "i=" + i + ", str='" + str + '\'' + '}'; } }
-
StaticDemo
package com.Hqyj.ObjectOutputStream; public class StaticTest { public static void main(String[] args) { //直接用类名访问静态成员变量 System.out.println("count"+ StaticDemo.count); //直接用类名调用静态方法 StaticDemo.showTime(); StaticDemo sD = new StaticDemo(); StaticDemo sD1 = new StaticDemo(); StaticDemo.count++; System.out.println(sD.getCount()); System.out.println(sD1.getCount()); } }