文章目录
基础8
abstract - 抽象类
- 如果一个类的所有子类都对这个类中的某个方法做了重写,那么这个时候这个类中的对应的方法可以不定义方法体,需要用abstract修饰方法,从而成为了一个抽象方法。
- 抽象方法所在的类必须是抽象类。— 抽象类中不一定有抽象方法
- 抽象类不能创建对象
- 抽象类一定不是最终类
- 注意:任何一个类都有构造方法
- 抽象方法没有方法体,一定要被重写。
- 抽象方法可以定义被static/final/private修饰吗?—不行
- Static修饰的不能被重写!叫隐藏
- 抽象方法一定不能定义在最终类中。
- 如果一个类中的抽象方法用的是默认权限,对子类有什么要求?— 要求父子类要同包
- 子类如果不是抽象类,子类继承抽象类之后,必须重写父类的抽象方法,是的话,可以不重写。
- 抽象类中可以定义一切方法,包括抽象方法,普通方法,构造方法(子类创建对象的时候会用到)!
- 抽象类中如果有抽象方法,子类不是抽象类,子类要重写父类所有抽象方法,所以父类抽象类一定不定用final修饰!
- 抽象方法没有方法体,所以一定要被重写,才有意义!
- abstract修饰的是抽象方法
interface — 接口
- 接口中定义都是抽象方法(JDK1.8以前)。
- 作用:作为模板或者是协议、约束等来使用。
- 在JDK1.8以前,接口中只能定义抽象方法 — 默认用public abstract修饰。接口中可以定义属性但是默认使用public static final修饰。从JDK1.8开始,接口中允许定义实体方法,必须用default修饰,也可以定义静态方法。
- 类和接口之间用的是implements关键字来产生关联 — 实现。类在实现接口之后需要重写接口中所有的抽象方法
- 接口不允许被实例化,也没有构造方法
- 在Java中,支持的是类和接口之间的多实现 — 一个类可以实现多个接口
- 在Java中,支持接口之间的多继承
- 接口是用 接口.属性/方法 来进行调用
- JDK1.8
- 把只有一个抽象方法的接口,称为函数式接口@FunctionalInterface
- 允许使用静态方法,实体方法
// 如果一个接口中只定义了一个抽象方法,那么把这个接口声明为函数式接口
// 函数式接口用@FunctionalInteface
@FunctionalInterface
interface Calc {
//抽象方法没有方法体
public double add(double i, double j);
// 接口中的默认方法(实体方法) --- 默认是public
public default double max(double i, double j) {
return i > j ? i : j;
}
//静态方法
public static double min(double i, double j) {
return i < j ? i : j;
}
}
内部类
局部/方法内部类
- 定义在方法中的类 — 方法/局部内部类 – - 为了重复使用某段逻辑,并且使这段逻辑只从属于某一个方法使用
- 方法内部类只能在定义他的方法中使用!
- 当方法内部类使用所在的方法中的数据的时候,要求这个属性必须是一个常量,但从JDK1.8开始,方法内部类使用到当前方法中的数据的时候,会将该数据默认为常量,称为常量的隐式声明
-方法内部类只能在定义它的方法中使用 - 可以使用外部类中的属性和方法
- 如果内部类和外部类存在了同名属性或者方法,则使用内部类中定义,就近原则,如果要调用外部类的,需用 外部类.this.方法名/属性
- 方法内部类只能用abstract/final
- 方法内部类中可以定义非静态的属性和非静态方法,但是不能定义静态变量和静态方法,但可以定义静态常量
- 从这几个点思考: 对象(地址)、存储/执行、生命周期(作用范围)
成员内部类
- 可以使用外部类中的属性和方法
- 可以定义非静态属性和非静态方法 ,但是不能定义静态变量和静态方法 然而定义静态常量
定义在类中类
Outer2.Inner2 oi2 = new Outer2().new Inner2();
静态内部类
- 用static修饰的内部类
- 静态内部类可以用abstract修饰!
- 只能使用外部类中的静态属性和静态方法,静态内部类中可以定义一切的方法和属性
- 就处在成员位置上,跟静态方法类似,只不过是类
Outer3.Inner3 oi3 = new Outer3.Inner3();
匿名内部类
- 匿名内部类本质上是实现了对应的接口或者是继承了对应的类
- 任何一个接口都可以存在匿名内部类形式
- 一个类只要可以被继承,那么就可以存在匿名内部类形式 — 最终类不存在匿名内部类形式
- 扩展:类中可以定义类,类中也可以定义接口,接口中可以定义类,接口中也可以定义接口 —
如果类中定义了接口或者是接口中定义了接口,那么称之为内部接口 — 类中定义的接口,以及接口中定义的类和接口默认都是静态的
class A {
static interface A1 {}
}
interface B {
static class B1 {}
static interface B2{}
}
包
- 声明包用的是package — 区分同名类,进行功能的划分
- 导入包用的是import — 导包的作用是用于提示代码从哪儿去找这个类
- " * " 通配符 ---- 表示导入当前包下的所有的类但是不包括子包下的类
- java — 原生包 javax — 扩展包
- org — 第三方厂商提供的一些常用的包
- java.lang -核心/基本包,包含了Java程序运行需要的基本类。在Java程序启动的时候,包下的类就已经自动加载到内存中,所以使用的时候可以不用导包
- java.util - 工具包 java.math - 数学运算 java.io - 数据传输 java.net - 网络通信
java.nio - 高并发 java.text - 格式化 - 总结:java.lang包下的类以及子包类在使用的时候可以不用导包
垃圾分代回收机制
- 针对的是堆内存。
- Java中的每种数据类型大小都是确定的,所以所有的内存是由Java自己进行分配,意味着内存的管理和回收也是由JVM自己进行—在Java中一旦产生内存问题导致程序员无法处理。理论上在正常情况下Java中的堆内存是足够使用的
— 当堆内存使用的负荷量(一般情况下70%)超过一定限度的时候,会启动垃圾回收器(Garbage Collector — GC)进行堆内存的回收释放
Heap space
Young Generation
eden
survivor
from space
to space
Old Generation
扩展:eden:from:to = 8:1:1 - 对象刚创建的时候是先放入新生代中的伊甸园区;如果在伊甸园区经过一次回收依然存在,那么将这个对象挪到幸存区,在幸存区中经过多次回收这个对象依然存在则挪到老生代。在回收的时候先回收新生代,如果新生代回收之后的内存足够使用则不扫描老生代;如果不够则扫描老生代。老生代的扫描频率要低于新生代
- 发生在新生代的回收 — 初代回收 minor gc
- 发生在老生代的回收 — 完全回收 full gc
- 扩展:对象创建完成之后会先试图放入新生代;如果新生代经过回收之后也放不开,则直接试图将该对象放入老生代。老生代如果也放不开,则会出现错误— OutOfMemoryError
Lambda
package cn.tedu.lambda;
public class LambdaDemo {
public static void main(String[] args) {
// 接口中只定义了1个抽象方法
// 可以利用Lambda表达式来重写这唯一的一个抽象方法
// Calc c = new Calc() {
//
// @Override
// public double add(double i, double j) {
// return i + j;
// }
//
// };
// 表示重写Calc中的唯一的一个抽象方法add
// Lambda表达式只能作用在函数式接口上
// Calc c = (double a, double b) -> {
// return a + b;
// };
// 方法体只有一句,可以省略{}和return不写
// 唯一的一句方法体的计算结果默认为当前方法的返回值
// Calc c = (double i, double j) -> i + j;
// 重写的是Calc接口中的方法add
// add方法的参数列表的类型是已知的
// 可以省略参数类型不写
// 函数式编程
Calc c = (x, y) -> x + y;
System.out.println(c.add(5.8, 9.47));
}
}
interface Calc {
double add(double i, double j);
}
更简化Lambda表达式
package cn.tedu.lambda;
public class LambdaDemo2 {
public static void main(String[] args) {
// 方法体只有1句
// 这一句方法体是直接操作参数
// 这一句方法体是调用了已有类Math中的静态方法sqrt
// 参数列表可以省略
// Calculator c = d -> Math.sqrt(d);
Calculator c = Math::sqrt;
System.out.println(c.sqrt(9));
}
}
interface Calculator {
double sqrt(double d);
}
小知识
- API — Application Programming Interface — 应用程序接口 — 接口以及实现类
局部 / 方法内部类 例子
目录,再往上一拉
package exer1;
public class InnerDemo1 {
public static void main(String[] args) {
new Outer1().m();
}
}
class Outer1 {
int i = 10;
public void m() {
System.out.println("Outer~~~");
// 当方法内部类使用所在的方法中的数据的时候
// 要求这个属性得是一个常量
// 从JDK1.8开始,方法内部类使用到当前方法中的数据的时候
// 将该数据默认为常量
// 常量的隐式声明
int j = 10;
// 方法内部类
// 只能在定义它的方法中使用
// 可以使用外部类中的属性和方法
// 如果内部类和外部类存在了同名属性或者方法,则使用内部类中定义
// 方法内部类只能用abstract/final
// 方法内部类中可以定义非静态的属性和非静态方法
// 但是不能定义静态变量和静态方法
// 然而定义静态常量
class Inner1 extends Object implements Cloneable {
int k = 8;
static final int x = 7;
public void m() {
System.out.println("Inner~~~");
i += 5;
m2();
// 外部类.this.外部类的方法或者属性
Outer1.this.m2();
System.out.println(j);
// j += 6;
System.out.println(k + 1);
System.out.println(x);
}
public void m2() {
System.out.println("Inner m2~~~");
}
}
Inner1 i1 = new Inner1();
i1.m();
}
public void m2() {
System.out.println("Outer m2~~~");
}
}
成员内部类—例子
package cn.tedu.innerclass;
public class InnerDemo2 {
public static void main(String[] args) {
// Outer2 o2 = new Outer2();
// System.out.println(o2.i);
// o2.m();
Outer2.Inner2 oi2 = new Outer2().new Inner2();
System.out.println(oi2.j);
}
}
class Outer2 {
public int i = 3;
Inner2 i2 = new Inner2();
// 成员内部类
// 可以使用外部类中的属性和方法
// 可以定义非静态属性和非静态方法
// 但是不能定义静态变量和静态方法
// 然而定义静态常量
class Inner2 {
int j = 10;
static final int k = 8;
public void m() {
i += 3;
Outer2.this.m();
}
}
public void m() {
System.out.println("Outer");
}
}
匿名内部类 — 例子
package cn.tedu.innerclass;
public class InnerDemo4 {
public static void main(String[] args) {
// 匿名内部类
// a是匿名内部类产生的对象
// 匿名内部类实际上是实现了对应的接口
A a = new A() {
@Override
public void m() {
System.out.println("running~~~");
}
@Override
public void m2() {
}
};
a.m();
// 匿名内部类实际上是继承了对应的类
B b = new B() {
@Override
public void m() {
System.out.println("running~~~");
}
};
b.m();
// C c = new C() {
// };
}
}
interface A {
void m();
void m2();
}
abstract class B {
public abstract void m();
}
final class C {
}
类型转化 — 自动转/强转
package cn.tedu.interfacex;
public class InterfaceDemo2 {
public static void main(String[] args) {
// A a = new B1();
// 在Java中支持的是类和类之间的单继承
// 所以此时会形成一棵继承结构树
// 所以比较容易的就能确定两个类之间是否有继承关系
// 因此在进行强制转换的时候
// 会检查要转换的对象的声明类和转换的类型是否有继承关系
// a对象的声明类型是A类,要转换的类型是B1
// B1继承了A,所以在编译时期就不报错
// 到了运行的时候才会检查对象的实际类型和要转换的类型是否一致
// 运行的时候,发现a的实际类型是B1,要转换的类型是B1
// 类型一致,允许转换
// B1 b1 = (B1) a;
// a对象的声明类型是A类,要转换的类型是B2
// B2继承了A,所以在编译时期就不报错
// 到了运行的时候,a的实际类型是B1,要转换的类型是B2
// 类型不一致,所以报错 --- ClassCastException
// B2 b2 = (B2) a;
// b1对象的声明类型B1类,要转换的是B2
// B2没有继承B1,编译就不通过
// B2 b2 = (B2) b1;
// C c = (C) a;
// 在Java中,类和接口之间是多实现,接口和接口之间是多继承
// 所以构成了一张图状结构 --- 网状结构
// 不容易确定两个节点之间的关系
// 因此在编译时期为了提高效率放弃检查(不会报编译错误)
// 直到运行的时候再确定类型是否相同
// D d = (D) a;
// C c = new C();
// D d = (D) c;
}
}
class A {
}
class B1 extends A {
}
class B2 extends A {
}
class C {
}
interface D {
}