目录
final关键字
作用
用于修饰类、变量、常量、方法
注意
1.final修饰类 为最终类 ,则不能给子类继承
2.final修饰方法,则该方法不能被重写
3.final修饰变量,要先进行赋值,赋值后该变量的值不能被修改
4.类型为基本数据类型,赋值后该变量的值不能被修改
5.类型为引用类型,则不能赋值一个新的对象,但是可以修改对象中成员变量的数值
6.final修饰局部变量,可以先不初始化,但是赋值后该变量的值不能被修改
7.使用final修饰成员变量,(包括静态/非静态)则需要先初始化(否则会编译出错),则变量值是不能被修改的
多态
定义
在Java中多态指的是行为的多态性
如何产生
多态是由于编译时类型和运行时类型不一致而出现的情况
编译时类型是引用变量的类型
运行时类型是new出来的对象类型
// --当在编译时,编译的语法校验是按照编译时的类型来决定
// --当运行时,调用方法时本质是调用运行时类型的方法
//编译时类型 运行时类型
Person p = new Doctor();
// 当引用变量调用该方法时,则显示出子类的行为特征
p.eat();
//由于p编译时类型是Person,所以在编译期间没有能在Person类中找到work方法,编译出错
p.work();//The method work() is undefined for the type Person
多态出现前提
1.父类的引用指向子类的对象
2.子类要重写父类方法
结果:当引用变量调用方法会出现行为的多态,显示子类的行为特征。
注意:变量是没有多态性的
练习
练习1
- -宠物商店,有寄养宠物的方法
- -宠物Pet,有喂食的方法 - - 猫类- - 狗类
public class PetShop {
// 寄养宠物的方法[宠物]
public void adoptPet(Pet pet) {//父类的引用 指向 子类对象
pet.feed();//当调用方法时,显示子类的行为特征
}
}
//宠物的父类
public class Pet {
// 有喂食的方法
public void feed() {
System.out.println("喂食宠物。");
}
}
//不同宠物的类
class Cat extends Pet {
public void feed() {
System.out.println("喂食小鱼干。");
}
}
class Dog extends Pet {
public void feed() {
System.out.println("喂食骨头。");
}
}
class Raby extends Pet {
public void feed() {
System.out.println("喂食萝卜。");
}
}
//测试类
public class PetMain {
public static void main(String[] args) {
// 宠物商店寄养宠物
PetShop shop = new PetShop();
Dog dog = new Dog();
Cat cat = new Cat();
shop.adoptPet(cat);//输出喂食小鱼干
}
}
练习2
--ElectronicShop电子维修店,有维修电子设备方法repair(),repair调用设备中的openAndCheck方法
public class ElectronicShop {
// 有维修电子设备方法repair(),repair调用设备中的openAndCheck方法
public void repair(ElectronicEquipment equipment) {
equipment.openAndCheck();// 检测维修
}
//编写测试代码
public static void main(String[] args) {
//电子设备维修店维修手机
ElectronicShop shop = new ElectronicShop();
shop.repair(new MobilePhone("华为", "重庆", "麒麟"));
shop.repair(new Flat("苹果", "深圳", "M1"));
}
}
--电子设备ElectronicEquipment
拥有属性:name[名称]\produtionAddress[生产点]\cpu[处理器]
拥有方法:openAndCheck();方法【输出name正在启动,输出设备的信息】
电子设备子类:--手机MobilePhone 重写openAndCheck();方法 --平板Flat 重写openAndCheck();方法 --电脑Computer 重写openAndCheck();方法
public class ElectronicEquipment {
// name[名称]\produtionAddress[生产点]\cpu[处理器]
public String name;
public String produtionAddress;
public String cpu;
// :openAndCheck();方法【输出name正在启动,输出设备的信息】
public void openAndCheck() {
System.out.println(name + "正在启动,设备的地址信息:" + produtionAddress + " cpu:" + cpu);
}
//构造器,有参与无参
public ElectronicEquipment(String name, String produtionAddress, String cpu) {
super();
this.name = name;
this.produtionAddress = produtionAddress;
this.cpu = cpu;
}
public ElectronicEquipment() {
super();
}
}
class MobilePhone extends ElectronicEquipment {
public void openAndCheck() {
System.out.println("正在启动手机,准备进行检测" + name + "设备的地址信息:" + produtionAddress + " cpu:" + cpu);
}
public MobilePhone(String name, String produtionAddress, String cpu) {
super();
this.name = name;
this.produtionAddress = produtionAddress;
this.cpu = cpu;
}
public MobilePhone() {
super();
}
}
class Flat extends ElectronicEquipment {
public void openAndCheck() {
System.out.println("正在启动平板,准备进行检测" + name + "设备的地址信息:" + produtionAddress + " cpu:" + cpu);
}
public Flat(String name, String produtionAddress, String cpu) {
super();
this.name = name;
this.produtionAddress = produtionAddress;
this.cpu = cpu;
}
public Flat() {
super();
}
}
class Computer extends ElectronicEquipment {
public void openAndCheck() {
System.out.println("正在启动电脑,准备进行检测" + name + "设备的地址信息:" + produtionAddress + " cpu:" + cpu);
}
public Computer(String name, String produtionAddress, String cpu) {
super();
this.name = name;
this.produtionAddress = produtionAddress;
this.cpu = cpu;
}
public Computer() {
super();
}
}
引用类型的转换
向上转型
把子类赋值给父类,子类型可以赋值给父类的引用变量
向下转型
把父类赋值给子类,当引用变量进行向下转换时,要注意类型是否兼容,可以通过instanceof关键字进行判别。
报错提醒:com.day0120.Doctor cannot be cast to com.day0120.Student【类型不兼容】ClassCastException
instanceof关键字
- - 判断变量中的对象是否为某个类或子类的对象【判断变量中存储的对象类型】
instanceof后面的类型要和前面变量的编译时类型要继承关系【子类、父类、本类】
Person p1 = new Doctor();//person是doctor和student的父类。
//instanceof关键字【判断变量中存储的对象类型】
//instanceof后面的类型要和前面变量的编译时类型要继承关系【子类、父类、本类】
if (p1 instanceof Student) {//判断为false
System.out.println("是Student");
Student d = (Student) p1;
d.eat();
}
if (p1 instanceof Doctor) {//判断为true
System.out.println("是Doctor");
Doctor d = (Doctor) p1;
d.eat();
}//结果输出:是Doctor Doctor 吃饭
编译时类型与运行时类型
Person p = new Student()
引用变量定义的类型Person 为编译时类型
new 出来的对象类型Student 为运行时类型
1、方法调用时要注意是按照编译时类型来决定(编译时类型是决定你编程时是否会出现编译错误【语法】)
2、在运行程序时,是通过运行时类型来决定是否出错(对象转换时,对象类型是否兼容---即可用instanceof判断)
举例:动物里面有狗 、 猫;狗不能赋值给猫
静态类型
- -根据入参变量的编译时类型决定调用哪个方法
(即谁入参到这个方法就根据谁的编译时类型来决定)
//静态类型:确定在同一个类中,调用了哪个方法
public class StaticTest {
//在同一个类中,如何调用哪个方法,根据方法签名【根据入参的变量的编译时类型】
public void test(A a) {
System.out.println("test(A a)-----------------");
}
public void test(B b) {
System.out.println("test(B b)-----------------");
}
public void test(C c) {
System.out.println("test(C c)-----------------");
}
//main
public static void main(String[] args) {
//
StaticTest test = new StaticTest();
//
A a = new C();
B b = new C();
//在同一个类中,调用哪个方法在编译时已经确定了【方法签名】【入参的数据编译时类型】
test.test(a);// A a \入参为A类型,即匹配 test(A a)
test.test(b);// B b \入参为B类型,即匹配 test(B b)
}
}
class A {
}
class B extends A {
}
class C extends B {
}
动态类型
- -根据调用方法的引用变量的运行时类型决定调用哪个类
(即谁调用了这个方法就根据谁的运行时类型来决定)
public class DynamicTest {
//1、确定调用哪个类【调用方法的变量的运行时类型确定】
//2、确定调用哪个方法【入参的变量的编译时类型确定】
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
a.test(b);// a--b \a为A类型,其中b为b方法
A a_c = new C();
a_c.test(b);// c--b \a_c为C类型,b为b方法
B b_c = new C();
b_c.test(a);// c--a \b_c为C类型,a为a方法
//
a_c = a;
b = b_c;
b.test(a_c);// c--a \b为b_c也即C类型,a_c为a方法
b_c.test(b_c);//c--b \b_c为c类型,b_c为b方法
}
}
class A {
public void test(A a) {
System.out.println("A ---------- a");}
public void test(B b) {
System.out.println("A ---------- b ");}
public void test(C c) {
System.out.println("A ---------- c");}}
class B extends A {
public void test(A a) {
System.out.println("B ---------- a");}
public void test(B b) {
System.out.println("B ---------- b ");}
public void test(C c) {
System.out.println("B ---------- c");}}
class C extends B {
public void test(A a) {
System.out.println("C ---------- a");}
public void test(B b) {
System.out.println("C ---------- b ");}
public void test(C c) {
System.out.println("C---------- c");}}
单例模式
定义
用于限定当前类只能生成一个实例对象,则这个类称为单例类。保证类在内存中只有一个对象的存在。
类别
创建步骤
1、设置构造器为私有化 不允许外部类来创建对象
2、提供一个静态的公有化的方法来获取唯一的对象
3、判断laz是否为空,目的是确保对象是唯一的
懒汉式
天生非线权安全,只有用到了才会new一个对象
//单例类:懒汉式【在用的时候才创建】
public class SingleLaz {
// 3、定义一个变量用于存放单例的对象
static SingleLaz laz = null;
// 1、将构造器进行私有化
private SingleLaz() {
}
// 2、提供一个静态方法来获取单例的对象
public static SingleLaz getInstance() {
//如果对象为null,则创建唯一的对象
if(laz == null) {
laz = new SingleLaz();
}
return laz;
}
}
饿汉式
饿汉式天生属于线程安全,一开始就会new一个对象占内存。
//单例类:饿汉式【一开始的时候就创建了】
public class SingleHug {
// 3、定义一个变量用于存放单例的对象
final static SingleHug laz = new SingleHug();
// 1、将构造器进行私有化
private SingleHug() {
}
// 2、提供一个静态方法来获取单例的对象
public static SingleHug getInstance() {
//返回唯一的对象
return laz;
}
}
使用场景
【多线程、频繁操作、资源消耗大】
1、在多线程的情况下,生成唯一的序列号 【确保只有唯一的生成器】
2、IO流操作、频繁的创建新的对象会造成性能损耗,固定唯一的io对象
3、数据库链接、获取唯一的数据库链接对象
初始化以及初始化块
初始化块的作用
用于在类加载或者创建对象时,可以设置一些固定的初始化操作(初始化成员变量、执行其他类的加载)
静态与非静态初始化
区别
静态初始化块只执行一次(类加载时),非静态初始化块每创建一个对象就执行一次。
顺序
静态初始化块-->非静态初始化块-->构造器
初始化在继承链中顺序
- -父类静态初始化块-->子类静态初始化块-->父类非静态初始化块-->父类构造器-->子类非静态初始化块-->子类构造器
抽象类
定义
抽象类:模板[给子类继承](普通类能定义的东西,抽象类都可以定义)
抽象方法
不知道具体实现的方法,是没有方法体的,使用abstract修饰。
Person -->eat(){System.out.println("人吃饭");}【普通类】
Person -->eat();【抽象类】//以;结尾
抽象类
抽象类的作用是作为一个模板给子类继承。
特点、注意事项
1.抽象类本身是不能被创建对象的【实例化的】即不能new创建对象,但是可以将子类的对象赋值给抽象类的引用变量。
People p = new People();//不能这样,抽象类不可以实例化!
People p = new Doctor();//doctor为people的子类对象
p.eat();
2.子类继承抽象类,如果子类不实现抽象方法,则该类必须为抽象类。
注意:抽象类可以继承抽象类、可以不实现抽象方法
为什么要用抽象类
1、抽象类本身是一种概念
2、作为一个模板给子类继承,让子类必须实现抽象方法
使用场景
如果不需要子类重写的方法,则使用普通类继承即可;但是如果要求子类一定要实现方法,则需要使用抽象类,让子类继承并重写方法的实现。
练习
多边形抽象类,定义求取周长和面积的抽象方法
子类如下:--四边形--三角形
public abstract class Polygon {//多边形
public abstract double getArea();// 面积
public abstract double getPerimeter() ; // 周长
}
public class Rectangle extends Polygon {//四边形
public double length;//长
public double width;//宽
@Override
public double getArea() {
return length * width;
}
@Override
public double getPerimeter() {
return (length + width) * 2;
}
public Rectangle(double length, double width) {//构造器
super();
this.length = length;
this.width = width;
}
public Rectangle() {
super();
}
}
//三角形
public class Triangle extends Polygon {
double bottom;
double height;
double side1;
double side2;
double side3;
@Override
public double getArea() {//子类不为
return (bottom * height) / 2.0;// (底*高)/2
}
@Override
public double getPerimeter() {
return side1 + side2 + side3;
}
//全参构造器
public Triangle(double bottom, double height, double side1, double side2, double side3) {
super();
this.bottom = bottom;
this.height = height;
this.side1 = side1;
this.side2 = side2;
this.side3 = side3;
}
public Triangle() {
super();
}
}
public class PolygonMain {//测试类
public static void main(String[] args) {
Polygon p = new Triangle(10, 5, 10, 10, 10);
System.out.println(p.getArea());
System.out.println(p.getPerimeter());
}
}
接口
定义
接口就是特殊的抽象类,接口类是作为一种规范;接口就是用于定义抽象方法(定义规范)
接口的作用是解耦[定义和实现分离]
使用场景?一般情况下都是使用接口
注意事项
1.不能定义变量、普通方法、初始化块、构造器;
2.可以定义默认方法、常量
//不能定义普通实现的方法\但是可以定义默认方法
public void demo() {//这种为普通方法,不能如此定义
System.out.println("sdfdsf");
}
//默认方法
default void test() {//在接口中,default 表示默认,和修饰符中的default作用不同
System.out.println("sdfdsf");
}
3.抽象方法默认是使用public abstarct修饰的
// 在接口中也能定义抽象方法 \默认 public abstract
public abstract void devilyData();//即该语句与 void devilyData(); 作用相同
4.常量默认是使用public final static修饰的
5.接口不能被实例化的
6.接口中可以定义默认方法,但是一定要使用default关键字修饰
7.接口可以多实现(一个类可以实现多个接口)
抽象类与接口关系
区别
抽象类是作为一个模板
- 可以定义抽象方法,并且要求子类必须要重写实现。
- 可以定义变量,普通方法,构造器,常量,初始化块。
接口作为一个规范
- 可以定义抽象方法
- 不可以定义常量,普通方法,构造器,初始化块。
- 只能定义常量和默认方法
使用场景
要给子类继承共通性的属性以及方法时(有东西继承的)用抽象类,不用给子类继承东西 的用接口。原因:java只能单继承,但接口可以多实现(解耦)。
练习
分析题意:类(共有三个类,警局(普通类--实现传话器类),警察(抽象类),传话器(接口))-->属性-->方法-->测试
//传话器Phone【接口】
public interface Phone {
// 报告的抽象方法
void report(String msg);
}
//警局PoliceHost【普通类】
public class PoliceHost implements Phone {
@Override
public void report(String msg) {
System.out.println(msg);
}
// 派遣出警
public void detach() {
// 1、新建一个警察对象
Police gao = new Gao();
//给高警官设置传话器
gao.setPhone(this);//new Phone()[不能实例化接口];-->new PoliceHost()[只能实例化子类] --> this【相当于PoliceHost的对象】
// 2、输出一点信息
System.out.println("派遣高警官出镜");
// 3、侦查
gao.scout();
}
}
//警官Police【抽象类】
public abstract class Police {
public Phone phone; // 传话器
public abstract void scout();// 侦查的抽象方法
public Phone getPhone() {//get,set方法
return phone;
}
public void setPhone(Phone phone) {
this.phone = phone;
}
}
// 李警官
class Li extends Police {
@Override
public void scout() {
System.out.println("李警官脚踏哈雷,火速到达现场进行侦查");
phone.report("李警官反馈:现场惨不忍睹!重要线索丢失!");// 侦查结束,反馈情况
}
}
// 高警官
class Gao extends Police {
@Override
public void scout() {
System.out.println("高警官搭乘喷气机,火速到达现场进行侦查");
phone.report("高警官反馈:现场留下了蛛丝马迹!继续侦查!");// 侦查结束,反馈情况
}
}
public class PoliceTest {
public static void main(String[] args) {
PoliceHost host = new PoliceHost(); // 警局派遣出警,高警官前去现场侦查
host.detach();
}
}
回调方法
解释:把A【警局】入参给B【警官】,B再调用A的方法,这个过程就叫做回调方法
举例:把警局对象(implements 于phone接口,即传话机)赋值给警官,警官回调警局(传话机)中的report方法。
警官调用report方法的操作称为方法的回调。
模板方法类型
定义
用于作为一个模板给子类继承、进行扩展实现(抽象类、接口实现)属性-功能模板
例子
//这是悍马的生产模型
public abstract class HummerModel {
// 模板方法
public abstract void start();
public abstract void stop();
public abstract void alarm();
public abstract void engineBoom();
public abstract void run();
}
//根据模板生产了一个车型
public class HummerM1 extends HummerModel {
@Override
public void start() {
System.out.println("HummerM1 start..");}
@Override
public void stop() {
System.out.println("HummerM1 stop..");}
@Override
public void alarm() {
System.out.println("HummerM1 alarm..");}
@Override
public void engineBoom() {
System.out.println("HummerM1 engineBoom..");}
@Override
public void run() {
System.out.println("HummerM1 run..");}
}
public class HummerMain {//测试类
public static void main(String[] args) {
HummerModel m1 = new HummerM1();//父类的引用指向子类对象
m1.start();//调用子类的实现操作,体现不同的行为特征
}
}
工厂模式
用于定义一个工厂类来生产某个东西。(工厂方法要静态)
在多个类中,同样都使用某个类型时,用工厂类来生产这个类型。后期进行业务修改与更新换代,只用改工厂类。(实现解耦)
- 在下面场景中,这个类型即为output类。
场景解释
有一个场景:假设程序中有个Computer类需要组合一个输出设备,现在有两个选择:直接让Computer类组合一个Printer,或者让 Computer类组合一个Output,那么到底采用哪种方式更好呢?
假设让Computer类组合一个Printer对象,如果有一天系统需要重构,需要使用BetterPrinter来代替Printer,这就需要打开Computer类源代码进行修改。如果系统中只有一个Computer类组合了Printer还好,但如果系统中有100个类组合了Printer,甚至1000个、10000个……将意味着需要打开100个、1000个、10000个类进行修改,这是多么大的工作量啊!
为了避免这个问题,工厂模式建议让 Computer类组合一个Output类型的对象,将Computer类与Printer类完全分离。Computer对象实际组合的是 Printer对象还是BetterPrinter对象,对Computer而言完全透明。当Printer对象切换到BetterPrinter对象时,系统完全不受影响。下面是这个Computer类的定义代码。
//电脑类
public class Computer {
// 组合了一个输出设备
// public Output output;
//注释掉的代码是用Computer类组合一个Printer
/*public Output getOutput() {
return output;}
public void setOutput(Output output) {
this.output = output;}*/
// 打印信息的方法
public void printData(String msg) {
OutputFactory.getOutput("led").show(msg);
//效果与下面同
}
/* public Output output = OutputFactory.getOutput();
public void printData(String msg) {
output.show(msg);
}*/
}
class Dell extends Computer {
// 组合了一个输出设备
// public Output output;
/*public void setOutput(Output output) {
this.output = output;}*/
// 打印信息的方法
public void printData(String msg) {
OutputFactory.getOutput("Output").show(msg);
}
}
//输出设备[电子显示屏]
public class Output {
// 显示信息的方法
public void show(String msg) {
System.out.println("显示屏显示信息:" + msg);
}
}
class LedOutput extends Output{
// 显示信息的方法
public void show(String msg) {
System.out.println("LED屏显示信息:" + msg);
}
}
//输出设备的生产--工厂类
public class OutputFactory {
//生产的方法
public static Output getOutput(String type) {//return的是output对象即返回的是输出设备
//根据类型来生产对象
if(type.equals("led")) {
return new LedOutput();
}else {
return new Output();
}
}
}
内部类
定义
类中包含类,目的就为了更好隐藏类的细节【链表-节点】(内部类可以继承和实现接口)
分类
非静态内部类
1、由于非静态内部类是外部类的成员所以可以使用public、protected、private、static修饰类
2、在非静态内部类中只能定义静态常量,不能定义静态变量
3、创建非静态内部类时,要通过外部类的对象来调用内部类的构造器
Clothes c = new Clothes();//创建非静态内部类时,要通过外部类的对象来调用内部类的构造器
Button b = c.new Button();//纽扣是衣服的非静态内部类
b.test();
4、非静态内部类可以访问外部类的属性和方法,包括私有化的
5、外部类中访问不了非静态内部类的非静态的属性、可以访问静态常量
6、外部类中只能使用public、abstract、final
7、内部类和外部类具有同名的属性时,按照最近原则调用
静态内部类(static)
1、静态内部类中可以定义静态的属性
2、静态内部类中不可以调用外部类的非静态属性和方法
3、通过外部类型,引用静态内部类的构造器来创建对象
//通过外部类型,引用静态内部类的构造器来创建对象
Collar collar = new Clothes.Collar();//衣领是衣服的静态内部类
collar.demo_test();
匿名内部类
实现了某个接口或抽象类,且没有类名的内部类【比如:(一次性)线程的应用,函数式接口、事件监听操作】【结合抽象类和接口使用】
特点
1.只使用一次(一次性的)
2.不需要定义一个Java文件
3.没有名字的、所以称为匿名
public interface Phone {
// 报告的抽象方法
void report(String msg);
}
public class PhoneMain {
public static void main(String[] args) {
//匿名内部类:一次性【线程中】、函数式接口、事件监听操作
//表达式右边的操作,实际上是创建一个实现了Phone接口的内部类的对象
Phone p = new Phone() {
@Override
public void report(String msg) {
System.out.println("收到数据:"+msg);
}
};
p.report("nihao ");
//举例:线程(一次性操作)
Thread t = new Thread(
new Runnable() {
@Override
public void run() {
//
System.out.println("买汉堡!");
}
});
t.start();
}
}