第九章:对象和类
一.为对象定义类
- 类为对象定义属性和行为,类是一个模板、蓝本或合约
- 对象是类的实例,一个类可以创建多个实例,创建实例的过程又被称为实例化
- 构造方法用来完成初始化动作,例如初始化对象的数据域
- java使用变量定义数据域,使用方法定义动作
- 统一建模语言(UML)也称为类图
二.定义类和创建对象
类是对象的定义,对象从类创建
无参构造的优先级大于私有成员变量
public class TestCircle{
public static void main(String[] args){
Circle circle=new Circle();
//可以通过两种方式修改circle的半径
//circle.radius=4;
//circle.setRadius(4);
System.out.println(circle.radius);//2.0
}
}
class Circle{
double radius=1.0;
Circle(){
radius=2.0;
}
void setRadius(double r) {
radius=r;
}
}
可以将两个类放在一个文件夹下,但是只能有一个公共类,而且公共类要与文件名同名,编译时会产生两个class文件。
注意
如果无参构造和成员变量都没有赋初值,默认为0
创建对象引用变量circle,创建对象,将这个对象的引用赋给这个变量
Circle circle=new Circle();
// 类型 对象引用 创建一个对象
三.使用构造方法创建对象
构造方法在使用new操作符创建对象的时候被调用
构造方法的特殊性:
- 必须与所在类名字相同
- 没有返回值类型
- 作用是初始化对象
可以有多个不同签名的构造方法,便于用不同的初始值来构造对象
一个类中可以不定义构造方法,默认隐含一个方法体为空的无参构造,当且仅当类中没有明确定义构造方法的时候才会自动提供它
报错原因是类中已经有构造方法,不会再调用默认无参构造
四.通过引用变量访问对象
1.对象的数据和方法通过点操作符来访问
2.上文中数据域radius是一个实例变量,因为它依赖于某个具体的实例
3.Math.pow()方法之所以不创建实例就直接调用,是因为Math类中的方法都是静态的
4.有时一个对象创建后并不需要引用,称为匿名对象
5.引用类型的数据域默认为null,如String类型等
6.对象类型的赋值,是将引用赋值给一个变量,赋值后两个引用指向一个对象
7.空指针异常(NullPointerException)
如果没有将对象引用赋值给这个变量,就调用这个对象的数据域和方法,会造成空指针异常
五.使用Java库中的类
1.Date类
public static void main(String[] args) {
// TODO Auto-generated method stub
Date date = new Date();// 为当前时间创建一个date对象
Date date2 = new Date(3215245);// 从1970年1月1日以毫秒为单位计算给定时间的date对象
System.out.println(date.toString());// 返回日期和时间的字符串表示
//结果:Sat Feb 15 22:19:49 CST 2020
System.out.println(date2.toString());// 返回日期和时间的字符串表示
//结果:Thu Jan 01 08:53:35 CST 1970
System.out.println(date.getTime());// 返回从1970年1月1日至今的毫秒数
//结果:1581776389841
date.setTime(1234564);// 设置一个新的流逝时间
System.out.println(date.toString());
//结果:Thu Jan 01 08:20:34 CST 1970
}
2.Random类
Random random = new Random();// 当前时间作为种子创建Random对象
Random random2 = new Random(456432154);// 以一个特定值作为种子创建一个Random对象
System.out.println(random.nextInt());// 返回随机一个整数
System.out.println(random.nextInt(6));// 返回6以内的不包含6的随机整数
System.out.println(random.nextLong());// 随机long值
System.out.println(random.nextDouble());// 随机[0.0-1.0)
System.out.println(random.nextFloat());// 随机[0.0F-1.0F)
System.out.println(random.nextBoolean());// 随机boolean值
注意
创建一个Random对象时,必须指定一个种子或默认的种子。如果种子相同,则他们会产生相同的序列,在软件测试中非常重要
六.静态变量、常量和方法
静态变量是被类中所有对象所共享的,静态方法不能访问类中的实例成员
静态变量存储在一个公共的内存地址
静态成员和静态方法最好使用类名来调用,提高可读性
System.out.println(Circle.PI);//3.14
class Circle {
double radius = 1.0;
static final double PI = 3.14;//定义常量,常量通常是要被对象共享的,所以用static修饰
Circle(double r) {
radius = r;
}
}
注意
实例方法可以访问实例成员和实例方法
静态方法只能访问静态成员和静态方法
七.可见性修饰符
修饰符 | 可访问性 | 类 | 构造方法 | 方法 | 数据 | 块 |
---|---|---|---|---|---|---|
(default)没有修饰符 | 包内访问 | 是 | 是 | 是 | 是 | 是 |
public | 无限制访问 | 是 | 是 | 是 | 是 | 否 |
private | 类内访问 | 否 | 是 | 是 | 是 | 否 |
protected | 包内类和其他包的子类可访问 | 否 | 是 | 是 | 是 | 否 |
为防止创建类的实例,可用private修饰类的构造方法如Math类
private Math()
八.数据域封装
将数据域设置为私有保护数据,并且使类易于保护
为了避免数据域被直接修改,用private修饰将数据域设置为私有的,称为数据域封装
为了能够访问和设置新值,使用get()访问器和set()修改器
熟练写访问器和修改器的小伙伴可以点击Eclipse中菜单栏Source->Getters and Setters快速创建
class Circle {
double radius = 1.0;
static final double PI = 3.14;
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
double getArea() {
return radius*radius*PI;
}
}
九.向方法传递对象参数
给方法传递一个对象,是将对象的引用传递给对象,并且引用的对象和传递的对象是一样的
注意:
下例中展示的是一个浅拷贝,当circle和circle2传到swap方法中时会产生他们的副本,改变副本的地址,对原本没有影响。
public static void main(String[] args) {
// TODO Auto-generated method stub
Circle circle = new Circle();
Circle circle2 = new Circle();
System.out.println(circle);// 交换前circle指向的地址 T_T.Circle@668bc3d5
System.out.println(circle2);// 交换前circle2指向的地址 T_T.Circle@3cda1055
swap(circle, circle2);// 进行交换
System.out.println();
System.out.println(circle);// 交换后circle指向的地址 T_T.Circle@668bc3d5
System.out.println(circle2);// 交换后circle2指向的地址 T_T.Circle@3cda1055
}
public static void swap(Circle A, Circle B) {
Circle C = A;
A = B;
B = C;
}
涉及到引用交换,为引用创建新对象的原地址都不变
十.不可变对象和类
1.可以定义一个不可变类来产生一个不可变对象,不可变对象的内容不可改变
2.不可变类的条件
- 没有set修改器
- 变量都为静态的
- 没有一个指向可变数据域的引用的访问器构造,如Data类中的setTime()方法
十一.变量的作用域
1.一个类的实例变量或静态变量称为类变量或数据域,方法内定义的变量称为局部变量。
2.类中的方法和变量可以以任意顺序出现,但是如果一个数据域是基于另一个数据域的引用来进行初始化时不可以
class Circle {
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public Circle() {
}
double getArea() {
return radius * radius * PI;
}
double radius = 1.0;
static final double PI = 3.14;
}
下面这个示例是需要注意的
int i=2;
int j=i+2;//j的值依赖于i,所以位置不能互换
3.如果一个局部变量和类变量有相同名字,那么局部变量优先,而同名的类变量将会被隐藏,所以为了避免混淆和错误,尽量不要使用和类变量相同名字的局部变量。
4.类中的static{}和{}代码域
Circle circle = new Circle();
Circle circle2 = new Circle();
//运行结果:
// 静态代码域,在类加载的时候调用一次,整个生命周期就只会调用一次
// 普通代码域,类的每个对象创建时都会调用
// 普通代码域,类的每个对象创建时都会调用
static {
System.out.println("静态代码域,在类加载的时候调用一次,整个生命周期就只会调用一次");
}
{
System.out.println("普通代码域,类的每个对象创建时都会调用");
}
注意
- 代码域中定义的变量都是局部的,只有代码域中可以使用
- 如果是内部类,必须使用静态内部类才能定义静态代码块
- 优先级:静态代码块>main方法>构造代码块>构造方法
class Student {
static {
System.out.println("Student 静态代码块");
}
{
System.out.println("Student 构造代码块");
}
public Student() {
System.out.println("Student 构造方法");
}
}
class StudentTest {
static {
System.out.println("StudentTest静态代码块");
}
public static void main(String[] args) {
System.out.println("我是main方法");
Student s1 = new Student();
Student s2 = new Student();
}
}
//结果:
//StudentTest静态代码块
//我是main方法
//Student 静态代码块
//Student 构造代码块
//Student 构造方法
//Student 构造代码块
//Student 构造方法
十二.this引用
1.关键字this引用对象自身,可以在构造方法内部调用其他的构造方法
class Circle {
double radius;
static final double PI = 3.14;
public Circle() {
this(1.0);//调用其他构造方法
}
public Circle(double radius) {
this.radius = radius;//这个this关键字用于引用所构建的对象的隐藏数据域radius
}
}
2.引用对象的实例成员
Circle circle = new Circle(3);//28.6
public Circle(double radius) {
this.radius = radius;
System.out.println(this.getArea());
}
3.如果在引用隐藏数据域以及调用一个重载的构造方法时,this引用是必须的
4.隐藏的静态变量可以通过类名.静态变量来实现,实例变量的隐藏需要this来调用
注意
- Java要求在构造方法中,语句this(参数列表)应在其他语句之前
- 如果一个类中有多个构造方法,最好尽可能用this(参数列表)实现他们,通常,无参数或参数少的构造方法可以使用 this(参数列表)调用参数多的构造方法
十三.总结
通过对本章的学习,我知道了类和对象的含义,以及如何定义他们,通过构造方法创建对象,通过引用变量访问对象,学会了一个异常(空指针异常,后期将会介绍更多的异常),知道了静态变量和常量如何定义,调用它们时最好使用类打点直接调用,学会了封装数据的好处和向方法中传递对象参数,浅拷贝。懂得了如何创建一个不可变类和变量的作用域,静态代码域和构造代码域的优先级,学会了超级实用的this引用的作用。