Java面向对象基础
类与对象
引入
单独变量解决:不利于数据的管理(把一只猫的信息拆解了,当信息量庞大时更加的麻烦)
//第一只猫信息
string cat1Name = "小白"
int cat1Age = 3;
string cat1Color = "白色"
//第二只猫信息
string cat1Name = "小红"
int cat1Age = 3;
string cat1Color = "红色"
n只猫.......
数组解决:
(1)数据类型体现不出来
(2)只能通过下标获取信息,造成变量名和内容的关系不对应
(3)猫的行为体现不出来(方法):如猫喜欢吃鱼这一行为体现不出来
String cat1[] = {"小白","3","白色"};
String cat1[] = {"小小红","3","红色"}
引入类与对象的原因:现有的技术不能完美的解决新的要求
类与对象
类分为两部分:变量的声明(属性)和方法的定义
【类的定义格式】
class 类名 {
类体内容
}
属性概念
// 创建一个猫类——自定义的数据类型
Class Cat {
// 属性/成员变量
String name; // 姓名
int age; // 年龄
String color //颜色
String[] master;// 属性可以是基本数据类型,也可以是是引用类型(数组,对象)
// 行为
}
1.从概念上或叫法上看:成员变量 = 属性 = field(字段)
2.属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象,数组)
属性注意细节
-
属性的定义同语法同变量,示例:访问修饰符 属性类型 属性名;
访问修饰符:public、private、protected、默认——控制属性的访问范围
-
属性的定义类型可以是任意类型,包含基本类型和引用类型
-
如果属性不赋值,有默认值,规则和数组一致:
boolean初始值:false
int初始值:0
short初始值:0
float初始值:0.0
double初始值:0.0
char初始值: \u0000
long初始值:0
byte初始值:0
Value初始值:null【参考代码】
public static void main(String[] args) { Person p1 = new Person(); System.out.println(p1.name);//null System.out.println(p1.age);//0 System.out.println(p1.sal);//0.0 System.out.println(p1.isPass);//false } static class Person { String name; int age; double sal; boolean isPass; .... }
对象
创建对象
1. 先声明在创建:
Cat cat ; // 声明对象
cat = new Cat(); // 创建
2. 直接创建
Cat cat = new Cat();
访问属性
基本语法:
对象名.属性名;
如:cat.name;
用类建立多个属性(自定义数据类型),通过实例化对象管理多个属性
public static void main(String[] args) {
// 创建Person对象
// p1 是对象名(对象引用)
// new Person() 创建的对象空间(数据) 才是真正的对象
Person p1 = new Person();
}
static class Person {
String name;
int age;
double sal;
boolean isPass;
....
}
p1 是对象名(对象引用) new Person() 创建的对象空间(数据) 才是真正的对象 就如:小明是人名 它 指向 人(本质——对象)
对象的内存与分配机制
【看个例子】
Person p1 = new Person();
p1.age = 10;
p1.name = "小明";
Person p2 = p1; // 把p1赋给p2,让p2指向p1
System.out.println(p2.age);
问p2.age = ? 并画出内存图
很显然最后p2.age = 10
Java内存结构的分析:
- 栈:一般存储基本数据类型(局部变量)、调用方法时开辟新栈
- 堆:存放引用类型(对象,如Cat cat 、数组等)
- 方法区:常量池(常量,比如字符串),类加载信息
对象创建过程总结:
- 先加载类(Person类)信息(属性和方法信息,只会加载一次)
- 在堆中分配空间,进行默认初始化(看规则)
- 把地址赋给p,p就指向对象
- 进行指定初始化(如:p1.age = 10;)
【再看个例子】
类与对象的区别及联系
- 类是抽象的,概念的,代表一类事物,比如猫类、人类......,即 它是数据类型
- 对象是具体的,实际的,代表一个具体事物,即 是实例
- 类是对象的模板,对象是类的一个个体,对应一个实例
成员方法
在某些情况下我们需要定义成员方法(简称方法),就如Person类中出了age、name等属性外,我们还想给人加一些行为,例如:说话、跑步、做计算等等行为,这时就要用成员方法才能完成。
成员方法定义
【语法格式】
权限修饰符 返回值类型 方法名(形参列表) {
// 方法体
// 返回值
}
1. 形参列表:表示成员方法输入,getSum(int num1 , int num2)
2. 返回数据类型:表示成员方法输出,void表示没有返回值
3. 方法体:表示为实现某一功能的代码块
4. return语句不是必须的
【例子】
- 添加speak成员方法,输出我是一个好人
- 添加cal01成员方法,计算1+...1000结果
- 添加cal01成员方法,计算1+...n结果
- 添加getSum成员方法,计算两个数的和
public class detail {
public static void main(String[] args) {
Person p1 = new Person();
p1.speak();
int ans = p1.cal01();
System.out.println(ans);
int ans2 = p1.cal02(1000);
System.out.println(ans2);
int ans3 = p1.getSum(5, 5);
System.out.println(ans3);
}
}
class Person {
String name;
int age;
public void speak() { // public:该方法是公开的
System.out.println("我是一个好人");
}
int cal01() {
int sum = 0;
for (int i = 1; i <= 1000 ; i++) {
sum += i;
}
return sum;
}
int cal02(int n){
int sum = 0;
for (int i = 1; i <= n ; i++) {
sum += i;
}
return sum;
}
int getSum(int a, int b){
return a+b;
}
}
方法的调用机制
方法调用小结:
- 当程序执行到方法时,就会开辟一个独立的空间(栈空间)
- 当方法执行完毕,或者执行到return语句时,就会返回
- 返回到调用方法的地方
- 返回后,继续执行方法后的代码
- 当main栈执行完毕后,整个程序也就退出了
方法使用细节
【返回类型】
-
一个方法最多有一个返回值
【如何返回多个结果】——返回数组
public class detail { public static void main(String[] args) { AA a = new AA(); int[] res = a.getSumAndSub(3,2); System.out.println("和为"+ res[0]); System.out.println("差为"+ res[1]); }}class AA { public int[] getSumAndSub(int num1, int num2){ int[] resArr = new int[2]; resArr[0] = num1 + num2; resArr[1] = num1 - num2; return resArr; }}
-
返回类型可以是任意类型,包括基本类型或引用类型(数组,对象)
-
如果方法要求有返回数据类型,则方法体中最后的执行语句必须为
return 值;
而且要求返回值类型必须和return的值的类型一致或兼容 -
如果方法是void,则方法体中可以没有return语句,或者写成
return ;
-
开发规范:方法名遵循驼峰命名法,getSum
【形参列表】
- 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如getSum(int num1 , int num2)
- 参数类型可以是任意类型,包含基本类型或引用类型,比如printArr(int[] [] map)
- 调用带参数的方法时,一定对应着参数列表传入相同的类型或者兼容类型的参数
- 方法定义时的参数称为形式参数,即形参;方法调用时的传入参数为实际参数,即实参。形参和实参的类型要一致或兼容、个数顺序必须相同。
【方法调用细节】
-
同一个类中的方法调用:可以直接调用
演示:A类 sayOk()调用print()
public class detail { public static void main(String[] args) { A a = new A(); a.sayOk(); }}class A { // 同一个类中的方法调用:直接调用即可 public void print(int n){ System.out.println("print()方法被调用 n=" + n); } public void sayOk(){ // sayOk()直接调用print() print(10); System.out.println("继续执行sayOk()方法"); }}
-
跨类中的方法A类调用B类方法:不能直接调用,需要通过对象名调用
public class detail { public static void main(String[] args) { A a = new A(); a.mi(); } } class A { // 跨类调用方法 public void mi(){ // 创建B类对象,然后用对象调用B类方法即可 System.out.println("mi()方法被调用"); B b = new B(); b.hi(); System.out.println("mi()方法 继续执行"); } } class B { public void hi(){ System.out.println("B类中的 hi()被调用"); } }
-
跨类的调用和方法的访问修饰符有关!
方法传参机制
基本数据类型传参机制:
【分析输出结果是什么】
public class MethodParameter01 {
public static void main(String[] args) {
int a = 10;
int b = 20;
// 创建AA对象
AA obj = new AA();
obj.swap(a,b); // 调用swap()
System.out.println("a=" + a + " b=" + b); //?
}
}
class AA {
public void swap(int a, int b){
System.out.println("\na和b交换前的值\na=" + a + "\tb=" + b);
// 完成 a 和 b 的交换
int temp = a;
a = b;
b = temp;
System.out.println("\na和b交换后的值\na=" + a + "\tb=" + b);
}
}
输出结果:
a和b交换前的值
a=10 b=20a和b交换后的值
a=20 b=10
a=10 b=20
【结论】
基本数据类型,传递的是值(值拷贝),形参的改变不影响实参!
引用类型传参机制:
B类中编写一个test100方法,可以接收一个数组,在方法中修改该数组,看看原来的数组是否发生变化?
public class MethodParameter01 { public static void main(String[] args) { B b = new B(); int[] arr = {1,2,3}; b.test100(arr); // 调用方法 // 遍历数组 System.out.println("\nmain方法里的 arr数组"); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } }}// B类中编写一个test100方法//可以接收一个数组,在方法中修改该数组,看看原来的数组是否发生变化? class B { public void test100(int[] arr){ arr[0] = 100; // 修改元素 // 遍历数组 System.out.println("test100的 arr数组"); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } }}
输出结果:
test100的 arr数组
100 2 3
main方法里的 arr数组
100 2 3
【再看一个案例】
B类中编写一个test200()方法,可以接收一个Person(age,sal)对象,在方法中修改对象属性,看看原来对象是否有变化。
public class MethodParameter02 {
public static void main(String[] args) {
B b = new B();
// 测试
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
System.out.println("main里的 p.age=" + p.age); // 2000
}
}
class Person {
String name ;
int age ;
}
class B {
public void test200(Person p){
p.age = 2000; // 修改对象属性
}
}
p=null和p = new Person()会对原来的对象有影响吗?
public class MethodParameter01 {
public static void main(String[] args) {
B b = new B();
// 测试
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
// 测试 : 如果test200 执行的是 p = null 下面的输出结果是?
System.out.println("main里的 p.age=" + p.age); // 10
}
}
class Person {
String name ;
int age ;
}
class B {
public void test200(Person p){
// p.age = 2000; // 修改对象属性
// 思考:
// p = null;
p = new Person();
p.name = "tom";
p.age = 99;
}
}
p=null情况分析:
p = new Person()....情况分析:
注:被闲置的对象最终会被当成垃圾被回收
总结:p在哪里调用它就造哪个栈里的p
【结论】
引用类型传递的是地址(传递也是值,但值是地址),可以通过形参影响实参!
方法重载(Overload)
方法重载:一个类中可以有多个方法具有相同的名字,但这些方法的参数必须不同,及或者是参数的个数不同,或者是参数的类型不同。(方法名必须相同,但要求形参列表不一致)
重载的好处:
- 减轻了起名的麻烦
- 减轻了记名的麻烦
使用细节:
- 方法名:必须相同
- 形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求)
- 返回类型:无要求
【示例】
下面的Area类中,getArea方法是一个重载方法
class Area { float getArae(float r){ return 3.14f * r * r; } double getArae(float x, int y){ return x * y; } float getArae(int x, float y){ return x * y; } double getArae(float x, float y, float y){ return (x*x + y*y + z*z) * 2.0; }}
下面的案例不是方法的重载而是方法的重复定义!——参数名无要求,同理返回类型也无要求
double getArae(int x, int y){ return x * y; } double getArae(int a1, int a2){ return x * y; }
可变参数
Java中允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法
【基本语法】
访问修饰符 返回类型 方法名(数据类型...形参名){
}
【示例】
可计算两个数的和 , 3个数的和 , 4个数的和....
public class VarParameter01 {
public static void main(String[] args) {
Hspmethod m = new Hspmethod();
int ans = m.sum(1,2,3,4);
System.out.println(ans);
}
}
class Hspmethod {
// 可计算两个数的和 , 3个数的和 , 4个数的和....
// 可以使用方法重载
// public int sum(int n1, int n2){
// return n1 + n2;
// }
// public int sum(int n1, int n2, int n3){
// return n1 + n2 + n3;
// }
// public int sum(int n1, int n2, int n3, int n4){
// return n1 + n2 + n3 + n4;
// }
//.....
// 上面的三个方法名相同,功能相同,但参数不同 -> 可使用可变参数优化
//解读:
// 1、 int ... 表示接受的是可变参数 , 类型是 int ,即可接收多个int(0~多)
// 2、 使用可变参数时,可以当作数组来使用 , 即 nums 可以当作数组
public int sum(int... nums){
int res = 0;
for (int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
}
【可变参数细节】
1、 可变参数的实参可以为0个或任意多个
2、 可变参数的实参可以为数组
3、可变参数的本质就是数组
4、可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
5、一个形参列表中只能出现一个可变参数
public class VarParameter02 {
public static void main(String[] args) {
Hspmethod m = new Hspmethod();
int[] arr = {1,2,3};
m.f1(arr);
}
}
class Hspmethod {
// 可变参数的实参可以为数组
public void f1(int... nums){
System.out.println(nums.length); // 3
}
// 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
public void f2(String str, int... nums){
}
// 一个形参列表中只能出现一个可变参数 下面的写法是错误的!
public void f3(double... nums, int... nums){
}
}
作用域
注意事项和使用细节:
-
属性(全局变量)和局部变量可以重名,访问时遵循就近原则。
-
在同一个作用域中,比如在同一个成员方法中,两个局部变量不能重名。
-
属性生命周期较长,伴随着对象的创建而创建,伴随着对的销毁而销毁。局部变量,生命周期较短,伴随着代码块的执行而创建,伴随着代码块的结束而销毁。即在一次方法的调用过程中。
public class VarParameter01 {
public static void main(String[] args) {
Person01 p1 = new Person01();
/*
属性生命周期较长,伴随着对象的创建而创建,伴随着对的销毁而销毁。
局部变量,生命周期较短,伴随着代码块的执行而创建,伴随着代码块的结束而销毁。
*/
p1.say();// 当执行say()方法时 say()方法的局部变量比如name就会被创建,当name执行完毕后
// name局部变量就销毁,但属性(全局变量)仍然可以使用
}
}
class Person01 {
String name = "tom";
public void say(){
// 细节:属性和局部变量可以同名,访问时遵循就近原则
String name = "King";
System.out.println("say() name=" + name);
}
public void hi(){
String address = "北京";
// String address = "上海"; // 错误:重复定义变量
String name = "lwt";
}
}
-
作用域范围不同
全局变量/属性:可以在本类中使用,也可以在其它类中使用(通过对象调用)
public class VarParameter01 { public static void main(String[] args) { Person01 p1 = new Person01(); T t1 = new T(); t1.test(); // 第一种跨类访问对象的属性方式 t1.test2(p1); // 第二种跨类访问对象的属性方式 } } class Person01 { String name = "tom"; public void say(){ // 细节:属性和局部变量可以同名,访问时遵循就近原则 String name = "King"; System.out.println("say() name=" + name); } } class T{ // 使用Person01类中的“tom” public void test(){ Person01 p1 = new Person01(); System.out.println(p1.name);// tom } public void test2(Person01 p){ System.out.println(p.name);// tom } }
局部变量:只能在本类中对应的方法中使用
-
修饰符不同
全局变量/属性可以加属性
局部变量不可以加属性
构造方法/构造器(construcor)
构造方法是一种特殊的成员方法,它的主要作用是完成新对象的初始化,节省代码。当创建对象的时候,其实就是在调用构造器。
idea快捷键:alt + insert
【语法格式】
修饰符 类名(传参){
}
【特点】
- 方法名必须和类名相同
- 没有返回值,也不能写void
- 在创建对象时,系统会自动调用该类的构造方法完成对对象的初始化。
构造器/构造方法使用细节
-
一个类可以定义多个不同的构造器,即构造方法的重载
public class Details { public static void main(String[] args) { Person p = new Person("tom",18);// 第一个构造器 Person p2 = new Person("jack");// 第二个构造器 } } class Person { String name; int age; // 第一个构造器 public Person(String pName, int pAge){ name = pName; age = pAge; } // 第二个构造器(构造器也是一种方法,当然可以重载) public Person(String pName){ name = pName; } }
-
构造器名必须和类名相同
-
没有返回值,也不能写void
-
构造器是完成对象的初始化,并不是创建对象
-
在创建对象时,系统会自动调用该类的构造方法。
-
如果我们没有定义构造器,系统会自动给类生成一个默认无参构造器(默认构造器),比如Car () {}
public class Car { //成员变量----属性 String color; int speed; int seat = 5; // Java会自动赠送每一个类一个无参的构造方法 //在创建对象的时候,自己调用方法 public Car() { // 默认构造器 System.out.println("你好,我是构造方法"); } public static void main(String[] args) { Car c1 = new Car();//默认调用的是构造方法------你好,我是构造方法 } }
-
一旦定义了自己的构造器,默认的构造器就被覆盖了,就不能再使用默认的无参构造器,除非显式定义一下,即:Person(){} (这点很重要)
【对象创建的流程分析】
class Person {
int age = 90;
String name;
// 第一个构造器
public Person(String pName, int pAge){
age = pAge; // 给属性赋值
name = pName;
}
}
Person p = new Person("小明", 18);
- 加载 Person类信息(Person.class),只会加载一次
- 在堆中分配地址空间(地址)
- 完成对象初始化【3.1、 默认初始化 age=0 name=null 3.2、显式初始化 age = 90,name = null,3.3、构造器的初始化 age = 20 name = "小明" 】
- 在对象堆中的地址,返回给p(p是对象名,也可以理解成对象的引用)——完成最终的初始化后才将地址赋给p,因此构造器是用来初始对象而不是创建对象(对象早已在堆中创建了)
this
什么是this:
Java虚拟机会给每个对象分配this,代表当前对象。
Java虚拟机会给每个对象分配this,代表当前对象,因此this与创建的对象的地址是一样的!
public class Details {
public static void main(String[] args) {
Person p = new Person("tom",18);
System.out.println("当前对象的hashcode值:" + p.hashCode());
}
}
class Person {
String name;
int age;
public Person(String name, int age){
this.name = name;
this.age = age;
System.out.println("this所代表的hashcode值:" + this.hashCode());
}
}
输出结果:
this所代表的hashcode值:460141958
当前对象的hashcode值:460141958
小结:简单地说,哪个对象调用,this就代表哪个对象。
【this关键字使用细节】
-
this关键字可以用来访问本类的属性、方法、构造器
package Constructor; public class Details { public static void main(String[] args) { T t = new T(); t.f3(); } } class T { String name = "tom"; int num = 100; // this关键字可以用来访问本类的属性 public void f3() { //传统方式 System.out.println("name= " + name +" " + "num= " + num); //也可以用this System.out.println("name= " + this.name + " " + "num= " + this.num); } }
-
this用于区分当前类的属性(全局变量)和局部变量
【主要是看有无局部变量】
package Constructor; public class Details { public static void main(String[] args) { T t = new T(); t.f3(); } } class T { String name = "tom"; int num = 100; // this用于区分当前类的属性(全局变量)和局部变量 public void f3() { String name = "jack"; int num = 999; //传统方式:先根据就近原则(作用域)来找,看方法体中有无局部变量 System.out.println("name= " + name +" " + "num= " + num);// name= jack num= 999 //this:明确访问当前对象的属性(全局变量) System.out.println("name= " + this.name + " " + "num= " + this.num);// name= tom num= 100 } }
-
访问成员方法的语法:this.方法名(参数列表)
public class ThisDetails { public static void main(String[] args) { T t = new T(); t.f2(); } } class T { public void f1(){ System.out.println("f1()方法..."); } public void f2(){ System.out.println("f2()方法..."); // 调用本类f1 // 第一种方式 f1(); // 第二种方式 this.f1(); } } 输出结果: f2()方法... f1()方法... f1()方法...
-
访问构造器的语法:this(参数列表);注意只能在构造器中使用(只能在构造器中去访问另外一个构造器,必须放在第一条语句)
public class Details { public static void main(String[] args) { T t = new T(); } } class T { /* 注意: 访问构造器语法:this(参数列表);必须放置在第一条语句 */ public T(){ //在这里去访问 T(String name, int age)构造器 this("tom",18); System.out.println("T() 构造器"); } public T(String name, int age){ System.out.println("T(String name, int age) 构造器"); } }
-
this不能在类定义的外部使用,只能在类定义的方法中使用
学习内容源自视频:b站韩顺平老师说Java