【JAVA SE】—— 类与对象 (万字长文!!)2

简介: 【JAVA SE】—— 类与对象 (万字长文!!)2

类和对象的内存结构

【JAVA SE】—— 类与对象 (万字长文!!)_构造方法_03

person是一个变量,只是这个变量里储存的是地址,所以这个变量也被叫做引用

【JAVA SE】—— 类与对象 (万字长文!!)_封装_04

注意事项👇

  • 使用 . 访问对象的字段.
  • “访问” 既包含读, 也包含写.
  • 对于一个对象的字段如果没有显式设置初始值, 那么会被设置一个默认的初值.

默认值规则

  1. 对于各种数字类型, 默认值为 0.
  2. 对于 boolean 类型, 默认值为 false.
  3. 对于引用类型(String, Array,以及自定制类), 默认值为 null

注意事项:

  1. null 在 Java 中为 “空引用”, 表示不引用任何对象. 类似于 C 语言中的空指针. 如果对 null 进行 . 操作就会引发异常
  2. 引用多次指向对象,引用仅指向最后一个对象
  3. 引用不一定在栈上

类的成员

字段/属性/成员变量

在类内,在方法外部定义的变量我们称为 “字段” 或 “属性” 或 “成员变量”,用于描述一个类中包含哪些数据

代码一:

class Person{
    // 普通成员变量 都是属于对象的
    public String name;// 引用类型(存的字符串首字符地址),默认值为null
    public int age;// 默认值为 0

    // 静态成员变量
    public static  int count;// 默认值为 0

}

public class ClassAndObject {
    public static void main(String[] args) {
        Person person = new Person();
        person.age = 10;// 访问普通成员
        person.count = 1;
        System.out.println(person.age);
        System.out.println(person.count);
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_代码块_05

代码二:(访问普通成员变量)

class Person{
    // 普通成员变量 都是属于对象的
    public String name;// 引用类型(存的字符串首字符地址),默认值为null
    public int age;// 默认值为 0
}

public class ClassAndObject {
    public static void main(String[] args) {
        Person person = new Person();
        person.age++;
        System.out.println(person.age);
        System.out.println("======================");
        Person person2 = new Person();
        person2.age++;
        System.out.println(person2.age);
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_java_06

【JAVA SE】—— 类与对象 (万字长文!!)_java_07

static关键字

**方法区:**保存在着被加载过的每一个类的信息,这些信息由类加载器在加载类的时候,从类的源文件中抽取出来,static变量和方法的信息也保存在方法区中

【JAVA SE】—— 类与对象 (万字长文!!)_构造方法_08

代码3:(访问静态成员变量)

class Person{
    // 普通成员变量 都是属于对象的
    public String name;// 引用类型(存的字符串首字符地址),默认值为null
    public int age;// 默认值为 0

    public static int count;
}

public class ClassAndObject {
    public static void main(String[] args) {
        Person person = new Person();
        person.age++;
        person.count++;//Person.count++;
        System.out.println(person.age);
        System.out.println(person.count);//Person.count;
        System.out.println("======================");
        Person person2 = new Person();
        person2.age++;
        person2.count++;//Person.count++;
        System.out.println(person2.age);
        System.out.println(person2.count);//Person.count;
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_类与对象_09

【JAVA SE】—— 类与对象 (万字长文!!)_封装_10


class Person{
  // 静态成员方法/类方法
    public static  void staticFunc(){
        System.out.println("static::func()");
    }
}

public class ClassAndObject {
    public static void main(String[] args) {
        Person.staticFunc();
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_java_11

注意事项1(能不能在方法中 创建一个 被 static修饰的变量)

class Person{
    // 普通成员办法
    public void eat(){
        static int size = 0;// error
    }

    // 静态成员方法/类方法
    public static  void staticFunc(){
       static int size2 = 0;// error
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_类与对象_12

无论是 普通成员方法还是静态成员方法,都不能在其内部创建一个被static修饰的变量

因为 被static修饰了的变量,该变量就属于类了(类变量/静态变量)。
而你把一个(类变量/静态成员变量)写在方法里,就意味着属于方法(是一个局部变量,不再是 类变量了),而不属于类
所以冲突了,不合适,导致编译器报错。

注意事项 2(能不能在方法中调用方法)

普通成员方法 调用动态成员方法

class Person{
    public String name = "author";

    public void eat(){
        staticFunc();// 可以这样写,没有警告
        System.out.println(name+"正在吃饭.");
    }

    public static  void staticFunc(){
        System.out.println("static::func()");
    }
}

public  class ClassAndObject {
    public static void main(String[] args) {
        Person p = new Person();
        p.eat();
    }
}
//由图得知,普通成员方法 可以调用 动态成员方法 ,因为普通成员方法是依赖对象的,静态成员方法 不依赖对象 所以 调用普通成员方法,new一个对象,对于静态成员方法来说没有任何影响,你用你的引用,我用我的类

【JAVA SE】—— 类与对象 (万字长文!!)_java_13

静态成员方法/类方法 调用 普通成员方法

class Person{
    public String name = "author";

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

    public static  void staticFunc(){
        eat();// error  图17
        System.out.println("static::func()");
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_封装_14

静态成员方法 是无法调用 普通成员方法的

原因也很简单,静态成员方法不需要对象,而普通成员方法需要对象
静态成员通过类名来调用,因此不需要new对象,那么 eat() 谁调?
没有对象。不能调,所以编译器会报错。


引用 一定是在栈上吗?? ? ?

不是

【JAVA SE】—— 类与对象 (万字长文!!)_类与对象_15

注意&总结:

  • 1.普通的方法内部,不能够定义静态的变量
  • 2.静态方法的内部不可以调用普通方法
  • 3.我们曾经说写方法都要统一加上了static,是因为我们之前写的方法只有被static修饰后才能直接调用而不依赖于对象
  • 4.一个对象存储到哪里,和是否被final修饰没有任何关系
  • 5.使用方法的时候,为什么都用static, 因为被static修饰的方法是可以直接调用的,不需要new对象

总结 static 关键字

  1. 修饰属性
    修饰属性,Java静态属性和类相关, 和具体的实例无关. 换句话说, 同一个类的不同实例共用同一个静态属性
  2. 修饰方法
    如果在任何方法上应用 static 关键字,此方法称为静态方法。
    静态方法属于类,而不属于类的对象。
    可以直接调用静态方法,而无需创建类的实例。
    静态方法可以访问静态数据成员,并可以更改静态数据成员的值。

注意事项1: 静态方法和实例无关, 而是和类相关. 因此这导致了两个情况:

  1. 静态方法不能直接使用非静态数据成员或调用非静态方法(非静态数据成员和方法都是和实例相关的).
  2. this和super两个关键字不能在静态上下文中使用(this 是当前实例的引用, super是当前实例父类实例的引用, 也是和当前实例相关).

注意事项2

  1. 我们曾经写的方法为了简单(不用new对象), 都统一加上了 static. 但实际上一个方法具体要不要带 static, 都需要是情形而定.
  2. main 方法为 static 方法.

注意事项3

被final(使其具有常量属性)修饰的成员变量,与成员变量是不是存储在方法区或者堆上是无关的
简单来说: 一个对象存储到哪里 和 你是否被final修饰无关
还是那句话,凡是被 static 修饰的成员变量或者成员方法(又称静态成员变量和静态成员方法),都是存储在方法区中
没有被static修饰的成员变量 或者 方法(又称:普通成员变量 和 普通成员方法)
都需要通过new对象,来实体化对象,通过指向对象的引用来调用我们普通成员变量和方法。

在代码中尽量少使用 static,这样的代码称为祖传代码,容易存在一些不合理的代码,代码不好修改


重写toString

【JAVA SE】—— 类与对象 (万字长文!!)_类与对象_16

程序解流程图

点击 println 》 Ctrl+点击,进入println函数

点击 valueOf 》 Ctrl+点击,进入valueOf函数

点击 toString 》 Ctrl+点击,进入toString函数

【JAVA SE】—— 类与对象 (万字长文!!)_java_17

通过我们层层解析,我发现它最后是通过 toString 来转换

那么我们可不可以 自己写一个 toString 方法
答案是可以的


代码如下:

toString是Object类提供的方法, 我们自己创建的Test类默认继承自Object类,可以重写 toString 方法实
现我们自己版本的转换字符串方法。

class Person{
    public String name;
    public int age;

    public static int count;

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

    public void print(){
        System.out.println("姓名"+name+"年龄"+age);
    }
    public String toString(){// 这里我们写的toString 方法 ,返回一个字符串
        return "author";
    }
}

public  class ClassAndObject {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person);
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_代码块_18

我们在打印数据时,如果我们写了一个toString的方法
编译器,就会执行我们所写的 toString 方法,而不是系统默认的toString

执行我们所写的 toString方法 的 前提是 toString 方法 的名字不能变,是定死了的,缺胳膊少腿 或者 画蛇添足都是不行的


同理:也就是说我们可以不用调用方法,就能输出一样的效果

class Person{
    public String name;
    public int age;

    public static int count;

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

    public void print(){
        System.out.println("姓名:"+name+" 年龄:"+age);
    }
    public String toString(){// 这里我们写的toString 方法 ,返回一个字符串
        return"姓名:"+name+" 年龄:"+age;
    }
}

public  class ClassAndObject {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person);
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_类与对象_19


自动生成Generate方法

toString函数 可以 通过快捷键和鼠标来让编译器自动生成

【JAVA SE】—— 类与对象 (万字长文!!)_类与对象_20

【JAVA SE】—— 类与对象 (万字长文!!)_类与对象_21

【JAVA SE】—— 类与对象 (万字长文!!)_类与对象_22

代码如下:

class Person{
    public String name;
    public int age;

    public static int count;

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

    public void print(){
        System.out.println("姓名:"+name+" 年龄:"+age);
    }

    @Override// 这个类似一个检查功能,检查我们要重写的东西,与原来的一不一样
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public  class ClassAndObject {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person);
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_封装_23

@Override在Java中称为 “注解”,此处的@Override表示下面实现的toString方法是重写了父类的方法


封装

访问限定修饰符

private/ public 这两个关键字称为访问限定修饰符

  • public修饰的成员变量或者成员方法,可以直接被类的调用者使用
  • private修饰的成员变量或者成员方法,不能被类的调用者使用
class Test {
    public int count=1;
    public int age=13;
}
class Main {
    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.age);
        System.out.println(test.count);
    }
}

当我们将变量使用public修饰时,在类外访时需要了解Test类内部的实现,才能够使用这个类,学习成本较高,一旦类的实现者修改了代码(把count改成num),那么类的使用者就需要大规模的修改代码,这使得代码的可维护性较低,维护难度较大,维护成本较高。为了解决这些问题,提高我们代码的可维护性,我们提出了封装这个概念。

封装可以被认为是一个保护罩,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制,我们的getter和setter方法来源就是如此。


getter和setter方法

我们使用private封装属性, 并提供public的getter和setter方法供类的调用者去访问这些属性。

class Test {
    private String name = "张三";
    private int age = 23;

    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;
    }
}
class Main {
    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.getName()+"的年龄是:"+test.getAge());
        test.setName("李四");
        test.setAge(33);
        System.out.println(test.getName()+"的年龄是:"+test.getAge());
    }
}

this表示当前对象引用(注意不是当前对象). 可以借助 this 来访问对象的字段和方法,稍后我们详细介绍


注意&总结:

1.getName即为getter方法,表示获取这个成员的值,setName即为setter方法,表示修改这个成员的值.

2.当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this,就相当于自己给自己赋值,this表示当前对象的引用
3.不是所有的字段都一定要提供 setter / getter 方法,要根据实际情况决定提供哪种方法

IDEA生成getter / setter方法快捷方法:

【JAVA SE】—— 类与对象 (万字长文!!)_代码块_24


构造方法

一个对象的产生要有两个过程:

1.为对象分配内存

2.调用合适的构造方法

什么是构造方法?

构造方法是一种特殊方法, 使用关键字new实例化新对象时会被自动调用, 用于完成初始化操作,就算我们在类中没有实现构造方法,那么编译器也会自动帮我们生成一个无参的构造方法,也就是说:一个类至少有一个构造方法


基本语法

构造类型方法名和类名是相同的,且构造方法比较特殊,没有返回值

语法规则

1.方法名称必须与类名称相同
2.构造方法没有返回值
3.每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造)

new 执行过程

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

上面这两步完成之后,我们的对象才真正产生了。(意味着调用完构造方法之后,我们的对象才真正产生了
注意 合适 这两个字,意味着构造方法不止一个。就好比鞋子很多,但要挑合适的


现在我们来写一个构造方法,代码如下

class Person{
    private String name;
    private  int age;

    public Person(){// 构造方法
        System.out.println("Person()::不带参数的构造方法");
    }
}

public class ClassAndObject {
    public static void main(String[] args) {
        // 前面我们也说了,构造方法 是在实例化对象(new对象)的过程中,会调用合适的构造方法
        // 所以 我们想要调用 构造方法时,只需要new对象就行了。
      Person person = new Person();
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_java_25

很多人可能会问,你有输出,怎么说是不带参数的构造方法?这只是让你理解 在new对象的过程中,会调用构造方法。


现在我们来写一写 多个构造方法,加深对 “合适” 的理解

class Person{
    private String name;

    public Person(){
        System.out.println("不带参数的构造方法");
    }

    public Person(String name){
       this.name = name;// 给Person类中 成员变量传参,类似 Setter 方法
        System.out.println("Person(String)::带一个String类型参数的构造方法");
    }
 /*注意 this 代表当前的对象 这种说法是错误的
 因为 如果你要调用当前对象的前提是产生一个对象(调用完合适的构造方法才能实例化对象)
 而我们现在这个程序,构造方法里就用this了,说明此时的 this 就不能代表当前的对象了
 只能说完成 new的执行过程的第一步: 为对象分配内存,有了内存就有了地址,this此时代表当前对象的引用
 */
}

public class ClassAndObject {
    public static void main(String[] args) {
        // 调用不带参数构造方法
        Person person = new Person();
        System.out.println("=================");
        // 调用 带一个 参数的构造方法
        Person person1 = new Person("author");
    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_java_26

注意

构造方法支持重载. 规则和普通方法的重载一致
构造方法重载规则:类名相同,参数的类型和个数,两者中,至少有一个不同项。
调用构造方法时,编译器会自动筛选调用合适的构造方法

注意: 若类中定义了构造方法,则默认的无参构造将不再生成.

【JAVA SE】—— 类与对象 (万字长文!!)_封装_27


this关键字

  1. this.data 调用当前的对象的 属性/字段/成员变量
  2. this.func() 调用当前的对象的方法
  3. this() 调用当前对象的其他构造方法

第一种我就不说了,前面已经见过它的应用了,我来讲第二种

class Person{
    private String name;

    public void eat(){
        System.out.println(name + "正在吃饭");
    }
    public void print(){
        this.eat();// 调用当前对象的eat方法,这里重复强调一个问题 静态成员方法中不能使用this
        // 写错也没关系,编译器会提示你写错了(红色警告波浪线)
        System.out.println("姓名:" + name);
    }
}

第三种 this() 调用当前对象的构造方法

class Person{
    private String name;

    public Person(){
        this();
        // 注意不能这么去写,因为 this() 表示调用当前对象的构造方法
        // 而现在Person 就是当前的构造方法,两者嵌套使用,会造成死循环
        // Person(){} 调用 this(), this() 调用 Person(){}
        // 而且编译器也出现 红色波浪线警告 图39
        System.out.println("不带参数的构造方法");
   }
}

【JAVA SE】—— 类与对象 (万字长文!!)_java_28


那么 this() 在上述情况下 该怎么使用?

调用 其他 有参数的构造方法

代码如下:

class Person{
    private String name;

    public Person(){
        this("author");
        System.out.println("不带参数的构造方法");
    }
    public Person(String name){
        System.out.println("带有一个参数的构造方法");
    }
}

public class ClassAndObject {
    public static void main(String[] args) {
        // 现在我们来通过new对象,来调用无参数的构造方法
        // 在进入无参数的方法后,执行第一条语句,就是this("author")
        // 意思是 调用带有一个参数的构造方法
        // 调用完成之后,再执行 无参数的构造方法 的 输出语句
        // 也就是说 先打印 "带有一个参数的构造方法" , 后打印 "不带参数的构造方法"
        Person person = new Person();// 图40

    }
}

【JAVA SE】—— 类与对象 (万字长文!!)_封装_29

但是 注意一点 this()在构造方法中 去调用 其他构造方法时,只能放在该构造方法的第一句的位置,this()才能使用( 图41 )。而且 this() 这种方法,只能用于构造方法中。

【JAVA SE】—— 类与对象 (万字长文!!)_构造方法_30


目录
相关文章
|
20天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
18天前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
41 17
|
9天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
13天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
54 4
|
14天前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
32 2
|
19天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
22天前
|
存储 Java 编译器
java wrapper是什么类
【10月更文挑战第16天】
23 3
|
3月前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
59 7
|
26天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
26天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2