面向对象的特征之二:继承性

继承性的好处:

  1. 减少了代码的冗余,提高了代码的复用性
  2. 便于功能的扩展
  3. 为之后多态性的使用,提供了前提

继承性的格式:

class A extend B{}

A:子类、派生类、subclass

B:父类、超类、基类、superclass

体现:一旦子类A继承了父类B以后,子类A中就获取了父类B中声明的所有属性和方法。此外,子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的扩展。子类和父类的关系,不同于子集和集合的关系。

特别地,父类中声明为private的属性或方法,子类继承后,仍然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。

java中关于继承的规定:

  1. 一个类可以被多个子类继承。
  2. 一个类只能有一个父类:java中类的单继承。而C++可以有多个父类。
  3. 子类和父类是相对的概念。
  4. 子类直接继承的类叫直接父类,间接继承的父类叫间接父类。
  5. 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。

image-20220307210901947

快捷键:ctrl+T可以看到继承结构

如果没有显示的声明一个类的父类的话,则此类继承与java.lang.Object类

所有的java类(除java.lang.Object类之外)都直接或间接的继承与java.lang.Object类。意味着所的java类具有java.lang.Object类声明的功能。

eclipse中debug调试的使用

设置断点:在一行代码左侧双击添加断点

常用操作

操作 作用
step into跳入(F5) 进入当前行所调用的方法中
step over跳过(F6) 执行完当前行的语句,进入下一行
step return跳回(F7) 执行完当前行所在的方法,进入下一行
drop to frame 回到当前行所在方法的第一行
resume恢复 执行完当前行所在断点的所有代码,进入下一个断点,如果没有断点就执行到结束
terminate终止 停止JVM,后面的程序不会再执行

执行debug:右键——>debug as——>java application

如果step into失效,可能是使用的jre而不是jdk。在debug as——>debug configuration——>preferences——>选中jre,remove——>add——>standard WM——>next——>jre home——>directory——>找到jdk

方法的重写

override/overwrite

子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。

重写以后,当创建子类的对象以后,通过子类对象调用子父类中同名同参数的方法时,实际执行的是子类重写父类的方法。

重写方法的声明:权限修饰符 返回值类型 方法名(形参列表)throws 异常的类型{}

约定俗成:子类中的叫重写的方法,父类被重写的方法叫被重载的方法

1、子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同;

2、子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。特殊情况:子类不能重写父类中声明为private权限的方法;

3、子类返回值类型不大于父类:

父类 子类
void void
A类(例如Object类) A类或A的子类(例如String类)
基本数据类型 相同的基本数据类型

4、子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理中讲);

*子类和父类中的同名同参数的方法要么都声明为非static的(重写),要么都声明为static的(不是重写)

protected权限修饰符修饰结构:不同包中的类,如果是子类,则可以使用该结构,如果不是子类,则不可以使用。

super关键字的使用

子类的方法或构造器中使用"super.属性"或"super.方法",显示地调用父类中的属性或方法。通常省略"super."。

属性:子类不能重写父类的属性。当子类和父类中定义了同名属性时,可以用super.调用父类中的属性。

方法:子类重写了方法之后,如果仍需使用父类原定义的方法,必须显示地使用super.来调用。

构造器:在子类构造器中使用"super(形参列表)"的方式,调用父类中声明的指定的构造器。"super(形参列表)"必须声明在子类构造器的首行。在类的构造器中,针对于"this(形参列表)""super(形参列表)"只能二选一 。在构造器首行,既没有调用"this(形参列表)"也没有调用"supper(形参列表)",则默认调用父类中空参的构造器"super()"。在一个类的多个构造器中,至少有一个类的构造器使用了"super(形参列表)"。与最多有n-1个构造器使用this相对应。

super构造器的例子:

class Person{
    private String name;
    private int age;

    public Person(){//必须提供空参的构造器,否则子类继承时找不到super()会报错

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

//子类,可以在另一个包下
public class Student extends Person{
    String major;

    public Student(){

    }
    public Student(String major){
        this.major=major;
    }
    public Student(String name,int age,String major){
        super(name,age);//super的使用
        this.major=major;
    }
}

子类对象实例化的过程

从结果上看:子类继承父类以后,就获取了父类中声明的属性或方法。创建子类的对象,在堆空间中就会加载多有父类声明过的属性。

从过程上看:由于子类的构造器至少含有一个super,当我们通过子类构造器创建子类对象时,一定会直接或间接地调用父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。(每个构造器都会将本类的结构加载到内存中。)正因为加载过所有父类的结构,所以内存中有所有父类的结构,子类对象才可以直接调用。

*明确:虽然创建子类对象时调用了父类的构造器,但是自始至终只创建过一个对象。(其他构造器中创建的对象的地址不对外暴露?需要了解编译原理方面内容)

image-20220311173245910

面向对象的特质之三:多态性

对象的多态性:父类的引用指向子类的对象,或子类的对象赋给父类的引用

Java引用变量有两个类型,编译时类型运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。方法的调用是在运行时确定的,称为动态绑定。若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism),简称:编译看左边,运行看右边。

对象的多态——在Java中,子类的对象可以替代父类的对象使用。一个变量只能有一种确定的数据类型,但一个引用类型变量可能指向(引用)多种不同类型的对象。子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。

多态性的适用前提:1、有类的继承关系,2、有方法的重写,3、只适用于重写的方法,不适用于属性、新添加的方法。(一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。属性是在编译时确定的,编译时父类,没有子类新增的结构,因而编译错误。)

虚拟方法调用(Virtual Method Invocation)(多态情况下):子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法(Virtual Method),父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。我们在编译期,只能使用父类中声明的方法,在运行期,我们实际执行的是子类重写父类的方法。

虚拟方法调用举例:

public class PersonTest{
    public static void main(String[] args){
        Person p1=new Person();//正常的方法调用
        //假设子类Man和Woman继承于Person
        Person p2=new Man();
        Person p3=new Woman();
        p2.eat();//虚拟方法调用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法
        //p2.earnMoney();//不能使用子类特有的方法
    }
}

为何要有多态性?多态性应用举例

/**
* 举例一:
*/
public class AnimalTest{
    public static void main(String[] args){
        AnimalTest test=new AnimalTest();
        test.func(new Dog());
        test.func(new Cat());
    }
    public void func(Animal animal){//可以用子类的对象来赋值:animal=new Dog();
        animal.eat();
        animal.shout();
    }
}
class Animal{
    public void eat(){
        System.out.println("动物:吃");
    }
    public void shout(){
        System.out.println("动物:叫");
    }
}
class Dog extends Animal{
    public void eat(){
        System.out.println("狗吃骨头");
    }
    public void shout(){
        System.out.println("汪汪汪!");
    }
}
class Cat extends Animal{
    public void eat(){
        System.out.println("猫吃鱼");
    }
    public void shout(){
        System.out.println("喵喵喵!");
    }
}
//输出语句:
狗吃骨头
汪汪汪!
猫吃鱼
喵喵喵!
/**
* 举例二:例如Obect类中的equals(Object obj)
*/
class Oerder{
    public void method(Object obj){

    }
}
/**
* 举例三:建立和数据库的连接
*/
class Driver{
    public void doData(Connection conn){//conn=new MySQLConnection(); / conn=new OracleConnection();
//        conn.method1;
//        conn.method2;
//        conn.method3;
    }
}

面试题:谈谈你对多态性的理解?

1、实现代码的通用性

2、Object类中定义的public boolean equals(Object obj){}

​ JDBC使用Java程序操作(获取数据库连接、增删改查CRUD)数据库(MySQL、Oracle、DB2、SQL Server)

3、抽象类、接口的使用肯定体现了多态性。(抽象类、接口不能实例化)

例题一:多态是编译时行为还是运行时行为?如何证明?

package com.atguigu.test;

import java.util.Random;
//面试题:多态是编译时行为还是运行时行为?
//证明如下:
class Animal  {
    protected void eat() {
        System.out.println("animal eat food");
    }
}
class Cat  extends Animal  {
    protected void eat() {
        System.out.println("cat eat fish");
    }
}
class Dog  extends Animal  {
    public void eat() {
        System.out.println("Dog eat bone");
    }
}
class Sheep  extends Animal  {
    public void eat() {
        System.out.println("Sheep eat grass");
    }
}
public class InterviewTest {
    public static Animal  getInstance(int key) {
        switch (key) {
        case 0:
            return new Cat ();
        case 1:
            return new Dog ();
        default:
            return new Sheep ();
        }
    }
    public static void main(String[] args) {
        int key = new Random().nextInt(3);
        System.out.println(key);
        Animal  animal = getInstance(key);
        animal.eat(); 
    }
}

例题二:

package com.atguigu.test;
//考查多态的笔试题目:
public class InterviewTest1 {
    public static void main(String[] args) {
        Base base = new Sub();
        base.add(1, 2, 3);
//      Sub s = (Sub)base;
//      s.add(1,2,3);
    }
}
class Base {
    public void add(int a, int... arr) {
        System.out.println("base");
    }
}
class Sub extends Base {
    public void add(int a, int[] arr) {
        System.out.println("sub_1");
    }
//  public void add(int a, int b, int c) {
//      System.out.println("sub_2");
//  }
}

不打开注释:两个add构成重写,输出为sub_1。

打开第二处注释:由于在运行时调用重载的方法,与父类构成重载的是第sub_1,所以输出sub_1而非sub_2。

打开第一处注释:由于强制类型装换,所以输出sub_2。

小结:方法的重载与重写

1.二者的定义细节:略

2.从编译和运行的角度看: 重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。

所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法, 这称为“早绑定”或“静态绑定”;而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”

引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”

关键字:instanceof

向上转型与向下转型

image-20220311181006603

问题的引入:如何调用调用子类特有的方法?使用强制类型转换符,向下转型。

//接上文Person p2=new Man();
Man n1=(Man)p2;
m1.earnMoney();
//使用强制类型转换时,运行时可能出现ClassCastException的异常
//Woman w1=(Woman)p2;
//w1.goShopping=true;
if(p2 instanceof Woman){
    Woman w1=(Woman)p2;
    w1.goShopping();
}

a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。要求a所属的类与类A必须是子类和父类的关系,否则编译错误。

为了避免在向下转型时出现ClassCastException的异常,先进行instanceof的判断,返回true时再向下转型,否则不进行向下转型。

B是A的父类,有:若a instanceof A为true,则a instanceof B也为true。

Object类的使用

Object类是所有Java类的根父类,如果在类的声明中未使用extends关键字指明其父类,则默认父类 为java.lang.Object类。

Object类中的功能(属性、方法)就具有通用性。

属性:无

方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll() ......

Object类只提供了一个空参的构造器

垃圾回收的说明:

垃圾回收机制关键点

垃圾回收机制只回收JVM堆内存里的对象空间。

对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力

现在的JVM有多种垃圾回收实现算法,表现各异。

垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。

可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。

程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有
一些效果,但是系统是否进行垃圾回收依然不确定。

垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一
个新的引用变量重新引用该对象,则会重新激活对象)。

永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。

补充:数组也作为Object类的子类出现,可以调用Object类中声明的方法

​ 引用类型有:类、数组、接口。其中只有接口是独立于类体系之外的

public class ReviewTest{
    @Test
    public void test1(){
        int[] arr=new int[]{1,2,3};
        print(arr);//[I@xxxxxxxx
        System.out.println(arr.getClass());//class [I
        System.out.println(arr.getClass().getSupperclass());//java.lang.Object
    }
    public void print(Object obj){
        System.out.println(obj);
    }
}

==操作符与equals方法

==运算符:基本类型比较值(自动类型提升),引用类型比较引用(是否指向同一个对象)

用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错

equals方法:Object类中定义的equals()和==相同

public boolean equals(Object obj){
    return (this==obj);
}

特殊:像String、Data、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的“实体内容”。

System.out.println(cust1.equals(cust2));//false
System.out.println(str1.equals(str2));//true

重写自定义类的equals方法,比较两个对象的实体内容是否相同。可以直接让eclipse生成equals方法。

//手动重写equals方法(有漏洞)
@Override
public boolean equals(Object obj){
    //是否指向同一个实体
    if(this==obj){
        reuturn true;
    }
    //如果不是同一个类则为false
    if(obj instanceof Customer){
        //向下转型成自定义类,才能调用类的属性
        Customer cust=(Customer)obj;
        //比较两个对象的属性是否相同
        return(this.age==cust.age && this.name.equals(cust.name));
    }
    reuturn false;
}

特殊的String

String s1="BB";
String s2="BB";
System.out.println(s1==s2);//true
String s3=new String("BB");
System.out.println(s1==s3);//false

因此,在比较字符串型的属性时,应该用equals方法,不用==。

toString类的使用

当我们输出一个对象的引用时,实际上就是调用了当前对象的toString(),前提是引用非null。

Object类中toString()的定义:

public String toString(){
    return getClass().getName()+"@"+Integer.toHexString(hashCode());
}

像String、Data、File、包装类等都重写了Object类中的toString()方法。

public class ToStringTest{
    public static void main(String[] args){

        Customer cust1=newCustomer("Tom",21);
        System.out.println(cust1.toString());
        System.out.println(cust1);//和上一行输出同样的内容,包名@虚拟地址

        String str=new String("MM");
        System.out.println(str);//MM,因为String重写了toString方法
    }
}

自定义类也可以重写toString()方法,当调用调用此方法时,返回对象的“实体内容”

@Override
public String toString(){
    return "Customer[name="+name+", age="+age+"]";
}

引用为null的情况:

public void test3(){
    String s=null;
    System.out.println(s);//null
    System.out.println(s.toString());//出现NullPointerException
}

Java中的JUnit单元测试

步骤:

  1. 选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
  2. 创建Java类,进行单元测试
    此时的Java类要求:①此类是public的,②此类提供公共的无参的构造器
  3. 此类中声明单元测试方法
    此时的单元测试方法:方法的权限是public,没有返回值,没有形参
  4. 此单元测试方法上需要声明注解@Test,并在单元测试类中导入import org.junit.Test;
  5. 声明好单元测试方法以后,就可以在方法体内测试相关代码
    左键双击单元测试方法名,右键:run as - JUnit Test
    如果执行结果没有任何异常:绿条
    如果执行结构出现异常:红条
import org.junit.Test;
public class JUnitTest{
    @Test
    public void testEquals(){
        String s1="MM";
        String s2="MM";
        System.out.println(s1.equals(s2));
    }
}

main方法是静态的,所以要调用类的属性需要先造类的对象;如get、set的普通方法,可以直接调用类的对象,无需造对象。测试类的方法也可以直接调用属性。

测试类中可以写多个测试方法,都是单独的运行

在开发中可以写@Test之后自动导入路径和包

包装类Wrapper的使用

针对八种基本数据类型定义相应的引用类型—包装类(封装类)

有了类的特点,就可以调用类中的方法,Java才是真正的面向对象

image-20220313094125692

基本类型、包装类与String类间的转换:

image-20220313122533364

基本类型、包装类与String类之间,要转换成谁,就找谁的方法。

建议从下往上阅读test1到test5:

public class WrapperTest{
    //String类型--->基本数据类型、包装类:调用包装类的parseXxx(String s)
    @Test
    public void test5(){
        String str1="123";//如果不是纯数字,会报NumberFormatException异常
        //错误的情况:
        //int num1=(int)str1;
        //Integer in1=(Integer)str1;
        int num2=Integer.parseInt(str1);
        System.out.println(num2+1);//124

        String str2="true1";
        boolean b1=Boolean.parseBoolean(str2);
        System.out.println(b1);//false,原因见下test1
    }

    //基本数据类型、包装类--->String类型
    @Test
    public void test4(){
        //方式一:连接运算
        int num1=10;
        String str1=num1+"";
        //方式二:调用String的valueOf方法
        float f1=12.3f;
        String str2=String.valueOf(f1);
        System.out.println(str2);//"12.3"
        Double d1=new Double(12.4);
        String str3=String.valueOf(d1);
        System.out.println(str3);//"12.4"
    }

    /*
     * JDK5.0新特性:自动装箱与拆箱
     * 因此可以把基本数据类型和包装类看成一个整体
    */
    @Test
    public void test3(){
        //自动装箱
        int num2=10;
        Integer in1=num2;
        boolean=true;
        Boolean b2=b1;
        //自动拆箱
        int num3=in1;
    }

    //包装类--->基本数据类型:调用包装类Xxx的xxxValue()
    @Test
    public void test2(){
        Integer in1=new Integer(12);
        int i1=in1.intValue();
        System.out.println(i1+1);//13
    }

    //基本数据类型--->包装类:调用包装类的构造器
    @Test
    public void test1(){
        int num1=10;
        Integer in1=new Integer(num1);
        System.out.println(in1.toString());//10
        Integer in2=new Integer("123");
        System.out.println(in2.toString());//123,Integer重写的toString将字符串"123"转换为了123
        Integer in3=new Integer("123abc");
        System.out.println(in3.toString());//运行时报异常

        Float f1=new Float(12.3f);
        System.out.println(f1);//12.3,省略toString
        Float f2=new Float("12.3");
        System.out.println(f2);//12.3

        Boolean b1=new Boolean(true);
        System.out.println(b1);//true
        Boolean b2=new Boolean("TrUe");
        System.out.println(b2);//true,忽略大小写
        Boolean b3=new Boolean("true123");
        System.out.println(b3);//false,不会报异常,字符串只要长得不像true,就是false

        Order order=new Order();
        System.out.println(order.isMale);//false
        System.out.println(order.isFemale);//null
    }
}
class Order{
    boolean isMale;//默认值为false
    Boolean isFemale;//默认值为null
}

小面试题:

@Test
public void test3(){
    Integer i=new Integer(1);
    Integer j=new Integer(1);
    System.out.println(i==j);//false
    /*
     * Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
     * 保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值
     * 的范围在-128~127内时,可以直接使用数组总的元素,不用再去new了。目的是
     * 提高装箱效率
    */
    Integer m=1;
    Integer n=1;
    System.out.println(m==n);//true

    Integer x=128;//相当于new了一个Integer对象
    Integer y=128;
    System.out.println(x==y);//false
}

应用场景举例:

Vector类中关于添加元素,之定义了形参为Object类型的方法:v.addElement(Object obj);//基本数据类型--->包装类--->使用多态

最后更新于 2022-03-25