目录
一 接口
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法 , 和字段 。 而接口中包含的方法都是抽象方法(Java8之后可以有静态方法)。
1.1 实现接口
interface IShape {
void draw();
int num = 10;
}
- 使用 interface 定义一个接口
- 接口中的方法一定是抽象方法, 因此可以省略 abstract
- 接口中的方法一定是 public, 因此可以省略 public
- 接口中的字段一定是public static final, 因此可以省略
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("○");
}
}
public class Test {
public static void main(String[] args) {
IShape shape = new Rect();
shape.draw();
}
}
- 使用 implements 实现接口。
- 实现接口必须重写抽象方法, 方法必须加上public (子类重写方法必须不低于父类访问权限)
- 在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
- 接口不能单独被实例化
- 接口的命名一般以大写字母 I 开头.
- 接口的命名一般使用 "形容词" 词性的单词.
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性
2.2 继承与实现:
实现多个接口:
Java 中只支持单继承, 但是可以同时实现多个接口,达到多继承类似的效果。
示例:
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
class Duck implements IRunning, IFlying {
String name;
@Override
public void fly() {
System.out.println(this.name + "正在用翅膀飞");
}
@Override
public void run() {
System.out.println(this.name + "正在用两条腿跑");
}
}
继承与实现:
下面的代码代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口。
示例:
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
class Duck extends Animal implements IRunning, IFlying {
Duck(String name){
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在用翅膀飞");
}
@Override
public void run() {
System.out.println(this.name + "正在用两条腿跑");
}
接口间的继承:
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字。
示例:
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
上述例子中, 若一个类实现了接口 IAmphibious, 则要实现 run 方法, 也需要实现 swim 方法。
多态
可以使用instanceof检查一个对象是否实现了某个特点的接口
示例:
interface IShape {
void draw();
int num = 10;
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("○");
}
}
class Test {
public static void main(String[] args) {
IShape shape = new Cycle();
if(shape instanceof IShape)
System.out.println("shape实现了ISape");
}
}
多态示例:
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public static void fly(IFlying running) {
running.fly();
}
}
interface IFlying {
void fly();
}
class Chicken extends Animal implements IFlying {
public Chicken(String name) {
super(name);
}
@Override
public void fly() {
System.out.println("我飞不起来");
}
}
class People extends Animal implements IFlying {
public People(String name) {
super(name);
}
@Override
public void fly() {
System.out.println("五段起飞!");
}
}
public class Demo {
public static void main(String[] args) {
Chicken chicken = new Chicken("小鸡");
People people = new People("上官婉儿");
Animal.fly(chicken); //我飞不起来
Animal.fly(people); //五段起飞!
}
}
2.3 接口的默认方法
可以为接口方法提供一个 默认 实现。必须用default修饰符标记这样一个方法
示例:
interface IFlying {
default void fly() {
System.out.println("I can fly");
}
}
class Chicken implements IFlying {
}
public class Demo {
public static void main(String[] args) {
Chicken chicken = new Chicken();
chicken.fly(); //I can fly
}
}
默认方法的一个重要用法是"接口演化", 即早已存在的接口,多添加一个非默认方法, 会导致已经实现的子类出现 源代码兼容 问题。(已经编译好的类, 仍能正常加载)。
默认方法冲突:
多个接口中定义同样的默认方法, 超类中也定义同样的方法,继承超类并实现这些接口将会有二义性。
语法规则:
超类优先: 超类提供了一个具体方法, 则忽略其他接口的默认方法。
接口冲突: 实现的两个接口中, 其中一个提供了默认方法, 另一个接口提供了同名(参数列表相同)方法, 必须覆盖这个方法来解决冲突。
示例:
interface IFlying {
default void fly() {
System.out.println("I can fly");
}
}
interface IRunning {
void fly();
}
class Chicken implements IFlying, IRunning {
@Override
public void fly() { //有二义性, 必须重写
IFlying.super.fly(); //或者自定义fly()方法
}
}
public class Demo {
public static void main(String[] args) {
Chicken chicken = new Chicken();
chicken.fly(); //I can fly
}
}
2.4 比较接口 与 克隆接口
对象的比较:Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较,而==默认情况下调用的就是equal方法(自定义类,都默认继承自Object类)。
2.4.1 Comparable 接口:
实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序
示例:
class People implements Comparable<People> {
String name;
People(String name) {
this.name = name;
}
@Override
public int compareTo(People o) { //在类中实现对象比较
return this.name.length() - o.name.length();
}
@Override
public String toString() {
return name;
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
People[] p = {new People("5548"), new People("666"), new People("19112")};
Arrays.sort(p);
System.out.println(Arrays.toString(p)); //输出:[666, 5548, 19112]
}
}
2.4.2 Comparator 接口:
Comparator 被称为外部比较器,当类的对象没有实现 Comparable 接口时, 我们可以通过“实现Comparator 类来新建一个比较器”,然后通过该比较器对类进行排序。
示例:
class People {
String name;
People(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
People[] p = {new People("5548"), new People("666"), new People("19112")};
Arrays.sort(p, new Comparator<People>() { //匿名类实现Comparator接口
@Override
public int compare(People o1, People o2) {
return o1.name.length() - o2.name.length();
}
});
System.out.println(Arrays.toString(p)); //输出:[666, 5548, 19112]
}
}
2.4.3 三种方式对比:
2.4.4 Cloneable 接口:
拷贝与克隆:
标记接口: Cloneable接口是一个标记接口,标记接口没有任何方法, clone方法从Object中继承而来。它唯一的作用是允许在类型查询中使用instanceof。另外, 没有实现Cloneable接口, 对该类对象克隆就会出现 异常。
深拷贝与浅拷贝:
默认的克隆是浅拷贝
区别示意图:
深拷贝示例:
class Age implements Cloneable { //克隆接口标记
int age;
Age(int age) {
this.age = age;
}
@Override
public String toString() {
return age + "";
}
@Override
protected Age clone() throws CloneNotSupportedException { //重写clone方法
return (Age) super.clone();
}
}
class Student implements Cloneable { //克隆接口标记
String name;
Age Sage; //引用变量
Student(String name, Age age) {
this.name = name;
this.Sage = age;
}
@Override
protected Student clone() throws CloneNotSupportedException { //重写clone方法
Student s = (Student) super.clone();
s.Sage = s.Sage.clone();
return s;
}
@Override
public String toString() {
return "老师: " + Sage + " 学生: " + name;
}
public static void main(String[] args) throws CloneNotSupportedException {
Student s1 = new Student("张三", new Age(18));
Student s2 = s1.clone();
}
}
备注: 重写clone方法的访问修饰符必须是public
2.5 抽象类与继承类的区别
二 lambda 表达式
Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。
2.1 基本语法:
(parameters) -> expression 或 (parameters) ->{ statements; }
用法:
//没有参数,有返回值
()->{return 2};
()->return 2; //只有一条语句,可以省略{}
()->2; //只有一条语句,可以省略return
//一个参数,有返回值
(int a)->a;
(a)->a; //参数列表对应唯一的方法,可以省略参数类型
a->a; //只有一个参数,可以省略()
//两个参数,有返回值
(a,b)->return a+b; //等同于(a,b) -> a+b;
//有参数,没有返回值
(a,b)->System.out.println(a+b);
2.2 函数式接口
定义:一个接口有且只有一个抽象方法,可在接口上加上 @FunctionalInterface 注解, 加上注解后, 出现两个及以上的抽象方法, 编译器会报错。
在Java中, lambda表达所能做的也只是转化为 函数式接口。
示例:
Comparator接口原码图:
lambda表达式转化函数式接口:
class Aa {
public static void main(String[] args) {
Integer[] nums = {6, 5, 2, 7};
Arrays.sort(nums, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
//等价于上述代码
Arrays.sort(nums, (a, b) -> {
return a - b;
});
}
}
2.3 方法引用
基本语法: 类型::方法名
示例:
@FunctionalInterface
interface IRunning {
void fly(Object a);
}
class Aa {
public static void test(Object a, IRunning run) {
run.fly(a);
}
public static void main(String[] args) {
test("lambda表达式", (b) -> System.out.println(b)); //输出: lambda表达式
test("方法引用", System.out::println); //输出: 方法引用
}
}
只有lambda表达式只调一个方法时, 才能把lambda表达式重写为 方法引用
2.4 构造器引用
构造器引用与方法引用类似, 只需把方法名改为new: 类名::new
示例:
知识有限...
2.5 变量作用域
示例:
class Aa {
public void func(String[] args) {
int abc = 10;
Integer[] nums = {6, 5, 2, 7};
Arrays.sort(nums, (a, b) -> {
System.out.println(abc);
return a - b;
});
ActionListener listener = e -> {
System.out.println(this.toString());
};
}
}
在lambda作用域中,引用的变量abc是事实最终变量。可以使用abc但不能给abc赋值。
规则:
- lambda表达式中捕获的变量必须是 事实最终变量 (被final修饰或使用之前没有修改)
- lambda表达式中不能声明一个与局部变量同名的参数变量
- 上述例子中, lambda表达式中的this指示的对象是Aa类对象
自由变量值: 指非参数而且不在代码中定义的变量
闭包: lambda表达式就是闭包
2.6 缺点与优点
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- Java 引入 Lambda,改善了集合操作
- 代码可读性变差
- 在非并行计算中,很多计算未必有传统的 for 性能要高
- 不容易进行调试
三 内部类
内部类是定义在另一个类中的类
3.1 基本语法:
示例:
class Aa {
class Bb { //内部类
}
}
备注:
- 内部类可以对同一个包下的其他类隐藏
- 内部类方法可以访问和定义这个类的作用域中的数据,包括私有数据
- 内部类可以用 private关键字 修饰
3.2 访问外部类对象:
示例:
class Aa {
private boolean flag;
class Bb {
void print() {
if (flag)
System.out.println("正在运行");
}
}
public void start() {
Bb bb = new Bb();
bb.print();
}
public static void main(String[] args) {
Aa a = new Aa();
a.flag = true;
a.start();
}
}
原理:
内部类的对象有一个隐式引用, 指向创建他的外部类对象, 这个引用在内部类定义中的不可见的
在内部类中, 原理是:
class Bb {
Aa a; //原理,实际并不存在
Bb(Aa a){ //原理,实际并不存在
this.a=a;
}
void print() {
if (a.flag) //原理,a对象并不存在
System.out.println("正在运行");
}
}
生成Bb对象的方法, 原理是:
public void start() {
Bb bb = new Bb(this); //原理, this不存在
bb.print();
}
特殊语法规则:
事实上,使用外围类引用的正规语法比上述还要复杂
内部类访问外部类的私有对象,可以这样编写:
class Bb {
void print() {
if (Aa.this.flag)
System.out.println("正在运行");
}
}
外部类引用被设置为创建内部类成员方法的this引用:
public static void main(String[] args) {
Aa a = new Aa();
a.flag = true;
Aa.Bb bb = a.new Bb();
bb.print();
}
备注:
- 内部类中声明的所有静态字段必须是final, 并赋初始值。
- 内部类不能有静态方法
编译原理:
编译器会将内部类转换为常规类文件, 用$分割外部类名与内部类名
我们用 javap -private ClassName 命令行看看:
Bb类:
CSDN是我们的包名, 编译器生成了一个额外的实例字段 this$0, 对应外围类的引用, 还可以看到构造器Aa$Bb的Aa类型参数
内部类是如何访问外部类的私有成员呢? 让我们看看
原因:
编译器在外部类添加了静态方法access$000。它将返回作为参数传递的那个对象的 flag 对象。
即:
if (Aa.this.flag)
System.out.println("正在运行");
实际上是调用:
if (access$000(this$0))
System.out.println("正在运行");
总言之, 如果内部类访问了外部类私有字段, 外围类会(自动)增加相应的访问字段的静态方法
如果内部类是私有内部类, 则:
非私有内部类:
私有内部类:
可以看到, 私有内部类生成了带一个参数的私有构造器方法和带两个参数的构造器(构成重载方法, 只是为了与私有构造器区别开来), 私有构造器其他人无法调用, 因此需要通过带两个参数的构造器来调用私有构造器。
3.3 局部内部类:
示例:
class Aa {
private boolean flag;
private int count;
public void start() {
class Bb {
void print() {
if (count == 0)
System.out.println("正在运行");
}
}
Bb bb = new Bb();
bb.print();
}
}
备注:
- 局部内部类是在一个方法中被定义
- 局部内部类不能有访问修饰符( private , public ), 作用域限定在方法的块中
- 局部内部类对外不可见
访问外部变量:
局部内部类可以访问外部类字段, 也可以访问局部变量。但局部变量必须是事实最终变量
如:
class Aa {
private boolean flag;
public void start(int count) {
class Bb {
void print() {
if (count == 0)
System.out.println("正在运行");
}
}
Bb bb = new Bb();
bb.print();
}
}
实际情况 (javap) : 暂略
3.4 匿名内部类:
示例1 :
class Test {
public void func() {
System.out.println("func()");
}
}
public class TestDemo {
public static void main(String[] args) {
new Test() {
@Override
public void func() {
System.out.println("我是内部类,且重写了func这个方法!");
}
};
}
}
示例2 :
interface People {
void eat();
}
class Aa {
public static void main(String[] args) {
new People(){
@Override
public void eat() {
System.out.println("螺蛳粉");
}
};
}
}
双括号初始化:
public class Demo {
public static void acc(List list) {
System.out.println(list);
}
public static void main(String[] args) {
acc(new ArrayList<String>() {{
add("a");
add("b");
}});
}
}
3.5 静态内部类
当内部类不需要外部类对象的一个引用时, 可以将内部类声明为 static。
示例:
public class Demo {
public static class Pair { //静态内部类
private double max;
private double min;
public Pair(double f, double s) {
max = f;
min = s;
}
public double getMax() {
return max;
}
public double getMin() {
return min;
}
}
public static Pair minmax(double[] values) {
Arrays.sort(values);
return new Pair(values[0], values[values.length - 1]);
}
public static void main(String[] args) {
Pair p = minmax(new double[]{5, 9, 2, 17, 4});
}
}
备注:
- 静态内部类可以有静态字段和静态方法
- 接口中声明的内部类自动是 static 和 public