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<ImportResultDto> importData(MultipartFile file);}
importData 要求输入一个file文件,返回导入的结果。
具体使用是在controller层:
public Result<ImportResultDto> imp(@RequestParam("file") MultipartFile file) {return userImportService.importData(file);}
userImportService 就是具体的实现类,是ImportService接口的业务实现。
又因为Excel导入通常都需要这么几个步骤:解析文件 -> 校验数据 -> 保存数据 -> 返回导入结果。
因此我们可以再单独封装一个抽象类。
public abstract class AbstractImportService<T> implements ImportService {@Override@Transactional(rollbackFor = Throwable.class)public Result<ImportResultDto> importData(MultipartFile file) {checkFileType(file.getOriginalFilename());List<T> 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<T> parseData(MultipartFile file);/*** 校验数据** @param dataList 数据* @return*/protected abstract ImportResultDto checkData(List<T> dataList);/*** 处理数据** @param dataList 数据*/protected abstract void saveData(List<T> 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毕竟是面向对象的语言,很多地方都需要用到象,比如集合类中,是无法塞进基本类型的。
某杠精:你胡扯,我塞入int类型的压根不报错!
public static void main(String[] args) {List<Integer> numbers = new ArrayList<Integer>();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() {// 因为某种原因,返回了nullreturn null;}
}
恭喜你,喜提 NullPointerException一份。
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源码:
2.安全
在泛型出之前,通过Object实现的类型转换需要在运行时检查,如果类型转换出错,程序直接GG,可能会带来毁灭性打击。。而泛型的作用就是在编译时做类型检查这无疑增加程序的安全性。
3.什么是泛型擦除?
泛型擦除是指在编译过程中,泛型会被擦除,最终只会得到同一份字节码。因此,泛型主要的功能就是在编译时确保数据类型的正确性,防止把问题留到运行时。