Java面向对象复习

news2024/12/22 20:36:08

文章目录

  • 一、类和对象
    • 1. 面向对象
    • 2. 类的定义
    • 3. 对象的创建
    • 4. 类在内存中的存储
    • 5.类的成员使用
    • 6. toString()方法
    • 7. static 关键字
      • 静态成员变量
      • 静态成员方法
  • 二、封装
    • 1. 构造方法
      • 概念基本使用
      • 构造方法的重载
      • this关键字
    • 2. private
    • 3. 封装的好处
  • 三、继承
    • 1. 继承的概念
    • 2. extends
    • 3. super关键字
    • 4. Object
    • 5. 更复杂的继承
    • 6. final 关键字
    • 7. 组和
  • 四、多态
    • 1. 向向转型
      • 概念
      • 向向转型发生的时机
      • 注意事项
    • 2. 向下转型
      • instanceof关键字
    • 3. 动态绑定(运行时绑定)
      • 重写(@Override)
      • 动态绑定的一个坑
    • 4. 多态
      • 多态的好处
  • 五、访问权限
    • 1. 包(package)
      • 导入包中的类
      • 静态导入
      • 将类放到包中
    • 2. 4种访问权限
  • 六、代码块
    • 1. 本地代码块
    • 2. 静态代码块
    • 3. 实例代码块
    • 4. 执行顺序
  • 七、抽象类
    • 1.抽象类的定义
    • 2. 抽象类的注意事项
    • 3. 抽象类的意义
  • 八、接口
    • 1. 接口的定义
    • 2. 接口中的方法和成员变量
    • 3. default关键字
    • 4. 接口的使用
    • 5.实现多个接口
    • 6. 接口的拓展
    • 7. 接口使用示例
    • 8. 抽象类和接口的区别


一、类和对象

1. 面向对象

面向过程和面向对象?

C语言就是典型的面向过程编程,它关注的实现过程。

Java是一个面向对象的语言,面向对象关注的是对象的创建和维护对象之间的关系,而不关系这个对象是怎么实现的。

举个列子:如果你要洗衣服

面向过程:拿盆子接水,放洗衣粉,手搓,再接水洗,把水倒掉拧干

面向对象:把衣服丢进洗衣机,放入洗衣粉,打开洗衣机

在这个过程中,洗衣机就可以当做一个对象,我们不必关心它的实现,只需拿过来用就可以了。

2. 类的定义

通过class关键字就能创建一个对象。

class Student {
    String name;
    int age;
    public void show() {
        System.out.println("haha");
    }
}
  • 在类中定义的变量,称为成员变量或者字段。
  • 在类中定义的方法,称为成员方法

注意:成员变量是在类的内部,方法的外部

3. 对象的创建

用类创建对象的过程成为实例化对象,通过new关键字实例化一个对象。我们可以把类当做一个图纸或者模板,通过一个图纸可以建很多的房子,也就是说一个类可以实例化多个对象。

Student std1 = new Student();
Student std2 = new Student();
Student std3 = new Student();

匿名对象(只能使用一次)

没有引用的对象称为匿名对象.

new Student();

4. 类在内存中的存储

类和数组一样都属于引用,而引用指向的是一个对象,像上面的std1和std2都属于引用,局部变量是在栈上开辟内存的,引用里面存的是对象的地址,这些变量里存的都是引用,每实例化一个对象句在堆区开辟一块内存,对象里存的就是成员变量。

注意:类里面的方法并不是存在堆区的,方法本身是不占用内存的,方法是存在方法区的方法表中,在每个对象new好之后前面又几个字节存的就是方法表的地址,只有在调用这个方法时,才会通过方法表的地址找到这个方法,然后为这个方法在栈上开辟栈帧。


class Student {
    //成员变量
    String name;
    int age;
    // 成员方法
    void show() {
        System.out.println("haha");
    }
}
public class Main {

    public static void main(String[] args) {
        Student std1 = new Student();
        Student std2 = new Student();
        Student std3 = new Student();
    }

}

在这里插入图片描述

通过上图发现,其实并不是所有引用都是在栈上的,如果它是一个实例成员变量,那么它就是在堆上的,比如上图的name和age。

5.类的成员使用

成员变量的初始化,可以给类中的字段给一个默认值,但是不建议。

class Student {
    //成员变量
    String name = "zhangsan";
    int age = 10;
    // 成员方法
    void show() {
        System.out.println("haha");
    }
}

更建议在使用时进行赋值

public static void main(String[] args) {
        Student std1 = new Student();
        std1.name = "李四";
        std1.age = 18;
    }

6. toString()方法

如果我们直接打印一个对象可能会出现下面的情况

public static void main(String[] args) {
        Student std1 = new Student();

        System.out.println(std1);
    }

打印结果

Student@1b6d3586

因为如果我们的类没有自己实现toString方法,那么它将会调用Object的**toString()**方法,该方法是打印的是类名+@+hash值

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

那么我们就可以重写(@Override)toString方法


class Student {
    //成员变量
    String name;
    int age;
    // 成员方法
    void show() {
        System.out.println("haha");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

那么打印的时候就是调用我们重写的toString方法,如果成员变量没有赋值打印出来的默认值。

@Override是一个注解,它表示该方法是重写了父类的方法。

7. static 关键字

我们把成员变量分为普通成员变量和静态成员变量,普通成员变量也叫做实例成员变量,静态成员变量也叫做类变量

静态成员变量

count是被static关键字修饰,它就是一个静态成员变量。我们可以通过对象来访问,也可以通过类名来访问。但是为了规范建议使用类名来访问静态成员变量,因为静态成员变量是不 依赖对象的。

class Student {
    //成员变量
    String name;
    int age;
    static int count;
    // 成员方法
    void show() {
        System.out.println("haha");
    }
}
public class Main {

    public static void main(String[] args) {
        Student std1 = new Student();
        System.out.println(std1.count);
        System.out.println(Student.count);
    }

}

为了验证静态成员变量是不依赖对象的,可以写出这么一段代码

class Student {
    //成员变量
    String name;
    int age;
    static int count;
    // 成员方法
    void show() {
        System.out.println("haha");
    }
}
public class Main {

    public static void main(String[] args) {
        Student std1 = new Student();
        std1.count++;
        Student std2 = new Student();
        std2.count++;
        System.out.println(Student.count);
        Student std = null;
        System.out.println(std.count);
    }

}

打印结果

2
2

说明std1和std2操作的都是同一个count,std=null表示std不指向任何对象,而因为静态成员变量是不依赖于对象的所以最后一行代码是可以正常输出的。

所以

  • 静态成员变量是不依赖于对象的
  • 访问静态成员变量的方式更建议类名.成员变量

静态成员方法

静态成员方法也是通过类名来访问使用的。

class Student {
    //成员变量
    String name;
    int age;
    static int count;
    // 成员方法
    static void show() {
        System.out.println("haha");
    }
}

需要注意的是:

  • 在静态成员方法里是不能使用普通成员变量的
  • 同理在普通成员方法里也是不能使用静态成员变量的
  • 同样的静态成员方法里是不能使用普通成员方法的

因为静态成员变量和成员方法它们都是不依赖于对象的,普通成员变量和方法都是依赖于对象的,如果在静态里使用此时对象都还没有创建出来何来的成员呢?显然是不合理的。

但在普通成员方法里是可以使用普通成员变量的,因为想用普通成员方法,就得实例化对象。

只要被static所修饰的成员变量就在方法区

二、封装

在面向对象的语言里,封装是其特征之一。

封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的,
把属性和动作隐藏,只提供相应的方法来调用即可,只要知道如何使用类就行了.
当类的实现者把内部的逻辑发生变化时,类的调用者根本不用因此而修改方法。
这样就降低了类使用者的学习和使用成本,从而降低了复杂程度,也保证了代码的安全性。

1. 构造方法

概念基本使用

构造方法是一种特殊方法,使用关键字new示例化对象的时候会被自动调用,用于完成初始化操作

  • 构造方法没有返回值,且方法名要和类名相同
  • 构造方法是用来构造对象的(实例化)

new的执行过程

  1. 为对象分配内存空间
  2. 调用合适的构造方法

每一个类都有一个默认的没有参数的构造方法,如果我们没有写构造方法,系统默认会有一个没有参数且没有任何内容的构造方法,比如下面这个代码就有一个默认的构造方法,当然如果你已经写了构造方法这个默认的就不存在了。

class Student {
    //成员变量
    public String name;
    public int age;
    
}

构造方法的重载

方法重载的几个要求并不陌生,构造方法也是能重载的

  • 方法名相同
  • 参数类型和个数不相同
  • 返回值不做要求
class Student {
    //成员变量
    public String name;
    public int age;
    public Student() {
        System.out.println("无参构造方法");
    }
    public Student(String s) {
        System.out.println("一个参数的构造方法");
    }
    public Student(String name, int age) {
        System.out.println("两个参数的构造方法");
    }
}
public class Main {

    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student("test");
        Student s3 = new Student("test",20);
    }

}

执行结果

无参构造方法
一个参数的构造方法
两个参数的构造方法

this关键字

this关键字一共有三种用法

  • this.data (访问当前对象的成员变量)
  • this.func() (访问当前对象的成员方法)
  • this.() (访问当前对象自己的构造方法)

有的书上说 this 代表当前对象,其实这样的说法并不准确。
正确的说法应该是:this表示当前对象引用,为什么这么说呢?

下面这段代码通过构造方法给成员变量赋值,而一个对象的创建要等构造方法执行完毕后才创建,所以this表示的是当前对象的引用

class Student {
    //成员变量
    String name;
    int age;

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

this关键字也可以访问成员方法

class Student {
    //成员变量
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void print() {
        System.out.println("haha");
    }
}
public class Main {

    public static void main(String[] args) {
        Student s1 = new Student("张三",20);
        s1.print();
    }

}

this调用构造方法

注意:this调用构造方法一定要放在构造方法的第一行,每一个构造方法里只能使用一次

class Student {
    //成员变量
    public String name;
    public int age;
    public Student() {
        this("haha");
        System.out.println("无参构造方法");

    }
    public Student(String s) {
        System.out.println("一个参数的构造方法");
    }
    public Student(String name, int age) {
        System.out.println("两个参数的构造方法");
    }
}
public class Main {

    public static void main(String[] args) {
        Student s1 = new Student();
        
    }

}

运行结果

一个参数的构造方法
无参构造方法

2. private

通过private关键字实现封装.被private修饰的成员变量和方法就只能在当前类中使用。在类外是无法直接访问的。和它对应的就是public关键字,被public修饰的成员变量和成员方法是在任何地方都是可以访问的。

成员变量被private修饰后,一般要提供对应的get和set方法(根据需求),一个用来设置值一个用来获取值,这两个方法都是public的。

这样外部就无法直接访问成员变量了,只能通过get和set方法来访问。

class Student {
    //成员变量
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = 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;
    }
}

构造方法也是可以被private修饰的,在单例设计模式中会使用到。下面就是一个简单的饿汉单例

class Test {
    private static Test test = new Test();

    private Test() {

    }
    public static Test getExample() {
        return test;
    }
}

3. 封装的好处

  1. 提高了数据的安全性
    别人不能够通过 变量名来修改某个私有的成员属性
  2. 操作简单
    封装后,类的调用者在使用的时候,只需调用方法即可。
  3. 隐藏了实现
    实现过程对类的调用者是不可见的,类的调用者只需调用方法即可,不知道具体实现

三、继承

1. 继承的概念

继承主要的意义就是为了代码的复用。

当我们定义的一些类存在了一些联系,简单来说就是多个类之间存在着大量重复的代码,为了消除代码的冗余就可以使用继承。

比如下面的代码,存在着大量代码的冗余,且这些类中存在着相同的方法和字段。

class Animal {
    public String name;

    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}
class Dog {
    public String name;
        
    public void eat() {
        System.out.println(this.name+"吃东西");
    }
}
class Bird {
    public String name;
    
    public void eat() {
        System.out.println(this.name+"吃东西");
    }
    
    public void fly() {
        System.out.println(this.name+"起飞");
    }
}

2. extends

在Java中通过extends实现继承

class 子类 extends 父类 {

}
  • Java中只支持单继承,一个子类只能继承一个父类
  • 子类会继承父类的所有非private修饰的字段和方法
  • 对于父类的private的字段和方法,子类是无法访问的,并不是没有继承而是被private修饰只能在当前类中使用。
  • 子类实例中包含着父类的实例,可以使用super关键字得到父类实例的引用

此时通过继承关系把上面的代码进行改造,让Dog类和Bird类继承Animal类。

class Animal {
    public String name;

    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}
class Dog extends Animal {


}
class Bird extends Animal {


    public void fly() {
        System.out.println(this.name+"起飞");
    }
}
public class TestDemo2 {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Bird bird = new Bird();
        dog.name = "二哈";
        bird.name = "鸟";
        dog.eat("饭");
        bird.eat("饭");
    }
}

运行结果

二哈正在吃饭
鸟正在吃饭

此时,像Animal这样被继承的类,我们称为父类、基类或者超类,对于像Cat和Bird这样

继承Animal的类,叫做子类或者派生类

就和现实中儿子继承父亲类似,子类会继承父类的字段和方法,从而达到代码复用的效果。

此时在内存中的存储

像成员方法和静态的成员变量都是存在方法区的,类里面的方法并不是是存在堆区的 ,方法本身是不占内存的,方法是存在方法区里的方法表中,在每个对象 new 好之后前面有几个字节存的就是方法表的地址,只有在调用这个方法时,会通过方法表的地址找到这个这个方法,然后在栈上为这个方法开辟栈帧。

在这里插入图片描述

3. super关键字

我们知道在一个类没有写任何构造方法的时候,默认会有意的没有参数且没有任何内容的构造方法。

下面这个例子,当我们给父类手动写上一个参数的构造方法时,继承关系下的子类都报错。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v12lFDWQ-1673791296780)(assets/1673577760402.png)]

当子类继承父类后,在构造子类之前,必须先帮父类进行构造

这时候就用到了super关键字。

super表示父类实例的引用,和this类似共有三种用法

  1. super.父类成员变变量

  2. super.父类成员方法

  3. super()调用父类的构造方法

注意:super和this一样不能在静态方法中使用

在用super关键字在子类的构造方法里帮父类构造的时候一定要在第一行

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
}
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }

    public void fly() {
        System.out.println(this.name+"起飞");
    }
}

如果父类和子类有了同名的成员变量?

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}
class Bird extends Animal {
    public String name = "鸟";
    public Bird(String name) {
        super(name);
    }

    public void fly() {
        System.out.println(super.name);//使用父类的成员变量
        super.eat("食物");//调用父类的成员方法
        System.out.println(this.name+"起飞");
    }
}
public class TestDemo2 {

    public static void main(String[] args) {
        Bird bird = new Bird("乌鸦");
        bird.fly();
    }
}

运行结果

乌鸦
乌鸦正在吃食物
鸟起飞

此时的内存结构图

在这里插入图片描述

4. Object

如果一个类没有指定继承任何类的时候,那么这个类是默认 继承的就是Object类。也就是说Object类是所有类的祖先。

比如随便写一个类就是默认继承Object类,它会继承Object类中的方法和字段。

class Animal {
    public String name;

    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}

5. 更复杂的继承

继承可以继承多层,也就是子类还可以进一步的再派生出新的子类,虽然语法上支持,但不建议继承的层数超过3层

当继承到第三层的时候,就应用使用final修饰这个类,这样这个类就无法继承了。

class A {
    String name;
}
class B extends A{
    
}
// 使用final修饰
final class C extends B {
    
}

6. final 关键字

  1. final修饰变量(常量,这个常量的值不能被修改)
  2. final修饰类,密封类(这个类不能再被继承)
  3. final修饰方法,密封方法(该方法不能进行重写)

7. 组和

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.
例如表示一个学校:

public class Student {

}
public class Teacher {

}
public class School {
 public Student[] students;
 public Teacher[] teachers;
}

组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.

组合表示 has - a 语义 在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师.

继承表示 is - a 语义 在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物

四、多态

1. 向向转型

概念

向上转型就是把一个子类对象父类引用,也就是父类引用引用了子类的对象

class Animal {
    public String name;
    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}
class Bird extends Animal {
    public void fly() {
        System.out.println(this.name+"起飞");
    }
}
public class TestDemo2 {

    public static void main(String[] args) {
        Animal animal = new Bird();//父类引用引用子类对象
    }
}

我们把一个 Animal类型引用了它的子类Bird这就是向上转型

向向转型发生的时机

直接赋值

public static void main(String[] args) {
        Animal animal = new Bird();//父类引用引用子类对象
    }

方法传参

public class TestDemo2 {
    public static void func(Animal animal) {
        
    }
    public static void main(String[] args) {
        Bird bird = new Bird();
        func(bird);
    }
}

方法返回

public static Animal func() {
        Bird bird = new Bird();
        
        return bird;
    }

注意事项

当发生向上转型的时候,通过父类引用只能调用父类自己的成员变量和方法

2. 向下转型

向下转型则是相反,拿一个子类的引用引用父类对象,前提是这个父类对象是通过向上转型得到的。

注意:向下转型的时候一定要进行强制类型转换

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    public void fly() {
        System.out.println(this.name+"起飞");
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        Animal animal = new Bird("鸟");
        Bird bird = (Bird)(animal);
        bird.fly();
    }
}

运行结果

鸟起飞

instanceof关键字

向下转型我们一般不建议使用,因为它非常不安全

下面这段代码让一个Cat对象发送向下转型,再让animall发生向下转型,显然这是不行的,一个Cat类型怎么能强制转换成Bird类型呢?运行救护发生类型转换异常

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
}
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    public void fly() {
        System.out.println(this.name+"起飞");
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        Animal animal = new Cat("test");
        Bird bird = (Bird)(animal);
        bird.fly();
    }
}

于是就可以使用instanceof关键字

instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较合适

public static void main(String[] args) {
        Animal animal = new Cat("test");
        if (animal instanceof Bird) {
            Bird bird = (Bird)(animal);
            bird.fly();
        }
    }

所以向下转型我们一般不建议使用,如果非要使用就一定要用instanceof 关键字判断一下。

3. 动态绑定(运行时绑定)

动态绑定发送的前提

  1. 先向上转型
  2. 通过父类引用来调用父类和子类中同名的覆盖方法(重写的方法)
class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
        System.out.println("Animal");
    }
}
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
    public void eat(String food) {
        System.out.println(this.name + "在吃" + food);
        System.out.println("Cat");
    }
}

public class TestDemo2 {
    public static void main(String[] args) {
        Animal animal = new Cat("哈哈");
        animal.eat("饭");
    }
}

运行结果

哈哈在吃饭
Cat

我们发现这里我们通过父类引用调用了 Animal 和 Cat
同名的覆盖方法(重写),运行的是子类Cat的eat方法。此时这里就发生了动态绑定。

动态绑定也叫运行时绑定,其实在编译期间还是调用的父类的方法,在运行期间就才发生了绑定,绑定了子类的同名方法。

重写(@Override)

@Override表示这个方法是重写的

重写的条件

  1. 方法名相同
  2. 方法的参数列表相同(参数个数和数据类型)
  3. 方法的返回值相同(也可以是父子关系)

返回值构成父子类关系也是可以发生重写的,此时叫做:协变类型

下面的代码的返回值就构成父子关系

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public Animal eat(String food) {
        
        return null;
    }
}
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
    @Override
    public Cat eat(String food) {
        return null;
    }
}

需要注意的是:

  1. 子类的重写的这个方法,他的访问修饰符,一定要大于等于父类方法的访问修饰符
  2. 被final和static修饰的方法是不能发生重写的

动态绑定的一个坑

来看一段代码,我们实例化一个Cat类,因为Cat是子类,所以要帮父类先构造,那么在父类的构造方法里有一个 eat 方法,那么会执行哪个类里的 eat 方法呢?

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
        this.eat();
    }
    public void eat() {
        System.out.println("Animal");
    }
}
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
    @Override
    public void eat() {
        System.out.println("Cat");
    }
}

public class TestDemo2 {
    public static void main(String[] args) {
        Animal animal = new Cat("哈哈");
    }
}

运行结果

Cat

我们发现这里调用的不是 是Animal 的 eat 方法,而是 Cat 的,因为这里也发生了动态绑定。

所以构造方法当中也是可以发生动态绑定的
注意:这样的代码以后不要轻易写出来!

4. 多态

多态其实就是一种思想,一个事物表现出不同的形态,就是多态。

假设我们要打印一些形状

class Shape {
    public void draw() {

    }
}

class Rect extends Shape{

    public void draw() {
        System.out.println("♦");
    }
}

class Cycle extends Shape{
    public void draw() {
        System.out.println("●");
    }
}

class Flower extends Shape{
    public void draw() {
        System.out.println("❀");
    }
}

class Triangle extends Shape{
    public void draw() {
        System.out.println("△");
    }
}

public class Test {
    public static void main(String[] args) {
        Shape shape = new Rect();
        shape.draw();
        Shape shape1 = new Cycle();
        shape1.draw();
        Shape shape2 = new Flower();
        shape2.draw();
        Shape shape3 = new Triangle();
        shape3.draw();
    }
}

运行结果

♦
●
❀
△

这不就是动态绑定吗?和多态有什么关系吗?

当我们在这个代码中添加一个drawMap方法后

public class TestDemo2 {
    public static void drawMap(Shape shape) {
        shape.draw();
    }
    public static void main(String[] args) {
        Shape shape = new Rect();
        Shape shape1 = new Cycle();
        Shape shape2 = new Flower();
        Shape shape3 = new Triangle();
        drawMap(shape);
        drawMap(shape1);
        drawMap(shape2);
        drawMap(shape3);
    }
}

运行结果

♦
●
❀
△

这不就是动态绑定吗?

我们细看会发现这是同样一个引用调用同样一个方法,能表现出不同的形态,这不就是多态思想?其实多态用到的就是动态绑定。

在这个代码中, 前面的代码是 类的实现者 编写的, TestDemo这个类的代码是 类的调用者 编写的.

当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当 前的shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape
对应的实例相关), 这种行为就称为 多态。

多态的好处

  1. 类调用者对类的使用成本进一步降低.
    封装是让类的调用者不需要知道类的实现细节.
    多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可

  2. 可扩展能力更强
    如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了, 改动成本很低

五、访问权限

1. 包(package)

`包时组织类的一种方式,使用包的主要目的是保证类的唯一性。

比如在合作开发的时候,在代码里如果出现两个TestDemo类,代码就会编译不通过。

导入包中的类

在Java中已经提供了很多现成的类供我们使用,比如

public static void main(String[] args) {
    //随机数生成的类
        java.util.Random random = new java.util.Random();
    }

不过一般使用这种方式使用(除非多个包的类名冲突),而是通过import关键字导入包

import java.util.Random;

public class TestDemo2 {
    public static void main(String[] args) {
        Random random = new Random();
    }
}

还有一种将包下的类全部导入的写法

import java.util.*;

静态导入

使用import static可以导入静态的方法和字段

import static java.lang.Math.*;

public class TestDemo2 {
    public static void main(String[] args) {
        int a = 100;
        int b = 200;
        System.out.println(max(a,b));
    }
}

如果不使用静态导入

public class TestDemo2 {
    public static void main(String[] args) {
        int a = 100;
        int b = 200;
        System.out.println(Math.max(a,b));
    }
}

将类放到包中

  • 通过关键字package语句指定该代码在哪个包中
  • 包名的规范一般用全小写字母
  • 包名一般是公司的域名
  • 注意:java.lang下的类是自动导入的

2. 4种访问权限

在这里插入图片描述

  • private修饰的字段和方法只能在当前类中使用,类外是无法访问的。构造方法也可以是private
  • 如果一个成员变量或者方法什么权限修饰符都不写,默认就是包访问权限,也就是在当前包中才能访问
  • 刚才我们发现, 如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 “封装” 的初衷.两全其美的办法就是 protected 关键字
    • 对于类的调用者来说, protected 修饰的字段和方法是不能访问的
    • 对于类的 子类 和 同一个包的其他类 来说, protected修饰的字段和方法是可以访问的
    • 注意在不同包的子类是通过super关键字访问
  • public修饰是在任何地方都可以访问到的

六、代码块

1. 本地代码块

方法内部的代码块

public static void main(String[] args) {
        {
            //本地代码块没有意义
        }
    }

2. 静态代码块

static修饰的代码块叫做静态代码块,

注意:静态代码块时在类加载的时候执行,整个程序中只会执行一次

public class TestDemo2 {
    static {
        System.out.println("这是一个静态代码块");
    }
    public static void main(String[] args) {
        TestDemo2 testDemo2 = new TestDemo2();
        System.out.println("===");
        TestDemo2 testDemo3 = new TestDemo2();

    }
}

运行结果

这是一个静态代码块
===

3. 实例代码块

实例代码块也叫构造代码块,在实例代码块中可以对属性进行赋值

class People{
    private String name ;
    private int age;//普通成员变量
    private static int cont;//静态成员变量  
    {
       	this.name = "java";
        this.age = 66;
        cont = 100;
        System.out.println("这是一个实例代码块");
    }

}

4. 执行顺序

如果静态代码块、实例代码块和构造方法同时存在,它们的执行顺序是什么呢?

执行顺序为:

  1. 静态代码块
  2. 实例代码块
  3. 构造方法
class Person {
    String name;

    public Person() {
        System.out.println("Person构造方法");
    }
    static {
        System.out.println("静态代码块,Person");
    }
    {
        System.out.println("实例代码块,Person");
    }
}
class Student extends Person {

    public Student() {
        System.out.println("Student构造方法");
    }
    static {
        System.out.println("静态代码块,Student");
    }
    {
        System.out.println("实例代码块,Student");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Person person = new Student();
    }
}

运行结果

静态代码块,Person
静态代码块,Student
实例代码块,Person
Person构造方法
实例代码块,Student
Student构造方法
  • 静态代码块只会执行一次
  • 如果出现继承关系:由父及子,静态先行。

七、抽象类

1.抽象类的定义

  • 包含抽象方法的类,就叫做抽象类

  • 如果一个方法没有具体的实现,就可以使用abstract来修饰,且包含抽象方法的类也一定要用abstract来修饰

    abstract class Shape {
        abstract public void draw();
    }
    

2. 抽象类的注意事项

1.抽象类是不能被实例化的(也就是不能new的)

2.在抽象类中可以包含抽象方法也可以包含非抽象方法

abstract class Shape {
    abstract public void draw();
    public void print() {
        System.out.println("普通方法");
    }
}

3.当一个普通的类,继承了一个抽象类,那么普通这个普通类一定要重写抽象类当中的抽象方法,或者用abstract修饰这个子类,让它也变成抽象类。

abstract class Shape {
    abstract public void draw();
    public void print() {
        System.out.println("普通方法");
    }
}
class Circle extends Shape {

    @Override
    public void draw() {
        
    }
}

abstract修饰子类

abstract class Shape {
    abstract public void draw();
    public void print() {
        System.out.println("普通方法");
    }
}
abstract class Circle extends Shape {

    
}

4.当一个普通类继承了一个上述继承一个抽象类且没有重写抽象方法,如果它自己也带以一个抽象类,那么此时这个普通类就得重写这两个类的抽象方法了。

abstract class Shape {
    abstract public void draw();
    public void print() {
        System.out.println("普通方法");
    }
}
abstract class Circle extends Shape {
    abstract public void circleInit();
}
class Son extends Circle {

    @Override
    public void draw() {
        
    }

    @Override
    public void circleInit() {

    }
}

5.抽象类不能被final修饰,因为抽象类本来就用来继承的,所以不能被final修饰

6.抽象方法是为了重写的,也不能被final修饰,且抽象方法不能用private修饰

7.父类(抽象类)的普通方法,在类外也可以被调用,但是需要让父类引用去引用子类对象(向上转型),或者在子类中通过super关键字访问父类(抽象类)的成员变量或者成员方法。

abstract class Shape {
    abstract public void draw();
    public void print() {
        System.out.println("抽象类的普通方法");
    }
}
class Circle extends Shape {

    @Override
    public void draw() {

    }
}

public class TestDemo2 {
    public static void main(String[] test) {
        Shape shape = new Circle();
        shape.print();
    }
}

运行结果

抽象类的普通方法

3. 抽象类的意义

  • 抽象类存在的最大意义就是为了被继承

  • 抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法

  • 使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.

  • 比如前面多态的代码就可以改成抽象类

八、接口

1. 接口的定义

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含 静态常量

在Java中使用interface定义一个接口

interface IShape {
    
}

2. 接口中的方法和成员变量

  • 接口中的方法默认都是抽象方法public abstract
  • 接口中的成员变量默认都是公开的静态常量public static final
interface IShape {
    public static final int ID = 100;
    abstract public void test();
}

注意:
1.接口中的方法前面的修饰符写或不写,默认都是 public abstract 也就是抽象方法
2.接口当中的成员变量,也是一样,默认就是 public static final

3. default关键字

接口中的方法都是没有具体实现的,但是从jdk1.8开始接口中的方法是可以有具体实现的,但这个方法必须被default修饰

interface IShape {
    public static final int ID = 100;
    abstract public void test();
    default void prt() {
        System.out.println("test");
    }
}

4. 接口的使用

  • 接口是不可以被实例化的
  • 接口的使用可以通过implements实现接口
  • 接口也是可以发送向上转型和动态绑定的
interface IShape {
    void draw();
}
class Rect implements IShape{

    public void draw() {
        System.out.println("♦");
    }
}

class Cycle implements IShape{
    public void draw() {
        System.out.println("●");
    }
}

class Flower implements IShape{
    public void draw() {
        System.out.println("❀");
    }
}

class Triangle implements IShape{
    public void draw() {
        System.out.println("△");
    }
}

public class TestDemo2 {
    public static void draw(IShape iShape) {
        iShape.draw();
    }
    public static void main(String[] test) {
        Rect rect = new Rect();
        Cycle cycle = new Cycle();
        Flower flower = new Flower();
        Triangle triangle = new Triangle();
        draw(rect);
        draw(cycle);
        draw(flower);
        draw(triangle);
    }
}

运行结果

♦
●
❀
△

5.实现多个接口

有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果。

  • 一个类可以实现多个接口,通过关键字 implements
  • 注意:如果实现了多个接口,一定要重写每个接口里面的方法
interface A {
    void prt();
}
interface B {
    void test();
}
class Test implements A,B {

    @Override
    public void prt() {
        
    }

    @Override
    public void test() {

    }
}

注意事项:如果两个接口,方法名相同,此时在类当中,只需要重写一个方法就好了,但最终调用的就是这一个方法。但并不建议这么做,接口都不一样就不要写同样的方法了。

interface A {
    void test();
}
interface B {
    void test();
}
class Test implements A,B {

    @Override
    public void test() {
        System.out.println("test");
    }
}
public class TestDemo2 {
    public static void main(String[] test) {
        A a = new Test();
        B b = new Test();
        a.test();
        b.test();
    }
}

运行结果

test
test

6. 接口的拓展

一个类可以拓展多个接口,通过关键字 extends 来拓展多个接口,也就说可以让一个接口具备另外几个接口的功能。

注意: 一但有一个普通类实现了这拓展的接口,那么这个类就要重写这个接口的抽像方法了

interface a {
    void a();
}
interface b {
    void b();
}
interface C extends a,b {
    void c();
}
class MyClass implements C {

    @Override
    public void a() {
        
    }

    @Override
    public void b() {

    }

    @Override
    public void c() {

    }
}

7. 接口使用示例

我们定义了一个 Animal 类来描述动物,三个接口,分别代表 飞、跑、游。再定义了一个Cat类继承了Animal 类,并且实现了 跑 这个接口


interface a {
    void a();
}
interface b {
    void b();
}
interface C extends a,b {
    void c();
}
class MyClass implements C {

    @Override
    public void a() {

    }

    @Override
    public void b() {

    }

    @Override
    public void c() {

    }
}
class Animal {
    protected String name;
    public Animal(String name) {
        this.name = name;
    }
}
interface IFly {
    void fly();
}
interface IRun {
    void run();
}
interface ISwimming {
    void swim();
}
class Cat extends Animal implements IRun {

    public Cat(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name+"猫在跑");
    }
}
class Bird extends Animal implements IFly,IRun {

    public Bird(String name) {
        super(name);
    }

    @Override
    public void fly() {
        System.out.println(this.name+"鸟在飞");
    }

    @Override
    public void run() {
        System.out.println(this.name+"鸟在跑");
    }
}
class Duck extends Animal implements IRun,IFly,ISwimming {

    public Duck(String name) {
        super(name);
    }


    @Override
    public void fly() {
        System.out.println(this.name+"鸭子飞");
    }

    @Override
    public void run() {
        System.out.println(this.name+"鸭子跑");
    }

    @Override
    public void swim() {
        System.out.println(this.name+"鸭子游泳");
    }
}
public class TestDemo2 {
    public static void  fly(IFly iFly) {
        iFly.fly();
    }
    public static void run(IRun iRun) {
        iRun.run();
    }
    public static void swimming(ISwimming iSwimming) {
        iSwimming.swim();
    }
    public static void main(String[] test) {
        Cat cat = new Cat("猫");
        Bird bird = new Bird("鸟");
        Duck duck = new Duck("鸭子");
        run(cat);
        run(bird);
        run(duck);
        fly(bird);
        fly(duck);
        swimming(duck);
    }
}

运行结果

猫猫在跑
鸟鸟在跑
鸭子鸭子跑
鸟鸟在飞
鸭子鸭子飞
鸭子鸭子游泳

我们发现接口是非常灵活的,我们不用管传过去的是什么,只要传过去的对象只要实现了对应的接口,都可以调用这个方法,发生向下转型和动态绑定。

8. 抽象类和接口的区别

成员变量

  • 抽象类中成员变量和普通类没有区别
  • 接口中的成员变量默认都是public static final

成员方法

  • 抽象类中可以包含普通方法,也可以包含抽象方法
  • 接口的方法默认都是抽象方法(abstract)
  • 在jdk1.8中default修饰的方法可以有具体实现

实现方式

  • 抽象类是不能被实例化的,所以只能被子类使用extend关键字继承,且一个类只能继承一个父类
  • 接口也是不能被实例化,只能使用implements来实现,同时一个类可以实现多个接口,一个接口可以使用extends扩展多个接口的功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/165141.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

二维矩阵的元素和

二维矩阵的元素和1.背景2.原理3.实现1.背景 对矩阵元素进行求和,或者求子矩阵的元素和;给定矩阵左上角坐标(x1,y1)和右下角坐标(x2,y2); 如何快速求出 以(x1,y1),&#…

SAP入门技术分享五:内表

内表1. 概要2. 内表与表头3.内表的类型(1)类型(2)标准表(3)排序表(4)哈希表4.比较内表速度(1)标准表与排序表(2)二分法查找&#xff0…

Kafka生产者分区

生产者分区 分区的原因 (1)便于合理使用存储资源,每个Patition在一个Broker上存储,可以把海量的数据按照分区切割成一块一块数据存储在多台Broker上。合理控制分区的任务,可以实现负载均衡的效果。 (2)提高并行度&am…

如何设计一个消息队列?

本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~ Github地址:https://github.com/…

用户登录、注册的简单案例: html+css+MyBatis+Servlet

用户登录一. 用户登录1. 流程与思路基本流程:详细过程:2. 准备环境建库建表Pojo实体类User:Maven坐标:mybatis核心配置文件:代理接口:3. 编写目录:HTML:Serlvet:4. 效果二…

【Leetcode】21. 合并两个有序链表

【Leetcode】21. 合并两个有序链表题目思路代码题目 思路 归并排序比较两个单链表每一个节点,将较小元素的节点值封装成一个新的节点添加到一个新的链表中如果两个单链表长度不一致,也就是有一个链表指针指向null ,那么将另一个链表中的所有节点全部添加…

【手写 Vue2.x 源码】第二十四篇 - 异步更新流程

一,前言 上篇,介绍了 Vue依赖收集的视图更新部分,主要涉及以下几点: 视图初始化时: render方法中会进行取值操作,进入 Object.defineProperty 的 get 方法get 方法中为数据添加 dep,并记录当…

20230115英语学习

Gold From Old Sim Cards Could Help Make Future Drugs SIM卡中回收的黄金,可用于制造药品 Chemists are paving a road to recycle discarded SIM cards, not for electronics, but for medicine. SIM cards, which allow your phone to connect to your netwo…

ATTCK 05

环境搭建 自行下载安装包 解压VMware中win7 win8 同样方法所要用到的攻击机为kali 调节kali的网络适配器为vmnet8 调节win7的网络适配器 增加vmnet5用来连接内网win8 vmnet5名称ip角色kali192.168.115.129攻击机win7192.168.115.150192.168.138.136win8192.168.138.138DC拓…

【 java 反射上篇】java反射机制不难吧?来看看这篇

📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言&#x1f4d…

BMS系统—产生原因如何工作

1 为什么需要BMS系统 1.1 介绍 1)BMS,battery management system,电池管理系统 2)BMS是一套嵌入式系统,由硬件和软件共同组成 3)BMS功能:管理多节锂电池组成的电池包,实现充放电管理、安全保护…

jsp动物园网上购票系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 动物园网上购票系统 是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统采用web模式开发,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#x…

Python:每日一题之FBI树(深度优先遍历)

题目描述 我们可以把由 “0” 和 “1” 组成的字符串分为三类:全 “0” 串称为 B 串,全 “1” 串称为 I 串,既含 “0” 又含 “1” 的串则称为 F 串。 FBI树是一种二叉树,它的结点类型也包括 F 结点,B 结点和 I 结点…

Anolis 8.6 部署 Kafka 3.3.1 安装和测试

龙蜥 8.6 安装 Kafka 3.3.1 并通过 SpringBoot 3.0.1 版本调试一.Kafka 安装1.下载编译后的文件2.拷贝到 Anolis 并解压3.启动服务3.常用命令1.Topic 增查删2.生产消费测试二.SpringBoot 连接 Kafka1.项目结构、依赖、配置文件和启动类2.生产者和生产监听3.消费者和消费监听4.自…

【Java寒假打卡】Java基础-并发工具类

【Java寒假打卡】Java基础-并发工具类HashMap在多线程下的问题ConcurrentHashMapCountDownLatchHashMap在多线程下的问题 package com.hfut.edu.test14;import com.sun.jdi.request.StepRequest;import java.util.HashMap;public class test2 {public static void main(String…

信号的时域和频域特性的区别到底是什么?

不严谨的说,时域和频域分析就是在不同的空间看待问题的,不同空间所对应的原子(基函数)是不同的。你想一下时域空间的基函数是什么?频域空间的基函数是什么?一般的时-频联合域空间的基函数是什么?小波域空间的基函数是什…

线索二叉树(c++)

1.引言: 二叉树的三种遍历方法能将二叉树中的结点按某种方式生成一个线性序列,能将一个非线性结构进行线性化操作。但随之也产生两个问题: 遍历效率低 在采用左右链表示方法作为二叉树的存储结构时,当二叉树更新后,并…

【博客586】ipvs的hook点位置以及hook点钩子函数剖析

ipvs的hook点位置以及hook点钩子函数剖析 ipvs实现负载均衡的基础 ipvs其实是基于netfilter框架来挂载hook点,从而对流量进行dnat等操作 ipvs的hook点剖析 IPVS的实现利用了Netfilter的三个Hook点,分别是:NF_INET_LOCAL_IN、NF_INET_LOCAL_O…

【nvidia CUDA 高级编程】NVSHMEM 直方图——复制式方法

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。 博客…

pandas时间序列,案列

一:pandas时间序列 1.1为什么要学习pandas中的时间序列 不管在什么行业,时间序列都是一种非常重要的数据形式,很多统计数据以及数据的规律也都和时间序列有着非常重要的联系,而且在pandas中处理时间序列是非常简单的 1.2生成一段时…