1、static
1.1、static静态变量
static表示静态,是Java中的一个修饰符,可以修饰成员方法,成员变量。(被static修饰的成员方法或者成员变量是共享的,谁用谁去拿)
(1)、被static修饰的成员变量,叫做静态变量。
特点:被该类所有对象共享。不属于对象,属于类。随着类的加载而加载的,优先于对象出现的。静态的内容不会随着对象的变化而变化。
调用方式:类名调用(推荐)、对象名调用。
(2)、被static修饰的成员方法,叫做静态方法。
特点:多用在测试类和工具类当中。javabean类中很少会用。
调用方式:类名调用(推荐)、对象名调用。
复习目前所学过的所有的类:
1、JavaBean类
用来描述一类事物的类。比如:Student、Teacher、Dog、Cat等等。
书写JavaBean类的时候,要私有化成员变量,书写空参构造方法,书写带全部参数的构造方法,
还需要针对每一个私有化的成员变量提供其对应的get()和set()方法,如果说还有额外的行为,比如
sleep或study等等,还有一些额外的成员方法。核心在于JavaBean类是用来描述一类事物的。
2、测试类
用来检查其他类是否书写正确,带有main方法的类,是程序的入口。
在测试类当中,创建JavaBean类的对象并进行赋值调用的。
3、工具类
不是用来描述一类事物的,而是帮我们做一些事情的类。
特点:
(1)、类名见名知意。
(2)、私有化构造方法。为了不让外界创建这个类的对象,因为即使创建了也没意义。
(3)、方法定义为静态。
案例1:用static修饰成员变量。
需求:写一个javab类来描述这个班级的学生。属性有:姓名、年龄、性别。行为有:学习。
package FFFFFF;
public class Student {
private String name;
private int age;
private String gender;
//新增教师姓名属性,所有同学都一个老师
public static String teacherName;
public Student() {
}
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
//行为
public void study(){
System.out.println(name + "正在学习");
}
//
public void show(){
System.out.println(name + "," + age + "," + gender + "," + teacherName);
}
}
package FFFFFF;
public class StudentTest {
public static void main(String[] args) {
Student.teacherName = "大何老师"; //利用类名调用静态变量
//1、创建第一个学生对象
Student s1 = new Student();
s1.setName("张三");
s1.setAge(23);
s1.setGender("男");
//s1.teacherName = "大何老师"; //利用对象名调用静态变量
/**分析:
* 1、如果Student类中的teacherName不用static修饰,
* 1.1、 s1和s2都不给teacherName赋值的时候,s1和s2的teacherName输出值为null。
* 1.2、 只有s1给teacherName赋值的时候,s1的teacherName输出为赋的值,s2的teacherName输出值为null。
* 2、如果Student类中的teacherName用static修饰,
* 2.1、 s1和s2都不给teacherName赋值的时候,s1和s2的teacherName输出值为null。
* 2.2、 s1和s2中只要有一个给teacherName赋值,s1和s2的teacherName输出值都为赋的值。
* 2.3、利用类名调用静态变量,对teacherName进行赋值,则s1和s2的teacherName输出值都为赋的值。
*/
s1.study();
s1.show();
/**竖着批量修改,比如把本题中s1改为s2,方便快捷
* 方法1:按住鼠标滚轮,滑动鼠标选择修改的范围
* 方法2:按住alt,然后按住鼠标左键选择修改的范围
*/
//1、创建第二个学生对象
Student s2 = new Student();
s2.setName("李红");
s2.setAge(21);
s2.setGender("女");
s2.study();
s2.show();
}
}
案例2:定义数组工具类。
需求:在实际开发当中,经常会遇到一些数组使用的工具类。请按照以下要求编写一个数组的工具类:ArrayUtil。
(1)、提供一个工具类方法printArr,用于返回整数数组的内容。返回的字符串格式如下:
[10,20,50,34,100] (只考虑整数型数组,且只考虑一维数组)
(2)、提供这样一个工具方法getAerage,用于返回平均分。(只考虑浮点型数组,且只考虑 一维数组)
(3)、定义一个测试类TestDome,调用该工具类的工具方法,并返回结果。
package Test01;
public class ArrayUtil {
//私有化构造方法
//目的:为了不让外界创建他的对象
private ArrayUtil(){}
//需要定义为静态的,方便调用
public static String printArr(int[] arr){
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < arr.length; i++) {
if(i == arr.length - 1){
sb.append(arr[i]);
} else {
sb.append(arr[i]).append(",");
}
}
sb.append("]");
return sb.toString();
}
public static double getAerage(double[] arr){
double sum = 0;
for (int i = 0; i < arr.length; i++) {
sum = sum + arr[i];
}
double aerage = sum/arr.length;
return aerage;
}
}
package Test01;
public class TestDome {
public static void main(String[] args) {
int[] arr1 = {1,2,3,4,5};
String str = ArrayUtil.printArr(arr1);
System.out.println(str);
double[] arr2 = {1.2,2.2,3.2};
double str2 = ArrayUtil.getAerage(arr2);
System.out.println(str2);
}
}
案例3:定义学生工具类。
需求:定义一个集合,用于存储3个学生对象。
学生的属性为:name、age、gender。
定义一个工具类,用于获取集合中最大学生的年龄。
package testDome02;
public class Student {
private String name;
private int age;
private String gender;
public Student() {
}
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
package testDome02;
import java.util.ArrayList;
public class StudentUtil {
private StudentUtil(){}
//静态方法
public static int getMaxAAgeStudent(ArrayList<Student> list){
//定义一个参照物
int max = list.get(0).getAge();
for (int i = 1; i < list.size(); i++) {
int temp = list.get(i).getAge();
if( temp > max){
max = temp;
}
}
return max;
}
}
package testDome02;
import java.util.ArrayList;
public class tset {
public static void main(String[] args) {
//创建一个集合用来存储学生对象
ArrayList<Student> list = new ArrayList<>();
//创建3个学生对象
Student stu1 = new Student("张三",23,"男");
Student stu2 = new Student("小红",18,"女");
Student stu3 = new Student("王二",21,"男");
list.add(stu1);
list.add(stu2);
list.add(stu3);
//调用工具类中的方法
int maxAgeStudent = StudentUtil.getMaxAAgeStudent(list);
System.out.println(maxAgeStudent);
}
}
1.2、static的注意事项
1、静态方法只能访问静态。
2、非静态方法可以访问所有。
3、静态方法中没有this关键字。
1.3、静态方法内容补充:
1、在定义方法的时候,在修饰符的位置加上static,这个方法就是一个静态方法
2、静态方法,可以通过类名去访问,也可以通过对象名去访问
3、只能能通过类名去访问的内容,本质上都是属于类最好都通过类名去访问
4、静态方法使用注意事项:
(1)、静态方法中不能访问成员变量
静态方法,可以在没有创建对象的时候就可以调用,成员变量是在对象完成之后才可以使
用,如果静态的方法可以访问成员变量,就相当于在创建对象之前,就使用对象内容
(2)、静态方法能不能访问成员方法,不能,原理同上
(3)、静态方法只能访问静态的内容,可以访问静态的属性,也可以访问静态的方法
(4)、静态方法能不能有this关键字,this表示本类当前对象,有了对象才有this,所以还是不行
5、总结:
(1)、静态的内容不可以访问非静态的内容,非静态的内容可以访问静态的内容
1.4、静态变量和非静态变量的区别
1、概念上所属不同:
(1)、非静态变量属于对象
(2)、静态变量属于类
2、内存空间位置不同:
(1)、非静态变量存储在堆内存中,和对象一个区域
(2)、静态变量属于类,和类.class文件在一个区域,都在方法区中,只不过静态变量在静态区
3、存储的时间不同,生命周期不同
(1)、非静态变量属于对象,所以生命周期和对象一致,随着对象的创建而创建,随着对象的消亡而消亡
(2)、静态变量属于类,所以生命周期和类一致,随着类的加载而加载,.class的销毁而销毁
4、访问方式的不同:
(1)、非静态变量,只能通过对象名访问
(2)、静态变量,既可以通过类名访问,也可以通过对象名来访问
2、继承
2.1继承的概述
1、继承
(1)、继承是面向对象的三大特征之一,可以让类和类之间产生子父关系。
(2)、Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起继承关系。
public class Student extends Person {}
Student称为子类(派生类),Person称为父类(基类或者超类)。
2、使用继承的好处:
(1)、可以把多个子类中重复的代码抽取到父类当中,提高代码的复用性。
(2)、子类可以在父类的基础上,增加其他的功能,使子类更强大。子类拥有父类的所有功能。
(3)、子类可以得到父类的属性和行为,子类可以使用父类的属性和行为。
3、什么时候用继承?
当类与类之间,存在相同(共性)的内容,并满足子类是父类中的一种,就可以考虑使用继承,来优化代码。
4、继承的好处和弊端
(1)、好处:
a、提高了代码的复用性
b、提高了代码的可维护性
c、是类中多态的前提
(2)、弊端:
提高了代码和代码的耦合性(耦合性:事物和事物之间互相影响的程度)
(3)、开发原则:高内聚、低耦合。
(4)、继承的好处远远大于弊端,所以继承还是要用。
2.2、继承的特点
Java只支持单继承,不支持多继承,单支持多层继承。
(1)、单继承:一个子类只能继承一个直接父类。
(2)、不支持多继承:子类不能同时继承多个父类。
(3)、多层继承:子类A继承父类B,父类B可以继承父类C。
(4)、每一个类直接或简介继承于Object(由Java虚拟机自动调用)
(5)、子类可以用间接父类(间接父类类似于现实生活当中的爷爷)中的变量与方法。
(6)、父类中的私有成员不能被继承(不能在子类中直接访问)
a、父类中的一些私有成员,不能在子类中直接调用。
b、其实在子类对象中,仍然包含父类这些私有的成员变量。
c、只不过在子类中,不能直接使用这些私有的内容,需要通过公共的访问方式访问。
案例1:
package EXTENDS;
public class Animal {
//权限修饰符
//private:子类就无法访问了
//私有:只能在本类中访问,比如爸爸的私房钱儿子是不能用的
//子类只能访问父类中非私有的成员
public void eat(){
System.out.println("吃东西");
}
public void drink(){
System.out.println("喝水");
}
}
package EXTENDS;
public class cat extends Animal {
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
package EXTENDS;
public class Dog extends Animal {
public void lookHome(){
System.out.println("狗在看家");
}
}
package EXTENDS;
public class Husky extends Dog{
public void breakHome(){
System.out.println("哈士奇在拆家");
}
}
package EXTENDS;
public class LiHua extends cat{
}
package EXTENDS;
public class Ragdoll extends cat {
}
package EXTENDS;
public class Teddy extends Dog{
public void touch(){
System.out.println("泰迪又在蹭我的腿了");
}
}
package EXTENDS;
public class Test {
public static void main(String[] args) {
Ragdoll ad = new Ragdoll();
ad.eat();
ad.drink();
ad.catchMouse();
Husky hs = new Husky();
hs.eat();
hs.drink();
hs.breakHome();
hs.lookHome();
LiHua lh = new LiHua();
lh.catchMouse();
}
}
2.3、继承中成员变量的访问特点:
1、就近原则、直接用变量名
2、在用本类中的值:this.变量名
3、用父类中的值:super.变量名
(先在局部位置找,本类成员位置找,父类成员位置找,逐级往上)
(this和super是在子类中使用的,不是在测试类当中使用的)
(变量:从局部位置开始往上找)
(this.变量:从本类成员位置开始往上找)
(super.变量:从父类成员位置开始往上找)
案例:
package Diaoyong;
public class Test {
public static void main(String[] args) {
er er1 = new er();
er1.zishu();
}
}
class fu {
String name = "funame";
}
class er extends fu {
String name = "ziname";
public void zishu(){
String name = "输出名字";
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
2.4、继承中:成员方法的访问特点
直接调用满足就近原则:谁离我近,我就调用谁
this调用,就近原则。
super调用,直接访问父类。
(this和super是在子类中使用的,不是在测试类当中使用的)
(变量:从局部位置开始往上找)
(this.变量:从本类成员位置开始往上找)
(super.变量:从父类成员位置开始往上找)
package cyff;
public class test {
public static void main(String[] args) {
Student s= new Student();
s.lunch();
}
}
class Person{
public void eat(){
System.out.println("吃米饭,吃菜");
}
public void drink(){
System.out.println("喝开水");
}
}
class Student extends Person{
public void lunch(){
this.eat();
this.drink();
super.eat();
super.drink();
}
public void eat(){
System.out.println("chifan");
}
public void drink(){
System.out.println("heshui");
}
}
继承中成员方法的相关补充:
1、在父子类中,如果写的都是不同名的方法,则子类既可以访问父类,也可以访问子类本身。
2、出现了同名的方法时,在子类调用这个方法的时候,只有子类对这个方法的实现。
3、如果父类和子类的方法名相同,但是实现不同,Java把这个叫做方法的重写。
a、重载:在一个类中,方法名相同,参数列表不同,与返回值类型无关。
b、重写:在父子类中,方法名一样,参数列表一致,返回值类型一致,实现体不同。
c、Java中可以通过@Override检测一个方法是否是重写父类的方法。
补充: 方法的重写
定义:把父类的方法在子类里面重写一遍。
当父类的方法不能满足子类现在的需求时,需要进行方法重写。
重写的本质:方法被重写之后,相当于覆盖掉了父类中的方法。
方法重写注意事项和要求
1、重写方法的名称、形参列表必须与父类中的保持一致。
2、子类重写父类方法时,访问权限子类必须大于等于父类(暂时了解:空着不写 < protected < public)
3、子类重写父类方法时,返回值类型子类必须小于等于父类。
4、建议:重写的方法尽量和父类保持一致。(重点强调)
5、私有方法不能被重写。
6、子类不能重写父类的静态方法,如果重写会报错的。
补充:5和6总结起来就是“只有被添加到虚方法表中的方法才能被重写”
练习案例1:利用方法的重写设计继承结构
现在有三种动物:哈士奇、沙皮狗、中华田园犬。
暂时不考虑属性,只要考虑行为。
请按照继承的思想特点进行继承体系的设计。
分析如下:
package JiCheng;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
public class test {
public static void main(String[] args) {
HaShiQi h = new HaShiQi();
h.eat();
h.drink();
h.home();
h.chaijia();
System.out.println("--------------");
ShaPiGou s = new ShaPiGou();
s.eat();
s.drink();
s.home();
System.out.println("--------------");
ZhongHua z = new ZhongHua();
z.eat();
z.drink();
z.home();
}
}
//定义动物类
class Animal{
public void eat(){
System.out.println("吃狗粮");
}
public void drink(){
System.out.println("喝水");
}
public void home(){
System.out.println("看家");
}
}
//定义哈士奇
class HaShiQi extends Animal{
public void chaijia(){
System.out.println("拆家");
}
}
//定义沙皮狗
class ShaPiGou extends Animal{
//因为沙皮狗吃的狗粮和骨头
//父类中的方法不能满足我们的需求了,所以需要进行重写。
@Override
public void eat(){
super.eat(); //吃狗粮
System.out.println("吃骨头");
}
}
//定义中华田园犬
class ZhongHua extends Animal{
//父类中的方法不能满足我们的需求了,所以需要进行重写
//而且中华田园犬完全用不到父类中的代码,所以不需要通过super进行调用。
@Override
public void eat(){
System.out.println("吃剩饭");
}
}
2.5、继承中:构造方法的访问特点
package WCGZ;
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student("张三",23);
System.out.println(s2.name + "," + s2.age);
}
}
class Person{
String name;
int age;
public Person(){
System.out.println("父类无参构造");
}
public Person(String name,int age){
this.name=name;
this.age=age;
}
}
class Student extends Person{
public Student(){
//子类构造方法中隐藏的super()去访问父类的无参构造
super(); //调用了父类的无参构造
System.out.println("子类的无参构造");
}
//给父类变量赋值
public Student(String name,int age){
super(name,age); //调用了父类的有参构造
}
}
输出为下:
父类无参构造
子类的无参构造
张三,23
对2.5进行小结:继承中构造方法的访问特点是什么?
1、子类不能继承父类的构造方法,但是可以通过super调用。
2、子类构造方法的第一行,有一个默认的super();
3、默认先访问父类中无参的构造方法,再执行自己。
4、如果不想调用父类中的无参构造,想要调用父类当中的有参构造,给成员变量赋值,则super需要自己写,而且要把赋值的数据写在小括号当中就可以了。
5、this语句和super语句只能出现在构造方法的第一行,而且只能出现一个。
this、super使用总结
this:理解为一个变量,表示当前方法调用者的地址值。
super:代表父类存储空间。
注意:要给一些数据设置一些默认值的时候就会用到this(....)访问本类构造方法。
1、含义:
this表示本类当前对象的引用
super表示本类当前对象父类部分的引用,哪个对象调用super所在的方法,super就代表哪个对象父类部分的数据
2、super和this都可以访问成员变量
super只能访问父类中的成员变量
this既可以访问父类中的成员变量,也可以访问子类中的成员变量
3、this和super都可以访问成员方法
this既可以访问父类中的成员方法,也可以访问子类的
super只能访问父类中的成员方法,格式 super.成员方法名(实际参数)
4、super和this都可以在构造方法中使用,但是使用的特点不同,可以用来访问构造方法的内容,this访问叫做this语句,super访问叫做super语句
this(实际参数);访问子类的构造方法
super(实际参数);访问父类的构造方法
super语句和this语句,只能出现在构造方法的第一行,其他位置均不可以
package FWQT;
public class Test {
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.name + "," + s.age + "," + s.school);//输出为 null,0,传智大学
}
}
class Student{
String name;
int age;
String school;
public Student() {
//表示调用本类其他构造方法
//细节:虚拟机就不会再添加super();
this(null,0,"传智大学");
//System.out.println("123"); //如果写在this语句的上面系统就会报错
}
public Student(String name, int age, String school) {
this.name = name;
this.age = age;
this.school = school;
}
}
练习案例3:带有继承结构的标准Javabean类
父类代码如下:
package AL01;
public class Employee {
/**
* 1.类名见名知意
* 2.所有的成员变量都需要私有
* 3.构造方法(空参 带全部参数的构造)
* 4.get/set
*/
//id,name,salary三种变量,经理和厨师都有,所以把它们抽取到父类Person当中
private String id;
private String name;
private double salary;
public Employee() {
}
public Employee(String id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//工作和吃饭这两种方法,经历和厨师都有,所以把他们抽取到父类Person当中
//工作
public void work(){
System.out.println("员工在工作");
}
//吃饭
public void eat(){
System.out.println("吃米饭");
}
}
经理模块代码如下:
package AL01;
public class Manager extends Employee {
//因为父类当中的成员变量只有id,name,salary,不能满足子类需求,所以还需要添加变量jiangjin
private double jiangjin;
/**标准JavaBean类要求要有无参和全参构造。
* 方法:Alt+Insert,然后全选,然后确定,再确定即可
*/
//空参构造
public Manager() {
}
//带全部参数的构造(父类+子类)
public Manager(String id, String name, double salary, double jiangjin) {
super(id, name, salary);
this.jiangjin = jiangjin;
}
/**关于get和set方法,
* 因为父类当中已经有id,name,salary的set、get方法了
* 所以在这里就不需要再书写
*只需要写父类当中没有的jiangjin的set、get方法
*/
public double getJiangjin() {
return jiangjin;
}
public void setJiangjin(double jiangjin) {
this.jiangjin = jiangjin;
}
//因为父类当中的work方法并不能满足子类要求,所以要重写work方法
@Override
public void work(){
System.out.println("管理其他人");
}
}
厨师模块如下:
package AL01;
public class Cook extends Employee {
//1、因为父类当中的成员变量有id,name,salary,能满足子类需求,所以不需要再添加变量
//2、构造方法一定要写
//空参构造
public Cook() {
}
//全参构造
public Cook(String id, String name, double salary) {
super(id, name, salary);
}
//3、关于get和set方法,
// 因为父类当中有id, name, salary的set、get方法,所以这里就不用写了
//因为父类当中的work方法并不能满足子类要求,所以要重写work方法
@Override
public void work(){
System.out.println("厨师在炒菜");
}
}
测试类模块如下:
package AL01;
public class Test {
public static void main(String[] args) {
//创建对象并赋值调用
Manager m = new Manager("001","张三",1500,6000);
System.out.println(m.getId() + "," + m.getName() + "," + m.getSalary() + "," + m.getJiangjin());
m.work();
m.eat();
Cook c = new Cook();
c.setId("002");
c.setName("李四");
c.setSalary(666);
System.out.println(c.getId() + "," + c.getName() + "," + c.getSalary());
c.work();
c.eat();
}
}
输出结果为:
001,张三,1500.0,6000.0
管理其他人
吃米饭
002,李四,666.0
厨师在炒菜
吃米饭
练习案例4:带有继承结构的标准JavaBean类
在黑马程序员中有很多员工(Employee)
按照工作内容不同分教研部员工(Teacher)和行政部员工(AdminStaff)
1、教研部根据教学的方式不同又分为讲师(Lecturer)和助教(Tutor)
2、行政部根据负责事项不同,又分为维护专员(Maintainer),采购专员(Buyer)
3、公司的每一个员工都有编号,姓名和其负责的工作
4、每个员工都有工作的功能,但是具体的工作内容又不一样
父类代码如下:
package YuanGong;
public class Employee {
private String bianhao;
private String name;
public Employee() {
}
public Employee(String bianhao, String name) {
this.bianhao = bianhao;
this.name = name;
}
public String getBianhao() {
return bianhao;
}
public void setBianhao(String bianhao) {
this.bianhao = bianhao;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void work(){
System.out.println("员工在工作");
}
}
package YuanGong;
public class JiaoYanBu extends Employee {
public JiaoYanBu() {
}
public JiaoYanBu(String bianhao, String name) {
super(bianhao, name);
}
}
package YuanGong;
public class XingZhengBu extends Employee {
public XingZhengBu() {
}
public XingZhengBu(String bianhao, String name) {
super(bianhao, name);
}
}
子类代码如下:
package YuanGong;
public class JiangShi01 extends JiaoYanBu {
public JiangShi01() {
}
public JiangShi01(String bianhao, String name) {
super(bianhao, name);
}
@Override
public void work(){
System.out.println("讲师在工作");
}
}
package YuanGong;
public class ZhuJiao02 extends JiaoYanBu{
public ZhuJiao02() {
}
public ZhuJiao02(String bianhao, String name) {
super(bianhao, name);
}
@Override
public void work(){
System.out.println("助教在工作");
}
}
package YuanGong;
public class WeiHu03 extends XingZhengBu {
public WeiHu03() {
}
public WeiHu03(String bianhao, String name) {
super(bianhao, name);
}
@Override
public void work(){
System.out.println("维护员在工作");
}
}
package YuanGong;
public class CaiGou04 extends XingZhengBu{
public CaiGou04() {
}
public CaiGou04(String bianhao, String name) {
super(bianhao, name);
}
@Override
public void work(){
System.out.println("采购员在工作");
}
}
测试类代码如下:
package YuanGong;
public class Test {
public static void main(String[] args) {
JiangShi01 js = new JiangShi01("001","张三");
System.out.println(js.getBianhao() + "," + js.getName());
js.work();
ZhuJiao02 zj = new ZhuJiao02("002","李四");
System.out.println(zj.getBianhao() + "," + zj.getName());
zj.work();
WeiHu03 wh = new WeiHu03("003","王二");
System.out.println(wh.getBianhao() + "," + wh.getName());
wh.work();
CaiGou04 cg = new CaiGou04("004","麻子");
System.out.println(cg.getBianhao() + "," + cg.getName());
cg.work();
}
}
3 多态
3.1 什么是多态?
同类型的对象,表现出的不同形态。
多态的表现形式
父类类型 对象名称 = 子类对象;
例如:
Person p = new Student();
多态的前提
1、有继承的关系或者实现关系
2、有父类引用指向子类对象 Fu f = new Zi();
3、有方法重写
多态的好处
使用父类型作为参数,可以接收所有子类对象,从而体现了多态的扩展性与便利。
案例1
父类代码如下:
package DuoTai;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void show(){
System.out.println(name + "," + age);
}
}
子类代码如下:
package DuoTai;
public class Student extends Person{
public Student() {
}
public Student(String name, int age) {
super(name, age);
}
@Override
public void show(){
System.out.println("学生的信息为:" + getName() + "," + getAge());
}
}
package DuoTai;
public class Teacher extends Person {
public Teacher() {
}
public Teacher(String name, int age) {
super(name, age);
}
@Override
public void show(){
System.out.println("教师的信息为:" + getName() + "," + getAge());
}
}
package DuoTai;
public class Admin extends Person {
public Admin() {
}
public Admin(String name, int age) {
super(name, age);
}
@Override
public void show(){
System.out.println("管理员的信息为:" + getName() + "," + getAge());
}
}
测试类代码如下:
package DuoTai;
public class Test {
public static void main(String[] args) {
//创建三个对象,并调用register方法
Student s = new Student();
s.setName("张三");
s.setAge(18);
Teacher t = new Teacher();
t.setName("李华");
t.setAge(25);
Admin ad = new Admin();
ad.setName("管家");
ad.setAge(35);
register(s);
register(t);
register(ad);
}
//既然这个方法既能接收老师,又能接收老师,还能接收管理员
//只能把参数写成这三个类型的父类
public static void register(Person p){
p.show();
}
}
3.2 多态调用成员变量的特点
变量调用:编译看左边,运行也看左边。
方法调用:编译看左边,运行看右边。
案例实现:
package DuoTaiA;
public class Test {
public static void main(String[] args) {
//创建对象 (多态方式)
//Fu f = new Zi();
Animal a = new Dog();
/**调用成员变量:编译看左边,运行也看左边
* 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果有,编译成功,如果没有编译失败。
* 运行也看左边:java运行代码的时候,实际获取的就是左边父类中的成员变量的值
*/
System.out.println(a.name);//动物
/**调用成员方法:编译看左边,运行看右边
* 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个方法,如果有,编译成功,如果没有编译失败。
* 运行也看左边:java运行代码的时候,实际获取的就是子类中的方法。
*/
a.show(); //Dog---show方法
/**理解方法:
* Animal a = new Dod();
* 现在用a去调用变量和方法
* 而a是Animal类型的,所以默认都会从Animal这个类中去找
*
* 成员变量:在子类的对象中,会把父类的成员变量也继承下去。 父:name 子:name,因为父类的变量还在,所以用了父类的变量
* 成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖的。父类的方法被覆盖了(相当于不在了)所以调用了子类的方法
*/
}
}
class Animal{
String name = "动物";
public void show(){
System.out.println("Animal---show方法");
}
}
class Dog extends Animal{
String name = "狗";
@Override
public void show(){
System.out.println("Dog---show方法");
}
}
class Cat extends Animal{
String name = "猫";
@Override
public void show(){
System.out.println("Cat---show方法");
}
}
3.3 多态的优势与弊端
1、多态的优势
a、在多态形式下,右边对象可以实现解耦合,便于扩展和维护。(修改下面被红线圈中的部分即可)
b、定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。
由于参数为Object类,所以可以添加任何类,因为Object类是所有类的父类。
2、多态的弊端是:不能使用子类的特有功能。(重点!!!)
3、 引用数据类型的类型转换,有几种方式?
自动类型转换属于向上转型,是多态的体现,本质上缩小了对象本身访问的范围,减少了访问权限,只能访问父类中的内容,而不能访问子类特有的内容。(由于继承关系,父类的内容子类都有,但是子类中的内容父类不一定有,所以子类的内容父类多。)
强制类型转换属于向下转型,前提条件为一定是一个向上转型的对象。本质上是恢复子类本身的访问范围。
注意:不可以把父类对象进行向下转型,必须是父类的引用指向子类对象这种类型,才可以向下转型。
4、 强制类型转换能解决哪些问题?
可以转换成真正的子类类型,从而调用子类独有功能。
转换类型与真实对象类型不一致会报错 。
转换的时候用instanceof关键字进行判断。
instanceof关键字案例实践如下:
package DTBD;
public class Test {
public static void main(String[] args) {
//创建对象
Animal a = new Dog();
//编译看左边,运行看右边
a.eat(); //狗在吃骨头
/**多态的弊端
* 不能调用子类的特有功能,比如Dog里面的lookHome,还有Cat里面的catchMouse
* 例如:a.lookHome(); 系统会报错
* 因为当调用成员方法的时候,编译看左边,运行看右边。
* 那么在编译的时候会先检查左边的父类中有没有这个方法,如果没有直接报错。
*/
/**弊端的解决方案:
* 变回原来的子类型就可以了,例如:把Animal类型的变量a转换成Dog类型,然后就可以调用子类Dog当中的lookHome了
* 细节:转换的时候,不能瞎转,如果转换成其他类的类型,就会报错,例如把上
* 面的Animal类型的变量a转换成Cat类型就会报错
*/
//Dog d = (Dog) a;
//d.lookHome(); //狗在看家
if(a instanceof Dog){
Dog d = (Dog) a;
d.lookHome();
} else if(a instanceof Cat){
Cat c = (Cat) a;
c.catchHouse();
} else {
System.out.println("没有这个类型无法转换");
}
//java14新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是Dog类型,则不强转,结果直接是false
/*if(a instanceof Dog d){
d.lookHome();
} else if(a instanceof Cat c){
c.catchHouse();
} else {
System.out.println("没有这个类型无法转换");
}*/
}
}
class Animal{
public void eat(){
System.out.println("动物在吃东西");
}
}
class Dog extends Animal{
@Override
public void eat(){
System.out.println("狗在吃骨头");
}
public void lookHome(){
System.out.println("狗在看家");
}
}
class Cat extends Animal{
@Override
public void eat(){
System.out.println("猫在吃小鱼干");
}
public void catchHouse(){
System.out.println("猫捉老鼠");
}
}
3.4 多态的综合练习
分析如下:
父类代码如下:
package DTZHLX;
public class Animal {
private int age;
private String color;
public Animal() {
}
public Animal(int age, String color) {
this.age = age;
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void eat(String something){
System.out.println("动物在吃"+something);
}
}
子类代码如下:
package DTZHLX;
public class Dog extends Animal{
public Dog() {
}
public Dog(int age, String color) {
super(age, color);
}
//行为:
//eat(String something)(something表示吃的东西)
//看家lookHome方法(无参数)
@Override
public void eat(String something){//由于继承关系,getAge()+"岁的"+getColor()会自动调用父类当中对应的的get方法
System.out.println(getAge()+"岁的"+getColor()+"颜色的狗两只前腿死死地抱住"+something+"猛吃");
}
//书写Dog类型特有的方法
public void lookHome(){
System.out.println("狗在看家");
}
}
package DTZHLX;
public class Cat extends Animal {
public Cat() {
}
public Cat(int age, String color) {
super(age, color);
}
//eat(String something)方法(something表示吃的东西)
//捉老鼠catchHouse方法(无参数)
@Override
public void eat(String something){//由于继承关系,getAge()+"岁的"+getColor()会自动调用父类当中对应的的get方法
System.out.println(getAge()+"岁的"+getColor()+"颜色的猫眯着眼睛侧着头吃"+something);
}
//书写Cat类型特有的方法
public void catchHouse(){
System.out.println("猫在捉老鼠");
}
}
Person类代码如下:
package DTZHLX;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**Animal dongwu利用多态
* instanceof的作用是判断,dongwu instanceof Dog是对输入的参数dongwu进行判断,判断是否为Dog类型
* 如果是Dog类型,则如果没有对dongwu进行强制转换,则不能调用子类特有的方法。例如下面标
* 记处1如果没执行,则标志处2会报错。但是无论有没有标记处1,标志处3都不受影响
*/
public void keepPet(Animal dongwu, String something){
if(dongwu instanceof Dog){
Dog d =( Dog )dongwu; //标记处1
System.out.println("年龄为"+age+"岁的"+name+"养了一只"+dongwu.getColor()+"颜色的"+dongwu.getAge()+"岁的狗"); //标志处3
dongwu.eat(something); //调用子类重写的方法
d.lookHome(); //标志处2,调用Dog类特有的方法
} else if(dongwu instanceof Cat){
Cat c = ( Cat )dongwu;
System.out.println("年龄为"+age+"岁的"+name+"养了一只"+c.getColor()+"颜色的"+c.getAge()+"岁的猫");
c.eat(something);
} else{
System.out.println("没有这种动物");
}
}
}
测试类代码如下:
package DTZHLX;
public class Test {
public static void main(String[] args) {
//由于多态的存在,创建Person对象的时候,只需要创建一个就行了
Person p = new Person("老王",36);
Dog d = new Dog(8,"黑");
p.keepPet(d,"骨头");
System.out.println("----------------------");
Cat c = new Cat(9,"黄");
p.keepPet(c,"鱼");
}
}
代码运行后,输出结果如下:
年龄为36岁的老王养了一只黑颜色的8岁的狗
8岁的黑颜色的狗两只前腿死死地抱住骨头猛吃
狗在看家
----------------------
年龄为36岁的老王养了一只黄颜色的9岁的猫
9岁的黄颜色的猫眯着眼睛侧着头吃鱼
4 包、final关键字
4.1 什么是包?
包就是文件夹。用来管理各种不同功能的Java类,方便后期代码维护。
包名的规则:公司域名反写+包的作用,需要全部英文小写,见名知意。
全类名:包名+类名
使用其他类的规则:
(1)、使用同一个包中的类时,不需要导包。
(2)、使用java.lang包中的类时,不需要导包。
(3)、其他情况都需要导包。
(4)、如果同时使用两个包中的同名类,需要用全类名。
实践案例如下:
package DaoBaoLianXi01;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package DaoBaoLianXi02;
public class Teacher {
private String name;
private int age;
public Teacher() {
}
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package DaoBaoLianXi03;
public class Teacher {
private String name;
private int age;
public Teacher() {
}
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package DaoBaoLianXi01;
import DaoBaoLianXi02.Teacher;
public class Test {
public static void main(String[] args) {
//创建对象,验证使用同一个包中的类时,不需要导包
Student s = new Student();
s.setName("张三");
s.setAge(23);
System.out.println(s.getName()+","+s.getAge());
//验证使用java.lang包中的类时,不需要导包。(String属于java.lang包中的字符串对象)
String str = "abc";
System.out.println(str);
//验证 其他情况下都需要导包
//调用了DaoBaoLianXi02里的Teacher类,所以第三行的时候导包了,不然会报错
//导包时的代码 import DaoBaoLianXi02.Teacher;
//Teacher t = new Teacher();
//验证 如果同时使用两个包中的同名类,需要全类名
//验证方式:新建两个包DaoBaoLianXi02和DaoBaoLianXi03,在这两个包里面分别定义一个Teacher类
//当同时使用两个包中的同名类,需要全类名,不然会报错。
DaoBaoLianXi02.Teacher t2 = new DaoBaoLianXi02.Teacher();
t2.setName("王二"); t2.setAge(12);
System.out.println(t2.getName()+","+t2.getAge());
DaoBaoLianXi03.Teacher t3 = new DaoBaoLianXi03.Teacher();
t3.setName("三三三"); t3.setAge(18);
System.out.println(t3.getName()+","+t3.getAge());
}
}
4.2 final关键字
被final修饰后,就不能再改变。final可以修饰方法、类、变量。
(1)修饰方法时:表明该方法是最终方法,不能被重写。(一般用于设置某种不希望被改变的规则。)
(2)修饰类时:表明该类是最终类,不能被继承。
(3)修饰变量时:被修饰的变量叫做常量,只能被赋值一次,且不能被修改。(可以定义和赋值分开写)
修饰方法:
public final void shout(){
}
修饰类:
final class Animal{
}
修饰变量:
final int a = 10; //变量被final修饰的同时必须直接赋值,否则会报错。
比如以下情况会报错:
final int b;
b = 4;
细节验证案例如下:
package finalLianXi;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package finalLianXi;
public class Test {
public static void main(String[] args) {
/**
* final 修改基本数据类型:记录的值不能发生改变。
* final 修饰引用数据类型:记录的地址值不能发生改变,内部的属性值还是可以改变的。
*/
//核心:常量记录的数据是不会发生改变的。(下面的s和Arr是引用变量,记录的都是地址值)
final double PI = 3.14; //基本数据类型
//创建对象
final Student s = new Student("张三",23); //s为引用数据对象
// s = new Student(); 会报错,因为这行代码运行后改变了s的地址值
s.setName("李四"); //对属性值name进行修改
s.setAge(24); //对属性值age进行修改
System.out.println(s.getName()+","+s.getAge()); //李四,24
//数组
final int[] Arr ={1,2,3,4,5};
//Arr = new int[10]; 会报错,因为这行代码运行后改变了Arr的地址值
Arr[0] = 10;
Arr[1] = 20;
//遍历数组
for (int i = 0; i < Arr.length; i++) {
System.out.print(Arr[i]+"\t"); //10 20 3 4 5
}
}
}
final练习案例:常量的练习
要求:将学生管理系统中用户的操作改写为常量的形式。
思路:把学生管理系统中的switch结构中的选项换成英语单词(英文单词提前被final修饰并赋值),从而增加了代码的可读性。