JAVA学习笔记——第七章 面向对象基础

🔥博客主页: A_SHOWY
🎥系列专栏:力扣刷题总结录 数据结构  云计算  数字图像处理  力扣每日一题_ 

类和对象的引出

如果输入两个人的信息,不管是单独定义变量还是利用数组都不利于数据的管理,效率低,所以要引出类与对象。JAVA设计者引入(类与对象)OOP,现有技术不能完美解决新的需求。

  1. 类就是数据类型,比如Cat类
  2. 对象就是一个类的具体实例
  3. 从类到对象有几种叫法,创建一个对象,实例化一个对象或者把类实例化 
  4. 类是抽象的,对象是具体的,类是对象的模板,对象是类的具体
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的空间才是真正的对象 

 对象在内存种的存在形式

在创建对象的时候,会把类信息加载到方法区,主要是属性和行为

属性 (成员变量)

基本概念

从概念上看,成员变量 = 属性,即成员变量是用来表示属性的。

属性是类的组成部分,一般是基本数据类型,当然也可以是引用数据类型。

属性的细节

  1. 属性的定义语法同变量:示例: 访问修饰符(控制属性的访问范围) 属性类型 属性名;有四种访问修饰符:public,protected,默认,private
  2. 属性如果不赋值有默认值,规则和数组一样

如何创建对象和访问属性

     创建对象

  1. 先声明再创建
    Cat cat;
    cat = new Cat();
  2. 直接创建 
    Cat cat = new Cat();

    访问属性

对象名.属性名 

类和对象的内存分配机制(重要) 

和数组的非常类似,都是地址引用。

过程:

 加载person类的信息(属性和方法信息),同时堆内开辟空间(根据对象属性)并且进行默认初始化,同时把这个地址返回给了p1,让p1指向这个地址。

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

和数组一样,也是地址拷贝。 

JAVA内存的结构分析

  1. 栈:一般存放基本数据类型(局部变量)
  2. 堆:存放对象(Cat cat,数组等)
  3. 方法区:常量池(常量,比如字符串)和类加载信息

例: 

成员方法

入门

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栈就会释放。

成员方法的好处

  1. 代码的复用性提高
  2. 可以将方法的细节封装起来,供其他用户使用即可
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(访问修饰符) 返回数据类型 方法名 (形参列表){方法体}

成员方法注意事项和细节

总细节

  1. 关于访问修饰符:控制方法使用范围,如果不写就是默认访问
  2. 一个方法最多有一个返回值(如果想返回多个,那么返回数组)
      public int[] getSumandSub(int n1, int n2){int[] resArr = new int[];resArr[0] = n1 + n2;resArr[1] = n1 - n2;return  resArr;}
  3. 返回类型可以是基本数据类型或者引用数据类型
  4. 如果方法要求有返回,必须最后又return,而且类型必须一致或者兼容,void没有返回值,你可以return空
  5. 在实际工作开发中方法命名要规范,见名知意
  6. 方法不能嵌套定义

形参列表方面的细节 

  1.  一个方法可以有0或者多个参数
  2. 调用带参数的方法时,对应参数列表传入相同类型或者兼容类型的参数
  3. 方法定义时的参数称为形式参数,简称形参(int a,int b)。方法调用时,参数为实际参数(1,4)。实参和形参类型要一致或者兼容,个数顺序必须一致

方法调用细节 

  1. 同一个类中的方法调用;直接调用即可。
    class A{public void print(int n){System.out.println(n);}public void sayok(){//在sayok调用printprint(10);}
    }
  2. 跨类中的方法A要调用B类方法,需要通过对象名调用,先创建对象。对象名.方法名
    class A{public void sayok(){//在sayok调用printB b = new B();b.hi();}
    }
    class B{public void hi() {System.out.println("hi");}
    }
  3. 跨类的方法调用和方法的访问修饰符相关和包相关

练习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代表是两个独立的对象。 

方法递归调用 

递归:方法自己调用自己 ,每次调用时传入不同的变量。

递归重要规则:

  1. 执行一个方法时,就创一个新的受保护的独立空间(栈空间)
  2. 方法的局部变量是 独立的
  3. 如果方法中使用的是引用类型(数组或者对象),就会共享该引用数据。指向堆里相同的空间会相互影响。
  4. 递归必须向递归条件逼近否则死循环
  5. 方法执行完毕或者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.

细节:

  1. 可变参数的实参可以有0个或者多个
  2. 可变参数的实参可以为数组
      int arr[] = {1,2,3};System.out.println(t1.sum(arr));//结果是6
  3. 可变参数的本质就是数组
  4. 可变参数可以和普通类型参数一起放在参数列表,但是必须保证可变参数在最后
  5. 一个形参列表中只能有一个可变参数

 作用域(scope):也就是作用范围

  1.  在java中主要的变量就是属性(成员变量)和局部变量
  2. 我们一般说的局部变量是成员方法中定义的
  3. java中作用域分类:全局变量:属性,作用范围是整个的类体。局部变量范围只能在方法中(不完全对),代码块中,除了属性以外的变量都是局部的
  4. 全局变量可以不赋值,有默认值,而局部的没有,必须赋值后使用

细节 

  1. 属性和局部变量可以重名,使用的时候遵循就近原则。
  2. 同一个作用域,两个变量不能重名
  3. 属性的生命周期长,伴随着对象的创建而创建,伴随着对象的消亡而消亡。局部变量的生命周期短,伴随着代码块的执行而创建,伴随着代码块结束而销毁,即在一次方法调用中调用结束就销毁。
  4. 全部变量可以被本类使用或者其他类使用(用对象调用)。感觉第一类方式更直观。 
    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";
    }
  5. 修饰符不同,全局变量可以加修饰符,局部变量不同。

构造方法/构造器 (constructor)

 在创建对象的同时,直接指定部分属性,这时就指的是构造器,主要作用是完成对新对象的初始化

基本语法:【修饰符】 方法名(形参列表){方法体;}

说明:

  1. 修饰符可以默认也可以是其它
  2. 没有返回值
  3. 方法名和类型保持一致
  4. 参数列表和成员方法一样的规则
  5. 构造器的调用是由系统完成

例子: 构造器被调用,完成对象的属性初始化

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;}
}

注意事项和细节

  1. 一个类可以构造多个构造器,即构造器的重载,比如在person方法的时候,只给人名,不给年龄。
  2. (重要)如果程序员没有给对象构造器,系统会给一个无参构造器也就是默认构造器。
  3. (重要)一旦使用了自己的构造器,就不能再用无参的了,除非显示的定义一下。

对象创建流程分析 

       注意细节

  1. string是引用类型,所以在常量区调用地址 
  2. 这个p是对象的引用或者叫对象名,真正的对象一直在栈中。

        过程(重要)

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

this关键字 

java虚拟机会给对象分配this,代表当前对象。哪个对象调用,this就代表哪个对象。

细节 

  1. this关键字可以用来访问本类的属性、方法、构造器
  2. this可以区分当前类的属性和局部变量
  3. 访问成员方法: this.方法名(参数列表)
      class  T4{public void f1(){System.out.println("f1方法");}public void f2(){System.out.println("f2方法");this.f1();f1();}}
  4. 访问构造器:this(参数列表):只能在构造器中调用另一个构造器,而且this这条语句必须放在第一条语句!
    class T3{public  T3(){this("jack",10);}public  T3(String name,int i){System.out.println("T3(String name,int i)构造器");}
    }
  5.  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;}
}