java-类与对象

super和this

相同点:

  1. 都是Java中的关键字
  2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段(使用stastic的无法进行使用)
  3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

不同点:

  1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
  2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
  3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
  4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有

(8)继承时代码执行顺序

1)) 在没有继承时,静态代码块、实例代码块、构造方法的执行顺序如下:

  • 静态代码块先执行,并且只执行一次,在类加载阶段执行
  • 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
  • final关键字

    final关键字用来修饰变量、成员方法以及类

    1))修饰变量

    使用final修饰变量,该变量只能被赋值一次,表示常量。示例如下:

    2))修饰类

    使用final修饰类,表示该类无法被继承,此种类称之为密封类。示例如下:

    3))修饰方法

    使用final修饰方法,代表该方法不能被重写,此种方法称之为密封方法。在重写阶段补笔记。现在还是不会..2/24

    (11)继承和组合

    和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。

多态

多态的概念:

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。

多态实现条件:

  1. 必须在继承关系上(向上转型) 父类 = 子类 方法
  2. 子类和父类有 同名的重写/覆盖 方法
  3. 通过父类对象的引用 去调用这个重写的方法

先大致总结,再分点讲解:

(1)向上转型

1))概念

在继承关系中,向上转型可以理解为:使用父类类型 引用 子类实例对象。

同时注意一点:Object类是所有类的父类

假设:Animal是父类,Dog是子类,那么如下便是向上转型的一种:

Animal animal = new Dog(); //使用父类类型引用子类实例对象

2)) 常见的可以发生 向上转型的三个时机

1,直接赋值:

Animal animal = new Dog(); //大到小 //直接用父类类型引用子类示例对象

2,形参为父类,实参为子类:

//设置的参数为父类,传递的类型为子类 public static void func(Animal animal) { // } public static void main(String[] args) { Dog dog = new Dog(); func(dog); } //实参传递子类对象,但是方法形参使用父类类型接收

3,返回类型为父类,实际返回值为子类:

#要求返回为父类 但是返回为子类 public static Animal func() { Dog dog = new Dog(); return dog; }

3))图示

(2)重写

1))概念

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

2))方法重写的规则

  1. 子类在重写父类方法时,一般必须与父类方法原型一致: 返回值类型、方法名 、(参数列表) 要完全一致
  1. 被重写的方法返回值类型可以不同,但是必须是具有父子关系的(例如父类中方法返回值为父类类型,子类方法返回值为子类类型),专业术语叫协变类型
  1. 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能使用 protected 修饰
  1. 父类被static、private修饰的方法、构造方法都不能被重写
  1. 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写

3))重写与重载的区别

最本质的区别就是:方法重写一般要求返回值、参数列表、方法名完全相同;而方法重载会要求参数链表不同。

4))println打印类解析:

在Java中,当我们直接对一个对象进行打印时,会得到该对象的地址,但是我们只需在该类中重写 toString 方法,再次运行时,就会执行我们写的 toString 方法,这就是方法的重写,示例如下:

原理如下:

(3)动态绑定

满足向上转型,并且子类、父类中有相同的重写方法时,当通过父类对象调用这个重写方法时,就会发生动态绑定。

动态绑定即是:当父类对象调用重写方法时,编译器会自动匹配执行 其向上转型的子类的重写方法。通俗来说就是:父类对象引用哪个子类对象,在调用重写方法时就会自动匹配该子类对象的重写方法。

动态绑定和静态绑定的对比:

(4)多态

当 当前环境具备向上转型、重写方法、动态绑定这几个条件时,就可以实现多态。如下:

上图是一个基本的多态应用示例,实现逻辑如下:

  1. 创建Shape类,再依次创建Triangle类、Rectangle类、Round类,使这三个类继承于Shape类
  2. 在Shape类中创建draw方法,再在每个子类中重写该方法,即完成重写方法条件
  3. 在主函数中创建一个Shape类型的集合,在其中放上子类实例对象;创建增强for循环,使用父类Shape循环遍历接收子类实例对象,完成向上转型;再在for循环内调用重写方法,完成动态绑定。

完成以上步骤即是实现多态。

那么到现在,我们就可以对多态的概念进行更加深入的总结:

多态概念:

当父类引用,引用的子类对象不一样的时候,调用这个重写的方法,所表现的行为是不一样的,我们把这种思想叫做多态。

(5)向下转型

将子类对象经过向上转型可以让父类引用调用子类的重写方法,但是却无法调用子类特有的方法,此时:将父类引用再还原成子类对象即可,这就是向下转型。

但是向下转型是不安全的,以上面多态来说:

1))当Shape引用的对象是Triangle类实例对象时:

编译通过

2))当Shape引用的对象不是Triangle类实例对象时:

报告异常:类型转换异常

2)))解决方法:可以使用 instanceof 关键字

instanceof关键字

用法:

if(实例对象 instanceof 类名) { } //若前者实例对象为该类的实例对象,则返回true,否则返回false

综上原因:

向上转换是父类类型引用子类实例对象,如:动物类引用狗类实例对象。狗属于动物,故属于一种包含关系;

向下转换是子类类型引用父类实例对象,如:狗类引用动物类实例对象。动物中包含狗,但不能说动物就是狗。所以当此时动物类实例对象为狗类实例对象时,强转成功,不会报错;但如果此时动物类实例对象是猫类、鼠类实例对象时,这些就和狗类无关,强转会失败,并报类型转换异常。

(6)多态的优缺点

优点:

能够降低代码的 "圈复杂度", 避免使用大量的 if - else

可扩展能力更强

缺点:

代码的运行效率降低。

抽象类

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

举例说明:

以上文多态中的举例为例,其父类中的draw方法根本不会被调用,于是我们可以将其构造为抽象方法

(1)抽象类的构造

在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。

//抽象类的创建 abstract class Test{ //抽象方法的创建,无需写方法体 public abstract void func(); }

需要注意的是:抽象类可以包含成员变量、成员方法、构造方法等。

(2)抽象类的特性

我们需要明白:抽象类的创建就是为了继承。

1))抽象类不能实例化对象

2))抽象方法的访问权限不能是private,且不能被final、static关键字修饰。因为抽象类在被继承后其抽象方法必须重写。

3))抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰

4))抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类

(3)抽象类、抽象方法示例

abstract class Family {

    String memberName;

    //定义抽象方法

    public abstract void doingNow();

    public Family(String name) {

       memberName = name;

    }

}

class Mother extends Family{

     public Mother() {

         super("妈妈");

     }

     public void doingNow() {

         System.out.println(this.memberName+"正在给我做饭");

     }

}

class Father extends Family{

     public Father() {

         super("爸爸");

     }

     public void doingNow() {

         System.out.println(this.memberName+"正在看报纸");

     }

 }

 public class Test{

     public static void main(String[] args) {

         //使用抽象类实现多态

        Family[] thisOne = {new Father(), new Mother()};

         for (Family one: thisOne) {

             one.doingNow();

         }

     }

 }

10,接口

(1)接口的概念

在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型

(2)语法规则

接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。

public interface 接口名称{ // }

注意事项:

  1. 接口是由interface关键字 修饰的
  2. 接口当中的抽象方法默认都是由 public abstract 修饰的
  3. 接口当中的成员变量默认都是由 public static final 修饰的(代表常量,需要大写表示)
  4. 接口中不能有被实现的方法,意味着只能有抽象方法。但是两个方法除外:一是被static修饰的方法,二是被default修饰的方法(被static修饰的方法不能重写,被default修饰的方法可选择性重写,被abstract修饰的方法必须重写)
  5. 接口不能被实例化
  6. 类 与 接口之间的关系可以使用 implements 关键字进行连接
  7. 接口也是有对应的字节码文件的
  8. 子类和父类之间是 extends 实现关系,类和接口之间是 implements 实现关系

提示:

由上述注意事项可知,接口中的成员变量和抽象方法都有默认修饰,故为了保持代码的简洁性,将其省略不写

创建接口时,接口的命名一般使用大写字母I开头

(3)接口特性

1)接口类型是一种引用类型,但是不能new接口对象:

2)接口中的方法是不能在接口中实现的,只能由实现接口的类来实现:

3)重写接口中方法时,不能使用默认的访问权限:

因为类中的默认访问权限是default,而在接口中默认访问权限是public

4)接口中不能有代码块和构造方法

5)如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类,但是后续该类的子类必须重写所有抽象方法

(4)实现多个接口

在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物。

语法格式:

没有多继承,但是有多接口 继承一个类,同时实现多个接口 class Duck extends Animal implements IFly,IRun,ISwim{ // }

以下列代码为例:

public abstract class Animal {

    //姓名、年龄

    public String name;

    int age;

    public Animal(String name,int age) {

        this.name = name;

        this.age = age;

    }

    //吃饭的动作

    public abstract void eat();

}

//由于不是每个动物都能run\swim\fly 故创建接口

interface IFly{

    void fly();

}

interface IRun{

    void run();

}

interface ISwim{

    void swim();

}

//把共性及行为提取后,创建指定的动物类

//狗是动物 具备跑步功能

class Dog extends Animal implements IRun{

    public Dog() {

        super("狗子",8);

    }

    public void eat() {

        System.out.println(this.name+" 正在吃狗粮");

    }

    public void run() {

        System.out.println(this.name+" 正在用四条腿跑步");

    }

}

//青蛙是两栖动物 能跳 能游泳

class Frog extends Animal implements IRun,ISwim{

    public Frog() {

        super("青蛙",2);

    }

    public void eat() {

        System.out.println(this.name+" 正在吃蛙粮");

    }

    public void run() {

        System.out.println(this.name+" 正在用两条腿跳着走");

    }

    public void swim() {

        System.out.println(this.name+" 正在标准蛙泳");

    }

}

//鸭子海陆空都能走

class Duck extends Animal implements IFly,IRun,ISwim{

    public Duck() {

        super("鸭子",4);

    }

    public void eat() {

        System.out.println(this.name+" 正在吃鸭粮");

    }

    public void run() {

        System.out.println(this.name+" 正在屁颠屁颠跑");

    }

    public void swim() {

        System.out.println(this.name+" 正在用鸭掌划水");

    }

    public void fly() {

        System.out.println(this.name+" 正在扑腾翅膀飞");

    }

}

class Test{

    public static void func(Animal animal) {

        animal.eat();

    }

    public static void flying(IFly iFly) {

        iFly.fly();

    }

    public static void running(IRun iRun) {

        iRun.run();

    }

    public static void swimming(ISwim iSwim) {

        iSwim.swim();

    }

    public static void main(String[] args) {

        func(new Dog());

        func(new Duck());

        func(new Frog());

        System.out.println("==============");

        running(new Dog());

        running(new Frog());

        running(new Duck());

        System.out.println("==============");

        swimming(new Duck());

        swimming(new Frog());

        System.out.println("==============");

        flying(new Duck());

    }

}

上面的代码展示了Java面向对象编程中最常见的用法:一个类继承一个父类,同时实现多个接口

继承表达的含义是is-a的关系,而接口表达的含义是具有xx特性。

狗是一种动物,会跑

青蛙是一种动物,会跑、会游泳

鸭子是一种动物,会跑、会游泳、会飞

注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。

(5)接口中的继承

在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。

接口中可以多继承,如下:

接口中的继承相当于把多个接口合并在一起。

(6)接口使用实例

例如:我们需要给一个对象数组进行排序,如下:

/定义学生类

public class Student {

    String name;

    int age;

    public Student(String name, int age) {

        this.name = name;

        this.age = age;

    }

}

class Test{

    public static void main(String[] args) {

        Student student1 = new Student("zhangsan",18);

        Student student2 = new Student("lisi",17);

        Student student3 = new Student("wangwu",19);

        //创建对象数组

        Student[] studentArray = {student1,student2,student3};

    }

}

1)实现Comparable接口

使用场景: 写在类中,一般为内置在类中默认的比较方式。

接口调用格式:

/

//实现Comparable接口,后面<类名>

public class Student  implements Comparable<Student>{

    //重写compareTo方法

    @Override

    public int compareTo(Student o) {

        return this.age - o.age;

    }

   

    String name;

    int age;

    public Student(String name, int age) {

        this.name = name;

        this.age = age;

    }

}

注意事项:

public class 类名 implements Comparable{

@Override

public int compareTo(类名 实例对象) {

// 对该方法重写,提供比较方式

}

}

使用方式:

1)

直接使用Arrays.sort对数组进行排序即可。

必须要实现Comparable接口并重写compareTo接口,否则会报错

2)

自己创建一个sort方法进行比较

内层的比较方式还是调用自己实现的Comparable 的 compareTo方法。

//创建一个自己的排序方式

public void  sort(Comparable[] comparables) {

    for(int i = 0; i < comparables.length-1; i++) {

        for(int j = 0; j < comparables.length-1-i; j++) {

            if(comparables[j].compareTo(comparables[j+1])>0) {

                Comparable tmp = comparables[j];

                comparables[j] = comparables[j+1];

                comparables[j+1] = tmp;

            }

        }

    }

}

使用方法1)) 调用原理:

上图为Arrays.sort源码,它需要将我们传递过去的数组中每个元素依次强转为Comparable类型再调用compareTo方法进行排序,但是目前Student这个类未实现Comparable这个接口,所以两者没关系,不能强转,程序报错。

当我们实现Comparable接口并重写compareTo方法,即可让其调用我们呢重写的compareTo比较方法,从而实现排序。

使用方法1))实现效果:

但是需要注意的是,该写法由于是实现在类内部,对类的侵入性较强,故一旦写好规定的比较方式,那么以后就只能以这种方式进行比较,因此灵活性较差。

Comparable接口和equals方法一般都作为一个类自带的比较方式,故当我们实现一个类时,一般都会实现Comparable接口重写compareTo方法,并重写equals方法

2))实现Comparator接口

使用场景:很灵活,一般根据业务逻辑进行调整。

我们可以称其为比较器,实现如下:

//根据姓名比较 class NameComparator implements Comparator<Student>{ @Override public int compare(Student one,Student two) { return one.name.compareTo(two.name); } } //根据年龄比较 class AgeComparator implements Comparator<Student>{ @Override public int compare(Student one,Student two) { return one.age-two.age; } }

注意事项:

单独创建一个类

class 类名 implements Comparator {

//重写该方法

@Override

public int compare(实例对象1,实例对象2) {

//比较方法

}

}

使用方法:

在调用Arrays.sort方法时,将比较器的实例对象一同传递,即可按照比较器的比较方式进行比较,如下:

调用原理:

上图中我们可以看到,sort的重载方法中,还可以对其传递一个Comparator的实例对象(比较器),使用比较器的比较方式进行排序。

实现效果:

3)Clone接口和深拷贝

在Object类中存在一个clone的方法,调用这个方法可以创建一个对象的 "拷贝". 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常。

演示如下:

1,

2,

3,

4,

5,

当我们进行到这一步时,发现代码并没有报错,但是执行改代码还是会抛出异常。这就需要提到本章节的Cloneable接口了。

Cloneable接口是一个空接口,我们也称之为标记接口。其作用就是:如果类实现了这个接口,那么就代表这个接口是可以克隆的。

6,

(7)深拷贝和浅拷贝

深拷贝、浅拷贝只和代码的实现方式有关。

示例说明:

//创建该类是为了在Animal类中再引用一个对象 class Money{ public int m = 19; } //创建一个待拷贝的类 class Animal implements Cloneable{ public String name; int age; public Animal(String name, int age) { this.name = name; this.age = age; } Money money = new Money(); @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Animal{" + "name='" + name + '\'' + ", age=" + age + ", money=" + money.m + '}'; } }

1))浅拷贝(只拷贝当前对象)

在上图中可以观察到:仅仅是拷贝了Animal对象,但是对Animal中的Money对象并没有发生拷贝,这就是浅拷贝。示意图如下:

2))深拷贝(需要拷贝该对象中所有包含的对象)

在上图中我们可以看到,我们不仅对dog对象进行了拷贝,还对dog对象中的money对象进行了拷贝,故当修改dog.money.m时不会影响到dogOne,这就是深拷贝。示意图如下:

(8)Object类

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。

Object类中也有定义好的方法,下面会选择重要的进行演示:

1)toString

在前文已经进行说明过,若想打印一个对象,只需在该对象中重写toString这个方法,而toString就是Object类中的方法。

2)equals

Object类中自带了equals这个方法,其作用时比较两个数据是否相同。

但是若想比较两个对象,则须在类中重写equals方法,否则其会对地址进行比较。

3)hashcode

我们看到了hashCode()这个方法,他帮我算了一个具体的对象位置,这里面涉及数据结构,但是我们还没学数据结构,没法讲述,所以我们只能说它是个内存地址。然后调用Integer.toHexString()方法,将这个地址以16进制输出。

结论:

1、hashcode方法用来确定对象在内存中存储的位置是否相同

2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

内部类

(1)概念

当一个事物的内部还有一部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结果最好使用内部类。

在Java中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。

简单的示意如下:

public class OuterClass { class InnerClass { } } //OuterClass 是外部类 //InnerClass 是内部类

注意事项:

定义在class类名{}花括号外部的,即使是在同一个文件中,都不能称之为内部类

(2)内部类的分类

首先我们先确定在哪些地方可以定义内部类,如下代码所示:

public class OuterClass{ //成员位置定义--》不加static修饰 实例内部类 //成员位置定义--》使用static修饰,静态内部类 public void method() { //在方法中定义--》局部内部类(使用场景小) } }

实际上内部类可分为四种:

实例内部类

静态内部类

局部内部类

匿名内部类

1)实例内部类

实例内部类就是未被static修饰的成员内部类。

访问权限:内部类访问外部类

1,实例内部类可以直接访问外部内中 任意访问修饰限定符修饰的成员

2,如果外部类和内部类中具有相同名称成员时,优先访问内部类自己的成员

3,在2的情况下,如果要访问外部类同名成员的时候,必须:外部类名称.this.同名成员名字,示例如下:

实例化对象:

实例内部类若要实例化对象,必须先实例化外部类对象,语法如下:

外部类名 外部类实例对象 = new 外部类名(); 外部类名.内部类名 内部类实例对象 = 外部类实例对象.new 内部类名();

不仅如此,内部类成员的访问必须通过内部类的实例对象进行访问。

注意事项:

  1. 内部类成员若是需要使用static关键字修饰,则必须加上final关键字,将该变量转为常量。
  2. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问
  3. 实例内部类对象必须在先有外部类对象前提下才能创建
  4. 实例内部类的非静态方法中包含了一个指向外部类对象的引用(所以实例内部类有双this引用)

2)静态内部类

被static关键字修饰的内部成员类称之为静态内部类。

访问权限:内部类访问外部类

1,静态内部类只能访问外部类的静态成员

2,静态内部类访问外部类中非static修饰的成员

实例化对象:

创建静态内部类对象时,不需要先创建外部类对象,因此静态内部类的使用场景更加广泛。语法如下:

外部类名.内部类名 实例对象名 = new 外部类名.内部类名():

3)局部内部类

定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,可作为辅助使用,此处简单了解下语法格式。

只能在{ }内部使用,极其局限:

注意事项:

局部内部类只能在所定义的方法体内部使用

不能被public、static等修饰符修饰

编译器也会有自己独立的字节码文件,命名格式:外部类名$内部类名。class

仅作了解,后续若是发现其妙用,再行记录

4)匿名内部类

匿名内部类就相当于实例化了一个 “实现了IA接口并重写了test方法的对象”,我们并不能知道这个类名叫什么,故为匿名。

匿名内部类的使用:

匿名内部类的使用方式有两种:

1,使用示例对象调用

语法形式:

//创建接口 interface IA{ void test(); } //外部类 public class Main{ public static void main(String[] args) { IA a = new IA() { public void test() { System.out.println("这是重写方法"); } }; //使用实例对象调用 a.test(); } }

实例:

2,直接在方法体后调用

语法形式:

interface IA{ void test(); } //外部类 public class Main{ public static void main(String[] args) { new IA() { public void test() { System.out.println("这是重写方法"); } }.test(); } }

实例:

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值