java基础 韩顺平老师的 面向对象(基础) 自己记的部分笔记

194,对象内存布局

基本数据类型放在堆里面,字符串类型放在方法区。

栈:一般存放基本数据类型(局部变量)

堆:存放对象(Cat cat,数组等)

方法区:常量池(常量,比如字符串),类加载信息

196,属性注意细节

1,属性可以是基本数据类型,也可以是引用类型(对象,数组)

2,属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;这里简单介绍访问修饰符:控制属性的访问范围。有四种访问修饰符 public,protected,默认,private。

3,属性如果不赋值,有默认值,规则和数组一致。具体说:int 0,short 0,byte 0,long 0,float 0.0,double 0.0,char \u0000,boolean false,String null。

复制代码
public class test1{
    public static void main(String[] args){
        //创建Person对象
        //p1 是对象名(对象引用)
        Person p1 = new Person();//new Person() 创建的对象空间(数据)才是真正的对象

        System.out.println("\n当前这个人的信息");
        System.out.println("age = " + p1.age + " name = " + p1.name + " sal = " + p1.sal + " isPass = " + p1.isPass);
    }
}
class Person{
    //四个属性
    int age;
    String name;
    double sal;
    String isPass;
}

运行结果:

198,对象分配机制

引用赋值,即地址赋值

199,对象创建过程

1,先加载Person类信息(属性和方法信息,只会加载一次)

2,在堆中分配空间,进行默认初始化(看规则)

3,把地址赋给 p ,p 就指向对象 Person p = new Person()

4,进行指定初始化,比如 p.name = "jack"

206,方法使用细节

1,一个方法最多有一个返回值 [思考,如何返回多个结果,返回数组]

2,返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)

复制代码
public class test1{
    public static void main(String[] args){
        AA a = new AA();
        int[] res = a.getSumAndSub(1,2);//用一个数组接收
        System.out.println("和 = " + res[0]);
        System.out.println("差 = " + res[1]);
    }
}
class AA{
    public int[] getSumAndSub(int n1, int n2)
    {
        int[] resArr = new int[2];//创建一个数组
        resArr[0] = n1 + n2;
        resArr[1] = n1 - n2;
        return resArr;
    }
}

运行结果:

3,如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值(值为常量或表达式);而且要求返回值类型必须和 return 的值类型一致或兼容(会发生自动类型转换)

4,如果方法是 void ,则方法体中可以没有 return 语句,或者只写 return;

5,接207;一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开

6,调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数

7,方法定义时的参数称为形式参数,简称形参;方法调用时的传入参数称为实际参数,简称实参,实参和形参的类型要一致或兼容,个数,顺序必须一致

8,方法体里面写完功能的具体的语句,可以为输入,输出,变量,运算,分支,循环,方法调用,但里面不能再定义方法!即:方法不能嵌套定义。

9,同一个类中的方法调用:直接调用即可。(我们想在A类里的一个方法里去调用A类里的另一个方法,直接调用即可)

复制代码
public class test1{
    public static void main(String[] args){
        A a = new A();
        a.sayOk();
    }
}
class A{
   public void print(int n)
   {
       System.out.println("print()方法被调用 n = " + n);
   }
   public void sayOk()// sayOk 调用 print(直接调用即可)
   {
       print(10);
       System.out.println("继续执行sayOk");
   }
}

运行结果:

10,跨类中的方法A类调用B类方法:需要通过对象名调用。(我们想在A类的一个方法里调用B类的一个方法,需要在A类的这个方法里创建B类的对象,再用B类的对象调用B类的一个方法)。

复制代码
public class test1{
    public static void main(String[] args){
        A a = new A();
        a.m1();
    }
}
class A{
   public void m1()
   {
       System.out.println("m1() 方法被调用");
       B b = new B();//创建B对象
       b.hi();
       System.out.println("m1() 方法继续执行");
   }
}
class B{
    public void hi()
    {
        System.out.println("B类中的 hi() 被执行");
    }
}

运行结果:

11,特别说明一下:跨类的方法调用和方法的访问修饰符相关,后面会细说。

213,克隆对象

编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象,注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同。

复制代码
public class test1{
    public static void main(String[] args){
        Person p = new Person();
        p.name = "milan";
        p.age = 100;
        //创建tools对象
        MyTools tools = new MyTools();
        Person p2 = tools.copyPerson(p);

        //到此 p 和 p2 是 Person对象,但是是两个独立的对象,属性相同
        System.out.println("p的属性 age = " + p.age + " 名字 = " + p.name);
        System.out.println("p2的属性 age = " + p2.age + " 名字 = " + p2.name);
    }
}
class Person{
    String name;
    int age;
}
class MyTools{
    //方法的返回类型 Person
    // 方法的名字 copyPerson
    // 方法的形参(Person p)
    //方法体,创建一个新对象,并复制属性,返回即可
    public Person copyPerson(Person p)
    {
        Person p2 = new Person();
        p2.name = p.name;//把原来对象的名字赋给p2.name
        p2.age = p.age;//把原来对象的年龄赋给p2.age
        return p2;
    }
}

运行结果:

218,递归执行机制

1,执行一个方法时,就创建一个新的受保护的独立空间(栈空间)

2,方法的局部变量是独立的,不会相互影响,比如n变量

3,如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据

4,递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError

5,当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。

220,猴子吃桃

有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到了第10天时,想再吃时(即还没吃完),发现只有1个桃子了。问题:最初共多少个桃子?

(已知第10天只剩1个桃子,猴子每天都吃其中的一半,并多吃一个。假设第9天有 x 个桃子,第 10天有 y 个桃子,(x / 2) - 1 = y,x = (y + 1) * 2)

思路:逆推

1,day = 10 时,有 1 个桃子

2,day = 9 时,有 (day10 + 1)* 2 = 4

3,day = 8 时,有 (day9 + 1)* 2 = 10

4,规律就是 前一天的桃子 = (后一天的桃子 + 1)* 2

5,递归

复制代码
public class test1{
    public static void main(String[] args){
       T t = new T();
       int day = 1;
       int peachNum = t.peach(day);
       if(peachNum != -1)
       {
           System.out.println("第" + day + "天有" + peachNum + "个桃子");
       }
    }
}
class T{
    public int peach(int day)
    {
        if(day == 10)
        {
            return 1;
        }else if(day >= 1 && day <= 9)
        {
            return (peach(day + 1) + 1) * 2;
        }
        else
        {
            System.out.println("day在1-10");
            return -1;
        }
    }
}

运行结果:

221,老鼠出迷宫

思路:

1,先创建迷宫,用二维数组表示 int[][] map = new int[8][7]

2,先规定map 数组的元素值:0 表示可以走;1 表示障碍物

3,将最上面的一行和最下面的一行,全部设置为1

4,将最右面的一列和最左面的一列,全部设置为1

使用递归回溯的思想来解决老鼠出迷宫

1,findWay方法就是专门来找出迷宫的路径

2,如果找到,就返回 true,否则返回 false

3,map 就是二维数组,即表示迷宫

4,i,j 就是老鼠的位置,初始化的位置为(1,1)

5,因为我们是递归的找路,所以先规定 map 数组的各个值的含义:0 表示可以走;1 表示障碍物;2 表示可以走;3 表示走过,但是走不通,是死路

6,当 map[6][5] = 2 就说明找到通路,就可以结束,否则就继续找

7,先确定老鼠找路策略:下 -> 右 -> 上 -> 左。(有不同的策略,先确定一个自己的策略)

复制代码
public class test1{
    public static void main(String[] args){
       int[][] map = new int[8][7];//8行7列
       for(int i = 0; i < 7; i++) //i 是列,
       {
           map[0][i] = 1;//将最上面的一行和最下面的一行,全部设置为1
           map[7][i] = 1;
       }
       for(int i = 0; i < 8; i++)//i 是行
       {
           map[i][0] = 1;//将最右面的一列和最左面的一列,全部设置为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();
        }
        //使用findWay给老鼠找路
        T t1 = new T();
        t1.findWay(map,1,1);//对数组的修改,会把原数组修改了,参考引用赋值
        System.out.println("\n====找路的情况如下=====");
        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 T{
    public boolean findWay(int[][] map, int i, int j)
    {
        //0 表示可以走,还没走;1 表示障碍物;2 表示可以走;3 表示走过,但是走不通,是死路
        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 //map[i][j] = 1,2,3
            {
                return false;
            }
        }
    }
}

运行结果:

225,汉诺塔

思路见代码:

复制代码
public class test1{
    public static void main(String[] args){
        T t = new T();
        t.move(3, 'A', 'B', 'C');
    }
}
class T{
    //num 表示要移动的个数,a,b,c分别表示A塔,B塔,C塔
   public void move(int num, char a, char b, char c)
   {
       if(num == 1)//只有一个盘子,就直接从A移到C
       {
           System.out.println(a + "->" + c);
       }
       else
       {
           //1,如果有多个盘,可以看成两个,最下面的和上面的所有盘(num-1)
           move(num - 1,a, c, b);//先移动上面的所有的盘到 b,借助 c,借助是指我们不能把上面的所有盘整体移过去,需要借助c
           System.out.println(a + "->" + c); //3,把最下面的这个盘,移动到 c。
           //4,再把 b 塔的所有盘,移动到 c,借助 a
           move(num - 1, b, a, c);//把B塔的盘移动也当成两个盘在移动,和上面的 1 同理
       }
   }
}

运行结果:

229,重载使用细节

1,方法名:必须相同

2,形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求)

3,返回类型:无要求

233,可变参数使用

概念:java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。就可以通过可变参数实现。

基本语法: 访问修饰符 返回类型 方法名(数据类型... 形参名){}

复制代码
public class test1{
    public static void main(String[] args){
        HspMethods m = new HspMethods();
        System.out.println(m.sum(1,2,3,4));
    }
}
class HspMethods{
    //可以计算 2个数的和,3个数的和,4,5....
    //可以使用方法重载
//    public int sum(int n1, int n2)//2个数的和
//    {
//        return n1 + n2;
//    }
//    public int sum(int n1, int n2, int n3)//3个数的和
//    {
//        return n1 + n2;
//    }
    //上面的两个方法名称相同,功能相同,参数个数不同 -> 使用可变参数优化
    //1,  int... 表示接受的是可变参数,类型是int,即可以接收多个int(0-多)
    //2,  使用可变参数是,可以当做数组来使用,即 nums 可以当做数组
    public int sum(int... nums)
    {
        int res = 0;
        for(int i = 0; i < nums.length; i++)
        {
            res += nums[i];
        }
        return res;
    }
}

运行结果:

234,可变参数细节

1,可变参数的实参可以为 0 个或任意多个(见 233 代码)

2,可变参数的实参可以为数组

复制代码
public class test1{
    public static void main(String[] args){
        int[] arr = {1, 2, 3};
        T t1 = new T();
        t1.f1(arr);
    }
}
class T{
   public void f1(int... nums)
   {
       System.out.println("长度 = " + nums.length);
   }
}

运行结果:

3,可变参数的本质就是数组

4,可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后(否则报错)

复制代码
public void f2(String str, double... nums){}

5,一个形参列表中只能出现一个可变参数

235,可变参数练习

问题:有三个方法,分别实现返回姓名和两门课成绩(总分),返回姓名和三门课成绩(总分),返回姓名和五门课成绩(总分)。封装成一个可变参数的方法。类名 HspMethod 方法名 showScore

分析:两门课,三门课,五门课成绩,理解为一个可以接收多个double 类型的可变参数,问题要求返回姓名和三门课成绩(总分),所以方法的返回类型是 String,形参(String, double... )

复制代码
public class test1{
    public static void main(String[] args){
        HspMethod t1 = new HspMethod();
        System.out.println(t1.showScore("milan", 60,80));
        System.out.println(t1.showScore("jack", 60,80,80));
        System.out.println(t1.showScore("lucy", 60,80,80,90,80));
    }
}
class HspMethod{
   public String showScore(String name, double... scores)
   {
       double totalScore = 0;
       for(int i = 0; i < scores.length; i++)
       {
           totalScore += scores[i];
       }
       return name + " 有" + scores.length + "门课的成绩总分 = " + totalScore;
   }
}

运行结果:

236,作用域基本使用

1,在java编程中,主要的变量就是属性(成员变量)和局部变量

2,我们说的局部变量一般是指在成员方法中定义的变量

3,java 中作用域的分类。

全局变量:也就是属性,作用域为整个类体;

局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中!

4,全局变量(属性)可以不赋值,直接使用,因为有默认值,局部变量必须赋值后才能使用,因为没有默认值。

237,作用域使用细节

1,属性和局部变量可以重名,访问时遵循就近原则。

复制代码
public class test1{
    public static void main(String[] args){
        Person p1 = new Person();
        p1.say();
    }
}
class Person{
    String name = "jack";
    public void say()
    {   
        String name = "king";
        System.out.println("say() name = " + name);
    }
}

运行结果:

2,在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名

3,属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。即在一次方法调用过程中。

4,全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)。 局部变量:只能在本类中对应的方法中使用

复制代码
public class test1{
    public static void main(String[] args){
        Person p1 = new Person();
        T t1 = new T();
        t1.test();//第1种跨类访问对象属性的方式
        t1.test(p1);//第2种跨类访问对象属性的方式

    }
}
class T{
    public void test()
    {
        Person p1 = new Person();//Person类中的全局变量name,可以在T类中使用,通过对象调用
        System.out.println(p1.name);//jack
    }
    public void test(Person p)
    {
        System.out.println(p.name);//jack
    }
}
class Person{
    String name = "jack";
}

运行结果:

5,修饰符不同 :全局变量/属性可以加修饰符;局部变量不可以加修饰符

239,构造器基本介绍

构造方法又叫构造器,是类的一种特殊方法,它的主要作用是完成对新对象的初始化

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

说明:1,构造器的修饰符可以默认,也可以是public protected private

2,构造器没有返回值

3,方法名和类名字必须一样

4,参数列表 和 成员方法 一样的规则

5,在创建对象时,系统会自动调用该类的构造器完成对对象的初始化

复制代码
public class test1{
    public static void main(String[] args){
        Person p1 = new Person("smith", 80);//当我们new 一个对象时,直接通过构造器指定名字和年龄
        System.out.println("p1的信息如下");
        System.out.println("p1对象name = " + p1.name);
        System.out.println("p1对象age = " + p1.age);
    }
}
class Person{
    String name;
    int age;
    public Person(String pName, int pAge)
    {
        System.out.println("构造器被调用~~ 完成对象的属性初始化");
        name = pName;
        age = pAge;
    }
}

运行结果:

6,一个类可以定义多个不同的构造器,即构造器重载

7,如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器),比如 Dog() { }

8,一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显示的定义一下,即:Dog() {}

244,对象创建的流程分析

看一个案例

复制代码
class Person{//类Person
    int age = 90;
    String name;
    Person(String n, int a)//构造器
    {
        name = n;//给属性赋值
        age = a;
    }
}
Person p = new Person("小倩",20);

流程分析(面试题)

1,加载Person类信息(Person.class),只会加载一次

2,在堆中分配空间(地址)

3,完成对象初始化

3.1,默认初始化 age = 0,name = null 3.2,显示初始化 age = 90,name = null 3.3,构造器的初始化 age = 20,name = 小倩

4,在对象在堆中的地址,返回给 p(p是对象名,也可以理解成是对象的引用)

249,this 使用细节

1,哪个对象调用,this就代表哪个对象

2,this 关键字可以用来访问本类的属性,方法,构造器

3,this用于区分当前类的属性和局部变量

4,访问成员方法的语法:this.方法名(参数列表)

复制代码
public class test1{
    public static void main(String[] args){
        T t = new T();
        t.f2();
    }
}
class T{
    public void f1()
    {
        System.out.println("f1() 方法..");
    }
    public void f2()
    {
        System.out.println("f2() 方法..");
        //调用本类的 f1
        f1();//第一种方式
        this.f1();//第二种方式 this.方法名(参数列表)
    }
}

运行结果:

5,访问构造器语法:this(参数列表);注意只能在构造器中使用(即只能在构造器中访问另外一个构造器,必须放置第一条语句)

复制代码
public class test1{
    public static void main(String[] args){
        T t = new T();
    }
}
class T{
    public T()
    {
        this("jack", 100);//这里去访问T(String name, int age) 构造器
        System.out.println("T() 构造器");
    }
    public T(String name, int age)
    {
        System.out.println("T(String name, int age) 构造器");
    }
}

运行结果:

6,this不能在类定义的外部使用,只能在类定义的方法中使用

250,this课堂练习

问题:定义Person类,里面有name,age属性,并提供compareTo比较方法,用于判断是否和另一个人相等,提供测试类TestPerson 用于测试,名字和年龄完全一样,就返回true,否则返回false

复制代码
public class TestPerson{
    public static void main(String[] args){
        Person p1 = new Person("mary", 20);
        Person p2 = new Person("smith", 30);
        System.out.println(p1.compareTo(p2));

    }
}
class Person{
    String name;
    int age;
    public Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
    public boolean compareTo(Person p)//和另一个人的姓名年龄比较,所以形参是Person对象,还要知道这个对象的信息(通过构造器)
    {
        return this.name.equals(p.name) && this.age == p.age;
    }
}

运行结果:

253,本章作业03

问题:编写类Book,定义方法updatePrice,实现更改某本书的价格,具体:如果价格>150,则更改为150,如果价格>100,更改为100,否则不变。

分析:更改某本书的价格,可以理解为更改一个对象的价格属性,需要先用构造器完成对这个对象的价格的初始化,再到方法中进行更改。

复制代码
public class test1{
    public static void main(String[] args){
        Book book = new Book("小王子", 120);
        book.info();
        book.updatePrice();
        book.info();
    }
}
class Book
{
    String name;
    double price;
    public Book(String name, double price)
    {
        this.name = name;
        this.price = price;
    }
    public void updatePrice()
    { //如果方法中,没有 price 局部变量,this.price 等价 price
        if(price > 100)
        {
            price = 100;
        }else if(price > 150)
        {
            price = 150;
        }
    }
    public void info()
    {
        System.out.println("书名 = " + this.name + " 价格 = " + this.price);
    }
}

运行结果:

260,本章作业10

复制代码
public class test1 {
    public static void main(String[] args) {
        Circle c = new Circle();
        PassObject po = new PassObject();
        po.printAreas(c,5);
    }
}
class Circle
{
    double radius;
    public Circle()//在第2问我们没有办法确认半径值,在for循环那里才知道半径值是变化的,所以要重写默认构造器
    {

    }
    public Circle(double radius)//第1问要用到这个
    {
        this.radius = radius;
    }
    public double findArea()//返回面积
    {
        return radius * radius * Math.PI;
    }
    public void setRadius(double radius)//添加方法 setRadius,修改对象的半径值
    {
        this.radius = radius;
    }
}
class PassObject
{
    public void printAreas(Circle c, int times)
    {
        System.out.println("radius\tarea");
        for(int i = 1; i <= times; i++)
        {
            c.setRadius(i);//可以用到Circle对象,并能把i传进去,如果每次都new 一个新对象(用构造器),就不划算
            System.out.println(i + "\t" + c.findArea());

        }
    }
}

运行结果: