知识要点-面向对象
1.抽象方法和抽象类
- 定义抽象方法只需在普通方法上增加abstract修饰符,并把普通方法的方法体(方法后花括号括起来的那部分)全部去掉,在最后增加分号即可
//定义一个Shape抽象类
public abstract class Shape{
{
System.out.println("执行Shape的初始化块");
}
private String color;
//定义一个计算周长的抽象方法
public abstract double calPerimeter();
//定义一个返回形状的抽象方法
public abstract String getType();
//定义Shape的构造器,该构造器并不是用于创建Shape对象
//而是用于被子类调用
public Shape(){}
public Shape(String color){
System.out.println("执行Shape的构造器");
this.color = color;
}
}
- Shape类里包含了两个方法,因为有抽象方法所以只能被定义为抽象类,不能创建实例,只能当做父类被其他子类继承
//下面定义一个三角形类,三角形类被定义成普通类
public class Triangle extends Shape{
//定义三角形的三边
private double a;
private double b;
private double c;
public Triangle(String color, double a,double b,double c){
super(color);
this.setSides(a,b,c);
}
public void setSides(double a,double b,double c){
if (a >= b + c || b >= a + c || c>= a + b){
System.out.println("三角形两边之和必须大于第三边");
return;
}
this.a = a;
this.b = b;
this.c = c;
}
//重写Shape类的计算周长的抽象方法
public double calPerimeter(){
return a+b+c;
}
//重写Shape类的返回形状的抽象方法
public String getType(){
return "三角形";
}
}
//下面定义一个Circle类,也是Shape的一个子类
public class Circle extends Shape{
private double radius;
public Circle(String color, double radius){
super(color);
this.radius = radius;
}
public void setRadius(double radius){
this.radius = radius;
}
//重写Shape类的计算周长的抽象方法
public double calPerimeter(){
return 2 * Math.PI * radius;
}
//重写Shape类的返回形状的抽象方法
public String getType(){
return getColor() + "圆形";
}
public static void main(String[] args){
Shape s1 = new Triangle("黑色",3,4,5);
Shape s2 = new Circle("黄色",3);
System.out.println(s1.getType());
System.out.println(s1.calPerimeter());
System.out.println(s2.getType());
System.out.println(s2.calPerimeter());
}
}
- 上面代码main方法中定义了两个Shape类型的引用变量,它们分别指向Triangle对象和Circle对象,由于在Shape类中定义了calPerimeter()方法和getType()方法,所以程序可以直接调用s1变量和s2变量的两个方法,无需强制类型转换为其子类类型
- 利用抽象类和抽象方法的优势,可以更好地发挥多态的优势
- final和abstract永远不能同时使用,因为final定义的类不能被继承,定义的方法也不能被重写,这与abstract相悖
- static和abstract也不能同时修饰,因为没有所谓的类抽象方法;但是可以同时修饰内部类
- private和abstract不能同时使用,abstract修饰的方法必须被子类重写,private访问权限会使子类无法重写
抽象类体现的是一种模板模式的设计,作为多个子类的通用模板
2.接口
- 一种更加特殊的“抽象类”,接口里不能包含任何普通方法,接口里的所有方法都是抽象方法
- Java 8 对接口进行了改进,允许在接口中定义默认方法
- Java 8 中接口的定义
[修饰符] interface 接口名 extends 父接口1,父接口2...{
零到多个常量定义...
零到多个抽象方法定义...
零到多个内部类,接口,枚举定义...
零到多个默认方法或类方法定义...
}
- 修饰符使用public或者省略,省略默认采用包权限
- 接口只能继承接口,不能继承类
- 接口不能包含构造器和初始化块定义,接口里可以包含成员变量(只能是静态常量),方法(只能是抽象实例方法,类方法或者默认方法),内部类(包括内部接口,枚举)定义
- 接口中的静态常量无论是否使用修饰符,总是使用public static final来修饰
- 接口中定义默认方法,用default修饰
- 接口的用途:1.定义变量,也可以用于进行强制类型转换;2.调用接口中定义的常量;3.被其他类实现
- 一个类可以实现一个或多个接口,继承使用extends关键字,实现使用implements关键字,类实现接口的语法如下
[修饰符] class 类名 extends 父类 implements 接口1,接口2....{
类体部分
//implements必须放在extends之后
}
- 一个类实现了一个或多个接口后,必须完全实现接口里所定义的全部抽象方法,否则该类也必须定义成抽象类
//一个实现接口的类
//定义一个Product接口
interface Output{
//接口里定义的成员变量只能是常量
int MAX_CACHE_LINE = 50;
//接口里定义的普通方法只能是public的抽象方法
void out();
void getData(String msg);
//在接口中定义默认方法,需要使用default修饰
default void print(String... msgs){
for(String msg : msgs){
System.out.println(msg);
}
}
//在接口中定义默认方法,需要使用default修饰
default void test(){
System.out.println("默认的test()方法");
}
//加接口中定义类方法,需要使用static修饰
static String staticTest(){
return "接口里的类方法";
}
}
interface Product{
int getProduceTime();
}
//让Printer类实现Output和Product接口
public class Printer implements Output, Product{
private String[] printData = new String[MAX_CACHE_LINE];
//用来记录当前需打印的作业数
private int dataNum = 0;
public void out(){
//只要还有作业,就继续打印
while (dataNum > 0){
System.out.println("打印机打印:"+ printData[0]);
//把作业队列整体前移一位,并将剩下的作业数减1
System.arraycopy(printData,1,printData,0,--dataNum);
}
}
public void getData(String msg){
if(dataNum >= MAX_CACHE_LINE){
System.out.println("输出队列已满,添加失败");
}else {
//把打印数据添加到队列里,已保存的数量加1
printData[dataNum++] = msg;
}
}
public int getProduceTime(){
return 45;
}
public static void main(String[] args){
//创建一个Printer对象,当成Output使用
Output o = new Printer();
o.getData("轻量级Java学习");
o.out();
//调用Output接口中定义的默认方法
o.print("孙悟空","猪八戒");
o.test();
//擦行间一个Printer对象,当成Product使用
Product p = new Printer();
System.out.println(p.getProduceTime());
//所有接口类型的引用变量都可以直接赋给Object类型的变量
Object obj = p;
}
}
3.接口和抽象类比较
它们都有如下特征:
- 都不能被实例化,都位于继承树的顶端,用于被其他类实现和继承
- 都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法
区别:
- 接口提现的是一种规范,对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务;对于接口的调用者而言,接口规定了调用者可以调用哪里服务,以及如何调用这些服务;接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准
- 抽象类作为系统中多个子类的共同父类,它所体现的时一种模板式设计,抽象类作为多个子类的抽象父类,可以被当成是系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能,但这个产品依然不能当成最终产品,必须由更进一步的完善
除此之外,还有如下差别
4.面向接口编程
5.内部类(嵌套类)
public class Cow{
private double weight;
//外部类的两个重载的构造器
public Cow(){}
public Cow(double weight){
this.weight = weight;
}
//定义一个非静态内部类
private class CowLeg{
//非静态内部类的两个实例变量
private double length;
private String color;
//非静态内部类的两个重载的构造器
public CowLeg(){}
public CowLeg(double length,String color){
this.length = length;
this.color = color;
}
public double getLength() {
return length;
}
public String getColor() {
return color;
}
public void setLength(double length) {
this.length = length;
}
public void setColor(String color) {
this.color = color;
}
//非静态内部类的实例方法
public void info(){
System.out.println("当前牛腿颜色是:" + color + ",高:" + length);
//直接访问外部类的private修饰的成员变量
System.out.println("本牛腿所在奶牛重:" + weight);
}
}
public void test(){
CowLeg cl = new CowLeg(1.12,"黑白相间");
cl.info();
}
public static void main(String[] args){
Cow cow = new Cow(378.9);
cow.test();
}
}
6.Java 8改进的匿名内部类
- 适合创建只需要使用一次的类
new 实现接口() | 父类构造器(实参列表){
//匿名内部类的类体成分
}
- 必须继承一个父类,或者实现一个接口,但是最多只能继承一个父类或实现一个接口
- 匿名内部类不能是抽象类,系统在创建匿名内部类时,会立即创建匿名内部类的对象
- 匿名内部类不能定义构造器,可以定义初始化块
7.Java 8 新增的Lambda表达式
- Lambda表达式支持将代码块作为方法参数(类似js中的箭头函数?)
(形参)->{方法}
- 只有一条语句可以省略花括号,只有return可以省略return
8.Lambda表达式与函数式接口
- Lambda表达式的目标类型必须是“函数式接口”,即只包含一个抽象方法的接口,但是可以包含多个默认方法,类方法
//Runnable接口中只包含一个无参数的方法
//Lambda表达式代表的匿名方法实现了Runnable接口中唯一的,无参数的方法
//因此下面的Lambda表达式创建了一个Runnable对象
Runnable r = () -> {
for(int i = 0; i < 100; i ++){
System.out.println();
}
}
9.方法引用和构造器引用
10.枚举类
- 枚举类:一个类的对象(实例)有限而且固定
- Java 5新增enum关键字用于定义枚举类,一个java源文件中最多只能定义一个public访问权限的枚举类,该java源文件也必须和该枚举类的类名相同
-
enum枚举类与普通类区别如下
public enum SeasonEnum{
//在第一行列出四个枚举实例
SPRING,SUMMER,FALL,WINNER;
}
11.对象与垃圾回收
- 垃圾回收机制只负责回收堆内存中的对象
- 程序无法精确控制垃圾回收的运行
- 在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而取消回收
- 对象在堆内存中运行时,可以把它所处的状态分为三种:
1.可达状态:当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态
2.可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象所占的内存,在回收该对象之前,系统会调用所有可恢复对象的finalize()方法进行资源清理。如果系统在调用finalize()方法时重新让一个引用变量引用该对象则这个对象会再次变为可达状态,否则该对象进入不可达状态
3.不可达状态:当对象与所有引用变量之间的关系都被切断,系统已调用过finalize()方法,这个对象会永久失去引用,最后变成不可达状态,这时系统才会真正回收该对象所占的资源
12.强制垃圾回收
- 程序可以强制系统进行垃圾回收--只是通知系统进行垃圾回收,并不确定是否进行
1.System.gc();
2.Runtime.getRuntime().gc();
13.finalize方法
特点:
- 永远不要主动调用某个对象的finalize方法,该方法应交给垃圾回收机制调用
- finalize()方法何时被调用,是否被调用具有不确定性
- 当JVM执行课恢复对象的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态
- 当JVM执行finalize()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行
14.对象的软,弱,虚引用
- 强引用:最常见,把对象赋给一个引用变量就是强引用
- 软引用:需要通过SoftReference类来实现,内存足够时不会被系统回收,内存不足时系统可能会回收,通常用于对象内存敏感的程序中
- 弱引用:需要通过WeakReference类实现,不管内存是否足够,都会被回收
- 虚引用:需要通过PhantomReference类实现,类似于没有引用,不能单独使用,必须和引用队列联合使用
15.修饰符的适用范围
16.使用JAR文件
- Java Archive File,压缩文件,与ZIP兼容,也被称作JAR包,jar中默认一个META-INF/MANIFEST.MF的清单文件
- 使用JAR文件好处:1.安全;2.加快下载速度,只需要简历一次HTTP链接;3.压缩大小;4.包封装,能够让JAR包里面的文件依赖于同一版本的类文件;5.可移植性,作为内嵌在Java平台内部处理的标准,能够在各种平台上直接使用