浅谈Java面向对象

169 阅读10分钟

面向过程:第一步做什么,第二步做什么......适合处理一些简单的问题;面向对象:分类的思维,解决一个问题会用到哪些分类,然后对这些类单独思考,最后才是对某个类上的细节进行面向过程编程。

面向对象的本质:以类的方式组织代码,以对象的方式组织(组装)数据

类是抽象的,是对一类事物共同点的描述。你可以把“抽象”理解为“抽像”,抽离一类事物中很像的共同点。

对象

对象是某一类中具体的事物。

创建对象

对象的创建和使用

  • 必须使用new关键字创建对象,构造器
  • 对象的属性,对象.属性
  • 对象的方法,对象.方法

使用关键字new创建对象

  • 给对象分配内存空间,new出来的对象都在堆中
  • 给创建好的对象进行默认的初始化
  • 对类中的构造器的调用
// Student.java
public class Student {
    // 属性
    String name; // null 默认的初始化
    int age; // 0

    // 方法
    public void study () {
        System.out.println(this.name+"在学习");
    }
}
// Application.java
public class Application {
    public static void main(String[] args) {
        // 使用关键字new创建Student类型的对象student(保存的是对象的引用)
        Student student = new Student();

        student.name = "小明";
        student.study();
    }
}

构造器

类中的构造器也叫构造方法,是在进行创建对象的时候必须要调用的。
构造器的特点

  • 1、必须和类的名字相同
  • 2、必须没有返回值类型,也不能写void 构造器的作用
  • 1、使用new关键字,必须要有构造器,new本质是在调用构造器
  • 2、用来初始化属性的值
// Person.java
class Person {} 
// Application.java
class Application {
    public static void main (String[] args) {
        Person person = new Person(); // new的本质是在调用构造器
    }
}

idea打开out文件夹下的Person.class文件,发现编译后的字节码文件默认会加上一个构造方法。
一个类即使什么都不写,它也会存在一个方法,无参构造。

// Person.class
public class Person {
    public Person() {
    }
}

一旦定义了一个有参的构造器,无参构造器就必须显式定义,否则无法用 new 去调用无参构造,会报错。
无参构造和有参构造相当于是方法的重载。

// Person.java
class Person {
    String name;

    // 无参构造
    public Person () {
    }
    
    // 有参构造
    public Person (String name) {
        this.name = name;
    }
}
// Application.java
class Application {
    public static void main (String[] args) {
        // 调用无参构造
        Person person = new Person();
        
        // 调用有参构造,初始化值
        Person person = new Person("张三");
    }
}

idea快捷键生成构造器:Alt + Ins

51.png

29.png

创建对象内存大致分析

// Pet.java
public class Pet {
    public String name;
    public int age;
    
    public void shout () {
        System.out.println(this.name + "叫了一声.");
    }
}

// Application.java
public class Application {
    public static void main(String[] args) {
        Pet dog = new Pet();
        dog.name = "旺财";
        dog.age = 3;
        dog.shout(); // 旺财叫了一声!

        System.out.println(person.name);
        
        Pet cat = new Pet();
        cat.name = "喵喵";
        cat.age = 2;
        cat.shout();
    }
}

24.png


面向对象的三大特性

封装、继承、多态

封装

设计程序要讲究:高内聚,低耦合。高内聚:类的内部数据操作细节由自己来完成,不允许外部干涉。低耦合:仅暴露少量的方法供外部使用。
属性私有,get/set。属性是私有的,只能用通过get来获取,用set来改变属性的值
属性为什么要私有?
试想:如果编写一个类来描述人类,人类有age属性,如果age不是私有的,可以随意给age设置值,负数的年龄、5000岁的年龄,会对系统造成破坏,为了防止外部随意篡改内部的属性,可将其修饰为私有属性,通过setter/getter来设置和获取,并在setter中对修改的age进行合法性逻辑的把控。

// Student.java
public class Student {
    // 属性私有
    private String name;
    private int age;
    private char gender;

    // getter / setter 一般不会重载
    public void setAge(int age) {
        if (age > 120 || age < 0) { // 不允许外部对私有的数据进行非法的干涉
            this.age = 3;
        } else {
            this.age = age;
        }
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
// Application.java
public class Application {
    public static void main(String[] args) {
        Student student = new Student();
//        student.name = "小明";
//        student.study();
        student.setAge(130);
        student.setName("小明");

        System.out.println(student.getName()); // 小明
        System.out.println(student.getAge()); // 3
    }
}

idea快捷键快速生成getter/setterAlt + Ins

25.png

封装的意义:

  • 提高代码的安全性,保护数据
  • 隐藏代码的实现细节
  • 增加系统的可维护性

继承

继承概述

继承可以理解为,对类的再一次分类,Student类、Teacher类等等都可以被划分在Person类之下。

继承的本质是对某一批类的抽象

使用extends关键字,子类是父类的拓展。
Java中类只有单继承,没有多继承! 一个子类只能继承自一个父类,而一个父类可以让多个子类继承。
除了继承之外,类与类之间的关系还有依赖、组合、聚合等。
子类和父类之间,可以理解为,子类 "是一个" 父类,Teacher是一个Person。

// Application.java
/**
 * @author admin
 * @description 继承
 */
public class Application {
    public static void main(String[] args) {
        Student student = new Student();

        student.say();
        System.out.println(student.money);
        // System.out.println(student.age); // 继承跟封装有什么关系,age在父类是私有属性,被封装了(被private修饰的属性和方法,不能被子类继承)
        // Ctrl + h 把鼠标光标放在类的括号中间,按 ctrl + h 显示类与类继承的树形关系
        
        student.toString(); // 所有的类都在Object类之下
    }
}

// Person.java
public class Person {

    // public protected default private
    public int money = 10_0000_0000;
    private int age = 23;

    public void say () {
        System.out.println("说了一句话");
    }
}

// Student.java
public class Student extends Person {
}

40.png

super 和 this

使用super.属性this.属性使用父类和子类的属性
使用super.方法this.方法使用父类和子类的方法
使用super()this()调用父类和子类的无参构造函数,如果传参了就是调用有参的构造函数。 当父类和子类有相同名字的属性,在子类中可以用super.父类的属性使用父类的属性 当子类重写了父类的方法,子类可以用super.父类的方法调用父类的方法

注意点:

  • static修饰的方法中访问不到thissuper,因为静态的方法与类一起诞生,此时并不存在thissuper
  • this();super();只能在构造器中第一行书写,两者只能存在一个。
  • this();最多能被调用n-1次(n为构造器个数),不能在构造器中调用自己,不能成环。
  • super();父类的构造器(不确定是否有参)最少被调用一次。当父类的有参被定义时,在子类构造器中未调用父类有参的构造器的前提下,父类无参必须显示定义。当子类的构造器中没有书写super(有参)this(无参、有参),会默认调用父类无参的构造器super()
// Application
public class Application {
    public static void main(String[] args) {
        Student student = new Student();
        /*
            Person无参构造执行了
            Student无参构造执行了
        */
//        student.test("很狂的神");
        student.test1();
    }
}


// Student.java
public class Student extends Person {
    private String name = "狂神";

    // 当父类无参构造不存在,子类连无参构造都不能写
    public Student() {
        // 隐藏代码 默认调用了父类的无参构造,当super调用的是有参构造,无参构造就不会被调用
        // super(); // 必须在构造器的第一行,不写参数是调用父类的无参
        // this("a", "b"); // 也必须在构造器的第一行, 不能在无参构造器里调用无参构造,递归
        // 因此this(); 和 super(); 不能同时存在
        System.out.println("Student无参构造执行了");
    }

    public Student(String name, String name1) {
        super(name);
        this.name = name1;
    }

    public void test (String name) {
        System.out.println(name); // 很狂的神
        System.out.println(this.name); // 狂神
        System.out.println(super.name); // kuangshen
    }

    public void test1 () {
        // 都是调用Student类的print方法
        print(); // Student
        this.print(); // Student 建议写全
        // ----------------------

        super.print(); // Person
    }

    public void print () {
        System.out.println("Student");
    }
}


// Person.java
public class Person {
    protected String name = "kuangshen";

    public Person() {
        System.out.println("Person无参构造执行了");
    }


    public Person(String name) {
        this.name = name;
    }

    public void print () { // 如果是private,在子类中super.print()也不能调用这个方法
        System.out.println("Person");
    }
}

多态

方法的重写

重写只针对方法。属性没有重写!

重写的前提是继承,子类重写父类的方法!

    1. 子父类具有同名同参数的方法
    1. 修饰符:范围相对父类可以扩大,但不能缩小:public>protected>default>private
    1. 返回值类型必须相同(注:如果是父类方法返回值是A类,子类重写的方法返回值可以是A类或者A类的子类)
    1. 抛出的异常:范围,可以被缩小,但是不能扩大。 ClassNotFoundException(小) Exception(大)
    1. 方法体不一样(一样的话,重写没有意义)

为什么要重写?
父类的功能,子类不一定需要,或者不一定满足子类的需求。

不能被重写的方法:

    1. private修饰的方法
    1. static修饰的方法,不叫重写,被static修饰的方法(类.方法())与实例调用方法(实例.方法())的方式都不同:子类和父类中同名同参数的方法要么都声明为非static(考虑重写),要么都声明为static的(不是重写)。
    1. final修饰的方法
// Application.java
public class Application {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Person person = new Teacher();

//        teacher.add(); // Teacher类
//        person.add(); // Person类

        teacher.add(); // Teacher类
        person.add(); // Teacher类
    }
}

// Teacher.java
public class Teacher extends Person {
//    public static void add () {
//        System.out.println("Teacher类");
//    }
    public void add () {
        System.out.println("Teacher类");
    }
}

// Person.java
public class Person {
//    public static void add () {
//        System.out.println("Person类");
//    }
    public void add () {
        System.out.println("Person类");
    }
}

idea会对方法的重写做标记

55.png

多态

多态的前提:1、类的继承关系;2、方法的重写。

  1. 对象的多态:父类的引用指向子类的对象。
// Man、Woman extends Person
// Person eat() walk()
// Man eat() walk() earnMoney()
// Woman eat() walk() shopping()

// 多态性
Person p2 = new Man();
Person p3 = new Woman();

p2.eat(); // 男人大口大口地吃饭!
p3.walk(); // 女人窈窕地走路!

// p2.earnMoney(); // 报错,只能调用父类和子类中重写父类的方法。
  1. 多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法

编译的时候,发现Person类有eat和walk方法(Ctrl + 鼠标左键 进到父类的方法中),没有earnMoney方法(报错);执行的时候,发现执行的是子类重写父类的方法

总结:编译看左边,运行看右边。

// Son extends Father 子类重写了父类的print方法 子类独有一个talk方法

Father father = new Son();

father.print(); // 编译时,father有print方法,可以调用
// father.talk(); // 不可以调用,编译报错
((Student)father).talk(); // 类型强转后,可以调用

多态注意点:

  1. 父类和子类,有联系,一定要有父子之间的关系才能进行类型转换,String s = new Student(); 这样是不行的。类型转换异常: ClassCastException
  2. 多态的使用,只体现在方法上(方法编译看左边,运行看右边),属性是不行的(属性编译和运行都是看左边)。
多态的必要性
  1. 如果没有多态,则必须要写很多重载的方法
class Order {
    public void method(Object obj) { // obj = new String();
        // ...
    }
}

再例如:java.lang.Object上的equals(Object obj)方法

String str = "1";
str.equals("1");
  1. 没有多态,抽象类和接口将没有意义。
多态在内存中的样子

image.png

向上转型和向下转型

怎么才能调用子类特有的属性和方法?

子类可以非常自然地转换成父类,称为多态(向上转型);

    Person p = new Man();

但是父类转换成子类则需要强制转换(也称为向下转型)。

    Man man = (Man)(new Person()); // 编译时通过,运行时不通过
    
    Person p1 = new Man();
    Man m1 = (Man)p1;

向下转型在多态的前提下,再向下转型为之前的类型。 向下转型不能乱转,否则运行时会报错ClassCastException

instanceof

语法:实例对象 instanceof 类
当对象是右边类或子类所创建对象时,返回true;否则,返回false。
实例对象跟类风马牛不相及的编译报错。

// Teacher extends Person
// Student extends Person
/*
    Object Person Teacher
    Object Person Student
    Object String
*/

public static void main(String[] args) {
    Student student = new Student();
    System.out.println(student instanceof Student); // true
    
    Object object = new Student();
    Person person = new Student();

    System.out.println(object instanceof Object); // true
    System.out.println(object instanceof Person); // true
    System.out.println(object instanceof Student); // true
    System.out.println(object instanceof String); // false
    System.out.println(person instanceof String); // 实例对象跟类风马牛不相及的编译报错
}

抽象类

public abstract class Action {
    public abstract void doSomething(); // 用在方法上,没有方法体
}
public class B extends Action{
    @Override
    public void doSomething() {
        System.out.println("实现抽象类");
    }
}

接口