【JavaSE】06-面向对象(上)

六、 面向对象(上)

6.0 面向对象的三大思想

  1. 面向对象思想编程内容的三条主线:

① 类及类的成员:属性、方法、构造器;代码块、内部类

② 面向对象的三大特征:封装性、继承性、多态性

③ 其它关键字:this,super,abstract,interface,static,final,package,import

6.1 类的成员一:属性

6.1.2 属性与局部变量

不同点:

( 1 \mathbf{1} 1) 属性/成员变量可以在声明前添加权限修饰符。如

public/private

( 2 \mathbf{2} 2) 属性/成员变量在类的大括号中就已经声明,局部变量只在类的方法中声明。

属性/成员变量局部变量
默认初始化值和数组情况一样没有默认初始化值,必须显式赋值。但形参可以等到调用方法再赋值。
在内存的堆中在内存的栈中

6.2 类的成员二:方法

权限修饰符 返回值类型 方法名 (形参列表){
		方法体
	}

( 1 \mathbf{1} 1) 方法里面可以再调方法。但不能在方法里定义方法。

6.2.1 对象数组题目

/**
 * 4. 对象数组题目:
 * 定义类Student,包含三个属性:学号number(int),
 年级state(int),成绩score(int)。
 * 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
 * 问题一:打印出3年级(state值为3)的学生信息。
 * 问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息。
 *
 * 提示:
 * 1) 生成随机数:Math.random(),返回值类型double;
 * 2) 四舍五入取整:Math.round(double d),返回值类型long。
 */

我首次代码:

public class Exercise4StudentTest {
    public static void main(String[] args) {
        //声明一个Student类型的数组
        Student[] stuArr = new Student[20];
        /**
         * 注意!此时堆中20个数组都是null。必须逐一new Student对象才能正常使用
         */
        for (int i=0; i<stuArr.length; i++){
            stuArr[i] = new Student();      //这一步我第一次做时漏了!
            stuArr[i].number = (i+1);
            stuArr[i].state = (int) (Math.random() * 10) + 1;
            stuArr[i].score = (int) (Math.random() * 100);
        }

        //问题一:打印出3年级(state值为3)的学生信息。
        for (int i=0; i<stuArr.length; i++){
            if (stuArr[i].state == 3){
                stuArr[i].say();
                System.out.println("-----------------------");
            }
        }

        //问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息。
        for (int i = stuArr.length-1; i>=0; i--){
            for (int j=0; j<i; j++){
                if (stuArr[j].score > stuArr[j+1].score){
                    Student temp = new Student();
                    temp = stuArr[j];
                    stuArr[j] = stuArr[j+1];
                    stuArr[j+1] = temp;
                }
            }
        }

        //遍历所有学生信息.
        for (int i=0; i<stuArr.length; i++){
            stuArr[i].say();
            System.out.println("----------------------");
        }
    }
}

class Student{
    int number;
    int state;
    int score;

    public void say(){
        System.out.println("学号:" + number);
        System.out.println("年级:" + state);
        System.out.println("成绩:" + score);
    }
}

代码优化思路:

( 1 \mathbf{1} 1) 把两个需求分别封装到方法里,main方法里直接调用方法可以使代码看起来更加简洁美观。

优化后的代码:

public class Exercise4StudentTest {
    public static void main(String[] args) {
        //声明一个Student类型的数组
        Student[] stuArr = new Student[20];
        /**
         * 注意!此时堆中20个数组都是null。必须逐一new Student对象才能正常使用
         */
        for (int i=0; i<stuArr.length; i++){
            stuArr[i] = new Student();      //这一步我第一次做时漏了!
            stuArr[i].number = (i+1);
            stuArr[i].state = (int) (Math.random() * 6) + 1;
            stuArr[i].score = (int) (Math.random() * 100) + 1;
        }
        //创建测试对象
        Exercise4StudentTest test = new Exercise4StudentTest();
        //1. 遍历对象数组的属性,并打印出来
        test.print(stuArr);
        //2. 打印出k年级(state值为k)的学生信息
        test.search(stuArr, 5);
        //3.使用冒泡排序按学生成绩排序
        test.sort(stuArr);
        //遍历对象数组的属性,并打印出来
        test.print(stuArr);
        
    }

    /**
     * 遍历对象数组的属性,并打印出来
     * @param stuArr: 需要打印的对象数组
     */
    public void print(Student[] stuArr){
        for (int i=0; i<stuArr.length; i++){
            stuArr[i].say();
        }
    }

    /**
     *打印出k年级(state值为k)的学生信息
     * @param stuArr: 需要搜索的对象数组
     * @param k: 需要查找的学号
     */
    public void search (Student[] stuArr, int k){
        for (int i=0; i<stuArr.length; i++){
            if (stuArr[i].state == k){
                stuArr[i].say();
                System.out.println("-----------------------");
            }
        }
    }

    /**
     * 使用冒泡排序按学生成绩排序
     * @param stuArr:需要排序的数组
     */
    public void sort (Student[] stuArr){
        for (int i = stuArr.length-1; i>=0; i--){
            for (int j=0; j<i; j++){
                if (stuArr[j].score > stuArr[j+1].score){
                    Student temp = new Student();
                    temp = stuArr[j];
                    stuArr[j] = stuArr[j+1];
                    stuArr[j+1] = temp;
                }
            }
        }
    }
}

class Student{
    int number;
    int state;
    int score;

    public void say(){
        System.out.println("学号:" + number + " 年级:" + state + " 成绩:" + score);
    }
}

6.3 匿名对象

6.3.1 匿名对象的声明

( 1 \mathbf{1} 1) 构造一个Phone类:

class Phone{
    double price;//价格

    public void showPrice(){
        System.out.println("手机价格为:" + price);
    }
    public void sendEmail(){
        System.out.println("发生邮件");
    }
    public void playGames(){
        System.out.println("玩游戏");
    }
}

( 2 \mathbf{2} 2) 创建一个Phone类对象,并分别写非匿名对象和匿名对象:

public class AnonymousObject {
    public static void main(String[] args) {
        //非匿名对象
        Phone p = new Phone();
        p.sendEmail();
        p.playGames();

        //匿名对象
        new Phone().sendEmail();
        new Phone().playGames();
    }
}

输出结果:

发生邮件
玩游戏
发生邮件
玩游戏

6.3.2 匿名对象特点

( 1 \mathbf{1} 1) 只能调用一次,重复调用不相同。见下面例子:

new Phone().price = 2999;
new Phone().showPrice();

输出结果:

手机价格为:0.0

6.3.3 开发中的使用方法

( 1 \mathbf{1} 1) 在上述代码的基础上,新建一个类:

class PhoneMall{
    public void show(Phone phone){
        phone.sendEmail();
        phone.playGames();
    }
}

其中,方法show的形参是Phone类型的对象,方法体中又调用了Phone类中的方法。

( 2 \mathbf{2} 2) 在main方法中新建PhoneMall类的对象,并调用类中的show方法:

PhoneMall mall = new PhoneMall();
mall.show(new Phone()); //匿名对象的形参

输出结果:

发送邮件
玩游戏

( 3 \mathbf{3} 3) 由于show方法的形参必须是Phone类型的对象,所以可以使用匿名对象新建一个Phone对象。

6.4 方法的重载

6.4.1 定义

在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。

“两同一不同”:同一个类,同一个方法名;参数列表不同。

个人理解:

( 1 \mathbf{1} 1) 之所以要允许同名方法的存在,是因为同一个方法,形参的数据类型不同,比如int、char、String、double等,没有必要因为形参的数据类型不同而起不同的方法名。

( 2 \mathbf{2} 2) 判断方法是否重载跟权限修饰符、返回值类型、形参变量名、方法体都没有关系。只跟方法名和形参列表是否相同有关。

( 3 \mathbf{3} 3) 在通过对象调用方法时,除了看方法名,还要看形参列表来确定某一个指定的方法。

6.4.2 举例

//如下的4个方法构成了重载
public void getSum(int i, int j){
    System.out.println(i + j);
}
public void getSum(double d1, double d2){
    System.out.println(d1 + d2);
}
public void getSum(String i, int j){
    System.out.println(i + j);
}
public void getSum(int j, String i){
    System.out.println(i + j);
}

第3个与第4个因为形参顺序不同,也算方法重载。

//如下的3个方法不能与上述4个方法构成重载
//	public int getSum(int i,int j){
//		return 0;
//	}
	
//	public void getSum(int m,int n){
//		
//	}
	
//	private void getSum(int i,int j){
//		
//	}

6.4.3 练习题

( 1 \mathbf{1} 1) 编写程序,定义三个重载方法并调用。方法名为mOL。
三个方法分别接收一个int参数、两个int参数、一个字符串参数。
分别执行平方运算并输出结果,相乘并输出结果,输出字符串信息。在主类的main ()方法中分别用参数区别调用三个方法。

我的首次答案:

public class OverLoad {
    public static void main(String[] args) {
        OverLoad test = new OverLoad();
        test.mOL(5);
        test.mOL(5, 6);
        test.mOL("Deep Dark Fantasies");
    }

    public void mOL(int i){
        System.out.println(i*i);
    }
    public void mOL(int i, int j){
        System.out.println(i*j);
    }
    public void mOL(String i){
        System.out.println(i);
    }
}

输出结果:

25
30
Deep Dark Fantasies

( 2 \mathbf{2} 2) 定义三个重载方法max(),
第一个方法求两个int值中的最大值,
第二个方法求两个double值中的最大值,
第三个方法求三个double值中的最大值,
并分别调用三个方法。

我的首次答案:

public class OverLoad {
    public static void main(String[] args) {
        OverLoad test = new OverLoad();

        //2.三个重载方法max()调用
        System.out.println(test.max(3, 93));
        System.out.println(test.max(3.3, 55.6));
        System.out.println(test.max(99.9, 3.3, 55.5));
    }

    //2.三个重载方法max()
    public int max(int i, int j){
        if (i>j){
            return i;
        }else {
            return j;
        }
    }
    public double max(double d1, double d2){
        if (d1>d2){
            return d1;
        }else {
            return d2;
        }
    }
    public double max(double d1, double d2, double d3){
        double max = d1;
        if (d2>max) {
            max = d2;
            if (d3>max){
                max = d3;
            }
        }else if (d3>max){
            max = d3;
        }
        return max;
    }
}

输出结果:

93
55.6
99.9

优化思路:

( 1 \mathbf{1} 1) 对方法max() 进行优化:

public int max(int i, int j){
    return (i>j)? i : j;
}
public double max(double d1, double d2){
        return (d1>d2)? d1 : d2;
}
public double max(double d1, double d2, double d3){
        double max = (d1>d2)? d1 : d2;
        return (max>d3)? max : d3;
}

6.5 可变形参的方法

6.5.1 可变个数形参的声明格式

Varargs (variable number of arguments) 机制:参数数量可变。

public void show(数据类型 ... 参数名){
    方法体;
}

6.5.2 调用

public class VariableArguments {
    public static void main(String[] args) {
        VariableArguments test = new VariableArguments();
        test.show();
        test.show("Boy");
        test.show("Boy", "Next Door");
        test.show("Deep", "Dark", "Fantasies");
    }
    public void show(String ... strings){
        System.out.println("show(String ... strings)");
    }
}

输出结果:

show(String ... strings)
show(String ... strings)
show(String ... strings)
show(String ... strings)

( 1 \mathbf{1} 1) 可以看到,传入的形参个数可以为0个、1个、2个、3个…

( 2 \mathbf{2} 2) 注意。当有确定形参个数的方法和可变个数形参方法同时存在时,程序会优先选择前者。(程序的确定性)

( 3 \mathbf{3} 3) 可变个数形参方法可以构成重载。

( 4 \mathbf{4} 4) 下列不构成重载:

public void show(String ... strings){
    System.out.println("show(String ... strings)");
}
public void show(String[] strings){
    System.out.println("show(String ... strings)");
}

它们的形参列表被认为是相同的。

( 5 \mathbf{5} 5) 遍历也是和数组一样:

public class VariableArguments {
    public static void main(String[] args) {
        VariableArguments test = new VariableArguments();
        test.show();
        test.show("Boy");
        test.show("Boy", "Next Door");
        test.show("Deep", "Dark", "Fantasies");
    }
    public void show(String ... strings){
        System.out.println("-------------------------");
        for (int i=0; i<strings.length; i++){
            System.out.println(strings[i]);
        }
    }
}

输出结果:

-------------------------
-------------------------
Boy
-------------------------
Boy
Next Door
-------------------------
Deep
Dark
Fantasies

( 6 \mathbf{6} 6) 可变个数形参,必须声明在末尾。如:

public void show(int i, String ... strings){
    
}

是可行的。但如果上述两个形参对调位置,则不行!
image-20220220145655627

( 7 \mathbf{7} 7) 由上述可推断,可变个数形参最多只能声明一个。

6.5.3 应用场景

在SQL数据库的时候,不知道用户后面要写多少个String型,就可以用可变个数形参。

6.6 方法参数的值传递机制 (重点)

6.6.1 形参和实参

( 1 \mathbf{1} 1) 形参:方法声明时的参数。

( 2 \mathbf{2} 2) 实参:方法调用时实际传给形参的参数值。

6.6.2 实参传入方法的方式

Java里方法的参数传递方式只有一种: 值传递 。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响 。

值传递机制:

( 1 \mathbf{1} 1) 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参。

( 2 \mathbf{2} 2) 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参。

6.6.3 值传递举例说明

( 1 \mathbf{1} 1) 形参是基本数据类型

问题:交换m和n中的值。

//两数交换方法
public static void trans(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    System.out.println("a=" + a);
    System.out.println("b=" + b);
}

public static void main(String[] args) {
    int a = 1;
    int b = 2;
    trans(a, b);
    System.out.println("---------------");
    System.out.println("a=" + a + ", b=" + b);//原始的变量不会被改变
}

输出:

a=2
b=1
---------------
a=1, b=2

可以看到,对于基本数据类型来说,虽然 trans(int a, int b) 方法内,形参 a 和形参 b 被交换了值,但是方法外的变量 ab 的值没有因此受到影响,保持原来的值不变。

这就是 Java 中的基本数据类型的值传递机制,只会传递参数的“数据值”,而不影响真正的变量。

( 2 \mathbf{2} 2) 形参是引用数据类型

//形参是引用数据类型的值传递,传递的是地址值
public static void myArray(int[] arr) {
    arr[0] = 100;
    for (int i : arr) {
        System.out.println(i);
    }
}

public static void main(String[] args) {
    int[] arr = new int[]{1, 2, 3, 4};
    myArray(arr);
    System.out.println("-----------");
    for (int i : arr) {
        System.out.println(i);//直接传递地址,原始数组会改变
    }
}

输出:

100
2
3
4
-----------
100
2
3
4

可以看到,对于引用数据类型来说,将实参引用数据类型变量的“地址值”传递给形参,方法内对形参进行修改,会影响实参的值,因为传入的是变量的地址值。

这就是 Java 中的引用数据类型的值传递机制,传递的是引用变量的“地址值”,会影响真正的变量。

习题一:

(1)定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积。

(2)考查参数的值传递。定义一个类PassObject,在类中定义一个方法printAreas(),该方法的定义如下:public void printAreas(Circle c, int time)在printAreas方法中打印输出1到time之间的每个整数半径值,以及对应的面积。
例如,time为5,则输出半径1,2,3,4,5,以及对应的圆面积。

(3)在main方法中调用printAreas()方法,调用完毕后输出当前半径值。程序运行结果如图所示。

我的首次答案:

public class ValueTransferExercise {
    public static void main(String[] args) {
        Circle c = new Circle();
        PassObject p = new PassObject();
        p.printAreas(c, 5);
    }
}
class Circle {
    double radius;  //半径

    public double findArea(){   //求面积
        return Math.PI * radius * radius;
    }
}

class PassObject {
    public void printAreas(Circle c, int time){
        //注意这里传入的第一个形参是Circle类的对象
        System.out.println("Radius              Area");
        for (int i =1 ; i<=time; i++){
            c.radius = (double) i;
            System.out.println(c.radius + "                 " + c.findArea());
        }
    }
}

输出结果:

Radius              Area
1.0                 3.141592653589793
2.0                 12.566370614359172
3.0                 28.274333882308138
4.0                 50.26548245743669
5.0                 78.53981633974483

优化代码:用制表符\t代替空格

class PassObject {
    public void printAreas(Circle c, int time){
        //注意这里传入的第一个形参是Circle类的对象
        System.out.println("Radius\t\t\tArea");
        for (int i =1 ; i<=time; i++){
            c.radius = (double) i;
            System.out.println(c.radius + "\t\t\t\t" + c.findArea());
        }
    }
}

6.7 递归方法

6.7.1 递归的概念

( 1 \mathbf{1} 1) 递归方法:一个方法体内调用它自身。

( 2 \mathbf{2} 2) 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。

( 3 \mathbf{3} 3) 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。

6.7.2 递归与循环的对比

题目1:

计算1-100之间所有自然数的和

方法一:循环法

int num = 100;
int sum = 0;
for (int i =  num; i>=0; i--){
    sum += i;
}
System.out.println("方法一的和为:" + sum);

方法二:递归法

public class Recursion {
    public static void main(String[] args) {
        //计算1-100之间所有自然数的和,方法二
        Recursion recursion = new Recursion();
        System.out.println("方法二的和为:" + recursion.sum(100));
    }
    //计算1-100之间所有自然数的和
    public int sum(int num){
        if (num==1){
            return 1;
        }else {
            return num + sum(num - 1);
        }
    }
}

输出结果:

方法一的和为:5050
方法二的和为:5050

题目2:

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

我的首次答案:

//例子3:已知有一个数列:f(0) = 1,f(1) =4,f(n+2)=2*f(n+1) + f(n),其中n是大于0的整数,求f(10)的值。
public class Recursion {
    public static void main(String[] args) {
        Recursion recursion = new Recursion();
        //例子3
        System.out.println("数列f(10)为:" + recursion.f(3));
    }
    
    public int f(int n){
    if (n==0){
        return 1;
    }else if (n==1){
        return 4;
    }else {
        return 2*f(n-1) + f(n-2);
    }
}

输出结果:

数列f(10)为:10497

题目3:斐波那契数列

/**
 * 例子4:斐波那契数列
 * 输入一个数据 n ,计算斐波那契数列 (Fibonacci)的第n个值:1 1 2 3 5 8 13 21 34 55
 * 规律:一个数等于前两个数之和
 * 要求:计算斐波那契数列 (Fibonacci)的第n个值,并将整个数列打印出来
 */

我的首次代码:

public class Recursion {
    public static void main(String[] args) {
        Recursion recursion = new Recursion();
        //例子4
        System.out.print("斐波那契数列为:");
        recursion.printFibonacci(10);
    }
    /**
     * 例子4:斐波那契数列
     * 输入一个数据 n ,计算斐波那契数列 (Fibonacci)的第n个值:1 1 2 3 5 8 13 21 34 55
     * 规律:一个数等于前两个数之和
     * 要求:计算斐波那契数列 (Fibonacci)的第n个值,并将整个数列打印出来
     */
    public int fibonacci(int n){
        if (n==1){
            return 1;
        }else if (n==2){
            return 1;
        }else {
            return fibonacci(n-1) + fibonacci(n-2);
        }
    }
    public void printFibonacci(int n){
        int[] arr = new int[n];
        for (int i=0; i<n; i++){
            arr[i] = fibonacci(i+1);
        }
        for (int i=0; i<n; i++){
            System.out.print(arr[i] + "\t");
        }
    }
}

输出结果:

斐波那契数列为:1	1	2	3	5	8	13	21	34	55

6.8 封装性

6.8.1 封装性的内涵

( 1 \mathbf{1} 1) 把特定功能的程序封装成方法,从而无需考虑方法内部的具体代码,提高复用性,降低代码复杂度。

( 2 \mathbf{2} 2) 程序设计追求 “高内聚,低耦合”。
① 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;

② 低耦合:仅对外暴露少量的方法用于使用 。

( 3 \mathbf{3} 3) 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性可维护性。通俗地说,把该隐藏的隐藏起来,该暴露的暴露出来 。这就是封装性的设计思想。

6.8.2 对属性的封装

( 1 \mathbf{1} 1) 在实际问题中,属性是有很多其他限制的,要禁止用户直接通过"对象.属性=…" 的方式去直接赋值,通过在属性前添加权限修饰符 private 即可避免属性在 main 方法中被修改,只能在方法中被修改。

( 2 \mathbf{2} 2) 在对属性 (private) 封装后,除了提供公共的 (public) 方法中要补上"属性赋值" (setXXX)的操作外,还需补上 “属性读取” (getXXX) 的方法。

6.8.3 四种权限修饰符

( 1 \mathbf{1} 1) 封装性的体现,需要权限修饰符来配合。

( 2 \mathbf{2} 2) 四种权限修饰符从小到大排列:private、缺省(不写)、protected、public。

image-20220221091917842

( 3 \mathbf{3} 3) 四种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类。

( 4 \mathbf{4} 4) 特别的,修饰类的话,只能使用:缺省、public。

6.8.4 封装性练习

题目1:

1.创建程序,在其中定义两个类:Person和PersonTest类。定义如下:
用setAge()设置人的合法年龄(0~130),用getAge()返回人的年龄。

我的首次答案:

public class Person {
    private int age;

    //设置合法年龄
    public void setAge(int i){
        if (i>=0 && i<=130){
            age = i;
        }else {
            age = 0;
        }
    }
    //获取年龄
    public int getAge(){
        return age;
    }
}
public class PersonTest {
    public static void main(String[] args) {
        Person p = new Person();
//        p.age = 1;    编译不通过,不可以直接调用age
        p.setAge(23);
        System.out.println("年龄为:" + p.getAge());
    }
}

输出结果为:

年龄为:23

6.9 构造器(或构造方法)

6.9.1 构造器内涵

( 1 \mathbf{1} 1) 特征:

① 它具有与类相同的名称
② 它不声明返回值类型。(与声明为 void 不同)
③ 不能被 static、final、synchronized、abstract、native 修饰,不能有 return 语句返回值。

( 2 \mathbf{2} 2) 作用:创建对象;给对象进行初始化。

如:

Order o = new Order();
Person p = new Person (Peter,15)

如同 我们规定每个“人”一出生就必须先洗澡,我们就可以在“人” 的
构造 器 中 加入完成“洗澡”的程序代码,于是每个“人”一出生就会自
动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们
要“洗澡”了 。

6.9.2 构造器说明

( 1 \mathbf{1} 1) 如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器。如:

Order o = new Order();

中的 “Order( )”。

( 2 \mathbf{2} 2) 构造器格式:

权限修饰符 类名(形参列表){
	代码;
}

例:创建一个Person类,并定义一个构造器

public class Person {
    String name;

    //构造器
    public Person(String n){
        name = n;
    } 
}

这样在新建对象p时就能直接给name属性赋值。起到初始化属性的作用。

public class PersonTest {
    public static void main(String[] args) {
        Person p = new Person("Rick");
        System.out.println(p.name);
    }
}

输出结果:

Rick

( 3 \mathbf{3} 3) 同一个类中可以定义多个构造器,称作构造器的重载。

( 4 \mathbf{4} 4) 一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器。必须自己定义一个重载的空参构造器。

( 5 \mathbf{5} 5) 一个类中,至少会有一个构造器。

6.9.3 属性赋值的先后顺序

总结:

① 默认初始化

② 显式初始化

③ 构造器中初始化

④ 通过"对象.方法" 或 "对象.属性"的方式,赋值

以上操作的先后顺序:① - ② - ③ - ④

6.9.4 构造器练习

题目1:

2.1. 在前面定义的Person类中添加构造器,利用构造器设置所有人的age属性初始值都为18。
2.2. 修改上题中类和构造器,增加name属性,使得每次创建Person对象的同时初始化对象的age属性值和name属性值。
public class Person {
    private String name;
    private int age;

    //构造器
    public Person(){
        age = 18;
    }
    public Person(String n, int i){
        name = n;
        age = i;
    }

    //设置合法年龄
    public void setAge(int i){
        if (i>=0 && i<=130){
            age = i;
        }else {
            age = 0;
        }
    }
    //获取年龄
    public int getAge(){
        return age;
    }
    //设置名字
    public void setName(String n){
        name = n;
    }
    //获取名字
    public String getName(){
        return name;
    }
}

测试:

public class PersonTest {
    public static void main(String[] args) {
        Person p = new Person("Rick", 23);
        System.out.println("姓名为:" + p.getName());
//        p.age = 1;    编译不通过,不可以直接调用age
//        p.setAge(23);
        System.out.println("年龄为:" + p.getAge());
    }
}

输出结果:

姓名为:Rick
年龄为:23

题目2:

编写两个类,TriAngle和TriAngleTest,其中TriAngle类中声明私有的底边长base和高height,同时声明公共方法访问私有变量。

此外,提供类必要的构造器。另一个类中使用这些公共方法,计算三角形的面积。

我的首次答案:

public class TriAngle {
    //属性
    private double base;
    private double height;

    //构造器
    public TriAngle(){

    }
    public TriAngle(double b, double h){
        base = b;
        height = h;
    }

    //方法
    public void setBase(double b){
        base =  b;
    }
    public  void setHeight(double h){
        height = h;
    }
    public double getBase(){
        return base;
    }
    public double getHeight(){
        return height;
    }
}

测试:

public class TriAngleTest {
    public static void main(String[] args) {
        TriAngle t = new TriAngle(8, 6);
        System.out.println("底边长:" + t.getBase());
        System.out.println("高为:" + t.getHeight());

        //计算三角形面积
        double area = 0.5 * t.getBase() * t.getHeight();
        System.out.println("面积为:" + area);
    }
}

输出:

底边长:8.0
高为:6.0
面积为:24.0

题目3:

image-20220221121431763

我的首次答案:

public class Student {
    String name;
    int age;
    String school;
    String major;

    //构造器
    public Student(){

    }
    public Student(String n, int a){
        name = n;
        age = a;
    }
    public Student(String n, int a, String s){
        name = n;
        age = a;
        school = s;
    }
    public Student(String n, int a, String s, String m){
        name = n;
        age = a;
        school = s;
        major = m;
    }

    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student("Rick", 23);
        Student s3 = new Student("Van", 45, "Fantasies College");
        Student s4 = new Student("Sihang Xie", 24, "Ocean University of China", "Computer Technology");
        System.out.println("姓名:"+s1.name + "\t年龄:"+s1.age + "\t学校:"+s1.school + "\t专业:"+s1.major);
        System.out.println("姓名:"+s2.name + "\t年龄:"+s2.age + "\t学校:"+s2.school + "\t专业:"+s2.major);
        System.out.println("姓名:"+s3.name + "\t年龄:"+s3.age + "\t学校:"+s3.school + "\t专业:"+s3.major);
        System.out.println("姓名:"+s4.name + "\t年龄:"+s4.age + "\t学校:"+s4.school + "\t专业:"+s4.major);
    }
}

输出:

姓名:null	年龄:0	学校:null	专业:null
姓名:Rick	年龄:23	学校:null	专业:null
姓名:Van	年龄:45	学校:Fantasies College	专业:null
姓名:Sihang Xie	年龄:24	学校:Ocean University of China	专业:Computer Technology

6.9.5 JavaBean

( 1 \mathbf{1} 1) JavaBean 是一种 Java 语言写成的可重用组件。

( 2 \mathbf{2} 2) 所谓 javaBean,是指符合如下标准的 Java 类:
① 类是公共的;

​ ② 有一个无参的公共的构造器;

​ ③ 有属性,且有对应的 get 、 set 方法。

( 3 \mathbf{3} 3) 用户 可以使用 JavaBean 将功能、处理、值、数据库访问和其他任何 可以用 Java 代码创造的对象进行打包,并且其他的开发者可以通过内部的 JSP页面、 Servlet 、其他 JavaBean 、 applet 程序或者应用来使用这些对象。用户可以认为 JavaBean 提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。

6.9.6 UML类图

image-20220221124629792

6.10 关键字:this的使用

6.10.1 this 的涵义

( 1 \mathbf{1} 1) this 可以用来修饰、调用:属性、方法、构造器。

( 2 \mathbf{2} 2) 个人理解:在形参名和属性名重名时用在属性名前,加以区分。

例:方法中,

//设置名字
public void setName(String name){
    this.name = name;
}

其中,“this.name” 指的是当前对象的属性name ,等号右边的 “name” 是此方法的形参。

6.10.2 this调用构造器

① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器。

② 构造器中不能通过"this(形参列表)"方式调用自己。

③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"。

④ 规定:"this(形参列表)"必须声明在当前构造器的首行。

⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器。

6.10.3 例子

题目1:

image-20220221141911221

我的首次代码:

Boy类:

public class Boy {
    private String name;
    private int age;

    //构造器
    public Boy(){
        System.out.println("The boy object has been created.");
    }
    public Boy(String name){
        this();
        this.name = name;
    }
    public Boy(int age){
        this();
        this.age = age;
    }
    public Boy(String name, int age){
        this(name);
        this.age = age;
    }

    //方法
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
    public void setAge(int age){
        if (age>=0 && age<=130){
            this.age = age;
        }
        this.age = 0;
    }
    public int getAge(){
        return this.age;
    }
    public void marry(Girl girl){
        System.out.println(this.name + " marry with "+girl.getName());
    }
    public void shout(){
        if (this.age>=22){
            System.out.println("恭喜您!结婚领证了!");
        }else {
            System.out.println("很遗憾!您未够年龄结婚~");
        }
    }

    //测试
    public static void main(String[] args) {
        Boy b1 = new Boy("Rick", 24);
        Girl g1 = new Girl("Rain", 22);
        System.out.println(b1.getName() + ", " + b1.getAge());
        System.out.println(g1.getName() + ", " + g1.getAge());
        b1.marry(g1);
        b1.shout();
        g1.marry(b1);
    }
}

Girl类:

public class Girl {
    private String name;
    private int age;

    //构造器
    public Girl(){
        System.out.println("The girl object has been created.");
    }
    public Girl(String name){
        this();
        this.name = name;
    }
    public Girl(int age){
        this();
        this.age = age;
    }
    public Girl(String name, int age){
        this(name);
        this.age = age;
    }

    //方法
    public void setName(String name){
        this.name = name;
    }
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age>=0 && age<=130){
            this.age = age;
        }
        this.age = 0;
    }

    public void marry(Boy boy){
        System.out.println("I want to marry with "+boy.getName());
        boy.marry(this);
    }
    
    public int compare(Girl girl){
        return this.age - girl.getAge();
    }
}

测试:

//测试
public static void main(String[] args) {
    Boy b1 = new Boy("Rick", 24);
    Girl g1 = new Girl("Rain", 22);
    System.out.println(b1.getName() + ", " + b1.getAge());
    System.out.println(g1.getName() + ", " + g1.getAge());
    b1.marry(g1);
    b1.shout();
    g1.marry(b1);
}

输出:

The boy object has been created.
The girl object has been created.
Rick, 24
Rain, 22
Rick marry with Rain
恭喜您!结婚领证了!
I want to marry with Rick
Rick marry with Rain

6.11 关键字:package、import

6.11.1 package关键字

( 1 \mathbf{1} 1​) package 语句作为 Java 源文件的第一条语句,指明该文件中定义的类所在的包。 若缺省该语句,则指定为无名包 。它的格式为:

package 顶层包名.子包名;

( 2 \mathbf{2} 2) 包对应于文件系统的目录, package 语句中,用 “.” 来指明包 (目录) 的层次;每"."一次,就代表一层文件目录。

( 3 \mathbf{3} 3​) 包通常用小写单词标识。通常使用所在公司域名的倒置:

cn.edu.ouc.xxx(包名)

( 4 \mathbf{4} 4) 同一个包下,不能命名同名的接口、类。不同的包下,可以命名同名的接口、类。

( 5 \mathbf{5} 5) JDK中主要的包:

image-20220223090101920

( 6 \mathbf{6} 6) MVC设计模式:

image-20220223090853251

以后的大项目的包,也要遵循这种结构来创建。

image-20220223090927422

6.11.2 import 关键字

( 1 \mathbf{1} 1) import的作用:为使用定义在不同包中的 Java 类,需用 import 语句来引入 指定包层次下 所需要的类或全部类 。 import 语句告诉编译器到哪里去寻找类。

格式:

import 包名.类名;

( 2 \mathbf{2} 2) 可以使用"xxx.*****"的方式,表示可以导入xxx包下的所有结构。

( 3 \mathbf{3} 3) 如果使用的类或接口是java.lang包下定义的,则可以省略import结构.

( 4 \mathbf{4} 4) 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。

Date date = new Date();
java.sql.Date date1 = new java.sql.Date(5243523532535L);

( 5 \mathbf{5} 5) 使用"xxx.*****"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

卡皮巴拉不躺平

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

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

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

打赏作者

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

抵扣说明:

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

余额充值