面对对象进阶-上(八)
staic
static表示静态,是JAVA中的一个修饰符,可修饰成员方法,成员变量
静态变量
被static修饰的成员变量,叫做静态变量。
特点:
- 被该类所有对象共享
- 不属于对象,属于类,可通过类名调用
- 随着类的加载而加载,优先于对象存在
调用方式:
- 类名调用(推荐)
- 对象名调用
例:
//Student.class
public class Student{
private String name;
private int age;
public static String teacherName;
public Student(){}
public Student(String name,int age){
this.name=name;
this.age=age;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age=age;
}
public int getAge(){
return age;
}
public void show(){
System.out.println("学生姓名:"+name+" 学生年龄:"+age+" 老师姓名:"+teacherName);
}
}
//Test.class
public class Test{
public static void main(String[] args){
//类名调用
Student.teacherName="王五";
Student student1=new Student("张三",18);
//也可以对象名调用
//student1.teacherName="王五";
student1.show();
Student student2=new Student();
student2.show();
}
}
/*
学生姓名:张三 学生年龄:18 老师姓名:王五
学生姓名:null 学生年龄:0 老师姓名:王五
*/
静态方法
被static修饰的成员方法,叫做静态方法。
特点:
- 多用在测试类或工具类中
- Javabean类中很少用
补充:工具类是帮我们做一些事情的,但不描述任何事物的类
- 工具类类名需见名知意
- 私有化构造方法。
- 方法定义为静态
调用方式:
- 类名调用(推荐)
- 对象名调用
范例
定义一个集合,存储三个学生对象。
学生类属性:name,age。
定义一个工具类,获取集合中最大学生的年龄
//javabean类
//Student.java
public class Student{
private String name;
private int age;
public Student(){}
public Student(String name,int age){
this.name=name;
this.age=age;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age=age;
}
public int getAge(){
return age;
}
}
//工具类
//StudentMaxAge.java
import java.util.ArrayList;
public class StudentMaxAge{
private StudentMaxAge(){}
public static int age(ArrayList<Student> list){
int max=list.get(0).getAge();
for(int i=1;i<list.size();i++){
int n=list.get(i).getAge();
if(n>max){
max=n;
}
}
return max;
}
}
//测试类
//test.java
import java.util.ArrayList;
public class Test{
public static void main(String[] args){
Student student1=new Student("张三",18);
Student student2=new Student("李四",19);
Student student3=new Student("王五",17);
ArrayList<Student> list=new ArrayList<>();
list.add(student1);
list.add(student2);
list.add(student3);
int max=StudentMaxAge.age(list);
System.out.println(max);//19
}
}
注意事项
- 静态方法只能访问静态变量和静态方法
- 非静态方法可以访问静态变量或静态方法,也可以访问非静态的成员变量和非静态的成员方法
- 静态方法中没有this关键字
继承
定义:继承是面对对象三大特征之一,可以让类与类间产生子父关系,子类可以直接使用父类中非私有的成员。
好处:可以把多个子类重复的代码抽取到父类中,子类可直接使用,减少代码冗余,提高代码的复用性。
格式:
public class 子类 extends 父类{}
继承后子类的特点:
子类可得到父类的属性和行为,子类可使用。
子类可在父类的基础上新增其他功能,子类更强大。
特点
- JAVA只能单继承:一个类只能继承一个直接父类
- JAVA不支持多继承:但支持多层继承
- JAVA所有类都直接或间接继承于Object类
子类继承父类的内容
- 构造方法:无论是否私有,都不能继承
- 成员变量:无论是否私有,都能继承
- 注:private成员变量虽然能继承,但不可直接使用
- 成员方法:若虚方法表存有该方法,则可以继承,否则不能继承
- 虚方法表存储的方法:非private,非static,非final
继承中成员变量的访问特点
遵循就近原则,先在局部位置找,本类成员位置找,父类成员位置找,逐级往上。
范例
//父类
public class Father{
String name="Father";
}
//子类
public class Son extends Father{
String name="Son";
public void printName(){
String name="printName";
System.out.println(name);//从局部位置开始寻找,打印结果为printName
System.out.println(this.name);//从本类成员位置开始寻找,打印结果为Son
System.out.println(super.name);//从父类成员位置开始寻找,打印结果为Father
}
}
public class Test{
public static void main(String[] args){
Son son=new Son();
son.printName();
}
}
/*
printName
Son
Father
*/
方法的重写
当父类方法不能满足子类现在的需求时,需进行方法重写。
@Override重写注解:@override是放在重写后的方法上,校验子类重写时语法是否正确,建议重写方法都加@Override注解。
方法重写的本质:覆盖虚方法表中的方法。
方法重写的注意事项:
- 重写方法的名称,形参列表需与父类一致
- 父类中私有方法不能被重写
- 子类重写父类方法时,访问权限子类必须大于等于父类
- 子类重写父类方法时,返回值类型子类必须小于等于父类
- 建议:重写的方法尽量和父类一致
- 只有被添加到虚方法表中的方法才能被重写
范例
有三种狗:哈士奇,沙皮狗,中华田园犬
不考虑属性,只考虑行为。
请按照继承的思想特点惊醒继承体系的设计。
行为:
哈士奇:吃饭(吃狗粮),喝水,看家,拆家
沙皮狗:吃饭(吃狗粮,吃骨头),喝水,看家
中华田园犬:吃饭(吃剩饭),喝水,看家
//父类
public class Dog {
public void eat(){
System.out.println("吃狗粮");
}
public void drink(){
System.out.println("喝水");
}
public void lookHome(){
System.out.println("看家");
}
}
//子类
//ChineseDog.java
public class ChineseDog extends Dog {
@Override
public void eat(){
System.out.println("吃剩饭");
}
}
//Husky.java
public class Husky extends Dog {
public void DestroyHome(){
System.out.println("拆家");
}
}
//Sharpei.java
public class Sharpei extends Dog{
@Override
public void eat(){
super.eat();
System.out.println("吃骨头");
}
}
//测试类
//Test.java
public class Test{
public static void main(String[] args){
Husky husky=new Husky();
husky.eat();
husky.drink();
husky.lookHome();
husky.DestroyHome();
System.out.println("=================");
Sharpei sharpei=new Sharpei();
sharpei.eat();
sharpei.drink();
sharpei.lookHome();
System.out.println("=================");
ChineseDog chineseDog=new ChineseDog();
chineseDog.eat();
chineseDog.drink();
chineseDog.lookHome();
}
}
/*
吃狗粮
喝水
看家
拆家
=================
吃狗粮
吃骨头
喝水
看家
=================
吃剩饭
喝水
看家
*/
继承中构造方法的访问特点
- 子类不能继承父类的构造方法,但可通过super调用
- 除了Object类,所有构造方法第一行,都有个隐藏的super()
- 默认先访问父类中无参的构造方法,再执行自己
- 若想访问父类有参构造,需手动书写
this和super
this:理解为一个变量,表当前方法调用者地址值。
super:代表父类存储空间的标识。
对比:
- 访问成员变量
- this.成员变量,访问本类成员变量
- super.成员变量,访问父类成员变量
- 访问成员方法
- this.成员方法(…),访问本类成员方法
- super.成员方法(…),访问父类成员方法
- 访问构造方法
- this();this(…),访问本类其它构造方法
- super();super(…),访问父类构造方法
注:this()和super()都在争夺构造方法第一行的位置,所以二者不可共存。
super省略规则:被调用的变量和方法,在子类中不存在,super.可以直接省略
范例
带有继承结构的标准Javabean类。
- 经理
- 成员变量:工号,姓名,工资,管理奖金
- 成员方法:工作(管理其他人),吃饭(吃米饭)
- 厨师
- 成员变量:工号,姓名,工资
- 成员方法:工作(炒菜),吃饭(吃米饭)
//父类
public class Employee{
private String id;
private String name;
private int salary;
public Employee(){}
public Employee(String id,String name,int salary){
this.id=id;
this.name=name;
this.salary=salary;
}
public void setId(String id){
this.id=id;
}
public String getId(){
return id;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setSalary(int salary){
this.salary=salary;
}
public int getSalary(){
return salary;
}
public void work(){
System.out.println("工作");
}
public void eat(){
System.out.println("吃米饭");
}
}
//经理
public class Manager extends Employee{
private double manageSalary;
public Manager(){}
public Manager(String id,String name,int salary,double manageSalary){
super(id,name,salary);
this.manageSalary=manageSalary;
}
public void setManageSalary(double manageSalary){
this.manageSalary=manageSalary;
}
public double getManageSalary(){
return manageSalary;
}
@Override
public void work(){
System.out.println("管理其他人");
}
}
//厨师
public class Cooker extends Employee{
public Cooker(){}
public Cooker(String id,String name,int salary){
super(id,name,salary);
}
@Override
public void work(){
System.out.println("炒菜");
}
}
//测试类
public class Test{
public static void main(String[] args){
Manager manager=new Manager("manager001","张三",3000,1500.33);
System.out.println("经理工号:"+manager.getId()+" 姓名:"+manager.getName()+
" 工资:"+manager.getSalary()+" 管理奖金:"+manager.getManageSalary());
manager.work();
manager.eat();
System.out.println("======================");
Cooker cooker=new Cooker();
cooker.setId("cooker001");
cooker.setName("李四");
cooker.setSalary(2500);
System.out.println("厨师工号:"+cooker.getId()+" 姓名:"+cooker.getName()+" 工资:"+manager.getSalary());
cooker.work();
cooker.eat();
}
}
/*
经理工号:manager001 姓名:张三 工资:3000 管理奖金:1500.33
管理其他人
吃米饭
======================
厨师工号:cooker001 姓名:李四 工资:3000
炒菜
吃米饭
*/
多态
定义:同类型的对象,表现出的不同形态。
表现形式:
父类类型 对象名称=子类对象;
前提:
- 有继承关系
- 有父类引用指向子类对象或接口类引用指向实现类对象
- 有方法重写
好处:
- 对象多态:使用父类型作为参数,可以接受所有子类对象,体现多态的扩展性和便利。
- 行为多态:同一个方法,具有多种不同表现形式,或形态的能力
特点
- 调用成员变量:编译看左边,运行也看左边
- 编译看左边:javac编译代码时,会看左边的父类是否有这个变量,若有,编译成功,若无,编译失败
- 运行也看左边:java运行代码时,实际获取的就是左边父类中成员变量的值
- 在子类对象中,会把父类的成员变量也继承下来。
- 调用成员方法:编译看左边,运行看右边
- 编译看左边,javac编译代码时,会看左边的父类是否有这个方法,若有,编译成功,若无,编译失败
- 运行看右边:java运行代码时,实际运行的是子类中的方法。
- 若子类对方法进行重写,则虚方法表中是会把父类的方法进行覆盖的。
多态创建对象,调用静态方法:
静态的成员。推荐类名进行调用。若使用对象名调用,在生成字节码文件后,会自动将对象名调用,改成类名调用。
优势与劣势
优势
多态形式下,右边对象可实现解耦合,便于扩展和维护。
定义方法时,使父类型作为参数,可接收所有子类对象,体现多态的扩展性和便利。
劣势
不能调用子类的特有功能,当调用方法时,编译看左边,运行看右边,编译时会先检查左边的父类是否有这个方法,若无则直接报错。
解决方法:
- 可以转换成真正的子类类型,从而调用子类独有功能,转换方式有:
- 自动类型转换,例如:Person p=new Student();
- 强制类型转换:例如:Student s=(Student)p;
- 转换类型与真实对象不一致会报错
- 转换时用instanceof关键字进行判断
- 语法:boolean result=object instanceof class
- 说明:若object是class一个实例,则返回true,若不是或者object是null,则返回false
范例:
//Test.class
public class Test{
public static void main(String[] args){
Person p=new Student();
if(p instanceof Teacher){
Teacher t=(Teacher)p;
t.work();
}else if(p instanceof Student){
Student s=(Student)p;
s.work();
}else{
System.out.println("没有这个类型");
}
}
}
//父类
//Person.class
public class Person{
public void eat(){
System.out.println("吃饭");
}
}
//子类
//Teacher.class
public class Teacher extends Person{
public void work(){
System.out.println("教学");
}
}
//Student.class
public class Student extends Person{
public void work(){
System.out.println("听课");
}
}
范例
需求
根据需求完成代码:
1.定义狗类
属性:
年龄,颜色
行为:
eat(String something)(something表示吃的东西)
看家lookHome方法(无参数)
2.定义猫类
属性:
年龄,颜色
行为:
eat(String something)方法(something表示吃的东西)
逮老鼠catchMouse方法(无参数)
3.定义Person类//饲养员
属性:
姓名,年龄
行为:
keepPet(Dog dog,String something)方法
功能:喂养宠物狗,something表示喂养的东西
行为:
keepPet(Cat cat,String something)方法
功能:喂养宠物猫,something表示喂养的东西
生成空参有参构造,set和get方法
4.定义测试类(完成以下打印效果):
keepPet(Dog dog,String somethind)方法打印内容如下:
年龄为30岁的老王养了一只黑颜色的2岁的狗
2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
keepPet(Cat cat,String somethind)方法打印内容如下:
年龄为25岁的老李养了一只灰颜色的3岁的猫
3岁的灰颜色的猫眯着眼睛侧着头吃鱼
5.思考:
1.Dog和Cat都是Animal的子类,以上案例中针对不同的动物,定义了不同的keepPet方法,过于繁琐,能否简化,并体会简化后的好处?
2.Dog和Cat虽然都是Animal的子类,但是都有其特有方法,能否想办法在keepPet中调用特有方法?
代码
//父类
//Animal.class
public class Animal{
private int age;
private String color;
public Animal(){};
public Animal(int age,String color){
this.age=age;
this.color=color;
}
public void setAge(int age){
this.age=age;
}
public int getAge(){
return age;
}
public void setColor(String color){
this.color=color;
}
public String getColor(){
return color;
}
public void eat(String something){
System.out.println("吃"+something);
}
}
//Person.class
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 keepPet(Animal animal, String something){
if(animal instanceof Dog){
Dog d=(Dog)animal;
System.out.println("年龄为"+age+"的"+name+"养了一只"+d.getColor()+"颜色的"+d.getAge()+"的狗");
d.eat(something);
d.lookHome();
}else if(animal instanceof Cat){
Cat c=(Cat)animal;
System.out.println("年龄为"+age+"的"+name+"养了一只"+c.getColor()+"颜色的"+c.getAge()+"的猫");
c.eat(something);
c.catchMouse();
}else{
System.out.println("没有这种动物");
}
}
}
//子类
//Dog.class
public class Dog extends Animal{
public Dog(){}
public Dog(int age,String color){
super(age,color);
}
@Override
public void eat(String something){
System.out.println(getAge()+"岁的"+getColor()+"颜色的狗两只前腿死死的抱住"+something+"猛吃");
}
public void lookHome(){
System.out.println("看家");
}
}
//Cat.class
public class Cat extends Animal{
public Cat(){}
public Cat(int age,String color){
super(age,color);
}
@Override
public void eat(String something){
System.out.println(getAge()+"岁的"+getColor()+"颜色的猫眯着眼睛侧着头吃"+something);
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
//Test.class
public class Test{
public static void main(String[] args){
Person p1=new Person("老王",30);
Dog d=new Dog(2,"黑");
p1.keepPet(d,"屎");
Person p2=new Person("老李",25);
Cat c=new Cat(3,"灰");
p2.keepPet(c,"老鼠");
}
}
/*
年龄为30的老王养了一只黑颜色的2的狗
2岁的黑颜色的狗两只前腿死死的抱住屎猛吃
看家
年龄为25的老李养了一只灰颜色的3的猫
3岁的灰颜色的猫眯着眼睛侧着头吃老鼠
抓老鼠
*/
包
作用:包就是文件夹,用来管理各种不同功能的Java类。
包名书写规则:公司域名反写.包的作用,全部英文小写,见名知意。例如:
//若域名为www.ljsblog.com,包名为:com.ljsblog.domain1
package com.ljsblog.domain1
全类名:包名.类名。例如:
一个Student类位于com.ljsblog.domain下,则其全类名,com.ljsblog.domain.Student
判断是否需要导包:
- 使用同一包的类时,不需导包
- 使用java.lang包的类时,不需导包
- 其他情况都需导包
- 若同时使用两个包的同名类,需用全类名。
final
- final修饰方法:最终方法,不能被重写
- final修饰的类:最终类,不能被继承
- final修饰的变量:是常量,不能被修改
- 基本数据类型:变量的值不能更改
- 引用数据类型:地址值不能修改,内部属性值可以修改
final修饰成员变量的注意事项:
- final修饰成员变量,不允许修饰默认值
- final修饰成员变量的初始化时机
- 在定义的时候直接赋值
- 在构造方法中完成赋值
final变量修饰变量的命名规范:
- 若变量名是一个单词,所有字母大写
- 若变量名是多个单词,所有字母大写,中间用下划线分割
权限修饰符
定义:用来控制一个成员(成员变量,方法,构造方法,内部类)能够访问的范围的。
分类;由小到大,private < 缺省/默认(空着不写)< protected < public
修饰符 | 同一类中 | 同一个包中其他类 | 不同包下的子类 | 不同包下的无关类 |
---|---|---|---|---|
private | 能 | 不能 | 不能 | 不能 |
默认 | 能 | 能 | 不能 | 不能 |
proteced | 能 | 能 | 能 | 不能 |
public | 能 | 能 | 能 | 能 |
使用规则:实际开发中,一般只用private和public。
- 成员变量私有
- 方法公开
特例:若方法中代码是抽取其他方法中共性代码,这个方法一般也私有。
代码块
分类:
- 局部代码块(已淘汰)
- 构造代码块(已淘汰)
- 静态代码块(重点)
- 同步代码块(牵扯多线程)
局部代码块
位置:方法中的一对大括号。
作用:提前结束变量的生命周期(已淘汰)
构造代码块
位置:类中方法外的一对大括号。
作用:可以把多个构造方法中重复的代码抽取出来。
执行时机:创建对象,执行构造方法时执行构造代码块(先于构造方法执行)
静态代码块
格式:static{}
特点:需通过static关键字修饰,随着类的加载而加载,自动触发,只执行一次。
使用场景:在类加载时,做一些数据初始化时使用。
范例:
package com.ljsblog.domain1;
public class Student{
private int age;
private String name;
static{
System.out.println("静态代码区");
}
public Student(){
System.out.println("无参构造");
}
public Student(int age,String name){
System.out.println("有参构造");
this.age=age;
this.name=name;
}
public void setAge(int age){
this.age=age;
}
public int getAge(){
return age;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
}
package com.ljsblog.domain1;
public class Test {
public static void main(String[] args) {
Student s1=new Student();
Student s2=new Student(18,"张三");
}
}