day3_面向对象(上)

2021-12-11  本文已影响0人  OmewSPG

面向对象的三条主要线索:

  1. Java类以及类的成员:属性、方法、构造器、代码块和内部结构
  2. 面向对象的三大特征:封装性,继承性,多态性
  3. 其它关键字:this,super,static,final,abstract,interface,package...

面向过程与面向对象

两者区别

面向对象与面向过程

思想概述

思想概述

Java基本元素:类和对象

类(Class)和对象(Object)是面向对象的核心概念。

类与类的成员

常见的类的成员:

class Person {
    // 属性(变量)
    String name;
    int age;

    // 方法(函数)
    public void speak(String lang) {
        System.out.println("Domo, my name is " + this.name + ",I Speak " + lang + "!");
    };
}

类的语法格式

语法格式

对象的创建和使用

实际上,面向对象的流程:
创建类 --> 创建类的对象 --> 调用属性或方法

比如:

public class OPPtest1 {
    public static void main(String[] args) {
        // 创建person对象 ( 类的实例化 )
        // 创建对象后,属性具有初始化值
        Person p1 = new Person();
        
        // 调用对象结构:属性、方法
        // 调用格式: 对象.属性
        p1.name = "Omew";
        p1.age = 16;
        System.out.println(p1.name);
        p1.speak("Chinese");
        
        // 创建第二个Person对象
        Person p2 = new Person();
        p2.name = "Kana";
        p2.age = 17;
        System.out.println(p2.name);
        p2.speak("Jpanese");
        
        // 将p1变量保存的对象地址赋给p3,导致p1和p3指向了堆空间中的同一个实体
        Person p3 = p1;
        System.out.println(p3.name);  // Omew
        
        p3.age = 10;
        System.out.println(p1.age);   // 10 
    }
内存解析 内存图

匿名对象

匿名对象

比如:

/*
 * 匿名对象
 * 1.创建匿名对象:没有显式地赋给一个变量名,即为匿名对象
 * 2.特征:一般情况下匿名对象只能调用一次
 * 3.匿名对象作为参数传递给另一个对象的方法时,可以多次调用
 */

public class InstanceTest {
    public static void main(String[] args) {
        new Phone().price = 1999;
        new Phone().showPrice();   // 0.0
        
        //***********************************
        
        PhoneMall mall = new PhoneMall();
        mall.display(new Phone());   // 999.0
    }
}


class PhoneMall{
    public void display(Phone p){
        p.price = 999;
        p.showPrice();
    }
}


class Phone{
    double price;
    
    public void showPrice(){
        System.out.println("该手机的价格为:" + price);
    }
}

上面程序中,每new Phone()一次就新建了一个Phone的实例,因为每个实例的首地址值都是不同的,所以直接对其进行属性赋值和方法调用,可能不会达到你想要的结果;
一般情况下,匿名对象作为参数放到方法里,才是最好的使用选择

属性

语法格式 成员变量(属性)与局部变量

比如:

/*
 * 类中的属性
 * 
 * 属性(成员变量)  vs 局部变脸(声明在方法内,方法形参,代码块,构造器内部的变量)
 * 1.声明属性时,可以用权限修饰符进行修饰(private,public,protected,缺省)
 *  .局部变量无法使用权限修饰符
 * 2.类的属性,依据其类型,具有默认初始化值
 *  .局部变量没有默认初始化值,在调用之前一定要显式赋值(形参在调用时赋值即可)
 * 3.类的属性加载在堆空间
 *  .局部变量加载在栈空间
 * 
 */
public class Type {
    public static void main(String[] args) {
        User u1 = new User();
        System.out.println(u1.name);  // null
        System.out.println(u1.age);   // 0
        System.out.println(u1.isMale);  // false
        
        u1.eat();
        u1.talk("英语");  // 形参在这里赋值
    }
}

class User{
    // 属性(成员变量)
    String name;
    int age;
    boolean isMale;
    
    public void talk(String lang){  // lang为形参,也是局部变量
        System.out.println("我可以用" + lang + "进行交流");
    }
    
    public void eat(){
        String food = "馅饼";   // food也为局部变量
        System.out.println("北方人喜欢吃: " + food);
    }
    
}
属性与局部变量的区别 对象属性默认初始化赋值

方法

何为方法(method、函数):

声明格式

比如:

package com.atguigu.java;
/*
 * 类中方法的声明和使用
 * 声明方式:
 *        权限修饰符  + 返回值类型  + 方法名(指定形参 ) {
 *        
 *        }
 *        
 * 权限修饰符: public , private , protected , 缺省
 * 方法名也属于标识符,遵守标识符的命名规则
 * 形参列表:
 *      方法名  (数据类型    形参1 ,  数据类型    形参2...)
 * 
 */
public class Method {
    public static void main(String[] args) {
        Customer cus1 = new Customer();
        cus1.eat();       // 客户吃饭
        cus1.sleep(8);    // 休息了8个小时
        cus1.name = "Omew";
        System.out.println(cus1.getName());   // Omew
        System.out.println(cus1.getNation("China"));   // 我的国籍是: China
    }
}

class Customer{
    // 属性
    String name;
    int age;
    boolean isMale;
    
    // 方法
    public void eat(){  // void 无返回值
        System.out.println("客户吃饭");
    }
    
    public void sleep(int hour){
        System.out.println("休息了" + hour + "个小时");
    }
    
    public String getName(){  // 返回String类型数据
        return name;     // 若有返回值,则一定要用 return 关键字
    }
    
    public String getNation(String nation){
        String info = "我的国籍是: " + nation;
        return info;
    }
}

注意:

对象数组(例题)

例题

可以这样写:

// 对象数组***
public class practice3 {
    public static void main(String[] args) {
        
        Student[] stu = new Student[20];   // 创建Student类数组
        for(int i = 0; i < 20; i++){
            stu[i] = new Student();    // 创建Student类的对象
            stu[i].number = i + 1;
            stu[i].state = (int)(Math.random() * 6 + 1);
            stu[i].score = (int)(Math.random() * 101);
        }
        
        
        practice3 pra = new practice3();
        
        pra.printStudent(stu);
        System.out.println("***********************************************");
        
        // 获取三年级学生信息
        
        pra.getStateStudent(stu, 3);
        System.out.println("***********************************************");
        
        // 按成绩冒泡排序
        
        pra.sortByScore(stu);
        pra.printStudent(stu);
        

    }
    
    // 封装函数
    // 遍历学生
    public void printStudent(Student[] stu){
        for(int i = 0; i < stu.length; i++){
            stu[i].getStudent();
        }
    }
    
    // 获取该年级学生信息
    public void getStateStudent(Student[] stu, int state){
        for(int i = 0; i < stu.length; i++){
            if(stu[i].state == state){
                stu[i].getStudent();
            }
        }
    }
    
    // 按成绩排序
    public void sortByScore(Student[] stu){
        Student temp = new Student();   // 注意交换的是整个对象,而不是属性
        for(int i = 0; i < stu.length; i++){
            for(int j = 0; j < stu.length - i - 1; j++){
                if(stu[j].score > stu[j + 1].score){
                    temp = stu[j];
                    stu[j] = stu[j + 1];
                    stu[j + 1] = temp;
                }
            }
        }
    }
    
    
}



class Student{
    int number;
    int state;
    int score;
    
    public void getStudent(){
        System.out.println("学号: " + number + ",年级" + state + ",成绩" + score);
    }
}

内存解析

再谈方法

方法重载

image.png

例子:

package com.atguigu.java;

/*
 * 方法的重载(overload)
 * 同一个类中,允许存在一个以上的同名方法,只需要他们的参数个数和参数类型不同即可
 * 
 * 比如,Arrays类中sort() , binarySearch()
 * 
 * 
 */
public class methodRebuild {
    public static void main(String[] args) {
        methodRebuild meth = new methodRebuild();
        
        meth.getSum(1,2);    // 3
        meth.getSum(3.13,4.13);    // 7.26

    }
    
    
    // 以下两个方法为重载关系
    public void getSum(int i, int j){
        System.out.println(i + j);
    }
    
    public void getSum(double d1, double d2){
        System.out.println(d1 + d2);
    }
    
}

可变形参的方法

image.png
image.png

比如:

package com.atguigu.java;
/*
 * 可变个数形参的方法(JDK 5.0)
 * 
 * 
 * 可变形参格式:数据类型 ... 变量名
 * 调用时,传入的参数可以是任意个
 * 可变个数形参的方法,可以和其它同方法名且 不同参数个数的方法构成重载
 * 可变个数形参的方法,不可以和其它同方法名且参数个数为数组的方法构成重载
 * 可变个数形参在方法的形参中,必须声明在末尾,且最多只能声明一个
 * 
 */
public class Arguments {
    
    public static void main(String[] args) {
        Arguments arg = new Arguments();
        arg.show(2);
        arg.show("Hello");
        arg.show("Hello","World");
    }
    
    public void show(int i){
        System.out.println("show(int i)");
    }
    
    public void show(String s){
        System.out.println("show(String s)");
    }
    
    public void show(String ... strs){
        System.out.println("show(String ... strs)");
    }
    
//  public void show(String[] strs){
//      
//      
//  }    // 报错;因为这是JDK5.0以前,可变个数形参方法的写法,实际上和上一个函数重名了
    
    
//  public void show(String ... strs ,int i){
//      
//  }   // 报错;可变个数形参在方法的形参中,必须声明在末尾,且最多只能声明一个
    
}

值传递机制(重要)

image.png

直接看例子吧:

package com.atguigu.java;
/*
 * 方法的形参传递机制:值传递
 * 
 * 1. 形参:方法定义时,声明在小括号内的数据
 *    实参:方法调用时,实际传递给形参的数据
 *    
 * 2. 值传递机制
 * 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
 * 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值
 * 
 */
public class methodArguments {
    public static void main(String[] args) {
        //************************基本数据类型*******************************
        int m = 10;
        int n = 20;
        System.out.println("m: "+ m + ",n: " + n);
        
        methodArguments test = new methodArguments();   // m: 10,n: 20
        test.exchange(m, n);
        System.out.println("m: "+ m + ",n: " + n);   // m: 10,n: 20
        
        
        //************************引用数据类型*******************************
        numbers nums = new numbers();
        nums.m = 10;
        nums.n = 20;
        System.out.println("nums.m: "+ nums.m + ",nums.n: " + nums.n);   // nums.m: 10,nums.n: 20
        
        test.exchange(nums);
        System.out.println("nums.m: "+ nums.m + ",nums.n: " + nums.n);   // nums.m: 20,nums.n: 10
        
    }
    
    
    public void exchange(int m, int n){
        int temp = m;
        m = n;
        n = temp;
    }
    
    public void exchange(numbers nums){   // 方法重载
        int temp = nums.m;
        nums.m = nums.n;
        nums.n = temp;
    }
    
    

}

class numbers{
    int m,n;
}


可以看到,如果传入的是普通数据类型,实际上并没有发生值的交换(即传入的是副本,仅仅交换副本并不会影响原始数据);
但是如果传入的是引用数据类型,因为实际上传入引用数据类型都是地址值,所以形参和实参会同时指向这个引用数据类型(比如说堆空间中对象),修改形参就是修改实参

总之记住:

这是绝对不会错的

一道很综合的例题

例题

我是这样写的:

package com.atguigu.practice;
/* 1.定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个
     findArea()方法返回圆的面积。

 * 2.定义一个类PassObject,在类中定义一个方法printAreas(),该方法的定义
               如下:public void printAreas(Circle c, int time)
       在printAreas方法中打印输出1到time之间的每个整数半径值,以及对应的面积。
       例如,times为5,则输出半径1,2,3,4,5,以及对应的圆面积。
       
 * 
 */
public class practice1 {
    public static void main(String[] args) {
        Circle c = new Circle();
        PassObject test = new PassObject();
        
        test.printArea(c, 5);
        System.out.println("now radius is: " + c.radius);
    }
}

class Circle{
    double radius;
    
    public double findArea(){
        return Math.PI * radius *radius;
    }
}

class PassObject{
    public void printArea(Circle c ,int time){
        System.out.println("Radius" + "\t" + "Area");
        for(int i = 1; i <= time; i++){
            c.radius = i;
            System.out.println(c.radius + "\t" + c.findArea());
        }
        c.radius ++;
    }
}

递归

image.png

注:什么叫做往已知方向递归?
比如上面那个求1-100的和,已知情况是当 num = 1 的时候 sum = 1 ;所以在求
当 num = 100 时 sum(100) 的值,会先求的 sum(num - 1) 、即sum( 99 ) 的值;如果想求 sum(99) 的值,就要先求sum(98) 的值... 以此类推,朝着已知条件sum(1) = 1 这个方向前进,这就是朝已知方向递归。

典型例子:
已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),其中n是大于0的整数,求f(10)的值。

    public int getSeq(int n){
        if(n == 0){
            return 1;
        }else if(n == 1){
            return 4;
        }else{
            return 2 * getSeq(n - 1) + getSeq(n - 2); 
        }
    }

朝已知条件方向f(0) = 1, f(1) = 4前进,只能是用减法推回去
但是如果:
已知一个数列:f(20) = 1,f(21) = 4,f(n+2) = 2*f(n+1)+f(n),其中n是大于0的整数,求f(10)的值。

    public int getSeq1(int n){
        if(n == 20){
            return 1;
        }else if(n == 21){
            return 4;
        }else if(n < 20){
            return getSeq1(n + 2) - 2 * getSeq(n + 1); 
        }else{    // 如果当 n > 20 毫无疑问只能向已知条件的反方向递归了
            return 2 * getSeq1(n - 1) + getSeq1(n - 2); 
        } 
    }

顺便一提,如果这里试求f(30),那么和上一题f(10)的结果是一样的,都是10497

斐波那契数列:

image.png
    public int Fibonacci(int n){
        if(n == 1){
            return 1;
        }else if(n == 2){
            return 1;
        }else{
            return Fibonacci(n - 1) + Fibonacci(n - 2);
        }
    }
上一篇下一篇

猜你喜欢

热点阅读