第六章、面向对象编程02

1、包

1.1 包的作用

  • 包可以区分名字相同的类
  • 方便管理类
  • 可以用于访问权限的控制

1.2 包的基本语法

//打包
package 关键字;
    
//包名
com.名字;
复制代码

实例,最长见的就是在idea中创建文件时,上面写的:

package com.oliver;

import java.util.Date;
复制代码

1.3 常用的包

  • java.lang.* : lang包是基本包,默认引入,不需要再引入
  • java.util.* : util包,系统提供的工具包, 工具类,使用Scanner
  • java.net.* : 网络包,网络开发
  • java.awt.* : 是做java的界面开发,GUI

引入包可以使用import,可以引入一个类,也可以引入包内全部的类

2、访问修饰符

之前提到过,类、方法或者参数可以设置访问级别,用于封装避免其他人使用,一共有下面四种:

  • 公开级别:用public修饰,对外公开
  • 受保护级别:用protected修饰,对子类和同一个包中的类公开
  • 默认级别:没有修饰符号,向同一个包的类公开
  • 私有级别:用private修饰,只有类本身可以访问,不对外公开

img

可以这样记忆,把它们都想成钱:

  • public就是慈善基金,给大家用的;
  • protected受保护的钱,就是遗产,自己、妻子(同包)、孩子(子类)可以使用;
  • default默认孩子要成家,家里就自己和妻子,这就是默认用的钱
  • private就是自己的小金库

可以修饰类的默认和public,其他不能。

自行测试,代码如下:

测试类A:

package com.oliver;

/**
 * @author :16140
 * @description :
 * @create :2022-08-07 14:45:00
 */
public class A {
    //测试属性
    public String name1 = "张三";
    protected String name2 = "李四";
    String name3 = "王五";
    private String name4 = "赵六";

    public static void main(String[] args) {
        A a = new A();
        a.test1();
    }

    public void test1(){
        System.out.println(name1);
        System.out.println(name2);
        System.out.println(name3);
        System.out.println(name4);
    }
}
复制代码

同包下创建B:

public class B {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.name1);
        System.out.println(a.name2);
        System.out.println(a.name3);
        //System.out.println(a.name4);	//不能访问
    }
}
复制代码

不同包下的子类,其实直接访问A对象还只能访问public,但是如果使用看继承过来的B对象,就可以访问public和protected了:

package com.oliver.test2;

import com.oliver.test1.A;

/**
 * @author :16140
 * @description :
 * @create :2022-08-07 15:00:00
 */
public class C extends A {
    public static void main(String[] args) {
        C c = new C();
        System.out.println(c.n1);
        System.out.println(c.n2);
    }
}
复制代码

不同包也不继承:

package com.oliver.test2;

import com.oliver.test1.A;

/**
 * @author :16140
 * @description :
 * @create :2022-08-07 15:08:00
 */
public class D {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.n1);
    }
}
复制代码

3、面向对象三大特征

封装、继承和多态。

  • 封装:开发者把数据保护在程序内部,只给使用者授权操作的方法
  • 继承:子类可以继承父类,这样就避免了重复创建类似的类
  • 多态:创建父类型的对象,指向子类型,程序在编译的时候可以根据子类型去自动创建相对的对象

3.1 封装

将属性和一些方法私有化(private),由公共方法去调用。

下面的代码是不能再直接设置值了,不太安全,这里属性私有化,提供了公开的set和get方法。

public class Encapsulation01 {
    public static void main(String[] args) {
        //Person person = new Person("z",1);    不可以这样做了
        Person person = new Person();
        person.setName("张三");
        person.setAge(20);
        System.out.println("我是" + person.getName() + ",今年" + person.getAge());
    }
}

class Person{
    private String name;
    private int 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;
    }
}
复制代码

3.2 继承

继承主要体现的是两个类的共同点,多态体现的是两个类的不同点。

比如创建一个银行卡,里面包含属性姓名,年龄,金额,再创建一个储蓄卡,同样是姓名,年龄,金额,这样就冗余了,不如提取出一个卡类,包含姓名,年龄,金额三个属性,让这两种卡继承这个类。

public class Extends01 {
    public static void main(String[] args) {
        BankCard bankCard = new BankCard();
        bankCard.setName("张三");
        bankCard.setAge(20);
        bankCard.setAmount(10000);
        System.out.println(bankCard.getMoney(100));

        CashCard cashCard = new CashCard();
        cashCard.setName("李四");
        cashCard.setAge(23);
        cashCard.setAmount(5000);
        System.out.println(cashCard.getMoney(300));
    }
}

//card类
class Card{
    public String name;
    public int age;
    public int amount;

    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 int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }

    public int getMoney(int count){
        int remain = getAmount() - count;
        setAmount(remain);
        return getAmount();
    }
}

class BankCard extends Card{

}

class CashCard extends Card{

}
复制代码

细节:

  • 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无 参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译
  • 如果希望指定去调用父类的某个构造器,则显式的调用一下super(),super在使用时,必须放在构造器第一行
  • super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
  • Java是单继承
  • 子类和父类必须满足is-a的关系才能使用继承,不能滥用
public class Extends02 {
    public static void main(String[] args) {
        //系统会默认让子类去使用父类的无参构造,所以要想让下面生效,必须使用super(带参)
        Cat cat = new Cat("xiaomao",20);
    }
}

class Animal{
    private String name;
    private int weight;

    public Animal() {
    }

    public Animal(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }
}

class Cat extends Animal{
    public Cat(String name, int weight) {
        //this(); this和super都必须在第一行,所以不能一起使用
        super(name, weight);
    }
}
复制代码

3.3 方法重写

既然可以使用继承,总不能所有子类都必须使用父类一样的方法吧,其实可以让子类去继承父类,然后修改或增加方法,完成功能的扩展,修改方法叫做方法重写或方法覆盖。

这个注意不要和方法重载搞混。

方法覆盖是书红:子类和父类方法名、返回值类型相同、参数也一样,这样就是方法覆盖,简单点理解,就是觉得父类的这个方法不好用,自己弄了个新的。

public class Override01 {
    public static void main(String[] args) {
        EnglishTeacher teacher1 = new EnglishTeacher("王老师");
        System.out.println(teacher1.teach(",英语流利的一批"));
        MathTeacher teacher2 = new MathTeacher("柳老师");
        System.out.println(teacher2.teach(",数学就没有我不会的"));
    }
}

class Teacher{
    public String name;

    public Teacher() {
    }

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

    public String teach(String introduction){
        return "大家好,我是" + this.name + introduction;
    }
}

class EnglishTeacher extends Teacher{
    public EnglishTeacher(String name) {
        super(name);
    }

    @Override
    public String teach(String introduction) {
        return "大家好,我是英语老师" + super.name + introduction;
    }
}

class MathTeacher extends Teacher{
    public MathTeacher(String name) {
        super(name);
    }

    @Override
    public String teach(String introduction) {
        return "大家好,我是数学老师" + super.name + introduction;
    }
}
复制代码

3.4 多态

仅仅能继承 + 覆盖就可以了吗?可以想象一下这样一个场景,你创建了一个类,里面写了很多行代码,而且这个类的方法和参数被无数个地方调用,这个时候这个类要被替换成另一个类,难道你要把所有的引用名字都替换一遍吗?

当然不是,这个其实是后面设计模式会详细介绍的一点,假如说你拿了个电视的遥控器去操作电视,如果想操作空调的话就只能拿起空调遥控,那么有没有可能这个遥控器能打开很多的电器设备,不需要换遥控器(引用呢),这里的多态也可以满足这个功能,主要记住一点就是父类型引用指向子类型对象。

语法如下:

父类型 父类变量名 = new 子类型1();

父类型 父类变量名 = new 子类型2();
复制代码

比如这个例子:

public class PloyMethod {
    public static void main(String[] args) {
        A a1 = new B();
        A a2 = new C();
        a1.sayHello();//hello B
        a2.sayHello();//hello C
    }
}

class A{
    public void sayHello(){
        System.out.println("hello A");
    }
}

class B extends A{
    @Override
    public void sayHello() {
        System.out.println("hello B");
    }
}

class C extends A{
    @Override
    public void sayHello() {
        System.out.println("hello C");
    }
}
复制代码

对象在编译器是不会去确定具体的类型的,只看父类,而运行时,是通过子类型去确定具体是那种类型的对象。(动态绑定机制,Java有这样的机制,并不是所有语言都有)

这里注意类型引用和具体对象类型不一样时,需要向下或者向上转型,向上转型还好说,不会出现什么问题,向下转型需要强转。比如我说一只猫是一个动物是对的,一个动物是一只猫就不一定对了,所以强转需谨慎。

4、Object类

4.1 equals方法

基本数据类型通过"=="比较可以得到正确的值,比如2==2,但是引用类型就不一定了,因为它比较的是内存地址,如果参数都相同的两个对象,它们的内存地址不一定一样,使用"=="去判断就不合适了。

那么可以使用equals方法,其实equals判断的 也是对象是否地址一致,不过我们学了方法重写,父类型Object的equals方法,在子类型里可以重写其方法。

比如我们看看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;
}
复制代码

我们也可以自己写一个equals方法,比如要比较银行账户和自己输入的用户名密码是否一致,那肯定要有一个账户,如果判断这个账户对象和自己是否一致,如果equals没重写那肯定基本不会通过验证了,所以要重写。

package com.oliver.obj;

import java.util.Objects;

/**
 * @author :16140
 * @description :
 * @create :2022-08-07 16:51:00
 */
public class Person {
    private String name;
    private String password;

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

    public static void main(String[] args) {
        Person person1 = new Person("张三","123456");
        Person person2 = new Person("张三","123456");
        System.out.println(person1 == person2);     //false
        System.out.println(person1.equals(person2));//true
    }

    public boolean equals(Object obj){
        //如果引用一致,那肯定是一个对象
        if (this == obj) return true;
        //如果二者类型都不一致,肯定不是一个对象
        if (obj == null || this.getClass() != obj.getClass()) return false;
        //二者不相同,而且不是null,类型也一致,就可以强转了
        Person person = (Person) obj;
        return Objects.equals(name,person.name) && Objects.equals(password,person.password);
    }
}
复制代码

4.2 hashCode方法

返回这个对象哈希码值,使用这个方法可以提高性能。

String类中的hashCode方法:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
复制代码

比较两个对象的时候,如果先比较hashcode,一致了再比较其他能提高很多效率。

4.3 toString方法

默认返回值是:全类名+@+哈希值的十六进制

比如上一个person1的值:com.oliver.obj.Person@1b6d3586

我们可能希望输出更好的结果,就需要重写toString方法,这个不必要介绍看一下idea给我们重写的toString方法实例即可:

@Override
public String toString() {
    final StringBuffer sb = new StringBuffer("Person{");
    sb.append("name='").append(name).append('\'');
    sb.append(", password='").append(password).append('\'');
    sb.append('}');
    return sb.toString();
}
复制代码

4.4 finalize方法

  • 当对象被回收时,系统会自动调用该对象的finalize方法,子类可以重写该方法
  • 当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来 销毁该对象,在销毁该对象前,会先调用finalize方法
  • 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制
public class FinalizeTest {
    public static void main(String[] args) {
        Rubbish rubbish = new Rubbish();
        rubbish = null;
        System.gc();	//不使用它的时候,测试很多回没有被回收
        //添加了以后很快就打印了垃圾被回收了
        System.out.println("回收完毕");
    }
}

class Rubbish{
    public Rubbish() {
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("垃圾被回收了");
        super.finalize();
    }
}
复制代码

4.5 Idea工具使用

debug的几个按键介绍:

  • F7(跳入) :跳入方法内
  • F8(跳过) :逐行执行代码
  • shift+F8(跳出)
  • F9(resume,执行到下一个断点)

猜你喜欢

转载自juejin.im/post/7129064608416923679