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的nationSystem.out.println(c.nation);//chinaSystem.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重写@Overridepublic void eat() {System.out.println("汪星人喜欢吃骨头");}}package com.hpe.java;public class Cat extends Animal{@Overridepublic void eat() {System.out.println("喵星人喜欢吃鱼");}@Overridepublic void sleep() {System.out.println("喵星人在睡觉");}}package com.hpe.java;public class Dog2Ha extends Dog{@Overridepublic 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{@Overridepublic void eat() {System.out.println("学生在吃饭");}@Overridepublic 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() {@Overridepublic void eat() {System.out.println("这里是匿名子类的eat方法");}@Overridepublic void sleep() {System.out.println("这里是匿名子类的sleep方法");}};person1.eat();//匿名子类的最简单的写法,可以用,但是不建议用。//匿名子类匿名对象method(new Person() {@Overridepublic void eat() {}@Overridepublic 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的方法@Overridepublic void start() {System.out.println("汽车启动了...");}@Overridepublic void add() {System.out.println("汽车加速了...");}@Overridepublic void run() {System.out.println("汽车行驶了...");}@Overridepublic void stop() {System.out.println("汽车停止了...");}//重写Oil的方法@Overridepublic void addOil() {System.out.println("汽车在加油...");}}
EVs类实现了Traffic。
package com.hpe.java3;//电动车public class EVs implements Traffic {@Overridepublic void start() {System.out.println("电动车启动了...");}@Overridepublic void add() {System.out.println("电动车加速了...");}@Overridepublic void run() {System.out.println("电动车行驶了...");}@Overridepublic 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{@Overridepublic 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();//接口中的静态方法}}