到目前为止,已经从事 Java 几个月的时间了,虽然还是一个小小白,但真的感觉到如果仅仅靠工作中学习到的东西来提升自己,那真的是太少了,也太缓慢了,而且这个过程本身也是没有一个系统性的道路,但是在工作中确实可以学习到一些实际解决的快速方法,不管是代码逻辑还是规范,还要学习的还有很多很多。
这条小白升级的路也不知道会有多久,但是我会从最基础开始,为自己巩固,也会自己记录。
那么今天来了解一下小白升级路中的《对象与类》,就看看这四个字,再想一想 Java又是面向对象编程,那么关系就越来越靠近了,简单介绍一下面向对象编程的简称 OOP,英文全称Object Oriented Programming。
1.类
类(CLass)是对象的模板或蓝图,在面向对象编程的三大特点中,封装和继承体现在类和类之间;
1.1 封装
在类中,从形式上看,封装是将数据和行为隐藏或包装到类中,这些数据和行为有的属于实例域(对象),还有的属于静态域(类),一般来说,有 static 修饰的Filed(变量,包括属性和方法)属于静态域,不属于实例域,静态域只能由类本身调用,实例域的由对象调用;封装的特性一定程度上的保护了类的隐私性,也防止对象对其他类的随意访问;
1.2 继承
封装是在一个类中出现,而继承是在两个类之间,当子类继承父类后,也获取到了父类的全部属性和方法,而此时子类也可以拥有自己的属性和方法,继承的出现让类的扩展性更加强大了,在 Java 中,所有类都有一个父类 Object,也继承了 Object 的属性和方法;子类继承父类,是使用(子 extends 父)代表继承;
2.对象
2.1 对象和类的区别
对象是类的实例化,具体化的形式;举个例:手机类(包括摄像头(属性),通讯录(属性),还有打电话功能(方法)),手机的具体化对象是小米 8,此时摄像头属性为 2000 万,通信录有 10 个电话号码,打电话给具体的某个人。这个例子中,手机类是类,是对象的模板,而小米 8 是实例化,可以更具体的按照模板去给定特定的数据或者方式。
一个类可以对应多个对象,每个对象也存在差异性。比如:小米八,华为 40,都有摄像头,通讯录,打电话功能,但是具体的参数确实不同的;
2.2 对象的初始化
从上面例子,大概的认识了类和对象区别,那接下来将会通过 Date类作为例子说一下对象的初始化过程,也就是对象怎么产生,怎么初始化得到的。
首先介绍一下 Date 类,这个类是 Java 标准库的一个类(注意是java.util.Date路径),已经由 Java 开发人员封装好的一个类,在实际开发中,我们使用的它的次数也会比较多。Date 类的对象是描述一个具体的时间点,但时间点的表现形式可以不同,像某些地区时间形式习惯为(月/日/年),而国内更习惯(年/月/日)。
大家应该知道,一个类的构造器名称和类是相同的,Date 类也是如此,当我们初始化 Date 类时调用的也是 Date()构造方法,在构造方法前加上 new 操作符,就成为了:new Date()
,此时在堆内存中该新建的对象被赋予了当前时间,我们可以直接使用其作为参数传入方法内,如System.out.println(new Date());
,也可将其赋值给一个对象变量,如Date birthday = new Date();
此时 birthday 这个对象变量在内存中指向了新建的 Date 对象,
我们应该注意到,对象和对象变量的区别,对象变量不一定代表有指向的对象,例如我们只是定义了一个Date对象
变量Date birthdayB = null;
,但没有指向(赋于)任何已存在的 Date 对象,那么我们此时是不能使用该对象变量,因为其并没有经过初始化或者指向任何已存在的 Date 对象。并且,对象变量仅仅只是引用了一个对象,并不是实际的对象;
上面的Date birthday = new Date();
语句表明,此时 birthday 对象变量已经引用了具体的Date对象,当使用System.out.println(birthday);
输出时会有Sat Sep 05 10:30:31 CST 2020(中国东八区标准时间)
,会发现这个输出的日期是让我们不太习惯,我们更习惯于2020-09-05 10:30:31
表现形式,所以当使用 Date 类输出一定形式的时间时,需要进行格式转化:
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date birthday = new Date();
System.out.println(birthday.toString()); //① Sat Sep 05 10:32:59 CST 2020
System.out.println(birthday); //② Sat Sep 05 10:32:59 CST 2020
System.out.println(System.currentTimeMillis()); //③ 1599273179522
System.out.println(format.format(birthday)); //④ 2020-09-05 10:32:59
上面四种输出方式:
①和②输出结果是一样的,①是由birthday.toString()
输出,因为Java 所有类都有一个公共的父类Object,toString方法是继承而来;
③输出的是一串数字,这又是什么呢?System.currentTimeMillis()
语句是获取从 1970年1月1日 00:00:00
到此刻的时间毫秒值;
④输出的是经过SimpleDateFormat类格式化后得到一种时间表现形式,当然日期格式化方式很多,这里暂不做考究;
3.自定义类
3.1 实例域和静态域
package study.pojo;
import java.util.Date;
public class DogDemo {
public static final String CLASSIFICATION_OF_ANIMALS = "狗";//①动物分类——狗(静态域)
private final String name;//②名字(实例域)
private static int id = 1;//③ID 号(静态域)
private Date birthday;//④出生日期(实例域)
{//⑤(实例域)
System.out.println("输出语句:打印非静态代码块");
}
static {//⑥(静态域)
System.out.println("输出语句:打印静态代码块");
}
public static void staticMethod() {//⑦(静态域)
System.out.println("静态方法");
}
public void exampleMethod() {//⑧(实例域)
System.out.println("实例方法");
}
public void printDogInfo(){//⑨打印小狗信息(实例域)
System.out.println(DogDemo.CLASSIFICATION_OF_ANIMALS+":"+name+"的ID 是"+id+",它的出生日期是:"+birthday);
}
public DogDemo(String name) {//构造器①(参数 name)
this.name = name;
}
public DogDemo(String name, Date birthday) {//构造器②(参数1 name,参数 2 birthday)
this.name = name;
this.birthday = birthday;
}
public String getName() {//参数 name get方法
return name;
}
public static int getId() {//参数 name get方法
return id++;
}
public Date getBirthday() {//参数 name get方法
return birthday;
}
public void setBirthday(Date birthday) {//参数 name get方法
this.birthday = birthday;
}
}
上面是自定义的一个动物——dog 类,当我们使用测试类 main 方法调用下面语句时:
public static void main(String[] args) {
DogDemo tangMu = new DogDemo("汤姆");
// 输出语句:打印静态代码块
// 输出语句:打印非静态代码块
}
这个时候只是新建了 DogDemo 类,但可以发现,调用的是一个带参数name的构造器,并且只是 new 了一个对象,但是在控制台有输出值,有以下几点原因:
(1)final 修饰静态常量和非静态变量
在自定义DogDemo类中,非静态name 被 final 修饰,静态常量CLASSIFICATION_OF_ANIMALS被 fianl 修饰。当非静态成员变量用 final 修饰时,需要在定义时赋值或者使用构造器赋值,并且被赋值后不可再被更改,也就是 final 修饰的变量只可被一次赋值;当静态成员常量被 final 时,需要在定义时赋值,如private static final String CLASSIFICATION_OF_ANIMALS = "狗";
关于 final 修饰的非静态变量,赋值方式总结如下:
①定义时赋值:private final String name = "汤姆";
在本类中,final修饰的是 name,因每个小狗姓名可能不同,我们就不能在定义时赋值;
②在构造器内赋值,无需传入参数:
public DogDemo() {
this.name = "汤姆";
}
③构造器传参数进行初始化:
public DogDemo(String name) {//构造器①(参数 name)
this.name = name;
}
④代码块中进行初始化,当使用代码块初始化,那就无需再通过构造器初始化,final 修饰的变量只需初始化一次:
{//⑥(实例域)
System.out.println("输出语句:打印非静态代码块");
name="汤姆";
}
(2)静态代码块和非静态代码块
在自定义DogDemo类中,静态代码块属于静态域,非静态代码块属于实例域,可以看到在自定义类中,非静态代码块在静态代码块前面,但是打印时,先打印的是静态代码块输出语句,这是为什么呢?因为静态代码块是当一个类加载时就初始化了,且只初始化一次,不会因为每次对象创建而创建,而非静态代码块随着每次对象的创建都会初始化一次,如下两个对象一个加载时出现的情况:
public static void main(String[] args) {
DogDemo tangMu = new DogDemo("小汤姆");
DogDemo jieMu = new DogDemo("小杰姆");
// 输出语句:打印静态代码块
// 输出语句:打印非静态代码块
// 输出语句:打印非静态代码块
}
说明静态代码块只打印了一次,并没有因为有两个对象的创建而创建两次。
(3)静态方法和实例方法
在自定义类中,静态方法有staticMethod() 方法,实例方法有exampleMethod()和printDogInfo(),还是老样子,静态域属于类本身,实例属于对象,当调用时,静态方法调用格式为:类名.静态方法名(参数列表)。实例方法:类对象.实例方法名(参数列表)。
public static void main(String[] args) {
DogDemo tangMu = new DogDemo("小汤姆");
tangMu.exampleMethod();
DogDemo.staticMethod();
// 输出语句:打印静态代码块
// 输出语句:打印非静态代码块
// 实例方法
// 静态方法
}
3.2 构造方法
(1)构造方法重载
在自定义DogDemo类中,定义了两个构造器:
public DogDemo(String name) {//构造器①(参数 name)
this.name = name;
}
public DogDemo(String name, Date birthday) {//构造器②(参数1 name,参数 2 birthday)
this.name = name;
this.birthday = birthday;
}
调用两种构造器时:
DogDemo tangMu = new DogDemo("小汤姆");
DogDemo jieMu = new DogDemo("小杰姆",new Date());
往回看两个构造方法,会发现两个方法名相同,且都是类名,它们区别是传入的参数不同,构造器①需要传入参数是name,构造器②需要传入参数name 和 birthday。按照 Java 重载的定义,一个类中出现两个或多个具有相同方法名,标签它们的参数列表不同,那么这些方法产生了重载,需要注意:参数列表不同可以是参数的个数、顺序等不同,并且重载的定义和方法的返回值无关。
(2)无参构造
自定义DogDemo类没有看到无参构造,是因为该类中存在未初始化定义的非静态变量 name需要在构造器传入初始化值。但是,在 Java 中,自动一类如果没有编写构造方法,那么系统会给该类默认提供一个无参构造器,并且对未初始化变量进行复制,其中,数值型设置为 0,布尔值设置为 false,所有对象变量设置为 null。如果删除 name 变量或者先定义 name 初始值,那么该类存在的无参构造方法为:
public DogDemo() {
}
4.类设计技巧
(1)保证数据私有;
(2)数据尽量要初始化,不依赖 Java 默认初始值;
(3)不要在类中过多使用基本类型;
(4)不是所有的域都需要独立的域访问器或域更改器;
(5)将职责过多的类分解;
(6)类名和方法名要体现它们的职责;