Java类和对象的使用

Java类和对象的使用

1.什么是类,什么是对象

概念:

  • 类就是一种复合类型,有点像C语言中的结构体,他就是描述一组事物的属性和行为的集合,在Java当中我们通常会用类去表示一个物体,比如说人,人的属性有很多,比如说名字、年龄、地址等等,然后就是行为,比如说吃饭、睡觉、打游戏等等,这些都属于行为,这些东西就可以用类来表示。

    public class Person{
        public String name;
        public int age;
        public String address;
        
        public void eat(){
            System.out.println("吃饭");
        }
        
        public void sleep(){
            System.out.println("睡觉");
        }
        
        public void game(){
            System.out.println("打游戏");
        }
    }
    
  • 那对象又是什么呢?就当我们已经构建好了一个类出来的时候,我们可以用这个类去声明一个对象,比如说我已经构建好了人这个类,我就可以用这个类去创建人这个对象出来。在Java当中,我们声明一个对象的话,要使用new关键字,Java就会帮我们在堆上申请一块内存,来存放我们创建好的对象。

    public class Person{
        public String name;
        public int age;
        public String address;
        
        public void eat(){
            System.out.println("吃饭");
        }
        
        public void sleep(){
            System.out.println("睡觉");
        }
        
        public void game(){
            System.out.println("打游戏");
        }
        
        public static void main(String[] args){
            Person person = new Person();
            person.name = "张三";
            //这里就会输出名字
            System.out.println(person.name);
        }
        
    }
    

    注意:我们用关键字new一个对象的时候,他是在我们的堆上开辟内存,而不是在栈上,并且他会把地址放到我们定义好的变量里,我们就可以通过定义好变量,来使用点运算符(.)来访问里面的内容。

    image-20230323113856326

2.类的定义

我们前面已经讲清楚了,类的概念和一些比较基本的用法,这一节里具体给大家讲讲,类该如何定义和使用!

  • 类的定义:

    class 类名{
        //成员属性/成员变量
        //成员方法
    }
    

    class是定义类的关键字,后面跟着类名,再是花括号,然后里面写内容。

    列如:

    class Person{
        //成员属性或者叫成员变量
        public String name;
        public int age;
        
        //成员方法
        public void eat(){
            System.out.println("吃饭!");
        }
        
        public void run(){
            System.out.println("跑步!");
        }
    }
    

    这里面引入了两个新的概念,成员属性和成员方法,他们和普通的属性和方法有什么区别呢?

    区别:

    成员属性普通属性
    生命周期随着对象创建而创建,随着对象销毁而销毁。创建在方法里面的,他们的生命周期出了大括号就被销毁了,也就是说只能在大括号里内部使用。
    初始值成员属性默认都是有初始值的,就是你不赋值,就可以直接拿来用。普通属性默认是没有初始值,也就是说你必须赋值了,才能使用,否则就直接报错了。
    访问权限成员属性是属于你这个对象的,也就是说你把这个对象实例化以后,你是可以直接用里面的属性的,当然如果你设置权限访问修饰符不是public,你就不能直接使用里面的属性。普通属性如果是在花括号里面定义的,只能在花括号里面访问,花括号外面访问不到。

    成员方法是属于类的方法,里面有几个很特殊的方法,我们需要重点讲讲,下面请看代码:

    class Person{
        //成员属性或者叫成员变量
        public String name;
        public int age;
        
        //成员方法
        public void eat(){
            System.out.println("吃饭!");
        }
        
        public void run(){
            System.out.println("跑步!");
        }
        
        public void setPerson(String a, int b){
            name = a;
            age = b;
        }
    }
    
    public class Test{
        public static void main(){
            Person p = new Person();
            p.setPerson("张三",18);
        	System.out.println(p.name + "-" + p.age);
            //此时打印张三-18
        }
    }
    

    大家有没有发现这样写很麻烦阿!每次都要写一个setPerson方法才能给成员属性赋值,刚好这一点Java帮你想到了,所以又出了一个新的语法叫做构造方法;什么是构造方法呢?就是在你实例化一个对象的时候,你就可以给他赋值了,就不用自己再写一个方法了,下面请看例子:

    class Person{
        //成员属性或者叫成员变量
        public String name;
        public int age;
        
        //成员方法
        public void eat(){
            System.out.println("吃饭!");
        }
        
        public void run(){
            System.out.println("跑步!");
        }
        
        //public void setPerson(String a, int b){
            //name = a;
            //age = b;
        //}
        
        //构造方法
        public Person(String n,int a){
            name = n;
            age = a;
        }
    }
    
    public class Test{
        public static void main(){
            Person p = new Person("张三",18);
        	System.out.println(p.name + "-" + p.age);
            //此时打印张三-18
        }
    }
    

    直接这么写,是不是就减少了很多不必要的麻烦呀!大家以后赋值的时候,就可以直接使用构造方法了,就不需要自己写了,构造方法也是有讲究的,不能随便写的,下面学习一下怎么写出一个构造方法?

    class Person{
        //成员属性或者叫成员变量
        public String name;
        public int age;
        
        //成员方法
        public void eat(){
            System.out.println("吃饭!");
        }
        
        public void run(){
            System.out.println("跑步!");
        }
        
        //public void setPerson(String a, int b){
            //name = a;
            //age = b;
        //}
        
        /**
        *构造方法:
        *	1.方法名必须与类名相同
        *	2.没有返回值的类型,这里不允许写返回值的类型
        *	3.如果自己没有写构造方法,Java默认会提供一个无参的构造方法
        *	4.如果自己已经写了一个构造方法,Java就不再提供无参的构造方法
        */
        public Person(String n,int a){
            name = n;
            age = a;
        }
        
        //如果要写无参的构造方法,那就自己添上去
        public Person(){
            
        }
    }
    
    public class Test{
        public static void main(){
            //然后下面直接在实例化的时候,就可以传参了
            Person p = new Person("张三",18);
        	System.out.println(p.name + "-" + p.age);
            //此时打印张三-18
        }
    }
    
    • 构造方法:

      1. 方法名必须与类名相同。

      2. 没有返回值的类型,这里不允许写返回值的类型。

      3. 如果自己没有写构造方法,Java默认会提供一个无参的构造方法。

      4. 如果自己已经写了一个构造方法,Java就不再提供无参的构造方法。

3.this关键字的使用

大家有没有想过一点,你调用的这些方法,有没有可能会搞混,就是说Java他怎么知道你调用的是谁的方法,下面请看例子:

class Person{
    //成员属性或者叫成员变量
    public String name;
    public int age;
    
    //成员方法
    public void eat(){
        System.out.println("吃饭!");
    }
    
    public void run(){
        System.out.println("跑步!");
    }
    
    //public void setPerson(String a, int b){
        //name = a;
        //age = b;
    //}
    
    /**
    *构造方法:
    *	1.方法名必须与类名相同
    *	2.没有返回值的类型,这里不允许写返回值的类型
    *	3.如果自己已经写了一个构造方法,Java就不再提供无参的构造方法
    */
    public Person(String n,int a){
        name = n;
        age = a;
    }
    
    //如果要写无参的构造方法,那就自己添上去
    public Person(){
        
    }
    
    public void show(){
        System.out.println(name + "-" + age);
    }
}

public class Test{
    public static void main(){
        Person p1 = new Person("张三",18);
        Person p2 = new Person("李四",28);
        Person p3 = new Person("王五",38);
    	p1.show();
        p2.show();
        p3.show();
        //站在Java这里考虑,我怎么知道我应该调用的是谁的方法
    }
}

这是个很重要的问题,我怎么能够清楚,我到底调用的是谁的方法呢?这时候又要引出一个概念啦!叫做this关键字,其实我们在调用一个方法的时候,会自动带上一个参数,这个参数就是当前调用这个方法的对象,请看例子:

class Person{
    //成员属性或者叫成员变量
    public String name;
    public int age;
    
    //成员方法
    public void eat(){
        System.out.println("吃饭!");
    }
    
    public void run(){
        System.out.println("跑步!");
    }
    
    //public void setPerson(String a, int b){
        //name = a;
        //age = b;
    //}
    
    /**
    *构造方法:
    *	1.方法名必须与类名相同
    *	2.没有返回值的类型,这里不允许写返回值的类型
    *	3.如果自己已经写了一个构造方法,Java就不再提供无参的构造方法
    */
    public Person(String n,int a){
        name = n;
        age = a;
    }
    
    //如果要写无参的构造方法,那就自己添上去
    public Person(){
        
    }
    
    //其实这里默认会带上一个参数,只是不显示出来而已,我写上去也不会报错,就是通过这个参数,Java才知道	//当前调用的是谁的方法
    public void show(Person this){
        System.out.println(name + "-" + age);
    }
}

public class Test{
    public static void main(){
        Person p1 = new Person("张三",18);
        Person p2 = new Person("李四",28);
        Person p3 = new Person("王五",38);
    	p1.show();
        p2.show();
        p3.show();
        //站在Java这里考虑,我怎么知道我应该调用的是谁的方法
    }
}

这里就要引出this的作用啦!

this有三个作用:

  1. this用来访问成员属性。
  2. this用来访问成员方法。
  3. this用来访问构造方法,这一点只能在构造方法里面来用,且必须把this放在构造方法里的第一行,否则报错。

看例子!看例子!

class Person {
    //成员属性或者叫成员变量
    public String name;
    public int age;

    //成员方法
    public void eat() {
        //可以用this来访问其他的成员方法
        this.show();
        System.out.println("吃饭!");
        //可以用this来访问其他的成员属性
        System.out.println(this.name);
    }

    public void run() {
        System.out.println("跑步!");
    }

    /**
     * 构造方法:
     * 1.方法名必须与类名相同
     * 2.没有返回值的类型,这里不允许写返回值的类型
     * 3.如果自己已经写了一个构造方法,Java就不再提供无参的构造方法
     */
    public Person(String n, int a) {
        //这里就是用this访问构造方法
        this();
        //当我们遇到赋值的时候,最好使用this关键字来访问你的成员变量来进行赋值,因为可能会遇到名字一样		  //的情况,不知道到底谁给谁赋值,所以带上this就不会遇到这种情况了
        this.name = n;
        this.age = a;
    }

    //如果要写无参的构造方法,那就自己添上去
    public Person() {
        System.out.println("无参的构造的方法被调用了");
    }

    //其实这里默认会带上一个参数,只是不显示出来而已,我写上去也不会报错,就是通过这个参数,Java才知道
    //当前调用的是谁的方法
    public void show(Person this) {
        System.out.println(name + "-" + age);
    }
}

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 18);
        p1.eat();
    }
}

注意:

  1. 当用this来访问构造方法的时候,必须是放在构造方法内部来使用并且必须放在构造方法的第一行来使用。
  2. 当我们遇到赋值的时候,最好使用this关键字来访问你的成员变量来进行赋值,因为可能会遇到名字一样 的情况,不知道到底谁给谁赋值,所以带上this就不会遇到这种情况了。
  3. 不能在静态方法里使用this关键字。

4.封装

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

那封装有什么意义呢?就比如说你有一些属性,你不想让别人访问到,比如说性别,身份证号,这些东西你不想让别人直接看到,就可以通过封装,把这些属性隐藏起来,然后对外只提供一些交互的接口就行了。

在我们学习封装之前,我们要必须要了解一下包的概念和访问修饰限定符!!!

访问修饰限定符

我们先了解一下访问修饰限定符,它分为这么几种,请看下列表格:

范围privatedefaultprotectedpublic
1同一包中的同一类
2同一包中的不同类
3不同包中的子类
4不同包中的非子类

权限的大小:public > protected > default > private

我们通过下面的例子,来具体了解了解每种权限的范围大小:

首先看private,private只能在当前类来访问,其他类是访问不到的,如果想访问,只能通过一些公开的接口来访问,我们看下面的例子:

class Student {

    //这些属性全部都用private来修饰,我们来看看会有什么效果
    private String name;
    private int age;
    private String sex;

    public Student() {

    }

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

    //我们提供一些对外的接口,就可以直接在其他类访问了
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        Student s = new Student("张三", 18, "男");
        //大家看这里,我在自己内部随便访问都是没有问题的
        System.out.println(s.name);
    }

}

public class Test {
    public static void main(String[] args) {
        Student s = new Student("张三", 18, "男");
        //大家看这里,这个我们的name是private修饰的,这时候能直接访问吗?
        //不能直接访问的,你看代码直接报错了
        //private的访问范围很小,只能在自己类里访问,不能在别的类里访问
        //System.out.println(s.name);
        //如果我们想在类外访问怎么办,我们刚刚说了,是不是要提供一些对外的接口
        //你看此时就可以访问了
        System.out.println(s.getName());
        s.setName("李四");
        System.out.println(s.getName());
    }
}

第二个是default,default是包访问权限,不是让你真的在变量前面加个default,就是你不写任何的访问修饰限定符,就默认是包访问权限,他只能在当前这个包里可以访问,其他包是访问不到的,我们看下面的例子:

class Person {
    //不写任何的访问修饰限定符,
    //他就是默认权限,只能在当前包中访问,出了包就访问不到了
    //这里先不讲包的概念
    String name;
    int age;
    String sex;

    public Person() {

    }

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

public class Test2 {
    public static void main(String[] args) {
        Person p = new Person("小明", 12, "男");
        //大家看这里,我在其他类也是可以正常访问的
        System.out.println(p.name);
        System.out.println(p.age);
        System.out.println(p.sex);
    }
}

第三个是protected,我们到继承那里再做讲解。

第四个是public,这个还需要演示吗?你不天天都在用这个吗?就是哪里都可以访问的到。

看到这里,就说明大家应该对访问修饰限定符应该有了一个基本的概念,这里我们再讲讲包的概念:

包(Package)

什么是包呢?即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。

在Java里面有很多工具包,我相信大家多多少少也用过这些工具,比如说Scanner从控制台里输入一个信息进来,当你在用这个工具类的时候,你是不是先要导包阿!你不导这个包是不是用不了阿!大家看下面这张图:

image-20230324182224753

你看Java多好,给你提供了这么多工具类,你看Scanner这个类是不是就在里面阿!你要用它是不是得导包呀!

//import用来导包的
import java.util.Scanner;

public class Test3 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        System.out.println(n);
    }
}

建立包还有个好处就是你的类名是不是可以重复阿!你在同一个包里,你的类名能重复吗?不能吧!不同包里类名是不是就可以重复阿!就不需要费脑筋想名字了。

那如何新建一个包呢?请看下图:

image-20230324183253430

包创建好之后,如何导包呢?我相信你们现在用的都是IDEA这个工具来开发Java,这个软件很智能,能帮你自动导包,请看下图:

image-20230324190431433

当我敲了个sc,你看编译器自动显示Scanner这个类,这个时候回车一下,编译器就可以自动导包了

image-20230324190555140

给大家演示一下在不同包里,包访问权限是访问不到里面的成员属性和成员方法的:

package com.baidu.www6;

//演示包访问权限,在不同包下是不能访问的
public class Dog {
    String name;
    int age;

    public Dog() {
    }

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

//下面是另一个包中的类
package com.baidu.www7;

import com.baidu.www6.Dog;

public class Test {

    public static void main(String[] args) {
        Dog dog = new Dog("小白", 15);
        //大家看这行代码,报错了对不对,因为默认权限下,是不能在另一个包里进行访问的
        System.out.println(dog.name);
    }
}

看到这里,相信大家应该了解了访问修饰限定符和包的概念了吧!还有一些点,我们放在下面来讲,封装这一块就算讲完了。

5.static静态关键字的使用

静态成员

static是静态关键字,他是用来修饰成员属性和成员方法的,当在属性或者方法的后面,添加了static关键字,那这个属性或者方法就不再和对象有关联了,他属于类的成员或者是类的方法。请看下面的例子:

class Student {
    public String name;
    public int age;
    //这个属性被static给修饰了,他就属于类的属性了
    public static String ClassID = "一班";

    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;
    }

    public static String getClassID() {
        return ClassID;
    }

    public static void setClassID(String classID) {
        ClassID = classID;
    }

    public void show() {
        System.out.println(name + "-" + age + "-" + ClassID);
    }
}

public class Test {

    public static void main(String[] args) {
        Student student1 = new Student("张三", 18);
        Student student2 = new Student("王五", 18);
        Student student3 = new Student("李四", 18);
        //静态成员也可以直接通过类名来访问
        System.out.println(Student.ClassID);
        //也可以通过对象访问:但是classRoom是三个对象共享的
        System.out.println(student1.ClassID);
        System.out.println(student2.ClassID);
        System.out.println(student3.ClassID);
    }
}

在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,它不再属于某个具体的对象,而是所有对象所共享的。

静态成员变量特性:

  1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中。
  2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问。
  3. 类变量存储在方法区当中。
  4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)。

既然说了可以通过类名访问,也可以通过对象访问,是不是还可以有点更极端的写法,比如说这种:

class Student {
    public String name;
    public int age;
    //这个属性被static给修饰了,他就属于类的属性了
    public static String ClassID = "一班";

    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;
    }

    public static String getClassID() {
        return ClassID;
    }

    public static void setClassID(String classID) {
        ClassID = classID;
    }

    public void show() {
        System.out.println(name + "-" + age + "-" + ClassID);
    }
}

public class Test {

    public static void main(String[] args) {
        Student student1 = null;
        System.out.println(student1.ClassID);
  //这里依然没有报错,大家看到了,我给student1赋值了null对吧!但是我依然可以访问到ClassID这个
  //属性的值,这是为什么呢?本质上看起来好像是用对象去调用里面的属性,实际上在底层是通过这个对象	
  //对应的那个类去访问的那个静态成员,所以才没有报错,大家以后看到这行代码,千万不要以为是错误哦!
    }
}

这里依然没有报错,大家看到了,我给student1赋值了null对吧!但是我依然可以访问到ClassID这个属性的值,这是为什么呢?本质上看起来好像是用对象去调用里面的属性,实际上在底层是通过这个对象对应的那个类去访问的那个静态成员,所以才没有报错,大家以后看到这行代码,千万不要以为是错误哦!

静态方法

说完了静态成员,我们再看看静态方法,请看下面的例子:

class Student {
    public String name;
    public int age;
    public static String ClassID = "一班";

    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;
    }

    public static String getClassID() {
        return ClassID;
    }

    public static void setClassID(String classID) {
        ClassID = classID;
    }

    public void show() {
        System.out.println(name + "-" + age + "-" + ClassID);
        //在成员方法里,可以调用静态方法吗?可以的
        func();
        //在成员方法里,可以调用静态属性吗?也是可以的
        System.out.println(ClassID);
    }

    public static void func() {
        //可以在静态方法里面使用非静态的成员吗?不可以的,静态方法属于类的方法
        //静态方法里面是不能直接调用非静态的成员属性
        //System.out.println(name);
        //可以在静态方法里面使用非静态的成员方法吗?也是不可以的
        //静态方法里面是不能直接调用非静态的成员方法
        //show();
        //如果要想用不是静态的变量或者是方法的话,必须通过实例化对象才能访问到
        Student s = new Student("小明", 14);
        System.out.println(s.name);
        s.show();
        System.out.println("输出静态成员:" + ClassID);
    }

}

public class Test {

    public static void main(String[] args) {
        Student student1 = new Student("张三", 18);
        Student student2 = new Student("王五", 18);
        Student student3 = new Student("李四", 18);
        student1.show();
        student2.show();
        student3.show();
        System.out.println(student1.ClassID);
        //通过类名直接调用func方法
        Student.func();
    }
}

静态方法特性:

  1. 不属于某个具体的对象,是类方法。
  2. 可以通过对象调用、也可以通过类名、静态方法名等方式调用,更推荐使用后者。
  3. 不能在静态方法中访问任何非静态成员变量。
  4. 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用。

6.代码块的使用

代码块分为四种分别是:

  1. 普通代码块
  2. 构造代码块
  3. 静态代码块
  4. 同步代码块

普通代码块

普通代码块:定义在方法中的代码块。

public class Test2 {
    public static void main(String[] args) {
        int a = 10;
        {
            int b = 20;
            System.out.println(a + b);
        }
        System.out.println(a);
    }
}

构造代码块

构造代码块:用来初始化成员变量

class Person {
    public String name;
    public int age;

    public Person() {
    }

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

    //用来初始化成员变量
    {
        System.out.println("构造代码块启动了");
        this.name = "张三";
        this.age = 12;
    }

    public void show() {
        System.out.println(name + "-" + age);
    }
}

public class Test3 {
    public static void main(String[] args) {
        Person p = new Person("李四", 125);
        p.show();
    }
}

静态代码块

静态代码块:用来初始化静态成员变量

class Person {
    public String name;
    public int age;
    public static String tmp;

    public Person() {
    }

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

    //初始化静态成员变量,并且只会调用一次
    static {
        System.out.println("静态代码块1启动了");
        tmp = "你好世界";
    }

    //如果有多个静态代码块,那就按照先后循序执行,静态成员的值取决于后执行的代码块
    static {
        System.out.println("静态代码块2启动了");
        tmp = "我不好";
    }

    //初始化成员变量,每当被实例化一次,构造代码块就会被启动一次
    {
        System.out.println("构造代码块启动了");
        this.name = "张三";
        this.age = 12;
    }

    public void show() {
        System.out.println(name + "-" + age + "-" + tmp);
    }
}

public class Test3 {
    public static void main(String[] args) {
        Person p1 = new Person("李四", 125);
        p1.show();
        Person p2 = new Person("王五", 125);
        p2.show();
    }
}

注意事项:

  1. 静态代码块不管生成多少个对象,其只会执行一次。
  2. 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的。
  3. 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行。
  4. 实例代码块只有在创建对象时才会执行。

7.继承

什么是继承

举个例子,比如说你家是百万富翁,你以后要继承你家的遗产,这是不是就是继承,那在Java中继承应该是个什么样子的呢?在Java中比如有一些类,有一些共性的东西,比如说狗有名字吧!有颜色吧!你看猫也有这些属性,然后你就会发现,如果你有,我有,大家都有这些属性,我们是不是就可以把这些属性抽取出来,比如说抽取成一个Animal的类,让大家都来继承它,这样是不是大大减少了代码的冗余阿!

class Dog{
    public String name;
    public int age;

    public void eat(){
        System.out.println("吃饭");
    }

    public void bark(){
        System.out.println("旺旺叫");
    }
}

class Cat{
    public String name;
    public int age;

    public void eat(){
        System.out.println("吃饭");
    }

    public void bark(){
        System.out.println("喵喵叫");
    }
}

使用继承有什么意义吗

继承的存在就是为了大大降低我们代码的冗余,把共有的一些东西抽取出来,这样的话代码就进行一个复用了。

继承的概念

继承:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性 的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。

image-20230324204931748

在Java当中父类可以被称为超类或者是基类,子类可以被称为派生类。

继承的定义

下面请看代码演示:

class Animal {
    public String name;
    public int age;
}

//Dog继承了Animal
class Dog extends Animal {

    public String color;

    //可以看到Dog这个类里面并没有name这个属性,但是代码依旧没有报错,这是因为Dog继承了Animal这个类之	 //后,他就拥有了父类的相关属性,如果在子类里面找不到,就会去父类里面找,结果就找到了name属性了
    //并且Dog这个类里面还可以拥有自己的相关属性和方法
    public void eat() {
        System.out.println(name+"在吃饭");
    }

    public void bark() {
        System.out.println(name+"旺旺叫");
    }
}

//Cat继承了Animal
class Cat extends Animal{

    public String address;

    public void eat() {
        System.out.println(name+"在吃饭");
    }

    public void bark() {
        System.out.println(name+"喵喵叫");
    }
}

在Java里面,继承使用的关键字是extends,通过这个关键字就可以继承别的类了。

注意:

  1. 在Java当中,一个类只允许继承一个类,就是说一个子类,只允许有一个父类,但是父类可以拥有多个子类,因为父类可以被很多类继承,但是子类只允许继承一个。
  2. 子类会将父类中的成员变量或者成员方法继承到子类中。
  3. 子类继承父类之后,必须要新添加自己特有的成员,体现出与父类的不同,否则就没有必要继承了。

Super关键字的使用

大家请看下面这个案例:

class Animal{
    public String name = "小王";
    public int age;
    
    public void run(){
        System.out.println("动物们都在跑");
    }
}

class Dog extends Animal{
    public String name = "小明";

    public void run(){
        System.out.println("小明在跑");
    }
    
    public void show(){
        /*
        	当子类继承了父类,子类就会把父类的属性和方法,放到自己的身上,来看这个案例,父类有name属			  性和run方法,子类也有name和run方法,此时这个方法是打印父类的呢?还是打印子类自己的呢?答				案是自己的,这是为什么呢?一句话概括就是子类没有的属性,就会去父类里面找,子类有了,就调用			 自己的,子类和父类都找不到相关的属性,那就报错了。		
        */
        System.out.println(name);
        run();
    }
}

public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.show();
    }
}

总结:就是就近原则,子类和父类有相同的属性和方法,优先调用自己的,子类里面找不到相关的属性或者方法的话,就会去父类里面找,父类里面都找不到的话,那就报错了。

那有些人就会想,我想要访问父类的属性,方法怎么办呢?用super关键字就可以访问到父类的属性和方法,请看案例:

class Animal{
    public String name = "小王";
    public int age;
    
    public void run(){
        System.out.println("动物们都在跑");
    }
}

class Dog extends Animal{
    public String name = "小明";

    public void run(){
        System.out.println("小明在跑");
    }
    
    public void show(){
        //使用super关键字来访问父类的属性和方法
        //此时打印的就是父类的属性和方法了
        System.out.println(super.name);
        super.run();
    }
}

public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.show();
    }
}

那有人就会问super是用来做什么的呢?

super有三个作用分别是:

  1. 用super来访问父类的成员属性。
  2. 用super来访问父类的成员方法。
  3. 用super来访问父类的构造方法。

前面两个我们已经举例子了,我们来具体说说第三个功能,请看下面案例:

class Animal {
    public String name = "小王";
    public int age;

    //当我们父类定义好了一个构造方法,此时发现子类立马报错了,这是为什么呢?
    //这是因为子类没有去帮父类的属性做一个初始化,所以才会报错,所以需要在子类里面
    //去帮助父类进行初始化,说白了就是在子类的构造方法里,调用父类的构造方法
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void run() {
        System.out.println("动物们都在跑");
    }
}

class Dog extends Animal {
    public String color;

    //我们在子类里面,帮助父类进行初始化,此时发现,代码里面就不报错了,说明在子类初始化
    //之前,必须要先帮助父类进行初始化,如果子类还有自己的属性,还可以在下面接着写对自己的
    //属性初始化的代码
    public Dog(String name, int age, String color) {
        //看这里!看这里!这一点很重要,在帮助父类初始化的时候,super()必须写在第一行,否则报错
        super(name, age);
        this.color = color;
    }

    public void run() {
        System.out.println("小明在跑");
    }

    public void show() {
        System.out.println(name + "-" + age + "-" + color);
    }
}

public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog("小白",15,"白色");
        dog.show();
    }
}

总结:子类在初始化之前必须要先去帮父类进行初始化,也就是说必须在子类里面写父类的构造方法,帮助父类初始化,如果不帮则报错,而且super必须写在子类的第一行,要不然也报错。

注意:子类在帮助父类初始化的时候,必须要把super()写在构造方法里的第一行,要不然就报错。

那很多同学就会问,你刚刚在写代码的时候,没看到你帮父类初始化阿!为什么那个时候不报错呢?其实是因为编译器帮你做了初始化,我们都知道在你不写构造方法的时候,编译器会自动帮你加个无参构造方法进去,当编译器看到你是继承父类的时候,编译器会自动帮你在构造方法里面加一个super(),所以才没有报错。就像是这样:

class Animal{
    public String name = "小王";
    public int age;
    
    public Animal(){
        
    }
    
}

class Dog extends Animal{
    
    public Dog(){
        /*编译器隐式的帮你加了一个super(),所以你的代码才没有报错,如果你自己在父类里写了个构造
        方法,编译器就不会提供给你无参的构造方法,这时候肯定就会报错了,因为子类里面还在调用父类
        里的无参的构造方法,结果发现找不到,所以就报错了,说了这么多就是想告诉大家一点,
        子类继承父类,先帮父类进行初始化,super必须写在构造方法里的第一行,这些都做到了,
        程序就肯定没有问题。*/
        super();
    }
        
}

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

注意:super只能指代直接的父类,并不能指代父类的父类,没有super.super的这种用法!

super和this的区别:

  1. super指代的是父类的引用,this代表的是当前自己的引用!
  2. 在构造方法里,必须放在第一行且super和this不能同时在构造方法里出现!
  3. super和this都不能在静态方法中使用,因为静态方法属于类的,并不属于对象,都没有对象怎么能调用this和super呢?

Protected关键字的使用

前面的章节之所以没有讲这个关键字,是因为我们还没有了解到继承,现在我们了解到,终于可以谈谈他了,请看下面这个案例:

package Demo1;

public class Person {

    //我在这里用了protected关键词来修饰变量,大家看看有什么效果
    protected String name = "小明";
    protected int age;
    private String sex = "男";

    public Person() {

    }

    public Person(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;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    //大家看这里,我在自己类里面访问,没有任何问题,代码可以正常输出
    public static void main(String[] args) {
        Person p = new Person("张三",12);
        System.out.println(p.name);
    }
}

//==============================================
package Demo1;

public class Test {

    //然后这是同一个包下,但是不是同一个类,也可以正常访问
    public static void main(String[] args) {
        Person p = new Person("张三", 12);
        System.out.println(p.name);
    }
}


//==============================================
package Demo2;

import Demo1.Person;

public class Test extends Person {

    public Test() {
        super();
    }

    //必须创建个方法去调用super关键字,借用super关键字来访问父类属性和方法
    public void fun() {
        System.out.println(super.name);
        System.out.println(super.getSex());
    }

    //现在才能正常访问
    public static void main(String[] args) {
       Test t = new Test();
       t.fun();
    }

    /*这里不是同一个包,但是却继承了Person类,但是却发现p.name却访问不了,
    这是因为protected修饰的量,是受保护的变量不能直接这样访问的,要通过super关键字来进行访问,
    所以我们创建了个方法,来用super关键字去访问父类的属性和方法那有同学就问了,为什么不能在main
    方法里用super呢?刚刚不是说了吗,super不能用在静态方法里,只能用在普通方法里,main方法不是
    静态方法吗?里面是用不了super的。*/
    /*
    错误写法
    public static void main(String[] args) {
        Person p = new Person();
        System.out.println(p.name);
    }
    */
}

这里还有一个小点没有跟大家提到,大家有没有想过被private修饰的变量,有没有被继承到子类呢?我们看看这个案例:

package Demo5;

class Person {
    //大家看这里阿!我用private修饰了两个变量
    private String name;
    private int age;
}

//并且我在另一个类里面是继承了Person这个类,看能不能直接访问父类的成员
public class Test extends Person {

    public void fun() {
     /*你会发现报错了,无论是我直接调用父类的成员属性还是通过super访问,都无法通过编译,
     那这是为什么呢?难道是没有继承过来吗?其实是已经继承过来了,但是不允许这么访问,
     之前是不是就说过private修饰的变量只能在自己的类里面使用,类外是不是使用不了,
     即便通过继承也是没有办法访问的,大家牢记一点,被private修饰过的变量只能在自己类里使用,
     不能在类外使用。*/
        System.out.println(name);
        System.out.println(super.name);
    }
}

final关键字的使用

final关键字有三个作用:

  1. final可以用来变量。他修饰的变量就不再是变量,而是常量了,因为final表示不可修改的意思,所以他修饰的变量你给他赋值后就不能修改了,所以就变成常量了,请看案例:

    public class Test2 {
        public static void main(String[] args) {
            //final所修饰的变量,在初始化的时候,就必须给他赋值,不允许定义好了变量,再给他赋值
            final int a = 10;
            //你看我想改变他的值,你看直接报错了,所以一旦你定义好了之后,就不允许更改了
            a = 1;
        }  
    }
    
  2. final可以用修饰方法。final所修饰的方法是不能重写的。这里我们具体到多态那一章去讲解。

  3. final可以用来修饰类。final所修饰的类是不能被继承的,被final所修饰的类还有个好听的名字叫做“密封类”或者叫做“最终类”,请看案例:

    //看这里我用final修饰类,表示这个类是不可继承的
    final class Animal {
        public String name = "小白";
        public int age = 12;
    }
    
    //大家看这里,我继承了Animal是不是直接报错了,所以这再次说明了final所修饰的类是不能继承的
    class Dog extends Animal {
        public void show() {
            System.out.println(super.name + "-" + super.age);
        }
    }
    

8.多态

什么是多态

用语文的角度来看就是一种事物,多种形态,那从Java的角度来看,多态就像是电脑的USB接口,比如你插上U盘,就可以自动读取你U盘里面的数据,你插上打印机,就可以打印文件,你不同的东西插上去,给你呈现的效果是不同的,多态就是用来干这个的,你给的东西不同,最后呈现的效果也不同。

img

使用多态有什么意义吗

我们设计多态就是想让他和电脑USB接口一样,你给的东西不一样,我给你返回的效果就不一样,大大能提示程序员开发的效率。

多态的概念

多态:就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。总的来说:就是同一件事情,发生在不同对象身上,就会产生不同的结果。

多态的实现条件

在Java中要实现多态,必须满足以下条件:

  1. 必须要继承一个类。

  2. 子类必须重写父类的方法。

  3. 通过父类的引用来调用子类重写的方法。

    //1.先定义一个父类叫做Animal
    class Animal {
        public String name;
        public int age;
    
        //定义一个方法
        public void run() {
            System.out.println("所有动物都在跑步");
        }
    }
    
    //2.想发生多态的前提必须要先继承一个类,然后重写里面的方法
    class Dog extends Animal {
        //重写父类的run方法
        @Override
        public void run() {
            System.out.println(name + "跑的飞快");
        }
    }
    
    class Bird extends Animal {
        //重写父类的run方法
        @Override
        public void run() {
            System.out.println(name + "人家不会跑,但是会飞");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            //通过父类来接收子类的new出来的对象
            Animal dog = new Dog();
            dog.name = "小花";
            //通过父类的引用调用run方法,最后结果发现竟然调用的是子类的方法
            dog.run();
            System.out.println("================");
            Animal bird = new Bird();
            bird.name = "小白";
            bird.run();
        }
    }
    

我们通过上面这个小案例,感受了一下多态的魅力,但是有很多细节没有跟大家讲到,现在就给大家分析一下:

方法的重写

为什么要有方法的重写呢?就是有这种情况父类定义好的方法,不是我子类想要的,就是你给这个功能不好,我子类就想自己重写一个,就感觉像是一个接口,你定义好了规范,我来实现里面的细节,达到我想要的功能。

方法重写的规则:

  1. 方法名必须一样

    class Animal {
        public String name;
        public int age;
    
    
        public void run() {
            System.out.println("所有动物都在跑步");
        }
    }
    
    
    class Dog extends Animal {
        //1.方法名必须一样
        //@Override这个注解就是用来帮我们检查编译错误的,
        //看自己是不是把方法名,参数列表,参数类型写错了
        @Override
        //你看我只要我方法名写错了,上面那个注解是不是立马报错了
        public void run111() {
            System.out.println(name + "跑的飞快");
        }
    }
    
  2. 参数列表和参数类型必须一样

    class Animal {
        public String name;
        public int age;
    
    
        public void run(String name) {
            System.out.println("所有动物都在跑步");
        }
    }
    
    
    class Dog extends Animal {
        //2.参数列表和参数类型必须一样
        //@Override这个注解就是用来帮我们检查编译错误的,
        //看自己是不是把方法名,参数列表,参数类型写错了
        @Override
        //参数列表和参数类型不同,注解也报错了
        public void run111() {
            System.out.println(name + "跑的飞快");
        }
    }
    
  3. 返回值类型没有特殊情况也必须一样,如果有特殊情况,必须构成父子关系,也就是协变类型

    class Animal {
        public String name;
        public int age;
    
    //    public void run() {
    //        System.out.println("所有动物都在跑步");
    //    }
    
        //构成父子关系就不会报错
        public Animal run() {
            System.out.println("所有动物都在跑步");
            return null;
        }
    }
    
    class Dog extends Animal {
        //3.返回值类型没有特殊情况也必须一样,如果有特殊情况,必须构成父子关系,也就是协变类型
        //@Override这个注解就是用来帮我们检查编译错误的,
        //看自己是不是把方法名,参数列表,参数类型写错了
    //    @Override
    //    //返回值类型其实也必须是一样的,不一样也会报错
    //    public int run() {
    //        System.out.println(name + "跑的飞快");
    //    }
        
        //但是构成父子类关系除外
        @Override
        public Dog run() {
            System.out.println(name + "跑的飞快");
            return null;
        }
    }
    
  4. 不能重写private所修饰的方法

    class Animal {
        public String name;
        public int age;
    
        private void run() {
            System.out.println("所有动物都在跑步");
        }
    
    }
    
    class Dog extends Animal {
    
        //4.不能重写private所修饰的方法,你看注解那里直接报错了
        @Override
        public void run() {
            System.out.println(name + "跑的飞快");
        }
    }
    
  5. 不能重写static所修饰的方法

    class Animal {
        public String name;
        public int age;
    
        public static void run() {
            System.out.println("所有动物都在跑步");
        }
    
    }
    
    class Dog extends Animal {
    
        //5.不能重写static所修饰的方法,你看注解那里直接报错了
        @Override
        public static void run() {
            System.out.println(name + "跑的飞快");
        }
    }
    
  6. 不能重写final所修饰的方法,final所修饰的方法叫做密封方法,也称作最终方法

    class Animal {
        public String name;
        public int age;
    
        public final void run() {
            System.out.println("所有动物都在跑步");
        }
    
    }
    
    class Dog extends Animal {
    
        //6.不能重写final所修饰的方法,final所修饰的方法叫做密封方法,也称作最终方法
        //注解就是用来检查这些东西,不符合规则就会报错
        @Override
        public void run() {
            System.out.println(name + "跑的飞快");
        }
    }
    
  7. 不能重写构造方法

  8. 子类的访问修饰限定权限,要大于等于父类 private < 默认 < protected < public

    class Animal {
        public String name;
        public int age;
    
        public void run() {
            System.out.println("所有动物都在跑步");
        }
    
    }
    
    
    class Dog extends Animal {
        //8.子类的访问修饰限定权限,要大于等于父类 private <  默认 < protected < public
        @Override
        //你看我这里的权限是小于父类的权限,是不是也报错了
        protected void run() {
            System.out.println(name + "跑的飞快");
        }
    }
    

向上转型

我们刚刚看到下面这种代码,大家有没有疑惑,等号两边类型应该是要相同的,但是这两个类型明明不相同,却还可以赋值,这是为什么呢?

class Animal {
    public String name;
    public int age;

    public void run() {
        System.out.println("所有动物都在跑步");
    }

}

class Dog extends Animal {

    @Override
    public void run() {
        System.out.println(name + "跑的飞快");
    }
}

public class Test {

    public static void main(String[] args) {
        //等号两边的类型不同,却还能赋值这是为什么呢
        Animal dog = new Dog();
        dog.name = "小花";
        dog.run();
    }
}

这里其实就发生了向上转型,把子类的new出来的实例,交给父类接收,叫做向上转型,为什么要这样做呢?还记得我们刚开始举得那个例子吗?我说电脑的USB接口,你插入什么东西进去,就可以展示什么功能对不对,这里其实就是为了实现我刚刚说的那个功能,把子类的实例交给父类来接收,如果子类重写父类的方法,那就可以通过父类的引用去调用子类的方法,从而体现出你给的东西不同,我就可以给你展现不同的功能,这里讲的很抽象,我们通过几个例子来看看:

//定义了一个shape类,里面定义了一个方法
class Shape {
    public void draw() {
        System.out.println("画图形");
    }
}

//定义了一个Cycle类继承了Shape,并重写了draw()方法
class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("画圆形");
    }
}

//定义了一个Rect类继承了Shape,并重写了draw()方法
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("画矩形");
    }
}

//定义了一个Flower类继承了Shape,并重写了draw()方法
class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("❀");
    }
}

public class Test {

    //题目:调用类里的方法,输出圆形,矩形,圆形,矩形,花

    //这是不用多态写的,你看用了大量的ifelse语句,因为他必须得判断才知道该调哪一个类的方法
    public static void func2() {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Flower flower = new Flower();
        String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
        for (String x : shapes) {
            if (x.equals("cycle")) {
                cycle.draw();
            } else if (x.equals("rect")) {
                rect.draw();
            } else {
                flower.draw();
            }
        }
    }

    /**
     * 这是用多态写的,你看只需要定义一个数组,然后把实例化的对象放到数组里,
     * 然后再依次调用他们的方法就可以很简单的写出那道题,这就是多态的好处
     */
    public static void func3() {
        Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()};
        for (Shape x : shapes) {
            x.draw();
        }
    }

    public static void main(String[] args) {
        //func2();
        func3();
    }

上面这个案例就是向上转型,把子类的实例交给父类接收,然后通过父类的引用,去调用子类重写父类的方法。

向上转型的优缺点
  • 优点:让代码实现更简单灵活。
  • 缺点:父类接收了子类的实例,此时通过父类只能访问自己的成员,不能访问到子类特有的成员

向下转型

向下转型一般用的很少,因为他不安全,可能会出现转换失败的现象,向下转型就是将父类的实例,让子类去接收,你想想一个大的范围,突然放到一个很小的范围里,就很容易出现问题,请看下面案例:


class Animal {
    public String name;
    public int age;

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

class Dog extends Animal {
    public void wangwang() {
        System.out.println(name + "在旺旺叫");
    }
    
    @Override
    public void eat() {
        System.out.println(name + "正在吃狗粮");
    }
}

class Cat extends Animal {
    public void miaomiao() {
        System.out.println(name + "在喵喵叫");
    }

    @Override
    public void eat() {
        System.out.println(name + "正在吃猫粮");
    }
}

public class Test {

    public static void main(String[] args) {
        Animal animal = new Dog();
        //把父类的实例交给子类,此时通过子类的实例,就可以访问到子类自己特有的方法
        Dog dog = (Dog)animal;
        dog.name = "小白";
        dog.wangwang();
    }
}

上面代码是可以正常运行的,我们来看看一个错误的案例:

class Animal {
    public String name;
    public int age;

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

class Dog extends Animal {
    public void wangwang() {
        System.out.println(name + "在旺旺叫");
    }

    @Override
    public void eat() {
        System.out.println(name + "正在吃狗粮");
    }
}

class Cat extends Animal {
    public void miaomiao() {
        System.out.println(name + "在喵喵叫");
    }

    @Override
    public void eat() {
        System.out.println(name + "正在吃猫粮");
    }
}

public class Test {

    public static void main(String[] args) {
        /**
         * 此时这就是一个错误的案例,先用父类Animal接收子类cat的实例,
         * 然后又对父类的实例进行向下转型,你父类里面存的都不是Dog的实例,这怎么能转换呢?
         * 所以代码一运行就报异常ClassCastException,类型肯定无法转换阿!所以为了避免这个错误
         * java写了一个关键字,叫做instanceof,这个关键字用来检查对象里引用的是哪个类,
         * 如果不是我想要引用的那个类,就为false,否则就为true
         */

//        Animal animal = new Cat();
//        Dog dog = (Dog)animal;
//        dog.name = "小白";
//        dog.wangwang();
        /**
         * 所以代码可以这么改,你看现在代码就没有报错了,因为你引用的都不是Dog的实例,所以肯定不能让你		   * 运行instanceof就是用来检查这些东西的,就是为了防止用向下转型出错。
         */
        Animal animal = new Cat();
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.name = "小白";
            dog.wangwang();
        }

    }

向下转型一般都不怎么用,如果有要用到的场景,我们还是要了解一下他的语法,instanceof就是为了检查这种错误而诞生的。

9.面试题

这里面有一道面试题:就是问你重载和重写的区别,大家重点记忆!!!

重载:

  • 方法名必须一致。
  • 参数类型和参数列表必须不同。
  • 和返回值没有关系。

重写:

  • 方法名必须一致
  • 参数类型和参数列表必须相同。
  • 返回值必须相同,除非构成父子关系,达成协变类型。
  • 子类的访问修饰限定权限,要大于等于父类 private < 默认 < protected < public
  • 不能重写private,static,final所修饰的方法。

10.致谢

感谢你一路看到这里,如果你觉得我写的不错的话,请给我一键三连!!!😉😉

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值