上帝视角学JAVA- 基础03-类01【2021-08-01】

1、类与对象

1.1 从基本数据类型到复杂的通用数据类型

语言是通过数据来描述整个世界的。

在最初的基本数据类型 int、char、byte、long、float、double、boolean

为什么会有类?

因为基本的数据类型不足以描述社会上的物品,事物。

一个人,会有身高、体重、性别等等属性。

身高可以用int 型描述、性别可以用float 型描述(以 0 为男性,1为女性)

但是人怎么用int型描述呢? 你可以说,也行啊。0 表示 人,1 表示非人类。

比如 int a; 这个a = 1,我们就说 a 是男人,这是没问题的。

那么你怎么来描述 a这个男人的 身高,体重,性别呢?

很显然基本数据类型不能够满足要求。需要更复杂的数据类型。而且万物属性不一,我们需要的是一种通用的数据类型,能够自定义。

由于这个需求,JAVA 创建了class 这个数据类型。没有什么神秘的。也是数据类型的一种。

当我们使用 基本数据类型时,如 int a; 这是申明 int 型的a变量。

对应自定义的数据类型,就需要先定义这个类型再声明使用。为什么要先定义?你不告诉计算机,人家怎么知道你想要的是什么?

如定义 Class A{}; 然后使用 A a;

前面是定义,就是告诉jvm,我自定义了一个叫A的数据类型。

后面 A a;就是声明了一个 A 类型的 a变量。比较一下 int a; 使用上没什么区别麻。

但是,A是我们自己定义的,可以自我修改,能干的事就多了。而int 是 jvm 定义的,我们不能修改int的定义,只能按照jvm规定使用。

// 基本数据类型 int 的声明+初始化
int a = 1;
// 自定义的高级数据类型 A 的声明初始化。高级的都是new 出来,凡是new 出来 都是放在 堆区。 
A a = new A();

1.2 类

类 就是通用数据类型的名字,使用 关键字 class 告诉 jvm 这是通用数据类型。

我们来定义一个 动物的数据类型。可以是特别抽象的动物类,也可以是 比较抽象的 猫类、狗类。

public class Animal {
    // 年龄
    public int age;
    // 性别
    public String sex;
    // 体重
    public int weight;
​
    // 吃饭
    public void eat(){
        System.out.println("吃饭");
    }
    // 叫唤
    public void speak(){
        System.out.println("叫唤");
    }
}

上面是我定义的一个 Animal 类,其实就是自定义的数据类型。class 就是告诉jvm 我要定义一个叫做Animal的类型数据了。

Animal 才是真正的数据类型名字。再深究一下,class 其实也是一种数据类型, 他的变量名叫做 Animal。

在定义Animal的时候,内部还有 数据类型看到没? int型的age、String 类型的sex

我们把age、sex、weight 叫做 Animal 的属性。

而eat()、speak() 是Animal 其实也是属性,但是叫属性不合适,因为它不是简单的一个数据类型,而是一个函数。

我们叫做Animal类的方法。

当我们使用 Animal 这个数据类型时,就可以描述动物这个事物了。基本数据类型描述复杂事物乏力的情况就被类给解决了。

使用以下!

// 先声明,再初始化
Animal animal = new Animal();
​
animal.age = 1;
animal.weight = 20;
animal.sex = "公";
animal.speak();
animal.eat();
// 基本数据类型 先声明,再初始化
int a;
a = 10;

以上是类的由来,以及最基本使用。后面的特性,都是根据现实社会需求而来。

1.3 类的属性(成员变量)与方法(成员方法)

属性 = 成员变量 = field = 域 = 字段

方法 = 成员方法 = 函数 = method

以上是对类 的描述的不同叫法,其实描述的是一回事。

以Animal 类为例, age是属性、也叫 成员变量、也叫field 、域、字段

eat() 是Animal 的方法 也是成员方法、函数、method 等

就和 我、余、在下、鄙人、郑 一样,同一个意思,不同的称呼。

我们在自定义Animal这个类时,需要它有什么属性,就给它加上就是了

需要描述它能干什么,就给它加上 方法就是了,怎么设计完全看实际需要,或者你的心情。

1.4 对象

Animal animal = new Animal();
int a;

比较上面2行代码,以熟悉的 int a为例,a是什么? a是变量,准确的来说是int型的变量a。

同理, animal是什么? 它是Animal 型的变量animal。【JAVA是严格区分大小写的,不要搞混了】

你肯定还见过下面的

int b;
int c;
int xxx;

b,c,xxx 是同一个东西吗?不是,他们只是同一种类型,int整型的变量。int 这玩意就是一种数据类型。或者说是一种模板。根据这个模板,创建了 b,c,xxx 这3个同类型的不同变量。

同理,

Animal cat = new Animal();
Animal dog = new Animal();
Animal yyy = new Animal();

上面这3个 cat、dog、yyy 也不是同一种东西,但是都是同一种类型Animal,这种类型是我们自定义出来的,使用class关键字自定义出来的。叫做 Animal类,对比的是int型。

其实,你要是说Animal型也没啥问题。本质如此,但是为了区分基本数据类型,再加上class 本来就是翻译为 类。Animal是class定义出来的,所以 我们一般叫Animal 类。

b、c、xxx实质是 int型的变量,cat、dog、yyy是Animal类的变量。

属于某一类、或者某一型的变量 我们取个名字,叫做这个类的对象。或者这种型的对象。

对于基本数据类型,我们一般不把 b、c、xxx这些叫做对象,因为基本数据类型简单。而为了区分类创建出来的 cat、dog、yyy

,我们把这3叫做 类Animal的对象。

现在是不是有点明白,为什么有的人把 类叫做模板。其实只要是数据类型都是模板,什么int、char、short 等,只要是数据类型就可以定义这种类型的变量。

总结:

  • 基本型与class型没啥很大区别。一个功能更强大,可以自定义,另一个是JVM定好了的,最基本的类型。

  • 他们的用法基本相似。为了稍有区分,取了不同的名字。

  • class 是自定义数据类型,功能很强大,很多特性 是基本数据类型没有的。而基本数据类型能用的,类基本都能用。

1.4.1 匿名对象

Animal cat = new Animal();
new Animal();

第一行是一般的对象创建,cat就是对象名称。

第二行就是匿名对象的写法。没错,只是new出来,但是没有赋值给变量。即没有给new出来的对象取名字。

这就是匿名对象。

名字都没有,有啥用呢?

直接这样写当然没有用。匿名对象的出现也是有应用场景的。当我们需要一个对象,但是这个对象只用一次,为了节约资源,就可以使用匿名对象了。比如将匿名对象作为参数传递给调用的方法,而且匿名对象基本都是这样用的。

System.out.println(new Animal(18,"母", 100));

上面这行代码,就是使用了匿名对象,直接作为参数传递给了println 方法。

Animal a = new Animal(18, "母", 100);
System.out.println(a);

对比一下 普通的对象写法。先new出对象给变量a,再传递a给println 方法。

结果是是一样,但是是有区别的。

  • 匿名对象不需要变量接收,节省了一个变量。

  • 匿名对象使用一次就会被销毁,因为没有名字嘛,后面就找不到它了。

1.5 内存解析

主要关注运行时数据区,见名知意,就是 jvm运行时候,数据存放的区域。

JAVA设计者,按照不同功能,切割了几大块,按照逻辑功能分成了 方法区、虚拟机栈、本地方法栈、堆、程序计数器。

至于为什么要这么划分,当然是有原因的。可以简单讨论一下。 类里面有成员方法,肯定要有方法区的。我们说过,new 出来的东西都是放在堆空间,类的初始化就是new出来的,所以肯定要有堆区。程序执行到哪里了,尤其是嵌套执行,肯定要有个东西记录下来的。那就是程序计数器。至于虚拟机栈、本地方法栈 的出现,可以自己百度一下。必定是有需求,才会出现的。

  • 堆 存放我们new 出来的 类的对象信息。

  • 栈,指的是虚拟机栈,存储局部变量等存放了编译器可知长度的7种基本数据类型,对象应用(即对象的首地址,回忆一下数组名)特点是方法执行完,自动释放

  • 方法区: 存储已被虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据。

这里提到了 常量、静态变量以及即时编译。这些内容,后续分解。

1.6 类中的属性探讨

public class Animal {
    // 年龄
    public int age;
    // 性别
    public String sex;
    // 体重
    public int weight;

}

以Animal 为例,这里我去掉了成员方法。这样研究属性更清晰一点。

1.6.1 成员变量与局部变量

区分:在类中的位置不同。

类的成员变量,你看上面,直接定义在了类Animal中,没有中间商,这种变量就是属于类的成员变量。

局部变量:以前讲局部变量,就是指方法内的变量,方法的形参。看到没,都和方法有关。

看下面的例子:

public class Animal {
    // 年龄
    public int age;
    // 性别
    public String sex;
    // 体重
    public int weight;
    // 吃饭
    public void eat(String food){
        System.out.println(food);
    }
    // 叫唤
    public void speak(){
        System.out.println("叫唤");
    }
    public Animal() {
    }
    public Animal(int age, String sex, int weight){
        int min = 0;
        int max = 120;
        if (age > min && age <=max){
            this.age = age;
        }else {
            this.age =min;
        }
        this.sex = sex;
        this.weight = weight;
    }
}

说明一下:这里有2个和类名相同名字的方法Animal,他们的参数不同,这是方法的重载。后面会说。

然后也没有返回值。这种方法叫做类的构造方法。有什么用呢?在构造方法里面再谈。现在看有参数的Animal方法

public Animal(int age, String sex, int weight){
        int min = 0;
        int max = 120;
        if (age > min && age <=max){
            this.age = age;
        }else {
            this.age =min;
        }
        this.sex = sex;
        this.weight = weight;
    }

这里面 的方法形式参数,以及方法里面定义的min、max变量都是局部变量。

再看看成员方法eat

// 吃饭
    public void eat(String food){
        System.out.println(food);
    }

这里的food属于方法eat 的形参。也是局部变量。

结论:

  • 类中的局部变量都是定义在方法里面的,不管是什么构造函数,还是什么成员方法,又或者其他方法。只要是定义在方法里面的就是局部变量。(后面又新知识再补充)

  • 虽然方法里的变量也都包含在类Animal里面,但是不是直接在类里面,有个中间商方法啊。

  • 实质的原因是 作用域范围。有效范围是看离这个变量最近的大括号。java是用大括号来限定范围的。脱离了大括号,这个变量也就失效了。

1.6.2 权限

权限这个玩意的含义是你有没有资格做什么事,在java中控制某些资源你能否访问。

根据实际需要,JAVA定义了4中权限,以应对需求。

分别是 public、缺省、protected、private

翻译就是

公共权限:大家都可以访问

缺省:缺省是指形式上不写修饰符,不写修饰符就是默认权限。比公共的权限要低一些。

protected:受保护的,看名字就知道这个权限更严格。

private:私有的,这是最严格的一种权限。

权限并不只是在成员变量有,而是类、方法、属性都有。因为我们需要精细控制。

但是局部变量是没有权限修饰符的。因为这个局部变量只在这个方法里面有效。我们控制能不能访问方法就可以了。

详细的权限介绍,在后续章节。

1.6.3 成员变量(属性)的初始化值

对于7种基本数据类型,初始化值和 Java基础02 里面讲的一样。

对于引用数据类型,初始化值就是null。

类也是一种引用数据类型。

引用数据类型指的是什么?首先肯定不是基本数据类型这7种。

它的含义是指 栈中的变量名存储的是堆中内存空间的首地址。即存的是地址的引用,而不是真正的值。真正的值需要通过这个地址去堆空间找。初始化为null的含义就是没有在堆空间给这个类型分配内存。

1.6.4 成员变量修饰符

其实 权限也是一种修饰符。

public static final int weight = 10;

以上面为例子,成员变量可以在类型前面最多加上3个修饰符,也可以不加。

分别是权限修饰符:这里写的是 public

静态修饰符:static

常量修饰符:finnal

具体修饰符怎么用,后面会统一讲解。

1.7 成员方法探讨

public class Animal {
    // 年龄
    public int age;
    // 吃饭
    public void eat(String food){
        System.out.println(age);
        System.out.println(food);
    }
    // 叫唤
    public void speak(){
        System.out.println("叫唤");
    }
}

上面的代码定义了1个成员变量、2个成员方法,其实和函数没啥大区别。

成员方法 eat里面使用了成员属性age。

age这个变量定义在Animal里面,eat方法外面。作用域范围在Animal里面,所以Animal里面都可以调用它。

1.7.1 成员方法修饰符

 private static final void speak(){
        System.out.println("叫唤");
    }

成员方法同样可以加上3个修饰符,也可以不加。还可以加上 abstract 修饰符。后面再讲

而第4个 void 不是修饰符,而是反函数返回值。成员方法必须写上函数返回值。这是的void,即没有返回值。

1.7.2 方法的重载

方法的重载你见过。java基础里面讲 Arrays的使用时,就有。前面的构造函数也提到了方法的重载。

出现的原因:

  • 还是懒。不重载可以吗?可以,定义方法的时候写不同名字就好了。

  • 但是不全是懒。使用重载会更方便记忆和使用。

定义: 同名的方法,但是参数不同,更加不同的参数可以唯一确定具体需要调用的函数。

参数的不同:

  1. 参数个数不同

  2. 参数类型不同

  3. 参数个数相同时,参数的类型不完全相同

再看一下例子:

Arrays.sort() 有很多个重载的 方法。参数个数有1个的,也有2个的,也有3个的。

前8个方法满足了 参数类型不同。

参数类型在个数相同时,是不能完全相同的。如上面第11、12、13、14方法,都是3个参数,但是第一个参数的类型是不同的,后面2个相同。满足 个数相同,类型不完全相同。

有这样的规定,就是为了 通过区别,来唯一确定需要调用哪个方法。如果不能确定,编译器就找不到北了。。。

再看,通过重载,我们只需要记住一个名称,sort(),就支持很多种功能。这无疑极大的简化了我们记忆的难度。有点规范性是吧。

必须注意一点:返回值不是重载的条件。函数返回值不同,不能唯一确定一个函数。

1.7.3 可变个数的形参

JAVA 5.0之前 采用数组形参来定义方法,传入多个同一类型的变量

public static void test(int a, String[] books);

在 5.0 以后采用可变个数形参来定义方法,传入多个同一类型变量

public static void test(int a, String...books);

形式上 数组的中括号变成了...

传递的时候,不需要传一个数组,可以直接写数组中的元素。但是,形参任然是一个数组。

public static void main(String[] args) {
        show("12","23423");
}
public static void show(String... strs){
    // 测试 strs 是不是字符串数组
    System.out.println(strs instanceof String[]);
    for (int i = 0; i < strs.length; i++) {
         System.out.println(strs[i]);
    }
}
// 输出结果
// true
// 12
// 23423
  • 注意 5.0之前 与5.0之后的写法其实是一会事,同一个函数使用可变参数类型时,只能选一种写法,不然就是函数重复定义错误。

public static void test(int a, String[] books);
public static void test(int a, String...books);

即上面这2行代码 都叫test,不能都存在,不构成重载,因为都是一回事。

  • 当固定参数与可变参数同时存在时,新的写法 可变参数只能写在最后面。旧写法没有顺序要求。原因也很简单,你不写在最后面,编译器不知道那些参数是属于可变参数的。

// 正确写法
public static void test(int a, String...books);
// 错误写法
public static void test( String...books,int a);

1.7.4 方法参数的值传递机制

实参:方法调用时传递的参数

形参:方法接收时的参数,即定义时,小括号的参数。

public static void main(String[] args) {
        int a = 5;
    	// 这个a 是传递给test方法,是实参
        test(a);
    }
	// 这个a 是test方法定义时,写在小括号的参数,是形参
    public static void test(int a){
        a = 10;
    }

主要看参数的类型,如果是基本数据类型,传递的是值本身而不是这个变量。

public static void main(String[] args) {
        int a = 5;
        test(a);
        System.out.println(a);
    }
    public static void test(int a){
        a = 10;
    }
//输出
// 5

上面这个例子里面,定义了int型变量 a = 5,并且a传递给了方法test,test方法里面对a进行了重新赋值a=10 但是输出确是 5

这说明传递给 test 函数的是a变量的值 5,而不是a变量本身。或者说a变量的地址。

看下面的变量与内存的关系。

变量名 与内存地址是对应关系,记住内存地址对我们来说太难了,所以给内存地址取了给别名,就是变量名。实质java是通过变量名找内存地址,通过内存地址找到区域,取出值。

而内存地址代表了一个区域,这个区域存的东西叫做值。

对于上面这个操作来说,只是将 5 这个值传递给了 test函数,test函数使用局部变量a进行接收,这个局部变量a与外面的a不是同一个a,你也可以将test的参数a改成b,这样就不会弄错。然后修改的是b变量的值为10,并没有改变a变量的值,因此输出还是5

再来看 引用数据模型。

以animal变量为例,存的值是另一个堆内存中保存了animal对象信息的内存空间的地址。

知道了存储模型,现在就可以知道赋值操作的本质了。

基本数据类型,赋值操作是将值替换。
引用数据类型,赋值操作也是将值替换,只不过这个值是地址值,而不是真正存储的数据。真正存储的数据在另一块内存空间里。

再来看引用数据类型的参数传递:

public static void main(String[] args) {

        Animal animal = new Animal();
        animal.age = 20;
        // 实参 animal
        test(animal);
        System.out.println(animal.age);
    }
	
   // 形参 ani
    public static void test(Animal ani){
        ani.age = 10;
    }
// 输出 10

实参animal的值是地址,指向的是 真正存储内容的空间。

形参接收时 会执行赋值操作 Animal ani = animal;

ani的值就变成了 animal 的地址。

对于变量值如果存的是地址,如果需要地址里面存的真正内容,java会再通过这个地址去获取值。

你修改里面的age,这个age是需要通过地址找到,修改的还是原来存储内容的空间里面的值。

换句话说,在test运行期间,animal 与ani 存储的都是同一片空间的地址。等价于1个人叫了2个名字。

修改了ani里面的内容就等价于修改animal。

比如 你大名 张军,小名狗蛋, 我打了狗蛋1巴掌,难道张军不疼吗?

你以为结束了吗?不,还有!

看看String类型。

public static void main(String[] args) {
        String a = "hello";
        test(a);
        System.out.println(a);
    }

    public static void test(String a) {
        a = "world";
    }
// 输出
// hello

String 不也是引用数据类型吗?怎么到这里就行不通了?

要理解这部分知识,还需要学习常量、不可变对象等知识。

先给结论,对应String类型的值传递你就当和基本类型一样,不会改变原来的值。

具体原因,下回分解!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值