Java进阶03面向对象高级(续)
一、接口新特性
1、JDK8的新特性
1.1 允许在接口中定义非抽象方法
允许在接口中定义非抽象方法,但需要使用关键字default修饰,这些方法就是默认方法
-
作用:解决接口升级的问题
-
格式
public default 返回值类型 方法名(参数列表){} public default void show(){}
-
注意事项
①默认方法不是抽象方法,所以不强制被重写 (但是可以被重写,重写的时候去掉default关键字
②public可以省略,default不能省略
③如果实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写
1.2 允许在接口中定义静态方法
接口中允许定义static静态方法
-
格式
public static 返回值类型 方法名(参数列表){} public static void show(){}
-
注意事项
①静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
②public可以省略,static不能省略
2、JDK9的新特性
接口中允许定义private私有方法
-
格式
//格式1:private 返回值类型 方法名(参数列表){} public void show(){} //格式2:private static 返回值类型 方法名(参数列表){} private static void method(){}
-
应用场景
interface Inter { public default void start(){ System.out.println("start方法执行..."); System.out.println("日志记录"); } public default void end(){ System.out.println("end方法执行..."); System.out.println("日志记录"); } }
start方法和end方法中都有日志记录,为了提高代码复用性,减少冗余,可以将日志记录抽取为一个私有方法,让需要记录的方法调用即可
public default void log(){ System.out.println("日志记录"); }
二、代码块
在Java类下,使用 { } 括起来的代码被称为代码块
1、局部代码块(了解即可)
public static void main(String[] args) {
{
System.out.println("我是局部代码块");
}
}
-
位置:方法中
-
作用:限定变量的生命周期,提早释放内存,提高内存利用率
2、构造代码块(了解即可)
class Student {
{
System.out.printIn("我是构造代码块")
}
}
-
位置:类当中方法外
-
特点:每次创建对象的时候执行,而且优先于构造方法执行
-
作用:如果发现多个构造方法中,存在相同的代码,就可以【考虑】使用构造代码块优化提高代码复用性
3、静态代码块
class Student {
static {
System.out.println("我是静态代码块");
}
}
-
位置:类中方法外,需要被static修饰
-
特点:随着类的加载而执行,只执行一次
-
作用:常用于项目的一些初始化操作
4、相关面试题(重点)
public class CodeTest1 {
static {
System.out.println("CodeTest1的静态代码块执行了");
}
{
System.out.println("测试类构造代码块");
}
public static void main(String[] args) {
new Zi();
}
}
class Fu {
static {
System.out.println("Fu...static代码块执行了");
}
{
System.out.println("Fu类构造代码块");
}
public Fu() {
System.out.println("Fu类空参数构造方法");
}
public Fu(int num) {
System.out.println("Fu类带参数构造方法");
}
}
class Zi extends Fu {
static {
System.out.println("Zi...static代码块执行了");
}
{
System.out.println("Zi类构造代码块");
}
public Zi() {
super(); //这句代码不写系统也会自己加,分析运行过程时要注意
System.out.println("Zi类空参数构造方法");
}
public Zi(int num) {
System.out.println("Zi类带参数构造方法");
}
}
-
执行结果
CodeTest1的静态代码块执行了 Fu...static代码块执行了 Zi...static代码块执行了 Fu类构造代码块 Fu类空参数构造方法 Zi类构造代码块 Zi类空参数构造方法
-
执行过程分析
程序运行,加载主方法所在类的字节码文件进方法区,即执行CodeTest1类的静态代码块,打印CodeTest1的静态代码块执行了;
进main方法,new Zi(),用到了Zi类即加载Zi类的字节码文件,但Zi类继承了Fu类,所以先加载Fu类字节码文件进方法区,Fu类的静态代码块随即执行,打印Fu...static代码块执行了;
然后再加载Zi类字节码文件进方法区,Zi类的静态代码块也随即执行,打印Zi...static代码块执行了;
紧接着,有new进栈,调用Zi类空参构造new对象,由于Zi类空参构造的第一句系统默认会加一句super();,因此在创建对象的时候会先调用Fu类的空参构造,而Fu类中含有构造代码块,会优先于构造方法执行,所以先打印Fu类构造代码块,再打印Fu类空参数构造方法;
之后才应该执行Zi类空参构造,而Zi类也含有构造代码块,会优先其构造方法执行,所以先打印Zi类构造代码块,再打印Zi类空参数构造方法。
三、内部类
内部类就是定义在一个类里面的类
1、创建格式
外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
Outer.Inner in = new Outer().new Inner();
2、好处(了解)
-
内部类可以直接使用外部类成员,包括私有
-
可以更加合理的运用封装思想来设计对象
3、访问特点
3.1 内访外
内部类访问外部类成员可以直接访问,包括私有
3.2 外访内
需要创建对象访问
3.3 小Demo
class Outer{
int num =10;
class Inner{
int num = 20;
public void show(){
int num = 30;
System.out.printIn(num); //30
System.out.printIn(this.num); //20
System.out.printIn(Outer.this.num); //10
}
}
}
注意:在成员内部类中访问所在外部类对象的格式为:外部类.this
4、内部类的分类
4.1 成员内部类
成员内部类是最常见的一种内部类,定义一个成员内部类就和定义成员变量类似,直接在一个类的内部再定义一个类,和成员变量可以说是同级关系。
4.2 静态内部类
有static修饰的成员内部类
class Outer{
static class Inner{
}
}
-
创建对象格式
外部类名.内部类名 对象名 = new 外部类名.内部类对象(); Outer.Inner in = new Outer.Inner();
4.3 局部内部类(鸡肋语法,了解即可)
局部内部类放在方法、代码块、构造器等执行体中
public class InnerClassDemo3 {
public static void main(String[] args) {
A a = new A(); //为了调用show方法创建A类对象
a.show(); //为了调用show中的print方法调show方法
}
}
class A {
public void show() {
// 局部内部类
class B {
public void print() {
System.out.println("print...");
}
}
int a = 10;
B b = new B();
b.print(); //要想调用局部内部类的print方法,必须要调用它所在的show方法,而show方法又在A类中,想要调用必须创建A类对象,通过A类对象去调用show方法,然后在show方法中再创建B类对象去调用print,比较繁琐
}
}
注意:此处的class B前不能加权限修饰符,因为其所处的方法都已经是public了,再给他加权限修饰符是不被允许的。切记:局部不能加权限修饰符,成员可以加
4.4 匿名内部类
匿名内部类本质上是一个特殊的局部内部类(定义在方法内部)
-
前提:需要存在一个接口或类
-
格式
new 类名/接口(){ } //这段代码不仅实现了接口,还重写了方法,同时创建了对象 new MyInter(){ @Override public void show(){ System.out.println("匿名内部类(实现类)重写后的show方法...") } }; interface MyInter{ void show(); }
注意:new 类名(){} 表示继承这个类;new 接口名(){}表示实现这个接口
-
匿名内部类可以作为方法的实际参数进行传输
public class Test1 { /* 思路: 为了调用useInter方法 1. 编写实现类 2. 重写方法 3. 创建实现类对象, 作为参数传入 */ public static void main(String[] args) { useInner(new InnerImpl()); } //模拟:方法的形参是接口类型 //问题:这种情况,该传入的实参是什么? //回答:实现类对象 public static void useInner(Inner i) { i.show(); } } interface Inner{ void show(); } class InnerImpl implements Inner{ @Override public void show() { System.out.println("实现类重写show方法"); } }
-
public class Test1 { /* 匿名内部类: 可以将以上三个步骤, 折成一步完成.即 这段代码既实现了接口,又重写了方法,还创建了对象做为实参传入调用方法 */ public static void main(String[] args) { useInner(new Inner() { @Override public void show() { System.out.println("匿名内部类重写后的show方法"); } }); } public static void useInner(Inner i){ i.show(); } } interface Inner{ void show(); }
注意事项:掌握了匿名内部类,也不能完全舍弃传统的实现类写法
-
使用场景:当接口中的抽象方法个数只有1-2个的时候,使用匿名内部类最合适
-
特点:可以让代码更加简洁,定义一个类的同时对其进行实例化
四、Lambda表达式
1、概述
Lambda表达式是JDK8开始后的一种新语法格式
2、作用
简化匿名内部类的代码
3、简化格式
(匿名内部类被重写方法的形参列表)->{被重写方法的方法体}
注:->只是语法格式,无实际含义
注意:Lambda表达式只能简化函数式接口,函数式接口指有且仅有一个抽象方法的接口,通常会在接口上加一个校验注解@FunctionalInterface,也是标记该接口必须满足函数式接口。
-
这里有一个需要主义的细节:如果接口中声明的抽象方法是Object类中的方法结构,不算数
//此处该注解不报错,属于函数式编程,因为toString和equals方法都是Object类中的,即使声明也不算数 @FunctionalInterface interface MyInter{ void show(); public abstract String toString(); public abstract boolean equals(); }
4、Lambda简化规则
-
参数类型可以省略不写
-
如果只有一个参数,参数类型可以省略,同时()也可以省略
-
如果Lambda表达式的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号;此时,如果运行代码是return语句,必须省略return不写,同时也必须省略”;“不写
5、用Lambda表达式简化上述匿名内部类的代码
public class Test1 {
public static void main(String[] args) {
useInner(()->{System.out.println("Lambda表达式重写后的show方法");}); //简化后语句
}
public static void useInner(Inner i){
i.show();
}
}
@FunctionalInterface
interface Inner{
void show();
}
6、Lambda表达式和匿名内部类的区别
区别 | 使用限制不同 | 实现原理不同 |
---|---|---|
匿名内部类 | 可以操作类、接口 | 编译之后,产生一个单独的.class字节码文件 |
Lambda表达式 | 只能操作函数式接口 | 编译之后,没有一个单独的.class字节码文件 |
7、Q:接口有没有继承Object类?
A:表面上没有,实际上有。如果接口没有继承Object类,多态创建对象调用Object类的方法会出现编译错误。为了避免这个问题,底层是默认让该接口继承了Object类的所有方法
public class LambdaDemo2{
public static void main(String[] args){
//多态创建对象
MyInter m = new MyInterImple(); //编译看左,执行看右。如果没有继承Object,编译看左发现MyInter中没有toString会直接报错,无法编译
m.toString();
}
}
interface MyInter{
}
class MyInterImpl extends Object implements MyInter{
}
五、常用API
API(Application Programming interface)应用程序编程接口,简单来说,就是Java已经写好的一些类和方法,我们直接拿过来用就可以了
1、Object类
所有的类都直接或者间接的继承了 Object 类 (祖宗类)。Object类的方法是一切子类都可以直接使用的
1.1 Object类的toString方法
-
Object类的toString方法
默认返回当前对象在堆内存中的地址信息类的全类名@十六进制哈希值。
public String toString(){ return getClass().getName()+"@"+Integer.toHexString(hashCode()); }
-
getClass().getName():获取类的全类名
-
"@":分隔符
-
Integer.toHexString(hashCode()):十六进制哈希值,常被人称作地址值
然而在开发中,输出对象地址是毫无意义的,我们更多得是希望看到对象的内容,因此需要重写toString方法
-
-
自己重写toString
@Override public String toString(){ return name+","+age; }
-
IDEA生成toString
@Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; }
结论:如果今后打印对象名,看到的不是地址信值,说明这个类重写过toString方法。如果在该类源码中没看到toString方法,继续向上找,其父类肯定重写了toString方法
1.2 Object类的equals方法
-
Object类的equals方法
public boolean equals(Object obj){ return (this == obj) }
默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false。该方法存在的意义是为了子类能够根据自己的需求重写该方法,一般是让对象之间比较内容
-
自己重写方法
@Override //this:stu1对象 //obj:stu2对象 public boolean equals(Object obj){ if(obj instanceof Student){ Student stu = (Student)obj; return this.age == stu.age &&this.name.equals(stu.name); }else{ return false; } }
-
IDEA自动生成的重写方法
@Override //this:stu1 //o:stu2 public boolean equals(Object o) { //1、比较两个对象的地址,地址相同,直接返回true if (this == o) return true; /* 2.1、看传入的对象是否为null,是的话,直接返回false。 因为代码能执行到这里,说明stu1一定不是null(有数据),如果stu2是null(无数据),则直接false,不用再判断 2.2、比较两个对象产生的字节码,如果字节码不相同,代表类型不同,直接返回false */ if (o == null || getClass() != o.getClass()) return false; //代码执行到这里,说明类型相同且非空,可以放心向下转型 Student student = (Student) o; //比较内容 return age == student.age && Objects.equals(name, student.name); }
1.3 Objects
Objects类与Object还是继承关系,objects类是从JDK 1.7开始之后才有的
-
Objects常见方法
-
equals方法
比较两个对象,底层会先进行非空判断,从而可以避免空指针异常,再进行equals比较。注意:该方法底层依赖于我们重写的equals方法
//底层源码 public static boolean equals(Object a,Object b){ return (a == b)||(a != null && a.equals(b)); } //上述代码的另一种形式 public static boolean equals(Object a,Object b){ if(a == b){ return true; } if(a == null){ return false; }else{ return a.equals(b); } }
源码避免空指针异常的分析:若a是null值,则a!=null判断结果为false,由于双与(&&)具有短路效果,左边判断为false后,右边不执行,a就没有机会调用equals方法,避免了空指针调用;若a不是null值,则a!=null判断结果为true,双与(&&)左边为true,右边继续执行,a会调用我们重写的equals方法去比较内容
-
isNull方法
判断变量是否为null
//底层源码 public static boolean isNull(Object obj){ return obj == null; }
-