Java基础自学记录七

面向对象编程(中级部分)

此为笔者个人笔记,如有错误还请指出

关于IDEA

只要不在DOS下允许,编码都用UTF-8

在IDEA中,当run时,会先编译成一个.class文件,再运行

.class文件在out文件夹中

IDEA快捷键盘

  • 删除当前行 ctrl + d

  • 复制当前行 ctrl + alt + c

  • 补全代码 alt + /

  • ctrl + / 添加注释 去除注释

  • 快速导入类 alt + enter

  • ctrl + shift + l 查找

  • ctrl + alt + l 格式对齐 非常舒服

  • 查看类的层级关系 ctrl + h

  • ctrl + B可以选择定位到哪个类的方法

  • 自动分配变量名用 .var (写完new 方法后.var自动生成new 的对象名)

模板

使用以及设置方法:

public class TestTemplate {
    //main模板生成main方法
    public static void main(String[] args) {
        //sout模板生成System.out.println();
        System.out.println("sout yyds");
        //fori模板生成for循环
//        for (int i = 0; i < ; i++) {
//
//        }
        
    }
}

Java基础自学记录七_第1张图片

包基本介绍

当类很多时,可以把功能近似的类放在同一包中管理

包的本质是创建不同的文件夹来保存类

可以通过打包在不同包下来区分名称相同的类

通过import …;来引用包

包命名

  • 只能包含数字、字母、下划线和.,注意不能用关键字或者保留字

  • com.公司名.项目名.业务模块名

建议需要上面类就具体导到哪个类就行,不建议用import.util.*;这样访问

package放在类最上面,一个类只能有一个这样的语句

import放在package 语句下面,可以有多个import 语句


访问修饰符

控制访问范围

  • public 修饰的对外公开

  • 用protected修饰的对子类和同一个包中的类公开

  • 默认 对一个包的类公开

  • private 只有类本身可以访问,不对外公开

注意只有默认和public 可以修饰类

封装介绍encapsulation

把抽象出来的数据和其操作封装在一起,数据保护在内部,程序的其他部分只能通过被授权的方法才能对数据进行操作 用户只运用方法就可以,无需在乎其内部实现

好处

  • 隐藏实现细节

  • 对数据进行验证,保证安全合理

实现方法: 提供公共的setter和getter

封装与构造器

为了防止通过构造器来设置与访问封装的数据,可以在构造器中使用setter和getter,而不要直接 this.xxx = xxx;

//我们可以将setter写在构造器中,这样仍然能达到封装的效果
public Person(String name, int age, int salary){
    setName(name);
    setAge(age);
    setSalary(salary);
}

继承extends

基本语法

class 子类 extends 父类{
    //...
}

父类又叫做超类,基类,子类又叫做派生类

子类继承父类后要写构造器,如果构造方法与父类一致可以直接super(…参数);

细节

  • 子类继承了所有属性和方法,但私有属性和方法不能在子类 直接 访问,可以间接的通过 公共的 方法访问 。注意子类确实继承了 所有的 属性和方法。

  • 子类必须调用父类的构造器,完成父类的初始化

  • 创建子类时,不管使用的是子类的哪个构造器,默认条件下都会去调用父类的无参构造器,如果父类没有提供无参构造器,必须在子类的构造器中用super()去指定调用哪个父类的构造方法 否则,编译不会通过 这个的原理是编译器在子类的构造器中默认加了一个super()

Java基础自学记录七_第2张图片

public Pupil(String name, int age, double score) {
    //
    setScore(score);
    this.name = name;
    this.age=age;
    System.out.println("调用子类有参构造器");
}

Java基础自学记录七_第3张图片

public Pupil(String name, int age, double score) {
    //
    super(name, age, score);
    System.out.println("调用子类有参构造器");
}

Java基础自学记录七_第4张图片

  • super在使用时,必须放在第一行

  • super() 和 this() 都只能放在构造器第一个,且两者不能同时出现

  • java的所有类都是Object的子类 ctrl + h可以看到继承关系

  • 父类构造器的调用不限于直接父类,将向上一直追溯到Object类(顶级父类)

Java基础自学记录七_第5张图片

  • 子类最多只能继承一个父类,如果想让A类即继承B类又继承C类,可以让B继承C类再A类继承B类

  • 不能滥用继承,子类和父类之间必须满足is - a的逻辑关系(例如Cat is an animal)

继承的本质分析

public class Run {
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println(son.name);
        System.out.println(son.salary);
        System.out.println(son.hobby);
    }
}
class Grandpa{
    String name="爷爷";
    String hobby="玩";
}
class Father extends Grandpa{
    String name="爸爸";
    double salary=10000.1;
}
class Son extends Father{
    String name="儿子";
    int age=14;
}


//输出:
儿子
10000.1

规则:1、看子类有没有该属性,有就访问并返回信息 2、没有就看上一层父类,有就访问并返回信息,没有就继续访问上一层父类 3、接着这样操作,直到找到有此属性的父类

Exercise

public class Exercise01 {
    public static void main(String[] args) {
        B b = new B();
    }
}

class A {
    A() {
        System.out.println("a");
    }

    A(String name) {
        System.out.println("a  name");
    }
}

class B extends A {
    B() {
        this("abc");
        System.out.println("b");
    }

    B(String name) {
        //默认有super()
        System.out.println("b   name");
    }
}

//运行结果:
a
b   name
b
//注意这里输出的  a   是由 B(String name)  调用的,B()使用了this(),因此不能再使用隐藏的super();
//预期结果:
我是A类
hahaha我是B的有参构造
我是c类的有参构造
我是c类的无参构造

Super关键字

作用:1、调用父类构造器;2、当子类中有与父类中的成员重名时,为了访问父类的成员,必须使用super,如果没有重名成员,super和this以及直接访问是一样的效果

好处:1、分工明确,父类属性由父类初始化,子类属性由子类初始化

细节:

  • super的访问不限于直接父类,如果爷爷类和本类中都有同名的成员,可以使用super去访问爷爷类成员, 但注意多个基类中都有同名成员时,super访问遵循就近原则。

Override 方法重写(注意不是方法重载)

子类有一个方法,和其某个父类的方法的名称、返回类型、参数 一样,那么我们就说子类的这个方法覆盖了父类的方法

public class Animal {
    public void cry(){
        System.out.println("动物在叫");
    }
}
public class Dog extends Animal {
    @Override
    public void cry(){
        System.out.println("小狗在叫");
    }
}

注意

  • 子类方法的参数、方法名称要和父类的参数、方法名称 完全一样

  • 子类的返回类型要和父类的返回类型 一样 或者 是父类返回类型的子类 例如父类方法返回类型是 Object 子类返回类型是 String

  • 子类方法不能缩小父类方法的访问权限 但是可以 扩大

注意方法重写(Override)和方法重写(Overload) 的区分!!!

Java基础自学记录七_第6张图片

Exercise

public class first {
    public static void main(String[] args) {
        Person mxy = new Person("mxy", 19);
        Student yll = new Student("yll", 18, "2021212121", 85.01);
        mxy.say();
        yll.say();
    }
}
public class Person {
    private String name;
    private int age;

    public Person(){
        setName("无名氏");
        setAge(0);
    }

    public Person(String name, int age){
        setName(name);
        setAge(age);
    }

    public void say(){
        System.out.println("你好!我叫"+name+",今年"+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;
    }
}
public class Student extends Person {
    private String id;
    private double score;

    public Student(){
        setId("");
        setScore(0);
    }

    public Student(String name, int age, String id, double score){
        super(name, age);
        setId(id);
        setScore(score);
    }

    @Override
    public void say(){
        System.out.println("大家好!我叫"+getName()+",学号是"+id+",今年"
                +getAge()+"岁了,成绩是"+score+"分");
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}

多态

解决的问题:代码的复用性不高,且不利于维护

对象的多态

  • 一个对象的编译类型和运行类型可以不一致

父类的引用可以指向子类的对象

Animal animal = new Dog();

animal的编译类型是Animal,运行类型是Dog

Animal animal = new Animal();
animal = new Cat();

编译类型是Cat,运行类型是Cat,且运行类型可以变化

编译类型看等号左边,运行类型看等号右边

animal.cry();

具体运行的是哪个类的cry方法看运行类型

因此可以通过使用父类来广泛的接收/指向 子类的对象

注意事项

  • 前提:存在继承关系

  • 语法: 父类类型的引用 = new 子类对象();

  • 特点:编译类型看左边,运行类型看右边

  • 可以在访问权限范围内调用父亲的所有成员,但是不能调用子类的特有成员,在编译阶段能调用哪些成员是由编译类型决定的

  • 不能调用子类特有成员

  • 相当于向上转型(父类引用指向的子类的对象) 最终运行效果看子类的实现

  • 调用子类和父类中都有的方法时用子类中的方法,子类中没有的方法用父类的方法

向下转型

上面提到的多态有一个问题:如果我确实需要调用子类特有的属性/方法时该怎么办?这就引出了多态的向下转型

语法: 子类类型 引用名 = (子类类型) 父类引用;

注意

  • 只能强转父类引用,不能强转父类对象
  • 父类的引用必须指向的是当前目标类型的对象,不能指向其他子类的对象
  • 向下转型后可以调用子类类型中所有的成员
  • 相当于有两个引用指向子类对象,一个是父类的引用,一个是子类的引用
  • 属性没有重写之说,属性的值看的是 编译类型!
  • instanceof比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类 在多态中判断的是 运行类型

向下转型时一定是属性看编译类型,方法看运行类型

public class Exercise02 {
    public static void main(String[] args) {
        Sub s = new Sub();
        System.out.println(s.cnt);
        s.say();
        Base b = s;
        System.out.println(b == s);
        System.out.println(b.cnt);
        b.say();
    }
}
class Base{
    int cnt = 10;
    public void say(){
        System.out.println(this.cnt);
    }
}
class Sub extends Base{
    int cnt = 20;
    public void say(){
        System.out.println(this.cnt);
    }
}


//向下转型后属性看编译类型,方法看运行类型,输出为
20
20
true
10
20

动态绑定机制

  • 当调用对象方法时,该方法会和该对象的内存地址/运行类型进行绑定

  • 当调用对象属性时,没有对象绑定机制,哪里声明哪里使用

public class DynamicBinding {
    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.getI()); //10
        System.out.println(a.sum()); //20
        System.out.println(a.sum1()); //30
    }
}
class A{
    public int i = 20;
    public int sum(){
        return getI() + 10;
    }
    public int getI(){
        return i;
    }
    public int sum1(){
        return i + 10;
    }
}
class B extends A{
    public int i = 10;

    @Override
    public int getI() {
        return i;
    }
}

分析:

方法有动态绑定机制,所以getI()绑定了运行类型,在a.getI()时调用的时B类中的方法,返回B类中的i;

在a.sum()时,运用了类的继承机制,在类B中找不到sum方法,就去其父类A中找,A中的sum方法调用getI方法,由于方法的动态绑定机制,这里调用的仍是B类中的方法,返回i = 10,运算结果为20

在a.sum1()时,运用了类的继承机制,使用A类中的sum1()方法,由于属性没有动态绑定机制,所以这里使用的是A类中的i值,运算结果为30

多态应用

多态数组 数组类型为父类类型,其中可以存放子类类型的数据

示例:
public class Person {
    private String name;
    private int age;

    public Person(String name, int age){
        setName(name);
        setAge(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;
    }

    public String say(){
        return name+"\t"+age;
    }
}
public class Student extends Person {
    private double score;

    public Student(String name, int age, double score){
        super(name, age);
        setScore(score);
    }

    public void Study(){
        System.out.println(super.getName()+"正在学习");
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String say() {
        return super.say()+"\tscore:"+getScore();
    }
}
public class Teacher extends Person {
    private double salary;

    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    public void teach(){
        System.out.println(super.getName()+"老师正在授课");
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String say() {
        return super.say()+"\tsalary:"+getSalary();
    }
}
public class PolyArr {
    public static void main(String[] args) {
        //要将1Person 2Student 2Teacher对象放在同一数组
        Person[] persons = new Person[5];
        persons[0] = new Person("1", 1);
        persons[1] = new Student("2",2,100);
        persons[2] = new Student("3",3,60);
        persons[3] = new Teacher("4",4,10000);
        persons[4] = new Teacher("5",5,20000);
        for (int i = 0; i < persons.length ; i++) {
            System.out.println(persons[i].say());
            //假如这里想要调用Student类中的特有方法Study怎么办?
            //可以判断运行类型+向下转型
            if(persons[i] instanceof Student){
                ((Student)persons[i]).Study();
            }else if(persons[i] instanceof Teacher){
                ((Teacher)persons[i]).teach();
            }
        }
    }
}

//输出
1	1
2	2	score:100.0
2正在学习
3	3	score:60.0
3正在学习
4	4	salary:10000.0
4老师正在授课
5	5	salary:20000.0
5老师正在授课

多态参数 方法定义的形参类型为父类类型,实参类型允许为子类类型

示例:
public class Employee {
    private String name;
    private double salary;

    public double getAnnual(){
        return 12 * getSalary();
    }

    public Employee(String name, double salary) {
        setName(name);
        setSalary(salary);
    }

    public String getName() {
        return name;
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
public class Manager extends Employee {
    private double bonus;

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        setBonus(bonus);
    }

    @Override
    public double getAnnual() {
        return super.getAnnual() + getBonus();
    }

    public void manage(){
        System.out.println("经理 "+super.getName()+"正在研究管理方法");
    }
}
public class OrdEmployee extends Employee {
    public OrdEmployee(String name, double salary) {
        super(name, salary);
    }

    @Override
    public double getAnnual() {
        return super.getAnnual();
    }

    public void Work(){
        System.out.println("员工 "+super.getName()+"正在工作");
    }
}
public class PolyParameter {
    public static void main(String[] args) {
        Employee e1 = new Manager("e1",10000,13.14);
        Employee e2 = new OrdEmployee("e2", 7000);
        Employee e3 = new Employee("e3", 6000);
        showEmpAnnual(e1);
        showEmpAnnual(e2);
        showEmpAnnual(e3);
        testWork(e1);
        testWork(e2);
        testWork(e3);
    }

    public static void showEmpAnnual(Employee e){
        if(e instanceof OrdEmployee){
            System.out.println(((OrdEmployee)e).getAnnual());//动态绑定机制
        }else if(e instanceof Manager){
            System.out.println(((Manager)e).getAnnual());//动态绑定机制
        }else{
            System.out.println("类型错误!");
        }
    }

    public static void testWork(Employee e){
        if(e instanceof OrdEmployee){
            ((OrdEmployee)e).Work();//动态绑定机制
        }else if(e instanceof Manager){
            ((Manager)e).manage();//动态绑定机制
        }else{
            System.out.println("类型错误!");
        }
    }
}

//输出:
120013.14
84000.0
类型错误!
经理 e1正在研究管理方法
员工 e2正在工作
类型错误!

Object类详解

Object类方法摘要

Java基础自学记录七_第7张图片

equals(Object obj)方法

== 与equals的区别?

== 是运算符 既可以判断基本类型,又可以判断引用类型。基本类型判断值是否相等;引用类型判断地址是否相等(是不是同一个对象)。
//注意这段代码输出为true
int a = 65;
float f = 65f;
System.out.println(a == f);
equals 是Object类中的方法,只能判断引用类型
查看jdk源码方法:将光标放在要查看的方法上面,输入ctrl+b即可
//String的equals方法源码:
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
//Object类的euqals方法源码:
public boolean equals(Object obj) {
    return (this == obj);
}
//每个引用类型都会重写equals方法

重写equals方法,用于自定义对象的比较

public class Person {  //extends Object
    private String name;
    private int age;
    private char gender;

    //重写Object的equals方法
    @Override
    public boolean equals(Object obj) {
        if(this == obj){
            return true;
        }
        if(obj instanceof Person){
            //向下转型
            Person p = (Person)obj;
            return this.age == p.age && this.name == p.name && this.gender == p.gender;
        }else{
            return false;
        }
    }

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public Person(String name, int age, char gender){
        setName(name);
        setAge(age);
        setGender(gender);
    }

    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;
    }

    public String say(){
        return name+"\t"+age;
    }
}
public class EqualsTest {
    public static void main(String[] args) {
        Person p1 = new Person("jetty", 12,'男');
        Person p2 = new Person("jetty", 12,'男');
        //如果不重写equals方法,比较会对比地址,从而输出false
        System.out.println(p1.equals(p2));
    }
}

hashCode方法

返回哈希码值,此方法主要是为了提高哈希表的性能

注意

  • 两个引用如果指向同一个对象,两者hashCode相等,指向不同对象一定不等
  • 哈希值是根据地址计算得来的,不能简单等价于地址
  • 在集合中hashCode如果需要重写的话也要重写

toString 方法

返回对象的字符串表示(包名+类名+hashCode十六进制)

Object的toString()方法源码:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public class EqualsTest {
    public static void main(String[] args) {
        Person p1 = new Person("jetty", 12,'男');
        System.out.println(p1.toString());
    }
}
//输出:
com.mat.object_.equals.Person@1b6d3586
//若不toString,直接System.out.println(p1);  会默认调用toString()

用途:(重写)用来打印或拼接对象的属性

直接输出一个对象时,toString方法会被默认的调用

finalize方法 (相当于析构函数)

当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法

当对象被回收时,系统会自动调用此方法,子类也可重写此方法,做一些释放资源的操作

当某个对象没有任何引用时,JVM就会认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,销毁对象前,会调用finalize方法

垃圾回收机制的调用是由系统来决定的,也可以通过System.gc()主动出发垃圾回收机制

重写finalize示例:

public class Test {
    public static void main(String[] args) {
        Cat kitty = new Cat("meow");
        kitty = null;
        System.gc();
        System.out.println("由于没有引用指向此Cat对象," +
                "系统调用了finalize方法将它回收了");
    }
}
class Cat{
    public String name;

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

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        //如果不主动调用System.gc();这里会没输出,由于JVM运用了特殊的算法来决定垃圾何时回收,这里后面讲到JVM的时候再讲
        System.out.println("调用了重写的finalize方法");
    }
}
//输出:
由于没有引用指向此Cat对象,系统调用了finalize方法将它回收了
调用了重写的finalize方法

开发中几乎不用重写finalize方法

断点调试

可以在debug过程中,动态的下断点

用debug来深入理解对象的构造顺序 理解动态绑定机制

OVER

你可能感兴趣的:(Java基础,java,开发语言,intellij-idea)