详解 Java 的对象与类(二)

简介: 详解 Java 的对象与类(二)

对象与类(二)



1. this 关键字


(1)引入例子


在说 this 之前,我们先来看一下程序清单1,当我们构造一个方法的时候,传参的变量名和成员变量名同名,都是 name,这会造成编译器输出 null ,因为编译器会认为,你把 name 传给了自己,这会带来麻烦。解决办法有两个,一个是程序清单2,另一个是程序清单3. 这两个程序都输出 “Jack”.


程序清单1:


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

c41f01da51724c73842d08a1522adc44.png


程序清单2:


class Person{
    public String name;
    public Person(String newname){
        name = newname;
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person("Jack");
        System.out.println(person.name);
    }
}


程序清单3:


class Person{
    public String name;
    public Person(String name){
        this.name = name;
        //this.name = newname;
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person("Jack");
        System.out.println(person.name);
    }
}


f6bae233348849e29dfcb7043f4e1b23.png


(2)this 的用法


this关键字本身代表的是:当前对象的引用


① this.data:调用当前对象的成员变量

② this.function( ):调用当前对象的成员方法

③ this( ):调用当前对象的其他构造方法


在刚刚的程序清单3中,我们可以看到,使用 this.[ 成员变量 ] 会避免我们出错。

而在程序清单4中,this.function( )的用法和 this.data 是一样的,使用 this 会让整个程序更加安全,不使用也是可以的。


程序清单4:


class Person{
    public String name;
    public void swim(){
        System.out.println(name + " 正在游泳");
    }
    public void run(){
        System.out.println(name + " 正在跑步");
    }
    public void exercise(){
        this.run();
        this.swim();
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "吉姆";
        person.exercise();
    }
}


30b3ae94e19341828a4e07f123ca664d.png


使用 this( ) 调用其他构造方法的规则:


① this( ) 语句只能放在某一个构造函数中的第一行被使用。

② 也就是说:在一个构造函数中,仅可以调用其他任意一个构造函数。注意:有且仅有一个。

③ this( ) 语句不能被用来自己调用自己。


程序清单5:


class Person{
    public String name;
    public int age;
    public Person(){
        this("露丝");
        System.out.println("调用不带参数的构造方法");
    }
    public Person(String name){
        this.name = name;
        System.out.println("调用带有一个参数的构造方法");
    }
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person();
    }
}


e0a4f759f6f544cbbbbd29042e2d0c1b.png


2. 封装



关键字 private


private 修饰的变量和方法只能在当前类中被使用


在程序清单6中,有一行代码会被编译器报错,我已经注释出来了,原因是:在一个类中,name 被 private 修饰,只能在当前类中被使用。


程序清单6:


class Person{
    private String name;
    public int age;
}
public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        person.name  = "露丝"; //error
        person.age = 18; 
    }
}


那么我们怎么才能访问 private 修饰的变量呢?请接着往下看:


在程序清单7中,Person 是类的实现者,我们可以通过 IDEA 编译器生成 Getter and Setter 方法。


而 Test 是类的调用者,可以创建对象调用这两个方法。其中,

【 set 表示设置值,get 表示拿到值。】


程序清单7:


class Person{
    private String name;
    private int 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 class Test {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("露丝");
        System.out.println(person.getName());
        person.setAge(10);
        System.out.println(person.getAge());
    }
}


输出结果:


e6ad1932d11d442bb3592ddd0caaa2c2.png


3. 理解包的含义



包 (package) 是组织类的一种方式,在 IDEA 中,包名应该为小写。


包就相当于文件夹,文件夹里面可以放很多 " .java " 文件


创建多个包,也可以防止某些类发生重名的情况带来的冲突。这就相当于你用 windows 系统在 C 盘创建了一个名为 " ABC " 的文件夹,而你又在这个文件夹中创建了一个名为 “123.txt ”的文件,那么,为了防止重名情况的发生,我们只好创建另一个名为 " XYZ " 的文件夹,在 " XYZ " 文件夹中创建一个“123.txt 文件”,这样就不会被系统提示错误。


(1)包访问权限


我们创建一个包 demo1,然后在这个包中创建两个类,一个类是 Test1,另一个类是 Test2.

可以看到,在同一个包中,可以调用另一个类的成员变量。


7b01f5a6db6b4db484cfa3740a213080.png


程序清单8:


public class Test1 {
  int a = 10;
}


public class Test2 {
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        System.out.println(test1.a); //10
    }
}


(2)import 和 package 的区别


import 只能导入一个包下某个具体的类,不能导入一整个包。

包 package 是一组类的集合。


import java.util.*;


上面代码块中,java.util 表示包,* 表示通配符,导入这个包底下所有的类,而 Java 在处理的时候,什么类被需要,就会拿什么包。


举个例子:


import java.sql.Timestamp;
import javax.sql.DataSource;


上面的是关于 MySQL 数据库的包,那么【 java.sql 】就表示 sql 下的所有包,而 【 Timestamp 】是一个类,【 DataSource 】是一个接口。


所以说,import 归根结底,导入的是:某个包下的某个类 / 接口。


4. 继承



(1)继承的语法


class A:子类 / 派生类
class B:父类 / 超类


class A extends B {
}


继承规则:


① 使用 extends 指定父类

② 在Java 中,一个子类只能继承一个父类 ( 而C++/Python等语言支持多继承 )

③ 子类可以继承父类的所有 public 的成员变量和成员方法

④ 对于父类的 private 的字段和方法,子类中是无法访问的

⑤ 子类的实例中,也包含着父类的实例,可以使用 super 关键字得到父类实例的引用


我们来看一个例子,比如说,我定义一个类 Dog,另一个类 Bird,那么这两个都是动物,都会吃东西,那么我就新创建一个类 Animal,把 Dog 和 Bird 的共性放在类 Animal 中,那么此时我们可以分别创建两个对象 new Dog( ) 和 new Bird( ),以此来访问父类的成员变量和成员方法。详见程序清单9.


程序清单9:


class Animal{
    public String name;
    public void eat(){
        System.out.println("动物正在吃东西");
    }
}
class Bird extends Animal{
    public void fly(){
        System.out.println(name + " 正在飞翔");
    }
}
class Dog extends Animal{
}
public class Test {
    public static void main(String[] args) {
        Dog dog1 = new Dog();
        dog1.name = "哈士奇";
        System.out.println(dog1.name);
        Bird bird1 = new Bird();
        bird1.name = "老鹰";
        bird1.fly();
    }
}


输出结果:


5dd70339bb2d40a383da82082720f2fd.png


看完输出结果,我来说明一下这个程序是怎么跑的。

毋庸置疑,程序一定是从主函数开始的,请看下图:


149272ce5cb540d0866dd7d86ba12ba8.png


大家也可以自己通过 IDEA 编译器试着调试一下,理解在整个过程中,到底代码是怎么跑的。这很关键,因为你会更清晰地知道代码每一步编译出来到底是干什么用的!


(2)子类构造的同时,要先帮助父类进行构造


这里先提一下,super( ) 表示调用父类对象的构造方法,后面会详细提到,这里先记住。


程序清单10:


class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Dog extends Animal{
    //调用父类带有两个参数的构造方法
    public Dog(String name, int age) {
        super(name, age);
    }
}


在程序清单10中,如果类 Animal 使用了带参数的构造方法,而类 Dog 没有使用构造方法,那么就会出现编译错误。


程序清单11:


class Animal {
    public Animal(){
        System.out.println("父类的构造方法");
    }
}
public class Test extends Animal{
    public static void main(String[] args) {
        new Test();
    }
}


输出结果:


e055ba2b164048eb82bdaabf2a71b989.png


在程序清单11中,编译不会出错的原因:当子类没有使用构造方法时,那么编译器就会默认有一个不带参数的构造方法,这在我的上一篇博客中提到过。这个默认构造方法如下:


public Test() {
  super(); //调用父类不带参数的构造方法
}


在程序清单12中,可以让我们更深刻地理解上面的问题


程序清单12:


class Base{
    public Base(String s){
        System.out.print("B");
    }
}
public class Test3 extends Base {
    public Test3(String s) {
        super(s);
        System.out.print("D");
    }
    public static void main(String[] args) {
        new Test3("C");
    }
}


输出结果:


14503351d7f849829dc56f93575b4859.png


代码运行顺序:


b8bd77b5c9b14d5db08c9c9d1d63e4ae.png


程序清单13:


class Animal {
    public String name;
    public int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }
}
class Bird extends Animal{
    String wing;
    public Bird(String name, int age, String wing) {
        super(name, age);
        this.wing = wing;
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog1 = new Dog("阿拉斯加",6);
        System.out.println(dog1.name);
        System.out.println(dog1.age);
        System.out.println("-----------------");
        Bird bird1 = new Bird("杜鹃",3,"黑色的翅膀");
        System.out.println(bird1.name);
        System.out.println(bird1.age);
        System.out.println(bird1.wing);
    }
}


输出结果:


340fd51719a744c8a92ccf8d2c0c54f3.png


我们来看一下这个程序是怎么跑的:


5a5c6e79c5d54d058ed88e2827b7867b.png


底层图理解:


子类 dog1 和 bird1 分别继承父类 Animal 的成员变量 name 和 age,为自己的对象所用,但是 wing 变量属于子类 bird1 自己。


c7317331ce1746e2bf96e6813c7186bd.png


在程序清单13中,类 Dog 和类 Bird 的构造方法,我们通过创建对象,给两个成员变量赋了初值,此时继承了 Animal 类中的成员变量。可以想象,万千世界,有很多动物,它们都有对应的名字,它们都会吃东西…


所以在上面的程序设计过程中,我们省略了定义每个动物类的成员变量和成员方法这一步骤,所以说,继承就是对类共性的提取。


(3)super 关键字


super关键字本身代表的是:父类对象的引用


(1)super.data:调用父类对象的成员变量

(2)super.function( ):调用父类对象的成员方法

(3)super( ):调用父类对象的构造方法


super 和 this 的语法很相似,但是第三种情况,也就是 super 调用父类对象的构造方法用的比较多。


程序清单14:


class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
}
class Dog extends Animal {
    public String name;
    public Dog(String name) {
        super(name);
    }
    public void sleep(){
        System.out.println(name + " 正在跳");
    }
    public void run(){
        System.out.println(super.name + " 正在跑");
    }
}
public class Test1 {
    public static void main(String[] args) {
        Dog dog1 = new Dog("阿拉斯加");
        System.out.println(dog1.name);
        dog1.sleep();
        dog1.run();
    }
}


输出结果:


3a9f79b22cd245af8a4763e7e20f40fd.png


在程序清单14中,当子类 Dog 继承父类 Animal 的时候,会获取父类 Animal 的成员变量 name 和 age。


然而,如果子类 Dog 在自己的类中创建了一个同名变量 name,那么当我们通过 Dog 创建对象的时候并进行赋值,编译器会以子类本身变量为优先,此时,我们就能更深入地理解上面代码中的 name 和 super.name 的区别。


(4)protected 关键字


在程序清单15中,有一行代码会被编译器报错,我已经通过注释表明出来了,造成错误的原因就是:被 private 关键字修饰的变量构成了封装,只能在自己的类中使用,而使用 public 和 protected 就不一样了。


程序清单15:


class Clothes {
    public int size;
    private int number;
    protected String color;
}
class Jacket extends Clothes {
}
public class Test1 {
    public static void main(String[] args) {
        Jacket jacket = new Jacket();
        jacket.size = 175;
        jacket.number = 30; //error
        jacket.color = "蓝色";
    }
}


df7a5312ce704a67b770c8167f01f506.png


在上面表格中,我列出了四个关键字对应修饰变量的范围。


① public 构成的权限在任意地方都可以使用变量
② protected 关键字就是为继承这一机制设定的
③ default 就是默认的包访问权限
④ private 权限最小


(5)final 关键字


在程序清单16中,有两个地方会被编译器报错,我已通过注释标明出来了。


程序清单16:


final class Clothes {
    public int size;
    private int number;
    protected String color;
}
//error
class Jacket extends Clothes {
}
public class Test2 {
    public static void main(String[] args) {
        final int a = 5;
        a = 10; //error
        int b = 6;
        b = 11;
    }
}


总结:


① 被 final 关键字修饰的类,不能被继承

② 被 final 关键字修饰的变量,不能被改变值


b4a5dfb4619d4eceb08c383b38b36797.jpg


Over. 谢谢观看哟~

目录
相关文章
|
11天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
8天前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
35 17
|
4天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
30 4
|
14天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
35 17
|
4天前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
12 2
|
9天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
13天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
13天前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
|
14天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
13天前
|
存储 Java 编译器
java wrapper是什么类
【10月更文挑战第16天】
20 3