Java中的抽象类、接口、设计模式、包装类和泛型(附带相关面试题)
Alphamilk
编辑于 2023年08月05日 00:37
收录于文集
共13篇

 一.抽象类(abstract)

abstract class className{ }

1.抽象类就是一个一些事务的具体抽象,比如说狗,猫,兔子等等可以往上抽象成同一个概念,即是动物的这个抽象类,因为都有着差不多的行为逻辑,所以抽象类中可以有吃,睡等共同方法 2.由于抽象类无法直接进行实例化,所以需要通过实例化子类实现向上转型,但是如果抽象类中有着static方法,则可以直接调用其方法而不需要实例化 3.必须有子类继承抽象类,而final类没有子类,所以抽象类不能是final类 4.抽象类中有多种方法可以使用,有普通方法,static方法,还有抽象方法 5.子类继承抽象类时候必须实现父类中的抽象方法 6.子类继承抽象类只能继承一个即所谓的单继承限制 7.如果抽象类中有构造函数则需要子类在继承的时候通过super()函数

案例代码:创建一个万物动物类与多个具体动物类

代码块
clike
自动换行
复制代码
package Example91;


abstract class Animal {
    public abstract void shout();
    public abstract void eat();//抽象方法
    public void test() {
        System.out.println("一般方法");
    }
    public static Dog dogReturn() {
        return new Dog(); //static方法 无需实例化
    }
}
class Dog extends Animal {
    @Override //覆写抽象类中抽象函数
    public void shout() {
        System.out.println("汪汪汪");
    }

    @Override
    public void eat() {
        System.out.println("嗨嗨嗨,我吃吃吃吃吃");
    }
}

class Cat extends Animal {
    @Override //覆写抽象类中抽象函数
    public void shout() {
        System.out.println("喵喵喵");
    }
    @Override
    public void eat() {
        System.out.println("亚斯啦嘞");
    }
}

public class javaDemo {
    public static void main(String[] args) {
        Animal animal = new Cat();//通过向上转型实例化抽象类
        Animal animal1 = Animal.dogReturn();//无需实例化直接调用static方法
        animal.shout();
        animal1.shout();
    }
}
复制成功

*面试题: 普通类和抽象类有哪些区别?

抽象类不能被实例化; 抽象类可以有抽象方法,只需申明,无须实现; 有抽象方法的类一定是抽象类; 抽象类的子类必须实现抽象类中的所有抽象方法,否则子类仍然是抽象类; 抽象方法不能声明为静态、不能被static、final修饰。(注意是抽象方法,普通方法没限制)


二.接口(interface)

interface interfaceName { }

接口的抽象要大于抽象类,因为接口可以继承接口,所以在分类向下划分的时候通常都使用接口,比如动物可以分为哺乳动物,卵生动物。而哺乳动物又分为大型动物,小型动物,再细分其具体动物。如果用抽象类会受到单继承限制。无法一步步细分,所以在能够同时使用接口和抽象类的前提下尽量使用接口

1.接口需要有子类继承(实现)接口 2.接口中方法有 抽象方法 后来的 default的普通方法 和 static方法 3.接口相比于抽象类,子类可以继承(实现)多个接口

案例代码:创建动物接口

代码块
clike
自动换行
复制代码
package Example92;

//利用接口的特性将animal分成两个部分用以突出接口的特点
interface animalPiece1 {
    public abstract void eat();

    public default void test() {
        System.out.println("default方法");
    }

    public static Dog dogReturn() {
        return new Dog();
    }//static方法
}

interface animalPiece2 {
    public abstract void shout();
}

class Dog implements animalPiece1, animalPiece2 {
    @Override
    public void eat() {
        System.out.println("吃吃吃吃吃");
    }

    @Override
    public void shout() {
        System.out.println("叫叫叫叫叫");
    }
}

public class javaDemo {
    public static void main(String[] args) {
        animalPiece1 animal = new Dog();//通过向上转型
        animalPiece1 animal1 = animalPiece1.dogReturn();
        animal.test();
        animal.eat();
    }
}
复制成功

*面试题: 接口和抽象类有什么区别?

(1)接口 接口使用interface修饰; 接口不能实例化; 类可以实现多个接口; ①java8之前,接口中的方法都是抽象方法,省略了public abstract。②java8之后;接口中可以定义静态方法,静态方法必须有方法体,普通方法没有方法体,需要被实现; (2)抽象类 抽象类使用abstract修饰; 抽象类不能被实例化; 抽象类只能单继承; 抽象类中可以包含抽象方法和非抽象方法,非抽象方法需要有方法体; 如果一个类继承了抽象类,①如果实现了所有的抽象方法,子类可以不是抽象类;②如果没有实现所有的抽象方法,子类仍然是抽象类。


三.设计模式

抽象类的模板模式 接口的工厂设计模式和代理模式

抽象类:模板模式

抽象类的模板模式(Template Pattern): 模板模式是一种行为设计模式,它通过定义一个抽象类作为模板,其中包含一个算法的骨架,将一些方法的实现推迟到具体子类中。这使得子类可以在不改变算法结构的情况下重写特定的步骤。抽象类充当模板的角色,定义了整个算法的流程和顺序,而具体子类提供特定步骤的实现。

 案例代码: 煮茶和泡咖啡就是一个比较典型的例子,完成喝咖啡或者茶的动作就只有加入茶叶和咖啡不同其他的行为就都一样,一样的喝东西行为就是一个模板。

代码块
clike
自动换行
复制代码
package Example3_1;
abstract class Action{
    public final void drink(){
        boilWater();
        addYoulike();
        spour();
        drinking();
    }
//    共同步骤
    public void boilWater(){
        System.out.println("正在煮水");
    }
//    子类需要独自完成的步骤
    protected abstract void addYoulike();

//    共同步骤
    public void spour(){
        System.out.println("搅拌");
    }
//    共同步骤
    public void drinking(){
        System.out.println("可以喝了,一口气喝完");
    }
}
class Coffee extends Action{
    @Override
    protected void addYoulike() {
        System.out.println("加入咖啡粉");
    }
}
class Tea extends Action{
    @Override
    protected void addYoulike() {
        System.out.println("加入茶叶");
    }
}
public class javaDemo {
    public static void main(String[] args) {
        Action tea = new Tea();
        tea.drink();
        System.out.println("----------------------");
        Action coffee = new Coffee();
        coffee.drink();
    }

}
复制成功

接口:工厂设计模式 代理设计模式

1.工厂设计模式

接口的工厂设计模式(Factory Design Pattern): 工厂设计模式是一种创建型设计模式,用于创建对象的过程被封装在一个共同的接口中。它通过定义一个工厂接口和多个具体的工厂类来实现对象的创建,由客户端通过工厂接口来请求对象的创建,而不需要关心具体的实现类。接口定义了创建对象的标准和方法,而具体的工厂类负责实现对象的创建逻辑。

案例:一个比较典型的汽车工厂CarFactory 里面有SuvCar,Bus,我可以自己通过输入字符串比如Bus就能自动创建相应的对象

代码块
clike
自动换行
复制代码
package Example4;

//定义汽车接口内部有汽车信息输出
interface Car{
    public void carInfomation();
}
//定义Suv汽车与其相关信息
class SuvCar implements  Car{
    private int price = 1000000;
    @Override
    public void carInfomation() {
        System.out.println("Suv车型的造价为"+this.price);
    }
}
//定义Bus汽车与其相关信息
class Bus implements Car{
    private int price = 5000000;
    @Override
    public void carInfomation() {
        System.out.println("Bus车型的造价为"+this.price);
    }
}
//定义汽车工厂,用以生产对应汽车实例
class CarFactory{
    public static Object makeCar(String str){
//        实现动态创建对象并且忽略传入值的大小写
        if (str.equalsIgnoreCase("SuvCar")){
            return new SuvCar();
        }
        if (str.equalsIgnoreCase("Bus")){
            return new Bus();
        }
        return null;
    }
}
public class javaDemo {
    public static void main(String[] args) {
        Bus bus = (Bus) CarFactory.makeCar("bus");
        bus.carInfomation();
        SuvCar suvCar =(SuvCar) CarFactory.makeCar("suvcar");
        suvCar.carInfomation();
    }
}
复制成功

 2.代理模式:

接口的代理模式(Proxy Pattern): 代理模式是一种结构型设计模式,它提供了一个代理类来控制对另一个对象的访问。代理类和原始对象实现了同样的接口,客户端通过代理类来访问原始对象,并且可以在代理类中添加额外的功能,如权限验证、缓存等。接口定义了客户端与代理类和原始对象之间的交互方式,而代理类充当了对原始对象的访问控制和附加功能的提供者。

案例:代理类内部会将所有对象实例化,所以主类只要实例化代理类就等于把所有的类对象都实例化过一遍,只需要调用自己想要的代理方法就行。核心就在于主类是通过代理类间接访问对象

代码块
clike
自动换行
复制代码
package Example5;
interface Plant{
    public void plant();
}
//花朵
class Flower implements Plant{

    public void plantTime(){
        System.out.printf("在春季适合");
    }
    @Override
    public void plant() {
        System.out.println("种植花朵");
    }
}
//小麦
class Wheat implements Plant{
     Wheat(){}

    public void plantTime(){
        System.out.printf("在冬季适合");
    }
    @Override
    public void plant() {
        System.out.printf("种植小麦");
    }
}
//设置代理类
class plantProxy{
//    代理类内部创建对象
    private Flower flower = new Flower();
    private Wheat wheat = new Wheat();
    public void getFlowerInfo(){
        this.flower.plantTime();
        this.flower.plant();
    }
    public void getWheat(){
        this.wheat.plantTime();
        this.wheat.plant();
    }
}
public class javaDemo {
    public static void main(String[] args) {
        plantProxy proxy = new plantProxy();
        proxy.getFlowerInfo();
        proxy.getWheat();
    }
}
复制成功

四.包装类

1.功能

1.大家熟悉String是作为一个类,而并非数据类型,所以为了将int double float boolean等等这些基本的数据类型也转为一个类,就是所谓包装,每个数据类型对应的类分别是 int ->Integer类double->Double类 boolean->Boolean类

2.数据转换

核心的几个函数

  • Integer.parseInt(String s):将字符串参数解析为带符号的十进制整数。如果字符串无效或格式不正确,将抛出 NumberFormatException 异常。返回解析后的整数值。

  • Double.parseDouble(String s):将字符串参数解析为一个双精度浮点数。如果字符串无效或格式不正确,将抛出 NumberFormatException 异常。返回解析后的浮点数值。

  • Boolean.parseBoolean(String s):将字符串参数解析为布尔值。如果字符串是忽略大小写的 "true&#​34;,则返回 true;否则返回 false

  • String.valueOf(Object obj):返回给定对象的字符串表示。如果对象为 null,则返回字符串 "null&#​34;。

案例代码:

代码块
clike
自动换行
复制代码
package Example96;


public class javaDemo {
    public static void main(String[] args) {
//        String通过包装类Integer的parseInt函数将str转为int
        String str= "123";
        int number = Integer.parseInt(str);
        System.out.println(number*number);

//        将number*number通过String的valueOf转为字符串类型
        String str1 =String.valueOf(number*number);
        System.out.println(str1);

//        将String转为boolean类型
        String str2 = "true";
        boolean flag = Boolean.parseBoolean(str2);

    }
}
复制成功

3.Object类接受所有类型数据

1.Object类是所有类的父类,那也就意味着Object可以接受所有的数据类型,也就是说如果在不知道方法要接受什么具体类型的数据时候,那么就进行Object定义并接受对应内容,到时候再进行强制向下转型。

代码块
clike
自动换行
复制代码
package Example97;

class Function {
    public void Sort(Object obj) {
        if (obj instanceof Integer) {
            System.out.println("传入的数据属于是int型");
        } else if (obj instanceof String) {
            System.out.println("传入的数据属于是String型");
        } else System.out.println("属于其他类");
    }
}

public class javaDemo {
    public static void main(String[] args) {
        Function f = new Function();
        f.Sort("123");
        f.Sort(123);
        f.Sort(123.123);
    }
}
复制成功

问题引出:但是强制向下转型可能会出现ClassCaseException错误,而如果用之前的instanceOf可能会非常麻烦,因为定义的数据可能是字符串但是将其强行向下转型到(Integer)类型则会报错,为此就提出一个新的概念--泛型

五.泛型

泛型是为了避免出现ClassCaseException的异常而出现,而该异常大概率是因为强制向下转型而出现的问题,故此泛型的解决办法就是避免出现向下转型

1.泛型的格式

类需要进行标记<>

2.泛型的引用与泛型引用通配符 “?”传递数据的上限和下限

 1.泛型的引用:

案例代码:

代码块
clike
自动换行
复制代码
package Example98;

class test<T> {
    private T test1;

    public void setTest1(T test1) {
        this.test1 = test1;
    }
    public T getTest1() {
        return test1;
    }
    public void Test() {
        System.out.println(test1);
    }
}
public class javaDemo {
    public static void main(String[] args) {
        test<String> t = new test<String>();
        t.setTest1("123");
        t.Test();
        fun(t);
    }
    public static void fun(test<?> test) {
        System.out.println(test.getTest1());
//        无法对test值进行操作
    }
}
复制成功

  2.通配符 “?”传递数据的上限和下限以及区别:

<?extends Number>:

只能用Number类或者其子类(Integer Double Boolean)即所谓上限,父类受到限制

<?super String>:

只能用String类或者其父类Object类即所谓下线,子类受到限制

3.泛型的作用

当我们编写代码时,可能会遇到需要处理多种数据类型的情况。泛型就像是一种通用的工具,它可以让我们在不同的地方使用相同的代码来处理不同的数据类型。 举个例子,假设我们有一个存储整数的容器类。如果没有泛型,我们可能需要为存储整数、字符串、浮点数等不同类型的值分别编写不同的容器类。这样就会导致代码重复,增加维护难度。 而有了泛型,我们可以编写一个通用的容器类,在需要存储不同类型的值时,只需要指定具体的类型,让泛型去帮助我们处理具体的类型操作。这样,我们就可以使用相同的代码去处理不同的数据类型,比如添加、删除、获取值等操作。 在使用泛型的时候,并不能直接修改泛型类中定义的属性。但是,我们可以通过提供方法(setter 和 getter)来操作和修改泛型类中的属性。这样,外部代码可以通过调用方法来改变内部属性的值。 总的来说,泛型的作用就是让我们能够编写通用、可复用的代码,用于处理不同的数据类型。它简化了代码的编写,提高了代码的可读性和可维护性。虽然不能直接修改泛型类中的属性,但我们可以通过方法来操作和修改属性的值。