1.多态
- 多态的理解
(1)同一个动作作用于不同的对象产生的不同的行为,比如不同子类对父类的不同的重写。
(2)多态就是一个对象的多种形态。 - 多态的体现
(1)基于继承的实现,不同子类重写了父类方法之后体现不同的形式。
(2)接口的实现。 - 形成多态的条件
(1)继承:子类去继承父类。
(2)重写:子类重写父类的方法。
(3)重载:同一个方法名,形参列表不同,实现的功能也不同。
(4)子类对象的多态性:父类的引用指向子类的实例。 - 程序分为两种状态,一种是编译时状态,一种是运行时状态。
举例:Pet p1 = new Dog(“泰迪”, “小泰”);
对于多态来说,编译时看左边,会把p1看作是宠物类对象,运行时看右边,堆中实际存放的类型是Dog类型。 - 向上转型和向下转型
(1)向上转型(小转大):将子类赋值给父类的引用,可以自动类型转换,例如Pet p1 = new Dog(“泰迪”, “小泰”);
(2)向下转型(大转小):将父类转换为子类,需要强制类型转换,例如Dog dog1 = (Dog) p1; - 多态的类型
(1)编译时(看左边)多态:方法重载(在编译期间,调用相同的方法名,根据不同的参数列表来确定调用的是哪个方法)。
(2)运行时(看右边)多态:方法的重写(只有在运行期间才确定使用的对象类型,才能确定变量的引用指向哪哪种对象的实例)。 - 补充instanceof运算符
该运算符用来判断一个对象是否属于一个类或者实现了一个接口,返回true挥着false。在强制类型转换之前,通过此运算符检查对象的真实类型,可以避免类型转换异常,从而提高代码健壮性。
这么说太抽象,用代码来说明一下👇
Pet父类
public class Pet {
private String name;
protected int health = 100;//健康值
public Pet() {
}
public Pet(String name) {
this.name = name;
}
public Pet(int health) {
this.health = health;
}
public Pet(String name, int health) {
this.name = name;
this.health = health;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
//宠物吃食
public void eat() {
System.out.println("宠物吃食");
}
//描述宠物信息
public void info() {
System.out.println("我的昵称是" + this.name + ",我的健康值是" + this.health);
}
}
Dog子类
public class Dog extends Pet {
//狗勾的有的属性
public String stain;//品种
//无参构造方法
public Dog() {
}
//两个参数的构造方法,其中name继承自父类
public Dog(String stain, String name) {
super(name);
this.stain = stain;
}
public String getStain() {
return stain;
}
public void setStain(String stain) {
this.stain = stain;
}
//重写宠物吃食的方法
public void eat() {
super.health = super.health + 3;
System.out.println("狗勾吃饱了,健康值加3");
}
//重写宠物info的方法
public void info() {
super.info();
System.out.println("我的品种是" + this.stain);
}
//Dog类独有的方法
public void sleep() {
System.out.println("狗勾在呼呼呼的大睡");
}
}
Penguin子类
public class Penguin extends Pet {
//企鹅独有的属性
private int age;
//无参构造方法
public Penguin() {
}
//两个参数的构造方法,其中name继承自父类
public Penguin(int age, String name,) {
super(name);
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//重写宠物吃食的方法
public void eat() {
super.health = super.health + 5;
System.out.println("企鹅🐧吃饱了,健康值加5");
}
重写宠物info的方法
public void info() {
super.info();
System.out.println("我的年龄是" + this.age);
}
//Penguin类独有的方法
public void swimming(){
System.out.println("企鹅🐧在游泳");
}
}
Master主人类
public class Master {
private String hostName;//主人姓名
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public Master() {
}
public Master(String hostName) {
this.hostName = hostName;
}
//给狗🐕喂食
public void feed(Dog dog) {
dog.eat();
}
//给企鹅🐧喂食
public void feed(Penguin penguin) {
penguin.eat();
}
}
TestMaster测试类
public class TestMaster {
public static void main(String[] args) {
//创建一个宠物类
Pet p = new Pet("开心");
p.eat();
p.info();
Dog dog = new Dog("金毛", "小金");
dog.info();
Penguin pg = new Penguin(12, "小鹅");
pg.info();//不同子类对父类的不同的重写
Master m = new Master();
m.feed(dog);
m.feed(pg);
System.out.println("==========================子类对象的多态性");
//子类对象的多态性:父类的引用指向子类的实例
//这时候p1只能调用重写的方法(父类的方法)
Pet p1 = new Dog("泰迪", "小泰");
p1.info();//调用Dog重写的info()方法
//p1可不可以调用狗类独有的方法? 不可以。
//编译期间,程序会把p1看成是Pet对象,而Pet类里没有sleep()方法
//现在就想用p1调用sleep方法怎么做?进行向下转型强制类型转换
Dog dog1 = (Dog) p1;//向下转型(大转小)
dog1.sleep();
//Penguin pg1 = (Penguin) p1;//p1已经转成了Dog类型,不能再转成Penguin
//使用instanceof检查p1类型,然后做相应的强制类型转换
if (p1 instanceof Dog) {
Dog dog2 = (Dog) p1;
dog2.sleep();
} else if (p1 instanceof Penguin) {
Penguin pg1 = (Penguin) p1;
pg1.swimming();
}
}
Pet p1 = new Dog(“泰迪”, “小泰”);其实是一种向下转型,这时候子类p1只能调用父类的方法,如果子类重写了,那么就调用子类重写后的方法,不能调用子类独有的方法。
那问题来了,怎么才能让p1调用独有的方法呢,这就得就得通过向下转型,Dog dog1 = (Dog) p1;将p1强制类型转换为Dog类型,然后就可以调用Dog独有的方法,但是不能调用父类的方法。
2.final
final可以用来修饰类、方法、成员变量和局部变量。
- final修饰类,表示这个类是最终类,不能被继承。
父类
public final class Person {
}
子类在继承Person类的时候会报错👇
-
修饰方法,表示这个方法不能被重写。
父类
public class Person {
public final void sleep() {
System.out.println(“人在睡觉”);
}
}重写sleep方法时会报错👇
-
修饰成员变量。
修饰成员变量后,成员不再有默认值,所以必须要赋值,并且赋值之后不能再修改,所以该成员变量没有setter方法,只有getter方法。
public class Person {
//private final String name;//没有赋值,所以报错。
private final String name = “talent”;
} -
修饰局部变量(一般用大写字母)。
修饰局部变量后,该变量就成了常量,一旦修改,不可改变。
public static void main(String[] args) {
final int A = 10;
//A = 20;//报错
final int B;
B = 20;
}
3.static
Person person = new Person();当我们通过new关键字创建对象的时候,这时候系统才会分配内存空间给每个对象,其方法才可以供外部调用。
但是,我们有时候希望无论是否产生了对象或者无论产生了多少对象,某些特定的数据在内存中只有一份。
例如所有的中国人都有个国家名称nation,每个中国人都共享这个国家名称,那么每个中国人都可以共享这个属性。
- static:静态的。
- static可以修饰变量、方法和代码块。
- static修饰变量后,变量变成静态变量。
(1)按照是否使用static进行修饰,可以分为:实例变量(创建了实例之后才能使用)和静态变量。- 实例变量:创建了多个对象,每个对象都独立有一套类中的非静态变量(实例变量)。每个对象的非静态变量互不影响。
- 静态变量:创建了多个对象,多个对象共享一个静态变量,当其中一个对象修改了静态变量,会导致其他对象的静态变量改变。
(2)static修饰变量 - 属性不再属于对象,而是属于类,只要是这个类创建的对象,都可以共享该静态变量;静态变量属于对象。
- 静态变量随着类的加载而加载;实例变量随着对象的创建而加载。
- 静态变量的加载时间早于对象的创建。
- 由于类只会加载一次,静态变量在内存中只有一份。
- static修饰方法
(1)static修饰方法后,这个方法就不再属于对象,而是属于类。
(2)静态方法是随着类的加载而加载,‘类名.方法名’。
(3)静态方法只能调用静态变量和静态方法,非静态方法能调用非静态变量和非静态方法,也能调用静态变量和静态方法。因为静态变量是在类加载的时候加载静态方法,而实例变量是在对象创建之后才加载实例变量,当静态方法调用实例变量的时候,实例变量还没有被加载,所以不能在静态方法里调用实例变量。 - 在静态方法里面可以使用this和super吗?
不可以,因为这两个关键字是有了对象以后才存在,而静态方法早于对象的创建。 - 开发中,如何确定一个属性使用static进行修饰呢?
如果一个属性需要被多个对象共享,使用static进行修饰。 - 开发中,如何确定一个方法使用static进行修饰呢?
工具类会使用static进行修饰方法。如jdbc工具类和connection连接对象,操作静态变量的方法一般都用static修饰。
请看代码👇
public class Chinese {
String name;
int age;
static String nation;
public void eat(){
System.out.println("在吃饭");
//调用静态变量和实例变量
nation = "china";
age = 12;
//调用静态方法
sleep();
}
public static void sleep(){
System.out.println("在睡觉");
//静态方法只能调用静态变量
nation = "china";
//age = 12;//静态方不能调用实例变量
//eat();静态方不能调用实例方法
}
}
public class TestChinese {
public static void main(String[] args) {
Chinese.nation = "中国";
Chinese.sleep();
Chinese c = new Chinese();
c.name = "张三";
c.age = 20;
//c.nation = "中国";
Chinese c2 = new Chinese();
c2.name = "李四";
c2.age = 40;
//c2的年龄不会影响c1的age,每个对象都独一份
c2.nation = "china";
//c2的nation会影响c1的nation
System.out.println(c.nation);//china
System.out.println(c2.nation);//china
}
}
4.代码块
代码块也叫做初始化块。
- 代码块的作用就是初始化类和对象。
- 代码块只能通过static修饰,修饰之后成为静态代码块。
- 可以看成匿名方法。
4.1.静态代码块
- 随着类的加载而加载,它是属于类的。
- 可以写输出语句。
- 静态代码块的执行要优先于非静态代码块的执行。
- 静态代码块里只能调用静态变量和静态方法。
- 比如jdbc对数据库连接池进行初始化时会用到静态代码块。
4.2.非静态代码块
- 随着对象的创建而加载,它是属于对象的。
- 可以写输出语句。
- 创建了几个对象,非静态代码块就加载几次。
- 作用:可以在创建对象的同时,给对象的属性进行初始化。
请看代码👇
package com.hpe.java2;
public class Animal {
String name;
int age;
static String desc = "我是一只动物";
//非静态代码块
{
name = "小白";
age = 20;
//调用静态变量
desc = "我是一只小动物";
//调用静态方法
show();
System.out.println("我是非静态代码块");
}
//静态代码块
static{
System.out.println("我是静态代码块");
//调用非静态变量和非静态方法
//age = 20;//报错
//eat();//报错
//调用静态变量和静态方法
desc = "hehe";
show();
}
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃饭");
}
public static void show(){
System.out.println("我是一只可爱的动物");
}
}
package com.hpe.java2;
public class TestAnimal {
public static void main(String[] args) {
Animal animal = new Animal();
Animal animal1 = new Animal();
}
}package com.hpe.java2;
public class TestAnimal {
public static void main(String[] args) {
Animal animal = new Animal();
Animal animal1 = new Animal();
}
}
5.抽象类
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更通用。
类的设计应该保证父类和子类能够共享特征。有时将一个父类设计的非常的抽象,以至于他没有具体的实例,这样的类叫做抽象类。
用abstract修饰一个类时,这个类叫做抽象类;用abstract修饰一个方法时,这个方法叫做抽象方法。
抽象方法:public abstract void eat();但这个方法只有方法的定义,没有方法的实现。
抽象类:在class前加abstract
特点:
- 抽象方法所在的类必须是抽象类。
- 抽象类不能实例化。
- 如果要实现抽象类,必须创建一个非抽象子类去继承抽象类。
- 子类继承抽象类,必须重写抽象类中所有的抽象方法(子类若是抽象类,则不用重写)。
- 抽象类里可以定义普通方法。
- 抽象类里可以定义构造方法。
注意:
不能用abstract修饰属性、构造器、私有方法、静态方法、final的方法。
举例说明
Animal是抽象类。
Dog是抽象类,继承Animal。
Cat是普通类,并继承Animal。
Dog2Ha是普通类,继承Dog。
请看代码👇
package com.hpe.java;
public abstract class Animal {
//动物吃饭,就是一个抽象的,因为我们不知道具体是什么动物。
//比如狗吃骨头,猫吃鱼🐟
public abstract void eat();
public abstract void sleep();
//抽象类中可以定义普通方法
public void show() {
System.out.println("描述");
}
//可以定义构造方法吗? 可以!
//所有类最终继承Object类
//如果不能定义构造方法,也就代表不能使用Object里定义的方法和属性了
public Animal() {
super();
}
}
package com.hpe.java;
//重写父类方法快捷键crtl+i/o
//Dog也是抽象类,对于Animal中的所有抽象方法,可以重写,可以不重写,也可以不全重写
//但是没有重写的抽象方法,交给下一代重写
public abstract class Dog extends Animal{
//抽象类Dog类重写了Animal的eat(),sleep()交给Dog2Ha重写
@Override
public void eat() {
System.out.println("汪星人喜欢吃骨头");
}
}
package com.hpe.java;
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("喵星人喜欢吃鱼");
}
@Override
public void sleep() {
System.out.println("喵星人在睡觉");
}
}
package com.hpe.java;
public class Dog2Ha extends Dog{
@Override
public void sleep() {
System.out.println("我是二哈,我智商低");
}
}
package com.hpe.java;
public class TestAnimal {
public static void main(String[] args) {
//抽象类不能被实例化,因为动物是一个抽象的概念
//Animal animal = new Animal();//报错
//Dog也是一个抽象类,不能被实例化
//Animal dog = new Dog();
//多态:父类的引用指向子类的实例
Animal dog = new Dog2Ha();
dog.eat();
dog.show();
Animal cat = new Cat();
cat.eat();
}
}
一般类和抽象类的区别:
- 一般类有足够的信息描述事务;抽象类描述事物的信息可能不足。
- 一般类中不能定义抽象方法,只能定义非抽象方法;抽象类中可定义抽象方法,也可定义非抽象方法。
- 一般类可以被实例化,抽象类不可以被实例化。
6.匿名xx
匿名类有两种:
- 与子类有关的匿名类
- 与接口有关的匿名类
下面用代码说明与子类有关的匿名类👇
package com.hpe.java1;
public abstract class Person {
public abstract void eat();
public abstract void sleep();
}
package com.hpe.java1;
public class Student extends Person{
@Override
public void eat() {
System.out.println("学生在吃饭");
}
@Override
public void sleep() {
System.out.println("学生在睡觉");
}
}
package com.hpe.java1;
public class TestPerson {
public static void main(String[] args) {
Person person = new Student();
method(person);//person是非匿名对象 Student是非匿名类
method(new Student());//匿名对象,只能用一次
//这里之所以说只能用一次,是因为再创建一个匿名对象的话,虽然不会报错,但两个匿名对象不是同一个对象。
//其实是new了一个子类,注意这里是子类,相当于Student类,只是这个子类没有名字
//创建了一个匿名子类的对象person1
//匿名子类 非匿名对象
Person person1 = new Person() {
@Override
public void eat() {
System.out.println("这里是匿名子类的eat方法");
}
@Override
public void sleep() {
System.out.println("这里是匿名子类的sleep方法");
}
};
person1.eat();
//匿名子类的最简单的写法,可以用,但是不建议用。
//匿名子类 匿名对象
method(new Person() {
@Override
public void eat() {
}
@Override
public void sleep() {
}
});
}
public static void method(Person person) {
System.out.println("学生");
}
}
与接口有关的匿名类和上面那种几乎一样,只需把抽象类Person类变成接口即可。
7.接口
我们对电脑已经非常熟悉了,众所周知,电脑具有拍照和播放光碟的功能。现在有一个TakingPhoto类,它提供拍照的功能;还有一个PlayVCD类,它提供了播放光碟的功能。电脑同时具有这两个类的提供的功能。因此我们希望定义一个Computer类,继承TakingPhoto和PlayVCD类。但此时问题就出现了 java中是不允许多重继承的!!!
为了解决这个问题,Java提出了接口的方式,作为“替代版”的多重继承。
1.接口是什么?
(1)多个类之间的公共规范。
(2)接口的出现为了解决java不能多继承的问题。
(3)接口里的方法都是抽象方法,它相当于一个特殊的抽象类。
2.怎么定义一个接口?
public interface Traffic{
}
3.接口的特点
(1)接口里的成员变量都是常量,默认会加上public static final。
(2)接口里的方法都是抽象方法(默认方法除外–jdk1.8新特性),默认会加上public abstract。
(3)接口不能实例化,抽象类通过继承实现,接口通过创建一个类去实现接口。
(4)接口里面不能定义普通方法,可以定义默认方法。
(5)接口不能定义构造方法。
(6)一个接口可以继承一个接口,并且可以继承多个接口。
4.实现类
(1)实现接口的类叫做实现类,写法例如:public class Car implements Traffic。
(2)实现类要重写接口里面所有的抽象方法(实现类是抽象类除外)。
(3)一个是实现类可以实现多个接口。
(4)如果一个类再继承一个类的同时实现一个接口,必须先继承后实现。
5.面试题:接口和抽象类的相同点和不同点。
相同点:
(1)接口和抽象类都不能被实例化。只能被其他类实现和继承。
(2)接口和抽象类都可以包含抽象方法,实现接口和抽象类的类都必须实现这些抽象方法(实现类是抽象类除外)。
(3)接口和抽象类都可以有静态方法,通过接口名和类名调用。
不同点:
(1)接口里只能包含抽象方法,能包含默认方法,能包含静态方法,不能包含普通方法;抽象类则完全可以包含普通的方法。
(2)接口里只能定义静态常量属性,不能定义普通属性;抽象类里既可以定义普通属性,也可以定义静态常量。
(3)接口不包含构造函数;抽象类可以包含构造函数,抽象类里的构造函数并不是用于创建对象,而是让其子类调用这些构造函数来完成属于抽象类的初始化操作。
(4)接口不包含初始化块,但抽象类可以包含初始化块。
(5)一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java的单继承不足。
用一张图来彻底说明接口和抽象类在开发中分别的作用👇。
通过代码来解释简单说明一下👇。
Traffic接口定义了车的一系列方法。
package com.hpe.java3;
public interface Traffic {
int a = 10;//这是常量
public static final int b = 10;//两种方式相同,都表示常量
//车启动
public abstract void start();//这是一个抽象方法
//车加速
abstract void add();//这也是一个抽象方法
//车行驶
public void run();//这也是一个抽象方法
//车停止
void stop();
//jdk1.8新特性-----默认方法
public default void m1() {
}
//接口里不能有构造方法
//public Traffic(){
//}
}
Car类实现了Traffic。
package com.hpe.java3;
//汽车类
public class Car implements Traffic, Oil {
//重写Traffic的方法
@Override
public void start() {
System.out.println("汽车启动了...");
}
@Override
public void add() {
System.out.println("汽车加速了...");
}
@Override
public void run() {
System.out.println("汽车行驶了...");
}
@Override
public void stop() {
System.out.println("汽车停止了...");
}
//重写Oil的方法
@Override
public void addOil() {
System.out.println("汽车在加油...");
}
}
EVs类实现了Traffic。
package com.hpe.java3;
//电动车
public class EVs implements Traffic {
@Override
public void start() {
System.out.println("电动车启动了...");
}
@Override
public void add() {
System.out.println("电动车加速了...");
}
@Override
public void run() {
System.out.println("电动车行驶了...");
}
@Override
public void stop() {
System.out.println("电动车停止了...");
}
}
测试一下。
package com.hpe.java3;
public class Test {
public static void main(String[] args) {
//Traffic traffic = new Traffic();//抽象类不能实例化
//创建汽车对象
Car car = new Car();
car.start();
car.run();
car.add();
car.stop();
//多态
//一般使用下面这种方式
//接口引用指向实现类实例
Traffic evs = new EVs();
evs.start();
evs.run();
evs.add();
evs.stop();
}
}
8.Java8新特性:接口增强
Java 8 对接口做了进一步的增强。
- 接口中可以添加使用 default 关键字修饰的非抽象方法,即默认方法(或扩展方法)。
- 接口里可以声明静态方法,并且可以实现。
下面先说一下默认方法。
-
接口冲突。 当一个类实现两个接口,并且这两个接口里有同名(包括参数相同)的默认方法,那么这个实现类必须要重写这个默认方法,否者无法通过编译。
package com.hpe.java6;
public interface MyFun1 {
default String getName() {
return “interface MyFun1”;
}
}package com.hpe.java6;
public interface MyFun2 {
default String getName() {
return “interface MyFun2”;
}
}package com.hpe.java6;
public class SubClass implements MyFun2, MyFun1{
@Override
public String getName() {
return “class SubClass Override”;
}
}package com.hpe.java6;
public class Test {
public static void main(String[] args) {
SubClass sub = new SubClass();
System.out.println(sub.getName("hello : "));//hello : class SubClass Override
}
}
注意:如果要想调用接口的默认方法,只需要修改SubClass类即可👇。
package com.hpe.java6;
public class SubClass implements MyFun1,MyFun2{
@Override
public String getName(String str) {
//注意这里super的写法
return MyFun1.super.getName(str);
}
}
package com.hpe.java6;
public class Test {
public static void main(String[] args) {
SubClass sub = new SubClass();
System.out.println(sub.getName("hello : "));//hello : interface MyFun1
}
}
-
超类优先。当继承的父类和实现的接口中有相同签名的方法时,不用重写接口的方法,优先使用父类的方法。
package com.hpe.java6;public interface MyFun1 {
default String getName(String str) {
return str + “interface MyFun1”;
}
}
package com.hpe.java6;public class MyClass {
public String getName(String str) {
return str + “class MyClass”;
}
}
package com.hpe.java6;public class SubClass extends MyClass implements MyFun1{
}
package com.hpe.java6;public class Test {
public static void main(String[] args) {
SubClass sub = new SubClass();
System.out.println(sub.getName("hello : "));//hello : class MyClass
}
}
再来说一下接口里的静态方法。
比较简单直接上代码👇。
public interface MyInterface {
public static void show(){
System.out.println("接口中的静态方法");
}
}
public class Test {
public static void main(String[] args) {
MyInterface.show();//接口中的静态方法
}
}