重点
1.接口
2.lambda表达式
3.内部类
1.接口
-
接口用于描述类应该做什么,而不是指定它们具体怎么做
-
接口的属性:
- 接口不是类(即不能使用
new
实例化一个接口) - 可使用
instanceof
检查一个对象是否实现某接口 - 可使用
extends
扩展接口 - 接口中不能包含实例字段,但可包含常量
- 接口中的方法自动为
public
,接口中的字段为public static final
- 每个类只能有一个超类,但可实现多个接口
- 接口不是类(即不能使用
-
接口可以提供多重继承的大多数好处,还能避免多重继承的复杂性和低效性
-
Java8中允许在接口中添加静态方法;Java9中允许方法为private方法,并且该方法可以是静态方法也可以是实例方法:
interface A{
public static void test(){
System.out.println("ooo");
}
}
interface B{
private int test1(){
return 1;
}
private static int test2(){
return 2;
}
}
-
可以为任何接口方法提供默认实现,但必须用
default
修饰,但是会带来冲突:- 如果一个类实现了两个接口,并且这两个接口都提供了相同方法的默认实现,则编译器会报错:
class C implements A, B {} // 编译器报错 interface A{ default void test(){ System.out.println("interfaceA"); } } interface B{ default void test(){ System.out.println("interfaceB"); } }
- 如果一个类继承了超类并且实现了接口,该超类和接口都有相同的方法,接口也对该方法提供了默认实现,则最终会采取类优先(即只考虑超类方法,接口中的默认方法被忽略,这也是为Java7提供兼容性):
class C extends A implements B {} // 创建C类的实例c,如果执行c.test()打印“super class” class A{ public void test(){ System.out.println("super class"); } } interface B{ default void test(){ System.out.println("interface"); } }
-
Object
的clone
方法默认是浅拷贝(即对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存)。浅拷贝存在的问题就是可能改变原对象中的属性(因为指向了同一个对象),但是也是需要区分情况:-
原对象和浅克隆对象共享的子对象为不可变对象(如
String
类型的对象),这种共享是安全的,比如Employee
类中存在String
类型的name
属性,此时原对象为emp
,克隆对象为emp2
,emp2
修改了name
属性的值后会重新创建一个String
类型对象,意味着原对象中该属性的值不会被改变 -
原对象和浅克隆对象共享的子对象为可变对象(如
Date
类型对象),这种共享就不安全
-
-
所有数组类型会存在公共的clone方法,而不是定义
protected
,调用后会创建原数组所有元素的副本:
int[] a = {1, 2, 3};
int[] b = a.clone();
b[1] = 22;
System.out.println(a[1]); // 还是2
2.lambda表达式
该表达式是一个可传递的代码块,可以在以后执行一次或多次
-
lambda
表达式的语法规则:- 即使表达式没有参数,也需要提供空括号:
()->{ for(int i = 0; i < 10; i++){ System.out.println(i); } }}
- 如果可推导出表达式的参数类型,则可忽略其类型:
// 不需要写为String first和String second Comparator<String> cmp = (first, second) -> first.length() - second.length()
- 如果方法中只有一个参数,而且参数类型可以推导出来,可以省略小括号:
ActionListener l = event -> xxx;
- 表达式的返回类型会由上下文推导出来,无需指定:
// 不使用表达式 class LenComparator implements Comparator<String>{ public int compare(String first, String second){ return first.length() - second.length(); } } Arrays.sort(xxx, new LenComparator()); // 使用后 Arrays.sort(xxx, (first, second) -> first.length() - second.length());
- 可以使用
var
指示推导的类型:
(@NonNull var first, @NonNull var second)-> first.length() - second.length();
- 表达式只在某些分支返回一个值,其他分支没有返回值是不合法的:
(int x)->{ if(x >= 0){ return 1; } // 还应该返回值 }
-
函数式接口是只有一个抽象方法的接口,如果需要这种接口的对象时,可以提供一个
lambda
表达式(即lambda
表达式可以转换为函数式接口):
// 底层会接收实现了Comparator<String>的某个类的对象,在该对象上调用compare方法后会执行表达式中的内容
Array.sort(arr, (first, second)->first.length() - second.length());
-
在
lambda
表达式中,对于变量的限制有以下几点:- 只能引用值不会改变的变量:
public static void countDown(int start, int delay){ ActionListener listener = event -> { start--; // 报错,因为在表达式中修改变量会引起并发安全的问题 }; new Timer(delay, listener).start(); }
- 不能引用在外部会改变的变量:
public static void repeat(){ for(int i = 1; i <= 10; i++){ System.out.println(i); // 报错,i在不断改变,因此不能捕获i } new Timer(1000, listener).start(); }
- 表达式中不能声明和方法局部变量同名的参数(毕竟还是在同一个方法中的,需要符合基本要求):
public void test(){ String first = "psj"; // 报错,已经有first变量了 Comparator<String> comp = (first, second)->first.length() - second.length(); }
- 表达式中使用的
this
是创建表达式的方法的this
:
public class Application{ public void init(){ // 调用的是Application的toString方法 ActionListener l = event -> System.out.println(this.toString()); } }
-
lambda
表达式的重点在于延迟执行(毕竟如果要立即执行代码就无需包装在表达式中的,直接写就是):
3.内部类
-
使用内部类的原因:
- 内部类可以对同一个包中的其他类隐藏
- 内部类方法可以访问定义这些方法的作用域中的数据,包括私有数据
- 非静态内部类的对象存在隐式引用,指向实例化该对象的外部类对象(意味着可以访问外部对象的全部状态);静态内部类没有该引用
-
内部类的特殊语法规则:
- 内部类中声明的所有静态字段都必须是
final
- 内部类不能有
static
方法 - 不同于外部类,内部类可以声明为
private
(可以将内部类理解为外部类的一个属性,属性当然可以私有)
public class Test { private class inner{ // 可以为private static int a = 1; // 报错,需要加上final并赋值 static void test(){} // 报错,不能使用static } }
- 内部类中声明的所有静态字段都必须是
-
局部内部类:
- 声明局部内部类不能使用
public
或private
- 局部内部类的作用域限定在声明该类的块中
- 局部内部类对外界隐藏(下面代码只有
ATest
方法知道innerA
类的存在) - 局部内部类可以访问局部变量,也可以访问外部类的字段
// 局部内部类(innerA)适用于该类只出现在外部类(A)某个方法(ATest)中使用 public class A{ String name = "psj"; public void ATest(int num){ class innerA{ // 这里的innerA不需要继承某个类或接口,不同于匿名内部类 public void innerATest(){ System.out.println(num); // 访问局部变量 System.out.println(name); // 访问外部类的字段 } } innerA a = new innerA(); a.innerATest(); } public static void main(String[] args) { A a = new A(); a.ATest(10); // 输出10和psj } }
- 声明局部内部类不能使用
-
匿名内部类:下面代码可以理解为创建一个类的对象,这个类继承了
B
类,并且定义了需要实现的方法innerATest
- 匿名内部类不能有构造器(构造器要求和类名一致,它连类名都没有)
- 匿名内部类可以提供一个对象初始化块
// 想要创建某个类(B)的一个对象,可以不需要为类指定名字 public class A{ String name = "psj"; public void ATest(int num){ var b = new B(){ // new xxx()的xxx可以是接口也可以是类 public void innerATest(){ { // 对象初始化块 } System.out.println(num); System.out.println(name); } }; b.innerATest(); } } class B{}
其他知识点
BigDecimal
类中的equals
方法和compareTo
方法不兼容:
BigDecimal x = new BigDecimal("1.0");
BigDecimal y = new BigDecimal("1.00");
System.out.println(x.equals(y)); // false,因为两个数的精度不同
System.out.println(x.compareTo(y)); // 0(表示相等)
-
回调是一种常见的设计模式,可以指定某个特定事件发生时采取的动作
-
对于
protected
方法在子类中可用的意思是指子类代码中可以用该方法,不是在其他包下创建子类的对象后也可以使用(对于private
方法也同理,指的是在本类代码中可以使用该方法)
// 包1下
public class A {
protected void test(){}
}
// 包2下
public class B extends A {
// 在静态方法中之所以不能直接调用非静态方法,是因为没有this指针,但是如果new一个对象就相当于有一个指针
public static void main(String[] args) {
B b = new B();
b.test(); // 可以使用,因为B是A的子类,B无论在哪个包下,在其类中都可以使用protected方法
}
}
// 包3下
public class C {
public static void main(String[] args) {
B b = new B();
b.test(); // 报错,因为C不是A的子类,无法在代码中调用test方法
}
}
public class A {
private void test(){}
public static void main(String[] args) {
A a = new A();
a.test(); // 可以使用,此时该方法是在本类中的
}
}
class B{
public static void main(String[] args) {
A a = new A();
a.test(); // 报错
}
}