面向对象基础部分已整理完,下一部分中级以及小项目开发
🔴🟢🟡
文章目录
类与对象
类是对象的模板,对象是类的一个个体
类是抽象的,代表一类事物,比如人类,它是数据类型
对象是具体的,实际的,代表一个具体事物, 是实例
任何类都有成员方法
和成员属性
属性/成员变量/字段
从概念或叫法上看: 成员变量 = 属性 = field(字段)
- 属性的定义语法同变量
- 属性如果不赋值,有默认值,规则和数组一致。具体说: int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null
- 属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)
如何创建对象
1 先声明再创建
Cat cat ; //声明对象 cat
cat = new Cat(); //创建
2 直接创建
Cat cat = new Cat();
如何访问属性
基本语法: 对象名.属性名;
//对象名.属性名;
cat.name ;
cat.color;
类和对象的内存分配机制(重要)
Java 内存的结构分析
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象
- 方法区:常量池(常量,字符串),类加载信息
- 先加载类信息(属性和方法),只会加载一次信息
- 在堆中分配空间,进行默认初始化
- 把地址给对象引用或对象名
- 进行指定初始化
Person jack = new Person();
jack.age = 3;
Person tom;//野指针
tom = jack;//把对象jack的地址给tom,相当于一个对象,两个引用
System.out.println(tom.age);
System.out.println(jack.hashCode());//22307196
System.out.println(tom.hashCode());//22307196
做个小练习:定义Person类,并实例化类
/**
* @Author: liu sen
* @Version: 1.0
* @Date: 2021/09/01/10:26
*/
public class demo01 {
public static void main(String[] args) {
//创建一个Person对象,jack为对象引用,或叫对象名
Person jack = new Person();
System.out.println(jack.name);//输出对象jack的名字,String类型默认为 null
System.out.println(jack.age);//输出对象jack的年龄,int类型默认为0
System.out.println(jack.gender);//输出对象jack的性别,char类型默认空
}
}
//定义一个人类
class Person{
String name;//姓名
int age;//年龄
char gender;//性别
}
OK,Perfect
方法介绍
方法是语句的集合,它们在一起执行一个功能
- 方法是解决一类问题的步骤的有序组合
- 方法包含于类或对象中
- 方法在程序中被创建,在其他地方被引用
设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合,设计方法的时候,最好保持方法的原子性,一个方法只完成1个功能,这样利于我们后期的扩展。
方法的好处
1 方法可以解决代码冗余的问题,提高代码复用和维护性
2 可以将实现的细节封装起来,供其他用户调用即可
方法的定义
访问修饰符 返回数据类型 方法名(形参列表..) {//方法体
语句;
return 返回值;
}
注意:
形参列表:表示成员方法输入
返回数据类型:表示成员方法输出, void 表示没有返回值
return 语句不是必须的
方法的调用机制
- 当程序执行到方法时,就会开辟一个独立的空间(栈空间)
- 当方法执行完毕时,或者执行到return语句时,就会返回到调用的地方
- 返回后继续执行下面的代码,直到main栈也结束,被回收,即程序结束
方法细节
1,访问修饰符
作用是控制方法使用的范围
- public
- protected
- 默认
- private
2,返回数据类型
一个方法最多有一个返回值,想要返回多个结果,可以借助数组,对象……来实现
public class demo09 {
public static void main(String[] args) {
AA aa = new AA();
int[] a = aa.getTwo(1,2);
System.out.println(a[0]);
System.out.println(a[1]);
}
}
class AA{
public int[] getTwo(int n1,int n2){
int[] arr = new int[2];
arr[0] = n1 + n2;
arr[1] = n1 - n2;
return arr;
}
}
- 返回类型可以是任意类型,可以是基本类型和引用类型(数组,对象)
- 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值;而且要求返回值类型必须和 return 的值类型一致或兼容
- 如果方法是 void ,则方法体中可以没有 return 语句,或者只写 return;
- 方法名,遵循驼峰命名法,最好见名之意,实际开发中,方法都是为了实现某些功能,按照规范来,最好见名之意
3,形参列表
- 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开
- 参数类型可以是任意类型,包含基本类型和引用类型
- 调用带参数的方法时,一定要对应着参数列表传入相同类型或兼容类型的参数,兼容类型是指比如int型可以自动转型为double,这就是兼容问题
- 方法定义时的参数,成为形式参数,方法在实际的调用时,称为实际参数,简称实参,实参和形参的类型要一致或兼容,个数,顺序必须一致
4,方法体
完成具体功能的语句集或语句,可以为输入,输出,变量,运算,分支,循环,方法调用,但是方法里不能再定义方法,即:方法不能嵌套定义
5,方法调用
- 同一个类的方法调用:直接调用即可
- 跨类中的方法,A类调用B类方法:需要通过对象名调用。比如:对象名.方法名(参数)
- 跨类的方法调用还与调用方法的访问修饰符相关
方法传参机制
基本数据类型的传参机制
(基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参! )
public class demo12 {
public static void main(String[] args) {
int a = 10;
int b = 20;
AA aa = new AA();
aa.swap(a,b);
System.out.println("a=" +a + "\t b=" +b);
}
}
class AA{
public void swap(int a,int b){
System.out.println("a=" +a + "\t b=" +b);
int temp = a;
a = b;
b = temp;
System.out.println("a=" +a + "\t b=" +b);
}
}
a=10 b=20
a=20 b=10
a=10 b=20
思考,为啥输出这?
首先,main栈里有两个整型a和b,分别是10,20,然后实例化了一个类AA,生成了一个对象在堆中,叫aa,通过aa点来调用了类中的方法,swap,这是就会开辟一个新栈,假设就叫做swap栈,它与main栈是两个独立的空间,执行方法,交换了两个数,这时交换的是方法中的两个数ab,而并非main栈中的ab,方法执行完成,swap栈就被销毁,回收了,夹着执行输出语句,输出的ab是main栈中的,所以是10 和20 ,main栈是栈底,main栈执行完即整个程序结束
引用数据类型的传参机制
(引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!)
public class demo13 {
public static void main(String[] args) {
B b = new B();
int arr[] = {1,2,3};
b.test(arr);
System.out.println("===========");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]+"\t");
}
}
}
class B{
public void test(int[] arr){
arr[0] = 200;
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]+"\t");
}
}
}
结果:
200
2
3
===========
200
2
3
分析:
main栈创建了对象b,在堆中,然后创建了一个引用类型的数组,它也会指向堆空间,大小为3, b.test(arr);调用B方法传递了一个数组进去,产生新栈,假设就叫B栈,哈哈,引用类型地值拷贝,他俩就指向同一个数组了,这时方法中修改了数组的第一个元素,就是在相同的地址下,修改数组,调用结束,回收B栈,回到主栈,执行for循环,遍历数组,所以最后都输出200,2,3
递归心法从入门到入土
递归就是方法自己调用自己,
每次递归时传入不同的变量,
递归有助于编程者解决自己,
哈哈,开玩笑,对复杂问题,
同时还可以使代码变的简洁。
递归的重要原则
- 执行一个方法时,就会创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会互相影响的
- 如果方法中使用的是引用类型,(比如数组),就会共享该数据类型的数据
- 递归必须向退出递归的条件逼近,否则就会出现无限递归,死龟
- 方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时方法执行完毕或返回时,该方法就执行完毕
递归的运行机制
递归计算阶乘
使用递归计算阶乘
public class jie {
public static void main(String[] args) {
jie jie = new jie();
System.out.println(jie.f(3));
}
public int f(int n){
if(n == 1){
return 1;
}else {
return n*f(n-1);
}
}
}
递归求出斐波那契数列
1,1,2,3,5,8,13……给你一个整数n,求出它的值是多少
思路分析:
当n = 1 1
当n = 2 1
当n >= 3 前两个数的和
public class feiBo {
public static void main(String[] args) {
T t = new T();
int n = t.fei(-1);
if(n != -1){
System.out.println(n);
}
}
}
class T{
public int fei(int n){
if (n >= 1) {
if(n == 1 || n == 2){
return 1;
}else{
return fei(n-1) + fei(n-2);
}
}else {
System.out.println("输入有误");
return -1;
}
}
}
递归猴子吃桃问题
一堆桃子,猴子第一天吃了其中的一半,并再多吃一个,以后每天猴子都吃了其中的一半,然后再多吃一个,当到第10天时,想再吃时,发现只有1个桃子,问题:这个猴子是谁?
看啥看,反正就是你,哈哈,批一下,问题:最初一共有多少桃子?
思路:逆推
10 1
9 (1+1)*2=4
8 (((1+1)*2)+1)*2=10
规律:前一天的桃子 = (后一天的桃子+1)*2
public class houZi {
public static void main(String[] args) {
int day = 1;
houZi houZi = new houZi();
if(day != -1){
System.out.println(houZi.peach(day));
}
}
public int peach(int n){
if(n == 10){
return 1;
}else if(n >= 1 && n <= 9){
return (peach(n+1)+1)*2;
}else {
System.out.println("输入错误");
return -1;
}
}
}
递归解决迷宫问题
public class miGong {
public static void main(String[] args) {
//思路
//1.先创建迷宫,用二维数组表示 int[][] map = new int[8][7];
//2.规定 map:0 表示可以走,1表示墙不能走
int[][] map = new int[8][7];
for (int i = 0;i < 7;i++){
map[0][i] = 1;
map[7][i] = 1;
}
for (int i = 0;i < 8;i++){
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
//打印
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j]+" ");
}
System.out.println();
}
way f = new way();
f.findWay(map,1,1);
System.out.println("=====找路=====");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j]+" ");
}
System.out.println();
}
}
}
class way{
//使用递归回溯的思想来解决
public boolean findWay(int[][] map,int i,int j){
if(map[6][5] == 2){
return true;
}else{
if(map[i][j] == 0){
//假定可以走通
map[i][j] = 2;
//下-右-上-左
if(findWay(map,i+1,j)){//下
return true;
}else if(findWay(map,i,j+1)){//右
return true;
}else if(findWay(map,i-1,j)){//上
return true;
}else if(findWay(map,i,j-1)){//左
return true;
}else {
map[i][j] = 3;
return false;
}
}else {
return false;
}
}
}
}
方法重载(OverLoad)
Java 中允许同一个类,有多个同名方法的存在,但要求形参列表不一致
好处:
- 减轻了起名的麻烦
- 减轻了记名的麻烦
public class demo01 {
public static void main(String[] args) {
System.out.println(1);
System.out.println("asd");
System.out.println('a');
}
}
一个名字,可以输出数字,字符,字符串,这就是所谓的方法的重载
源码:
重载细节
- 方法名:必须相同
- 参数列表:必须相同(参数类型和个数或顺序,至少有一样不同,参数名无要求)
- 返回类型:无要求
可变参数
java允许将同一个类中多个同名功能相同但参数个数不同的方法,封装成一个方法,就可以通过可变参数实现
基本语法
访问修饰符 返回类型 方法名(数据类型… 参数名){
}
快速入门
public class demo04 {
public static void main(String[] args) {
demo04 demo04 = new demo04();
System.out.println(demo04.sum(1,2,3,4,5,6,7,8,9,10));
}
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;
}
//上面三个方法的名称相同,功能相同,参数个数不同----》使用可变参数优化
//int...表示接受的是可变参数,类型是int,即可以接受多个int
//使用时可以把 nums 视为个数组来使用
//遍历nums,求和即可
public int sum(int... nums){
int res = 0;
for (int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
}
注意事项:
- 可变参数的实参可以是任意多个或0个
- 可变参数的实参可以为数组
- 可变参数的本质就是数组
public class demo05 {
public static void main(String[] args) {
int arr[] = {1,2,3};
demo05 demo05 = new demo05();
demo05.d(arr);
}
public void d(int... nums){
System.out.println("长度:" + nums.length);
}
}
- 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
public class demo05 {
public static void main(String[] args) {
demo05 demo05 = new demo05();
demo05.f("小 张",12,1.2);
}
public void f(String str,double... nums){
System.out.println(str+"+"+nums.length);
}
}
- 一个形参列表中只能出现一个可变参数
练习:
三个方法,分别实现返回姓名和两门课成绩(总分),返回姓名和三门课成绩(总分),返回姓名和五门课成绩(总分),封装成一个可变参数的方法
public class demo06 {
public static void main(String[] args) {
demo06 demo06 = new demo06();
System.out.println(demo06.fa("小 张",100.3,123,12.9));
}
public double fa(String name,double... nums){
double num1 = 0;
System.out.println("名字="+name);
for (int i = 0; i < nums.length; i++) {
num1+=nums[i];
}
return num1;
}
}
作用域
面向对象中,变量作用域是非常重要的知识点
作用域:Scope
- 在Java编程中,主要的变量就是属性(成员变量)和局部变量
- 局部变量一般是指在成员方法中定义的变量,除了属性以外的其他变量,作用域就是定义它的代码块的范围
- 全局变量:就是属性,作用域是整个类,全局变量(属性)可以不赋值,直接使用,有默认值,而局部变量必须给初始化后才能使用,局部变量没有默认值
public class demo07 {
public static void main(String[] args) {
}
//全局变量:就是属性,作用域是整个类
int age = 8;
public void say(){
//局部变量一般是指在成员方法中定义的变量
//n 和 name 就是局部变量
//n 和 name 的作用域就是在say方法中
int n =10;
String name = "xiao liu";
System.out.println("age=" + age);
}
}
细节:
- 属性和局部变量可以重名,访问时遵循就近原则
class Person{
String name = "jack";
public void say(){
String name = "king";
System.out.println("name:" + name);//会输出king,就近原则
}
}
class Person{
String name = "jack";
public void say(){
// String name = "king";
System.out.println("name:" + name);//会输出king,就近原则
}
}
-
在同一个作用域里,例如在同一个成员方法中,两个局部变量,不能重名
-
属性声明周期较长,伴随着对象的创建而创建,伴随这对象的销毁而销毁,局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁,即在一次方法调用的过程中
-
作用范围不同:
-
全局变量/属性,可以被本类调用,或其他类使用(通过对象调用)
-
局部变量,只能在本类中对应的方法中使用
-
修饰符
-
全局变量/属性可以加修饰符
-
局部变量不可以加修饰符
构造器
需求:
创建一个人对象,之前是先把一个对象创建好之后,再给人的属性赋值,现在有需求,要在创建人类对象时,就直接指定这个对象的属性值,这是就需要使用构造器
public class demo08 {
public static void main(String[] args) {
//当我们new对象时,直接通过构造器指定属性初始化
Person person = new Person("小刘",8);
System.out.println("名字:"+person.name+" 年龄:" + person.age);
}
}
class Person{
String name;
int age;
//构造器
public Person(String pName,int pAge){
name = pName;
age = pAge;
}
}
语法:
修饰符 方法名(形参列表){
方法体;
}
注意:
- 构造器的修饰符可以默认
- 构造器没有返回值,void也不可以
- 方法名和类名字必须一样
- 参数列表和成员方法一样的规则
- 创建对象时,系统会自动调用该类的构造器完成对象的初始化
构造器:constructor
构造器细节
- 一个类可以定义多个构造器,即构造器重载,再new对象时就有了两种选择
class Person{
String name;
int age;
//构造器
public Person(String pName,int pAge){
name = pName;
age = pAge;
}
//构造器重载
public Person(String pName){
name = pName;
}
}
- 构造器名和类名相同,不相同就不是构造器,不是构造器就有返回值,没有返回值也要写void
- 构造器没有返回值
- 构造器是完成对象的初始化,不是创建对象,当构造器被调用,对象已经在堆中存在了
- 在创建对象时,系统自动调用该类的构造方法
- 如果程序元没有定义构造器,系统就会自动给类生成一个默认的无参构造器,比如Person{}
- 一旦定义了自己的构造器,默认构造器就被覆盖了,不会给你添加了,除非你显示的定义一下,这在开发中常用
对象的创建流程(⭐)
class Person{
int age = 90;
String name;
Person(String name,int a){
name = n;
age = a;
}
}
Person p = new Person("xiao liu",2);
- 加载Person类信息(Person.class),只会加载一次
- 在堆中分配空间(地址)
- 完成对象初始化
- 默认初始化(age = 0;name = null;)
- 显式初始化(age = 90;name = null;)
- 构造器初始化(age = 2; name = “xiao liu”)
- 把对象在堆中的地址返回给p(对象的引用,对象名)
图示:
this关键字
class Person{
String name;
int age;
//构造器
public Person(String pName,int pAge){
name = pName;
age = pAge;
}
}
构造器的参数名不是很友好,如果要将pName换成了 name 就好了,试一试
public class demo08 {
public static void main(String[] args) {
//当我们new对象时,直接通过构造器指定属性初始化
Person person = new Person("小刘",8);
System.out.println("名字:"+person.name+" 年龄:" + person.age);
}
}
class Person{
String name;
int age;
//构造器
public Person(String pName,int pAge){
name = pName;
age = pAge;
}
//构造器重载
public Person(String pName){
name = pName;
}
}
输出:
更改后:
public class demo08 {
public static void main(String[] args) {
//当我们new对象时,直接通过构造器指定属性初始化
Person person = new Person("小刘",8);
System.out.println("名字:"+person.name+" 年龄:" + person.age);
}
}
class Person{
String name;
int age;
//构造器
public Person(String name,int age){
name = name;
age = age;
}
//构造器重载
public Person(String pName){
name = pName;
}
}
结果:
根据变量的作用域原则,这时的构造器的name是局部变量,不是属性,age也一样,自己给自己赋值,构造器相当于无效,因此它会有默认值就是 null 和 0
思考:怎么解决呢?
没错,this关键字闪亮登场!!!
什么是 this
java虚拟机会给每个对象分配 this ,代表当前对象
就好比,我们每一个人在说自己的时候,说,我的,不同的人说所代表的人也就不同,this就是指的当前对象的属性,nice
public class demo08 {
public static void main(String[] args) {
//当我们new对象时,直接通过构造器指定属性初始化
Person person = new Person("小刘",8);
System.out.println("名字:"+person.name+" 年龄:" + person.age);
}
}
class Person{
String name;
int age;
//构造器
public Person(String name,int age){
this.name = name;
this.age = age;
}
//构造器重载
public Person(String pName){
name = pName;
}
}
结果:
进阶
哪个对象调用,this就代表哪个对象
因为Java是跑在虚拟机上的,所以没有实际的内存地址,只有 hashCode 可以模拟地址
输出对象的hashCode和this的hashCode,看看是否一样
public class demo08 {
public static void main(String[] args) {
//当我们new对象时,直接通过构造器指定属性初始化
Person person = new Person("小刘",8);
System.out.println("名字:"+person.name+" 年龄:" + person.age);
System.out.println(person.hashCode());
}
}
class Person{
String name;
int age;
//构造器
public Person(String name,int age){
this.name = name;
this.age = age;
System.out.println(this.hashCode());
}
//构造器重载
public Person(String pName){
name = pName;
}
}
this细节
- this 关键字可以用来访问本类的属性,方法,构造器
- this 用于区分当前类的属性和局部变量
- 访问成员方法的语法:this.方法名(参数列表);
- 访问构造器的语法:this(参数列表);注意只能在构造器中使用,只能在构造器中访问另一个构造器,this访问构造器的语法,必须放在第一条语句
public class demo09 {
public static void main(String[] args) {
T t = new T();
}
static class T{
public T(){
//构造器必须在第一行
this("小刘",2);
System.out.println("T() 被调用");
}
public T(String name,int age){
System.out.println("T(String name,int age) 构造器");
}
}
}
- this不能在类定义的外部使用,只能在类定义的方法中使用
System.out.println(name);
System.out.println(this.name);
区别
name 就近原则,如果有局部变量,就是用近的局部变量,所谓近水楼台先得月
this.name 就是明确到对象的 name 属性
🔴🟢🟡美美的