Java 面向对象基础

Java 面向对象基础

1.类和对象

  • 对象:对象是有数据以及对数据进行操作的方法组成的,是对现实世界中事物的抽象描述。
  • :类是具有相同数据格式(属性)和相同操作功能(方法)的对象的抽象。一个类是对一类对象的描述,是构成对象的模板。对象是类的具体实例。

面向对象的程序设计思想(相较于面向过程的程序设计思想更接近人的思维模式)

2.类的定义

Java的基本语法
修饰符 class 类名{
    //类体
    [数据声明]
    [方法声明]
}
  • 修饰符:有几种可使用的修饰符,但现在只使用public,声明类可被任意访问.

  • 类名:可以是任何合法的标识符,并且是所声明类的名字.

  • 类体:声明了与类相关的数据和方法的集合

(1)成员变量

成员变量声明的一般格式

//修饰符 类型 名称;
例如:
private int x;
  • 修饰符:有几种可使用的修饰符,但现在只使用public或private. private声明该属性只能由该类的方法访问.

  • 名称:可以是任何合法的标识符,它是所声明属性的名字.

  • 类型:可以是任何基本类型或任何类类型

(2)类的使用(对象的创建与使用)
  1. 创建对象

    类定义后,可以通过实例化产生对象,这个过程称为创建对象。

    创建对象的格式: new 类名()。 创建对象的过程中,也称为实例化。对象也叫实例。

    注意区别和联系:实例/对象,对象,对象引用

  2. 赋值

    可以将创建的对象给同一类型的引用类型的变量赋值。

    变量=new 类名();

  3. 成员变量的使用

    在赋值后,就可以通过相应的引用类型的变量对对象的成员变量进行访问。

    成员变量的使用格式: 变量.成员变量

例:类和成员变量的使用

package classtext;
public class Birthday {
	public int year;
	public int month;
	public int day;
}
  • 在数据声明之前加上public表示该数据成员允许被访问,

  • 加上private则不允许被该类之外的类访问访问

  • 自己用class创建的类型都属于引用类型

  • 引用类型都必须用句式:

Birthday d1 = new Birthday();

(创建过程与数组类似)

存储空间的分配与数组基本一致(占用两块存储空间)

package classtext;
public class Demo {
	public static void main (String[] args) {
		Birthday d1 = new Birthday();//出现new的时候创建变量
		d1.year = 2003;
		d1.month = 10;
		d1.day = 15;
		System.out.printf("%04d/%02d/%02d\n", d1.year, d1.month, d1.day);
		Birthday d2 = new Birthday();
		d2.year = 2004;
		d2.month = 2;
		d2.day = 14;
		System.out.printf("%04d/%02d/%02d\n", d2.year, d2.month, d2.day);
	}
}
  • d1中存储的是Birthday的地址(类似于C语言中的指针)

  • .称为成员访问运算符,在Java中没有->这个运算符

  • d1称为对象引用,它引用的部分称为对象或是实例

(但由于d1引用的存储空间没有名字,所以有时候为了方便也会直接将d1称为对象或是实例)

  • 通过成员访问运算符来访问相应的成员
(3)静态成员变量

静态成员变量

  • static 修饰
  • 生命期:和程序共存亡
  • 访问方式:类名.成员变量名(推荐), 对象.成员变量名(不推荐使用)

实例成员变量

  • 创建实例对象后,才能访问的成员
  • 访问方式:对象名.成员变量名
package classtext;
public class Birthday {
	int year;
	int month;
	int day;
	
	public static int DAYS_OF_YEAR = 365;
}
  • 一个静态成员变量是整个类共享的一份,
  • 无论创建了多少个对象,它永远都只有一个,放于内存的静态区域
  • 一般用来表示一些固定不变的常量,比如圆周率
package classtext;
public class Demo {
	public static void main (String[] args) {
		Birthday d1 = new Birthday();
		d1.year = 2003;
		d1.month = 10;
		d1.day = 15;
		System.out.printf("%04d/%02d/%02d\n", d1.year, d1.month, d1.day);
		
		System.out.printf("%d\n", d1.DAYS_OF_YEAR);//警告⚠:不推荐该写法
		System.out.printf("%d\n", Birthday.DAYS_OF_YEAR);
		
		System.out.printf("%d\n", d1.year);
		//System.out.printf("%d\n", Birthday.year);报错⚠
		
		d1.DAYS_OF_YEAR = 366;
		System.out.println(d1.DAYS_OF_YEAR);//警告⚠
		System.out.println(d2.DAYS_OF_YEAR);//警告⚠
	}
}

静态成员变量直接通过类名来进行访问

(4)成员方法

成员方法的格式

修饰符 返回类型 方法名称(参数列表) {
    [语句]
}
  • 修饰符:有几种可用的修饰符,但现在只使用public或private. private声明该属性只能由该类的方法访问

  • 名称:可以是任何合法的标识符

package classtext;
public class Birthday {
	int year;
	int month;
	int day;
	
	public void setBirthday (int y, int m, int d){
		year = y;
		month = m;
		day = d;
	}
	
	public void print(){
		System.out.printf("%04d/%02d/%02d\n", year, month, day);
	}
}
  • 代码中year,month,day和setBirthday()方法在一起,但运行时,他们位于不同的区域。通过对象连接数据和方法

  • 方法在内存中只有一份,放于指令区在类内对数据成员的访问可以直接进行访问

package classtext;
public class Demo {
	public static void main (String[] args){
		Birthday d1 = new Birthday();
		d1.setBirthday(2003, 10, 15);
		System.out.printf("%04d/%02d/%02d\n", d1.year, d1.month, d1.day);
		d1.print();
		
		Birthday d2 = new Birthday();
		d2.setBirthday(2004, 2, 14);
		d2.print();
	}
}
  • 所有的对象共用一份方法

  • 调用的时候将运行环境传递给方法

  • 给对象分配内存的时候只包含数据成员的内存,即new的时候不需要给方法分配内存

  • 在类外访问数据成员需要通过对象间接访问

(5)静态成员方法

静态方法

  • static 修饰
  • 调用方法:类名.方法名(推荐),对象.方法名(不推荐使用)
  • 可直接访问本类的静态成员,不能直接访问本类的实例成员

实例方法

  • 调用方式:对象名.成员方法名();
  • 可以访问实例成员,也可以访问非实例成员
package classtext;
public class Birthday {
	int year;
	int month;
	int day;
	
	public void setBirthday (int y, int m, int d) {
		year = y;
		month = m;
		day = d;
	}
	
	public void print() {
		System.out.printf("%04d/%02d/%02d\n", year, month, day);
	}
	
	public static void fun() {
		System.out.printf("\n");
	}
}
package classtext;
public class Demo {
	public static void main (String[] args) {
		Birthday d1 = new Birthday();
		d1.setBirthday(2003, 10, 15);
		System.out.printf("%04d/%02d/%02d\n", d1.year, d1.month, d1.day);
		d1.print();
		
		Birthday.fun();
		d1.fun();//警告⚠
		
        //Birthday.print();报错⚠
	}
}
  • 静态成员方法通过类名调用

  • 实例成员通过对象名调用
    思 考 : 为 什 么 需 要 将 main() 方 法 设 为 static ?

package classtext;
public class Demo {
	Demo d = new Demo();
	d.main();
	public void main (String[] args) {
		Birthday d1 = new Birthday();
		d1.setBirthday(2003, 10, 15);
		System.out.printf("%04d/%02d/%02d\n", d1.year, d1.month, d1.day);
		d1.print();
	}
}

如果不将main方法设为static则会出现鸡生蛋蛋生鸡的问题:

如果main方法不是static则需要通过声明一个对象来调用main方法

但是main方法又是程序执行的入口点

所以需要在main方法中声明对象并调用

但是想使用main方法就需要通过声明一个对象来调用main方法

……

所以为了解决这个循环的问题就需要将main方法定义为static类型

解读输出语句:

System.out.println();

System是一个类

out是System的一个静态成员变量

println是out的一个实例方法

作业1

定义一个圆形类,该类有一个半径,可以设置半径的大小,并且可以计算圆的周长和面积.

面向过程

package ch3_Homework;
import java.util.Scanner;
public class CircleTest {
	public static void main (String[] args) {
		Circle c1=new Circle();
        System.out.println("请输入待求圆的半径:");
		Scanner sc=new Scanner(System.in);
		c1.r=sc.nextDouble();
		double ans1=calcCirum(c1.r);
		double ans2=calcArea(c1.r);
		sc.close();
		System.out.printf("该圆的周长是:%.2f\n", ans1);
		System.out.printf("该圆的面积是:%.2f\n", ans2);
	}
	public static double calcCirum(double r) {
		return 2*Circle.pi*r;
	}
	public static double calcArea(double r) {
		return Circle.pi*r*r;
	}
}
//错误写法(对面向过程的程序设计思想理解不到位)
package ch3_Homework;
import java.util.Scanner;
public class CircleTest {
	public static void main (String[] args) {
		Circle c1=new Circle();
        System.out.println("请输入待求圆的半径:");
		Scanner sc=new Scanner(System.in);
		c1.r=sc.nextDouble();
		sc.close();
		calcCirum(c1.r);
		calcArea(c1.r);
		
	}
	public static void calcCirum(double r) {
		System.out.printf("该圆的周长是:%.2f\n", 2*Circle.pi*r);
	}
	public static void calcArea(double r) {
		System.out.printf("该圆的面积是:%.2f\n", Circle.pi*r*r);
	}
}

面向对象

package ch3_Homework;
public class Circle {
	 public double r;
	 public static double pi = 3.14;
    //此处的成员方法不可是静态的成员方法
    //因为静态的成员方法是不需要创建对象就可以直接调用的,那么在该方法中用到的本类的实例成员就是不存在的
    //所以应该用非静态的成员变量
	 public double CalcPerimeter() {
		 return 2 * r *pi;
	 }
	 public double CalcArea() {
		 return r * r *pi;
	 }
}

package ch3_Homework;
import java.util.Scanner;
public class CircleTest {
	public static void main (String[] args) {
		Circle x = new Circle();
		System.out.println("请输入圆的半径:");
		Scanner sc = new Scanner(System.in);
		double y = sc.nextDouble();
		x.r = y;
		System.out.printf("圆的面积是:%.2f\n", x.CalcArea());
		System.out.printf("圆的周长是:%.2f", x.CalcPerimeter());
	}
}
//⚠雷区⚠
package ch3_Homework;
public class Circle {
	public double r;
	public static double pi = 3.14;
    //这样的写法会导致根本用不到创建的对象
	public static double calcCirum(double r) {
		return 2*Circle.pi*r;//此处的r指的是(double r)中传入的参数r
	}
	public static double calcArea(double r) {
		return Circle.pi*r*r;//此处的r指的是(double r)中传入的参数r
	}
}

package ch3_Homework;
public class CircleTest {
	public static void main (String[] args) {
//		Circle c1=new Circle();
//		c1.r=10;
		
		double r=10;
		double ans1=Circle.calcCirum(r);
		double ans2=Circle.calcArea(r);
		System.out.printf("该圆的周长是:%.2f\n", ans1);
		System.out.printf("该圆的面积是:%.2f\n", ans2);
	}
}

思考:如果需要计算若干个圆的周长和,程序该怎么写?

package ch3_Homework;
public class Circle {
	public double r;
	public static double pi = 3.14;
	public double calcCircum() {
		return 2*Circle.pi*r;
	}
	public double calcArea() {
		return Circle.pi*r*r;
	}
}

package ch3_Homework;
import java.util.Random;
import java.util.Scanner;
public class CircleTest {
	public static void main (String[] args) {
		System.out.println("请确定待求圆的个数n:");
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt();
		sc.close();
		Circle[] circles=new Circle[n];//此时数组中的每个空间存储的内容为null
		Random rnd=new Random();//Random类型生成0~1之间的随机值
		int i;
		double sumCircum=0, sumArea=0;
		for (i=0; i<circles.length; ++i) {
			circles[i]=new Circle();//赋值
			circles[i].r=rnd.nextDouble()*10;
			sumCircum+=circles[i].calcCircum();
			sumArea+=circles[i].calcArea();
		}
		System.out.printf("上述n个圆的周长和为:%.2f\n上述n个圆的面积和为:%.2f", sumCircum, sumArea);
	}
}

将若干个圆放到对象数组中

对象数组在内存中的存储方式
在这里插入图片描述

3.构造方法(Constructor/ctor)

  • 构造方法的作用: 完成类对象的初始化工作

    (对象的创建是由new来完成的)

  • 构造方法的基本语句:

    修饰符 类名([参数表]) {
        [语句]
    }
    
  • 特点:

    • 构造方法的名称必须与类名相同
    • 构造方法没有返回值并且不可被继承
    • 只能通过new运算符调用
  • 注意点★:

    • 每个类至少必须有一个构造方法.可以有多个,也就是说可以重载.
    • 如果没有自定义构造方法,编译器将自动生成一个无参构造方法
    • 如果自定义了构造方法,编译器将不再生成无参构造方法

未构造方法时

package classtext;
public class Birthday {
	int year;
	int month;
	int day;
	
	public void setBirthday (int y, int m, int d) {
		year = y;
		month = m;
		day = d;
	}
    
	public void print() {
		System.out.printf("%04d/%02d/%02d\n", year, month, day);
	}
}
package classtext;
public class Demo {
	public static void main (String[] args) {
		Birthday d1 = new Birthday();
		d1.print();
	}
}
/*
 * 运行结果:0000/00/00
 */

没有构造方法则初始值都是0

构造方法之后

package classtext;
public class Birthday {
	int year;
	int month;
	int day;
	
	public void setBirthday (int y, int m, int d) {
		year = y;
		month = m;
		day = d;
	}
	
	public Birthday() {
		year = 1900;
		month = 1;
		day = 1;
	}
    
	public void print() {
		System.out.printf("%04d/%02d/%02d\n", year, month, day);
	}
}
package classtext;
public class Demo {
	public static void main (String[] args) {
		Birthday d1 = new Birthday();
		d1.print();
	}
}
/*
 * 运行结果:1900/01/01
 */
  • 构造方法之后就有了初始值

  • 解读Birthday d1 = new Birthday();

    ①d1分配内存空间

    ②new一个Birthday的存储空间

    ③Birthday()调用构造方法

    ④调用完构造方法之后将地址附给d1

    (③④两步哪一个在前哪一个在后其实是不确定的)

也可以直接在构造方法时传入参数

package classtext;
public class Birthday {
	int year;
	int month;
	int day;
	
//	public void setBirthday (int y, int m, int d) {
//		year = y;
//		month = m;
//		day = d;
//	}
	
	public Birthday() {
		year = 1900;
		month = 1;
		day = 1;
	}	
    
	public Birthday (int y, int m, int d) {
		year = y;
		month = m;
		day = d;
	}
	
	public void print() {
		System.out.printf("%04d/%02d/%02d\n", year, month, day);
	}
}

一般在构造函数的时候就把参数传入了,而不使用setBirthday这样的方法

4.this关键字

(1)this代表当前对象的引用
  • 使用this访问成员变量
  • 用来区分成员变量和局部变量重名
package ch3_Homework;
public class Circle {
	public double r;
	public static double pi = 3.14;
	//构造方法
//	public Circle(double r) {
//		r=r;
//		此处的两个r都是参数r,将自己的r自己的值赋给自己,所以会出现警告
//		而我们希望它可以将参数r的值传递给实例成员r则可以将第一个r改为this.r
//	}
	public Circle(double r) {
		this.r=r;
	}
    public double calcCircum() {
		return 2*Circle.pi*this.r;
	}
	public double calcArea() {
		return Circle.pi*this.r*this.r;
	}
}

package ch3_Homework;
import java.util.Random;
import java.util.Scanner;
public class CircleTest {
	public static void main (String[] args) {
		System.out.println("请确定待求圆的个数n:");
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt();
		sc.close();
        
		Circle[] circles=new Circle[n];
        //此时数组中的每个空间存储的内容为null
        
		Random rnd=new Random();
        //Random类型生成0~1之间的随机值
        
		int i;
		double sumCircum=0, sumArea=0;
		for (i=0; i<circles.length; ++i) {
			circles[i]=new Circle(rnd.nextDouble()*10);
            //构造方法没有谁对其调用,而是直接new出来的
            //此处构造方法所调用的this指的就是刚刚new出来的对象
            
			sumCircum+=circles[i].calcCircum();
			sumArea+=circles[i].calcArea();
            //此处通过对象circles[i].来调用的calcArea()
            //则此处calcArea()中调用的this指的就是创建的对象circles[i]
		}
		System.out.printf("上述n个圆的周长和为:%.2f\n上述n个圆的面积和为:%.2f", sumCircum, sumArea);
	}
}
(2)调用构造方法

注意:只能是构造方法中的第一条语句(即在使用this调用之前不可有任何的其他语句)

//Test1:
package classtext;
public class Birthday {
	public int year;
	public int month;
	public int day;
	
	public Birthday() {
		year = 1900;
		month = 1;
		day = 1;
		System.out.println("1");
	}
	
	public Birthday(int y, int m, int d) {
		year = y;
		month = m;
		day = d;
		System.out.println("2");
	}
	
	public Birthday(int year) {
		this.year=year;
		this.month=1;
		this.day=1;
		System.out.println("3");
	}
	public void print() {
		System.out.printf("%04d/%02d/%02d\n", year, month, day);
	}
}

package classtext;
public class Demo {
	public static void main (String[] args) {
		Birthday d1 = new Birthday(2002);
		d1.print();
	}
}
/* 
 * 运行结果:
 * 3
 * 2002/01/01
 */
//Test2:
package classtext;
public class Birthday {
	public int year;
	public int month;
	public int day;
	
	public Birthday() {
		year = 1900;
		month = 1;
		day = 1;
		System.out.println("1");
	}
	
	public Birthday(int y, int m, int d) {
		year = y;
		month = m;
		day = d;
		System.out.println("2");
	}
	
	public Birthday(int year) {
        //Birthday(2003, 10, 15);
        //这样的调用不被允许
		this(2003, 10, 15);
		System.out.println("3");
	}
    
	public void print() {
		System.out.printf("%04d/%02d/%02d\n", year, month, day);
	}
}

package classtext;
public class Demo {
	public static void main (String[] args) {
		Birthday d1 = new Birthday(2002);
		d1.print();
	}
}
/*
 * 运行结果:
 * 2
 * 3
 * 2003/10/15
 */

作业2

定义一个类, 用于描述直角坐标系中的点。该类还需要提供一个方法,可以计算到另一个点的距离。

package ch3_Homework;
public class Point {
	public double x, y;
	public Point(double x, double y) {
		this.x=x;
		this.y=y;
	}
	public double distance(Point another) {
		return Math.sqrt((this.x-another.x)*(this.x-another.x)+(this.y-another.y)*(this.y-another.y));
	}
}

package ch3_Homework;
import java.util.Scanner;
public class Point_test {
	public static void main(String[] args) {
		System.out.println("请输入两个点的坐标:");
		Scanner sc=new Scanner(System.in);
		int x1, y1, x2, y2;
		x1=sc.nextInt();
		y1=sc.nextInt();
		x2=sc.nextInt();
		y2=sc.nextInt();
		sc.close();
		Point P1=new Point(x1, y1);
		Point P2=new Point(x2, y2);
		System.out.printf("P1(%.2f, %.2f)\n", P1.x, P1.y);
		System.out.printf("P2(%.2f, %.2f)\n", P2.x, P2.y);
		System.out.printf("P1, P2之间的距离是%.2f", P1.distance(P2));
	}
}

为什么不使用静态方法来求两点之间的距离呢?

①静态成员方法是通过类进行访问的,而当我们让类去承载这个过程的时候,表明对象无法完成该过程

②如果一个类里面的成员方法全为静态,则该类本质上就退化成了一个结构体,也无法直接访问该类里面的实例成员

5.封装性(Encapsulation)

面向对象的程序设计有三种特性:封装性、继承性、多态性

只有同时具有这三种特性才可以称作是面向对象的程序设计

否则只能称作是基于对象的程序设计

是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。

  • 封装好处
    • 隐藏实现细节,提供公共的访问方式
    • 提高了代码的复用性
    • 提高安全性
  • 封装原则
    • 将不需要对外提供的内容都隐藏起来
    • 把属性隐藏,提供公共方法对其访问
  • 代码实现
    • 修改属性的可见性来限制对属性的访问
    • 为每个属性创建对应的get/set方法
    • 在方法中,加入对属性的存储限制
封装的例子

定义一个Student类,用于描述一个学生信息,包括学号、姓名、年龄、性别。

  1. 确保每个学生对象都有一个学号和姓名
  2. 使用封装方法,确保学生的年龄不能设置为负数
  3. 一旦设置性别后,不能更改
package classtext;
public class Student {
	public int sno;
	public String name;
	private String sex;
	private int age;
    
	//利用构造方法,保证每个学生类都有学号和姓名
	public Student(int sno, String name) {
		this.sno=sno;
		this.name=name;
	}
    
    //读取年龄
    public int getAge() {
		return this.age;
	}
    //快捷方式输出一个读取函数:Alt+Shift+S(Source) --> Generate Getters and Setters
    
    //先将age设为private,再利用方法成员对更改条件进行限制
	public void setAge(int age) {
		if (age>=0) this.age=age;
	}
    
    //读取性别
    public String getSex() {
		return this.sex;
	}
    
    //先将sex设为private,再利用方法成员对更改条件进行限制
	public void setSex(String sex) {
		if (this.sex==null) this.sex=sex;
	}
    
	public void print() {
		System.out.printf("学号:%d\n姓名:%s\n性别:%s\n年龄:%d\n", sno, name, sex, age);
	}
}

package classtext;
public class Student_test {
	public static void main(String[] args) {
		Student s1=new Student(22212170, "GRANNY");
		s1.setAge(19);
		s1.setSex("女");
		s1.print();
		System.out.println("");
		Student s2=new Student(22212189, "TONNY");
		s2.setAge(-19);//此处的年龄不允许更改为负数,所以还保持初始值0
		s2.setSex("男");
		s2.setSex("女");//此时s2.sex="男",所以操作已经不起作用了
		s2.print();
        System.out.println("");
		System.out.println(s1.getAge());
		System.out.println(s1.getSex());
		System.out.println(s2.getAge());
		System.out.println(s2.getSex());
	}
}
/*
* 运行结果:
* 学号:22212170
* 姓名:GRANNY
* 性别:女
* 年龄:19
* 
* 学号:22212189
* 姓名:TONNY
* 性别:男
* 年龄:0
* 
* 19
* 女
* 0
* 男
*/

作业3

(1)复数加法

实现一个复数类(类名Complex),能使该类进行复数的创建、显示、两个复数相加等操作。(在数学上,通常将负数表示为a+bi的形式,其中a为实部,b为虚部。两个复数相加,实部与实部相加,虚部于虚部相加,即:(a1+b1i)+(a2+b2i)=(a1+a2)+(b1+b2)i)。

  1. 定义Complex类,类中需包含两个私有数据成员real、image,分别代表实部和虚部,默认值均为0。
  2. Complex类中需实现一个公有构造方法,根据参数值初始化数据成员real和image。
  3. 定义print()方法,该方法能将复数表示为"a+bi"的形式。
  4. 定义一个公有成员方法,实现两个复数的相加,方法名为add,方法返回一个相加后的结果。
  5. 在ComplexTestor类的main方法中,创建两个复数r1和r2,调用Complex类的add方法,相加后的结果为复数r3,通过调用复数的print方法,输出加法运算式及运算结果。
package ch3_Homework;
public class Complex {
	private int real, image;
	
	public Complex(int real, int image) {
		this.real=real;
		this.image=image;
	}
	
	public void print(Complex r) {
		String s=String.valueOf(r.real)+"+"+String.valueOf(r.image)+"i";
		System.out.println(s);
	}
	
	public Complex add(Complex another) {
		Complex ans=new Complex(0, 0);
		ans.real=this.real+another.real;
		ans.image=this.image+another.image;
		return ans;
	}
}

package ch3_Homework;
import java.util.Scanner;
public class Complex_test {
	public static void main (String[] args) {
		System.out.println("请输入两个待求复数的实部和虚部:");
		Scanner sc=new Scanner(System.in);
		int real1, image1, real2, image2;
		real1=sc.nextInt();
		image1=sc.nextInt();
		real2=sc.nextInt();
		image2=sc.nextInt();
		Complex r1=new Complex(real1, image1);
		Complex r2=new Complex(real2, image2);
		sc.close();
		r1.print(r1.add(r2));
	}
}
//修改:
package ch3_Homework;
public class Complex {
	private int real;
	private int image;
	
	public Complex(int real, int image) {
		this.real=real;
		this.image=image;
	}
	
	public void print() {
		System.out.printf("%d+%di\n", real, image);
	}
	
	public String toString() {
		return String.format("%d+%di", real, image);
	}
	
	//复数进行加法运算的三种方法:
	//方法1:
	public Complex addx(Complex another) {
		Complex ans=new Complex(0, 0);
		ans.real=this.real+another.real;
		ans.image=this.image+another.image;
		return ans;
	}
	
	//方法2:
	public Complex addy(Complex another) {
		int r=this.real+another.real;
		int i=this.image+another.image;
		Complex ans=new Complex(r, i);
		return ans;
	}
	
	//方法3:是对方法2的优化处理将函数简写成一个简单的形式
	public Complex addz(Complex another) {
		return new Complex(this.real+another.real, this.image+another.image);
	}
}

package ch3_Homework;
import java.util.Scanner;
public class Complex_test {
	public static void main (String[] args) {
		System.out.println("请输入两个待求复数的实部和虚部:");
		Scanner sc=new Scanner(System.in);
		int real1, image1, real2, image2;
		real1=sc.nextInt();
		image1=sc.nextInt();
		real2=sc.nextInt();
		image2=sc.nextInt();
		Complex r1=new Complex(real1, image1);
		Complex r2=new Complex(real2, image2);
		sc.close();
		Complex r3;
		r3=r1.addx(r2);
		r3=r1.addy(r2);
		r3=r1.addz(r2);
		System.out.printf("加法运算:\n(%d+%di)+(%d+%di)=", real1, image1, real2, image2);
		r3.print();
		System.out.printf("(%s)+(%s)=%s", r1, r2, r3);
	}
}

在Java的程序中通常将类名设置为首字母大写,方法名通常设置为首字母小写(构造方法除外)

(2)学生通讯录管理程序

(此代码留着,后面还需要升级)

  1. 编写一个学生类,用于记录学生的姓名(必填)、年龄、性别、手机号码(必填)、宿舍地址等信息。
  2. 保存年龄时校验年龄范围必须介于10~30之间。
  3. 手机号码必须有效(11位,13、15、17、18开头)。
  4. 使用数组或List保存学生类对象。
  5. 支持信息的添加、修改、删除。
  6. 支持根据姓名或手机号码模糊查找。
  7. 添加和修改学生的时候,姓名、手机号码必填;手机号码不能重复;姓名可以重复;
//初稿
package ch3_Homework;
public class Directory {
	public String name;
	public int age;
	public String sex;
	private long num;
	public String address;
	
	public Directory(String name, long num) {
		this.name=name;
		if (num/100000000000l==0 && (num/1000000000l==13 || num/1000000000l==15 || 
            num/1000000000l==17 || num/1000000000l==18) && num/10000000000l!=0)
			this.num = num;
	}
	
	public void print() {
		System.out.printf("姓名:%s\n", this.name);
		System.out.printf("性别:%s\n", this.sex);
		System.out.printf("年龄:%d\n", this.age);
		System.out.printf("手机号码:%d\n", this.num);
		System.out.printf("宿舍地址:%s\n", this.address);
	}
    
    public void setAge(int age) {
		if (age>=10 && age<=30)
			this.age=age;
	}
}

package ch3_Homework;
import java.util.Scanner;
public class Directory_test {
	public static void main(String[] args) {
		System.out.println("请确定将录入的学生的人数n:");
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt(), i;
		Directory[] students=new Directory[n];
		sc.close();
		for (i=0; i<n; ++i) {
			students[i]=new Directory("GRANNY", 18852184673l);
			students[i].setAge(19);
			students[i].sex="女";
			students[i].address="学二楼412室";
			students[i].print();
			System.out.println("");
		}
	}
}

6.继承性(Inheritance)

基本概念

继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

  • 父类/子类:在继承关系中,被继承的类为父类,由继承得到的类为子类。(超类,基类/派生类)
  • 子类可以继承父类中的属性和方法,也可以在子类中添加新的数据和方法,也可以重写原有的方法。
  • 在Java中,所有的类都是直接或间接地继承类Object,也就是说在Java的类树中,Object是这棵树的根。

语法

修饰符 class 子类名 extends 父类 {//extends为关键字 
    成员声明
}
  • 当类只从一个类继承时,称为单继承,Java只支持单继承。
  • Java中类的继承结构为树状。
package classtext;
public class Student {
	public int sno;
	public String name;
	private String sex;
	private int age;
    
	public void setSno(int sno) {
		this.sno = sno;
	}

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

	public void setAge(int age) {
		if (age>=0)
            this.age = age;
	}
    
	public void setSex(String sex) {
		if (this.sex==null)
            this.sex = sex;
	}
    
	public void print() {
		System.out.printf("学号:%d\n姓名:%s\n性别:%s\n年龄:%d\n", sno, name, sex, age);
	}
}

package classtext;
public class CollegeStudent extends Student {
	public String major;
    
    public void setMajor(String major) {
		this.major = major;
	}
}

package classtext;
public class Student_test {
	public static void main(String[] args) {
		CollegeStudent s=new CollegeStudent();
		s.setSno(22212170);
		s.setName("GRANNY");
		s.setSex("女");
		s.setAge(19);
		s.setMajor("数据科学与大数据技术");
		s.print();
		System.out.printf("专业:%s", s.major);
	}
}
/* 运行结果:
 * 学号:22212170
 * 姓名:GRANNY
 * 性别:女
 * 年龄:19
 * 专业:数据科学与大数据技术
 */
  • CollegeStudent类只能访问Student中修饰词为public的数据成员和方法成员
  • private类型成员也被继承,只是无法直接访问(可以通过public方法成员访问),即子类继承了父类的所有成员
  • 继承和访问不一样
  • CollegeStudent可以通过getter和setter来访问父类中的private成员
package classtext;
public class Student {
	public int sno;
	public String name;
	private String sex;
	private int age;
	
	public Student() {
		System.out.println("B");
	}
}

package classtext;
public class CollegeStudent extends Student {
	public String major;
	public CollegeStudent() {
		System.out.println("A");
	}
}

package classtext;
public class Student_test {
	public static void main(String[] args) {
		CollegeStudent s=new CollegeStudent();
	}
}
/* 运行结果:
 * B
 * A
 */
  • 解读CollegeStudent s=new CollegeStudent();

    ①s分配内存空间

    ②new一个CollegeStudent的存储空间

    ③Student()调用构造方法(先调用其父类的构造方法)

    ④CollegeStudent()调用构造方法

    ⑤调用完构造方法之后将地址附给s

  • 在调用子类的构造方法CollegeStudent(……)的时候默认隐藏了一个super();表示调用了父类的构造方法

    即使没有写出来程序也会自动运行这个步骤

  • 如果写出来,则构造方法super();必须(只能)是第一个语句,

    由此才保证了在调用子类的构造方法时,首先调用父类的构造方法

说明:

  • 大学生是学生的一种类型,所以把CollegeStudent定义为Student的子类是合乎它们之间的逻辑关系的
  • 通过继承可以避免程序的冗余。通过运行结果可以看到,子类CollegeStudent从父类Student中继承了成员变量studentNo、name和方法showInfo()
  • 子类可以使用父类中非private的方法和属性——不必重新定义
  • 子类中可以增加新的属性和方法
  • 继承的目的:程序代码重用,减少冗余
(1)子类构造方法

要点:

  • 父类构造方法的默认调用
  • 显式调用父类构造方法super
  • 子类的构造过程(5步)
  • 父子类构造方法的调用顺序
  • this()和super()谁写第一行?
    • 永远不可能出现同时调用this()和super()的情况
(2)构造方法不能继承
  • 子类不能继承父类的构造方法

  • 有两种方法获得构造方法

    • 使用默认构造方法
    • 编写一个或多个构造方法

7.super关键字

  • super()调用父类的构造方法
  • super代表父类的引用
    • 使用super明确访问父类的成员变量/方法
package classtext;
public class Student {
	public int sno;
	public String name;
	private String sex;
	private int age;
	
	public Student() {
		System.out.println("B");
	}
	
	public Student(int sno, String name) {
		this.sno=sno;
		this.name=name;
		System.out.println("C");
	}
}

package classtext;
public class CollegeStudent extends Student {
	public String major;
	public CollegeStudent() {
		super();
		System.out.println("A");
	}
	
	public CollegeStudent(int sno, String name) {
		System.out.println("D");
	}
}

package classtext;
public class Student_test {
	public static void main(String[] args) {
		CollegeStudent s=new CollegeStudent(1, "擎天柱的美甲师");
	}
}

/* 运行结果:
 * B
 * D
 */

package classtext;
public class CollegeStudent extends Student
{
	public String major;
	public CollegeStudent()
	{
		super();
		System.out.println("A");
	}
	
	public CollegeStudent(int sno, String name)
	{
        super();
		System.out.println("D");
	}
}
/* 运行结果:
 * B
 * D
 */

package classtext;
public class CollegeStudent extends Student
{
	public String major;
	public CollegeStudent()
	{
		super();
		System.out.println("A");
	}
	
	public CollegeStudent(int sno, String name)
	{
		super(sno, name);
		System.out.println("D");
	}
}
/* 运行结果:
 * C
 * D
 */

package classtext;
public class CollegeStudent extends Student
{
	public String major;
	public CollegeStudent()
	{
		super();
		System.out.println("A");
	}
	
	public CollegeStudent(int sno, String name)
	{
		this();
		System.out.println("D");
	}
}
/* 运行结果:
 * B
 * A
 * D
 */
package classtext;
public class Student
{
	public int sno;
	public String name;
	private String sex;
	private int age;
}

package classtext;
public class CollegeStudent extends Student
{
	public String major;
	public void fun()
	{
		super.name="XXX";
		System.out.printf("%s", super.name);
	}
}
  • this()的调用也是需要放在构造方法的第一句的,所以super(……)永远不会出现需要同时调用的情况
    (在调用this()的时候也会间接调用一个super(),而我们不需要同时调用两次super(……), 所以在子类构造方法中this()和super()不会同框)

  • super的第二种用法:在子类中通过super来调用父类中的成员变量(super也可以省略)

8.方法覆写(override)

覆写:

  • 在子类中定义了与父类中同名的方法

  • 且方法的

    • 参数(个数、类型、排雷顺序)
    • 返回类型

    完全一致

    package classtext;
    public class Student {
    	public int sno;
    	public String name;
    	private String sex;
    	private int age;
        
    	public void setSno(int sno) {
    		this.sno = sno;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public void setAge(int age) {
    		if (age>=0)
                this.age = age;
    	}
        
    	public void setSex(String sex) {
    		if (this.sex==null)
                this.sex = sex;
    	}
        
    	public void print() {
    		System.out.printf("学号:%d\n姓名:%s\n性别:%s\n年龄:%d\n", sno, name, sex, age);
    	}
    }
    
    package classtext;
    public class CollegeStudent extends Student {
    	public String major;
        
        public void setMajor(String major) {
    		this.major = major;
    	}
    }
    
    package classtext;
    public class Student_test {
    	public static void main(String[] args) {
    		CollegeStudent s=new CollegeStudent();
    		s.setSno(22212170);
    		s.setName("GRANNY");
    		s.setSex("女");
    		s.setAge(19);
    		s.setMajor("数据科学与大数据技术");
    		s.print();
    		System.out.printf("专业:%s", s.major);
    	}
    }
    /* 运行结果:
     * 学号:22212170
     * 姓名:GRANNY
     * 性别:女
     * 年龄:19
     * 专业:数据科学与大数据技术
     *
     * 在这个代码中s.print();无法将major输出,
     * 所以单独在最后一句输出专业
     */
    
    //也可以使用如下方法:覆写(override)
    package classtext;
    public class Student {
    	public int sno;
    	public String name;
    	private String sex;
    	private int age;
        
    	public void setSno(int sno) {
    		this.sno = sno;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public void setAge(int age) {
    		if (age>=0)
                this.age = age;
    	}
        
    	public void setSex(String sex) {
    		if (this.sex==null)
                this.sex = sex;
    	}
        
    	public int getSno() {
    		return sno;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public String getSex() {
    		return sex;
    	}
    
    	public int getAge() {
    		return age;
    	}
    
    	public void print() {
    		System.out.printf("学号:%d\n姓名:%s\n性别:%s\n年龄:%d\n", sno, name, sex, age);
    	}
    }
    
    package classtext;
    public class CollegeStudent extends Student {
    	public String major;
    
    	public void setMajor(String major) {
    		this.major = major;
    	}
    	
    	public void print() {
    		System.out.printf("学号:%d\n姓名:%s\n性别:%s\n年龄:%d\n专业:%s\n", 
                              sno, name, super.getSex(), super.getAge(), major);//此处的super.可省略
    	}
    }
    
    package classtext;
    public class Student_test {
    	public static void main(String[] args) {
    		CollegeStudent s=new CollegeStudent();
    		s.setSno(22212170);
    		s.setName("GRANNY");
    		s.setSex("女");
    		s.setAge(19);
    		s.setMajor("数据科学与大数据技术");
    		s.print();
    	}
    }
    /*
     * 注意:覆写方法要和父类中的返回值类型、方法名称、参数都相同才构成覆写
     * 进行覆写之后,父类中的print()仍然存在
     */
    
    //所以也可以将子类中的覆写方法写成:
    public void print() {
        super.print();
        System.out.printf ("专业:%s\n", major);
    }
    /* 此处的super.不可省略,若省略,则形成死循环的递归调用
     * 这里的super语句不需要一定放到第一句
     * 上下两句交换位置只会改变输出内容的顺序
     * (Tips)此处有一个快捷键小技巧:
     * 选中相应代码行,Alt+↑/↓移动选中代码行的位置
     */
    
    问题探究:override和overload的区别

    分析该例是覆写还是重载?(重载:方法名相同类名不同)

    class Base {
        public void amethod(int i) {
            System.out.println("i="+i);
        }
    }
    class Scope extends Base() {
        public void amethod(int i, float f) {
            System.out.println("i="+i);
            System.out.println("f="+f);
        }
    }
    class test1 {
        public static void main (String xxx[]) {
            Scope s1;
            s1=new Scope();
            s1.amethod(10);
            s1.amethod(10, 2.3f);
        }
    }
    

答:此处的参数不同所以只能构成重载而不是覆写

作业4

定义四边形类(Quadrangle),拥有周长计算和显示图形的信息两个方法。

定义长方形类(Rectangle),继承自四边形,覆写周长计算和图形信息显示的方法。

定义正方形类(Square) ,继承自四边形或长方形,覆写周长计算和图形信息显示的方法。

编写测试程序,完成上述类的各个方法的测试。

写程序时仔细琢磨:

(1)各个类的数据成员如何处理?

(2)构造方法如何编写更好?

package ch3_Homework;
public class Quadrangle
{
	public double a;
	public double b;
	public double c;
	public double d;
	
    //由于后面的子类中没有使用其他的父类的构造方法,
    //所以在子类使用自己的构造方法的时候依然需要使用调用这个默认的父类构造方法
    //所以这个构造方法及时不起任何作用也不能删除
	public Quadrangle(){
	}
    
    /*若没有这样一个空的构造方法,则在刚刚创建其继承子类的时候就会报错:
    因为在调用其父类的构造方法的时候
    */
	public Quadrangle(double a, double b, double c, double d){
		if (a+b+c>d && a+b+d>c && a+c+d>b && c+b+d>a){
			this.a=a;
			this.b=b;
			this.c=c;
			this.d=d;
		}
	}
	
	public double perimeter(){
		return this.a+this.b+this.c+this.d;
	}
	
	public void print(){
		System.out.printf("四边形的四条边长分别为:\n%.2f  %.2f  %.2f  %.2f\n", this.a, this.b, this.c, this.d);
		System.out.printf("四边形的周长为:\n%.2f\n", this.perimeter());
	}
}

public class Rectangle extends Quadrangle{
	public Rectangle(double a, double b){
		super.a=a;
		super.b=b;
	}
    
	//不需要使用这个构造方法可以将其去掉
	public Rectangle(){
	}
	
    //长方形和正方形的周长的计算依然可以继承四边形的计算,
    //所以在这里就可以不用再将计算周长的方法进行覆写了
	public double perimeter(){
		return 2*(super.a+super.b);
	}
	
	public void print(){
		System.out.printf("以四边形的前两条边为长和宽的长方形的长和宽分别为:\n%.2f  %.2f\n", super.a, super.b);
		System.out.printf("长方形的周长为:\n%.2f\n", perimeter());
	}
}

public class Square extends Rectangle{
	public Square(double a){
		super.a=a;
	}
	
	public double perimeter(){
		return 4*super.a;
	}
	
	public void print(){
		System.out.printf("以长方形的长为边长的正方形的边长为:\n%.2f\n", super.a);
		System.out.printf("正方形的周长为:\n%.2f\n", perimeter());
	}
}

package ch3_Homework;
import java.util.Scanner;
public class Quadrangle_test{
	public static void main(String[] args){
		Scanner sc=new Scanner(System.in);
		
		System.out.println("请输入四边形的四条边长:");
		double a, b, c, d;
		a=sc.nextDouble();
		b=sc.nextDouble();
		c=sc.nextDouble();
		d=sc.nextDouble();
		sc.close();
		Quadrangle q1=new Quadrangle(a, b, c, d);
		q1.print();
		
		Rectangle q2=new Rectangle(a, b);
		q2.print();
		
		Square q3=new Square(a);
		q3.print();
	}
}
package ch3_Homework;
public class Quadrangle {
	public int a;
	public int b;
	public int c;
	public int d;
	
	public Quadrangle(int a, int b, int c, int d) {
		if (a+b+c>d && a+b+d>c && a+c+d>b && c+b+d>a) {
			this.a=a;
			this.b=b;
			this.c=c;
			this.d=d;
		}
	}
	
	public int perimeter() {
		return a+b+c+d;
	}
	
	public void print() {
		System.out.printf("四边形的四条边长分别为:\n%d  %d  %d  %d\n", a, b, c, d);
		System.out.printf("四边形的周长为:\n%d\n\n", this.perimeter());
	}
}

package ch3_Homework;
public class Rectangle extends Quadrangle {
	public Rectangle(int hight, int width) {
		super(hight, hight, width, width);
	}
	
	public void print() {
		System.out.printf("长方形的长和宽分别为:\n%d  %d\n", a, c);
		System.out.printf("长方形的周长为:\n%d\n\n", perimeter());
	}
}

//正方形的子类可以继承在四边形下,也可以继承在长方形下
//继承在长方形下的正方形子类:
package ch3_Homework;
//定义正方形类(Square) ,继承自四边形或长方形,覆写周长计算和图形信息显示的方法。
public class Squarex extends Rectangle {
	public Squarex(int a) {
		super(a, a);
        //继承在长方形下,所以其super()调用的就是Rectangle的构造方法
	}
	
	public void print() {
		System.out.printf("正方形的边长为:\n%d\n", a);
		System.out.printf("正方形的周长为:\n%d\n\n", perimeter());
	}
}

//继承在四边形下的正方形子类:
package ch3_Homework;

public class Squarey extends Quadrangle {
	public Squarey(int a) {
		super(a, a, a, a);
        //继承在四边形下,所以其super()调用的就是Quadrangle的构造方法
	}
	
	public void print() {
		System.out.printf("正方形的边长为:\n%d\n", a);
		System.out.printf("正方形的周长为:\n%d\n\n", perimeter());
	}
}


package ch3_Homework;
public class Quadrangle_test {
	public static void main(String[] args) {
		Quadrangle q1=new Quadrangle(1, 2, 3, 4);
		q1.print();
		Rectangle q2=new Rectangle(6, 7);
		q2.print();
		Squarex q3=new Squarex(9);
		q3.print();
	}
}

9.多态(polymorphism)

多态:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同采用多种不同的行为方式。(发送消息就是函数调用)

实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

​ 现实中,关于多态的例子不胜枚举。比方说按下F1键这个动作,如果当前在Word下弹出的就是Word帮助;在Windows下弹出的就是Windows帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。

(1)引用类型和对象类型

分析下面两行代码:

01:CollegeStudent S=null;
02:S=new CollegeStudent();
  • S是对象的引用,其数据类型称为引用类型

  • S所指的对象类型是CollegeStudent

    思考:引用类型和对象类型必须一样吗?

(2)向上转型(upcast)

子类型的对象地址赋给父类型的引用变量

01:Student s;
02:s=new CollegeStudent();
  • 物理层面:s可以接收任何new返回的地址

  • 逻辑层面:子类的对象一定可以看作是特殊的父类对象

    例如:汽车对象是特殊的交通工具对象;桌子对象是特殊的家具。

  • 一个引用变量的引用类型和对象类型未必完全相同

Student s1, s2;
s1=new Student();
s2=new CollegeStudent();
s1.showInfo();
s2.showInfo();

相同类型Student的引用s1和s2,调用相同的方法showInfo(),呈现不同的形态,称为多态。

package classtext;
public class Quadrangle {
	public int a;
	public int b;
	public int c;
	public int d;
	
	public Quadrangle(int a, int b, int c, int d) {
		if (a+b+c>d && a+b+d>c && a+c+d>b && c+b+d>a) {
			this.a=a;
			this.b=b;
			this.c=c;
			this.d=d;
		}
	}
	
	public void print() {
		System.out.printf("四边形的四条边长分别为:\n%d  %d  %d  %d\n", a, b, c, d);
	}
}

package classtext;

public class Rectangle extends Quadrangle {
	public Rectangle(int hight, int width) {
		super(hight, hight, width, width);
	}
	
	public void print() {
		System.out.printf("长方形的长和宽分别为:\n%d  %d\n", a, c);
	}
}

package classtext;

public class Square extends Rectangle {
	public Square(int a) {
		super(a, a);
	}
	
	public void print() {
		System.out.printf("正方形的边长为:\n%d\n", a);
	}
}

package classtext;
public class Quadrangle_test {
	public static void main(String[] args) {
		Quadrangle q1=new Quadrangle(3, 4, 5, 6);
		q1.print();
		Rectangle r1=new Rectangle(3, 4);
		r1.print();
		Square s1=new Square(3);
		s1.print();
		System.out.println("");
		
		Quadrangle q2=new Quadrangle(3, 4, 5, 6);
		q2.print();
		Quadrangle r2=new Rectangle(3, 4);
		//在这里,把子类的类型给父类的引用,这就是向上转型
		r2.print();
		Quadrangle s2=new Square(3);
		s2.print();
	}
}
  • 引用类型和对象类型可以不同

  • 在上述例子中,q1和q2、r1和r2、s1和s2分别调用同一个方法,但是产生了多种形态的结果,这就称之为多态

  • 为什么会出现多种形态?
    因为他们指向的真实的对象类型不同,指向的真实的对象类型是什么它所调用的方法就来自哪里
    即,调用的方法是由真实的对象类型确定,而不是引用类型来确定

  • 产生多态的条件是什么?
    ①存在继承,有了继承关系才可以将Rectangle类型的对象赋给Quadrangle类型
    ②存在覆写,没有覆写的时候,不同变量调用的都是同一个构造方法,无法形成多态的结果
    ③存在向上转型, 将子类的对象赋给父类的引用

(3)实例—王者荣耀之人物出场

每个人物出场时,都会说一些个性台词,比如:

  • 鲁班七号:正在思考,如何攻克地心引力

  • 后羿:发光的,一个就够了

  • 亚瑟:为王者的信念,连睡觉也穿着铠甲

    ……

请使用类和继承,模拟实现此效果。要求:

  1. 在游戏一开始,每个英雄均说出自己的一句台词
  2. 如果要求使用循环集中控制,程序如何实现
  3. 如果现在增加一个英雄,程序需要变动哪些地方
package classtext;

public class Yase extends Hero {
	public void speak() {
		System.out.println("为王者的信念,连睡觉也穿着铠甲");
	}
}

public class Houyi extends Hero {
	public void speak() {
		System.out.println("发光的,一个就够了");
	}
}

public class Luban extends Hero {
	public void speak() {
		System.out.println("正在思考,如何攻克地吸引力");
	}
}

public class Libai extends Hero {
	public void speak() {
		System.out.println("今朝有酒今朝醉");
	}
}

public class Game {
	public static void main(String[] args) {
		Luban luban1=new Luban();
		Houyi houyi1=new Houyi();
		Yase yase1=new Yase();
		Luban luban2=new Luban();
		Houyi houyi2=new Houyi();
		Yase yase2=new Yase();
		
		//定义了几个英雄就要调用几次speak()(很麻烦)
		luban1.speak();
		houyi1.speak();
		yase1.speak();
		luban2.speak();
		houyi2.speak();
		yase2.speak();	
		
		System.out.println("");
	
		//有没有社么方法可以批量调用呢?
		//可以将不同的英雄放到同一个数组中,但是不同英雄都属于不同的类,不便于确定其数组类型
		//当我们发现不同类之间有共同的方法等的元素的时候,就可以利用面向对象的程序设计中的继承思想
		//所以可以定义一个类作为不同英雄人物的父类
		Hero hero1=new Luban();
		Hero hero2=new Houyi();
		Hero hero3=new Yase();
		Hero hero4=new Luban();
		Hero hero5=new Houyi();
		Hero hero6=new Yase();
		
		hero1.speak();
		hero2.speak();
		hero3.speak();
		hero4.speak();
		hero5.speak();
		hero6.speak();
		
		System.out.println("");
		
		//将其放入对象数组中以实现批量调用
		Hero[] heros_x=new Hero[6];
		heros_x[0]=hero1;
		heros_x[1]=hero2;
		heros_x[2]=hero3;
		heros_x[3]=hero4;
		heros_x[4]=hero5;
		heros_x[5]=hero6;
		
		for (int i=0; i<6; ++i)
			heros_x[i].speak();
		
		System.out.println("");
		
		// 简化写法:
		Hero[] heros_y=new Hero[] {
		    new Luban(),
		    new Houyi(),
		    new Yase(),
		    new Luban(),
		    new Houyi(),
		    new Yase(),
            new Libai()
		}; 
		for (Hero hero:heros_y)
			hero.speak();
	}
}

10.覆写和隐藏

(1)实例方法能够被覆写
class Super{
    String name(){
        return "mother";
    }
}
class Sub extends Super{
    static String name(){
        return "baby";
    }
}
class Test{
    public static void main(String[] args){
        Super S=new Sub();//此处发生向上转型
        System.out.println(s.name());//s.name();调用,是引用对象的类型
    }
}
//运行结果:baby
(2)静态方法不能被覆写
class Super{
    static String name(){
        return "mother";
    }
}
class Sub extends Super{
    static String name(){
        return "baby";
    }
}
class Test{
    public static void main(String[] args){
        Super s=new Sub();
        System.out.println(s.name);//静态方法不推荐这种写法,最好用类名.的方法调用
    }
}
//运行结果:mother

由于name是静态的方法,所以在这里变量s不会注意它真实的对象是谁,只会在意它的本身的类型,即Super

(3)父类静态方法可以被子类隐藏
class Super{
    static String name(){
        return "mother";
    }
}
class Sub extends Super{
    static String name(){
        return "baby";
    }
}
class Test{
    public static void main(String[] args){
        Sub s=new Sub();//此处没有向上转型,s是子类的对象也是子类的引用
        System.out.println(s.name());//此时父类的name被隐藏
    }
}
//运行结果:baby

实际上由于不允许用对象.的方法调用静态方法,而应该用类名.的方法来调用
所以如果正确使用Sub.name或Super.name的调用就不会出现这样的问题
(使用Sub.name最后的运行结果为baby,使用Super.name最后的运行结果为mother)

总结-方法
  1. 静态方法在编译时根据引用类型确定
  2. 实例方法在运行时根据对象类型确定
(4)变量的隐藏1
class Base{
    int x=1;
    static int y=2;
}
class Subclass extends Base{
    int x=4;
    int y=5;
}
public class Test{
    public static void main(String[] args){
        Subclass s=new Subclass();
        System.out.println(s.x+" "+s.y);
//      System.out.println(Subclass.y);//类名.只能调用静态变量,而在子类Subclass中y为public变量,所以此处报错
    }
}
//运行结果:4 5
(5)变量的隐藏2
class Base{
    int x=1;
    static int y=2;
}
class Subclass extends Base{
    int x=4;
    int y=5;
}
public class Test{
    public static void main(String[] args){
        Base s=new Subclass();
        System.out.println(s.x+" "+s.y);
    }
}
//运行结果:1 2

只有实例方法才有覆写,变量以及静态方法都只有隐藏

总结-变量
  1. 成员变量没有覆写之说(变量没有多态性)
  2. 父类的实例变量可以被子类隐藏
  3. 父类的静态变量可以被子类隐藏

作用5

设计一个形状Shape类和它的两个实现类圆Circle和矩形Rectangle,并编写测试程序。

按如下要求完成程序的编写:

  1. 添加一个类Shape,该类中编写一个方法area,方法无参数,返回值为double类型。

  2. Circle类的编写:

    (1)添加一个类Circle,在该类中定义Shape的实现类Circle;

    (2)定义一个double类型的字段r,代表圆的半径;

    (3)定义一个构造函数,该构造函数接受一个double类型的参数,并保存到字段r中;

    (4)实现area()方法,并编写程序计算圆的面积。π取3.14。

  3. Rectangle类的编写

    (1)添加一个类Rectangle,在该类中定义Shape的实现类Rectangle;

    (2)定义两个double类型的字段width和height,分布代表矩形的宽和高;

    (3)定义一个构造函数,该构造函数接受两个double类型的参数,并分别保存到字段width和height中;

    (4)实现area()方法,并编写程序计算矩形的面积。

  4. 测试类的编写

    main方法中编写测试程序:

    (1)创建一个Circle类对象,半径为2,调用Area方法计算面积,并输出。

    (2)创建一个Rectangle类对象,长3,宽5,调用Area方法计算面积,并输出。

package ch3_Homework;
public class Shape {
	public double area() {
		return 0;
	}
}
public class Circle_S extends Shape {
	public double r;
	public double PI=3.14;
	public Circle_S(double r) {
		this.r=r;
	}
	public double area() {
		return PI*r*r;
	}
}
public class Rectangle_S extends Shape{
	public double width;
	public double height;
	public Rectangle_S(double width, double height) {
		this.width=width;
		this.height=height;
	}
	public double area() {
		return width*height;
	}
}
public class Calculate_shape {
	public static void main(String[] args) {
		Circle_S c=new Circle_S(2);
		System.out.println(c.area());
		Rectangle_S r=new Rectangle_S(3, 5);
		System.out.println(r.area());
	}
}

11.多态的应用场景

(1)把多态用在方法的参数类型上

有一个动物园,里面有若干动物:猫、狗等,动物能自己吃东西

饲养员负责给动物喂食,饲养员喂食后,动物开始吃

用面向对象的思想,分析并模拟实现此场景

问题:如果动物园里的动物增加了很多(100种),饲养员类该怎么处理?

package classtext;
public class Feeder {
	public void feed(Cat c) {
		c.eat();
	}
	public void feed(Dog d) {
		d.eat();
	}
	public void feed(Tiger t) {
		t.eat();
	}
}

public class Cat {
	public void eat() {
		System.out.println("猫吃东西");
	}
}

public class Dog {
	public void eat() {
		System.out.println("狗吃东西");
	}
}

public class Tiger {
	public void eat() {
		System.out.println("老虎吃东西");
	}
}

public class Zoo {
	public static void main(String[] args) {
		Feeder f=new Feeder();
		Cat c1=new Cat();
		Cat c2=new Cat();
		Dog d1=new Dog();
		Dog d2=new Dog();
		Tiger t=new Tiger();
		
		f.feed(c1);
		f.feed(c2);
		f.feed(d1);
		f.feed(d2);
		f.feed(t);
	}
}

这种写法,每增加一种动物,Feeder类就需要增加一个喂新增的动物的方法
如果新增的动物有成百上千种则会很麻烦
基于此,我们进行以下改进:

package classtext;
public class Feeder {
	public void feed(Animal a) {
		a.eat();
	}
}

public class Animal {
	public void eat() {
		System.out.println("动物吃东西");
	}
}

public class Cat extends Animal {
	public void eat() {
		System.out.println("猫吃东西");
	}
}

public class Dog  extends Animal {
	public void eat() {
		System.out.println("狗吃东西");
	}
}

public class Tiger extends Animal {
	public void eat() {
		System.out.println("老虎吃东西");
	}
}

public class Zoo {
	public static void main(String[] args) {
		Feeder f=new Feeder();
		Cat c1=new Cat();
		Cat c2=new Cat();
		Dog d1=new Dog();
		Dog d2=new Dog();
		Tiger t=new Tiger();
		
		f.feed(c1);
		f.feed(c2);
		f.feed(d1);
		f.feed(d2);
		f.feed(t);
	}
}

把参数类型转换成一个父类的类型,调用子类的对象

  • f.feed(c1);这里的c1是Cat类型并不会报错,因为在这里Cat是Animal的子类,

    即在运行的时候出现了向上转型:在运行时将c1附给Feeder类喂食方法种的Animal

  • f.feed(c1)也可以写成f.feed((Animal)c1)//这里相当于一个隐式类型转换

(2)把多态用在方法的返回值类型上
//通过调用特定的类AnimalFactory的方法来创建动物
package classtext;
public class AnimalFactory {
	public static Cat createCat() {
		return new Cat();
	}
	public static Dog createDog() {
		return new Dog();
	}
}

public class Zoo {
	public static void main(String[] args) {
		Feeder f=new Feeder();
		Cat c1=AnimalFactory.createCat();
		Cat c2=AnimalFactory.createCat();
		Dog d1=AnimalFactory.createDog();
		Dog d2=AnimalFactory.createDog();
		Tiger t=new Tiger();
        
		f.feed(c1);
		f.feed(c2);
		f.feed(d1);
		f.feed(d2);
		f.feed(t);
	}
}
//使用多态改进:
package classtext;
public class AnimalFactory {
	public static Animal create(String name) {
		if (name.equals("猫") || name.equals("招财") || name.equals("cat"))
			return new Cat();
		else if (name.equals("狗") || name.equals("旺财") || name.equals("dog"))
			return new Dog();
		return null;
	}
}
package classtext;
public class Zoo {
	public static void main(String[] args) {
		Feeder f=new Feeder();
		Cat c1=AnimalFactory.create("招财");
		Cat c2=AnimalFactory.create("cat");
		Dog d1=AnimalFactory.create("旺财");
		Dog d2=AnimalFactory.create("dog");
		Tiger t=new Tiger();
		
		f.feed(c1);
		f.feed(c2);
		f.feed(d1);
		f.feed(d2);
		f.feed(t);
	}
}
/*
Cat c1=AnimalFactory.create("招财");
Cat c2=AnimalFactory.create("cat");
Dog d1=AnimalFactory.create("旺财");
Dog d2=AnimalFactory.create("dog");
Tiger t=AnimalFactory.create("tiger");
*/
//这些全部都会报错
//原因:AnimalFactory.create("XXX")返回值为Animal类型,而前面的c1\c2\d1\d2\t均是Animal的子类
//只可以实现由子类到父类的向上转型,不能实现由父类到子类的向下转型

比较字符串的内容的时候我们使用XXX.equals(“比较内容”)的方法来比较
name=="比较内容"的形式,比较的是字符串的地址

//基于上述错误可以使用以下两种方法进行改进:
//方法一:强制类型转换(修改对象的类型)
package classtext;
public class Zoo {
	public static void main(String[] args) {
		Feeder f=new Feeder();
		Cat c1=(Cat)AnimalFactory.create("招财");
		Cat c2=(Cat)AnimalFactory.create("cat");
		Dog d1=(Dog)AnimalFactory.create("旺财");
		Dog d2=(Dog)AnimalFactory.create("dog");
		Tiger t=new Tiger();
		
		f.feed(c1);
		f.feed(c2);
		f.feed(d1);
		f.feed(d2);
		f.feed(t);
	}
}
//方法二:修改引用的类型
package classtext;
public class Zoo {
	public static void main(String[] args) {
		Feeder f=new Feeder();
		Animal c1=AnimalFactory.create("招财");
		Animal c2=AnimalFactory.create("cat");
		Animal d1=AnimalFactory.create("旺财");
		Animal d2=AnimalFactory.create("dog");
		Tiger t=new Tiger();
		
		f.feed(c1);
		f.feed(c2);
		f.feed(d1);
		f.feed(d2);
		f.feed(t);
	}
}
//如果在Animal c2=AnimalFactory.create("cat");语句中将cat改为"CAT"(英文字符串就会有一个大小写的问题),
//则会出现报错:空指针异常(NullPointerException——NPE)
//可以使用以下两种方法改进:
//方法一:在方法设置时忽略英文大小写比较字符串内容
package classtext;
public class AnimalFactory {
	public static Animal create(String name) {
		if (name.equals("猫") || name.equals("招财") || name.equalsIgnoreCase("cat"))
			return new Cat();
		else if (name.equals("狗") || name.equals("旺财") || name.equalsIgnoreCase("dog"))
			return new Dog();
		return null;
	}
}
//方法二:将大写英文字符串改为小写再进行字符串的内容比较
package classtext;
public class AnimalFactory {
	public static Animal create(String name) {
		if (name.equals("猫") || name.equals("招财") || name.toLowerCase().equals("cat"))
			return new Cat();
		else if (name.equals("狗") || name.equals("旺财") || name.toLowerCase().equals("dog"))
			return new Dog();
		return null;
	}
}

在类AnimalFactory中将返回的子类类型统一向上转型为父类的Animal类型

(3)把多态用在集合上
package classtext;
public class Zoo {
	public static void main(String[] args) {
		Feeder f=new Feeder();
		Animal[] animals=new Animal[] {
				AnimalFactory.create("招财"),
				AnimalFactory.create("CAT"),
				AnimalFactory.create("旺财"),
				AnimalFactory.create("dog"),
				new Tiger();
				
		};
		for (Animal a:animals)
			f.feed(a);
	}
}

将不同类型的子类元素均存储在父类的数组中实现向上转型

补充说明

实现两个int整型的求和

package test;
//方法一:通过Scanner输入数值之后再求和
import java.util.Scanner;
public class Addint1 {
	public static void main(String[] args) {
		System.out.println("请输入两个int整型:");
		Scanner sc=new Scanner(System.in);
		int n, m;
		n=sc.nextInt();
		m=sc.nextInt();
		sc.close();
		System.out.println(n+m);
	}
}
//方法二:
import java.util.Arrays;
public class Addint2 {
	public static void main(String[] args) {
		System.out.println(Arrays.toString(args));
		int n=Integer.parseInt(args[0]);
		int m=Integer.parseInt(args[1]);
		System.out.println(n+m);
	}
}
/*
 * 运行结果:
 * [4, 5]
 * 9
 */

方法二实操:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值