8.5 多态
一个对象可以有多种形态。一个对象现在可以是学生类的对象,也可是老师类的对象。要形成多态必须是父类对象中存放子类的实例,用父类对象去调用子类重写的方法。
语法:
父类 对象名=new 子类();
例如:
QianFeng stu=new Student();
QianFeng tea=new Teacher();
类图,可以反映出一组类之间的关系
IDEA显示类图:
父类对象是无法直接访问子类中特有的属性和方法的,父类可操作的东西都必须在父类中定义出来。
代码语言:javascript复制package com.qf.start;
import com.qf.oop.QianFeng;
import com.qf.oop.Student;
import com.qf.oop.Teacher;
import java.util.Scanner;
public class Main4 {
public static void main(String[] args) {
//低耦合(两段代码之后的相关性不要太强),高内聚(数据相通,单一职责,一个方法只完成一个功能)
Scanner input = new Scanner(System.in);
System.out.println("老师还是学生");
String type=input.next();
QianFeng qianFeng=null;
if("老师".equals(type)){
//创建老师
qianFeng=new Teacher("张鹏",18,"男",1,"java"); //子类实例赋值给父类对象
}else{
//创建学生
qianFeng=new Student("谢兴灵",18,"男",1,"java2202");
}
//调用重写父类的方法这里会根据子类对象的类型实际去调用具体子类中实现的方法
qianFeng.aTtend();
}
}
代码语言:javascript复制public class QianFeng {
public void aTtend(){
System.out.println("上课");
}
}
public class Student extends QianFeng {
@Override
public void aTtend() {
System.out.println(super.name "正在认真的听课");
}
}
public class Teacher extends QianFeng {
@Override
public void aTtend() {
System.out.println(super.name "正在讲课");
}
}
案例:打印机
代码语言:javascript复制package com.qf.case1;
public class Box {
public void work(){
System.out.println("墨盒开始工作了");
}
}
package com.qf.case1;
public class BwBox extends Box {
@Override
public void work() {
System.out.println("墨盒开始喷黑墨水");
}
}
package com.qf.case1;
public class ColorBox extends Box {
@Override
public void work() {
System.out.println("墨盒开始喷彩色墨水");
}
}
代码语言:javascript复制package com.qf.case1;
public class Paper {
protected double width;
protected double height;
public Paper(double width, double height) {
this.width = width;
this.height = height;
}
public void show(){
System.out.println("显示纸张规格");
}
}
package com.qf.case1;
public class A4 extends Paper {
public A4(){
super(21,29.3); //因为A4纸的规格是全球统一,不需要别人来指定所以直接在创建对象的时候就写死
}
@Override
public void show() {
System.out.println("这是A4纸,宽:" super.width ",高:" super.height);
}
}
package com.qf.case1;
public class B3 extends Paper {
public B3() {
super(35.3, 50);
}
@Override
public void show() {
System.out.println("这是B3纸,宽:" super.width ",高:" super.height);
}
}
代码语言:javascript复制package com.qf.case1;
//打印机类
public class Printer {
//要把盒子装到打印机中来
private Box box;
//要把纸也装到打印机中来
private Paper paper;
public void setBox(Box box) {
this.box = box;
}
public void setPaper(Paper paper) {
this.paper = paper;
}
public void print(){
box.work(); //到时候安装什么盒子就打印什么颜色
paper.show(); //安装什么纸张就显示什么纸张的规格
System.out.println("开始打印内容....");
}
}
代码语言:javascript复制package com.qf.start;
import com.qf.case1.*;
public class Main5 {
public static void main(String[] args) {
//创建打印机
Printer printer=new Printer();
//父类对象存放子类实例,彩色打印机的实例
Box box=new ColorBox();
//父类对象存放子类实例,A4实例
Paper paper=new A4();
printer.setBox(box);//安装一个彩色盒子进打印机
printer.setPaper(paper);//安装A4纸进打印机
printer.print();//根据不同的盒子和纸张进行打印内容
}
}
8.6 装箱拆箱
代码语言:javascript复制QianFeng qianFeng=new Student();
这就是装箱操作,就是将子类的实例存入父类对象中。
拆箱的语法
子类 对象名=(子类)父类对象;
代码语言:javascript复制package com.qf.start;
import com.qf.oop.QianFeng;
import com.qf.oop.Student;
public class Main6 {
public static void main(String[] args) {
//装箱
QianFeng qianFeng=new Student();
qianFeng.setId(1);
qianFeng.setName("张鹏");
qianFeng.setGender("男");
qianFeng.setAge(25);
//拆箱 将父类对象强转回子类对象
Student stu=(Student)qianFeng;
stu.setClazz("java2202");
qianFeng.show();
System.out.println("================================");
stu.show();
}
}
使用多态就要装箱,如果紧急情况下要对子类里的东西进行调用的时候就必须要拆箱。
instanceof 判断当前父类对象是不是某一个子类的类型,在拆箱前先进行类型的判断,不要拆错了
案例:
代码语言:javascript复制if(box instanceof BwBox) {
BwBox bwBox = (BwBox) box;
}
8.7 抽象方法和抽象类
abstract 当你在父类定义一个方法时,这个方法你也不知道到底怎么去实现它,因为具体实现它的代码要写子类里,面对这种情况的时候这样的方法就用抽象方法。一个类只要有一个抽象方法整个类必须变成抽象类。
抽象类的语法:
public abstract class 类名{
}
抽象方法的语法:
public abstract 返回值 方法名(参数);
代码语言:javascript复制package com.qf.case1;
public abstract class Box {
public abstract void work();
}
代码语言:javascript复制package com.qf.case1;
public abstract class Paper {
protected double width;
protected double height;
public Paper(double width, double height) {
this.width = width;
this.height = height;
}
public abstract void show();
}
子类继承的是抽象类,子类必须实现父类中的抽象方法,不然就和上图一样报错。
抽象类不能new , 不能实例化,抽象类必须用子类来实例化
8.8 static 静态
static可以修饰属性和方法。被static修饰的属性和方法被称之为静态属性和静态方法。
成员变量和成员方法在创建对象的时候才会被加载,加载到对象中去。对象名.属性,对象名.方法名()去调用。
用static修饰的内容,会和类同时加载。所以static修饰的属性和方法会早于成员属性和方法进行加载。
代码语言:javascript复制一个类文件中方法之间的调用 1、static方法不可以在内部调用成员方法,成员属性,但是成员方法可以在内部调用静态方法,静态属性 2、static方法可以在内部调用static方法 3、成员方法可以在内部调用成员方法
package com.qf.start;
public class Main7 {
public static void main(String[] args) {
//main是一个静态方法,helloWorld是一个成员方法,所以不能直接调用,需要创建对象来调用
Main7 m7=new Main7();
m7.helloWorld();
}
public void helloWorld(){
helloChongQing();//普通的成员方法,在成员方法内部可以调用另一个成员方法
System.out.println("成员helloWorld方法:HelloWorld");
}
public void helloChongQing(){
hello(); //静态方法 成员方法内部可以调用静态方法
System.out.println("你好重庆");
}
public static void hello(){
System.out.println("静态hello方法:你好");
}
}
代码语言:javascript复制静态方法在不同类文件中的调用方式: 1、 类名.方法名();
package com.qf.case2;
public class Area {
public static double clac(double r,double pi){
return r*r*pi;
}
}
package com.qf.start;
import com.qf.case2.Area;
public class Main8 {
public static void main(String[] args) {
double clac = Area.clac(3, 3.14);
System.out.println(clac);
}
}
手机: 打电话发短信 (每一部手机都必须有的功能,从娘胎里带出来的)这种就从生产手机开始就自带就属于静态的内容,后面安装的QQ,微信,游戏都是手机买过来以后自己安装,这里就属性成员方法
静态属性:
代码语言:javascript复制package com.qf.case2;
public class Area {
public static double pi; //定义出静态属性
public static double clac(double r){
return r*r*pi; //如果pi是一个成员属性,这里是调用不到的
}
}
代码语言:javascript复制package com.qf.start;
import com.qf.case2.Area;
public class Main8 {
public static void main(String[] args) {
Area.pi=3.1415926; //为静态属性赋值
double clac = Area.clac(3); // 调用静态方法
System.out.println(clac);
}
}
强调:静态的内容一定是跟类走,与对象无关.
代码语言:javascript复制package com.qf.start;
import com.qf.case2.Area;
public class Main8 {
public static void main(String[] args) {
Area area1=new Area();
area1.pi=3.1415926; //对象是可以调用静态内容的,只是工具没提示,没提示代表不建议你这么使用 这里为静态pi设置3.1415926
double clac = area1.clac(3);
System.out.println("第一个对象调用静态的方法");
System.out.println(clac);
Area area2=new Area();
System.out.println("第二个对象调用静态的方法");
System.out.println(area2.clac(3)); //第二个对象调用求面积的静态方法,但是没有设置pi的值
System.out.println("类调用静态的方法");
Area.pi=3.1415;//类名.属性名
System.out.println(Area.clac(3));//使用类调用静态方法,也没有设置pi的值
}
}
结果:
第一个对象调用静态的方法
28.2743334
第二个对象调用静态的方法
28.2743334
类调用静态的方法
28.2743334
由此可见,静态的内容一定是跟类走,与对象无关,只要有一个地方设置了静态属性的值,其他地方通用。
静态属性的调用:
类名.属性名
JVM的内存模型图
方法区
1、方法区中保存着,类、静态变量、静态方法、常量、普通方法
2、方法区是线程共享的;当有多个线程都用到一个类的时候,而这个类还未被加载,则应该只有一个线程去加载类,让其他线程等待;
3、方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。jvm也可以允许用户和程序指定方法区的初始大小,最小和最大限制;
4、方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展Java程序,这样可能会导致一些类,不再被使用,变为垃圾。这时候需要进行垃圾清理。
Student stu=new Student(); 465
stu=new Student(); 466 这样以后465将不可再用,就会成为垃圾,成为垃圾以后就会被gc回收
栈:
栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。
堆内存
对象的引用存储在栈区,但是对象实体存放在堆内存中
- 静态域
//静态域,是最先执行的地方 类加载的时候自动执行
static {
//这块区域一般也是做对静态变量的初始化操作
//配合配置文件,去读文件中配置的内容来初始化静态变量
pi=3.141592;
}
//构造区,创建对象的时候才会执行
{
System.out.println("构造区,创建对象时自动执行");
}
8.9 final 最终
final 最终的意思,它可以修饰在类,方法和属性,局部变量上
用final修饰的类,这个类不能被继承,final修饰的是类,类中的方法要自动拥有final的特性。
用final修饰的方法,这个方法不能被重写
final修饰的方法只是不能被重写,并不不能调用和不能继承
用final修饰的属性或变量上,这个属性或变量的值不能被修改
代码语言:javascript复制package com.qf.case2;
public class Area {
//public final static double pi=3.141592;不报错
//这样也不报错
static {
pi=3.141592;
}
public final static double pi;
public static double clac(double r){
return r*r*pi;
}
}
要在一个类真正加载完成之前对一个终态的静态变量赋值就可以.
以上代码在final修饰变量之后,没有直接赋值,但是也没有报错,那是因为,在构造方法和静态域,在这里面进行赋值操作。
final要求,final修饰的变量在一个对象真正实例化赋值前进行最终变量的赋值就可以。如果在定义的时候不给值就一定要把终态变量用构造方法进行赋值。
局部变量上使用final。
代码语言:javascript复制package com.qf.start;
import com.qf.case3.People;
import com.qf.case3.Student;
public class Main9 {
public static void main(String[] args) {
final int num=10;
final People people=new People("张鹏",25);
people.show();
//num=20; 因为不让修改值所以报错
people.setName("谢兴灵"); //可以修改成功是因为final修饰的对象只是不允许重新new,而没有不允许修改属性值
people.setAge(30);
people.show();
}
}
结果:
张鹏------------25
谢兴灵------------30
修改people原本的引用地址了,所以不允许。
用final修饰的变量称为常量,因为它不可修改。比如pi(圆周率)这变量就应该使用final来修饰,一天24小时,这种固定数据都要用final来修饰,使用final修饰的变量称为常量,一般使用大写字母来代表与普通变量进行区分。