文章指引
1.什么是面向对象?
众所周知,Java是一门面向对象的程序设计语言,那什么是面向对象呢?
Java是一门纯面向对象的语言(Object Oriented Program,继承OOP),在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来涉及程序,更符合人 们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好。而我们学过的C语言是面向过程的语言,相比于面向对象,面向过程的代码调试和维护起来会更加的麻烦,加大工作量。
2.类的定义和使用
2.1 认识类
类是用来对一个实体(对象)来进行描述的,是对象的抽象化,主要描述该实体(对象)具有哪些属性(外观尺寸等),哪些功能(用来干 啥),描述完成后计算机就可以识别了。
举个例子,我们需要建造一栋房子,那在建造之前,必须先绘制图纸,然后再根据图纸建造,那这里的图纸,就相当于类,是一种抽象化的概念,告诉你我的对象都有什么属性,但并不是真正的对象,而通过图纸建造的房子,就是我们说的对象了,Java叫类的实例化。
2.2 类的定义
类通过关键字class来定义,后面跟类名+{},可以自己定义,但是一般情况下,我们约定类的定义以大驼峰方式来命名,方法用小驼峰来命名,类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类
具有哪些功能,称为类的成员方法,举个例子:
class WashMachine{
public String brand; // 品牌
public String type; // 型号
public double weight; // 重量
public double lenght; // 长
public double weidth; // 宽
public double height; // 高
public String color; // 颜色
public void washClothes(){ // 洗衣服
System.out.println("洗衣功能");
}
public void dryClothes(){ // 脱水
System.out.println("脱水功能");
}
public void setTime(){ // 定时
System.out.println("定时功能");
}
}
这里我们对类起名的时候,将每个单词的首字母大写,我们称为大驼峰命名方式,如上面的例子WashMachine,对方法起名的时候,第一个单词小写,后面单词首字母大写,我们称为小驼峰,如washClothes。
采用Java语言将洗衣机类在计算机中定义完成,经过javac编译之后形成.class文件,在JVM的基础上计算机就可以识别了。
需要说明的是,javac编译时JVM会对每一个类生成一个.class字节码文件,可以理解为将代码翻译成计算机可以看懂的指令。
我们来测试一下,创建一个项目,我们写一个main方法跑一下。
然后在电脑中找到我们的文件夹,进入out -> production -> 项目名称, 我们看到每一个类都有一个.class文件。
javap指令
如果你想看一下他,你可以使用jdk自带的反解析工具javap指令,可以在命令行执行,这里我们直接在这个界面按住Shift,点击鼠标右键,找到在此处打开Powershell窗口 .
在这个窗口中,我们使用javap -c +文件名(或者类名)来查看。这里面都是一些汇编代码,暂时不用理解。
查看更多指令输入javap -help
2.3 类的实例化
2.3.1 new关键字
类的实例化也就是对象的创建,我们需要用new关键字来实例化对象。上面代码中,我们看到这样一行代码:
WashMachine washmachine = new WashMachine();
我们通过类名 + 对象名(相当于一个变量,自己起名字)+new+类名 +()来实例化对象,只是什么意思呢?
首先,我们来谈谈new关键字。
new关键字,是用来为对象申请内存空间的,JVM在虚拟机堆中为这个对象开辟申请一块内存空间,注意所有的对象都是在堆上的,而局部变量是在虚拟机栈上的。
我们来简单画个图帮助理解。
2.3.2 引用
这样,我们就拥有了一个对象(不是女朋友),对象中储存着我们类的相关信息。
注意,我们说的对象并不是此处的变量名washmachine,而是储存在堆中的那块空间。而我们起的变量名washmachine,我们称之为引用。但在平时的叫法中,一般把这个引用就叫做对象,类似于C语言中我们把指针变量叫做指针,但其实指针变量存储的地址才是指针。
那什么叫引用呢?C语言中并没有引用这个概念,我们来把这个引用打印出来看看它的值是什么。
这里我们看到,他给出了类名@一串数字,这是什么?
其实这个就是堆上为对象申请内存空间的地址的哈希值,我们暂时把他理解为内存地址。这类似于C语言中的指针,不过在Java中并没有指针这个说法,我们叫作引用,washmachine这个引用指向了或者引用了刚刚new出来的对象。
我们再来看下这段代码:
我们直接把new WashMachine() 的返回值作为println()的参数,打印出来也是地址,那说明引用中存放的是new的对象的地址。我们来试试把washmachine中的值赋给washmachine1,我们打印washmachine1中的值。
发现正是washmachine中的值,说明washmachine和washmachine1中存储的是同一个对象的地址,washmachine把值赋给washmachine1,washmachine1引用了washmachine所引用的对象,如下示意图。
2.3.3 构造方法
在C语言中,我们学过( )为函数调用操作符,那在实例化对象时,类名后面跟的( )又是什么呢?
WashMachine washmachine = new WashMachine();
其实, 它也是函数调用操作符,只不过,它调用的是类中的特殊方法,这种方法叫构造方法 ,在new对象时,会首先调用构造方法,但我们看到,WashMachine类中并没有这个方法呀,这是因为编译器会在编译阶段帮我们默认生成一个不带参数的构造函数。如果我们自己写了构造方法,编译器就不再帮我们生成了。
值得注意的是,构造方法是没有返回值的,写成void也不行,而且方法名必须和类名相同,构造方法在创建对象时由编译器自动调用,并且在整个对象的生命周期内只调用一次。我们来试试自己写一个构造方法。
果然如我们所料,在new对象时,调用了我们的构造方法。那既然是方法,能不能传参调用呢?答案是可以的,能传几个参数呢?这就由你自己决定了,
注意,如果你自己写了构造函数 ,无论带参不带参,编译器都不会再帮你生成了。
成员初始化
构造函数一般用于初始化成员变量,我们来试试带参数的构造方法。
为了更深层地理解对象的实例化和构造方法,我们来打断点调试一下,看看编译器是如何执行的,如何初始化变量。
第一步,打断点,debug走起
我们发现变量表还没有东西,接着点击向下箭头进入构造方法内部。看看变量表会发生什么变化。
在调用构造方法之后,构造方法还没有执行时,已经生成了我们的成员变量,且赋了初始值,所以我们说在类加载时进行成员变量的初始化。
插播一下,在分配对象内存空间时JVM对字段进行初始化,对不同的对象赋不同的初始值,具体如下表:
数据类型 | 默认值 |
---|---|
byte、short、int | 0 |
long | 0L |
float | 0.0f |
double | 0.0 |
char | ‘\u0000’ |
boolean | false |
引用类型 | null |
成员初始化结束后,才是正常的构造方法执行! 如图
更详细成员初始化请点击
IDEA构造器
这里给大家介绍一个IDEA快捷键,一键生成构造方法,从此敲代码靠鼠标!!!
在类空白处,按下鼠标右键,选择Generate
选择Constructor
按住Ctrl 点击要初始化的成员变量,点击OK,大功告成!
来看看效果吧!解放双手就现在!
2.4 this引用
在上面构造方法中我们使用了this.成员名来访问成员变量,这个this是什么呢?为什么用它?该怎么理解?
我们来把上面写过的两个参数构造方法改造一下,删掉this试试
public Student(String name, double score) {
name = name;
score = score;
System.out.println("这是一个带两个参数的构造方法");
}
name = name; 相当于形参给形参赋值,并没有给成员变量赋值,那我们的this就派上用场了。
2.4.1 什么是this引用
java编译器给每个“成员方法“增加了一个隐藏的引用类型参数,该引用参数指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
注意:this引用的是调用成员方法的对象
我们打开jclasslib工具来看看类的字节码信息
在方法的本地变量表中我们发现,在类的Student()方法中,我们只有两个参数name和score,但字节码中编译器帮我们加了一个this引用,并且放在第一个参数位置上,这就是增加的隐藏引用类型参数。
2.4.2 this引用的特性
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
- this只能在"成员方法"中使用
- 在"成员方法"中,this只能引用当前对象,不能再引用其他对象,具有final属性
- this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法
我们打个断点,来调试我们上面的代码
我们记下这个值,@491,继续调试。
原来student的值也是@491,他们存储了相同的地址,即this引用引用了student指向的对象,他俩引用同一个对象。
2.4.3 this 调用构造方法
构造方法中,可以通过this调用其他构造方法来简化代码。
注意:
1.this(…)必须是构造方法中第一条语句
2.不能形成环,当我们这样调用时,编译器报错了,进入无限循环调用,编译不通过。
3.封装
3.1 封装的定义
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。何为封装呢?将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。简单来说就是:套壳屏蔽细节。
对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。
3.2 访问限定符
在之前的代码中,我们发现有些方法,类,成员变量前面加了public,而有些没有,有些还是private,这是什么呢?
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。
Java中提供了四种访问权限限定符:
private: 只能在自己类中使用,其他一律不可访问。
default: 指什么也不写时默认访问权限,同一个包里都能访问。
protected:主要是用在继承中,我们在继承部分再详细介绍。
public: 是访问权限最大的控制符,谁都可以访问。
一般情况下,成员变量设置为private,成员方法设置为public。
这里我们说到一个词,包访问权限,那什么叫包呢?
3.3 封装扩展之包
3.3.1 包的概念
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于文件夹目录。
在电脑磁盘中,我们把类似类型或相关的文件放在同一个文件夹中,可以理解为一个包。如下图:
在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
3.3.2 导入包中的类 import的使用
一般情况下,我们建议一个Java文件只写一个类,注意每个Java文件只允许存在一个public修饰的类。那我们想用其他Java文件中或者其他包中的类时怎么办呢?总不能用一次定义一次吧。另外,Java 中已经提供了很多现成的类供我们使用,例如Date类:可以使用 java.util.Date 导入 java.util 这个包中的 Date类.
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
使用import关键字来导入其他包中的类(当然要有权限访问,例如私有类就不行啦)。
当然咯,还可以用 java.util. *导入util包中的所有类。
还有一种写法,不用import也可以使用,不过比较繁琐,如下代码:
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
但是,既然繁琐为什么还要用它?import不香吗? emmm,存在即合理。
前面说过,在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
既然Java允许这样做,那就一定会出现import导入包时,不同包中存在相同的类名,那我们在直接通过类名使用时,编译器也懵逼了,这啥呀这是??? 我们来看这段代码
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.getTime());
}
}
报错惹,这是因为util包和sql包中都有Date类,编译器左右为难,我tm怎么知道你要用那个Date类,一气之下,愤怒罢工!!
那这个时候,我们显式的指定要导入的类名就派上用场了,指定了我们要使用哪个包中的类。
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
这样编译器就很高兴啦!最讨厌模糊不清的感觉了。
import还能导入包中静态的方法和字段,只需要在import后加上static即可。
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double r = 4.5;
// 静态导入的方式写起来更方便一些.
// double area = Math.PI * Math.pow(r, 2);
double area = PI * pow(r, 2);
System.out.println(area);
}
}
看到这里,很多人想这不是和C语言【#include】差不多一个意思嘛,但其实还是有区别的,C语言在预编译阶段,会把头文件的内容全部加到我们的代码文件中,但Java不同,它是在你使用这个类时再去包里找。import 更类似于 C++ 的 namespace 和 using。
3.3.3 自定义包
我们可以在一个项目创建多个包来妥善管理我们的文件,就需要自定义创建包,基本规则如下:
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如com.baidu.demo1 ).
- 包名要和代码路径相匹配. 例如创建 com.baibu.demo1 的包, 那么会存在一个对应的路径 com/baidu/demo1 来存储 代码.
- 如果一个类没有 package 语句, 则该类被放到一个默认包中.
我们来试试用IDEA创建自定义包。
- 在 IDEA 中先新建一个包: 右键 src -> new -> package
- 在弹出的对话框中输入包名, 例如 com.baidu.demo1 然后回车即可
3.在新建的包中新建类,右键包名 -> new ->Java class, 然后输入类名即可。
我们打开资源管理器,可以看到不同层级的包已经被创建了。
我们来新建一个TestDemo类。
可以看到,我们的Java文件中第一行编译器自动帮我们添加了package语句,说明一下这个类定义在哪个包中。
插播一个小技巧,上图中我们发现,在IDEA中并没有一层一层显示我们的包
这样显示不太习惯怎么办? 我们只需要点击右上角的小齿轮,点掉Flatten Packages即可。
我们试试不同包中类的导入,再新建一个demo2包,在demo2中我们新建一个Student类,我们在demo1的TestDemo类中导入Student类试试看。
package com.baidu.demo1;
import com.baidu.demo2.Student;
public class TestDemo {
public static void main(String[] args) {
Student student = new Student("张三", 89.2);
System.out.println(student.name);
}
}
运行成功!芜湖!
3.3.4 常见的包
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包。
=========================================================
码字不易,点个赞再走吧!收藏不迷路! 持续更新中…