🔥博客主页: A_SHOWY
🎥系列专栏:力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_
类和对象的引出
如果输入两个人的信息,不管是单独定义变量还是利用数组都不利于数据的管理,效率低,所以要引出类与对象。JAVA设计者引入(类与对象)OOP,现有技术不能完美解决新的需求。

- 类就是数据类型,比如Cat类
- 对象就是一个类的具体实例
- 从类到对象有几种叫法,创建一个对象,实例化一个对象或者把类实例化
- 类是抽象的,对象是具体的,类是对象的模板,对象是类的具体
public class lei1 {public static void main(String[] args) {Cat cat1 = new Cat();//cat1是对象名,new出来的那个新的Cat的空间才是真正的对象cat1.name = "小白";cat1.age = 16;cat1.color = "白";Cat cat2 = new Cat();cat2.name = "小黄";cat2.age = 6;cat2.color = "黄";}
}class Cat{//属性String name;int age;String color;}
cat1是对象名,new出来的那个新的Cat的空间才是真正的对象
对象在内存种的存在形式
在创建对象的时候,会把类信息加载到方法区,主要是属性和行为
属性 (成员变量)
基本概念
从概念上看,成员变量 = 属性,即成员变量是用来表示属性的。
属性是类的组成部分,一般是基本数据类型,当然也可以是引用数据类型。
属性的细节
- 属性的定义语法同变量:示例: 访问修饰符(控制属性的访问范围) 属性类型 属性名;有四种访问修饰符:public,protected,默认,private
- 属性如果不赋值有默认值,规则和数组一样
如何创建对象和访问属性
创建对象
- 先声明再创建
Cat cat; cat = new Cat(); - 直接创建
Cat cat = new Cat();访问属性
对象名.属性名
类和对象的内存分配机制(重要)
和数组的非常类似,都是地址引用。
过程:
加载person类的信息(属性和方法信息),同时堆内开辟空间(根据对象属性)并且进行默认初始化,同时把这个地址返回给了p1,让p1指向这个地址。

进行指定初始化,比如p1.name = 小明 字符串常量会放在常量池

和数组一样,也是地址拷贝。
JAVA内存的结构分析
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象(Cat cat,数组等)
- 方法区:常量池(常量,比如字符串)和类加载信息
例:

成员方法
入门
public class fangfa1 {public static void main(String[] args){Person p1 = new Person();p1.speak();p1.cal01();p1.cal02(100);int a = p1.getsum(10,20);//返回的值需要声明一个变量接收System.out.println(a);}
}
class Person{String name;int age;//成员方法//添加speak方法,输出我是一个好人//1.Public表示方法公开2.void表示方法没有返回值3.小括号内传入参数,形参列表4.{}方法体执行的代码//方法写好后,不调用不输出public void speak() {System.out.println("我是一个好人");}//添加cal01方法,可计算1-1000结果public void cal01(){int res = 0;for(int i = 0 ;i < 1000; i++){res += i;}System.out.println(res);}public void cal02(int n){int res = 0;for(int i = 0 ;i < n; i++){res += i;}System.out.println(res);}//返回一个intpublic int getsum(int n1,int n2){int res = 0;res = n1 + n2;return res;}
}
方法调用机制

当退出getSum方法的时候,这个getSum栈就会释放。
成员方法的好处
- 代码的复用性提高
- 可以将方法的细节封装起来,供其他用户使用即可
public class fangfa2 {public static void main(String[] args){int [][] map = {{0,0,0},{0,0,1},{0,0,2}};Mytools tools = new Mytools();tools.printArr(map);}
}
class Mytools{public void printArr(int[][] map){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();}}
}
成员定义方法
public(访问修饰符) 返回数据类型 方法名 (形参列表){方法体}
成员方法注意事项和细节
总细节
- 关于访问修饰符:控制方法使用范围,如果不写就是默认访问
- 一个方法最多有一个返回值(如果想返回多个,那么返回数组)
public int[] getSumandSub(int n1, int n2){int[] resArr = new int[];resArr[0] = n1 + n2;resArr[1] = n1 - n2;return resArr;} - 返回类型可以是基本数据类型或者引用数据类型
- 如果方法要求有返回,必须最后又return,而且类型必须一致或者兼容,void没有返回值,你可以return空
- 在实际工作开发中方法命名要规范,见名知意
- 方法不能嵌套定义
形参列表方面的细节
- 一个方法可以有0或者多个参数
- 调用带参数的方法时,对应参数列表传入相同类型或者兼容类型的参数
- 方法定义时的参数称为形式参数,简称形参(int a,int b)。方法调用时,参数为实际参数(1,4)。实参和形参类型要一致或者兼容,个数顺序必须一致。
方法调用细节
- 同一个类中的方法调用;直接调用即可。
class A{public void print(int n){System.out.println(n);}public void sayok(){//在sayok调用printprint(10);} } - 跨类中的方法A要调用B类方法,需要通过对象名调用,先创建对象。对象名.方法名
class A{public void sayok(){//在sayok调用printB b = new B();b.hi();} } class B{public void hi() {System.out.println("hi");} } - 跨类的方法调用和方法的访问修饰符相关和包相关。
练习1:

public class fangfa3 {public static void main(String[] args){A a = new A();boolean res = a.isOdd(3788);System.out.println(res);}
}
class A{public boolean isOdd(int n){return (n % 2 != 0) ? true : false;}
}

public class fangfa3 {public static void main(String[] args) {A a = new A();a.chars('#', 4, 4);}
}
class A{public void chars(char a,int n1, int n2){for(int i = 0; i < n1; i++){for (int j = 0; j < n2; j++){System.out.print(a + "\t");}System.out.println();}}
}
成员方法传参机制(非常重要)
基本数据类型传参机制
要注意数据空间是相互独立的,对于基本数据类型,传递的是值,形参的任何改变不影响实参。
引用数据类型传参机制
引用传递的时候,传递的是一个地址,所以是地址传递。可以通过形参影响实参。 形参没有传递对象,而是传递对象地址,两个栈中的p指向同一个空间。 下图红框中的画的本质是把这个p(对象)的地址给了下面test200中的p

补充(重要):
现在给了个p = null以后,就是新的栈这根线断了,原来两个栈都指向同一个地址,现在只不过一个断了,另一个没影响,所以输出还是10.
补充2:

这个new开辟了新的空间,所以结果还是10.
对象克隆

import javax.jnlp.PersistenceService;public class kelong {public static void main(String[] args){Person p = new Person();p.name = "tom";p.age = 16;myTooles tools = new myTooles();Person p2 = tools.copyperson(p);System.out.println(p.name + p.age);System.out.println(p2.name + p2.age);System.out.println(p == p2);}
}
class Person{String name;int age;
}
class myTooles{public Person copyperson(Person p){Person p2 = new Person();p2.name = p.name;p2.age = p.age;return p2;}
}

这个false代表是两个独立的对象。
方法递归调用
递归:方法自己调用自己 ,每次调用时传入不同的变量。

递归重要规则:
- 执行一个方法时,就创一个新的受保护的独立空间(栈空间)
- 方法的局部变量是 独立的
- 如果方法中使用的是引用类型(数组或者对象),就会共享该引用数据。指向堆里相同的空间会相互影响。
- 递归必须向递归条件逼近否则死循环
- 方法执行完毕或者return就返回,遵守谁调用就返回谁
递归例子: 斐波那契数列
public class digui {public static void main(String[] args){T t = new T();System.out.println(t.feibo(7));}
}
class T{public int feibo(int n ){if(n >= 1){if(n == 1|| n == 2){return 1;}else{return feibo(n -1) + feibo(n - 2);}}else{System.out.println("请输入大于等于1的数");return -1;}}
}
递归例子: 猴子吃桃

public class digui {public static void main(String[] args){T t = new T();System.out.println(t.day(1));}
}
class T{public int day(int n ){if(n == 10) return 1;return (day(n + 1) + 1) * 2;}
}
递归例子:迷宫的实现


public class migong {public static void main(String[] args){int[][] map = new int[8][7];
//上下两行都是1边界for(int i = 0; i < 7; i++){map[0][i] = 1;map[7][i] = 1;}
//左右两行都是1边界for(int i = 0; i < 6; i++){map[i][0] = 1;map[i][6] = 1;}map[3][1] = 1;map[3][2] = 1;T2 t = new T2();t.findWay(map,1,1);//输出地图for(int i = 0; i < 8; i++){for(int j = 0; j < 7; j++){System.out.print(map[i][j]);}System.out.println();}}
}class T2{public boolean findWay(int[][] map, int i ,int j){if(map[6][5] == 2) {//假设已经找打到return true;}else{if(map[i][j] == 0){//当前这位置为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;}}}
}
假如把地图的地形改一下,把这个起点团团围住,那么将会出现一个3。思路比较简单,在这不再进行模拟。整体方法思路是假设已经找到出口,return true,否则进行一个规则的找路,进行递归。找不到就返回3。
递归例子:汉诺塔
public class diguidemo {public static void main(String[] args){Tower t = new Tower();t.move(5,'A','B','C');}
}
class Tower{public void move(int num, char a,char b,char c){if(num == 1){System.out.println(a + "->" + c);}else{//把前n - 1个扔到bmove(num - 1 ,a,c,b);//然后把最后一个给cSystem.out.println(a + "->" + c);//把b的移动到cmove(num - 1,b,a,c);}}
}
总结来说,都是先假设最内层为真,然后再逐层往外想。
方法重载(overload)
方法重载:java 中允许在一个类中,多个同名方法存在,但是要求形参列表不一致,可以减轻起名和记名字的麻烦。方法名必须相同,形参列表必须不同 (类型,个数或者顺序),返回类型不影响,比如一个返回void一个返回int,无所谓,没有构成方法重载。
可变参数
可变参数:java允许同一类中多个同名同功能但是参数个数不同的方法,封装成一个方法。
基本语法:访问修饰符 返回类型 方法名(数据类型...形参名)
例子:
public class diguidemo {public static void main(String[] args) {kebian t1 = new kebian();System.out.println(t1.sum(2, 5, 8));}
}class kebian{public int sum(int a,int b) {return a + b;}public int sum(int a,int b,int c){return a + b + c;}public int sum(int a,int b,int c,int d){return a + b + c +d;}//使用可变参数,这个nums可以看作数组来使用public int sum(int ... nums){int res = 0;for(int i = 0; i < nums.length; i++){res += nums[i];}return res;}
}
最后输出结果是结果15.
细节:
- 可变参数的实参可以有0个或者多个
- 可变参数的实参可以为数组
int arr[] = {1,2,3};System.out.println(t1.sum(arr));//结果是6 - 可变参数的本质就是数组
- 可变参数可以和普通类型参数一起放在参数列表,但是必须保证可变参数在最后
- 一个形参列表中只能有一个可变参数
作用域(scope):也就是作用范围
- 在java中主要的变量就是属性(成员变量)和局部变量
- 我们一般说的局部变量是成员方法中定义的
- java中作用域分类:全局变量:属性,作用范围是整个的类体。局部变量范围只能在方法中(不完全对),代码块中,除了属性以外的变量都是局部的
- 全局变量可以不赋值,有默认值,而局部的没有,必须赋值后使用
细节
- 属性和局部变量可以重名,使用的时候遵循就近原则。
- 同一个作用域,两个变量不能重名
- 属性的生命周期长,伴随着对象的创建而创建,伴随着对象的消亡而消亡。局部变量的生命周期短,伴随着代码块的执行而创建,伴随着代码块结束而销毁,即在一次方法调用中调用结束就销毁。
- 全部变量可以被本类使用或者其他类使用(用对象调用)。感觉第一类方式更直观。
public class scope {public static void main(String[] args){TEST abc= new TEST();abc.test();//第一种跨类访问对象属性的方式Person1 p1 = new Person1();abc.test2(p1);//第二种跨类访问对象属性的方式} } class TEST{public void test(){Person1 p1 = new Person1();System.out.println(p1.name);}public void test2(Person1 p){System.out.println(p.name);} } class Person1{String name = "JACK"; } - 修饰符不同,全局变量可以加修饰符,局部变量不同。
构造方法/构造器 (constructor)
在创建对象的同时,直接指定部分属性,这时就指的是构造器,主要作用是完成对新对象的初始化
基本语法:【修饰符】 方法名(形参列表){方法体;}
说明:
- 修饰符可以默认也可以是其它
- 没有返回值
- 方法名和类型保持一致
- 参数列表和成员方法一样的规则
- 构造器的调用是由系统完成
例子: 构造器被调用,完成对象的属性初始化
public class scope {public static void main(String[] args){PerSon p2 = new PerSon("Tom",22);}
}class PerSon{String name;int age;//构造器没有返回值也没void,名称和类一样public PerSon(String pname,int page){System.out.println("构造器被调用");name = pname;age = page;}
}
注意事项和细节
- 一个类可以构造多个构造器,即构造器的重载,比如在person方法的时候,只给人名,不给年龄。
- (重要)如果程序员没有给对象构造器,系统会给一个无参构造器也就是默认构造器。
- (重要)一旦使用了自己的构造器,就不能再用无参的了,除非显示的定义一下。
对象创建流程分析

注意细节
- string是引用类型,所以在常量区调用地址
- 这个p是对象的引用或者叫对象名,真正的对象一直在栈中。
过程(重要)

- 加载Person类信息,只会加载一次 ,在第一次调用的时候
- 在堆中分配空间(得到一个地址)
- 完成对象的初始化,先进行默认初始化,age为0,name为空,然后看赋值语句,进行显示初始化,age从0变成90。再进行构造器初始化,age从90替换成20,name变成小倩
- 在对象在堆中的地址返回给p(p就是对象的引用(对象名))
this关键字
java虚拟机会给对象分配this,代表当前对象。哪个对象调用,this就代表哪个对象。

细节
- this关键字可以用来访问本类的属性、方法、构造器
- this可以区分当前类的属性和局部变量
- 访问成员方法: this.方法名(参数列表)
class T4{public void f1(){System.out.println("f1方法");}public void f2(){System.out.println("f2方法");this.f1();f1();}} - 访问构造器:this(参数列表):只能在构造器中调用另一个构造器,而且this这条语句必须放在第一条语句!
class T3{public T3(){this("jack",10);}public T3(String name,int i){System.out.println("T3(String name,int i)构造器");} } - this只能在类定义的方法中使用,不能在类定义的外部使用,和对象是关联的。
习题(精妙)

public class scope {public static void main(String[] args){Persons p6 = new Persons("Tom",15);Persons p7 = new Persons("Tom",15);System.out.println(p6.compareTo(p7));}
}
class Persons{int age;String name ;//构造器public Persons(String name,int age){this.name = name;this.age = age;}public boolean compareTo(Persons p){return (p.age == this.age && p.name.equals(this.name));//这个this就是p6}
}
练习一:
注意健壮性
下面写出具有一定健壮性的代码:Double与double的区别,Double可以返回null
同时为了满足数组万一是null或者数组里面没有元素,要判断if(arr != null && arr.length > 0)
public class scope {public static void main(String[] args){double arr[] = {};A01 a01 = new A01();Double res = a01.mx(arr);if(res == null) System.out.println("arr的输入有误");else{System.out.println(res);}}
}
class A01{public Double mx(double arr[]) {//Double可以返回nullif (arr != null && arr.length > 0) {//满足万一arr是null或者是空数组double max = arr[0];for (int i = 0; i < arr.length; i++) {if (max < arr[i]) {max = arr[i];}}return max;}else {return null;}}
}
练习二

public class scope {public static void main(String[] args){Book book = new Book("ashauai",188);book.updatePrice();System.out.println(book.price);}
}
class Book{String name;double price;public Book(String name,double price){this.name = name;this.price = price;}public void updatePrice(){if(price > 150){price = 150;}else if(price > 100){price = 100;}}}
练习三

输出10,9,10 ,main类第一句,是一个匿名对象,只能用一次,就销毁了,后面从新new是新的地址。
练习四

class Fuyong{String name;char gender;int age;String job;double salary;public Fuyong(String name,char gender,int age){this.name = name;this.gender = gender;this.age = age;}public Fuyong(String job,double salary){this.job = job;this.salary = salary;}public Fuyong(String name,char gender,int age,String job,double salary){this(name,gender,age);this.job = job;this.salary = salary;}
}
练习五
方式1:普通方式
public class Homework5 {public static void main(String[] args){Circle d1 = new Circle();PassObject d2 = new PassObject();d2.printAreas(d1,5);}
}
class Circle{double radius;//半径public double findArea(double radius){return Math.PI * radius * radius;}
}
class PassObject{public void printAreas(Circle c,int times){for(int i = 1; i <= times; ++i){double t = c.findArea(i);System.out.println((double) i + "\t" + t);}}
}
方式2:set方法+findArea无参的情况
public class Homework5 {public static void main(String[] args){Circle d1 = new Circle();PassObject d2 = new PassObject();d2.printAreas(d1,5);}
}
class Circle{double radius;//半径public double findArea(){return Math.PI * radius * radius;}//定义一个set方法public void setRadius(double radius){this.radius = radius;}
}
class PassObject{public void printAreas(Circle c,int times){for(int i = 1; i <= times; ++i){c.setRadius(i);double t = c.findArea();System.out.println((double) i + "\t" + t);}}
}
扩展题:石头剪刀布

有几个核心步骤,定义这个Tom类,几个属性,tom出的,电脑出的,总次数,赢得次数,有几个方法,电脑出拳(用到random),玩家出拳用到get(返回)set(判断界限并设置)方法 。判断谁赢了和赢得次数(通过判断返回的谁赢了字符串)这几个方法。
在main方法里,设置两个数组来记录,第一个二维数组记录局数,各自出的拳,再定义一个一维数组记录结果,再打印。
import java.util.Scanner;
import java.util.Random;
public class caiquan {public static void main(String[] args){Tom t = new Tom();int isWinConutNum = 0;int arr1[][] = new int[3][3];//存出拳情况String arr2[] = new String[3];int j = 0;Scanner scanner = new Scanner(System.in);for(int i = 0; i < 3; i++){//获取玩家出的拳System.out.println("请输入你要出的拳 (0-拳头,1-剪刀,2-布):" );int num = scanner.nextInt();t.setTomGuessNum(num);int tomGuess = t.getTomGuessNum();arr1[i][j + 1] = tomGuess;//获取电脑出拳int comGuess = t.getTomGuessNum();arr1[i][j + 2] = comGuess;//比较String isWin = t.vsComputer();arr2[i] = isWin;arr1[i][j] = t.count;//打印System.out.println("====================================");System.out.println("局数\t玩家出拳\t电脑出拳\t输赢的情况");System.out.println(t.count + "\t\t" + tomGuess + "\t\t" + comGuess + "\t\t" +isWin);System.out.println("====================================");isWinConutNum = t.winCount(isWin);}//对最终结果输出System.out.println("局数\t玩家出拳\t电脑出拳\t输赢的情况");for(int i = 0; i < arr1.length; i++){for (int k = 0; k < arr1[0].length; k++){System.out.print(arr1[i][k] + "\t\t");}System.out.print(arr2[i]);System.out.println();}System.out.println("你赢了" + isWinConutNum + "次");}
}
class Tom{int tomGuessNum;//Tom出拳的类型int comGuessNum;//电脑出拳的类型int winCountNum;//玩家赢的次数int count = 1;//玩家的局数
//电脑出拳public int computerNum(){Random r = new Random();comGuessNum = r.nextInt(3);//返回0-2随机数return comGuessNum;}
//玩家出拳public void setTomGuessNum(int tomGuessNum){if(tomGuessNum > 2 || tomGuessNum < 0){//抛出一个异常throw new IllegalArgumentException("数字输入错误");}this.comGuessNum = comGuessNum;}public int getTomGuessNum(){return tomGuessNum;}//判断谁赢了public String vsComputer(){if(tomGuessNum == 0 && comGuessNum == 1){return"你赢了";}else if(tomGuessNum == 1 && comGuessNum == 2){return "你赢了";}else if(tomGuessNum == 2 && comGuessNum == 0){return "你赢了";}else if(tomGuessNum == comGuessNum){return "平手";}else return "你输了";}// 记录赢得次数public int winCount(String s){count++;if(s.equals("你赢了")){winCountNum++;}return winCountNum;}
}