一、对象的构造及初始化
1.如何初始化对象
通过前面知识点的学习知道,在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方法才可以将具体的日期设置到对象中。通过上述例子发现两个问题:
- 每次对象创建好后调用SetDate方法设置具体日期,比较麻烦,那对象该如何初始化?
- 局部变量必须要初始化才能使用,为什么字段声明之后没有给值依然可以使用?
2.构造方法
概念:构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
public class Date { public int year; public int month; public int day; // 构造方法: // 名字与类名相同,没有返回值类型,设置为void也不行 // 一般情况下使用public修饰 // 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次 public Date(int year, int month, int day){ this.year = year; this.month = month; this.day = day; System.out.println("Date(int,int,int)方法被调用了"); } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { // 此处创建了一个Date类型的对象,并没有显式调用构造方法 Date d = new Date(2021,6,9); // 输出Date(int,int,int)方法被调用了 d.printDate(); // 2021-6-9 } }
- 注意:构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。
特性:
- 名字必须与类名相同
- 没有返回值类型,设置为void也不行
- 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
- 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)``
public class Date { public int year; public int month; public int day; // 无参构造方法 public Date(){ this.year = 1900; this.month = 1; this.day = 1; } // 带有三个参数的构造方法 public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { Date d = new Date(); d.printDate(); } }//上述两个构造方法:名字相同,参数列表不同,因此构成了方法重载
如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。
public class Date { public int year; public int month; public int day; public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { Date d = new Date(); d.printDate(); } }
上述Date类中,没有定义任何构造方法,编译器会默认生成一个不带参数的构造方法。
注意:一旦用户定义,编译器则不再生成
- 构造方法中,可以通过this调用其他构造方法来简化代码:
public class Date { public int year; public int month; public int day; // 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复 // 此处可以在无参构造方法中通过this调用带有三个参数的构造方法 // 但是this(1900,1,1);必须是构造方法中第一条语句 public Date(){ //System.out.println(year); 注释取消掉,编译会失败 this(1900, 1, 1); } // 带有三个参数的构造方法 public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } }
注意:
- this(…)必须是构造方法中第一条语句
- 不能形成环(无参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用
3.默认初始化
在上文中提出的第二个问题:为什么局部变量在使用时必须要初始化,而成员变量可以不用呢?
要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情
Date d = new Date(2021,6,9);
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:
- 检测对象对应的类是否加载了,如果没有加载则加载
- 为对象分配内存空间
- 处理并发安全问题 比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
- 初始化所分配的空间
- 设置对象头信息
- 调用构造方法,给对象中各个成员赋值
4.就地初始化
在声明成员变量时,就直接给出了初始值。如以下代码:
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(); } }
- 注意:代码编译完成后,编译器会将所有给成员初始化的这些语句添加到各个构造函数中
二、封装
1.封装的概念
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
2.访问限定符
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:
比如:
public:可以理解为一个人的外貌特征,谁都可以看得到
default: 对于自己家族中(同一个包中)不是什么秘密,对于其他人来说就是隐私了
private:只有自己知道,其他人都不知道
【说明】
- protected主要是用在继承中,继承部分详细介绍
- default权限指:什么都不写时的默认权限
- 访问权限除了可以限定类中成员的可见性,也可以控制类的可见性
3.封装扩展之包
3.1包的概念
在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。
在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。
3.2导入包中的类
Java 中已经提供了很多现成的类供我们使用. 例如Date类:可以使用 java.util.Date 导入 java.util 这个包中的 Date类。
public class Test { public static void main(String[] args) { java.util.Date date = new java.util.Date(); // 得到一个毫秒级别的时间戳 System.out.println(date.getTime()); } }
但是这种写法比较麻烦一些, 可以使用 import语句导入包:
import java.util.Date; public class Test { public static void main(String[] args) { Date date = new Date(); // 得到一个毫秒级别的时间戳 System.out.println(date.getTime()); } }
如果需要使用 java.util 中的其他类, 可以使用 import java.util.*:
import java.util.*; public class Test { public static void main(String[] args) { Date date = new Date(); // 得到一个毫秒级别的时间戳 System.out.println(date.getTime()); } }
但还是建议显式的指定要导入的类名, 否则还是容易出现冲突的情况。
import java.util.*; import java.sql.*; public class Test { public static void main(String[] args) { // util 和 sql 中都存在一个 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导入包中静态的方法和字段:
import static java.lang.Math.*; public class Test { public static void main(String[] args) { double x = 30; double y = 40; // 静态导入的方式写起来更方便一些. // double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); double result = sqrt(pow(x, 2) + pow(y, 2)); System.out.println(result); } }
3.3自定义包
基本规则:
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 )
- 包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径com/bit/demo1 来存储代码.
- 如果一个类没有 package 语句, 则该类被放到一个默认包中.
操作步骤:
1.在 IDEA 中先新建一个包: 右键 src -> 新建 -> 包:
2.在弹出的对话框中输入包名, 例如 com.bit.demo1
3.在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可.
4.此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了
5.同时我们也看到了, 在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句
3.4包的访问权限控制举例
Computer类位于com.bit.demo1包中,TestComputer位置com.bit.demo2包中:
package com.bit.demo1; public class Computer { private String cpu; // cpu private String memory; // 内存 public String screen; // 屏幕 String brand; // 品牌 public Computer(String brand, String cpu, String memory, String screen) { this.brand = brand; this.cpu = cpu; this.memory = memory; this.screen = screen; } public void Boot(){ System.out.println("开机~~~"); } public void PowerOff(){ System.out.println("关机~~~"); } public void SurfInternet(){ System.out.println("上网~~~"); } } //---------------------------------// package com.bite.demo2; import com.bite.demo1.Computer; public class TestComputer { public static void main(String[] args) { Computer p = new Computer("HW", "i7", "8G", "13*14"); System.out.println(p.screen); // System.out.println(p.cpu); // 报错:cup是私有的,不允许被其他类访问 // System.out.println(p.brand); // 报错:brand是default,不允许被其他包中的类访问 } } // 注意:如果去掉Computer类之前的public修饰符,代码也会编译失败
3.5常见的包
java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
java.lang.reflect:java 反射编程包;
java.net:进行网络编程开发包。
java.sql:进行数据库开发的支持包。
java.util:是java提供的工具程序包。(集合类等) 非常重要
java.io:I/O编程开发包。