先看一个日期类的例子:
public class Date {
public int year;
public int month;
public int day;
public void setDay(int y, int m, int d){
year = y;
month = m;
day = d;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
public static void main(String[] args) {
// 构造三个日期类型的对象 d1 d2 d3
Date d1 = new Date();
Date d2 = new Date();
Date d3 = new Date();
// 对d1,d2,d3的日期设置
d1.setDay(2020,9,15);
d2.setDay(2020,9,16);
d3.setDay(2020,9,17);
// 打印日期中的内容
d1.printDate();
d2.printDate();
d3.printDate();
}
}
//----------------
//打印
2020/9/15
2020/9/16
2020/9/17
但是三个对象都在调用setDate和printDate函数,但是这两个函数中没有任何有关对象的说明,setDate和printDate函数如何知道打印的是那个对象的数据呢
其实在成员方法的第一个形参中默认隐藏了一个this,this引用的是调用成员方法的对象。
**this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。**只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
public class Date {
public int year;
public int month;
public int day;
public void setDay(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
public void printDate(){
System.out.println(this.year + "/" + this.month + "/" + this.day);
}
}
注意:this引用代表的是的是调用成员方法的对象。
每个成员方法括号中的第一个参数默认是this
注意,我们要习惯使用this。
this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
this只能在"成员方法"中使用
在"成员方法"中,this只能引用当前对象,不能再引用其他对象
this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法
对象的引用传递给该成员方法,this负责接收
通过前面知识点的学习知道,在Java方法内部定义一个局部变量时,必须要初始化,否则会编译失败。
public static void main(String[] args) {
int a;
System.out.println(a);
}
// Error:(26, 28) java: 可能尚未初始化变量a
要让上述代码通过编译,非常简单,只需在正式使用a之前,给a设置一个初始值即可。如果是对象:
public class Date {
public int year;
public int month;
public int day;
public void setDay(int y, int m, int d){
this.year = y;
this.month = m;
this.day = d;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
}
public static void main(String[] args) {
Date d = new Date();
d.printDate();
d.setDate(2021,6,9);
d.printDate();
}
// 代码可以正常通过编译
需要调用之前写的SetDate方法才可以将具体的日期设置到对象中。通过上述例子发现两个问题:
这两个问题就涉及到了构造方法和默认初始化的问题。
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
什么是构造方法?
构造方法非常特殊:这个方法没有返回值,方法名和类名是一样的。
如果我们没有在类中写任何的构造方法,那么Java会帮我们提供一个默认的不带参数的构造方法。
public class Date {
public int year;
public int month;
public int day;
public void setDay(int y, int m, int d){
year = y;
month = m;
day = d;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
Date() {
System.out.println("这个是构造方法,在实例化对象的时候就会进行调用");
}
//
//
public static void main(String[] args) {
Date date1 = new Date();
}
}
//--------------
//编译器运行结果为
//这个是构造方法,在实例化对象的时候就会进行调用
当然在一个类中没有写构造方法的话,Java会默认提供。
但是一个类当中写了构造方法的话,java就不会提供了。
看看效果
public class Date {
public int year;
public int month;
public int day;
public void setDay(int y, int m, int d){
year = y;
month = m;
day = d;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
public Date() {
System.out.println("这个是构造方法,在实例化对象的时候就会进行调用");
}
public Date(int year ,int month ,int day) {
this.year = year;
this.month = month;
this.day = day;
}
//
//
public static void main(String[] args) {
Date date1 = new Date(11,11,11);
date1.printDate();
}
}
//--------------
//编译器运行结果为
//11/11/11
这样我们就可以通过构造方法在进行对象实例化的过程中进行成员变量的初始化。
至于我们写了两个Date函数,那么这两个函数就属于重载关系
public Date() {
System.out.println("这个是构造方法,在实例化对象的时候就会进行调用");
}
public Date(int year ,int month ,int day) {
this.year = year;
this.month = month;
this.day = day;
}
重载:方法名相同,参数列表不同,与返回值无关。
我们可以说当一个对象调用完构造函数,那么这个对象就生成了。
因为生成一个对象分为两步1. 为对象分配内存 2. 调用合适的构造方法
补充:
对于构造方法,我们不能进行添加void
因为如果添加了返回值的话,那么就不属于我们的构造方法了。
如果自己没有写构造函数,那么编译器会提供一个默认的不带任何参数的构造方法。
名字必须与类名相同
没有返回值类型,设置为void也不行
创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。一旦用户定义,编译器则不再生成。
如果生成了一个带有参数的构造方法,那么一定要在对象实例化的过程中进行传参,否则会报错。
在这个过程中this代表的是调用当前对象的其他构造方法。
public class Date {
public int year;
public int month;
public int day;
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
public Date() {
this(11,11,11);
}
public Date(int year ,int month ,int day) {
this.year = year;
this.month = month;
this.day = day;
}
public static void main(String[] args) {
Date date1 = new Date();
date1.printDate();
}
}
//------------
//编译器运行结果为
//11/11/11
但是在调用其他构造方法的时候有一定的要求:
只能在构造方法内部才能使用,不能在其他方法中使用。
不能形成环,否则进行反复调用构造函数,形成封闭循环
至于这个我们先不管,以后会讲。
总结
this.date 访问当前对象的成员变量
this.func() 访问当前对象的成员方法
this() 调用当前对象的其他构造方法
this不能调用静态static修饰的成员方法和变量这个后面我们再说
为什么局部变量在使用时必须要初始化,而成员变量可以不用呢?
要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情:
Date d = new Date(2021,6,9);
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:
检测对象对应的类是否加载了,如果没有加载则加载
为对象分配内存空间
处理并发安全问题
比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
初始化所分配的空间
即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:
数据类型 | 默认值 |
---|---|
byte | 0 |
char | ‘\u0000’ |
short | 0 |
int | 0 |
long | 0L |
boolean | false |
float | 0.0f |
double | 0.0 |
reference | null |
所以在调用构造函数进行初始化之前,我们就会进行赋值为默认值。
补充
public只是权限的问题。(只提一下)
局部变量:定义在方法内部的变量。
成员变量:定义在类的内部的变量,定义在成员方法的外部。
定义在成员方法内部的变量就叫做局部变量。
在声明成员变量时,就对成员变量设置初始值。
public class Date {
public int year = 1900;
public int month = 1;
public int day = 1;
//进行就地初始化
public Date(){
}
public Date(int year, int month, int day) {
}
public static void main(String[] args) {
Date d1 = new Date(2021,6,9);
Date d2 = new Date();
}
}
当然这个除非是业务需求,不然没有提别大的意义,如果成员变量会进行修改,那么就地初始化相当于进行了一个默认值的定义。
**面向对象程序三大特性:封装、继承、多态。**而类和对象阶段,主要研究的就是封装特性。
何为封装呢?
简单来说就是套壳屏蔽细节。把类的实现细节进行隐藏,对外只提供一些交互的接口。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
比如:对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务。但实际上:电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
然而要进行封装就涉及到了访问限定符。
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:
private 默认权限 protected public
默认权限就是我们什么都没加的时候的使用默认权限访问限定符。
public class Date {
int year;
}
private
权限非常小,被修饰之后只能在自己的类中进行使用。
class Student {
public String name;
public void show() {
System.out.println("姓名" + name);
}
}
默认权限default
只能在自己的包中进行使用
这就涉及到了包的概念,如果不懂包是什么,可以先跳过这块继续看下一块的内容。
protect和public以后再讲,因为涉及到了子类的知识
要了解封装就要了解权限,要了解权限,就要了解包。
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。
包就像文件夹一样,一个文件夹当中不能有相同的文件名,不同文件夹下可以有相同的文件名。
在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在
不同的包中即可。
Java 中已经提供了很多现成的类供我们使用. 例如Date类:可以使用java.util.Arrays
导入java.util
这个包中的Arrays
类.
public class Test {
public static void main(String[] args) {
int[] arr = {1,2,3};
java.util.Arrays.toString(arr);
}
}
但是这种方法比较麻烦,我们可以使用import语句导入包
import java.util.Arrays; // 导包
public class Test {
public static void main(String[] args) {
int[] arr = {1,2,3};
Arrays.toString(arr);
}
}
如果需要使用java.util
中的其他类, 可以使用import java.util.*
import java.util.*;
public class Test {
public static void main(String[] args) {
int[] arr = {1,2,3};
Arrays.toString(arr);
}
}
import java.util.*
不是把里面的所有的类都导进来,是随取随用。用到哪个类,就会导哪个类。
但是我们更建议显式的指定要导入的类名. 否则还是容易出现冲突的情况.
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 这个时候编译器就不知道该导入哪个包了,毕竟两个包中都有Date类,所以编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
//--------------
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.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 static
可以导入包中静态的方法和字段
pow方法就是Math
中的一个静态方法,我们使用import static
就可以进行简写,在调用的时候只需要写方法名。
没有import static
修饰过的
import java.lang.Math;
public class Test {
public static void main(String[] args) {
double x = 10;
double result = Math.pow(x,2);
}
}
import static
修饰过的
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
double x = 10;
double result = pow(x,2);
}
}
至于这个import static
不建议大家使用
包就是所谓的文件夹
操作步骤
www.bit.com
,那么包的名字就是com.bit.www
package
是声明当前类是在哪个包底下。
package com.bite.demo1;
除了JDK自动导入的,剩下的都需要我们用import
自己导入
10.Java类和对象完+作业+继承
回顾
什么是类?什么是对象?什么是面向对象?什么是面向过程
如何定义一个类
如何实例化一个对象
如果通过对象访问对象的成员属性、成员方法
通过(对象.)进行访问
this引用?代表的是当前对象的引用
构造方法中的this引用可以访问成员变量,可以访问成员方法,也可以访问当前对象的其他构造方法
引用指向null代表什么?代表这个引用不指向任何对象
引用不能指向引用,但是引用能指向引用所指向的对象。
一个引用只能指向一个对象,不能指向多个对象。
封装:根据业务需求,对类的细节进行隐藏,提供公开的接口,来和数据进行交互。
class Person {
private String name;
public int age;
private void eat() {
System.out.println("吃饭");
}
public void show() {
System.out.println(name + " " + age);
}
}
我们通过private关键字进行对成员变量和成员方法进行封装,private封装后的只能在类中使用。
name成员变量、eat成员方法都已经封装起来了
使用private关键字进行了封装。此时,name的权限就变小了,只能在当前类当中才能进行访问。
补充
public哪里都能访问
private只能在当前类访问
default只能在包中进行访问
在Java编译器当中我们对构造函数创建有快捷方法。
第一步右键选择Generate、或者快捷键Alt+Insert
第二步:选择Constructor
第三步选择构造函数的形参(多选按住CTRL)
这样就可以在封装的情况下,通过构造函数在对象实例化的过程中进行初始化成员变量。
但是,如果想要更改姓名的话却无法更改,除非再次进行实例化一个对象,所以我们可以通过创建成员方法作为接口那么我们就创建了成员方法setName
进行姓名的更改getName
进行名字的查询
class Person {
private String name;
public int age;
private void ear() {
System.out.println("吃饭");
}
public void show() {
System.out.println(name + " " + age);
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person("mengdehong",20);
person.setName("meng");
System.out.println(person.getName());
person.show();
}
}
//--------------
//编译器运行结果为
//meng
//meng 20
但是我们有多个成员变量进行封装的时候,我们都要对每个成员变量进行set变量、get变量的方法吗?
实际上我们也有快捷操作。
第一步左键选择Generate
第二部选择Getter and Setter
第三步进行选择所需要创建成员方法的成员变量
这样就创建好了成员方法作为接口
今天我们讲了类和对象的一部分,剩下的内容我们会在下一篇博客中讲完。