Java面向对象复习


一、类和对象

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操作的都是同一个countstd=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扩展多个接口的功能

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java