系列文章专栏
文章目录
前言
这篇笔记介绍了 Java 语言中类和对象的概念及创建,详细的记录了类的成员的基本使用,包括属性、方法、构造器、代码块、内部类,其中前三个应当重点掌握。
一、面向对象
- 面向过程:强调功能行为,以函数为最小单位,考虑怎么做;
- 面向对象:强调具备功能的对象,以类/对象为最小单位,考虑谁来做
二、基本要素:类和对象
2.1 类和对象的概念
- 类(class):对一类事物的描述,是抽象的、概念上的定义
- 对象(instance):是实际存在的该类事物的每个个体,也称实例
- 面向对象程序设计的重点是类的设计,设计类,即设计类的成员
- 属性 = 成员变量 = field = 域、字段
- 方法 = 成员方法 = method = 函数
2.2 类和对象的创建及使用
- 创建过程
- 创建类,设计类的成员(主要包括属性和方法)
- 创建类的对象
- 通过“对象.属性”或“对象.方法”调用对象的结构
- 代码示例
public class PersonTest {
public static void main(String[] args) {
// 2.创建类的对象:类名 对象名 = new 类名()
Person p1 = new Person();
// 3.调用对象的结构:属性、方法
// 调用属性:对象名.属性
p1.name = "Tom";
p1.isMale = false;
System.out.println(p1.name);
// 调用方法:对象名.方法
p1.eat();
p1.sleep();
p1.talk("Chinese");
}
}
// 1.创建类,设计类的成员
class Person{
// 属性
String name;
int age = 1;
boolean isMale;
// 方法
public void eat(){
System.out.println("人会吃饭");
}
public void sleep(){
System.out.println("人会睡觉");
}
public void talk(String language){
System.out.println("人会说话,说的是 " + language);
}
}
- 类之间多个对象的关系:一个类的多个对象,每个对象都独立的拥有一套类的属性。(非 static 的)
- 对象的内存解析
- 堆(Heap):存放对象实例
- 栈(Stack):指虚拟机栈,存储局部变量
- 方法区(MethodArea),存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
三、类的成员
3.1 属性(成员变量)
属性(成员变量)和局部变量的相同点
- 变量定义格式:数据类型 变量名 = 变量值;
- 先声明,后使用;
- 变量都有其对应的作用域
属性(成员变量)和局部变量的不同点
区别 | 属性 | 局部变量 |
---|---|---|
声明位置 | 直接定义在类的{}中 | 声明在方法内、方法形参、构造器形参、构造器内部的变量 |
权限修饰符 | 声明时可使用权限修饰符,常用的权限修饰符有private、public、缺省、protected,目前声明属性时,都使用缺省即可 | 不可以使用权限修饰符 |
默认初始化值 | 有默认初始化值(同一维数组) | 无默认初始化值,调用前需显式赋值(形参调用时赋值即可) |
内存中加载位置 | 堆空间中(非 static) | 栈空间 |
3.2 方法
3.2.1 方法的声明
声明格式:权限修饰符 返回值类型 方法名(形参列表){方法体}
注意:static、final、abstract 来修饰的方法,后面补充
- 权限修饰符:private、public、缺省、protected,暂时默认方法的权限修饰符使用 public (封装性时补充)
- 返回值类型
- 有返回值:声明时需指定返回值的类型,同时,方法体中需使用 return 关键字返回指定类型的变量或常量,
return 数据
即可 - 无返回值:声明时使用 void 来表示,一般不需 return,若有,则表示结束此方法,
return;
即可,且 return 关键字后不可声明执行语句
- 有返回值:声明时需指定返回值的类型,同时,方法体中需使用 return 关键字返回指定类型的变量或常量,
- 方法名:属于标识符,遵循标识符的规则和规范
- 形参列表:可声明0个、1个,或多个形参
数据类型1 形参1, 数据类型2 形参2, ...
- 方法体:方法功能的体现
- 注意:方法在使用时,可以调用当前类的属性或方法,特殊的,方法A中又调用了方法A称为递归方法;方法中不能定义其他方法
//练习:对象+数组;把操作封装到方法里调用
public class StudentTest {
public static void main(String []args) {
//声明Student类型的数组
Student[] stus = new Student[20];
for(int i = 0;i < stus.length;i++){
stus[i] = new Student();
stus[i].number = i +1;
stus[i].state = (int)(Math.random() * (6 - 1 + 1) + 1); //(b-a+1)+a
stus[i].score = (int)(Math.random() * (100 - 0 + 1));
}
StudentTest test = new StudentTest();
test.print(stus);
test.searchState(stus,3);
test.sort(stus);
}
//遍历功能
public void print(Student[] stus){
for(int i = 0;i < stus.length;i++){
System.out.println(stus[i].info());
}
}
//查找功能
public void searchState(Student[] stus, int state){
for(int i = 0;i < stus.length;i++){
if(stus[i].state = state){
System.out.println(stus[i].info());
}
}
}
//排序功能
public void sort(Student[] stus){
for(int i = 0;i < stus.length - 1;i++){
for(int j = 0;j < stus.length - 1 - i;j++){
if(stus[j].score > stus[j+1].score){
Student temp = stus[j];
stus[j] = stus[j+1];
stus[j+1] = temp;
}
}
}
}
}
class Student{
int number; //学号
int state; //年级
int score; //成绩
public String info(){
return "学号是 " + number + ",年级是 " + state + ",成绩是 " + score;
}
}
补充:引用类型的变量,只可能存储两类值:null或地址值(含变量类型)
匿名对象的使用:创建对象后,不显示的赋值给一个变量名,匿名对象只能调用一次
正常:类名 对象名 = new 类名();
匿名对象:new 类名().方法名();
3.2.2 方法的重载
- 概念:同一类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
- 判断是否重载:与方法的返回值类型、权限修饰符、形参变量名、方法体都无关
- 在通过对象调用方法时,通过方法名及参数列表两部分可确定一个指定的方法
- 示例
public class OverLoadTest {
public static void main(String []args) {
OverLoadTest test = new OverLoadTest();
test.mOL(5);
test.mOL(5,6);
test.mOL("ww");
}
//以下三种方法构成重载
public void mOL(int i){
System.out.println(i * i);
}
public void mOL(int i,int j){
System.out.println(i * j);
}
public void mOL(String s){
System.out.println(s);
}
}
3.2.3 可变形参的方法
- 格式:
方法名(数据类型 ... 变量名)
,调用可变个数形参的方法时,传入的参数个数可以是任意个 - 是否构成重载:可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载;但与本类中方法名相同,形参类型也相同的数组之间不构成重载
- 位置:可变个数形参在方法中的形参中,必须声明在末尾,故最多只能声明一个可变形参
- 示例
public class OverLoadTest {
public static void main(String []args) {
OverLoadTest test = new OverLoadTest();
test.mOL(1);
test.mOL(1,2);
test.mOL(1,2,3,4);
test.mOL(); //也调用可变个数形参
}
//以下三种方法构成重载
public void mOL(int i){
System.out.println(i * i);
}
public void mOL(int i,int j){
System.out.println(i * i + j * j);
}
public void mOL(int ... arr){ //可变个数形参
int sum = 0;
for(int i = 0;i < arr.length;i++){ //同数组一样使用
sum += arr[i] * arr[i];
}
System.out.println(sum);
}
}
3.2.4 方法参数的值传递机制
引入:变量的赋值及参数类型的区分
- 变量的赋值
- 基本数据类型:赋值的是变量所保存的数据值
- 引用数据类型:赋值的是变量所保存的数据的地址值
- 参数类型的区分
- 形参:方法定义时,声明的小括号内的参数
- 实参:方法调用时,实际传递给形参的数据
重点:方法参数的值传递机制
- 基本数据类型:实参赋值给形参的是实参真实存储的数据值
//基本数据类型
public class ValueTransferTest {
public static void main(String []args) {
int m = 10;
int n = 20;
System.out.println("m=" + m + ",n=" + n);
//直接交换两个变量的值,交换成功
//int temp = m;
//m = n;
//n = temp;
//System.out.println("m=" + m + ",n=" + n);
//封装在方法里交换两个变量的值:交换失败
//原因:仅在方法体内完成了交换,而方法内的局部变量在方法结束后就消失,并不会改变main方法内变量的值
//正确:应该定义引用类型变量,传递地址值,进而实现封装进方法后变量的交换
ValueTransferTest test = new ValueTransferTest();
test.swap(m,n);
System.out.println("m=" + m + ",n=" + n);
}
public void swap(int m,int n){
int temp = m;
m = n;
n = temp;
}
}
- 引用数据类型:实参赋值给形参的是实参存储数据的地址值
//引用数据类型:类、数组、接口
public class ValueTransferTest {
public static void main(String []args) {
int[] arr = new int[]{-1,3,-2,7,6,9};
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + " ");
}
System.out.println();
//直接排序:可排序成功
//for(int i = 0;i < arr.length-1;i++){
//for(int j = 0;j < arr.length-i-1;j++){
// if(arr[j] > arr[j+1]){
// int temp = arr[j];
// arr[j] = arr[j+1];
// arr[j+1] = temp;
//}
//}
//}
//for(int i = 0;i < arr.length;i++){
// System.out.print(arr[i] + " ");
//}
//以引用类型变量封装在方法里:传递地址值实现变量的复制
ValueTransferTest test = new ValueTransferTest();
for(int i = 0;i < arr.length-1;i++){
for(int j = 0;j < arr.length-i-1;j++){
if(arr[j] > arr[j+1]){
test.swap(arr,j,j+1);
}
}
}
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + " ");
}
}
public void swap(int[] arr,int i, int j){
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
3.2.5 递归方法
- 含义:方法体内调用它自身,包含了一种隐式的循环,会重复执行某段代码,但这种重复执行无须循环控制
- 注意:递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环
- 示例
//计算1-n之间所有自然数的和--使用递归方法(recursion)
public class RecursionTest {
public static void main(String []args) {
RecursionTest test = new RecursionTest();
System.out.println(test.getSum(100));
}
public int getSum(int n){
int sum = 0;
if(n == 1){
sum = 1;
return sum;
}
else{
sum = n + getSum(n - 1); //体现递归
return sum;
}
}
}
3.3 构造器
3.3.1 构造器的说明
- 构造器
- 含义:也称构造方法、constructor,格式是
权限修饰符 类名(形参列表) { }
,作用是创建对象及初始化对象的属性 - 一个类中,至少会有一个构造器,若没有显示的定义类的构造器,则系统默认提供一个空参的构造器
- 一旦显示的定义了类的构造器之后,系统不再提供默认的空参构造器
- 一个类中定义的多个构造器,彼此构成重载
- 含义:也称构造方法、constructor,格式是
- 示例
public class PersonTest {
public static void main(String []args) {
Person p1 = new Person(); //Person()就相当于一个构造器
p1.eat;
Person p2 = new Person("Tom")
System.out.println(p2.name);
}
}
class Person{
String name;
int age;
// 构造器
public Person(){
System.out.println("hello")
}
public Person(String n){
name = n;
}
// 方法
public void eat(){
System.out.println("人可以吃饭")
}
}
- 总结属性赋值的先后顺序
- 默认初始化值
- 显式初始化
- 构造器中赋值
- 通过"对象.方法" 或 “对象.属性”的方式,赋值
3.3.2 JavaBean 的使用
- JavaBean :JavaBean 是一种 Java 语言写成的可重用组件,符合如下标准的 Java 类均是 JavaBean
- 类是公共的
- 有一个无参的公共的构造器 (构造器的权限和类一致)
- 有属性,且有对应的 get、set 方法
3.3.3 UML 类图
UML 类图 | 说明 |
---|---|
Account | 类名 |
-balance:double | 属性名:属性类型 |
+Account(init_balance:double) | 方法:有下划线则为构造器 |
补充说明:+表示 public 类型,-表示 private 类型,#表示 protected 类型;方法的写法:方法的类型(+、-) 方法名(参数名:参数类型):返回值类型
3.4 代码块
- 代码块说明:作用是初始化类和对象;如果有修饰,只能使用 static ;可分为静态代码块、非静态代码块
- 静态代码块
- 内部可以有输出语句
- 随着类的加载而执行,而且只执行一次,只能调用静态的属性、静态的方法,不能调用非静态的结构,作用是初始化类的信息
- 如果一个类中,定义了多个静态代码块,则按照声明的先后顺序执行,静态代码块的执行,优先于非静态代码块的执行
- 非静态代码块
- 内部可以有输出语句
- 随着对象的创建而执行,每创建一个对象,就执行一次非静态代码块,可以在创建对象时,对对象的属性等进行初始化
- 如果一个类中,定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
public class BlockTest {
public static void main(String []args) {
String desc = Person.desc;
System.out.println(desc);
Person p1 = new Person();
Person p2 = new Person();
Person.info();
}
}
class Person{
//属性
String name;
int age;
static String desc = "我是一个人";
//代码块
//静态代码块
static{
System.out.println("输出静态代码块");
desc = "我是个爱动的人";//只能调用静态结构
info();
}
//非静态代码块
{
System.out.println("输出非静态代码块");
age = 1;
eat(); //调用非静态结构
desc = "我是个不爱动的人";//调用静态结构
info();
}
//构造器
public Person(){
}
public Person(String name,int age){
this.name = name;
this,age = age;
}
//方法
public void eat(){
System.out.println("人要吃饭");
}
public static void info(){
System.out.println("我是快乐的人")
}
}
- 程序中成员变量赋值的执行顺序
- 默认初始化
- 显式初始化 / 在代码块中赋值
- 构造器中初始化
- 创建对象以后,通过"对象.属性"或"对象.方法"的方式,进行赋值
- 补充技巧:由父类到子类,静态先行
3.5 内部类
- 内部类:Java中允许将一个类A声明在另一个类B中,则类A就是内部类,,B就是外部类
- 内部类的分类
- 成员内部类(静态、非静态)
- 作为外部类的成员:调用外部类的结构;可以被 static 修饰;可以被4种不同的权限修饰
- 作为一个类:类内可以定义属性、方法、构造器等;可以被 final 修饰,表示此类不能被继承,反之不使用final,就可以被继承;可以 abstract 修饰
- 局部内部类(方法内、代码块内、构造器内)
- 成员内部类(静态、非静态)
- 需要关注的3个问题
- 实例化成员内部类的对象
- 创建静态成员内部类实例----
外部类.内部类 对象名 = new 外部类.内部类();
- 创建非静态成员内部类实例 ---- 先创建外部类的对象,通过对象调用
Person p = new Person(); Person.Bird bird = p.new Bird();
- 创建静态成员内部类实例----
- 成员内部类中区分调用外部类的结构:内部类的属性----
this.name
;外部类的属性----Person.this.name
- 开发中局部内部类的使用:注意点----在局部内部类的方法中如果调用局部内部类所声明的方法中的局部变量的话,要求此局部变量声明为 final 的(jdk 7及之前版本:要求此局部变量显式的声明为 final 的)
- 实例化成员内部类的对象