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);
    }
    //返回一个int
    public 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调用print
            print(10);
        }
    }
  2. 跨类中的方法A要调用B类方法,需要通过对象名调用,先创建对象。对象名.方法名
    class A{
        public void sayok(){//在sayok调用print
            B 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个扔到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.

细节:

  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可以返回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;
    }
}

 

  • 31
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A_SHOWY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值