面向对象——Robyn编程学习(Java)

面向对象

如何理解面向对象?

我的理解是程序就是客观世界的主观反映,在客观世界里我们根据动植物进行各种分类,同时又根据姓名等划分出一个个不同的对象个体,每个个体既有共性,也有自己的特性,而java则就是体现了这一思想,通过类来承载实例的特征。(写的多了就会运用随心)运用之妙,存乎一心。

面向对象大纲

面向对象基础

对象

对象在内存中的存在形式:

image

Java内存的结构分析

栈:一般存放基本数据类型(这里存放的是对象的引用

堆:存放引用数据类型的地址(对象,数组)

方法区:存放常量池(字符串)和类加载信息

方法的调用机制分析

image

方法的传参机制

  1. 基本数据类型的传参机制
int a = 10;
int b = 20;
//创建 AA 对象 名字 obj
AAobj = newAA();
obj.swap(a, b); //调用 swap(这里是一个交换方法)
System.out.println("main 方法 a=" + a + " b=" + b);//a=10 b=20

得到结论:基本数据类型形参的改变不影响实参

  1. 引用类型的传参机制
//测试
B b = new B();
int[] arr = {1, 2, 3};
b.test100(arr);//调用方法(这里对数组进行了修改)
System.out.println(" main 的 arr 数组 ");
//遍历数组
// for(int i = 0; i < arr.length; i++) {
// System.out.print(arr[i] + "\t");

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

方法的递归调用

  1. 斐波那契数列问题
public int fibonacci(int n) {
        if (n >= 1) {
            if (n == 1 || n == 2) {
                return 1;
            } else {
                return fibonacci(n - 1) + fibonacci(n - 2);//此处体现递归思想
            }
        } else {
            System.out.println("请输入n>=1的整数");
            return -1;//代表程序运行失败
        }
    }
  1. 猴子吃桃问题
 public int rabbit(int day) {
            if (day == 10) {
                return 1;
            } else if (day >= 1 && day <= 9) {
                return 2 * (rabbit(day + 1) + 1);
        }else {
            return -1;
        }
    }

递归问题关键就在于找到终结的值,然后让电脑自己判断。

作用域

全局变量:类中的变量 局部变量:方法中的变量

构造器的初始化

image

this关键字

  1. this 关键字可以用来访问本类的属性、方法、构造器
  2. this 用于区分当前类的属性和局部变量
  3. 访问成员方法的语法:this.方法名(参数列表);

访问修饰符

image

两个比较有意思的练习题

//圆半径问题
public class Work6 {
    public static void main(String[] args) {
        //就是一个圆的习题(记得用匿名变量)
        //封装的思想
class Circle{
    double radius;
    public double findArea(double radius){
        return Math.PI * radius *radius;
    }
}
class PassObject{
    public void printAreas(Circle c,int times){
        System.out.println("Radius"+"\t\t"+"Area");
        for(double i = 1;i <= times;i++){
            System.out.println(i+"\t\t"+c.findArea(i));
        }
    }
}
    PassObject pass = new PassObject();
    Circle cir = new Circle();
    pass.printAreas(cir,5);
    }
//体现的就是封装思想   

猜拳游戏

这个题的逻辑就特别清晰,变量用来干什么,方法对于变量的修改和返回,数组作为对于结果的存储。以后写代码都应该这样,逻辑清晰。 写代码时就要有逻辑,用那些变量和那些方法,这个可以用LeetCode解决。
//实例化tom对象
        Tom tom = new Tom();
        int isWincount = 0;
        //创建一个二维数组接收出拳情况
        int arr[][] = new int[3][3];
        int j = 0;
        //创建一个一维数组接收输赢统计
        String arr2[] = new String[3];

        Scanner scanner = new Scanner(System.in);
        for (int i = 0; i <3 ; i++) {
            System.out.println("请出拳!");
            int num = scanner.nextInt();
            tom.setTomGuessNum(num);
            int tomGuess = tom.getTomGuessNum();
            arr[i][j+1] = tomGuess;
            //获取电脑出拳
            int comGuess = tom.computerNum();
            arr[i][j+2] = comGuess;
            String win = tom.vsComputer();
            arr2[i] = win;
            arr[i][j] = tom.count;
            //对每一句的结果进行输出
            System.out.println("=====");
            System.out.println(tom.count+"\t"+tomGuess+"\t"+comGuess+"\t");
            System.out.println("=====");
            isWincount = tom.comWincount(win);//记录赢的次数,顺便改变局数
        }
        System.out.println("你最后赢了"+isWincount+"次!");
    }
}
//创建一个玩家对象TOM
class Tom{
        //设置玩家出拳
    int tomGuessNum;
        //设置电脑出拳
    int comGuessNum;
    int winCountNum;//记录赢的次数
    int count = 1;//记录比赛的局数

    public int getTomGuessNum() {
        return tomGuessNum;
    }
    //首先设置tom的出拳
    public void setTomGuessNum(int tomGuessNum) {
        if(tomGuessNum > 2 || tomGuessNum < 0){
            throw new IllegalArgumentException("数字输入错误");
        }else {
            this.tomGuessNum = tomGuessNum;
        }
    }
    //设置电脑随机出拳
    public int computerNum(){
        Random random = new Random();
        comGuessNum = random.nextInt(3);
        return comGuessNum;
    }
    //判断比赛的胜负
    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 comWincount(String s){
        count++;
        if(s.equals("你赢了")){
            winCountNum++;
        }
        return winCountNum;

面向对象特征

面向对象——封装

封装的好处就是数据没有办法更改,只能用已有的方法去更改数据


封装的三个步骤:

  1. 属性私有化
  2. 提供一个公共的set方法
  3. 提供一个公共的get方法访问
Account account = new Account();
        account.setName("jack");
        account.setBalance(60);
        account.setPwd("123456");
就是构造器与get,set方法的结合

面向对象——继承

public class 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 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;
    }
    public String getDetail(){
        return "电脑属性为:"+ cpu + memory + disk;
    }
}

class PC extends Computer{
    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(getCpu()+"\t"+getMemory()+"\t"+getDisk());
        System.out.println(getDetail()+brand);
    }
}

class AAA{
    public static void main(String[] args) {
        PC pc = new PC("levleo",16,500,"IBM");
        pc.printInfo();
    }
}

其实就是子类继承了父类的属性和方法,但是必须要在构造器中显式调用(父类是无参构造器除外),此外父类的私有属性必须通过公用方法来进行访问。

面向对象——多态

多态理论建立的基础

image

多态的几大注意事项

  1. 向上转型:父类类型 引用名 = new 子类类型();父类的引用指向子类的对象
  2. 向下转型:建立在向上转型的基础上,当父类的引用指向当前目标类型的对象时,就可以向下转型吧父类的引用转化为子类类型,从而使用子类类型中所有的成员。 子类类型 引用名 = (子类类型)父类引用。
 Person[] persons = new Person[4];
        persons[0] = new Student("jack", "男", 10, 001);
        persons[1] = new Student("mary", "女", 20, 002);
        persons[2] = new Teacher("smith", "男", 36, 5);
        persons[3] = new Teacher("scott", "男", 26, 1);

        //创建对象
        Test homework13 = new Test();
        homework13.bubbleSort(persons);

        //输出排序后的数组
        System.out.println("---排序后的数组-----");
        for(int i = 0; i < persons.length; i++) {
            System.out.println(persons[i]);
        }

        //遍历数组,调用test方法
        System.out.println("=======================");
        for (int i = 0; i < persons.length; i++) {//遍历多态数组
            homework13.test(persons[i]);
        }

    }

    //定义方法,形参为Person类型,功能:调用学生的study或教师的teach方法
    //分析这里会使用到向下转型和类型判断
    public void test(Person p) {
        if(p instanceof Student) {//p 的运行类型如果是Student
            ((Student) p).study();
        } else if(p instanceof  Teacher) {
            ((Teacher) p).teach();
        } else {
            System.out.println("do nothing...");
        }
    }

    //方法,完成年龄从高到底排序,这个冒泡排序太经典了
    public void bubbleSort(Person[] persons) {
        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;
                }
            }
        }

一个小思考:为什么main方法里不能有private修饰符呢

因为main方法是一个静态方法

方法内的变量是局部变量

局部变量只在定义它的内部有效,并且不能使用 private ,protected,public 修饰符进行修饰,用了也没有用,因为局部变量只在定义它的内部有效,当局部变量所在的方法调用结束后,java 虚拟机将自动释放局部变量所占用的资源。

多态的基础:动态绑定机制

当调用对象方法的时候,该对象会和对象的运行类型绑定

调用对象属性的时候,没有动态绑定机制,那里声明,哪里使用。(这也是为什么上面编译类型决定属性值的原因)

Sub sub = new Sub();
        System.out.println(sub.count);
        sub.display();
        //向上转型
        Base b = sub;//两个引用指向同一个对象
        System.out.println(b == sub);
        System.out.println(b.count);//编译类型
        b.display();//运行类型
    }
}
class Base{
    int count = 10;
    public void display(){
        System.out.println(this.count);
    }
}

class Sub extends Base{
    int count = 20;

    public void display() {
        System.out.println(this.count);

在编写程序时,发现了一些细节,为什么有些地方必须得实例化,有些地方就不用实例化呢?还有困扰我的一点,和普通方法相比:main方法究竟有何特色之处,为何全部要调用?

这就引入了面向对象的高级部分:类变量与类方法

面向对象高阶:类变量与类方法

静态成员的调用

为啥要有类变量,因为有的变量是随着类加载时始终存在,不需要每次创建对象时都再重新创建,因此就可以设置为类变量(其实就是所有对象共享一个变量)

静态方法也是一样的道理:就是不需要实例化就可以直接运行,因此静态方法只能访问静态成员

为什么main()访问非静态成员,必须创建实例对象才可以,就可以从以下代码中寻找答案

 private static String name = "汉合";//静态属性
    private int n1 = 10000;//非静态方法
    //静态方法
    public static void hi(){
        System.out.println("Main的hi方法");
    }
    //非静态方法
    public void cry(){
        System.out.println("main的cry方法");
    }
    //下面就是见证奇迹的时刻
    public static void main(String[] args) {
        System.out.println(name);
        Main01 main01 = new Main01();//调用非静态方法必须实例化对象才行
        System.out.println(main01.n1);//调用非静态属性才是一样
        main01.cry();
//        System.out.println(n1);
        hi();

静态方法的调用

image

静态(类)方法与类加载相关,普通方法与对象有关,因此类方法中不能出现this和super,比如main方法不能出现this和super

!!!类加载的三种情况:

  1. 创建对象实例
  2. 创建子类对象实例,父类也会被加载
  3. 使用类的静态成员时

类加载时的访问顺序

#类加载时的顺序
①调用静态代码块和静态属性初始化
②调用普通代码块和普通属性初始化   
#创建一个子类对象的调用顺序:
①父类的静态代码块和静态属性
②子类的静态代码块和静态属性
③父类的普通代码块和普通属性
④父类的构造方法
⑤子类的普通代码块和普通属性
⑥子类的构造方法

正是因为对于类变量的理解,我们对于Java中的main方法有了更深的理解。

image

设计模式(重点)

设计模式是在大量的实践中总结和理论化之后优选的代码结构,类似于棋谱。

下面以单例模式为设计要求

什么是单例模式?就是一个类中只存在一个对象实例,并且该类只提供一个可以获得其对象实例的方法。关键就是在这个类何时创建一个对象实例,这就可以区分出饿汉式和懒汉式设计模式。

//演示懒汉式,线程安全,但是存在资源浪费的情况
public class SingleTon01 {
    public static void main(String[] args) {
        //获取小红女朋友对象
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
    }
}
//设置女朋友类
class GirlFriend{
    private String name;
    //一定要私有化,防止出现问题
    private GirlFriend(String name) {
        this.name = name;
    }
    //提前创建静态对象,方便调用
    private static GirlFriend gf = new GirlFriend("小红");
    //提供一个公共对象返回对象,注意这里也有静态方法比较好
    public static GirlFriend getInstance(){
        return gf;
    }
    //设置一个toString方法
    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

懒汉式(需要时才创建,存在线程安全问题)

public class SingleTon01 {
    public static void main(String[] args) {
        //获取小红女朋友对象
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
    }
}
//设置女朋友类
class GirlFriend{
    private String name;
    private  static GirlFriend girlFriend;//这个对象需要时才实例化
    //一定要私有化,防止出现问题
    private GirlFriend(String name) {
        this.name = name;
    }
    //提供一个公共对象返回对象,注意这里也有静态方法比较好,如果需要则创建一个对象
    public static GirlFriend getInstance(){
        if(girlFriend == null ){
            girlFriend = new GirlFriend("小红红");
        }
        return girlFriend;
    }
    //设置一个toString方法
    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

抽象类

image

因为方法要重写:所以一般都是用public,不可能用private/final/static修饰的

接口

  //接口的调用很有意思,实现了接口就都是接口对象,就成为了接口对象,然后用向下转型来分别转型
        MysqlDB mysqlDB = new MysqlDB();
        t(mysqlDB);
        OracleDB oracleDB = new OracleDB();
        t(oracleDB);
    }
    public static void t(DBInterface db) {//跟USB接口有点像,传入一个插头对象,然后会自动识别传入的对象
        db.connect();
        db.close();
    }

可以发现,接口就是自动识别传入的对象,从而实现相应的功能

接口多态性的思考(可以说:接口就是为了多态而生的)

Usb[] usb = new Usb[2];
        usb[0] = new Phone();
        usb[1] = new Camera();
        //遍历数组从而得到特有的方法
        for(int i = 0;i < usb.length;i++){
            usb[i].work();//动态绑定机制
            if(usb[i] instanceof Phone){
                ((Phone)usb[i]).call();

这不就是接口对象吗?果然体现的是多态的思想

请问实现接口和继承类有何区别呢

其实都是为了拓展功能,当子类继承父类,就拓展了父类的所有功能,如果再需要拓展功能,就可以通过接口的形式来拓展。

匿名内部类

public class InnerClassExercise {
    public static void main(String[] args) {
        CellPhone cellPhone = new CellPhone();
        Bell bell = new Bell() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了");
            }
        };
        cellPhone.alarmClock(bell);

    }
}

interface Bell{//接口
    void ring();//方法
}

class CellPhone{
    public void alarmClock(Bell bell){//传入实现接口的接口对象
        System.out.println(bell.getClass());
        bell.ring();
    }
}

单例设计模式的应用

public class InnerClassExercise {
    public static void main(String[] args) {
        CellPhone cellPhone = new CellPhone();
        Bell bell = new Bell() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了");
            }
        };
        cellPhone.alarmClock(bell);

    }
}

interface Bell{//接口
    void ring();//方法
}

class CellPhone{
    public void alarmClock(Bell bell){//传入实现接口的接口对象
        System.out.println(bell.getClass());
        bell.ring();
    }
}

把需求给转化I成功能函数,这个真的很有意思,以后写代码也要注意这方面的应用!

posted @ 2022-09-27 19:52  深海之燃  阅读(47)  评论(0)    收藏  举报