Java 基础语法让你弄懂类和对象
Java 基础语法
其实在学习 C 语言时就一直听到 C 语言是面向过程的,而 Java 是面向对象的这句话。并且我们之前的学习中也遇到了类,但是对它好像没有其他的认知。
那么面向过程与面向对象到底是什么呢?它们有哪些不同的意义呢?
类与对象到底是什么呢?这章就来带你揭晓!
一、类与对象的初步认知
我们可以举一个洗衣服的例子来认识面向对象和面向过程
对于面向过程: 我们可以看成是手洗衣服的过程
对于面向对象: 我们可以看作直接用洗衣机去洗
其中总共有四个对象:人、衣服、洗衣粉、洗衣机
而整个洗衣服的过程就是:人将衣服放进洗衣机、人倒入洗衣粉、人启动洗衣因此整个过程主要就是上述四个对象之间交互完成的,人不需要关心洗衣机具体是如何洗衣服并且甩干的
因此对于面向对象,重点就是
找对象
创建对象
使用对象
那么对象从何而来呢?它其实是由类的实例化产生的
下面让我们来了解类以及类的实例化吧!
二、类和类的实例化
类
就是一类对象的统称
对象
就是这一类具体化的一个实例
举个栗子讲讲
比如我们做月饼的模子就是一个类,而通过这个模子可以做出月饼。那么在这个例子中,做月饼的模子就是类,而那个月饼就是对象。并且一个月饼就是一个实体,而一个模子可以实例化无数个对象,也就是说一
个类可以产生无数的对象
那怎么声明一个类呢?首先我们要知道
声明一个类就是创建一个新的数据类型(感觉类似于 C 语言中的 struct)
类在 Java 中属于引用类型
Java 使用关键字 class 来声明类,并且类中可以定义一些属性和行为
上个代码看看
// 定义一个类 class Person{ // 属性(成员变量) public int age; public String name; // 行为(成员方法) public void eat(){ System.out.println("吃饭"); int a = 10; // 局部变量 } }
其中 Person 就是类名,{} 中的就是类的主体
。类里面可以创建属性和行为
注意:
此处写的方法不带关键字 static
类的实例化
用类类型创建对象的过程,称为类的实例化
我们要理解
- 类只是一个模型一样的东西,限定了类有哪些成员变量
- 一个类可以实例化出多个对象,实例化出的对象会占用实际的物理空间,存储类成员变量
- 就如上述的月饼的例子,类就如一个模子,并没有实际的存在,实例化出的对象才能实际存储数据,占用物理空间
// 定义一个类 class Person{ // 属性(成员变量) public int age; public String name; // 行为(成员方法) public void eat(){ System.out.println("吃饭"); int a = 10; // 局部变量 } } public class Main{ public static void main(String[] args){ // 通过 new 实例化一个对象 Person person = new Person(); // 成员方法调用需要使用对象的引用调用 person.eat(); } } // 结果为:吃饭
其中 Person 为我们创建的类,person 为我们使用 Person 类创建的引用类型。关键字 new 用于创建一个对象的实例。使用 .
符号来访问对象中的属性和方法(既包含读,也包含写)
我们可以看一下在内存中上述代码是怎么存储的
注意 Person 类中定义的 a 是一个局部变量,因为它在方法里面。而局部变量保存在栈中,而实例化的对象以及该类中的类成员变量,保存在堆中
三、类的成员
类的成员可以包含:字段、方法、代码块、内部类和接口等
1. 字段/属性/成员变量
在类中但是在方法外部定义的变量,我们称为:“字段”或“属性”或“成员变量”(一般不严格区分)
我们可以对上述创建的对象进行调用
class Person{ public int age; public String name; } public class Main{ public static void main(String[] args){ Person person = new Person(); System.out.println("age = " + person.age); System.out.println("name = " + person.name); } } // 结果为: // age = 0 // name = null
结果居然为 0 和 null,这是因为在 Java 中有一个默值认规则。
如果一个对象的字段没有设置初始值,那么就会被设置为一个默认的值
- 对于各类数字类型,默认值为0 或者 0.0
- 对于 boolean 类型,默认值为 false
- 对于引用类型(String、Array、以及自定制类),默认值为 null
- 对于 char 类型,默认值为 ‘\u0000'
因此我们要注意,如果字段本身没有初始值,且使用前没有初始化,可能调用时会出现异常(使用引用类型时),如
class Person{ public int age; public String name; } public class Main{ public static void main(String[] args){ Person person = new Person(); System.out.println(person.name.length); } } // 会出现 NullPointerException 异常
2. 方法
方法其实之前就专门讲过了,这里就特意讲两点
- 如果我们要想知道我们的对象里面有什么变量、值为多少,就类似于要做一个 show 方法去展示。但是如果想看的类很多,就很麻烦,这是我们可以使用一下步骤(编译器:IDEA)
在该类的空白处,点击右键就可以看到 Generate
- 再点击它,再找到 toString() 再点击就会出现以下代码(以上步骤可以使用快捷键:Alt + Insert 实现)
@Override public String toString() { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}';其中 @Override 叫做重写的注解,就解释了这段代码是重写的,又 toString 属于 objet 的方法,所以就是重写了 object 方法
为什么要这么做呢?如果我们直接通过
System.out.println(person); // 结果为:Person@1b6d3586
但是如果我们讲上述步骤完成后结果就变成了
// 结果为:Person{age=0.0, name='null'}
这是因为上述步骤修改了 object 类中的 toString 方法
这里我们与不重写前的结果比较可以知道 toString 方法 @ 后一段的内容应该就是表示地址
因此我们通过上面步骤对 toString 进行重写,就可以直接通过打印对象来得到该对象中的参数。并且我们将重写内的值改变,打印结果也会改变,如
@Override public String toString() { return "Person{" + "年龄=" + age + ", 名字='" + name + '\'' + '}';
打印结果就会改变为
// 结果为:Person{年龄=0.0, 名字='null'}
- 还有一点是关于构造方法的,下面会讲到!
3. static 关键字
上述的成员变量以及方法其实都是普通的成员变量以及方法,在 Java 中还有一种静态成员变量(也叫类变量)和静态成员方法。它们是由 static 修饰的
那么 static 有什么作用呢?
- 修饰属性
- 修饰方法
- 代码块
- 修饰类
修饰属性:
如果在成员变量前加上 static,此变量就叫做静态变量
- 静态变量属于类,和具体的实例无关。也就是同一个类的不同实例公用一个静态属性
- 可以直接调用静态变量,而无需创建类的实例
- 静态变量存储在方法区
我们来看一个代码
class Person{ public static int cnt; public static void speak(){ System.out.println("我是静态成员方法!"); } } public class Main{ public static void main(String[] args){ System.out.println("cnt = " + Person.cnt); Person.speak(); } } // 打印结果为: // cnt = 0 // 我是静态成员方法!
大家注意没,我调用时是直接使用的类名,而不是对象名。这就是静态变量与普通成员变量的第一点不同,调用时直接使用类名。
既然和实例无关那会不会静态变量的存储也会不同,我们开看一个代码
class Test{ public int a; public static int count; } public class Main{ public static void main(String[] args) { Test t1 = new Test(); t1.a++; Test.count++; System.out.println(t1.a); System.out.println(Test.count); System.out.println("============"); Test t2 = new Test(); t2.a++; Test.count++; System.out.println(t2.a); System.out.println(Test.count); } } // 结果为: /** 1 1 ============ 1 2*/
这是因为 count 被 static 修饰后,所有类共享,并且其存储区域在方法区
修饰方法:
- 静态方法属于类,而不属于类的对象
- 可以直接调用静态方法,而无需创建类的实例
- 静态方法只能访问静态数据成员,并且可以更改静态数据成员的值
看一段代码
class Person{ int a; public static int cnt; public static void speak(){ cnt = 10; //a = 100; 会报错,因为访问量非静态数据成员 System.out.println("我是静态成员方法!"); } }
注意:
this 和 super 两个关键字不能在静态上下文中使用(this 是当前实例的引用, super 是当前实例父类实例的引用,也是和当前实例相关)【后面会介绍到!】
小结:
就用一段代码作为总小结吧
class Person{ public int age; // 实例变量(属于对象) public static int count; // 静态变量,编译时已经产生(属于类本身),只有一份且存放在方区 public final in SIZE = 10; // 被 final 修饰的叫常量,后续不可以更改(属于对象) public static final in COUNT = 99; // 静态的常量(属于类本身) // 实例成员函数 public void eat(){ int a = 10; //局部变量(存放在栈中) } // 静态成员函数 public static void staticTest(){ //不能访问非静态成员 // age = 10; 会报错 System.out.println("StaticTest()"); } } public class Main{ public static void main(String[] args){ // 产生对象 实例化对象 Person person = new Person();// person 为对象的引用 System.out.println(person.age);// 默认值为0 //System.out.println(person.count);// 会有警告! // 正确访问方式: System.out.println(Person.count); System.out.println(Person.COUNT); Person.staticTest(); // 总结:所有被 stati c所修饰的方法或者属性,全部不依赖于对象。 person.eat(); } }
为啥 main 函数是静态的,如果是非静态的可以啵,比如
class TestDemo{ public void main(String[] args){ TestDemo testDemo = new TestDemo(); testDemo.main(); } }
按照非普通成员方法的形式,如果 mian 函数要调就是上述代码吧。但大家发现一个问题没?
如果此时要使用 main 方法,就需要使用对象调用,那么好我们就在 main 方法里创建对象并且调用好了吧。诶?不对呀,要调用 main 方法就要使用对象啊???可我们创建的对象在 main 方法里面,怎么调用???
所以 main 方法要加上 static !
还有一点就是静态方法里面可以调用普通方法吗?no!
- 调用普通方法,就要用对象的引用
- 而静态方法的使用是直接使用类,不需要创建对象
- 所以静态方法里不能使用普通方法
四、封装
其实上面关于类主要也就是讲了类的实现和类的调用。如果我们以后使用了别人实现的类,结果后来别人修改了里面的某个变量名。人傻了?我们要一个一个修改原有的变量名吗?
因此出现了一种方法叫做:封装
封装的本质就是让类的调用者不必太多了解类的实现者是如何实现类的,只要知道如何使用就行
1. private 实现封装
private / public 这两个关键字表示访问权限控制
- 被 public 修饰的成员变量或者成员方法,可以直接被类的调用者使用
- 被 private 修饰的成员变量或者方法,不能直接被类的调用者使用
如果我们使用 public 修饰,那么类的实现的代码被修改了,可能你创建的代码就要花很多精力去维护。因此在实际中,我们一般用 private,至于 public 的使用要视情况而定,并且最好一个类只提供一个必要的 public 方法
让我们看一个代码更好的理解上述意思
class Person{ private int age = 13; private String name = "ZhangSan"; public void show(){ System.out.println("name = "+name + " age = "+age); } } } public class Main{ public static void main(String[] args){ Person person = new Person(); person.show(); } } // 结果为:name = ZhangSan age = 13
上述代码就是使用了 private 修饰,所以主类里面不可以使用 age 和 name,而当我们要输出它们时,就可以直接使用 show 方法,无论实现 Person 类的函数中的 name 和 age 怎么改变,都可以正常打印。
如果你想获取或者修改这个 private 属性,那么就要用到接下来介绍的 getter / setter 方法
2. getter 和 setter 方法
使用这个方法可以在所创建的类中空白处右击鼠标,选择 Generate,就会出现
然后点击就可以。Getter 是获取这个属性,Setter 是修改这个属性,我们用上述代码示范
class Person{ private double age; private String name; // 使用 setter 和 getter 方法 public double getAge() { return age; } public void setAge(double age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public class Main{ public static void main(String[] args){ Person person = new Person(); person.setAge(13); person.setName("张三"); double age = getAge(); String name = getName(); System.out.println("姓名:" + name + " 年龄:" + age); } } // 打印结果为:姓名:张三 年龄:13
注意
大家有注意没,上述 setter 方法的代码
public void setAge(double age) { this.age = age; }
其中 this 是啥,为啥要用它呢?首先其实这份代码可以改成这样public void setAge(double Myage) { age = Myage; }其中我将形参修改了,反正 Myage 就表示我要修改成的值,如果我不修改,直接这样可以吗
public void setAge(double age) { age = age; }应该不行对吧,因为此时的 age 就表示该方法里面的了,不能将对象的值进行修改。所以为我们此时引入 this
this 表示当前对象的引用
五、构造方法
我们要知道,使用对象时,new 执行了两个过程
- 为对象分配内存
- 调用对象的构造方法
第二点的构造方法是啥???其实我们上述代码都都用到啦!
因为如果你自己没有创建构造方法的话,编译器会默认提供一个不带参数的构造方法
那什么又是不带参数的构造方法呢?
1. 基本语法
首先我们要知道构造方法的语法规则
- 方法名必须与类名相同
- 构造方法没有返回值
- 每一个类中至少定义一个构造方法(没有明确定义,系统会自动生成一个无参构造,如果自己定义了,默认构造将不再生成)
我们来看一个构造无参的构造方法吧!
class Person{ private double age; private String name; public Person(){ System.out.println("这是一个无参的构造方法"); } } public class Main{ public static void main(String[] args){ Person person = new Person(); } } // 结果为:这是一个无参的构造方法
既然有无参数的,那么也有有参数的
class Person{ private double age; private String name; public Person(){ System.out.println("这是一个无参的构造方法"); } public Person(double age, String name){ this.age = age; this.name = name; System.out.println("这是一个有参的构造方法"); } public void show(){ System.out.println("name: "+name+" age: "+age); } } public class Main{ public static void main(String[] args){ Person person1 = new Person(); Person person2 = new Person(13, "张三"); person2.show(); } } // 结果为: // 这是一个无参的构造方法 // 这是一个有参的构造方法 // name:张三 age:13
小结
构造方法不仅仅可以构造对象,同时也可以帮助我们进行成员变量的初始化
上述代码里面的构造方法其实构成了重载(方法名相同、参数列表不同、返回值不做要求)
2. this 关键字
其实上面已经讲了 this,但是大家要格外注意,this 是表示当前对象的引用(注意不是当前对象)原因如下
对象的形成要经过两步:1. 为对象分配内存 2. 调用合适的构造方法
而我们使用 this 时,其实我们已经完成了内存的分配,但我们并没有完成构造方法的调用,所以此时还不能说创建了对象,只是将对象的地址得到了,也就是对象的引用
this 的用法
this.成员变量:调用成员变量
this.成员方法:调用成员方法
this() :调用其他的构造方法
其中调用成员变量我们上面用了很多了,接下来我们先看下调用成员方法吧!
class Person{ private double age; private String name; public Person(double age, String name){ this.age = age; this.name = name; System.out.println("这是一个有参的构造方法"); } public void show(){ System.out.println("name: "+name+" age: "+age); this.eat(); } public void eat(){ System.out.println("吃饭"); } } public class Main{ public static void main(String[] args){ Person person = new Person(13, "张三"); person.show(); } } // 结果为: // 这是一个有参的构造方法 // name:张三 age:13 // 吃饭
上述代码的 show 方法中就用到了 this 调用成员方法。接下来我们再看看怎么调用其他构造的方法
class Person{ private double age; private String name; public Person(){ this(15, "李四"); System.out.println("这是一个无参的构造方法"); } public Person(double age, String name){ this.age = age; this.name = name; System.out.println("这是一个有参的构造方法"); } public void show(){ System.out.println("name: "+name+" age: "+age); } } public class Main{ public static void main(String[] args){ Person person = new Person(); person.show(); } } // 结果为: // 这是一个有参的构造方法 // 这是一个无参的构造方法 // name:李四 age:15
大家看结果,自己思考下执行的顺序
注意
使用 this() 调用构造函数,必须放在第一行
不能在静态方法中使用
六、认识代码块
1. 什么是代码块
代码块就是
根据代码块定义的位置以及关键字,可以分为四种
本地代码块
实例代码块(也叫构造代码块)
静态代码块
同步代码块(这个先不讲,俺也不会)
2. 本地代码块
本地代码块是定义在方法中,比如
public class Main{ public static void main(String[] args) { { int x = 10 ; System.out.println("x1 = " +x); } int x = 100 ; System.out.println("x2 = " +x); } }
这个在 C 语言里也见过,但几乎没用过
3. 实例代码块
构造代码块是定义在类中(且不加修饰符)
一般用于初始化实例成员变量
class Person{ private double age; private String name; public Person(){ System.out.println("这是一个无参的构造方法"); } // 实例代码块 { this.name = "Yb"; this.age = 15; System.out.println("实例代码块"); } } public class Main{ public static void main(String[] args){ Person person = new Person(); } }
4. 静态代码块
静态代码块是定义在类中(且加上 static 修饰)
一般用于初始化静态成员属性和需要提前准备的一些数据
class Person{ private double age; private String name; public Person(){ System.out.println("这是一个无参的构造方法"); } // 实例代码块 {this.name = "Yb"; this.age = 15; System.out.println("实例代码块"); } // 静态代码块 static{ // 不能用 this // this.age = 15; 会报错 System.out.println("静态代码块"); } } public class Main{ public static void main(String[] args){ Person person1 = new Person(); Person person2 = new Person(); } } // 结果为: /** 静态代码块 实例代码块 这是一个无参的构造函数 实例代码块 这是一个无参的构造函数 */使用 {} 定义的一段代码
注意:上述代码的打印结果,是先实行静态代码块,再实行实例代码块,最后才执行构造函数,并且在同一个类中,静态代码块不管生成多少对象,只会执行一次
七、补充说明
1. toString 方法
其实上面已经讲到了,重写 object 的 toString 方法,将对象自动转换成字符串,因此不需要使用 show 方法去查看对象的参数
因此这里就再重述一些知识点
- toString 方法会在使用 println 的时候被自动调用
- 将对象转换成字符串这样的操作叫做序列化
- toString 使 Object 提供的方法,我们自己创建的 Person 类默认继承了 Object 类,可以重写 toString 方法实现我们自己的版本
- @Override 在 Java 中称为注释,上述代码中的 @Override 表示下面实现的 tostring 方法使重写了父类的方法
2. 匿名对象
匿名对象顾名思义是表示没有名字的对象,即没有引用对象,可以看下面的代码
// 不使用匿名对象 Person person = new Person(); person.eat(); // 使用匿名对象 new Person().eat();
并且如果一个对象只是用一次,后面不需要使用了,可以考虑使用匿名对象.。理由如下
// 不使用匿名对象 Person person = new Person(); person.eat(); person.show(); // 使用匿名对象 new Person().eat(); new Person().show();
其中我们注意到,不使用匿名对象的代码,只需要 new 一个对象就行,而使用匿名对象的代码,其实 new 了两个对象
还有匿名对象只能在创建对象时使用,就是说创建匿名对象就要使用它
八、总结
最好我们再来巩固几个问题吧!
(1)引用一定在栈上吗? 不一定
我们来看一段代码
class Person{ private double age; private String name; private int[] elem = new int[10]; } public class Main{ public static void main(String[] args){ Person person = new Person(); } }
我们用一张图来清晰感受下吧
其中 person是一个引用它在栈上,而 elem 是数组,它也是引用,可是它却存放在堆中,所以引用不一定在栈上
(2)引用能指向引用吗? 不能
之前就讲过了,引用不能指向引用,这个说法不对。正确的说法应该是,该引用指向了另一个引用所指向的对象
一个引用可以指向多个对象吗? 不能
(3)一个引用可以指向多个对象吗? 不能
这不就是海王了嘛,比如 person 去找对象
Person person = new Person(); // person 先找了一个对象 person = new Person(); // 然后又找了一个 person = new Person(); // 牛逼!又找了一个 person = new Person(); // 佩服!还找了一个
你问 perosn 有几个对象,我告诉你,就一个,而且还是最后一个,你问我为啥?海外必死
(4)一个引用赋值null 代表什么?
代表当前引用不指向任何对象
(5)你能用上述知识写一个代码实现交换两个值吗?
class Value{ private int val; public int getVal() { return val; } public void setVal(int val) { this.val = val; } } public class TestDemo{ public static void swap(Value val1, Value val2){ int tmp = val1.getVal(); val1.setVal(val2.getVal()); val2.setVal(tmp); } public static void main(String[] args) { Value value1 = new Value(); value1.setVal(10); Value value2 = new Value(); value2.setVal(20); System.out.println("交换前:value1 = " + value1.getVal() + " value2 = " + value2.getVal()); swap(value1,value2); System.out.println("交换前:value1 = " + value1.getVal() + " value2 = " + value2.getVal()); } } /**结果为: 交换前:value1 = 10 value2 = 20 交换前:value1 = 20 value2 = 10*/