目录
question1:在Java中,一个子类能同时继承多个父类吗?
question2:子类能全部继承父类所有的public的字段和方法吗?
question3:子类能继承父类的private修饰的字段和方法吗?
question4:设置为private,子类不能访问,设置成public,又违背了封装的初衷,有什么好的解决办法吗?
question7:如果子类和父类的字段重名,子类对象调用的是谁的字段?
question8:如果子类和父类的字段重名,子类对象想调用父类的字段,怎么办?
一、包
包:是组织类的一种形式。使用包的主要目的是保证类的唯一性。
包名:全小写,最好是公司域名的逆置
包访问权限,就是只能在当前包中使用
包实际上,可以看作一个文件夹。
jia包里面包含的都是字节码文件。
java.long这个包,不需要手动导入
java.util:工具程序包
二、继承
2.1 继承的意义
观察下面的代码,我们可以观察出里面有大量的冗余代码。通过分析,Animal、Cat和Bird这三个类中都有相同的name属性、age属性、eat()方法,同时,从逻辑上讲,Cat和Bird都是一种animal,因此,我们可以让Cat和Bird分别继承Animal类。
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(this.name+ "吃饭饭");
}
}
class Cat{
public String name;
public int age;
public void eat(){
System.out.println(this.name+ "吃饭饭");
}
public void barkMiao(){
System.out.println("喵喵喵");
}
}
class Bird{
public String name;
public int age;
public void eat(){
System.out.println(this.name+ "吃饭饭");
}
public void fly(){
System.out.println(this.name +"飞");
}
}
public class Main2 {
public static void main(String[] args) {
Cat cat = new Cat();
Bird bird = new Bird();
}
}
引入继承后,修改后的代码如下:
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(this.name+ "吃饭饭");
}
}
class Cat extends Animal{
public void barkMiao(){
System.out.println("喵喵喵");
}
}
class Bird extends Animal{
public void fly(){
System.out.println(this.name +"飞");
}
}
public class Main2 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "小猫咪";
cat.eat();
Bird bird = new Bird();
bird.name = "小麻雀";
bird.eat();
}
}
我们可以看到,当子类继承了父类之后,子类就拥有了父类的方法和属性。因此,继承的意义是方法的重复使用。
2.2 如何实现继承
如何实现继承:
第一步:先抽取共性,放在父类中
第二步:使用extends关键字继承
question1:在Java中,一个子类能同时继承多个父类吗?
在Java中,只能是单继承,不能是多继承。
在C++、python等语言支撑多继承。
question2:子类能全部继承父类所有的public的字段和方法吗?
会,子类会继承父类的所有的public的字段和方法。
question3:子类能继承父类的private修饰的字段和方法吗?
子类可以继承,但是不能访问。
question4:设置为private,子类不能访问,设置成public,又违背了封装的初衷,有什么好的解决办法吗?
引入protected关键字。对于类的子类(同一个包中或者不同包中)和同一个包中的其他类,protected修饰的字段是可以访问的。
范围 | private | default(是包访问权限,不是关键字,什么都不用写) | protected | public |
同一包中的同一类 | ✔ | ✔ | ✔ | ✔ |
同一包中的不同类 | ✔ | ✔ | ✔ | |
不同包中的子类(不同包中可能会有继承关系) | ✔ | ✔ | ||
不同包中的非子类 | ✔ |
question5:子类能继承父类的构造方法吗?
因为构造方法不能被继承,只能被显示的引用。子类在继承父类时,需要先帮助父类构造,使用super(),这个的意思就是显示的调用父类的构造方法。
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(this.name+ "吃饭饭");
}
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public Animal(String name){
this.name = name;
}
}
class Cat extends Animal{
public void barkMiao(){
System.out.println("喵喵喵");
}
// 如果不写构造方法,编译器会默认提供一个不带参数的构造方法
// 如果父类中写了构造方法,在子类中就得使用super()显示的告诉编译器用的是哪个构造方法
public Cat(String name){
super(name);
}
public Cat(String name,int age){
super(name,age);
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Main2 {
public static void main(String[] args) {
Cat cat1 = new Cat("小黄");
System.out.println(cat1.toString());
Cat cat2 = new Cat("小黑",3);
System.out.println(cat2.toString());
}
}
question6:this和super的区别?
this是指当前对象的引用。super是指当前对象父类的引用。
this和super都可以调用对象的构造函数、参数和方法。在这两个都调用构造函数时,都必须放在第一行。因为第一行只有一个,所以super()和this()不能共存。
super和this都依赖对象,static不依赖对象,所以这两个都不能在static方法里面使用。
question7:如果子类和父类的字段重名,子类对象调用的是谁的字段?
如果子类和父类的字段重名,调用时采用就近原则。
class Animal{
public String name = "父类的小猫";
public int age;
public void eat(){
System.out.println(this.name+ "吃饭饭");
}
}
class Cat extends Animal{
public String name = "子类的小猫";
public void barkMiao(){
System.out.println("喵喵喵");
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Main2 {
public static void main(String[] args) {
Cat cat1 = new Cat();
System.out.println(cat1.toString());
}
}
运行结果:
question8:如果子类和父类的字段重名,子类对象想调用父类的字段,怎么办?
在子类方法中,如果想调用父类的字段,使用super。
question9:如果子类和父类的方法重名,怎么办?
如果子类的方法和父类方法有以下特征:
方法名相同、返回值相同和参数列表相同,则子类方法重写了父类方法。
question10:如果不想被继承,应该怎么办?
当这个类不想被继承,加上final修饰,称为密封类。
final还可以修饰方法,称为密封方法。还可以修饰变量或者字段,称为常量。
question11:继承还有哪些注意事项呢?
在继承时,最多不要超过三层。
三、组合
面向对象还有一个特点,组合。将一个类的实例作为另一个类的字段。也是能够起到代码重用的效果。
class Student{
}
class Teacher{
}
class School{
public Student[] students;
public Teacher[] teachers;
}
四、多态
4.1 向上转型
向上转型是指:父类引用 指向 子类对象
发生的三种时机:
直接赋值: Animal animal = new Cat();
方法的传参:方法的参数类型是父类引用,但是调用时传的是子类对象
返回值:方法的返回值是父类引用,但是返回的是子类对象。
class Animal{
public String name = "父类的小猫";
public int age;
public void eat(){
System.out.println(this.name+ "吃饭饭");
}
}
class Cat extends Animal{
public String name = "子类的小猫";
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Main2 {
public static void main(String[] args) {
Animal animal = new Cat(); // 直接赋值
}
// 方法的返回值
public static Animal findCat(){
Cat cat = new Cat();
return cat;
}
// 方法的传参
public static void main2(String[] args) {
Cat cat = new Cat();
feed(cat);
}
public static void feed(Animal animal){
}
}
4.2 动态绑定
第一步,发生向上转型,即父类引用指向子类对象
第二步:通过父类引用,调用子类和父类同名的override方法。
结论:编译时调用的父类的方法,实际上调用的是子类的方法。
class Animal{
public String name = "小猫";
public int age;
public void eat(){
System.out.println(this.name+ "吃饭饭,是父类的方法哦");
}
}
class Cat extends Animal{
public void eat(){
System.out.println(this.name+ "吃饭饭,是子类的方法哦");
}
}
public class Main2 {
public static void main(String[] args) {
Animal animal = new Cat(); // 向下转型,父类引用 指向子类对象
animal.eat(); // 通过父类引用 调用子类和父类同名的override方法
}
}
运行结果:
4.3 向下转型
上下转型的前提是,发生了向上转型,同时,向下转型在使用时必须进行强制类型转换。
向下转型极度不安全,不建议使用。
instanceof可以判定一个引用是否是某个类的实例,如果是,则返回true,此时再进行向下转型时就比较安全了。
4.4 方法重写
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override)
question1:重载和重写的区别
重写:方法名、参数列表都一样,返回值最好都一样。
重载:方法名相同,参数列表不同,返回值不做要求
question2:重写的注意事项
重写的方法不能是密封方法,即被final修饰。
子类的访问修饰限定符的权限一定要大于等于父类的权限:
private < final < proteced < public
父类不能被private修饰。
重写的方法也不能被static修饰。
question3:在构造方法中调用重写的方法
class ClassA{
public ClassA(){
func();
}
public void func(){
System.out.println("这是父类的func()");
}
}
class classB extends ClassA{
public int a = 1;
@Override
public void func() {
System.out.println("这是子类的func()"+a);
}
}
public class TestDemo4{
public static void main(String[] args) {
//子类不能继承父类的构造方法,只能显示的引用父类的构造方法,即子类在构造前,需要先帮父类构造
// 先帮父类构造,调用父类的构造函数,在父类的构造函数里面有个func()函数
// 此处发生了动态绑定,调用的子类的func()函数,由于public int a = 1;这行代码并没有被执行
// 所以输出是 这是子类的func()0
classB classB = new classB(); //运行结果:这是子类的func()0
}
}
尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题。
4.5 理解多态
了解向上转型、动态绑定和方法重写后,就可以利用多态的形式来设计程序了。可以重写一些只关注父类的代码,同时可以兼容各类子类的情况。
class Shape{
public void draw(){
// 啥都不用实现,这个方法就是让子类重写的
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("画⚪");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("画正方形");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("画花");
}
}
//--------上面的代码是 类的实现者编写的 ,下面的代码是类的调用者编写的
public class TestDemo4 {
// 这个方法的参数类型为Shape,在这个方法内部不知道也不关注当前的shape指向哪个子类型的实例
// 所以shape这个引用调用draw()方法可能会有多种不同的表现,这种行为就称为多态
public static void drawShape(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape shape1 = new Cycle();
Shape shape2 = new Flower();
Shape shape3 = new Rect();
drawShape(shape1); //画⚪
drawShape(shape2); //画花
drawShape(shape3); //画正方形
}
}
4.6 多态的好处
①类调用者对类的使用成本进一步降低。
封装让类的调用者不需要知道类的实现细节。
多态能让类的调用者连这个类的类型都不必知道,只需要知道这个对象具有某个方法即可。
②能够降低代码的圈复杂度,避免使用大量的if-else
圈复杂度:是一种描述一段代码复杂程度的方式,一段代码如果平铺直叙,就比较容易理解。而如果有很多的条件分支或者循环语句,就认为理解起来更复杂。
可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,该个数就称为圈复杂度。如果一个方法的圈复杂度太高,就需要考虑重构。
不同公司对于代码的圈复杂度的规范不一样,一般不会超过10.
举一个例子:
// 不适用多态 假设打印多个形状
public static void drawShapes1(Shape shape){
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
String[] shapes = {"rect","cycle","flower","cycle"};
for(String str:shapes){
if(str.equals("rect")){
rect.draw();
}else if(str.equals("cycle")){
cycle.draw();
}else if(str.equals("flower")){
flower.draw();
}
}
}
// 使用多态
public static void drawShapes2(Shape shape){
// 会根据传入的对象,调用合适的方法
Shape[] shapes = {new Cycle(),new Rect(),new Flower(),new Rect()};
for(Shape shape1 :shapes){
shape1.draw();
}
}
③可扩展能力更强
假设现在想要新增一种新的情况,使用多态的方式修改代码的成本也很低。
class Star extends Shape{
@Override
public void draw() {
System.out.println("星星");
}
}
对于类的调用者来说,只要创建一个新类的实例就行,改动成本很低。如果不用多态,就需要修改drawShape1中的if-else。
五、抽象类
5.1 什么是抽象类:
如果类中的方法被子类继承后会重写,那这个方法就不用具体的实现,因为不管你实现啥,子类都会重写,此时,就可以用abstract修饰,就可以不用具体的实现了。
由abstract修饰的方法是抽象方法,包含抽象方法的类是抽象类,抽象类也由abstract修饰。
5.2 抽象类有啥:
抽象类里面由抽象方法、普通的成员变量、普通的成员方法
5.3 抽象类的特点:
抽象类不能被实例化
不能实例化要他干啥:
就是为了被继承。(抽象类也是类,也只能被一个类继承)
继承了抽象类然后呢?
首先,抽象类中的抽象方法不能被private修饰,子类需要重写抽象类的所有抽象方法。
抽象类继承抽象类:不需要重写所有的抽象方法
普通类继承抽象类:需要重写所有的抽象方法
六、接口
6.1 什么是接口?
由Interface修饰,接口中不能有普通方法,必须全是抽象方法。
抽象方法默认为:public abstract ,写的时候可以省略 public abstract
成员变量默认为:public static final 修饰的 ,写的时候可以省略 public static final
interface Ishape{
public static final int a = 0;
void draw();
}
class Cycle1 implements Ishape{
@Override
public void draw() {
System.out.println("⚪");
}
}
public class TestDemp6 {
public static void main(String[] args) {
Ishape ishape = new Cycle1();
ishape.draw();
}
}
6.2 接口的特点
不能被实例化
不能实例化,那他的作用是啥?
为了被多继承
interface Ishape{
public static final int a = 0;
void draw();
}
interface Color{
void paint();
}
class Cycle1 implements Ishape, Color {
@Override
public void draw() {
System.out.println("⚪");
}
@Override
public void paint() {
System.out.println("上色");
}
}
public class TestDemp6 {
public static void main(String[] args) {
Cycle1 cycle1 = new Cycle1();
cycle1.draw();
cycle1.paint();
}
}
6.3 类和接口的关系是啥?
实现 implements ,同时类需要实现接口里的所有抽象方法,同时,一个类可以实现多个接口,接口之前用逗号隔开.
6.4 接口和接口的关系?
是extends,扩展。该接口拓展了其他接口的功能。
6.5 抽象类和接口的区别
区别 | 抽象类 | 接口 |
结构组成 | 普通成员变量、普通成员方法、抽象方法 | 抽象方法+全局常量 |
权限 | 各种权限 | 都是public(抽象方法:public abstract) 全局常量:public static final |
子类使用 | 不同类继承抽象类:继承 extends 需要实现所有的抽象方法 抽象类继承抽象类:不需要实现所有的抽象方法 | 接口和接口之间:extends 不需要实现所有的抽象方法 抽象类和接口:实现,implements 需要实现每个接口的所有抽象方法 |
限制 | 类只能是单继承,抽象类也不例外 | 一个子类可以实现多个接口 |
相同点 | 都不能被实例化 |