Java基础20问(6-10)

6.Java接口和抽象类的区别?

不同点

1.接口在Java8之前不能写方法实现逻辑,Java8及以后的版本,可以用default关键字写方法的实现。

2.接口中方法都是public的,public可以省略,而抽象类没有这个限制。

3.接口用interface关键字,抽象类用abstract class来声明。

相同点

接口和抽象类都不能直接new。

实战总结

接口用于制定规范,而抽象类用于代码的复用,比如模板方法模式。

实际开发中,我们用接口来制定规范,直接参与上层业务(Controller)代码的编写,然后具体的实现放到业务层(ServiceImpl),如果实现类有很多相同的逻辑,就可以考虑封装为一个抽象类。

举一个例子,Excel导入,我们可以封装一个导入接口:

public interface ImportService {

    /**
     * Excel导入
     * @param file 上传的excel文件
     * @return
     */
    Result importData(MultipartFile file);

}

importData 要求输入一个file文件,返回导入的结果。

具体使用是在controller层:

public Result imp(@RequestParam("file") MultipartFile file) {
        return userImportService.importData(file);
    }

userImportService 就是具体的实现类,是ImportService接口的业务实现。

又因为Excel导入通常都需要这么几个步骤:解析文件 -> 校验数据 -> 保存数据 -> 返回导入结果。

因此我们可以再单独封装一个抽象类。

public abstract class AbstractImportService implements ImportService {

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public Result importData(MultipartFile file) {
        checkFileType(file.getOriginalFilename());

        List dataList = parseData(file);
        ImportResultDto resultDTO = checkData(dataList);
        if (resultDTO == null || CollectionUtils.isEmpty(resultDTO.getErrorList())) {
            this.saveData(dataList);
            return Result.success();
        }
        return Result.fail(ResultEnum.SYSTEM_OP_ERROR.getCode(), "导入失败", resultDTO);
    }

    /**
     * 解析文件中的数据
     *
     * @param file 文件
     * @return
     */
    protected abstract List parseData(MultipartFile file);

    /**
     * 校验数据
     *
     * @param dataList 数据
     * @return
     */
    protected abstract ImportResultDto checkData(List dataList);

    /**
     * 处理数据
     *
     * @param dataList 数据
     */
    protected abstract void saveData(List dataList);

    /**
     *  校验文件类型
     */
    private static void checkFileType(String fileName) {
        if (StringUtils.isNotBlank(fileName) && fileName.contains(".")) {
            String suffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
            if (StringUtils.isNotBlank(suffix) && Arrays.asList(".xls", ".xlsx").contains(suffix)) {
                return;
            }
        }
        throw new BusinessInfoException(ResultEnum.SYSTEM_INNER_ERROR, "只支持excel文件导入,请检查!");
    }
}

这就是一个典型的模板方法模式,因为这个抽象类已经实现了接口,所以真正的实现类就不需要重复去实现了,只需要继承这个抽象类即可。

7.Java中有了基本类型为什么还需要包装类?

先说渊源,Java毕竟是面向对象的语言,很多地方都需要用到象,比如集合类中,是无法塞进基本类型的。

Java基础20问(6-10)_第1张图片

某杠精:你胡扯,我塞入int类型的压根不报错!

public static void main(String[] args) {
    List numbers = new ArrayList();
    numbers.add(1);
}

这是因为从JDK5开始,就支持了自动拆装箱系统,所以并不是集合可以装进基本类型,而是系统帮我们自动装箱了。

自动装箱都是通过包装类的 valueof()方法来实现的自动折箱都是通过包装类对象的xxxValue()来实现的。

已int为例:

Integer a = 1;     // 自动装箱
int b = a;         // 自动拆箱

相当于

Integer a = Integer.valueOf(1);     
int b = a.intValue();               

避坑指南

1.注意空指针

有的时候,我们会直接用包装类去参与四则运算,这个时候要注意包装类对象不能是null,因为自动拆箱会调用xxxValue()方法,就会有报空指针的风险。

public class Test {
    public static void main(String[] args) {
        Integer a = getCount();
        int b = a + 10;   
    }

    private static Integer getCount() {
        // 因为某种原因,返回了null
        return null;
    }
}

恭喜你,喜提 NullPointerException一份。

Java基础20问(6-10)_第2张图片

2.Entity中一定要用包装类

java老鸟都知道,Entity中一定不能用基本类型,因为基本类型有默认初始值,比如int类型默认就是0,如果结合Mybatis,会不小心把这个默认值误更新到数据库!而用Integer则没问题,因为对象默认为null,mybatis默认的策略就是不更新Null值,是安全的。

8.接口的返回值一定要用包装类

我们写接口给外部调用,按理说就必须要是一个明确的值,如果你接口返回boolean,默认是false,这个false我们也不知道究竟是计算后得到的,还是基本类型默认的,这就有二义性。

3.为什么用float会有精度丢失的问题?

要解答这个问题,我们得知道,计算机中用32bit来标识一个浮点数,即将一个数字转换为一个32位的二进制数。比如10,就是1010。表达整数没问题,但是并非所有小数都能用二进制表示,我们知道10进制转二进制用的是辗转相除法(除以2),那总有除不尽的时候吧?

所以,即便float有32bit,也是一个近似值。double也是一样的,只是精度更高了,有64bit。

对于金额的计算,Java提供了BigDecimal来表示和运算。

9.请说说String、StringBuilder和StringBuffer的区别?

可变性: String是不可变的,StringBuilder和StringBuffer是可变的。

为什么String要设计为不可变的呢?

首先String使用太频繁了,JVM单独开辟一块区域用作字符串缓存池,节省内存的开支。又因为String不可变,所以hashCode也不变,方便命中缓存提高效率,我们用HashMap的时候,你敢说自己不用String作为Key吗?一样的道理。

安全性: StringBuffer是线程安全的,而StringBuilder是非线程安全的。

StringBuffer 和 StringBuilder如何取舍?

StringBuilder是非线程安全的,也就意味着效率更高,在方法体里面,本来也不会涉及到线程安全问题,拼接较多的话值得使用。

尽量不要使用 + 来拼接字符串,因为 + 虽然编译时也会转成StringBuilder,但每次都会new一个,反而影响性能。

10.什么是泛型

泛型是JDK5引入的一种新特性,泛型包括泛型类和泛型方法,目的是保证数据类型的安全,常与集合类一起使用。

1.方便:可以提高代码的复用性。

以List接口为例,我们可以将String、Integer等类型放入List中,如不用泛型,存放String类型要写一个List接口,存放Integer要写另外一个List接口,泛型可以很好的解决这个问题。

ArrayList源码:

Java基础20问(6-10)_第3张图片

2.安全

在泛型出之前,通过Object实现的类型转换需要在运行时检查,如果类型转换出错,程序直接GG,可能会带来毁灭性打击。。而泛型的作用就是在编译时做类型检查这无疑增加程序的安全性。

3.什么是泛型擦除?

泛型擦除是指在编译过程中,泛型会被擦除,最终只会得到同一份字节码。因此,泛型主要的功能就是在编译时确保数据类型的正确性,防止把问题留到运行时。

你可能感兴趣的:(JavaSE成神之路,java,开发语言)