04【面向对象、封装、package、static】

一、面向对象

1.1 面向对象概述

1.1.1 什么是对象

在Java中,对象(Object)是指一个具体事物的实例,任何事物都可以使用对象(类)来描述,如猫、狗、计算机、杯子、云、水、空气、叶子、灰尘等看得见的、看不见的、宏观的、微观的、具体的、抽象的都是对象,总之"万物皆对象";

1.1.2 面向对象程序设计

我们前面就提到过,Java是一门面向对象的编程语言,面向对象是一种程序设计思想,与之对应的还有面向过程程序设计;面向对象是把一个对象的特征(属性)和行为单独封装到对象源代码中;这些属性和行为都被集中到一个地方,这样比把方法或者过程与数据分散开来更为方便和安全,含义更加明确;

1.1.3 面向对象和面向过程

举例:开车去上班

  • 面向过程:去车库提车、拿钥匙、打火、踩离合、挂挡、踩油门、刹车、加油、到公司、找车库停车
  • 面向对象:找个司机(对象)、到公司

可以看得出来,面向过程关注的是步骤,将所有步骤连在一起"我就能开车到公司上班",面向对象则关注的是"开车去上班"这个事物整体,完成这件事的步骤全部封装起来,交给指定的对象去做(司机);

面向过程适合简单、不需要协作的事务,重点关注如何执行;面向过程来处理事物时,我们首先思考的是"如何按步骤来实现?"一步一步,最终完成,适合简单任务,不需要过多协作的情况;

但是当我们思考较为复杂的设计任务时,比如将"开车"改为了"造车",就会发现我们列出步骤来一步步执行是不现实的,一个汽车厂不可能将造汽车的全部过程都执行一遍,从一个个螺丝的加工生产到发动机的生产、座椅的生产、汽车玻璃的提炼/生产、甚至车载芯片的研发/生产、发动机的研发、电器设备的研发等,相信国内汽车厂没有一家是这样造汽车的,因为自己要做的事物太多了。

这个时候,面向对象更加符合我们的思维方式。我们首先思考的是"车由什么组成?"开始思考问题,为了协作,我们找到轮胎厂采购轮胎(交给轮胎厂对象来生产轮胎),找到玻璃厂采购玻璃(玻璃厂对象来生产玻璃),找到发动机厂采购发动机等,最终进行产品的组装。而不是按步骤一个个造出来。这就是面向对象思维方式

但是需要注意的是,具体到某个轮胎厂、玻璃厂的一个个流水线操作仍然是有步骤的,仍然离不开面向过程思维;面向对象可以帮助我们从整体上分析整个系统,但是具体到某一个操作上,任然需要面向过程的思路去处理;面向对象和面向过程是相辅相成的,面向对象离不开面向过程;

面向对象与面向过程都是解决问题的一种思维方式,也都是代码的组织方式:

  • 面向过程是一种"执行者思维",主要用于解决一些简单问题的场景
  • 面向对象是一种"设计者思维",主要解决复杂、需要协作的问题场景

1.1.4 面向对象的特点

面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。面向对象的语言中,包含了三大基本特征,即封装、继承和多态。

Tps:关于面向对象和面向过程大家不必太过纠结,我们现在是初学者,理解表面意思即可,随着后面的深入学习,我们会对面向对象有着更深刻的理解;

1.2 类和对象

1.2.1 类和对象的区分

在现实世界中,属于同一类的对象很多,类是抽象的,不是具体的,我们人习惯以对象的方式认识现实世界;

例如我说一辆汽车,那你脑海中立马就能够呈现出一辆汽车的模样吧,不管他是什么品牌、什么颜色、什么价格、什么参数等总而言之,都是一辆汽车,都是属于"汽车类"产品;

tips:类是抽象的,对象是具体的,对象是类的实例化;

类是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征行为特征来描述该类事物。

  • 属性:该事物的状态信息;
  • 行为:该事物的功能信息;

1.2.2 类和对象的举例

  • 类举例:

举例:猫类

属性:名字、体重、年龄、颜色; 行为:走、跑、叫;

  • 实例化对象:

猫对象:

属性:旺财、8kg、6岁、棕色;行为:悄悄的走、飞快的跑、喵喵叫;

1.3 Java类的定义

1.3.1 类的定义格式

public class 类名 {
	//成员变量
    //成员方法
}
  • 定义类:就是定义类的成员,包括成员变量和成员方法。
  • 成员变量:和以前定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外。
  • 成员方法:和以前定义方法几乎是一样的。只不过把static去掉,static的作用在面向对象后面课程中再详细讲解。

类的定义格式举例:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Student {
    // 成员变量(属性)
    String name;        //姓名
    int age;            //年龄

    // 成员方法(行为)
    public void study(){
        System.out.println("学习");
    }

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

1.3.2 类的实例化

前面我们说道过,类是抽象的,不是具体的,类只是负责把事物描述起来,提供模板;对象是类的实例化,是具体的;我们定义好一个Java类后需要通过对象将类进行实例化;

  • 创建对象:
类名 对象名 = new 类名();
  • 使用对象访问类中的成员:
对象名.成员变量;
对象名.成员方法();
  • 练习:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_类的实例化 {

    public static void main(String[] args) {
        //创建对象格式: 类名 对象名 = new 类名();
        Student s = new Student();

        System.out.println("s: " + s);            //s: com.dfbz.demo.Student@1540e19d

        //直接输出成员变量的值
        System.out.println("姓名: " + s.name);      //null
        System.out.println("年龄" + s.age);         //0
        System.out.println("--------");

        //给成员变量赋值
        s.name = "刘德华";
        s.age = 38;

        //再次输出成员变量的值
        System.out.println("姓名: " + s.name);      //刘德华
        System.out.println("年龄: " + s.age);       //18
        System.out.println("---------");

        //调用成员方法
        s.study();          //学习
        s.eat();            //吃饭
    }
}

1.3.3 成员变量的默认值

数据类型 默认值
基本类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) 0
布尔(boolean) false
引用数据类型 数组,对象,String null
  • 定义手机类并使用:
    • 属性:品牌,价格,颜色;
    • 行为:打电话,发短信

1)定义Phone类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Phone {

    //成员变量
    String brand;
    int price;
    String color;

    //成员方法
    //打电话
    public void call(String name){
        System.out.println("给"+name+"打电话");
    }

    //发短信
    public void sendMessage(){
        System.out.println("大家新年好!");
    }
}

2)定义测试类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_类的实例化小案例 {
    public static void main(String[] args) {
        // 创建对象
        Phone p = new Phone();

        System.out.println("品牌: " + p.brand);     // null
        System.out.println("价格: " + p.price);     // 0
        System.out.println("颜色: " + p.color);     // null

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

        p.brand = "华为";
        p.price = 2999;
        p.color = "银白色";

        System.out.println("品牌: " + p.brand);     //华为
        System.out.println("价格: " + p.price);     //2999
        System.out.println("颜色: " + p.color);     //银白色
        System.out.println("------------");

        p.call("刘德华");
        p.sendMessage();
    }
}

1.4 对象内存图

回顾JVM内存,JVM总共分为5大内存区域,寄存器、本地方法栈、方法区、栈内存(虚拟机栈)、堆内存;和我们程序员有关的为方法区、栈内存、堆内存;

  • 方法区:存储类的信息(有多少变量、方法、是什么修饰符修饰的等)、常量信息、静态变量等信息
  • 栈内存(VM栈):方法调用时进栈内存执行,也就是方法运行时消耗的内存;
  • 堆内存:存储类的实例信息,只要是new出来的信息都存在堆内存

new这个类的时候,Jvm去方法区找有没有这个class,没有就加载到方法区,属性方法这些都是在方法区class中的;Jvm加载完后,就根据这个模板在堆中创建对象给属性赋默认值,然后再执行赋值语句给对象赋值;

一个对象内存图,示例代码:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_一个对象内存图 {

    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.brand = "小米";
        phone.price = 8999;
        phone.color = "尊贵金";

        phone.call("小红");
        phone.sendMessage();

        System.out.println("手机的品牌: " + phone.brand);
        System.out.println("手机的价格: " + phone.price);
        System.out.println("手机的颜色: " + phone.color);
    }
}
  • 1)首先将主程序所在的类加载到内存(方法区)
  • 2)开始执行主程序(main方法)
  • 3)执行main方法中的代码
  • 4)任何对象在创建之前都需要将其加载到内存(方法区),方法区存储该类的一些基本信息
  • 5)加载到方法区后,开始在堆内存中开辟内存空间,对象所有成员变量的值都是存储在堆内存中的
  • 6)创建完对象后,开始执行main方法中的其他代码,在调用方法时需要将方法区存储的方法(静态)装载到栈内存中执行,执行方法所消耗的内存都是栈内存,如:方法中的成员变量
  • 7)方法执行完毕后,方法中的所有内存都将被释放,这个过程也叫弹栈
  • 8)继续调用方法
  • 9)继续将方法区存储的方法(静态)装载到栈内存中执行

对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息只保存一份,节约内存空间。

1.5 局部变量和成员变量

变量根据定义位置的不同,我们给变量起了不同的名字,看下列测试类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Person {
    String name;            // 成员变量

    int age;                // 成员变量

    /**
     * 跑步方法
     */
    public void running() {
        int count = 10;           // 局部变量
        System.out.println("跑了" + count + "公里");
    }

    /**
     * 自我介绍方法
     *
     * @param name: 姓名(局部变量)
     * @param age:  年龄(局部变量)
     */
    public void intro(String name, int age) {
        System.out.println("大家好,我叫" + name + ",今年" + age + "岁");
    }
}
  • 成员变量和局部变量的区别:
    • 成员变量:
      • 1)作用域:成员变量定义在类中,在整个类中都可以被访问。
      • 2)存储位置:成员变量分为类成员变量实例成员变量(对象成员变量),实例变量存在于对象所在的堆内存中。
      • 3)使用方法:成员变量有默认初始化值。
      • 4)权限修饰:成员变量的权限修饰符可以根据需要,选择任意一个
    • 局部变量:
      • 1)局部变量只定义在局部范围内,如:方法内,代码块内等。
      • 2)局部变量存在于栈内存中,当方法弹栈(执行完毕)后,局部变量销毁;
      • 3)局部变量没有默认初始化值,使用前必须手动赋值;
      • 4)局部变量声明时不指定权限修饰符;

1.6 值传递和引用传递

1.6.1 思考

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_值传递与引用传递 {
    public static void main(String[] args) {
        Phone p1=new Phone();
        p1.price=2899;

        test(p1);
        System.out.println(p1.price);           // 2899 or 3899?

        int i=20;
        test2(i);

        System.out.println(i);                  // 20 or 50?
    }

    public static void test(Phone p){
        p.price=3899;
    }

    public static void test2(int i){
        i=50;
    }
}

1.6.2 形参和实参

形参也叫形式参数,是一个方法的参数列表中的参数;实参也叫实际参数,是调用者在调用方法时实际传递的参数;

1.6.3 值传递和引用传递概念

  • 值传递(参数类型是基本数据类型):方法调用时,实参把它的值传递给对应的形参,形参只是用实参的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形参值的改变不影响实参的值。
  • 引用传递:(参数类型是引用数据类型参数):也称为传地址。方法调用时,实参是对象(或数组),这时实参与形参指向同一个地址,在方法执行中,对形参的操作实际上就是对实参的操作,这个结果在方法结束后被保留了下来,所以方法执行中形参的改变将会影响实参。

Tips:在Java中,除了基本数据类型之外的数据类型都是引用数据类型,都是通过new在堆内存开辟空间;

值传递与引用传递:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_值传递与引用传递 {
    public static void main(String[] args) {
        Phone p1=new Phone();
        p1.price=2899;

        test(p1);
        System.out.println(p1.price);           // 2899 or 3899?

        int i=20;
        test2(i);

        System.out.println(i);                  // 20 or 50?
    }

    public static void test(Phone p){
        p.price=3899;
    }

    public static void test2(int i){
        i=50;
    }
}

1.6.4 小练习

  • 示例代码:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_值传递和引用传递小练习{
    public static void main(String[] args) {
        String[] arr1 = {"1", "2", "3"};
        String[] arr2 = {"100", "200", "300"};

        test(arr1, arr2);
        System.out.println("-------arr1-------");
        for (int i = 0; i < arr1.length; i++) {
            System.out.print(arr1[i] + ',');                // 1,2,3
        }

        System.out.println();
        System.out.println("-------arr2-------");
        for (int i = 0; i < arr2.length; i++) {             // 100,200,300
            System.out.print(arr2[i] + ',');
        }
    }

        // 0x11  0x22
    public static void test(String[] arr1, String[] arr2) {
        String[] temp;

        // 0x11
        temp = arr1;

        // 0x22
        arr1 = arr2;

        // 0x11
        arr2 = temp;
    }
}

1.7 匿名对象

顾名思义,匿名就是没有名字的对象,在创建对象时,只通过new的动作在堆内存开辟空间,却没有把堆内存空间的地址值赋值给栈内存的某个变量用以存储;

使用场景:

  • 1)如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
  • 2)我们经常将匿名对象作为实参传递给一个方法调用。
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo06_匿名对象 {
    public static void main(String[] args) {

        Student student=new Student();
        intro(student);
        student.study();

        // 使用匿名对象传递
        intro(new Student());

        // 使用匿名对象调用方法
        new Student().study();
    }

    public static void intro(Student student) {
        System.out.println("姓名: "+student.name+",年龄: "+student.age);
    }
}

二、封装

2.1 封装概述

封装是面向对象的三大特征之一,面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性

  • 举例:

就拿一辆汽车来说,外部有一个壳,将内部的原件封装起来,至于汽车内部的细节是什么,我们不需要关心,对于用户来说,我们只需要会操作这辆汽车就可以,汽车对外提供方向盘、离合器、油门、以及一些娱乐设备,这样就将内部的东西不在直接暴露给外部,增加了安全性;

我们来举例一段代码,看看没有封装的对象会存在什么问题:

准备一个Person类,具备姓名、年龄等属性

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
class Person {
    // 学生姓名
    String name;

    // 学生年龄
    int age;
}

编写测试类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_没有封装的情况 {

    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "小灰";
        p1.age = 2800;

        intro(p1);

        Person p2 = new Person();
        p2.name = "小蓝";
        p2.age = -280;

        intro(p2);
    }

    public static void intro(Person person) {
        System.out.println("大家好,我叫" + person.name + ",今年" + person.age + "岁");
    }
}

上述代码是可以正常运行的(编译不会报错),但是对于我们程序逻辑来说是错误的;

Student类未进行封装,其所有属性直接暴露给外部,外部程序可以任意的对Student的属性就行修改,这是非常有安全隐患的,我们的正常年龄可以设置一个正常区间如0~120岁,不可能到2800多岁,也不可能是负的岁数;因此我们要对属性加以管控,而不是直接暴露给外端;

2.2 private 关键字

  1. private 是一个权限修饰符,代表最小权限。
  2. 可以修饰成员变量成员方法
  3. 被private修饰后的成员变量和成员方法,只在本类中才能访问。

封装的原则:将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问

1)使用private 修饰成员变量,代码如下:

public class Student {

    // 学生姓名
    private String name;

    // 学生年龄
    private int age;
}

2)提供 getXxx 方法 / setXxx 方法,可以访问成员变量,代码如下

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Student {

    // 私有成员变量,外界不能(直接)访问这个成员了
    private String name;
    private int age;

    // 外界可以通过这个方法来设置name属性
    public void setName(String stuName) {
        name = stuName;
    }

    // 外界可以通过这个方法来设置age属性
    public void setAge(int stuAge) {

        if (stuAge > 0 && stuAge < 120) {
            age = stuAge;
            System.out.println("赋值成功,您的年龄为: " + stuAge);
        } else {
            System.out.println("您输入的年龄不合法," + stuAge);
        }
    }

    // 外界可以通过这个方法来访问name
    public String getName() {
        return name;
    }

    // 外界可以通过这个方法来访问age
    public int getAge() {
        return age;
    }

    public void show() {
        System.out.println("大家好,我叫【" + this.name + "】,今年【" + this.age + "】岁");
    }
}

3)测试类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {

    public static void main(String[] args) {
        Student s1 = new Student();
        s1.setName("小灰");
        s1.setAge(2888);
        s1.setAge(-280);
        s1.setAge(20);

    }

    public static void intro(Student student) {
        System.out.println("大家好,我叫" + student.getName() + ",今年" + student.getAge() + "岁");
    }
}

经过封装后,属性再也不是直接暴露给外部了,我们可以在外部操作属性之前加以控制;

2.3 this 关键字

2.3.1 this的使用

this是Java中的一个关键字,代表所在类的当前对象的引用(地址值),即对象自己的引用;

  • 定义一个Cat类:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Cat {
    public void show() {
        System.out.println("我的内存地址值是: " + this);
    }
}
  • 测试类:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_this关键字的介绍 {
    public static void main(String[] args) {
        Cat c1 = new Cat();
        System.out.println(c1);              // Cat@1540e19d

        // Cat@1540e19d
        c1.show();
        System.out.println("----------");


        Cat c2 = new Cat();
        System.out.println(c2);              // Cat@677327b6

        // Cat@677327b6
        c2.show();
    }
}

2.3.2 this的内存图分析

  • 定义一个Worker类:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Worker {

    // 私有化
    private String name;
    private int age;

    // 对外提供的修改方法
    public void setName(String name) {      // 就近原则

        // this代表当前对象的内存地址值
        this.name = name;
    }

    public void setAge(int age) {

        // this代表当前对象的内存地址值
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void show() {
        System.out.println("大家好,我叫【" + name + "】,今年【" + age + "】岁");
    }
}
  • 测试代码:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_this关键字的内存图 {

    public static void main(String[] args) {
        Worker w1=new Worker();
        w1.setName("小红");
        w1.setAge(20);
        w1.show();

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

        Worker w2=new Worker();
        w2.setName("小龙");
        w2.setAge(30);
        w2.show();
    }
}

this内存图:

Tips :方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。

2.4 构造方法

构造方法也叫构造器,顾名思义就是用来构造类的;当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。

Tips:无论你与否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法,一旦自己定义了构造方法,Java自动提供的默认无参数构造方法就会失效。

  • 构造方法的定义格式
修饰符 构造方法名(参数列表){
    // 方法体
}

构造方法的写法上,方法名与它所在的类名相同。它没有返回值,所以不需要返回值类型,甚至不需要void。使用构造方法后,代码如下:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Phone {
    private String name;
    private int price;

    // 无参构造方法
    public Phone() {

    }

    // 有参构造方法
    public Phone(String name, int price) {
        this.name = name;
        this.price = price;
    }
}

构造方法注意事项:

  1. 如果你不提供构造方法,系统会给出无参数构造方法。
  2. 如果你提供了构造方法,系统将不再提供无参数构造方法。
  3. 构造方法是可以重载的,既可以定义参数,也可以不定义参数。

2.5 标准JavaBean

JavaBean 是 Java语言编写类的一种标准规范。符合 JavaBean 的类,要求类必须是和公共的,并且具有无参数的构造方法,提供用来操作成员变量的 setget 方法,采用private修饰成员变量。

  • 格式如下:
public class ClassName{
    //成员变量
    //构造方法
    //无参构造方法【必须】
    //有参构造方法【建议】
    //成员方法  
    //getXxx()
    //setXxx()
}

编写符合 JavaBean 规范的类,以学生类为例,标准代码如下:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Phone {
    //成员变量
    private String brand;
    private int price;

    //构造方法
    public Phone() {

    }

    public Phone(String brand, int price) {
        this.brand = brand;
        this.price = price;
    }
    //成员方法  

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

三、package包

3.1 包的概述

Java允许使用包(package)将类组织再一个集合中。包类似于我们计算机中的文件夹,方便我们对Java类型进行管理。借助包可以方便地组织自己的代码;

package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:

package 顶层包名.子包名;

查看我们的代码目录:

所有的类都在一个目录下(根目录),随着项目的类越来越,我们的项目会变得难以维护;包(package)类似于我们的文件夹,当文件多了,我们可以创建多个文件夹对文件进行归类,包则是对Java源代码进行归类;

3.2 创建包

在src目录上右击--->New--->Package:

包的名称一般为公司倒写域名,例如com.alibaba、com.baidu等;.代表分割,com.dfbz实际创建了一个二级目录;

也可以通过创建类的方式来创建包:

输入com.dfbz.Demo02:在com/dfbz包下创建了一个Demo02类:

输入com.abc.Demo01:在com/abc包下创建了一个Demo01类:

最终目录结构:

3.3 声明包

在任何类的第一句代码都必须声明这个类在哪个包下的,我们之前的类都创建在根目录(src目录),因此不需要声明所在的包,除了根目录下的类都需要在类的第一行声明包;

1)包对应于文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次;

2)包通常用小写单词,类名首字母通常大写;

3.4 import导入包

为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类

如果想要导入这个包下的所有类,使用*

import com.dfbz.*;

3.5 不同包的同名类

不同的包下是可以存在同名类的,就跟计算机的不同文件夹下可以存在同名的文件一样,当一个类需要用到这个同名类时我们需要精确导入;

在不同包下准备两个同名类:

Tips:访问不同包下类的成员时,必须保证该属性被public修饰,否则不能访问(关于权限修饰符我们后面再详细了解)

  • Car:
package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Car {
    public String name;
    public String brand;
}
  • Car:
package com.dfbz.demo02;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Car {
    public String name;
    public String color;
}
  • 测试类:
package com.dfbz.demo03;

import com.dfbz.demo01.Car;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {

        Car car1 = new Car();
        car1.name = "比亚迪-汉";
        car1.brand = "比亚迪";

        // 已经导入过了一个Car类,接下来需要精确导入
        com.dfbz.demo02.Car car2 = new com.dfbz.demo02.Car();
        car2.name = "红旗-HS7";
        car2.color = "黑色";
    }
}

包结构如下:

四、static关键字

4.1 static概述

static 关键字它可以用来修饰的成员变量成员方法被修饰的成员是属于类的,而不是单单是属于某个对象的。被static修饰的成员由该类的所有实例(对象)共享;

4.2 定义和使用格式

4.2.1 类变量

static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作,因为该变量属于类,而不是某个对象。

  • 类变量:使用 static关键字修饰的成员变量。

定义格式:

static 数据类型 变量名;

举例:

static String className;

类变量是属于"类的变量",不属于某个对象,类变量被所有对象共享,类变量可以通过类名访问,也可以通过对象名来访问。

示例,定义一个Person类,创建一些类变量:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Person {
    public static String className = "Person";
    public static String classInfo= "我是一个Person类型,包名是com.dfbz.demo01";
}

运行测试类:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_访问静态成员变量 {
    public static void main(String[] args) {

        Person p1=new Person();
        Person p2=new Person();

        // 通过对象名可以访问类变量
        System.out.println(p1.className);           // Person
        System.out.println(p2.className);           // Person

        // 通过类名也可以访问类变量(推荐方式)
        System.out.println(Person.className);       // Person
    }
}

4.2.2 静态方法

static 修饰成员方法时,该方法称为类方法。静态方法在声明中有 static ,建议使用类名来调用,而不需要创建类的对象。调用方式非常简单。

  • 类方法:使用 static关键字修饰的成员方法,习惯称为静态方法

定义格式:

修饰符 static 返回值类型 方法名 (参数列表) { 
	// 执行语句
}

举例:在Person类中定义静态方法

public static void showInfo(){
    System.out.println("我是一个Person类");
    System.out.println("我所在的包是com.dfbz.demo01");
}

测试代码:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_访问静态成员方法 {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person();

        // 可以通过对象名来调用方法
        p1.showInfo();
        p2.showInfo();

        // 也可以通过类名来调用方法
        Person.showInfo();
    }
}

4.2.3 静态方法的特点

静态方法调用的注意事项:

  • 静态方法可以直接访问类变量(被static修饰的变量)和静态方法。(静态方法能够访问静态资源)
  • 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。
  • 静态方法中,不能使用this关键字。

Tips:静态方法只能访问静态成员。

示例代码:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_静态成员方法的注意事项 {
    public static void main(String[] args) {
        Demo03Example.method();
    }
}

class Demo03Example {
    public static int a = 10;
    public int b = 20;

    public static void method() {
        System.out.println(a);

//        System.out.println(b);          // 访问出错,静态方法中不能访问非静态的成员

//        System.out.println(this.b);       // 访问出错,静态方法中不能访问this
    }
}

4.3 静态原理图解

static 修饰的内容:

  • 1)是随着类的加载而加载的,且只加载一次。
  • 2)存储于一块固定的内存区域(方法区),可以直接被类名调用。
  • 3)它优先于对象存在,所以,可以被所有对象共享。

定义一个Student类:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Student {
    public String name;
    public int age;

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

    public static String className = "Student";

    public void say() {
        System.out.println("大家好");
    }

    public static void showClass() {
        System.out.println("我是一个Student类,我拥有3个变量和两个方法");
    }
}

测试类:

package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_静态成员的内存图 {
    public static void main(String[] args) {
        Student s1=new Student("小灰",20);
        Student s2=new Student("小蓝",30);
    }
}

内存图:

4.4 代码块

4.4.1 构造代码块

  • 代码块:每当对象创建时,对象的代码块将会被执行(优先于构造方法执行)

格式:

public class ClassName {
    {
        //执行语句
    }
}

作用:给对象进行初始化赋值。用法演示,代码如下:

public class Person {
    private int age;
    private String name;
    {
        // 默认每个对象的age都为18
        this.age = 18;
        // 执行语句
    }
}

4.4.2 静态代码块

  • 静态代码块:定义在成员位置,使用static修饰的代码块{ }。
    • 位置:类中方法外。
    • 执行:随着类的加载执行,而执行且执行一次。

格式:

public class ClassName {
    static {
        //执行语句
    }
}

作用:给类变量进行初始化赋值。用法演示,代码如下:

package com.dfbz.demo01;

public class Computer {
    private String name;
    private double price;

    // 类一加载,静态代码块中的代码就会被执行,而且是执行一次
    static{
        System.out.println("加载网卡驱动....");
        System.out.println("加载声卡驱动....");
        System.out.println("加载第三方软件插件....");
    }
}

Tips:static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况下,去调用方法。下面将介绍两个工具类,来体现static 方法的便利。

静态代码块在类加载的时候执行;

4.4.3 代码块小练习

  • 示例代码:
package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_静态代码块 {
    public static void main(String[] args) {
        new A();      			// 1,3,2
        new A();          		// 3,2
    }
}

class A {
    static {
        System.out.println("1");
    }
    public A() {
        System.out.println("2");
    }
    {
        System.out.println(3);
    }
}
posted @ 2023-02-09 13:43  绿水长流*z  阅读(145)  评论(0)    收藏  举报