JavaSE-面向对象编程(oop)
面向对象编程的本质就是:以类的方式组织代码,以对象的方式组织(封装)数据。
面向对象的三大特性:
- 封装
- 继承
- 多态
类与实例化对象
实例化对象的方式
- 调用new语句创建对象
// Student.java
public class Student{
String studentName;
public void study(){
System.out.print(this.studentName+"正在努力学习!");
}
}
// Main.java
public class Main{
public static void main(String[] args){
// 实例化对象
Student student = new Student();
student.studentName = "chen";
student.study();
}
}
-
调用对象的clone()方法
-
运用反射手段创建对象
-
运用反序列化手段,见最后。
调用静态方法无需实例化对象,而调用非静态方法需要实例化对象
-
类是一种抽象数据类型,它对某一类事物整体描述、定义,但并不代表某一具体事物。
-
对象是抽象类的具体实例。
对象的创建过程:
-
分配内存空间,初始化属性默认值
-
实例化构造器,初始化实例、实例属性
-
返回地址给引用
在 JVM 中,对象在内存中的表现
表现为三个区域:
-
对象头:
-
标记字段:默认存储对象的HashCode,GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。见【JavaSE-多线程】
-
类型指针:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
-
-
实例数据:这部分主要是存放类的数据信息,父类的信息。
-
对齐填充:由于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。Tip:一个空对象占多少个字节?就是8个字节,是因为对齐填充的关系哈,不到8个字节对其填充会帮我们自动补齐。
构造器
什么是构造器,特点:
骨架,特点:
-
和类名相同
-
没有返回值
作用:
-
new 本质就是调用构造方法
-
初始化对象的值
注意点:
-
定义有参构造器时,必须定义无参构造器。
-
快捷键:
alt + insert
实例:
// Student.java
public class Student{
String name;
public Student(){
// ... 默认空构造器,一个类即使什么都不写,它会自动存在一个方法
}
public Student(String name){
// 有参构造器:一旦定义了有参构造器,无参构造器就必须显示定义。
this.name = name;
}
}
// Main.java
public class Main{
public static void main(String[] args){
// 实例化对象
Student student = new Student("chen");
System.out.print(student.name);
}
}
封装
简单来说就是:属性私有、get / set。alt + insert
// Student.java
public class Student{
// 属性私有化,只能通过设置get、set方法进行调用与赋值
private String studentName;
// 公共属性提供调用以及赋值
// public String studentName;
public String getName(){
return this.studentName;
}
public void setName(String name){
this.studentName = name;
}
}
// Main.java
public class Main{
public static void main(String[] args){
// 实例化对象
Student student = new Student();
student.setName("chen");
student.getName();
}
}
单继承
ctrl + h 可以查看继承关系。
-
在java中所有的类都是直接或间接继承Object对象,子类继承父类时父类不能使用final 修饰。
-
在java中类只有单继承,没有多继承。
-
通过super.xxx 继承父类属性或调用父类方法
-
父类中私有属性或者方法无法通过super字段进行继承调用。
-
super调用父类构造方法必须在构造方法的头一个位置,且根据父类构造器(有参 / 无参)进行传值。
-
super只能出现子在子类的方法或者构造方法中。
-
super和this不能同时调用构造方法。
- this():本身调用者这个对象,没有继承关系也可以调用,调用的是本类的构造。
- super():代表父类对象的应用,只能在继承条件下才可以使用,调用的是父类的构造器。
-
判断继承关系
System.out.print(x instanceof y); //=> true / false
// Parent.java
public class Parent /*extends Object*/ {
public int money = 10_0000_0000;
public void inheritMoney(){
System.out.print('继承财产'+this.money);
}
}
// Son.java
public class Son extends Parent{
//...
}
// Main.java
public class Main{
public static void main(String[] args){
Son son = new Son();
son.inheritMoney();
}
}
super 关键字:
// Parent.java
public class Parent /*extends Object*/ {
public int money = 10_0000_0000;
public Parent(){
System.out.println("Parent无参数执行了");
}
public void inheritMoney(){
System.out.println('继承财产'+this.money);
}
/*
私有无法通过super进行继承调用
private void inheritMoney(){
System.out.print('继承财产'+this.money);
}
*/
}
// Son.java
public class Son extends Parent{
public Son(){
// super(); 被隐藏了!!这里调用了父类的无参构造,必须放在第一行
System.out.println("Son无参数执行了");
}
public void test(){
super.money = 11_0000_0000;
super.inheritMoney();
}
}
// Main.java
public class Main{
public static void main(String[] args){
Son son = new Son();
son.test();
/*
Parent无参数执行了
Son无参数执行了
继承财产1000000000
*/
}
}
重写
必须存在继承关系或者实现关系,子类重写父类方法,方法名相同,参数类型相同,关键在于判别方法是否静态,且公有。重写是方法的重写而非成员属性的重写。
-
静态方法:方法调用只和左边(定义数据类型)有关。
-
非静态方法:子类重写父类方法,调用时只会执行子类方法。
-
快捷键:
alt + insert : override
// Parent.java
public class Parent {
/*
public staic void test(){
System.out.println("Parent");
}
*/
public void test(){
System.out.println("Parent");
}
}
// Son.java
public class Son extends Parent{
/*
public staic void test(){
System.out.println("Son");
}
*/
// 重写
@Override
public void test(){
System.out.println("Son");
}
}
// Main.java
public class Main{
public static void main(String[] args){
Son son = new Son();
Parent parent = new Son();
son.test(); //=> "Son" then //=> "Son"
parent.test(); //=> "Parent" then //=> "Son"
}
}
多态
-
多态是方法的多态,属性没有多态的概念。
-
注意类型转换异常ClassCastException。
-
存在条件:必须存在关系,方法需要重写
-
继承关系:父类的引用指向子类对象,会进行类型的转换。
-
实现关系:方法形参是接口,具体传入的是接口的实现类的对象
-
// Parent.java
public class Parent {
public void test(){
System.out.println("Parent");
}
}
// Son.java
public class Son extends Parent{
// 重写
@Override
public void test(){
System.out.println("Son");
}
public void test2(){
System.out.println("Son独有方法");
}
}
// Main.java
public class Main{
public static void main(String[] args){
Son son = new Son();
Parent parent = new Son();
Object obj = new Son();
// String son2 = new Son(); //=> error, ClassCastException
son.test(); //=> "Son"
parent.test(); //=> "Son"
obj.test(); //=> "Son"
// parent.test2(); //=> error
// 强制转换
((Son) parent).test2(); //=> "Son独有方法"
}
}
// Demo.java
public interface Demo{
void pay();
}
//Demo2.java
public class Demo2 implements Demo{
@Override
public void pay(){
System.out.println("Demo2 实现Demo 的pay方法");
}
}
//Demo3.java
public class Demo3 implements Demo{
@Override
public void pay(){
System.out.println("Demo3 实现Demo 的pay方法");
}
}
// Main.java
public class Main{
public static void main(String[] args){
handlePay(new Demo2());
}
public static void handlePay(Demo demo){
demo.pay();
}
}
instanceof 语法
instanceof 关键字
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:
Boolean bool = obj instanceof Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
obj 必须是引用类型(对象),不是基本类型
int i = 0;
System.out.println(i instanceof Integer);//编译不通过,编译报错
System.out.println(i instanceof Object);//编译不通过,编译报错
obj 为null
一般我们知道Java分为两种数据类型,一种是基本数据类型,有八个分别是 byte short int long float double char boolean,一种是引用类型,包括类,接口,数组等等。而Java中还有一种特殊的 null 类型,该类型没有名字,所以不可能声明为 null 类型的变量或者转换为 null 类型,null 引用是 null 类型表达式唯一可能的值,null 引用也可以转换为任意引用类型。我们不需要对 null 类型有多深刻的了解,我们只需要知道 null 是可以成为任意引用类型的特殊符号。在 JavaSE规范中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回 false。
obj 为实例对象
Integer integer = new Integer(1);
System.out.println(integer instanceof Integer);//true
obj 为 class 接口的实现类
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList arrayList = new ArrayList();
System.out.println(arrayList instanceof List);//true
List list = new ArrayList();
System.out.println(list instanceof ArrayList);//true
集合中有个上层接口 List,其有个典型实现类 ArrayList,所以我们可以用 instanceof 运算符判断某个对象是否是 List 接口的实现类,如果是返回 true,否则返回 false。
obj 为 class 类的直接或间接子类
新建一个父类 Person.class,然后在创建它的一个子类 Man.class
public class Person {
}
Man.class
public class Man extends Person{
}
测试:
Person p1 = new Person();
Person p2 = new Man();
Man m1 = new Man();
System.out.println(p1 instanceof Man);//false
System.out.println(p2 instanceof Man);//true
System.out.println(m1 instanceof Man);//true
注意第一种情况, p1 instanceof Man ,Man 是 Person 的子类,Person 不是 Man 的子类,所以返回结果为 false。
问题
编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
看下面的例子
Person p1 = new Person();
System.out.println(p1 instanceof String);//编译报错
System.out.println(p1 instanceof List);//false
System.out.println(p1 instanceof List<?>);//false
System.out.println(p1 instanceof List<Person>);//编译报错
按照我们上面的说法,这里就存在问题了,Person 的对象 p1 很明显不能转换为 String 对象,那么自然 Person 的对象 p1 instanceof String 不能通过编译,但为什么 p1 instanceof List 却能通过编译呢?而 instanceof List 又不能通过编译了?
解释
obj instanceof T
-
obj 的数据类型必须是引用类型、空类型;否则会发生编译报错
-
如果obj 的数据类型强制转换为T 类型时发生编译错误则表达式instanceof 就会发生编译错误,否则判定为编译通过。
所以上述的问题中为什么 p1 instanceof String 会发生编译报错,而p1 instanceof List不会发生编译报错。
instanceof 实现策略(判定true 、 false)
-
如果obj 时null,返回false,否则设定S 为obj 的数据类型,剩余的问题就是判断S 数据类型与T 数据类型的关系。
-
如S == T 返回true
-
接下来要做的就是“子类型检查”,而Java语言的类型系统里数组类型、接口类型与普通类类型三者的子类型规定都不一样,必须分开来讨论。
-
S是数组类型:如果 T 是一个类类型,那么T必须是Object;如果 T 是接口类型,那么 T 必须是由数组实现的接口之一;
-
S是接口类型:对接口类型的 instanceof 就直接遍历S里记录的它所实现的接口,看有没有跟T一致的;
-
S是类类型:对类类型的 instanceof 则是遍历S的super链(继承链)一直到Object,看有没有跟T一致的。遍历类的super链意味着这个算法的性能会受类的继承深度的影响。
类型转换
目的:方便方法的调用,减少重复的代码。
// Father.java
public class Father{
public void fatherSay(){
System.out.println("father");
}
}
// Son.java
public class Son extends Father{
public void sonSay(){
System.out.println("son");
}
}
// Main.java
public class Main{
public static void main(String[] args){
// 高 <-------------- 低,可以任意转换,但是会丢失子类原本的方法!
Father obj = new Son();
// 调用子类的方法就必须进行类型转换;
obj.fatherSay(); //=> "father"
// obj.sonSay(); //=> error
Son obj2 = (Son) obj;
obj2.sonSay(); //=> "son"
((Son) obj).sonSay(); //=> "son"
}
}
抽象类
特点:约束
-
不能new这个抽象类,自能靠子类去实现它
-
抽象类中可以有普通方法
-
抽象方法必须在抽象类中,继承的子类必须重写父类抽象方法
-
关键字
abstract
// Father.java
public abstract class Father{
// 抽象类中的抽象方法
public abstract void doSomething();
public void methods(){
System.out.print("这不是一个抽象方法");
}
}
// Son.java
public class Son extends Father{
// 重写父类的抽象方法
@override
public void doSomething(){
// ...
}
}
// Main.java
public class Main{
public static void main(String[] args){
Father father = new Father(); //=> error,抽象类不能被new
}
}
存在意义
可以将这个类的对象抽象出来,子类只需继承这个类重写此类的构造方法就可以,提高开发效率!
抽象类存在构造器吗
抽象类可以有构造方法,只是不能直接创建抽象类的实例对象而已。在继承了抽象类的子类中通过super()或super(参数列表)调用抽象类中的构造方法。
abstract class Demo{
publicDemo(){
System.out.println("无参构造器");
}
}
public class Demo2 extends Demo{
public Demo2(){
super();
}
public static void main(String[] args){
Demo2 demo2 = new Demo2();
}
}
---> 输出结果
无参构造器
接口类(多继承)
特点:接口类声明关键字interface 而不是class
-
接口中的所有方法定义其实都是抽象的,默认是public abstract
-
使用关键字implements 继承接口。相比于extends单继承,可实现多继承。
-
实现类重写接口中的方法。
-
接口不能被实例化,没有构造方法。
-
可以实例化接口的实现类,调用重写方法。
-
在接口中定义的成员属性是一个静态常量,默认public static final。
// UserService.java 接口类
public interface UserService{
// 定义抽象方法
void add(String name);
void delete(String name);
void update(String name);
void query(String name);
}
// TimerService.java
public interface TimerService{
// 定义抽象方法
void timer(String name);
}
// UserServiceImpl.java 接口实现类
/* 多继承 */
public class UserServiceImpl implements UserService,TimerService{
// 方法重写
@override
public void add(String name){
}
@override
public void delete(String name){
}
@override
public void update(String name){
}
@override
public void query(String name){
}
@override
public void timer(String name){
}
}
内部类
定义一个类(A)中又定义了类(B)。B就是A的内部类,A就是B的外部类。
成员内部类
- 成员内部类:非静态的内部类可调用外部类的方法,获取外部类的属性。
// Outer.java
public class Outer {
private static int i = 10;
public static void out(){
System.out.println("out");
}
// 定义内部类
public class Inner{
public void in(){
System.out.println("in");
}
public void getId(){
// 访问外部私有属性,调用外部方法
System.out.println(i);
out();
}
}
}
// Main.java
public class Main{
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.getId();
}
}
静态内部类
- 静态内部类:静态内部类只能访问外部静态属性
public class Outer {
// private int i = 10; // 静态内部类只能访问外部静态属性
private static int i = 10;
public static void out(){
System.out.println("out");
}
// 定义内部类
public static class Inner{
public void in(){
System.out.println("in");
}
public void getId(){
System.out.println(i);
out();
}
}
}
局部内部类
形式一:
public class Outer {
public void out(){
// 局部内部类
class inner{
public void in(){
}
}
}
}
形式二:
public class Outer {
public static void main(String[] args){
Inner inner = new Inner();
inner.in();
UserService userService = new UserService(){
// 这样就可以new 接口
@Override
public void add(){
}
}
}
}
class Inner{
public void in(){
}
}
interface UserService{
void add();
}
匿名内部类
public class Outer {
public static void main(String[] args){
// 没有名字初始化类,不用将实例保存到变量中。
new UserService(){
// 这样就可以new 接口
@Override
public void add(){
}
}
}
}
interface UserService{
void add();
}
Object 类
所有类默认继承Object 类
class Cat{
public int age;
public String type;
public Cat(int age, String type){
this.age = age;
this.type = type;
}
@Override
public boolean equals(Object data){
if(data instanceof Cat){
Cat otherCat = (Cat) data;
if(otherCat.age == this.age && otherCat.type == this.type){
return true;
}
return false;
}
return false;
}
@Override
public String toString() {
return "Cat{" +
"age=" + age +
", type='" + type + '\'' +
'}';
}
}
public class Demo5{
public static void main(String[] args){
Cat cat = new Cat(1,"布偶");
Cat cat2 = new Cat(1,"蓝短");
System.out.println(cat.equals(cat2));
}
}
IDEA:
// alt+ enter
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Cat)) return false;
Cat cat = (Cat) o;
return age == cat.age && type.equals(cat.type);
}
序列化、反序列化
序列化与反序列化
序列化:Java 对象转换为字节序列的过程。
反序列化:字节序列转换为Java 对象的过程。
idea序列化配置
类实现Serializable,alt+enter 就可以创建序列化uid
为什么需要序列化和反序列化
当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。基本原理和网络通信是一致的,通过特殊的编码方式:写入数据将对象以及其内部数据编码,存在在数组或者文件里面然后发送到目的地后,在进行解码,读出数据。OK到此显示出来为我们所用即可。
当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。
对象序列化与反序列化注意点
序列化:java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。只有实现了Serializable和Externalizable接口的类的对象才能被序列化。
反序列化:java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
class Obj {
private String name;
private int age;
private String sex;
public Obj(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public Obj() {
}
}
public class Demo {
public static void main(String[] args) throws IOException {
output();
}
private static void output() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Jming_er\\Desktop\\javaStudy\\src\\com\\study\\a.text");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
Obj obj = new Obj("chen", 18, "男");
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
private static void input() throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Jming_er\\Desktop\\javaStudy\\src\\com\\study\\a.text");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Obj o = (Obj)objectInputStream.readObject();
System.out.println(o);
}
}
报错
序列化
解决方法:序列化的类必须实现Serializable,此时还没加uid
class Obj implements Serializable{
private String name;
private int age;
private String sex;
public Obj(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public Obj() {
}
}
public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
output();
}
private static void output() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Jming_er\\Desktop\\javaStudy\\src\\com\\study\\a.text");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
Obj obj = new Obj("chen", 18, "男");
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
private static void input() throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Jming_er\\Desktop\\javaStudy\\src\\com\\study\\a.text");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Obj o = (Obj)objectInputStream.readObject();
System.out.println(o);
}
}
查看输出文件
所以此处的输出内容不是直接输出的,是要经过程序将其读取进来这过程叫做反序列化
public static void main(String[] args) throws IOException, ClassNotFoundException {
input();
}
输出结果是:com.study.myFile.Obj@5b6f7412
对Obj 类重写toString()再执行,发现执行结果报错
Exception in thread "main" java.io.InvalidClassException: com.study.myFile.ObjectOutputStreamTestObject; local class incompatible: stream classdesc serialVersionUID = 6762953453798583304, local class serialVersionUID = 4471594530836362455
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1963)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1829)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2120)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1646)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:482)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:440)
at com.study.myFile.myObjectOutputStream.main(myObjectOutputStream.java:56)
原因:在写的时候传入的类对象中没有加入toString() 方法,但是在读的时候却执行了toString() 方法,没有定义uid所以程序就认为不是同一个类,因此处理方法:
类中加入序列版本号(点击类alt + enter【Add ‘serialVersionUID’】)
class Obj implements Serializable{
private static final long serialVersionUID = 4990315073013474645L;
private String name;
private int age;
private String sex;
public Obj(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public Obj() {
}
// 写的时候加不加对读没关系,只要加上serialVersionUID 认为是同一个类就行
@Override
public String toString() {
return "Test{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
output();
input();
}
private static void output() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Jming_er\\Desktop\\javaStudy\\src\\com\\study\\a.text");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
Obj obj = new Obj("chen", 18, "男");
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
private static void input() throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Jming_er\\Desktop\\javaStudy\\src\\com\\study\\a.text");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Obj o = (Obj)objectInputStream.readObject();
System.out.println(o); // Obj{name='chen', age=18, sex='男'}
}
}
特定修饰符无效反序列化
(1) 如果变量加上transient 修饰读取的结果为null,或者说就不可以反序列化。
(2) 如果变量加上static 修饰读取的结果为null,或者说就不可以反序列化。