一:类与对象
1.1、什么是类
从语法角度来说:
【修饰符】 class 类名{
}
从概念角度来说:
类:是一类具有相同特性的事物的抽象描述。与生活中对事物的分类是一个道理。
1.2、什么是对象
对象是这一类事物中的一个具体的个体、实体、实例。
1.3、类与对象的关系
从语法角度:类是创建对象的模板。
从概念来说:类是众多对象共同特征的描述。
1.4、创建对象的关键字:new
new 类名() //匿名对象
类名 变量名/对象名 = new 类名(); //给对象取了一个名字
1.5、类的第一个成员:属性
(1)属性的声明
【修饰符】 class 类名{
//属性
【修饰符】 数据类型 属性名;
}
public class Employee {//员工类
//属性
public String name;
public int age;
public double salary;
}
(2)在其他类中如何访问这些属性
//(1)先创建对象
类名 变量名/对象名 = new 类名();
//(2)对象名.属性
对象名.属性 = 值;
System.out.println(对象名.属性)
//TestEmployee类是作为测试类,作为程序的入口
public class TestEmployee {
public static void main(String[] args) {
//第一步:创建一个Employee类的对象
Employee emp = new Employee();
Employee emp2 = new Employee();
//第二步:通过对象.属性的方式访问
emp.name ="张三";
emp.age = 23;
emp.salary = 15000;
System.out.println("员工对象emp的信息:姓名:" + emp.name +",年龄:" + emp.age +",薪资:" + emp.salary);
System.out.println("员工对象emp2的信息:姓名:" + emp2.name +",年龄:" + emp2.age +",薪资:" + emp2.salary);
}
}
(3)属性的特点
-
每一个对象的属性是独立的
-
对象的属性有默认值
-
byte,short,int,long:默认值是0
-
float,double:默认值是0.0
-
char:默认值 编码值为0的空字符
-
boolean:默认值:false
-
引用数据类型:null
-
(4)属性值存储在堆,new出来的都在堆中存储
二、类的成员之一:成员变量(非常重要)
2.1 成员变量的分类
根据有没有static修饰,成员变量可以分为两类:
(1)静态成员变量,简称静态变量,又称为类变量
(2)非静态成员变量,简称实例变量,俗称属性
【修饰符】 class 类名{
【其他修饰符】 static 数据类型 变量名; //静态变量
//属性
【修饰符】 数据类型 属性名; //实例变量
}
2.2 静态变量与实例变量的区别
-
静态变量:它的值是整个类“共享的”,==不推荐==(但是不是说不可以)通过“对象名.静态变量”的方式进行访问,==推荐使用“类名.静态变量”==的方式进行访问
-
实例变量:它的值是每一个对象“独立的”,只能是通过“对象名.实例变量”的方式进行访问
BankAccount类
public class BankAccount {//Account账户,银行账号
public static double rate;//rate,利率 静态变量
public String id;//id,卡号 实例变量
public double balance;//balance,余额 实例变量
}
TestBankAccount类
public class TestBankAccount {
public static void main(String[] args) {
//第一步:创建BankAccount类的对象
BankAccount b1 = new BankAccount();
//第二步:访问b1对象的属性
b1.id = "1111111";
b1.balance = 5000;
// BankAccount.id = "333333";//报错
BankAccount.rate = 0.035;
System.out.println("b1账号的信息,卡号:" + b1.id
+",余额:" + b1.balance
+",b1的利率:" + b1.rate
+",BankAccount的利率:" + BankAccount.rate);
//b1账号的信息,卡号:1111111,余额:5000.0,b1的利率:0.035,BankAccount的利率:0.035
BankAccount b2 = new BankAccount();
b2.id = "22222222";
b2.balance = 8000;
b2.rate = 0.04;
System.out.println("b1账号的信息,卡号:" + b1.id
+",余额:" + b1.balance
+",b1的利率:" + b1.rate
+",BankAccount的利率:" + BankAccount.rate);
// b1账号的信息,卡号:1111111,余额:5000.0,b1的利率:0.04,BankAccount的利率:0.04
System.out.println("b2账号的信息,卡号:" + b2.id
+",余额:" + b2.balance
+",b2的利率:" + b2.rate
+",BankAccount的利率:" + BankAccount.rate);
// b2账号的信息,卡号:22222222,余额:8000.0,b2的利率:0.04,BankAccount的利率:0.04
}
}
2.3 静态变量与实例变量的内存分析
-
静态变量先初始化,它在类加载完就进行了初始化,它的值存储在方法区,整个类包括所有对象“共享的”
-
实例变量后初始化,在new对象时才会初始化,每一个对象都会初始化一次,它的值存储在堆中,每一个对象都是“独立的”
Chinese类
public class Chinese {//中国人
public static String country;//国家名,共享
public String name;//姓名,独立
public int age;//年龄,独立
}
TestChinese类
public class TestChinese {
public static void main(String[] args) {
System.out.println(Chinese.country);//null
Chinese.country = "中国";
Chinese c1 = new Chinese();
c1.name = "张三";
c1.age = 23;
c1.country = "中华人民共和国";
Chinese c2 = new Chinese();
c2.name ="李四";
c2.age = 24;
}
}
2.4 引用数据类型的成员变量
需求:
(1)声明一个MyDate类型,有属性:年,月,日
(2)声明另一个Employee类型,有属性:姓名(String类型),生日(MyDate类型)
(3)在TestEmployee测试类中的main中,创建两个员工对象,并为他们的姓名和生日赋值,并显示
MyDate类
//(1)声明一个MyDate类型,有属性:年,月,日
public class MyDate {
public int year;
public int month;
public int day;
}
Employee类
//(2)声明另一个Employee类型,有属性:姓名(String类型),生日(MyDate类型)
public class Employee {
public String name;
//只要是类,就可以在Java中当成数据类型
public MyDate birthday;
}
TestEmployee类
public class TestEmployee {
public static void main(String[] args) {
//第一步:创建员工Employee类的对象
Employee e1 = new Employee();
//第二步:给e1对象的属性赋值
e1.name = "张三";
//因为String比较特殊,它可以像基本数据类型一样,直接赋字符串的值,
//看不出来"张三"是一个对象
//e1.birthday = "1995-5-1";
//报错,"1995-5-1"是字符串值,而birthday是MyDate类型
e1.birthday = new MyDate();
//注意:birthday是除了字符串之外的其他引用数据类型
//那么给它赋值必须赋一个对应类型的“对象”,否则会报错
e1.birthday.year = 1995;
e1.birthday.month = 5;
e1.birthday.day = 1;
System.out.println("e1员工的信息:姓名:" + e1.name
+",生日:" + e1.birthday);
//e1员工的信息:姓名:张三,生日:MyDate@16b98e56
//除了字符串之外(字符串比较特殊),其他对象,默认打印出来的都是 虚拟首地址
System.out.println("e1员工的信息:姓名:" + e1.name
+",生日:" + e1.birthday.year+"年"
+ e1.birthday.month+"月"
+ e1.birthday.day+"日");
//e1员工的信息:姓名:张三,生日:1995年5月1日
Employee e2 = new Employee();
e2.name = "李四";
e2.birthday = new MyDate();
//没有这一句,编译没有问题,但是运行下面的代码会报NullPointerException
e2.birthday.year = 2000;
e2.birthday.month = 8;
e2.birthday.day = 9;
System.out.println("e2员工的信息:姓名:" + e2.name
+",生日:" + e2.birthday.year+"年"
+ e2.birthday.month+"月"
+ e2.birthday.day+"日");
Employee e3 = new Employee();
e3.name = "王五";
MyDate my = new MyDate();
my.year = 2001;
my.month = 6;
my.day = 9;
e3.birthday = my;
e3.birthday.day = 10;
System.out.println("e3员工的信息:姓名:" + e3.name
+",生日:" + e3.birthday.year+"年"
+ e3.birthday.month+"月"
+ e3.birthday.day+"日");
System.out.println("my对象:" + my.year+"年" + my.month +"月" + my.day+"日");
}
}
图片1234:能.出什么
图片:怎么看错误
引用数据类型的属性内存图分析
2.5 方法的参数传递机制(再谈引用数据类型)
(1)参数是基本数据类型
实参给形参传什么值?实参给形参传递的是数据值的副本。
形参的修改对实参有什么影响?形参的修改对实参没有影响。
(2)参数是引用数据类型
实参给形参传什么值?实参给形参传递的是首地址的副本。
形参的修改对实参有什么影响?
-
形参数组对元素的修改相当于实参数组自己修改的元素。
-
形参对象对属性的修改相当于实参对象自己修改属性。
//(1)声明一个MyDate类型,有属性:年,月,日
public class MyDate {
public int year;
public int month;
public int day;
}
public class TestParamPassValue {
public static void main(String[] args) {
MyDate date = new MyDate();
System.out.println(date.year + "年" + date.month + "月" + date.day +"日");
//0年0月0日
change(date);//(date)实参 给形参 (MyDate my)的是对象的首地址的副本。此时形参对对象的属性做了修改,相当于实参自己修改了属性
System.out.println(date.year + "年" + date.month + "月" + date.day+"日");
//0年0月1日
}
public static void change(MyDate my) {//(MyDate my)是形参,是引用数据类型
my.day++; //修改了my对象的属性day
}
}
2.6 方法的返回值类型(再谈引用数据类型)
方法的返回值类型分为3种情况:
-
void:表示无返回值
-
有返回值
-
8种基本数据类型:方法返回的是数据值的副本
-
引用数据类型:方法返回的是对象的首地址(无论是数组还是普通的类对象)
-
//(1)声明一个MyDate类型,有属性:年,月,日
public class MyDate {
public int year;
public int month;
public int day;
}
public class TestReturnValue {
public static void main(String[] args) {
MyDate date = create(2000,5,1);
System.out.println(date.year + "年" + date.month + "月" + date.day +"日");
//2000年5月1日
}
public static MyDate create(int year, int month, int day){
MyDate my = new MyDate();
my.year = year;
my.month = month;
my.day = day;
return my;
}
}
三、类的成员之二:成员方法(非常重要)
3.1 成员方法的分类
根据有没有static修饰,成员方法可以分为两类:
(1)静态方法:有static修饰,又称为类方法
(2)非静态方法:没有static修饰,又称为实例方法
【修饰符】 class 类名{
【其他修饰符】 static 返回值类型 方法名(【形参列表】){ //静态方法,类方法
【方法体语句;】
}
【其他修饰符】 返回值类型 方法名(【形参列表】){ //非静态方法,实例方法
【方法体语句;】
}
}
3.2 静态方法与非静态方法的区别
1、在其他类中调用这个方法的方式不同
-
静态方法:在其他类中 ,==推荐==使用“类名.静态方法”的方式调用,==不推荐==“对象名.静态方法”的方式调用。
-
非静态方法:在其他类中,只能是通过“对象名.非静态方法”的方式进行调用。
public class Demo {
public static void m1(){
System.out.println("m1是一个静态方法");
}
public void m2(){
System.out.println("m2是一个非静态方法");
}
}
public class TestDemo {
public static void main(String[] args) {
Demo.m1();//静态方法,推荐使用“类名.静态方法”调用
Demo d = new Demo();
d.m2();//非静态方法,只能通过“对象.非静态方法”调用
d.m1();//不推荐,静态方法,通过“对象.静态方法”调用
}
}
2、在方法中使用本类的其他成员的限制不同
-
静态方法:不可以访问本类的其他的非静态成员,包括属性以及其他的非静态方法等。也不可以出现this单词。
-
静态方法中“只能”访问本类的其他静态成员。
-
-
非静态方法:可以直接访问本类的所有成员,包括静态的和非静态的,包括成员变量,也包括成员方法。因为静态的东西是本类以及本类所有对象共享。
public class Account {
//成员变量
public static double rate;//静态变量
public String id;//实例变量,属性
public double balance;//实例变量,属性
/* public void method(){//调用它变的麻烦了,需要new对象
System.out.println("rate =" + rate);
}*/
//成员方法
public static void showRate(){
System.out.println("现在的利率值:" + rate);
// System.out.println("当前" + id + " 的利息:" + balance * rate);//报错
// System.out.println("当前" + this.id + " 的利息:" + this.balance * rate);//报错
//因为balance是对象有关,必须有对象,才有balance的值
//而调用showRate方法时,根本没有 当前对象 的概念
}
public void showInterest(){
//粗糙算 利息 = 余额 * 利率
System.out.println("当前" + id + " 的利息:" + balance * rate);
System.out.println("当前" + this.id + " 的利息:" + this.balance * rate);
//当前对象 this ,这里指的是调用这个方法的对象
}
}
public class TestAccount {
public static void main(String[] args) {
Account.rate = 0.035;
Account.showRate();//此时没有创建Account类的对象
Account a1 = new Account();
a1.id = "111111";
a1.balance = 5000;
a1.showInterest();//先创建了一个Account对象,才调用showInterest()方法
//当前111111 的利息:175.00000000000003
Account a2 = new Account();
a2.id = "22222";
a2.balance = 8000;
a2.showInterest();
//当前22222 的利息:280.0
a1.showRate();
//虽然这么写,编译器仍然处理为 Account.showRate();
//所以,在showRate()根本不可能有当前对象的概念
/* Account a3 = new Account();
a3.method();*/
}
}
3、思考题:一个方法什么时候该声明为静态的,什么时候该声明为非静态的?
原则(非常重要):
-
如果在这个方法的方法体中,涉及到 访问本类的 其他 “非静态”成员,那么这个方法“只能”声明为 非静态的。
-
如果在这个方法的方法体中,==不 涉及== 到访问 本类的 其他 “非静态”成员,那么这个方法既可以声明为静态的,也可以声明为非静态。此时一般都是声明静态的,因为这样的方法,调用更方便。
3.3 成员变量与成员方法的结合
案例一:圆
需求:
-
声明一个代表圆的类Circle,包含属性 radius(半径)
-
声明一个测试类,创建圆对象,求圆的面积和周长
public class Circle {
//成员变量,因为是非静态的,又称为实例变量,俗称属性
public double radius;//半径
/*
因为所有的圆对象,都具有求面积的功能,这是所有圆对象共同的特征。
类的概念:一类具有相同特性的事物的抽象描述。换句话说,这个类中就应该体现这个类的所有对象的共同特征。
*/
public double area(){//求面积
// return Math.PI * this.radius * this.radius;
return Math.PI * radius * radius;
//this代表当前对象,调用这个方法的对象,每一个圆的面积值都是独立,用各自自己的半径值来计算的。this可以省略
}
public double perimeter(){//求周长
// return 2 * Math.PI * this.radius;
return 2 * Math.PI * radius;
}
public String getInfo(){
// return "半径:" + this.radius +",面积:" + this.area() +",周长:" + this.perimeter();
return "半径:" + radius +",面积:" + area() +",周长:" + perimeter();
}
}
public class TestCircle {
public static void main(String[] args) {
//创建Circle的对象
Circle c1 = new Circle();
c1.radius = 2.5;
//Math类中有一个常量PI,代表圆周率
// double a1 = Math.PI * c1.radius * c1.radius;
// double a1 = c1.area();
// double p1 = 2 * Math.PI * c1.radius;
// double p1 = c1.perimeter();
// System.out.println("c1的信息:半径:" + c1.radius +",面积:" + a1 + ",周长:" + p1);
System.out.println("c1的信息:" + c1.getInfo());
System.out.println("================");
//创建Circle的对象
Circle c2 = new Circle();
c2.radius = 3.0;
//Math类中有一个常量PI,代表圆周率
// double a2 = Math.PI * c2.radius * c2.radius;
// double a2 = c2.area();
// double p2 = 2 * Math.PI * c2.radius;
// double p2 = c2.perimeter();
// System.out.println("c2的信息:半径:" + c2.radius +",面积:" + a2 + ",周长:" + p2);
System.out.println("c2的信息:" + c2.getInfo());
}
}
案例二:日期和员工
需求:
(1)声明一个MyDate类型,有属性:年,月,日
(2)声明另一个Employee类型,有属性:姓名(String类型),生日(MyDate类型)
(3)在TestEmployee测试类中的main中,创建两个员工对象,并为他们的姓名和生日赋值,并显示
版本1:请看第2.4小节
//(1)声明一个MyDate类型,有属性:年,月,日
public class MyDate {
public int year;
public int month;
public int day;
}
//(2)声明另一个Employee类型,有属性:姓名(String类型),生日(MyDate类型)
public class Employee {
public String name;
//只要是类,就可以在Java中当成数据类型
public MyDate birthday;
}
public class TestEmployees {
public static void main(String[] args) {
Employee e1 = new Employee();
e1.name = "张三";
e1.birthday = new MyDate();
e1.birthday.year = 1996;
e1.birthday.month = 8;
e1.birthday.day = 9;
System.out.println("e1的信息:姓名:" + e1.name+",生日:" + e1.birthday.year+"年" +e1.birthday.month+"月" +e1.birthday.day+"日");
System.out.println("======================");
Employee e2 = new Employee();
e2.name = "张三";
e2.birthday = new MyDate();
e2.birthday.year = 1994;
e2.birthday.month = 5;
e2.birthday.day = 1;
System.out.println("e2的信息:姓名:" + e2.name+",生日:" + e2.birthday.year+"年" +e2.birthday.month+"月" +e2.birthday.day+"日");
System.out.println("==================");
MyDate today = new MyDate();
today.year = 2024;
today.month = 4;
today.day = 14;
System.out.println("今天的日期:" + today.year +"年" + today.month+"月" + today.day+"日");
}
}
版本2:把重复代码抽取成方法
public class MyDate {
public int year;
public int month;
public int day;
//这个方法的作用 :是为MyDate类型的对象的year, month, day属性赋值的
public void setDate(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
//左边的this此时不可以省略
//this.变量 代表的是当前对象.属性,没有加this.的,在这里是局部变量,形参
}
//这个方法的作用:通过一个MyDate类型的对象,获取该对象的year, month, day属性值
public String getDateInfo(){
// return this.year +"年" + this.month+"月" + this.day+"日";
return year +"年" + month+"月" + day+"日";
}
}
public class Employee {
public String name;
public MyDate birthday;
//这个方法的作用,为一个员工对象,设置 birthday属性值
public void setBirthday(int year, int month, int day){
// this.birthday = new MyDate();
/*this.birthday.year = year;
this.birthday.month = month;
this.birthday.day = day;*/
// this.birthday.setDate(year, month, day);
//this是员工类的对象,e1或e2
birthday = new MyDate();
birthday.setDate(year, month, day);
}
//这个方法的作用:根据一个员工对象,获取这个员工对象的姓名和生日属性值
public String getInfo(){
// return "姓名:" + this.name+",生日:" + this.birthday.year+"年" +this.birthday.month+"月" +this.birthday.day+"日";
// return "姓名:" + name+",生日:" + birthday.year+"年" + birthday.month +"月" + birthday.day+"日";
return "姓名:" + name+",生日:" + birthday.getDateInfo();
}
}
public class Employee {
public String name;
public MyDate birthday;
//这个方法的作用,为一个员工对象,设置 birthday属性值
public void setBirthday(int year, int month, int day){
// this.birthday = new MyDate();
/*this.birthday.year = year;
this.birthday.month = month;
this.birthday.day = day;*/
// this.birthday.setDate(year, month, day);
//this是员工类的对象,e1或e2
birthday = new MyDate();
birthday.setDate(year, month, day);
}
//这个方法的作用:根据一个员工对象,获取这个员工对象的姓名和生日属性值
public String getInfo(){
// return "姓名:" + this.name+",生日:" + this.birthday.year+"年" +this.birthday.month+"月" +this.birthday.day+"日";
// return "姓名:" + name+",生日:" + birthday.year+"年" + birthday.month +"月" + birthday.day+"日";
return "姓名:" + name+",生日:" + birthday.getDateInfo();
}
}
public class TestEmployees2 {
public static void main(String[] args) {
Employee e1 = new Employee();
e1.name = "张三";
e1.setBirthday(1996,8,9);
System.out.println("e1的信息:" + e1.getInfo());
System.out.println("======================");
Employee e2 = new Employee();
e2.name = "张三";
e2.setBirthday(1994,5,1);
System.out.println("e2的信息" +e2.getInfo());
System.out.println("==================");
MyDate today = new MyDate();
today.setDate(2024,4,14);
System.out.println("今天的日期:" + today.getDateInfo());
}
}
四、对象数组
这里只讨论一维对象数组,关于二维对象数组,等熟练使用一维对象数组之后,之后根据现有知识自然可以做到举一反三。
4.1 对象数组的声明和初始化
1、声明
元素的类型[] 数组名; //此时数组的元素是一个类的对象,所以元素类型此时是一个类的类型
类类型[] 数组名; //声明了一个对象数组
例如:存储一组员工对象
员工类:Employee
员工数组:Employee[] employees;
例如:存储一组圆对象
圆类:Circle
圆数组:Circle[] circles;
例如:存储一组矩形对象
矩形类:Rectangle
矩形数组:Rectangle[] all;
2、初始化
(1)静态初始化
//元素的类型[] 数组名 = {元素值1, 元素值2, ......};
类类型[] 数组名 = {对象1, 对象2, ......};
(2)动态初始化
//元素的类型[] 数组名 = new 元素的类型[长度];
类类型[] 数组名 = new 类类型[长度];
数组名[下标] = new 类类名(); //创建元素对象
(3)示例代码
public class Rectangle {//矩形
//成员变量,属性
public double length;
public double width;
//成员方法
public double area(){//求面积的方法
return length * width;
}
public double perimeter(){//求周长
return 2 * (length + width);
}
public String getInfo(){
return "长:" + length +",宽:" + width +",面积:" + area() +",周长:" + perimeter();
}
}
静态初始化示例代码:
public class TestRectangle {
public static void main(String[] args) {
/*
例如:存储一组矩形对象
矩形类:Rectangle
矩形数组:Rectangle[] all;
*/
// Rectangle[] all = new Rectangle[] {new Rectangle(), new Rectangle(), new Rectangle()};
Rectangle[] all = {new Rectangle(), new Rectangle(), new Rectangle()};
//上面数组中存储了3个矩形对象
/* for (int i = 0; i < all.length; i++) {
System.out.println(all[i]);
}*/
/*
array.Rectangle@4eec7777
array.Rectangle@3b07d329
array.Rectangle@41629346
*/
//all[下标]是Rectangle类型的对象,
// 就可以通过 all[i]. 出 Rectangle里面定义的成员(包括属性和方法)
all[0].length = 8;
all[0].width = 5;
all[1].length = 6;
all[1].width = 3;
all[2].length = 9;
all[2].width = 4;
for (int i = 0; i < all.length; i++) {
//all[i]是Rectangle类型的对象,就可以通过 all[i]. 出 Rectangle里面定义的成员(包括属性和方法)
System.out.println(all[i].getInfo());
}
}
}
动态初始化示例代码:
public class TestRectangle2 {
public static void main(String[] args) {
Rectangle[] all = new Rectangle[3];
all[0] = new Rectangle();
all[0].length = 8;
all[0].width = 5;
all[1] = new Rectangle();
all[1].length = 6;
all[1].width = 3;
all[2] = new Rectangle();
all[2].length = 9;
all[2].width = 4;
for (int i = 0; i < all.length; i++) {
//all[i]是Rectangle类型的对象,就可以通过 all[i]. 出 Rectangle里面定义的成员(包括属性和方法)
System.out.println(all[i].getInfo());
}
}
}
3、遍历
for(int i=0; i<数组名.length; i++){
数组名[i]代表一个元素,只不过此时这个元素是一个对象。既然是一个对象,就可以 数组名[i].属性 或 数组名[i].方法。
}
4.2 对象数组的内存分析
4.3 对象数组的排序(延伸)
public class TestRectangle3 {
public static void main(String[] args) {
Rectangle[] all = new Rectangle[3];
all[0] = new Rectangle();
all[0].length = 8;
all[0].width = 5;
all[1] = new Rectangle();
all[1].length = 6;
all[1].width = 3;
all[2] = new Rectangle();
all[2].length = 9;
all[2].width = 4;
//排序之前:
System.out.println("排序之前:");
for (int i = 0; i < all.length; i++) {
System.out.println(all[i].getInfo());
}
//按照面积排序;从小到大排序
//冒泡排序
for (int i = 0; i < all.length - 1; i++) {//循环轮数
//内循环循环次数
for(int j=0; j<all.length-1-i; j++){
// if(all[j] > all[j+1]){//报错,因为all[j]和all[j+1]存储的是对象的首地址,地址是无法比较大小的
if(all[j].area() > all[j+1].area()){//需要比较对象的属性值,或面积值等
//交换all[j]和all[j]里面的对象
Rectangle temp = all[j];
all[j] = all[j+1];
all[j+1] = temp;
}
}
}
//排序后的结果输出
System.out.println("按照面积从小到大排序之后:");
for (int i = 0; i < all.length; i++) {
System.out.println(all[i].getInfo());
}
}
}