【Java基础】面向对象编程(韩顺平P191-P423个人笔记)

前言

第二部分(191-423) 是面向对象编程,集中学习这一部分。

参考资料:https://www.bilibili.com/video/BV1fh411y7R8p=192&vd_source=7b719da0e4965c852fae45929d82a5b7

1. 面向对象编程(基础部分)(191-262)周六

类与对象

案例:
养猫问题:看一个养猫猫问题 张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。还有一只叫小花,今年 100 岁,花色。请编写一个程序,当用户 输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。

使用现有技术解决:
1)单独的定义变量解决
2)使用数组解决

现有技术的缺点分析:
1)不利于数据的管理
2)效率低=> 引出新知识 类与对象

类与对象的关系示意图:

在这里插入图片描述

package chapter06;

public class Object01 {
    public static void main(String[] args){
    
        //使用OOP面向对象解决
        //实例化一只猫 
        // 1. new Cat() 创建一只猫
        // 2. Cat cat1 = new Cat(); 把创建的猫赋给cat1
        Cat cat1 = new Cat();
        cat1.name = "小白";
        cat1.age = 3;
        cat1.color = "白色";

        Cat cat2 = new Cat();
        cat2.name = "小黑";
        cat2.age = 4;
        cat2.color = "黑色";

        //怎么访问对象的属性
        System.out.println("第一只猫的信息:"+cat1.name+" "
                            +cat1.age+" "+cat1.color);
        System.out.println("第二只猫的信息:"+cat2.name+" "
                            +cat2.age+" "+cat2.color);
    }    
}

//定义一个猫类 一个数据类型
class Cat{
    //属性
    String name;
    int age;
    String color;

}

注意事项:

  1. 类是抽象的,概念的,代表一类事物,比如人类,猫类…, 即它是数据类型.
  2. 对象是具体的,实际的,代表一个具体事物, 即 是实例.
  3. 类是对象的模板,对象是类的一个个体

对象在内存中存在形式:
在这里插入图片描述

属性/成员变量/字段

1)从概念或叫法上看: 成员变量 = 属性 = field(字段) (即 成员变量是用来表示属性的,授课中,统一叫 属性)
2)属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。比如我们前面定义猫类 的 int age 就 是属性

注意事项:
1)属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;
这里老师简单的介绍访问修饰符: 控制属性的访问范围 有四种访问修饰符 public, proctected, 默认, private ,后面我会详细介绍
2) 属性的定义类型可以为任意类型,包含基本类型或引用类型
3) 属性如果不赋值,有默认值,规则和数组一致。具体说: int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000, boolean false,String null

package chapter06;

public class PropertiesDetail {
    public static void main(String[] args){
        //创建Person对象
        Person person1 = new Person();
        System.out.println("当前这个人的信息:");
        System.out.println("age="+person1.age+" name="+person1.name+
                        " sal="+person1.sal+" isPass="+person1.isPass);
    }
}

class Person{
    int age;
    String name;
    double sal;
    boolean isPass;
}

如何访问对象:

  1. 先声明再创建
    Cat cat ; //声明对象
    cat cat = new Cat(); //创建
  2. 直接创建
    Cat cat = new Cat();
    如何访问属性:
    基本语法
    对象名.属性名;
    案例演示赋值和输出
    cat.name ;
    cat.age;
    cat.color;

思考题:
在这里插入图片描述

p2的内存指向p1
在这里插入图片描述

Java 内存的结构分析

  1. 栈: 一般存放基本数据类型(局部变量)
  2. 堆: 存放对象(Cat cat , 数组等)
  3. 方法区:常量池(常量,比如字符串), 类加载信息
    Java 创建对象的流程简单分析
    Person p = new Person();
    p.name = “jack”;
    p.age = 10;
    1)先加载Person类信息(属性和方法信息,只会加载一次)
    2)在堆中分配空间,进行默认初始化(看规则)
    3)把地址赋给p,p 就指向对象
    4)进行指定初始化,比如p.name=“jack” p.age=10
    思考题:
    在这里插入图片描述

在这里插入图片描述

成员方法(209)

在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外( 年龄,姓名…),我们人类还有一 些行为比如:可以说话、跑步…,通过学习,还可以做算术题。这时就要用成员方法才能完成。现在要求对 Person 类完善。

快速入门版:

package chapter06;

public class Method01 {
    public static void main(String[] args){
        //方法使用
        //注意:不能在同一个Package里有多个class 里拥有同样名字的类
        //1. 方法写好后,如果不去调用,不会输出
        //2. 先创建对象,然后调用方法即可
        Personx p1 = new Personx(); 
        p1.speak(); 
        p1.cal01();
        p1.cal02(3);
        int sum = p1.getSum(2, 3);
        System.out.print("getSum方法返回的值="+sum);
       
    }
}

class Personx{
    int age;
    String name;

    //方法(成员方法)
    //添加 speak 成员方法,输出 “我是一个好人”
    //1. public 表示方法是公开
    //2. void : 表示方法没有返回值
    //3. speak() : speak 是方法名, () 形参列表
    //4. {} 方法体,写我们要执行的代码
    public void speak(){
        System.out.println("我是一个好人");
    }

    //添加 cal01 成员方法,可以计算从 1+..+1000 
    public void cal01(){
        int res = 0;
        for(int i =1;i<=1000;i++){
            res += i;
        }
        System.out.println("cal01方法 计算结果="+res);
    }

    //添加 cal02 成员方法,该方法可以接收一个数 n,计算从 1+..+n 的结果
    public void cal02(int n){
        int res = 0;
        for(int i =1;i<=n;i++){
            res += i;
        }
        System.out.println("cal02方法 计算结果="+res);
    }

    //添加 getSum 成员方法,可以计算两个数的和
    //1. public 表示方法是公开的
    //2. int :表示方法执行后,返回一个 int 值
    //3. getSum 方法名
    //4. (int num1, int num2) 形参列表,2 个形参,可以接收用户传入的两个数
    //5. return res; 表示把 res
    public int getSum(int num1, int num2){
        int res;
        res = num1+num2;
        return res;
    }
}

方法调用机制原理:
在这里插入图片描述

为什么需要成员方法?
将多次使用的功能写到一个类的方法中,提高效率。

  1. 提高代码的复用性
  2. 可以将实现的细节封装起来,然后供其他用户来调用即可

成员方法的定义:

访问修饰符 返回数据类型 方法名(形参列表..{//方法体 
    语句; 
    return 返回值; 
}
  1. 形参列表:表示成员方法输入 cal(int n) , getSum(int num1, int num2)
  2. 返回数据类型:表示成员方法输出, void 表示没有返回值
  3. 方法主体:表示为了实现某一功能代码块
  4. return 语句不是必须的。
    注意事项和细节:
package chapter06;

public class MethodDetail {
    public static void main(String[] args){
        AA a = new AA();
        int[] res = a.getSumAndSub(3, 2);
        System.out.println("和="+res[0]);
        System.out.println("差="+res[1]);

        细节: 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
        byte b1 = 1;
        byte b2 = 2;
        a.getSumAndSub(b1, b2); // byte->int
        // a.getSumAndSub(1.1, 1.8) // double->int (x)

        //细节: 实参和形参的类型要一致或兼容、个数、顺序必须一致
        //a.getSumAndSub(100) // x 个数不一致
        a.f3("tom", 10);// ok
        // a.f3(100,"jack"); //实际参数和形式参数顺序不对
    }
    
}

class AA{
    // 细节:方法不能嵌套定义
    public void f4(){
        //错误
        // public void f5(){

        // }
    }
    public void f3(String str,int n){

    }
    //1. 一个方法最多有一个返回值(返回多个结果=》返回数组)
    public int[] getSumAndSub(int n1,int n2){
        int[] resArr = new int[2];
        resArr[0]=n1+n2;
        resArr[1]=n1-n2;
        return resArr;
    }
    //2.返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
    // 如上所示getSumAndSub

    //3.如果方法要求返回数据类型,则方法中最后执行语句必须为return值
    // 而且要求返回值类型必须和return的值 类型一致或兼容
    public double f1(){
        double d1 = 1.1 * 3;
        int n =100;
        return n;//int -> double
        // return d1; // ok? double->int (x) 

    }
    //如果方法是void,则方法体中可以没有return语句,或者只写return
    // 在实际工作中,我们的方法都是为了完成某个功能,所以方法名要有一定含义
    // 最好见名知意
    public void f2(){
        System.out.println("hello1");
        System.out.println("hello1");
        System.out.println("hello1");
        int n = 10;
        // return ;
    }
}
  • 访问修饰符(作用是控制 方法使用的范围)
    如果不写默认访问,[有四种:public,protected,默认,private]
  • 返回数据类型
  1. 一个方法最多有一个返回值 [思考,如何返回多个结果 返回数组 ]
  2. 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
  3. 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的 值类型一致或兼容
  4. 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return
  • 方法名
    遵循驼峰命名法,最好见名知义,表达出该功能的意思即可, 比如 得到两个数的和 getSum, 开发中按照规

  • 形参列表

  • 方法体
    在这里插入图片描述

  • 方法调用细节说明:
    在这里插入图片描述

package chapter06;

public class MethodDetail02 {
    public static void main(String[] args){
        A a = new A();
        a.sayOk();
        a.m1();
    }
    
}

class A{
    public void print(int n ){
        System.out.println("print()方法被调用 n="+n);
    }

    public void sayOk(){ //sayOk调用print(直接调用即可)
        print(10);
        System.out.println("继续执行sayOk()~~~~");
    }

    //跨类中的方法A类调用B类方法:需要通过对象名调用
    public void m1(){
        System.out.println("m1()方法被调用");
        B b = new B();
        b.hi();
        System.out.println("m1()继续执行");
    }
}

class B{
    public void hi(){
        System.out.println("B类中的hi()被执行");
    }
}

成员方法传参机制

基本数据类型的传参机制

在这里插入图片描述

结论:基本数据类型传递的是值(值拷贝),形参的任何拷贝不影响实参。

引用数据类型的传参机制

在这里插入图片描述

结论:引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!

思考题:方法的p=null

不改变main中p的值,只是方法中p的指针不指向main的p,因此p的输出还是10。
在这里插入图片描述

思考题:p=new Person()
方法中新建一个类,此时会创建一个新的堆,方法中的p指向新堆,并不改变main的堆。但是main中输出的还是main中的p,并不改变值,p.age仍然等于10。
在这里插入图片描述

应用实例:

  1. 编写类 MyTools 类,编写一个方法可以打印二维数组的数据。
  2. 编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象, 注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
package chapter06;

public class MethodExercise02 {
    public static void main(String []args){
    // 1) 编写类 MyTools 类,编写一个方法可以打印二维数组的数据。
    // 2) 编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。
    // 克隆对象, 注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
        int[][] arr = {{1,2,3},{34,34,6}};
        MyTools m = new MyTools();
        m.output(arr);

        Person p = new Person();
        p.name="jack";
        p.age=10;
        Person p2 = m.copyPerson(p);
        System.out.println("person p ="+p.name+" "+p.age);
        System.out.println("person p2 ="+p2.name+" "+p2.age);

        //判断对象是否为同一个
        System.out.print(p==p2); // false
    }
}

class MyTools{
    public int[][] output(int[][] arr){
        for(int i=0;i<arr.length;i++){
            for(int j=0;j<arr[i].length;j++){
                System.out.print(arr[i][j]+" ");
            }
            System.out.println();
        }
        return arr;
    }
    public Person copyPerson(Person p){
        Person p2 = new Person();
        p2.name = p.name;
        p2.age = p.age;
    
        return p2;
    }

}

class Person{
    String name;
    int age;
}

方法递归调用

递归就是方法自己调用自己,每次调用时传入不同的变量。
在这里插入图片描述

在这里插入图片描述

递归重要规则:
1)执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
2)方法的局部变量是独立的,不会相互影响,比如n变量
3)如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用类型的数据
4)递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError
5)当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁。同时,当方法执行完毕或返回时,该方法也就执行完毕。

课堂练习:
1)使用递归实现斐波那契数1,1,2,3,5,8,13…给你一个整数n,求出它的值是多少
2)猴子吃桃问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时,想再吃时(即还没吃),发现只有1个桃子了。问题:最初共有多少个桃子?

package chapter06;

public class RecursionExercise01 {
    public static void main(String[] args) {
        // 1)使用递归实现斐波那契数1,1,2,3,5,8,13....
        // 给你一个整数n,求出它的值是多少
        T t = new T();
        int res = t.fibonacci(7);
        System.out.println("fibonacci = "+res);
        // 2)猴子吃桃问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!
        // 以后每天猴子都吃其中的一半,然后再多吃一个。
        // 当到第10天时,想再吃时(即还没吃),发现只有1个桃子了。
        // 问题:最初共有多少个桃子?
        int tao = t.tao(1);
        System.out.println("一共"+tao+"个桃子。");

        
    }
    
}

class T{
    public int fibonacci(int n){
        // 3=2+1;5=3+2;....
        if(n>=1){
            if(n==1||n==2){
                return 1;
            }else{
                return fibonacci(n-1)+fibonacci(n-2);   
            }
        }else{
            System.out.println("请输入>1的整数");
            return -1;
        }
    }

    public int tao(int day){
        // day10, tao=1
        // day9, tao=(day10+1)*2
        // day8, tao=(day9+1)*2
        // tao=(后一天的tao+1)*2
        if(day == 10){
            return 1;
        }else if(day>=1 && day<=9){
            return((tao(day+1)+1)*2);
        }else{
            System.out.println("请输入1-10的数字");
            return -1;
        }
    }
}

方法重载(overload)(232)

java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!
重载的好处:
1)减轻了起名的麻烦
2)减轻了记名的麻烦

案例:类:MyCalculator 方法:calculate

  1. calculate(int n1, int n2) //两个整数的和
  2. calculate(int n1, double n2) //一个整数,一个 double 的和
  3. calculate(double n2, int n1)//一个 double ,一个 Int 和
  4. calculate(int n1, int n2,int n3)//三个 int 的
package chapter06;

public class OverLoad01 {
    public static void main(String[] args){
        MyCalculator mycal = new MyCalculator();
        System.out.println(mycal.calculator(1, 1.8));

    }
}

class MyCalculator{
    public int calculator(int n1,int n2){
        int res = n1 + n2;
        return res;
    }
    public double calculator(int n1,double n2){
        double res = n1 + n2;
        return res;
    }
    public double calculator(double n1,int n2){
        double res = n1 + n2;
        return res;
    }
    public int calculator(int n1,int n2,int n3){
        int res = n1 + n2 + n3;
        return res;
    }
}

注意事项和使用细节:
1)方法名:必须相同
2)形参列表:必须不同(形参类型或个数、顺序,至少有一样不同,参数名无要求)
3)返回类型:无要求

可变参数

java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
基本语法:

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

}

快速入门案例:
看一个案例 类 HspMethod,方法 sum 【可以计算 2 个数的和,3 个数的和 , 4. 5, 。。

package chapter06;

public class VarParameter01 {
    public static void main(String[] args){
        HspMethod s = new HspMethod();
        System.out.print(s.sum(1,2,5));

    }   
}

class HspMethod{
    public int sum(int n1,int n2,int n3){
        return n1+n2+n3;
    }
    public int sum(int n1,int n2,int n3,int n4){
        return n1+n2+n3+n4;
    }

    // ... 方法名称相同,功能相同,参数个数不同 -> 使用可变参数优化
    // 1. int... 表示接受的是可变参数,类型是int,即可以接收多个int(0-多)
    // 2. 使用可变参数时,可以当做数组来使用 即 nums 可以当做数组
    public int sum(int... nums){
        System.out.print("接收的参数个数="+nums.length);
        int res = 0;
        for(int i =0;i<nums.length;i++){
            res+=nums[i];
        }
        return res;
    }
}

在这里插入图片描述

作用域

在这里插入图片描述

注意事项和细节使用:
在这里插入图片描述

在这里插入图片描述

构造方法/构造器

基本语法:

[修饰符] 方法名(形参列表){ 
     方法体;
} 
  1. 构造器的修饰符可以默认, 也可以是 public protected private
  2. 构造器没有返回值
  3. 方法名 和类名字必须一样
  4. 参数列表 和 成员方法一样的规则
  5. 构造器的调用, 由系统完成

构造方法又叫构造器,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。
特点如下:
1)方法名和类名相同
2)没有返回值,也不能写void
3)在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。

package chapter06;

public class Constructor01 {
    public static void main(String[] args){
        Pperson p1 = new Pperson("smith", 80);
        System.out.println("p1对象name="+p1.name);
        System.out.println("p1对象age="+p1.age);
    }
}
class Pperson{
    String name;
    int age;
    public Pperson(String pName,int pAge){
        System.out.println("构造器被调用~ 完成对象的属性初始化");
        name = pName;
        age = pAge;
    }
}

注意事项和使用细节:
在这里插入图片描述

对象创建的流程分析

在这里插入图片描述

this关键字

引出:如果构造器的形参能够直接使用属性名就好了
java虚拟机会给每个对象分配this,代表当前对象。
小结:哪个对象调用,this就代表哪个对象
在这里插入图片描述

package chapter06;

public class This {
    public static void main(String[] args){
        Dog dog1 = new Dog("小黄",11);
        System.out.println("this.hashcode="+dog1.hashCode());
        dog1.info();
        Dog dog2 = new Dog("小白", 20);
        System.out.println("this.hashcode="+dog2.hashCode());
        dog2.info();
    }
    
}
class Dog{
    String name;
    int age;
   public Dog(String name, int age){
        this.name = name;
        this.age = age;
        System.out.println("this.hashcode="+this.hashCode());
   } 
   public void info(){
        System.out.println("this.hashCode="+this.hashCode());
        System.out.println(name+"\t"+age+"\t");
   }
}

this的注意事项和细节说明:
1)this关键字可以用来访问本类的属性、方法、构造器
2)this用于区分当前类的属性和局部变量
3)访问成员方法的语法:this.方法名(参数列表);
4)访问构造器的语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另一个构造器,必须放在第一条语句)
在这里插入图片描述

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

本章作业

我只写了一个,前面的题目想了一下思路。这个题的代码思路来自与自己,和老师讲的差不多,界面上面有一些区别。
在这里插入图片描述

package chapter06;

import java.security.PublicKey;
import java.util.Random;
import java.util.Scanner;

public class Homewotk14 {
    public static void main(String[] args){
        // 14. Tom设计他的成员变量.成员方法,可以电脑猜拳
        // 电脑每次都会随机生成0,1,2
        // 0表示石头 1表示剪刀 2表示布
        // 并要可以显示Tom的输赢次数(清单)
        Tom t = new Tom();
        t.shwoInfo(4);

    }
    
}
class Tom{
    int tomGuessNum;
    int comGuessNum;
    int winCountNum;

    public int comGuess(){
        Random r = new Random();
        int comGuessNum = r.nextInt(2); // 随机生成0-2
        System.out.println("电脑输出:"+comGuessNum);
        return comGuessNum; 
    }
    public int tomGuess(){
        Scanner myScanner = new Scanner(System.in);
        System.out.println("请输入:0/1/2, 其中0表示石头 1表示剪刀 2表示布");
        int tomGuessNum = myScanner.nextInt(); //用户输入
        if(tomGuessNum==0 || tomGuessNum==1 || tomGuessNum==2){
            return tomGuessNum;
        }else{
            System.out.println("输入错误");
            return -1;
        }
    }

    public String vsCom(){ // 对局信息
        int tomGuessNum = tomGuess();
        int comGuessNum = comGuess();
        if((tomGuessNum==0&&comGuessNum==1)||(tomGuessNum==1&&comGuessNum==2)||(tomGuessNum==2&&comGuessNum==0)){
            winCountNum+=1;
            return "Tom赢";
        }else if((tomGuessNum==2&&comGuessNum==1)||(tomGuessNum==0&&comGuessNum==2)||(tomGuessNum==1&&comGuessNum==0)){
            return "电脑赢";
        }else{
            return "平手";
        } 
    }
    
    public void shwoInfo(int n){
        for(int i=0;i<n;i++){
            String res = vsCom();
            System.out.println(res+"!");
        }
        System.out.println("对局"+n+"次,"+"Tom一共赢了"+winCountNum+"次");
    }
}

2. 面向对象编程(中级部分)(263-359)

包的三大作用:
1)区分相同名字的类
2)当类很多时,更好的管理(Java API文档)
3)控制访问范围

包的基本语法:package com.hspedu;
说明:

  • package 关键字,表示打包
  • com.kspedu; 表示包名

包的本质:实际上就是创建不同的文件夹/目录来保存类文件
在这里插入图片描述

快速入门:
在src下面新建两个包,包里面都含有Dog类,在Test.java调用的情况如下:

在这里插入图片描述

包的命名

命名规则:只能包含数字、字母、下划线、小圆点.,但不能用数字开头,不能是关键字或保留字
命名规范:一般是小写字母+小圆点,比如com.公司名.项目名.业务模块名 (com.sina.crm.user //用户模块)

常用的包

一个包下,包含很多的类,java 中常用的包有:

  1. java.lang.* //lang 包是基本包,默认引入,不需要再引入.
  2. java.util.* //util 包,系统提供的工具包, 工具类,使用 Scanner
  3. java.net.* //网络包,网络开发
  4. java.awt.* //是做 java 的界面开发,GUI

如何引用? 语法:import 包;
引入一个包的主要目的是要使用该包下面的类,比如import java.util.Scanner; 就只是引入一个类Scanner,import java.util.*;//表示引入Java.util包的所有都引入

建议:使用到哪个类,就导入哪个类,不建议使用*导入。

注意事项和使用细节:

  1. package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
  2. import指令位置放在package的下面,在类定义前面,可以有多句且没有顺序要求
package chapter07; //package 声明当前类所在的包

// import指令
import java.util.Scanner;

// 类定义
public class Test{
}

访问修饰符

基本介绍:java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):

  1. 公开级别:用 public 修饰,对外公开
  2. 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
  3. 默认级别:没有修饰符号,向同一个包的类公开.
  4. 私有级别:用 private 修饰,只有类本身可以访问,不对外公开.
    在这里插入图片描述

使用的注意事项:
1)修饰符可以用来修饰类中的属性,成员方法以及类
2)只有默认的和public才能修饰类!并且遵循上述访问权限的特点。
3)因为没有学习集成,因此关于在子类中的访问权限,后续再 讲解
4)成员方法的访问规则和属性完全一样。
A.java

package com.hspedu.modifier;

public class A {
    //四个属性,分别使用不同的访问修饰符来修饰
    public int n1 = 100;
    protected int n2 = 200;
    int n3 = 300;
    private int n4 = 400;
    public void m1(){
        //在同一类中,可以访问public protected 默认 provate修饰属性和方法
        System.out.println("n1="+n1+" n2="+n2+" n3="+n3+" n4="+n4);
    }
    public void m2(){}
    void m3(){}
    private void m4(){}
    public void hi(){
        //在同一类中,可以访问public protected 默认 provate修饰属性和方法
        m1();
        m2();
        m3();
        m4();
    }
}

B.java

package com.hspedu.modifier;

public class B {
    public void say(){
        A a = new A();
        //在同一个包下,可以访问public,protected和默认,不能访问private
        System.out.println("n1="+a.n1+" n2="+a.n2+" n3="+a.n3);
    }
}
 
package com.hspedu.pkg;

import com.hspedu.modifier.A;

public class Test {
    public static void main(String[] args){
        A a = new A();
        //在不同包下,可以访问public修饰的属性或方法
        System.out.println(a.n1);
        // 不能访问 protected 默认和private修饰的属性和方法
    }
}
package com.hspedu.modifier;

public class Test {
    public static void main(String[] args){
        A a = new A();
        a.m1();
        a.hi();
        B b = new B();
        b.say();
    }
}
// 只有默认和public可以修饰类
class Tiger{

}

面向对象编程三大特征

面向对象编程有三大特征:封装、继承和多态

封装介绍

封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。

封装的理解和好处

1)隐藏实现细节:方法(连接数据库)<–调用(传入参数…)
2)可以对数据进行验证,保证安全合理

Person {name,age}
Person p = new Person();
p.name="jack";
p.age=1200;

封装的实现步骤(三步)

在这里插入图片描述

快速入门案例(284)

案例:不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认年龄,必须再1-120,年龄和工资不能直接查看,name的长度在2-6字符之间。

package com.hspedu.encap;

public class Encapsulation01 {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("jack");
        person.setAge(30);
        person.setSalary(10000);
        System.out.println(person.info());
    }

}

class Person {
    public String name;// 姓名公开
    private int age; // 年龄和工资私有化
    private double salary;

    public Person(){
        
    }

    public Person(String nam,int age,double salary){
        // this.name=name;
        // this.age=age;
        // this.salary=salary;

        // 将set方法写在构造器中,仍然可以进行验证
        setName(nam);
        setAge(age);
        setSalary(salary);
    }

    // 自己写setXXX getXXX太慢,使用快捷键生成
    // 然后根据要求来完善代码
    public String getName() {
        return name;
    }

    public void setName(String name) {
        if(name.length()>=2 && name.length()<=6){
            this.name = name;
        }else{
            System.out.println("名字的长度不对,需要2-6个字符,默认名字");
            this.name="Null";
        }
        
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(age<=120 && age>=1){
            this.age = age;
        }else{
            System.out.println("年龄设置错误,需要在1-120之间,默认年龄18");
            this.age=18;
        }
       
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    //写一个方法,返回属性信息
    public String info(){
        return "信息为 name="+name+ " age="+age+" 薪水="+salary;
    }
}

构造器和setXXX结合: 详细请看上图

public Person(String nam,int age,double salary){
        // this.name=name;
        // this.age=age;
        // this.salary=salary;

        // 将set方法写在构造器中,仍然可以进行验证
        setName(nam);
        setAge(age);
        setSalary(salary);
 }

面向对象编程-继承

为什么需要继承?两个类的属性和方法有很多相同的,则需要继承(代码复用性)。
当多个类存在相同的属性(变量)和方法时,可以抽象出父类,在父类中定义这些相同的属性和方法,子类不需要重新定义这些属性和方法,只需要通过 extends 来 声明继承父类即可。
继承带来了1)代码的复用性提高;2)代码的扩展性和维护性的提高
继承示意图:
在这里插入图片描述

继承的基本语法:

class 子类 extends 父类{
}

1)子类就会自动拥有父类定义的属性和方法
2)父类又叫超类,基类
3)子类又叫派生类

快速入门案例

父类

package com.hspedu.extend_.improve_;

// 父类,是Pupil和Graduate的父类
public class Student {
    // 共有属性
    public String name;
    public int age;
    private double score; //成绩

    //共有的方法
    public void setScore(double score) {
        this.score = score;
    }
    public void showInfo(){
        System.out.println("学生名 "+name+" 年龄 "+age+" 成绩 "+score);
    }
    
}

子类1

package com.hspedu.extend_.improve_;

// 让Pupil继承Student
public class Pupil extends Student {
    public void testing(){ 
        System.out.println("小学生"+name+"正在考数学..");
    }
    
}

子类2

package com.hspedu.extend_.improve_;

public class Graduate extends Student{
    public void testing(){ 
        System.out.println("大学生"+name+"正在考数学..");
    }
}

测试代码:

package com.hspedu.extend_.improve_;

public class Extends01 {
    public static void main(String[] args){
        Pupil p = new Pupil();
        p.name = "jay";
        p.testing();
        p.setScore(50);
        p.showInfo();
    }
    
}

继承的深入讨论/细节问题

1)子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
2) 子类必须调用父类的构造器, 完成父类的初始化
3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器。如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。
4) 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
5) super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
6) super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7) java 所有类都是 Object 类的子类, Object 是所有类的基类.
在这里插入图片描述

8)父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
9)子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。 思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
10) 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
在这里插入图片描述

子类创建的内存布局:
当子类创建好后,建立查找的关系(先看子类是否有该属性->子类有则直接返回信息,如果没有查看弗雷是否有该属性->如果父类有则返回信息,如果没有继续查找上级父类直到Object…)

在这里插入图片描述

课堂练习:
在这里插入图片描述

案例:
编写 Computer 类,包含 CPU、内存、硬盘等属性,getDetails 方法用于返回 Computer 的详细信息
编写 PC 子类,继承 Computer 类,添加特有属性【品牌 brand】
编写 NotePad 子类,继承 Computer 类,添加特有属性【color】
编写 Test 类,在 main 方法中创建 PC 和 NotePad 对象,分别给对象中特有的属性赋值,以及从 Computer 类继承的 属性赋值,并使用方法并打印输出信息

Computer类

package com.hspedu.extend_.exercise;

public class Computer {
    // 编写 Computer 类,包含 CPU、内存、硬盘等属性,
    // getDetails 方法用于返回 Computer 的详细信息 
    private String cpu;
    private int memory;
    private int disk;

    public Computer(String cpu,int memory,int disk){
        this.cpu = cpu;
        this.memory = memory;
        this.disk = disk;
    }
    public String getDetails(){
        return "cpu:"+cpu+" memory: "+memory+"disk:"+disk;
    }
    public String getCpu() {
        return cpu;
    }
    public void setCpu(String cpu) {
        this.cpu = cpu;
    }
    public int getMemory() {
        return memory;
    }
    public void setMemory(int memory) {
        this.memory = memory;
    }
    public int getDisk() {
        return disk;
    }
    public void setDisk(int disk) {
        this.disk = disk;
    }

}

PC子类

package com.hspedu.extend_.exercise;

public class PC extends Computer {
    // 编写PC 子类,继承 Computer 类,添加特有属性【品牌 brand】 

    private String brand;

    public PC(String cpu,int memory,int disk,String brand){
        super(cpu, memory, disk);
        this.brand=brand;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }
    public void printInfo(){
        System.out.println("PC信息如下:");
        System.out.println(getDetails()+"brand:" + brand);

    }
}

NotePad 子类

package com.hspedu.extend_.exercise;

public class NotePad extends Computer {
    // 编写 NotePad 子类,继承 Computer 类,添加特有属性【color】
    private String color;
    
    public NotePad(String cpu,int memory,int disk,String color){
        super(cpu, memory, disk);
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void printInfo(){
        System.out.println("NotePad信息如下:");
        System.out.println(getDetails()+"color:" + color);

    }
}

Test类

package com.hspedu.extend_.exercise;

public class ExtendsExercise03 {
    public static void main(String[] args) {
        // 编写 Test 类,在 main 方法中创建 PC 和 NotePad 对象,
        // 分别给对象中特有的属性赋值,以及从 Computer 类继承的 属性赋值,
        // 并使用方法并打印输出信息
        PC pc = new PC("intel", 16, 500, "IBM");
        pc.printInfo();

        NotePad ipad = new NotePad("AMD", 8, 128, "LightGrey");
        ipad.printInfo();
        }
}

super关键字

super代表父类的引用,用于访问父类的属性、方法、构造器。
基本语法:
1) 访问父类的属性/方法,但不能访问父类的private属性/方法。 super.属性名/方法名;
2)访问父类的构造器;super(参数列表); 只能放在构造器的第一句,只能出现一句!
super带来的编程便利:
在这里插入图片描述

super和this的比较:
在这里插入图片描述

方法重写/覆盖(override)

基本介绍:方法覆盖(重写)就是子类有一个方法和父类的某个方法的名称、参数一样、返回类型一样(或者子类返回类型为父类的子类),此时我们就说子类的这个方法覆盖了父类的方法。
在这里插入图片描述

方法重载vs方法重写:

在这里插入图片描述

练习题:

  1. 编写一个 Person 类,包括属性/private(name、age),构造器、方法 say(返回自我介绍的字符串)。
  2. 编写一个 Student 类,继承 Person 类,增加 id、score 属性/private,以及构造器,定义 say 方法(返回自我介绍的信息)。
  3. 在 main 中,分别创建 Person 和 Student 对象,调用 say 方法输出自我介绍 代码
    Person类
package com.hspedu.override_;

public class Person {
    // 1) 编写一个 Person 类,包括属性/private(name、age),构造器、方法 say(返回自我介绍的字符串)
    private String name;
    private int age;

    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String say(){
        return "name:"+name+" age:"+age;
    }
    
}

Student类

package com.hspedu.override_;

import java.util.jar.Attributes.Name;

public class Student extends Person{
    // 2) 编写一个 Student 类,继承 Person 类,增加 id、score 属性/private,以及构造器,
    // 定义 say 方法(返回自我介绍的信息)。 
    private int id;
    private double score;
    public Student(String name,int age,int id,double score){
        super(name, age);
        this.id = id;
        this.score = score;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public double getScore() {
        return score;
    }
    public void setScore(double score) {
        this.score = score;
    }
    public String say(){
        return "学生信息 "+super.say()+" id="+getId()+" score="+getScore();
    }
}

main

package com.hspedu.override_;

public class Exercise {
    public static void main(String[] args){
        Student stu = new Student("Zoe", 22, 118, 98.0);
        System.out.println(stu.say());
    }
    
}

面向对象编程-多态

多(多种)态(状态)的基本介绍:方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

多态的具体体现

  1. 方法的多态
    重载和重写就体现了多态
  2. 对象的多态
    在这里插入图片描述

快速入门案例:

使用多态的机制来解决主人喂食物的问题
在这里插入图片描述

package com.hspedu.objectpoly;

public class Master {
    private String name;
    public Master(String name) {
    this.name = name;
    }
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    //使用多态机制,可以统一的管理主人喂食的问题
    //animal 编译类型是 Animal,可以指向(接收) Animal 子类的对象
    //food 编译类型是 Food ,可以指向(接收) Food 子类的对象
    public void feed(Animal animal, Food food) {
        System.out.println("主人 " + name + " 给 " + animal.getName() + " 吃 " + food.getName());
    }
    //主人给小狗 喂食 骨
    // public void feed(Dog dog, Bone bone) {
    // System.out.println("主人 " + name + " 给 " + dog.getName() + " 吃 " + bone.getName());
    // }
    // //主人给 小猫喂 黄花鱼
    // public void feed(Cat cat, Fish fish) {
    // System.out.println("主人 " + name + " 给 " + cat.getName() + " 吃 " + fish.getName());
    // }
    //如果动物很多,食物很多
    //===> feed 方法很多,不利于管理和维护
    //Pig --> Rice
    //Tiger ---> meat ... //... }
}

多态的注意事项

多态的前提是:两个对象(类)存在继承关系

多态的向上转型:

本质:父类的引用指向子类的对象。
语法:父类类型 引用名 = new 子类类型(); 例如Animal cat = new Cat();
特点:1)可以调用父类中所有成员(遵守访问权限),2)但不能调用子类特有的方法。(只有儿子继承父亲,没有父亲继承儿子的)(原因:编译阶段能调用哪些成员,是由编译类型来决定的。)
3)最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法。(调用重写的方法)

多态的向下转型:

语法:子类类型 引用名 = (子类类型) 父类引用; 例如 Cat cat = (Cat) animal
1)只能强转父类的引用,不能强转父类的对象;2)要求父类的引用必须指向的是当前目标类型的对象;3)当向下转型后,可以调用子类类型中所有的成员。

  • 属性没有重写之说!属性的值看编译类型
package com.hspedu.objectpoly;

public class PloyDetail02 {
    public static void main(String[] args) {
        Base base = new Sub(); // 向上转型
        System.out.println(base.count); // 看编译类型 10
        Sub sub = new Sub();
        System.out.println(sub.count); //? 20
    }
}

class Base{ //父类
    int count = 10; //属性
}
class Sub extends Base{ //子类
    int count = 20; //属性
}
  • instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
package com.hspedu.objectpoly;

public class PolyDetail03 {
    public static void main(String[] args){
        BB bb = new BB();
        System.out.println(bb instanceof BB); // true
        System.out.println(bb instanceof AA);// true
        //aa 编译类型 AA, 运行类型是 BB
        //BB 是 AA 子类
        AA aa = new BB();
        System.out.println(aa instanceof AA);//true
        System.out.println(aa instanceof BB);//true
        Object obj = new Object();
        System.out.println(obj instanceof AA);//false
        String str = "hello";
        //System.out.println(str instanceof AA);
        System.out.println(str instanceof Object);//true
    }
}
class AA{ // 父类

}

class BB extends AA{

}

练习题:
在这里插入图片描述

解读:

  • s.count 是当前对象就是Sub的初始化count=20;
  • s.display 是当前对象,如上输出20
  • b == s,多态的向上转型,父类引用b指向子类对象s
  • b.count,属性不会重写之说,看编译类型为Base,count=10
  • b.display()为Sub的方法,多态的向上转型,子类Sub有display方法重写,输出Sub子类的结果count=20

Java的动态绑定机制:

  1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
  2. 当调用对象属性时,没有动态绑定机制,哪里声明哪里使用
    在这里插入图片描述

<很复杂,建议看看视频。简要总结:调用方法看运行类型,调用属性看编译类型。>

多态的应用

1)多态数组
数组的定义类型为父类类型,里面保存的实际元素是子类类型。
例如:
Person[] persons = new Person[5];
persons[0] = new Student(“zoe”,19);

package com.hspedu.poly_.polyarr_;

public class PloyArray {
    public static void main(String[] args){
        Person[] persons = new Person[5];
        persons[0] = new Person("zoe", 19);
        persons[1] = new Student("jack",22,66);
        persons[2] = new Student("song",20,96);
        persons[3] = new Teacher("jay", 33,5000);
        persons[4] = new Teacher("JJ", 30,10000);
        
        //循环遍历多态数组,调用say
        for(int i=0;i<persons.length;i++){
            System.out.println(persons[i].say()); //动态绑定机制

            // 调用子类特有的方法
            if(persons[i] instanceof Student){ //判断person[i]的运行类型是不是Student
                Student student = (Student) persons[i]; //向下转型
                student.study();
            }else if(persons[i] instanceof Teacher){
                Teacher teacher = (Teacher) persons[i];
                teacher.teach();
            }else if(persons[i] instanceof Person){
                   //
            }else{
                System.out.println("类型有误,请检查");
            }
        }

    }
    
}

父类
在这里插入图片描述
子类
在这里插入图片描述
在这里插入图片描述
2)多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型。

package com.hspedu.poly_.param_;

public class PloyPrameter {
    public static void main(String[] args) {
        Woker e = new Woker("Jack", 1000);
        Manager m = new Manager("zoe", 10000, 2000);

        Test t = new Test();
        t.showEmpAnnual(e);
        t.showEmpAnnual(m);
        t.testWork(e);
        t.testWork(m);
        
    }
}

class Test{
    public void showEmpAnnual(Employee e){ //父类形参允许实参是子类类型
        System.out.println(e.getName()+" 年入 "+e.getAnnual());
    }

    public void testWork(Employee e){
        if(e instanceof Woker){
            Woker w = (Woker) e; //向下转型
            w.work();
        }else if(e instanceof Manager){
            Manager m = (Manager) e;
            m.manage();
        }else{
            System.out.println("类型有误,请重新输入!");
        }
    }
}

父类Employee和子类Work Manager

package com.hspedu.poly_.param_;

public class Employee {
    private String name;
    private double salary;
    
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }

    // 计算年工资getAnnual
    public double getAnnual(){
        return 12*salary;
    }
}


package com.hspedu.poly_.param_;

public class Woker extends Employee{
    public Woker(String name,double salary){
        super(name, salary);
    }

    @Override
    public double getAnnual(){
        return super.getAnnual();
    }
    public void work(){
        System.out.println("员工"+getName()+"正在工作");
    }
}


package com.hspedu.poly_.param_;

public class Manager extends Employee{
    private double bonus;

    public Manager(String name,double salary,double bonus){
        super(name, salary);
        this.bonus = bonus;
    }

    public void manage(){
        System.out.println("经理"+getName()+"管理中。。");
    }
    @Override
    public double getAnnual(){
        return super.getAnnual()+bonus;
    }
}

Object类详解

== 和equals的对比

== 是一个比较运算符,既可以判断基本类型,也可以判断引用类型。

  • 如果判断基本类型就是判断值是否相等;
    在这里插入图片描述

  • 如果判断引用类型就是判断地址是否相等(即是不是同一个对象)。
    在这里插入图片描述

equals 是Obeject类中的方法,只能判断引用类型。

默认判断你的地址是否相等(判断对象是否相同)。
子类中往往重写该方法,用于判断内容是否相等。如下面为String中equals的源码,判断String内容是否一致:
在这里插入图片描述

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        return (anObject instanceof String aString)
                && (!COMPACT_STRINGS || this.coder == aString.coder)
                && StringLatin1.equals(value, aString.value);
    }

Integer也重写了 Object 的 equals 方法, //变成了判断两个值是否相等
在这里插入图片描述

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

如何重写equals方法

判断两个 Person 对象的内容是否相等,如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false

package com.hspedu.obejct_;

public class EqualsExercise01 {
    public static void main(String[] args) {
        Person person1 = new Person("jack", 20, '男');
        Person person2 = new Person("jack", 2, '男');
        System.out.println(person1.equals(person2));
    }
    
}

class Person{
    private String name;
    private int age;
    private char gender;
    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public boolean equals(Object obj){
        if(this == obj){
            return true;
        }
        if(obj instanceof Person){
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age==p.age && this.gender==p.gender;
        }
        return false;
    }
}

hashCode方法

返回该对象的哈希码值,支持此方法是为了提高哈希表的性能。

在这里插入图片描述

1)提高具有哈希结构的容器的效率!
2)两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
3)两个引用,如果指向的是不同对象,则哈希值是不一样的
4)哈希值主要根据地址号来的,不能完全将哈希值等价于地址!

package com.hspedu.obejct_;

public class HashCode {
    public static void main(String[] args) {
        AA aa = new AA();
        AA b = aa;
        AA aa2 = new AA();
        System.out.println(aa.hashCode()); //925858445
        System.out.println(b.hashCode()); // 925858445
        System.out.println(aa2.hashCode()); //798154996
    }
}
class AA{}
  1. 后面在集合中hashCode如果需要的话也会重写,如何重写请看集合的课程

toString方法

基本介绍:默认返回 全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】 子类往往重写 toString 方法,用于返回对象的属性信息。
在这里插入图片描述

1)重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString形式
2)当直接输出一个对象时,toString方法会被默认的调用,比如 System.out.println(monster); 就会默认调用 monster.toString()

finalize方法

1)当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作。(资源包括属性、数据库连接、文件等等)

2)什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。

3)垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制。

在实际开发中,几乎不会运用 finalize , 为了面试得了解。

断点调试(debug)

断点调试过程中是运行状态,是以对象的运行类型来执行的。

项目-零钱通

这个项目我没做,有时间/感兴趣的话再看

本章作业

P342-358是作业的讲解,没写出来可以看看。

1

在这里插入图片描述

package com.hspedu.homework;

public class Homework01 {
    public static void main(String[] args) {
        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 20, "大数据工程师");
        persons[1] = new Person("jack", 22, "大数据工程师");
        persons[2] = new Person("jack", 10, "大数据工程师");
        persons[3] = new Person("jack", 23, "大数据工程师");
        persons[4] = new Person("jack", 5, "大数据工程师");

        Person temp = null;
        for(int i =0;i<persons.length-1;i++){
            for(int j =0;j<persons.length-1-i;j++){
                if(persons[j].getAge()<persons[j+1].getAge()){
                    temp = persons[j];
                    persons[j] = persons[j+1];
                    persons[j+1] = temp;
                }
            }
        }
        // 输出排序的结果
        for(int i =0;i<persons.length;i++){
            System.out.println(persons[i]);
        }
      
    }
}

class Person{
    private String name;
    private int age;
    private String job;

    public Person(){

    }
    public Person(String name, int age, String job) {
        this.name = name;
        this.age = age;
        this.job = job;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getJob() {
        return job;
    }
    public void setJob(String job) {
        this.job = job;
    }
    @Override
    public String toString(){
        return "Person{"+
                "name="+name+'\''+
                ",age="+age+
                ",job="+job+'\''+
                '}';
    }
}

14

在这里插入图片描述

解释:C的无参构造器执行的this 需要有参构造器,执行C的有参构造器,执行super找到父类B的有参构造器,输出”hahah我是B类的有参构造“,构造器默认有super(),需要到B的父类A中输出”我是A类“,随后继续执行B。。。


待续。。。

3. 面向对象编程(高级部分)(373-423)

类变量和类方法

类变量定义

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量。

任何一个该类的对象去访问它时,取到的都是相同的值。同样,任何一个该类的对象去修改它时,修改的也是同一个变量。

类变量内存布局

静态变量放在哪里? 有些书中说在方法区,有些说在堆中。静态变量的存放和JDK版本相关,jdk8以上的都存在class堆中。

不管static变量在哪里,共识1)static变量是同一类所有对象共享;2)static类变量在类加载的时候就生成了(即使没有创建对象实例,也可以访问)。

在这里插入图片描述

静态变量被对象共享,不影响对静态变量的使用。

如何定义类变量

访问修饰符 static 数据类型 变量名;[推荐]
static 访问修饰符 数据类型 变量名;

如何访问类变量

静态变量的访问修饰符的访问权限范围和普通属性是一样的

类名.类变量名 【推荐】
或者
对象名.类变量名

类变量使用注意事项和细节讨论

[图片]

[图片]

类方法

[图片]

类方法的使用场景

[图片]

类方法使用注意事项

[图片]
在这里插入图片描述

理解main方法语法

  • main方法是虚拟机调用,java虚拟机调用类的main()方法,必须得是public权限。
  • main方法不必创建对象,所以必须是static
  • String[] args参数在命令台执行java程序的最末尾传入。(类似python运行深度学习程序传参)
    [图片]

特别提示:

  1. 在 main()方法中,可以直接调用 main 方法所在类的静态方法或静态属性。
  2. main()方法 不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后, 才能通过这个对象去访问类中的非静态成员。

代码块

代码块又称初始化块,属于类中的成员[即 类中的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。

但和方法不同的是:没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。

基本语法:

[修饰符]{
    代码
}

1)修饰符可选,要写也只能写static
2)代码块分为两类,使用static修饰的叫静态代码块,没有的叫做普通代码块/非静态代码块
3)逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4);(分号)可以写上,也可以省略

更好的理解:
1)代码块相当于另一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2)场景:如果多个构造器都有重复的语句,可以抽取到初始化块中,提高代码的重用性

代码块使用注意事项和细节

1)static代码块的作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象就执行一次。
2)类什么时候被加载?(重要)

  • 创建对象实例时(new)
  • 创建子类对象实例,父类也会被加载
  • 使用类的静态成员时(静态属性,静态方法)
    3)普通代码块,在创建对象实例时,会被隐式的调用(创建一次调用一次)。如果只是使用类的静态成员时,普通代码块不会被执行。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

调用顺序:父类和子类的静态代码块和静态属性在类中共享,所以优先。其次是父类的普通代码块和普通属性初始化,随后是父类的构造方法,最后执行子类的。

练习题:
在这里插入图片描述

单例设计模式

设计模式:在大量事件中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考能力。

单例(单个的实例):

  1. 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能 存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
  2. 单例模式有两种方式:1)饿汉式;2)懒汉式。

应用场景:
在这里插入图片描述

单例模式步骤[饿汉式]:

  1. 构造器私有化
  2. 类的内部直接创建对象(static)
  3. 提供一个公共的静态方法,返回gf对象

懒汉式,只有当用户使用getInstance时,才返回对象,后面再次调用时会返回上次创建的对象。
在这里插入图片描述
在这里插入图片描述

[图片]

单例模式应用案例:

package com.hspedu.single_;

public class SingleTon02 {
    public static void main(String[] args){

        // GirlFriend xh = new GirlFriend("小红");
        // GirlFriend xx = new GirlFriend("小黄");

        //通过方法获取对象
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);

        System.out.println(instance==instance2); //相同的对象

    }
}

// 希望在程序运行过程中,只能创建一个GirlFriend对象
class GirlFriend{
    private String name;

    /* 饿汉式
    
    // 为了能够在静态方法中,返回GirlFriend对象,需要将其修饰成static
    private static GirlFriend gf = new GirlFriend("小红红");

    // 如何保障只能创建一个GirlFriend对象?
    // 步骤[单例模型-饿汉式]:
    // 1. 构造器私有化
    // 2. 类的内部直接创建对象(static)
    // 3. 提供一个公共的静态方法,返回gf对象
    private GirlFriend(String name){
        this.name=name;
        System.out.println("构造器被调用");
    }

    public static GirlFriend getInstance(){
        return gf;
    }
    */

    // 懒汉式
    private static GirlFriend gf; //默认null
    private GirlFriend(String name){
        this.name=name;
        System.out.println("构造器被调用");
    }
    public static GirlFriend getInstance(){
        if(gf == null){ //需要使用的时候再创建
            gf = new GirlFriend("小明");
        }
        return gf;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
        "name='" + name + '\'' +
        '}';
    }

}

final关键字

final可以修饰类、属性、方法和局部变量

应用场景

1)当不希望类被继承时,可以用final修饰【final class 类名】
2)当不希望父类的某个方法被在子类覆盖/重写(override)时,可以用final关键字修饰 【访问修饰符 final 返回类型 方法名】
3)当不希望类的某个属性的值被修改,可以用final修饰【public final double TAX_RATE = 0.08;】
4)当不希望某个局部变量被修改,可以用final修饰【final double NUM = 0.0】

final使用注意事项和细节

1)final修饰的属性又叫常量,一般用 XX_XX_XXl来命名,比如TAX_RATE

2)final修饰的属性正在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一:

  • 定义时:如public final double TEX_RATE=0.08;
  • 构造器中
  • 代码块中

3)如果final修饰的属性是静态的,则初始化的位置只能是:

  • 定义时
  • 静态代码块中

4)如果final类不能继承,但是可以实例化对象。
5)如果类不是final类,但是含有final方法,则该方法虽然不能重写,但可以被继承。
6)如果一个类已经是final类,就没必要再将方法修饰成final方法。
7)final不能修饰构造方法
8)final和static往往搭配使用,效率更高,不会导致类加载(底层编译器做了优化处理)
在这里插入图片描述

9)包装类(Integer, Double, Float, Boolean等都是final),String也是final类

课堂练习:
[图片]

抽象类

当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract了来修饰该类就是抽象类。

抽象类介绍

在这里插入图片描述

抽象类使用的注意事项和细节

在这里插入图片描述
[图片][图片]

课堂练习:
在这里插入图片描述

抽象类最佳实践-模板设计模式

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但是子类总体上会保留抽象类的行为方式。

模板设计模式能解决的问题:
1)当功能内部一部分实现是确定的,一部分实现是不确定的。可以把不确定的部分暴露出去,让子类实现。
2)编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给子类实现,就是一种模板模式。

最佳实践:统计不同任务的完成时间
[图片]

接口

基本介绍

[图片]

注意事项和细节

[图片]

在这里插入图片描述

实现接口 vs 继承类

实现接口是对 java 单继承机制的一种补充
当子类继承了父类,就自动的拥有父类的功能。如果子类需要扩展功能,可以通过实现接口的方式扩展。
接口和继承解决的问题不同:
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法。即更加的灵活…
继承满足 is - a的关系,而接口只需满足 like - a的关系
接口在一定程度上实现代码解耦 [即:接口规范性+动态绑定机制]

接口的多态特性

1)多态参数
[图片]

2)多态数组
[图片]

3)接口存在多态传递现象
[图片]

[图片]

课堂练习:
[图片]

解决方法:明确指定的x,访问接口的 x 就使用A.x;访问父类的 x 就使用super.x

内部类

定义在外部类局部位置(方法中/代码块) :
(1) 局部内部类(有类名)
(2) 匿名内部类 (没有类名)

定义在外部类的成员位置:(本质上为一个成员)
(1) 成员内部类(没用static修饰)
(2) 静态内部类(使用static修饰)

基本介绍

[图片]

类的五大成员:属性、构造器、方法、代码块、内部类

基本语法

class Outer{ // 外部类
    class Inner{ // 内部类
    
    }
}

class Other{ // 外部其他类
}

局部内部类的使用

局部内部类是定义在外部类的局部位置,比如方法中,并且有类名

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量不能使用修饰符,但是可以使用final修饰(局部变量也可以使用)。
  3. 作用域:仅仅在定义它的方法或代码块中
  4. 局部内部类—访问—>外部类的成员 [访问方式:直接访问]
  5. 外部类—访问—>局部内部类的成员 [访问方式:创建对象,再访问](必须在作用域内)
  6. 外部其他类—不能访问—>局部内部类
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则。如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。(外部类.this 本质就是外部类的对象,即哪个对象调用了该方法,外部类.this就指向该对象。)
    System.out.println(“外部类的n2=”+外部类名.this.n2);

重要tips:

  1. 局部内部类定义在方法/代码块中
  2. 作用域在方法体或者代码块中
  3. 本质仍然是一个类

匿名内部类的使用

  1. 本质是类
  2. 内部类
  3. 该类没有名字
  4. 还是一个对象

匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名。

1)基本语法

new 类或接口(参数列表){
    类体x
};
  • jdk底层在创建匿名内部类Outer04$1,立马就创建了Outer04$1实例,并且把地址返回给tiger。
  • 匿名内部类(Outer04$1)只使用一次,但是对象(tiger)可以重复使用。

在这里插入图片描述

2)匿名内部类即是一个类的定义,本身也是一个对象。因此,它既有定义类的特征,也有创建对象的特征。
3)可以直接访问外部类的所有成员,包含私有的
4)不能添加访问修饰符,因为它的地位就是一个局部变量
5)作用域:仅仅在定义它的方法或代码块中
6)匿名内部类—访问—>外部类成员 [访问方式:直接访问]
7)外部其他类—不能访问—>匿名内部类
8)如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则。如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。

成员内部类的使用

成员内部类是定义在外部类的成员位置,并且没有static修饰。
[图片]

  1. 可以直接访问外部类的所有成员,包含私有的

  2. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员

  3. 作用域和外部类的其他成员一样,为整个类体。先创建成员内部类对象,再调用方法。

  4. 成员内部类—访问—>外部类成员(比如属性)[访问方式:直接访问]

  5. 外部类—访问—>成员内部类[访问方法:创建对象,再访问]

  6. 外部其他类—访问—>成员内部类
    [图片]

  7. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则。如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
    [图片]

静态内部类的使用

静态内部类是定义在外部类的成员位置,并且有static修饰

  1. 可以直接访问外部类的所有静态成员,包含私有,但不能直接访问非静态成员

  2. 可以添加任意访问修饰符(public、protected 、默认、private)。因为它的地位就是一个成员

  3. 作用域:同其他的成员,为整个类体

  4. 静态内部类—访问—>外部类(比如静态属性) [访问方式:直接访问是所有静态成员]

  5. 外部类—访问呢—>静态内部类 [访问方式:创建对象,再访问]

  6. 外部其他类—访问—>静态内部类
    [图片]

  7. 如果外部类和静态内部类的成员重名时,静态内部类访问的话,默认遵循就近原则。如果访问外部类的成员,则可以用(外部类名.成员)去访问。(成员是静态的,不用加this)

练习题

[图片]

验证代码:

package com.hspedu.Innerclass_;

public class Exercise {
    public Exercise(){
        Inner s1 = new Inner();
        s1.a=10;
        System.out.println("s1.a="+s1.a);
        Inner s2 = new Inner();
        System.out.println("s2.a="+s2.a);
        
    }

    class Inner{
        public int a = 5;
    }

    public static void main(String[] args) {
        Exercise e = new Exercise();
        Inner r = e.new Inner();
        System.out.println("r.a="+r.a);
    }
}

面向对象编程部分的学习完成,下篇为枚举和注解/异常/常用类/集合/泛型/多线程/IO流等内容:【Java基础】枚举和注解/异常/常用类/集合/泛型/多线程/IO流(韩顺平P424-660)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值