JAVA基础-面向对象进阶(一)

目录

1.static关键字

应用场景

 2.static修饰成员方法

 应用场景

 3.static注意事项

4.代码块

静态代码块

实例代码块

5.单例设计模式

         饿汉式单例设计模式

懒汉式单例设计模式

 6.继承

 ​7.继承的相关注意事项 

7.1权限修饰符

7.2单继承

7.3 Object

7.4 方法重写

7.5 子类成员的访问特点 

7.6 方法重写的应用场景

7.7 子类构造器的特点

7.8 子类访问父类构造器

​7.9 this调用兄弟构造器


1.static关键字

  • 叫静态,可以修饰成员变量、成员方法

成员变量按照有无static修饰,分为两种:

  • 类变量(静态变量):有static修饰,属于类,在计算机里只有一份,会被类的全部对象共享。
  • 实例变量:属于每个对象的变量,无static修饰

 

 用static修饰的变量name,在学生对象中都不会放name,name是被每个对象共享的

访问方法:

类名.类变量(推荐)

对象.类变量(不推荐)

Student类:

public class Student {
    //静态变量:由static修饰,属于类持有,在内存中只加载一次,被类和类的全部对象共享
    static String name;
    //实例变量:无static修饰,属于每个对象。
    int age;
}

Test:

public class Test {
    public static void main(String[] args) {
        //目标:理解static成员变量的用法
        //1.类名.静态变量(推荐)
        Student.name = "袁华";
        System.out.println(Student.name);

        //对象.静态变量(不推荐)
        Student s1 = new Student();
        s1.name = "马冬梅";

        //对象.静态变量(不推荐)
        Student s2 = new Student();
        s2.name = "秋雅";

        System.out.println(s1.name);//static修饰的静态变量name在内存中只有一个,修改的一直都是一个name

        //2.对象.实例变量
        //Student.age = 12;//报错!!
        s1.age = 23;
        s2.age = 18;
        System.out.println(s1.age);//23,实例变量是每个对象都有的
    }
}

static修饰的静态变量name在内存中只有一个,修改的一直都是一个name 

 

 

 实际上这里的name和age都不应该加static,因为每个学生都有单独的name和age

 应用场景:在开发中,如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义为类变量

应用场景

eg:系统启动后,要求用户类可以记住自己创建了多少个用户对象

我们可以在构造器里写User.number++,因为每次创建一个对象就要调用一次构造器

public class User {
    public  static int number;//静态变量,只有一份,可以被共享

    public  User(){
       // User.number++;
        //注意:访问当前类中的静态变量,前面的类名可以省略
        number++;
    }
}
public class Test2 {
    public static void main(String[] args) {
        //目标:搞清楚static修饰成员变量的使用场景
        new User();
        new User();
        new User();
        System.out.println("创建了几个对象:" + User.number);
    }
}

 2.static修饰成员方法

我们一般称类方法为静态方法 (有static修饰的成员方法)

静态方法:由static修饰,属于类持有,建议用类名访问,也可以用对象访问

实例方法:对象的方法,必须用对象触发访问

 成员方法执行原理:

Student对象调用实例方法时的原理:
 

 

 应用场景

类方法最常用的应用场景是做工具类

什么是工具类呢?工具类中的方法都是一些静态方法,每个方法都是用来完成一个功能的,工具类是给开发人员共同使用的。

实例:登录和注册类中要进行生成验证码的功能,每次都写一遍会造成代码的复用,所以我们直接创建一个工具类写一个生成验证码的方法,其他类只需要调用即可

public class itheimaUtil {
    //将构造器私有,不能再对工具类创建对象
    private itheimaUtil(){

    }
    //静态方法,可以直接通过类名调用,不需要创建对象再调用,节省内存
    public static String createCode(int n){
        String code = "";
        String data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

        Random r = new Random();
        for (int i1 = 0; i1 < n; i1++) {
            int index = r.nextInt(data.length());
            char ch = data.charAt(index);
            code += ch;
        }

            return code;
    }
}

 工具类中的方法为什么用静态方法,不用实例方法?

  • 实例方法需要创建对象进行调用,只能创建工具类的对象然后才能调用,此时创建这个对象也只是为了调用方法,而我们完全可以不创建对象直接用类名调用静态方法,创建对象会占内存,会造成内存的浪费
  • 静态方法,直接用类名调用即可,调用方便,也能节省内存

工具类没有创建对象的需求,我们应该将工具类的构造器进行私有。

 3.static注意事项

public class Test {
    //静态变量
    public static String schoolName = "我是bbb";
    //静态方法
    public static void inAddr(){
        System.out.println("我现在正在学java~~");
    }
    //实例变量
    private String name;
    //实例方法
    public void printInfo(){
        System.out.println("名字叫:" + name);
    }
    public static void main(String[] args) {
        //目标:理解static的注意事项
        Test t = new Test();
        t.testNostatic();
    }

        //1.静态方法中可以直接访问类的静态成员,不可以直接访问实例成员(可以创建对象来访问)
        public static void testStatic() {
            System.out.println(schoolName);
            inAddr();
            //Test t = new Test();

            //System.out.println(this);//报错,没有对象调它
            //System.out.println(name);//报错
            //printInfo();//报错,类的实例成员和实例方法属于类的对象,只能创建对象之后调用
        }

        //2.实例方法中既可以直接访问静态成员,也可以直接访问实例成员
        public void testNostatic(){
            System.out.println(schoolName);
            inAddr();
            System.out.println(this.name);//能访问是因为实例方法也要用对象来调
            this.printInfo();//3.实例方法中可以出现this关键字,静态方法中不可以出现
        }

}

4.代码块

 静态代码块

import java.util.ArrayList;

public class CodeTest1 {
    public static String schoolName = "学java的bb";
    //要定义一个静态的集合来储存一个班级所有人的名字,在一个项目中只有一个这样的集合
    public static ArrayList<String> names = new ArrayList<>();
    //静态代码块:由static修饰,属于类持有,与类一起优先加载,自动执行一次
    //作用:可以用来初始化静态变量的数据
    static {
        System.out.println("==static静态代码块执行了==");
        names.add("张麻子");
        names.add("叶飞");
        names.add("bbbb");
    }

    public static void main(String[] args) {
        //目标:搞清楚静态代码块的特点,了解其应用场景
        System.out.println("===main方法执行了==");
//        names.add("张麻子");//也可以在main方法中进行初始化,但是有些方法会反复调用,会造成集合的多次初始化
//        names.add("叶飞");//要保证只初始化一次,只能在代码块中初始化
//        names.add("bbbb");
        System.out.println(names);
    }
}

实例代码块

实例可以理解为:实例 == 对象

import java.util.ArrayList;

public class CodeTest2 {
    private String name;
    //创建一个对象集合,属于这个对象
    private ArrayList<String> names = new ArrayList<>();
    //实例代码块:属于类的每个对象的,每次创建对象都会执行一次,而且是在构造器之前执行的
    //作用:实例变量的初始化
    {
        System.out.println("===实例代码块执行了一次===");
        //name = "dlei";//初始化实例变量,无意义,不可能所有对象的name都是dlei
        //names.add("东南");
        //names.add("西北");//但这样会导致所有对象集合都存储的是东南、西北,没有意义

    }

    public CodeTest2(){
        System.out.println("====构造器执行了一次====");
    }

    public static void main(String[] args) {
        //目标:搞清楚实例代码块的作用,使用场景
        //实例 == 对象
        new CodeTest2();
        new CodeTest2();
        new CodeTest2();
    }
}

 实际这两种代码块使用并不常见,我们只需要理解:
 静态代码块跟着类走,而实例代码块跟着对象走

5.单例设计模式 

 单例设计模式确保一个类只有一个对象

写法:

  • 把类的构造器私有
  • 定义一个类变量记住类的对象
  • 定义一个类方法,返回对象

 饿汉式单例设计模式

public class A {
    //2.定义一个静态变量用于记住类的一个唯一对象
    private static A a = new A();//静态变量只会加载一次,所以就只会new一个A对象
    //但是用public修饰不安全,应该用private进行封装,让对象不能随意被修改
    //public static String name = "黑马";//对比理解

    //1.把类的构造器私有化处理
    //如果不私有构造器,可以在Test类中随意创建多个对象
    //但是构造器私有之后一个对象也无法创建,单例至少要有一个对象
    private A(){

    }

    //3.提供一个静态方法返回这个唯一的对象
    public  static A getInstance(){
        return a;
    }
}
public class Test {
    public static void main(String[] args) {
        //目标:实现单例设计模式
        //A a = null;//静态变量是public修饰的不安全,可以随意修改为null
        //A a1 = A.a;
        //A a2 = A.a;
        //System.out.println(a1);
        //System.out.println(a2);

        A a1 = A.getInstance();
        A a2 = A.getInstance();
        System.out.println(a1);
        System.out.println(a2);
    }
}

单例设计模式中一个类只能创建一个对象,但是为什么有a1和a2两个对象呢,实际上这两个对象返回的是一个对象,那就是在A类中创建的a对象,我们也可以用sout(a1 == a2);来验证,返回结果为true

单例设计模式有很多,上面这种方式叫做饿汉式单例

饿汉式单例:拿对象时,对象早就创建好了

懒汉式单例:拿对象时,才开始创建对象

懒汉式单例设计模式

拿对象时,才开始创建对象

写法:

  • 把类的构造器私有
  • 定义一个类变量用于存储对象
  • 提供一个类方法,保证返回的是同一个对象

public class B {
    //2.定义一个静态变量用来记住类的一个唯一对象
    //不能写private static B b = new B();//这样就又变成了饿汉式单例
    private static B b;//此时b == null 懒汉式单例不能从一开始就创建对象

    //1.私有构造器
    private B(){
    }

    //3.提供一个静态的get方法,返回一个唯一的对象
    public static B getInstance(){
        //第一次来拿对象的时候,是需要创建对象的,后面不创建的
        if (b == null) {
            //第一次拿对象,需要自己创建对象
            b = new B();
        }
        return b;
    }
}
package com.itheima.d6_static_sginleinstance;

public class Test2 {
    public static void main(String[] args) {
        B b1 = B.getInstance();
        B b2 = B.getInstance();
        System.out.println(b1);
        System.out.println(b2);
    }
}

执行过程:

第一次调用B.getInstance()时:

  • b初始值为null(因为静态变量默认初始值为null)
  • 进入if(b == null)条件分支,执行b = new B()创建一个新的B实例
  • 返回这个新创建的实例给b1

第二次调用B.getInstance()时:

  • b 已经不再是 null(因为第一次调用时已经创建了实例并赋值给 b)。
  • 不会进入 if (b == null) 条件分支,直接返回已存在的实例 b 给 b2

虽然看起来创建了两个对象(b1 和 b2),但实际上它们引用的是同一个实例。

 原则:频繁用的对象定义为饿汉式,不频繁用的定义为懒汉式。

 6.继承

父类A:

public class A {
    public int i;
    public void print1(){
        System.out.println("===print1===");
    }

    private int j;
    private void print2(){
        System.out.println("===print1===");
    }
}

子类B:

public class B extends A{
    private int k;
    public void print3(){
        //继承特点:子类中只能继承父类非私有的成员
        System.out.println(i);
        print1();

        //System.out.println(j);//报错
        //print2();//报错
    }
}

Test:

public class Test {
    public static void main(String[] args) {
        //目标:认识继承,搞清楚继承的特点,以及继承后类创建对象的特点
        //子类对象的创建特点,会由子类和父类等多张设计图共同创建出子类对象,但是能访问什么还是要看权限
        B b = new B();//创建的子类对象拥有子类和父类中的所有方法和成员变量(无论私有和公有)
        System.out.println(b.i);
        //System.out.println(b.j);//b对象里虽然有j,但是不能访问
        //System.out.println(b.k);//b对象里虽然有k,但是不能访问,要看权限
        b.print1();
        //b.print2();
        b.print3();
    }
}

子类对象的创建特点:会由子类和父类等多张设计图共同创建出子类对象,但是能访问什么还是要看权限

创建的子类对象拥有子类和父类中的所有方法和成员变量(无论是否私有),但能否访问要看权限

内存原理:

 

7.继承的相关注意事项 

7.1权限修饰符

什么是权限修饰符?

就是用来限制类中的成员(成员变量、成员方法、构造器、代码块...)能够被访问的范围

private < 缺省 < protected < public 

 

注意:在不同包下创建一个子类对象来调用方法private、缺省、protected会报错

 7.2单继承

java是单继承的,java中的类不支持多继承,但是支持多层继承

public class Test {
    public static void main(String[] args) {
        //目标:搞清楚继承的特点
    }
}

//1.java是单继承的:一个类只能继承一个直接父类
//2.java不支持多继承
//calss A{}
//calss B{}
//calss C entends A,B{}//报错

//3.java支持多层继承
class M{}
class N extends M{}
class P extends N{}

 如果可以多继承,那么两个父类中的同名方法会造成子类不知道该继承哪一个

7.3 Object

 M类并没有继承任何类,也没有写任何的方法,为什么还是会出现这么多方法

是因为M类其实自己默认继承了Object类,他是所有类的祖宗类

Java中所有类都继承了Object

class M extends Object{}

 7.4 方法重写

写一个父类Animal: 

public class Animal {
    public void run(){
        System.out.println("动物会跑~~~");
    }
}

再写一个Tiger继承Animal:

public class Tiger extends Animal{
    //方法重写:名称与参数列表必须与父类被重写的方法一样
    @Override //重写的校验注解:安全,优雅(提示这是重写的代码)
    public void run(){
        System.out.println("老虎跑的非常快~~~");
    }
}

Test类:

public class Test {
    public static void main(String[] args) {
        //目标:认识方法重写
        Tiger t = new Tiger();
        t.run();
    }
}

使用@Override注解进行重写方法格式的校验,更加安全并提高代码可读性

 

静态方法是属于类本身持有的 ,但继承是过继给对象的,要用的话直接用类名来调用静态方法

7.5 子类成员的访问特点 

 

在变量体现如下:

下面的代码在实际开发中应该分三个类,这里为了方便观看写到了一个类里面 

public class Test {
    public static void main(String[] args) {
        //目标:继承后子类访问成员的特点,就近原则
        Zi zi = new Zi();
        zi.showName();
    }
}

class Zi extends Fu{
    String name = "子类名称";

    public void showName(){
        String name = "成员变量";
        System.out.println(name);//访问子类的局部name
        System.out.println(this.name);//访问子类的成员name
        System.out.println(super.name);//访问父类的成员name
    }
}

class Fu{
    String name = "父类名称";
}

 方法中体现如下:

public class Test2 {
    public static void main(String[] args) {
        //目标:继承后子类访问成员的特点,就近原则
        Zi2 z = new Zi2();
        z.go();
    }
}

class Zi2 extends Fu2{
    @Override
    public void run(){
        System.out.println("子类跑的贼溜~~");
    }

//中转方法
    public void go(){
        run();//子类的
        super.run();//必须调用父类的
    }
}

class Fu2{
    public void run(){
        System.out.println("父类跑");
    }
}

总结一下,在子类中访问成员(成员变量、成员方法)遵循就近原则,子类没有找父类,父类没有就报错

如果子父类中出现了重名的成员,此时一定要在在子类中使用父类的成员应该用:

super.父类成员变量/父类成员方法(只能在子类中使用)

 7.6 方法重写的应用场景

public class Student {
    private String name;
    private char sex;
    private double height;
    private String desc;

    public Student() {
    }

    public Student(String name, char sex, double height, String desc) {
        this.name = name;
        this.sex = sex;
        this.height = height;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

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

    public char getSex() {
        return sex;
    }

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

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                ", height=" + height +
                ", desc='" + desc + '\'' +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        //目标:搞清楚方法重写在实际开发应用中的场景
        Student s = new Student("赵敏",'女',169.5,"执着,真诚");
        System.out.println(s);
        //System.out.println(s.toString());//输出对象,其实默认是调用继承自父类Object的toString来返回所谓的地址
    }
}

如果在Student类中不重写toString,当输出Student对象s的时候输出的就是所谓的地址值,默认继承自Object父类,但是我们可以用快捷键重写toString方法来输出对象的属性。

 7.7 子类构造器的特点

  • 子类的全部构造器,都会先调用父类的构造器,再执行自己。

父类Animal

public class Animal {
    public Animal(){
        System.out.println("===父类Animal的无参构造器被执行了");
    }

    public Animal(String n){
        System.out.println("===父类Animal的有参构造器被执行了");
    }
}

子类Wolf:

public class Wolf extends Animal{
    public Wolf(){
        super();//写不写都有,调用父类无参构造器
        System.out.println("====子类Wolf的无参构造器被执行了===");
    }

    public Wolf(String n){
        super();//写不写都有,调用父类有参构造器
        System.out.println("====子类Wolf的you参构造器被执行了===");
    }
}

Test:

public class Test {
    public static void main(String[] args) {
        //目标:子类构造器的特点,都会先执行父类的构造器,再执行自己
        Wolf f = new Wolf();
        Wolf f1 = new Wolf("我是狼");
    }
}

如果把父类Animal中的无参构造器删除,子类无参和有参构造器都会报错,因为子类的有参和无参构造器都一定要调用父类的无参构造器。

如果父类中必须只有有参构造器,没有无参构造器,那么在子类Wolf的有参构造器中就一定要写super(n),指定去调用父类的有参构造器。

 7.8 子类访问父类构造器

为什么子类的构造器都要先调用父类的构造器?

是为了把继承自父类的数据先初始化,再把自己的数据初始化

我们通过一个例子来看一下:
People父类:
 

public class People {
    private String name;
    private int age;

    public People() {
    }

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

子类Teacher:

public class Teacher extends People{
    private String skill;
    public Teacher(){

    }

    public Teacher(String name, int age, String skill) {
        super(name,age);
        //this.name = name;//报错
        //this.age = age;//报错
        this.skill = skill;
    }


    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }
}

Test:

public class Test {
    public static void main(String[] args) {
        //目标:子类为什么一定要调用父类的构造器,初始化继承自父类部分的数据
        Teacher t1 = new Teacher();
        t1.setName("波妞");
        t1.setAge(35);
        t1.setSkill("java,嵌入式");
        System.out.println(t1.getName());
        System.out.println(t1.getAge());
        System.out.println(t1.getSkill());

        Teacher t2 = new Teacher("波仔",15,"java,吹牛逼");
        System.out.println(t2.getName());
        System.out.println(t2.getAge());
        System.out.println(t2.getSkill());
    }
}

我们可以通过set和get方法,为对象添加或获取属性值,但是这种方法对于对象有很多属性的情况会很麻烦,所以我们可以用到构造器给对象的属性赋值,但是我们会发现下面的情况:

 这是因为Teacher子类中并没有name和age属性,而是从父类People中继承的,所以这时候就体现出构造器先调用父类构造器的好处,我们可以在子类的有参构造器中写super(name,age)来调用父类中的有参构造器,来给Teacher对象封装数据。

内存原理:

 7.9 this调用兄弟构造器

  • 任意类的构造器中,是可以通过this()去调用该类的其他构造器的
public class Student {
    private String name;
    private int age;
    private String schoolName;

    public Student() {
    }

    public Student(String name, int age) {
        //this调用兄弟构造器
        this(name,age,"清华大学");
    }

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

    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 getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }
}
public class Test {
    public static void main(String[] args) {
        //目标:this调用兄弟构造器的作用
        //1.创建学生对象封装数据
        Student s1 = new Student("孙悟空" , 500,"菩提洞");
        System.out.println(s1.getName());
        System.out.println(s1.getAge());
        System.out.println(s1.getSchoolName());

        //需求:只要不给学校,学校默认是清华的
        //注意事项:
        //this(...) super(...) 不能同时出现,且必须在构造器的第一行
        //如果同时出现会造成调用两次父类构造器
        //必须在第一行是为了保证先去调用兄弟构造器中的super()去调用父类构造器
        Student s2 = new Student("蜘蛛精" , 900);
        System.out.println(s2.getName());
        System.out.println(s2.getAge());
        System.out.println(s2.getSchoolName());
    }
}

注意事项:
this(...) super(...) 不能同时出现,且必须在构造器的第一行
如果同时出现会造成调用两次父类构造器
必须在第一行是为了保证先去调用兄弟构造器中的super()去调用父类构造器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值