🔥博客主页: 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);
}
//返回一个int
public 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调用print print(10); } }
- 跨类中的方法A要调用B类方法,需要通过对象名调用,先创建对象。对象名.方法名
class A{ public void sayok(){//在sayok调用print B 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个扔到b
move(num - 1 ,a,c,b);
//然后把最后一个给c
System.out.println(a + "->" + c);
//把b的移动到c
move(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可以返回null
if (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;
}
}