现在已经是六月份了,为了九月份可以去一个好的公司,甚至是阿里,字节这样的大厂,我要开始准备背面试题了,从今天开始,每天打卡一次。
面向对象有三大特征,继承,封装,多态
封装,继承增加了代码的复用性,多态增加了代码的健壮性,灵活性。
ArrayList和LinkList都实现了List接口,有以下不同点
一、接口的默认方法
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展方法,示例如下:
代码如下:
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a); }}
Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。
代码如下:
Formula formula = new Formula() {
@Override public double calculate(int a) {
return sqrt(a * 100);} };formula.calculate(100); l/ 100.0 formula.sqrt(16);ll4.0
文中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算sqrt(a * 100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。
译者注:在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做mixin。新的Java 8的这个特新在编译器实现的角度上来说更加接近Scala的trait。在C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,和ava 8的这个在语义上有差别。
二、Lambda表达式
首先看看在老版本的Java中是如何排列字符串的:代码如下:
List<string> names = Arrays.asList("peterF", "anna", "mike" , "xenia");
Collections.sort(names, new Comparator<string>() {
@Override public int compare(String a, String b){
return b.compareTo(a); }});
只需要给静态方法Collections.sort传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。
在Java 8中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:代码如下:
Collections.sort(names, (String a, String b)-> {
return b.compareTo(a); });
看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:代码如下:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
对于函数体只有一行代码的,你可以去掉大括号以及return关键字,但是你还可以写得更短点:代码如下:
Collections.sort(names, (a, b) -> b.compareTo(a));
Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还能作出什么更方便的东西来:
三、函数式接口
Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而"函数式接口"是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为默认方法不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加@Functionallnterface注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
示例如下:代码如下:
@Functionalnterface interface Converter<F,T> {
T convert(F from); } Converter<String, Integer> converter =(from) -> Integer.valueOf(from); Integer converted = converter.convert("123");
System.out.printIn(converted);ll 123
需要注意如果@Functionallnterface如果没有指定,上面的代码也是对的。
译者注将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如RhinoJavaScript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有org.w3c.dom.events.EventTarget的
addEventListener第二个参数EventListener。
四、方法与构造函数引用
前一节中的代码还可以通过静态方法引用来表示:代码如下:
Converter<String, Integer> converter = Integer:valueOf; Integer converted = converter.convert("123");System.out.printIn(converted); l1 123
Java 8允许你使用:关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:
代码如下:
converter = somethingstartsWith; String converted = converter.convert("Java");System.out.println(converted); l/ "J"
接下来看看构造函数是如何使用:关键字来引用的,首先我们定义一个包含多个构造函数的简单类:代码如下:
class Person {
String firstName; String lastName;Person()0
Person(String firstName, String lastName) {
this.firstName = firstName; this.lastName = lastName;}}
接下来我们指定一个用来创建Person对象的对象工厂接口:
代码如下:
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName); }
这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:
代码如下:
PersonFactory<Person> personFactory = Person:new; Person person = personFactory.create(""Peter","Parker");
我们只需要使用Person:new来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
五、Lambda 作用域
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。
六、访问局部变量
我们可以直接在lambda表达式中访问外层的局部变量:代码如下:
final int num = 1; Converter<Integer, String> stringConverter =(from) -> String.valueOf(from + num);stringConverter.convert(2); 1/ 3
但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:代码如下:
int num = 1;Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);stringConverter.convert(2);1l 3
不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:代码如下:
int num = 1;Converter<Integer, String stringConverter = (from)-> String.valueOf(from + num); num = 3;
在lambda表达式中试图修改num同样是不允许的。
七、访问对象字段与静态变量
和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:代码如下:
class Lambda4 {
static int outerStaticNum; int outerNum;
void testScopes() {
Converter<Integer, String> stringConverter1 = (from)-> {
outerNum = 23; returnString.valueOf(from); };
Converter<Integer, String> stringConverter2 =(from) -> {
outerStaticNum = 72; returnstring.valueOf(from); 3; }}
相同:
不同:抽象类:
不同:接口:
什么时候用接口,时候用抽象
抽象和接口的语义是不一样的,抽象描述的是一种抽象概念,比方说“动物”,“植物”,“玩具”,而接口描述的是一类事物的共同特征比方说“会飞的”,“会跑的”,“长得帅的”,“长得美帝的”等等打个比方,迪迦可以有一个叫做“奥特曼”的抽象类,一个“会飞的”接口。
JDK1.8HashMap底层实现原理
equals和==最大的区别是一个是方法一个是运算符。
注意: equals方法不能用于基本数据类型的变量,如果没有对 equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址。
Java通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。
在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java的异常处理是通过5个关键词来实现的: try、catch、throw、throws和finally。
在Java应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。
throw和throws的区别:
位置不同:
throw:方法内部
throws:方法的签名处,方法的声明处
内容不同:
throw+异常对象(检查异常,运行时异常)throws+异常的类型(可以多个类型,用,拼接)
作用不同:
throw:异常出现的源头,制造异常。
throws:在方法的声明处,告诉方法的调用者,这个方法中可能会出现我声明的这些异常。然后调用者对这个异常进行处理:要么自己处理要么再继续向外抛出异常