Java基础---类和对象
前言
本篇文章将介绍Java类和对象的相关内容
关于面向对象
传统的结构化程序设计通过设计一系列的过程(即算法)来求解问题。一旦确定了这些过程,就要考虑存储数据的方式(即:算法 + 数据结构 = 程序)。注意在这种设计模式中,算法是第一位的,数据结构是第二位的。但是面向对象程序设计(OOP)
调换了这一顺序:将数据放在第一位,然后再考虑操作数据的算法。
面向对象与面向过程的区别:
面向对象程序设计 | 面向过程程序设计(结构化编程) | |
---|---|---|
定义 | 面向对象顾名思义就是把现实中的事务都抽象成为程序设计中的“对象”,其基本思想是一切皆对象,是一种“自下而上 ”的设计语言,先设计组件,再完成拼装 | 面向过程是“自上而下 ”的设计语言,先定好框架,再增砖添瓦。通俗点,就是先定好main()函数,然后再逐步实现mian()函数中所要用到的其他方法 |
特点 | 封装、继承、多态 | 算法 + 数据结构 |
优势 | 适用于大型复杂系统,方便复用 | 适用于简单系统,容易理解 |
劣势 | 比较抽象、性能比面向过程低 | 难以应对复杂系统,难以复用,不易维护、不易扩展 |
对比 | 易维护、易复用、易扩展 ,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合 的系统,使系统 更加灵活、更加易于维护 | 性能比面向对象高 ,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素 |
设计语言 | Java、Smalltalk、EIFFEL、C++、Objective-、C#、Python等 | C、Fortran |
面向对象的基本特征:
封装
:保护内部的操作不被破坏继承
:在原本的基础之上继续进行扩充多态
:在一个指定的范围之内进行概念的转换
类与对象的基本概念
把数据以及对数据的操作方法放在一起,作为一个相互依存的整体,这就是对象;对同类对象抽象出其共性,从而形成类。类与对象是整个面向对象中最基础的组成单元。
类
:是抽象的概念集合,表示的是一个共性的产物,类之中定义的是属性和行为(方法)对象
:对象是一种个性的表示,表示一个独立的个体,每个对象拥有自己独立的属性,依靠属性来区分不同对象
类与对象的定义和使用
定义标准类
定义一个标准类,通常拥有以下四个组成部分:
- 所有成员变量都要使用
private
关键字修饰 - 为每一个成员变量编写一对
Getter/Setter
方法 - 编写一个
无参
数的构造方法 - 编写一个
全参
数的构造方法
这样标准的类也叫做Java Bean
,定义语法如下:
class 类名称 {
private 属性 (即变量) ;
public 行为 (即方法) ;
}
如下示例,定义一个Student
类:
public class Student {
private String name; // 姓名
private int age; // 年龄
public Student() { //无参数的构造方法
}
public Student(String name, int age) { //全参数的构造方法
this.name = name;
this.age = age;
}
public String getName() { //变量name的Getter方法
return name;
}
public void setName(String name) { //变量name的Setter方法
this.name = name;
}
public int getAge() { //变量age的Getter方法
return age;
}
public void setAge(int age) { //变量age的Setter方法
this.age = age;
}
}
类的使用
类的定义完成之后,无法直接使用。如果要使用,首先需要导包(这不是必须的):
- 导包需要指出使用的类
- 对于和当前类
属于同一个包
的情况,可省略
导包语句不写 java.lang
包下的内容不需要导包- 导包格式:
import 包名称.类名称;
(举例:import java.lang)
使用类必须依靠对象,实例化对象的方法有如下两种方式:
//方式一:声明并实例化对象
类名称 对象名称 = new 类名称 () ;
//方式二:先声明对象,然后实例化对象
类名称 对象名称 = null ;
对象名称 = new 类名称 () ;
示例,实例化一个student
对象:
//方式一
Student student = new Student();
//方式二
Student student = null;
student = new Student();
实例化过程:实例化对象语句分为两个部分,new Student()
构造了一个Student类型的对象,并且它的值是对新创建对象的引用
,然后将这个引用
存储在变量student
中,完成实例化
引用数据类型与基本数据类型最大的不同在于:引用数据类型需要内存的分配和使用。所以,关键字
new
的主要功能就是分配内存空间
,也就是说,只要使用引用数据类型,就要使用关键字new来分配内存空间。
实例化对象之后,可以按照如下方式进行类的操作:
- 调用类中的属性(变量):
对象名.成员变量名;
(前提是类中的属性没有被关键字Private
修饰) - 调用类中的方法:
对象名.成员方法名(参数);
示例:操作Student类
public class Demo {
public static void main(String[] args) {
Student stu1 = new Student(); //声明并实例化对象(调用一个无参的构造方法)
stu1.setName("张三");
stu1.setAge(20);
System.out.println("姓名:" + stu1.getName() + ",年龄:" + stu1.getAge());
Student stu2 = null; //声明对象
stu2 = new Student("李四", 21); //实例化对象(调用一个全参的构造方法)
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
}
}
输出结果:
姓名:张三,年龄:20
姓名:李四,年龄:21
区分不同实例化的方式
首先给出两种内存空间的概念:
- 堆内存:保存
对象的属性内容
,堆内存需要用new
关键字来分配空间
- 栈内存:保存的是
堆内存的地址
(在这里为了分析方便,可以简单理解为栈内存保存的是对象的名字)
在任何情况下,只要看见关键字new
,都表示要分配新的堆内存空间,一旦堆内存空间分配了,里面就会有类中定义的属性
,并且属性内容都是其对应数据类型的默认值
。
以上两种实例化对象方式内存表示如下:
两种方式的差别在于①②,第一种声明并实例化的方式实际就是①②组合在一起,而第二种先声明然后实例化是把①和②分步骤来。
如果没有实例化对象的过程,直接使用类,如下(则会报错):
public class Demo {
public static void main(String[] args) {
Student stu1 = null; //声明对象
//stu1 = new Student(); //取消实例化对象步骤
stu1.setName("张三");
stu1.setAge(20);
System.out.println("姓名:" + stu1.getName() + ",年龄:" + stu1.getAge());
}
}
运行结果:
Exception in thread "main" java.lang.NullPointerException
at Demo.Demo.main(Demo.java:8)
此时,程序只声明了Student对象,但并没有实例化Student对象
(只有了栈内存,并没有对应的堆内存空间),则程序在编译的时候不会出现任何的错误,但是在执行的时候出现了上面的错误信息。这个错误信息表示的是“NullPointerException(空指向异常)”,这种异常只要是应用数据类型都有可能出现。
对象引用传递分析
同一块堆内存空间,可以同时被多个栈内存所指向,不同的栈可以修改同一块堆内存的内容。
引用传递代码示例:
public class Demo {
public static void main(String[] args) {
Student stu1 = new Student(); //声明并实例化对象stu1
stu1.setName("张三");
stu1.setAge(20);
System.out.println("姓名:" + stu1.getName() + ",年龄:" + stu1.getAge());
Student stu2 = stu1; //引用传递
stu2.setName("李四");
stu1.setAge(21);
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
}
}
输出结果:
姓名:张三,年龄:20
姓名:李四,年龄:21
对应的内存分配图如下:
我们来看另一种情况:
public class Demo {
public static void main(String[] args) {
Student stu1 = new Student(); //声明并实例化对象stu1
Student stu2 = new Student(); //声明并实例化对象stu2
stu1.setName("张三");
stu1.setAge(20);
stu2.setName("李四");
stu2.setAge(21);
System.out.println("姓名:" + stu1.getName() + ",年龄:" + stu1.getAge());
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
stu2 = stu1; //引用传递
stu2.setName("王五");
stu2.setAge(22);
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
}
}
输出结果:
姓名:张三,年龄:20
姓名:李四,年龄:21
姓名:王五,年龄:22
对应的内存分配图如下:
垃圾:指的是在程序开发之中
没有任何对象所指向
的一块堆内存空间
,这块空间就成为垃圾,所有的垃圾将等待GC(垃圾收集器)
不定期的进行回收与空间的释放。
关于封装性
前文提到面向对象的程序设计具有封装性。通俗的理解就是外部在调用类使用时,无法直接访问其内部属性,相当于对外不可见。举例如下代码:
public class Demo {
public static void main(String[] args) {
Student stu = new Student(); //声明并实例化对象stu
stu.name = "张三"; //直接访问name属性
stu.age = 20; //直接访问age属性
System.out.println("姓名:" + stu.getName() + ",年龄:" + stu.getAge());
}
}
实际上在该段代码编写阶段就会直接提示错误信息,运行后可以也可以看到错误提示,name与age变量在Student类中是private访问控制。这就是封装性的具体体现,在Student类中,变量受到private关键字修饰,致使外部无法通过直接调用其属性并修改或访问。但是我们可以在定义类中增添对应属性的Setter/Getter方法来修改、访问。这就是具有封装性的标准类的一般创建方式。
关于构造方法
构造方法是在对象使用关键字new实例化的时候被调用。
构造方法与普通方法最大的区别在于:构造方法
在实例化对象(new)的时候只调用一次
,而普通方法
是在实例化对象之后可以随意调用多次
。
在创建类中会默认生成一个无参数的构造方法,但是一旦定义了一个构造方法,无参构造方法将不会自动生成。一个类中至少存在一个构造方法
。
同样的,构造方法也属于方法,同样可以重载
,一般在构建类的时候,会创建一个无参数的构造的方法以及一个全参数的构造方法。(如有需要,可以自行创建其他参数形式的构造方法):
public class Student {
private String name; // 姓名
private int age; // 年龄
public Student() { //无参数的构造方法
}
public Student(String name, int age) { //全参数的构造方法
this.name = name;
this.age = age;
}
}
在进行构造方法重载时有一个编写建议:所有重载的构造方法按照参数的个数由多到少,或者是由少到多排列。
关于匿名对象
没名字的对象称为匿名对象,对象的名字按照之前的内存关系来讲在栈内存之中,而对象的具体内容在堆内存之中保存,这样,没有栈内存指向堆内存空间,就是一个匿名对象。
先定义一个Book
类:
public class Book {
private String name;
private double price;
public Book() { //无参的构造方法
}
public Book(String name, double price) { //全参的构造方法
this.name = name;
this.price = price;
}
public void getInfo(){
System.out.println("书名:" + name + ",价格:" + price);
}
}
创建并使用匿名对象:
public class Demo {
public static void main(String[] args) {
new Book("Java核心技术 卷Ⅰ",149.00).getInfo(); //创建并使用匿名对象
}
}
输出结果:
书名:Java核心技术 卷Ⅰ,价格:149.0
匿名对象由于没有对应的栈内存指向,所以
只能使用一次
,一次之后就将成为垃圾,并且等待被GC回收释放。