今天主要回顾一下 Java 面向对象的最后一部分的知识,算是对面向对象的一个总结了吧!
先来讲两个关键字吧!
1. abstract
如果一个类的所有子类都对这个类中的某个方法做了重写,那么这个时候这个类中的对应方法可以不定义方法体,需要用abstract修饰方法,从而成为一个抽象方法。抽象方法所在的类必须是抽象类。
抽象类不能创建对象。 --- 抽象方法一定是抽象类,但抽象类中不一定有抽象方法。
抽象类一定不是最终类,因为最终类不能被继承。
注意:任何一个类都有构造方法
抽象方法没有方法体,一定要被重写。
抽象方法可以定义被 static/final/private 修饰吗? --- 不行
抽象方法中一定不能定义在最终类中。
如果一个类中的抽象方法用的是默认权限,对子类有什么要求? --- 要求父子类要同包
package abstractx; public class AbstractDemo { //不允许被实例化 //Pet p = new Pet(); //p.eat(); //创建的是匿名内部类 Pet p = new Pet(){ @Override public void eat() { // TODO Auto-generated method stub } }; } //不想让这个类实例化,就定义一个抽象类 abstract class Animal{} //抽象类 abstract class Pet{ // public Pet(){} //抽象方法,因为长得像方法,但是没有方法体 public abstract void eat();/*{ //System.out.println("在吃东西~~~");//有没有方法体都不重要了 }*/ public void drink(){ System.out.println("在喝水中~~~"); } } //子类继承抽象类之后必须重写其中的抽象方法,除非子类也是抽象类 class Cat extends Pet{ @Override public void eat(){ System.out.println("这只猫在吃土~~~"); } } class Dog extends Pet{ @Override public void eat(){ System.out.println("这只狗在吃猫~~~"); } }
练习:定义一个类表示形状,提供获取周长和面积的方法,然后给这个类提供子类:矩形 - 正方形,椭圆 - 圆形
package abstractx; public class AbstractExer { public static void main(String[] args) { Shape f; f = new Rectangle(3,4); System.out.println(f.getGirth() + "," + f.getArea()); f = new Oval(4,5); System.out.println(f.getGirth() + "," + f.getArea()); } } abstract class Shape{ public abstract double getGirth(); public abstract double getArea(); } //代表矩形 class Rectangle extends Shape{ double width; double height; public Rectangle(double width, double height){ this.width = width; this.height = height; } @Override public double getGirth() { return 2 * (width + height); } @Override public double getArea() { // TODO Auto-generated method stub return width * height; } } //正方形 class Square extends Rectangle{ private double width; public Square(double width) { super(width, width); } } //椭圆形 class Oval extends Shape{ private double a; private double b; public static final double PI = 3.14; public Oval(double a, double b){ this.a = a; this.b = b; } @Override public double getGirth() { // TODO Auto-generated method stub return PI * a * b; } @Override public double getArea() { // TODO Auto-generated method stub return PI * (a + b); } } //圆形 class Circle extends Oval{ private double r; public Circle(double r){ super(r, r); } }
2. interface
(作用:作为模板或者是协议、约束等来使用)
接口中定义的都是抽象方法。类和接口之间用的是 implements 关键字来产生关联 --- 实现。类和实现接口之后需要重写接口中所有的抽象方法
接口不允许被实例化,也没有构造方法。
在 Java 中,支持的是类和接口之间的多实现 --- 一个类可以实现多个接口
在 Java 中,支持接口之间的多继承(接口可以继承接口),
在 JDK1.8 中,接口中允许定义实体方法 -- 这个是实体方法必须用 default 修饰
package cn.tedu.interfacex; import java.io.Serializable; public class InterfaceDemo { public static void main(String[] args) { System.out.println(Shape.girth); //Shape.girth = 1;//说明是使用final修饰的 //接口不允许实例化 //Shape s = new Shape(); } } interface Shape extends Cloneable,Serializable{ double girth = 0;//默认是用的 static,final //public Shape(){}不允许使用 //接口中的方法默认public abstract修饰 /*public abstract */double getGirth(); public abstract double getArea(); } //利用implements关键字让类和接口产生了联系 --- 实现 //Rectangle实现了 Shape接口 /*class Rectangle implements Shape, Cloneable{ } */ interface E{ void main(); } /*class B implements E{ void main() {//报错了,范围不一样 } }*/
关于接口的实现中,我们有:
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;//编译通过,但是运行会报错 B2 b3 = (B2) b1;//编译直接不通过 C c = (C) a;*///编译直接不通过 //在 Java 中,类和接口之间是多实现,接口和接口之间是继承关系 //所以构成了一张图状结构 --- 网状结构, //不容易确定两个节点之间的关系 //因此在编译时期为了提高效率放弃检查 //直到运行时候在确定类型是否是否相同 D d = (D) a;//编译通过,但是运行报错 } } class A{} class B1 extends A{} class B2 extends A{} class C{} interface D{}
接着我们谈谈关于内部类方面的知识吧!!
内部类
1. 方法内部类
定义在方法中的类 --- 方法/局部内部类 --- 为了重复使用某段逻辑,并且使这段逻辑只从属于某一个方法使用
package cn.tedu.innerclass; public class InnerDemo1 { public static void main(String[] args) { Outer1 outer1 = new Outer1(); outer1.m(); } } class Outer1{ int i = 10; public void m(){ System.out.println("Outer~~~"); //当方法内部类使用所在的方法中的数据的时候, //要求这个属性的是一个常量 //从JDK1.8开始,方法内部类使用到当前方法中的数据的时候,将该数据默认为常量,不能改变 //常量的隐式声明: int j = 5; //方法内部类 //只能在定义它的方法中使用 //可以使用外部类中的属性何方法 //如果内部类和外部类存在同名属性或者方法,则使用内部类中定义 //只能用abstract/final修饰 //方法内部类可以定义非静态的属性和非静态方法 //但是不能定义静态变量和静态方法 //然而定义静态常量 class Inner1{//不能用static 修饰 int k = 8; static final int x = 7;//会报错,加final变成一个常量 public void m(){ System.out.println("Inner~~~"); i += 5; m2(); //外部类.this.外部类的方法或者属性 Outer1.this.m2(); System.out.println(j); //j++;//会报错 } public void m2(){ System.out.println("Inner m2~~~"); } } Inner1 i1 = new Inner1(); i1.m(); } public void m2(){ System.out.println("m2~~~"); } }
2. 成员内部类
Outer2.Inner2 oi2 = new Outer2().new Inner2();
package cn.tedu.innerclass; public class InnerDemo2 { public static void main(String[] args) { Outer2 o2 = new Outer2(); Outer2.Inner2 oi2 = new Outer2().new Inner2();//利用外部类对象创建内部类 System.out.println(oi2.j); } } class Outer2{ int i = 3; Inner2 i2 = new Inner2(); //成员内部类 //可以使用外部类中的属性和方法 //可以定义非静态属性和非静态方法 //但是不能定义静态属性和静态方法 //然而定义静态常量 class Inner2{//修饰不受什么限制 int j = 10; static final int k = 8;//也是要加final public void m(){ i += 3; Outer2.this.m();//调用外部类中的,不然就是递归了 } } public void m(){ System.out.println("Outer~~~"); } }
3. 静态内部类
用 static 修饰的类
Outer3.Inner3 oi3 = new Outer3.Inner3();
package cn.tedu.innerclass; public class InnerDemo3 { public static void main(String[] args) { Outer3.Inner3 oi3 = new Outer3.Inner3(); oi3.m(); } } class Outer3{ int i = 0; //静态内部类 //只能使用外部类中的静态属性和静态方法 //静态内部类中可以定义一切的方法和属性,无论静态还是非静态 static class Inner3{ int j = 8; static int k = 4; public void m(){ //System.out.println(i);//报错 } } }
4. 匿名内部类
匿名内部类本质上是实现了对应的接口或者是继承了对应的类
任何一个接口都可以存内在匿名内部类形式
一个类只要可以被继承,那么就可以存在匿名内部类形式 --- 最终类不存在匿名内部类的形式
package cn.tedu.innerclass; public class InnerDeom4 { public static void main(String[] args) { //匿名内部类 //a是匿名内部类产生的对象 //匿名内部类实际上是实现了对应的接口 A a = new A(){ @Override public void m() { System.out.println("running"); } }; a.m(); //匿名内部类实际上是继承了对应的类 B b = new B(){ @Override public void m() { System.out.println("running"); }}; b.m(); //C c = new C(){}; } } interface A{ void m(); } abstract class B{ public abstract void m(); } final class C{}
扩展:类中可以定义类,类中也可以定义接口,接口中也可以定义类,接口中也可以定义接口 --- 如果类中定义了接口或者式接口中定义了接口,那么称之为内部接口 --- 类中定义的接口,以及接口中定义的类和接口默认都是静态的。
谈到这儿,还有个 Lambda 表达式的内容:
package cn.tedu.innerclass; public class LambdaDemo { public static void main(String[] args) { //接口中只定义了1个抽象方法 //可以利用 /*Calc c = new Calc(){ @Override public double add(double i, double j) { // TODO Auto-generated method stub return i + j; }};*/ //表示重写Calc中的唯一的一个抽象方法add //Lambda表达式只能作用在函数式接口上 //方法体只有一句,可以省略{}和return 不写 //唯一的依据方法体的计算结果默认为当前方法的返回值 //Calc c = (double i, double j) -> i + j; //重写的是 Calc 接口中的方法add //add方法的参数列表的类型是已知的 //可以省略参数类型不写 //函数式编程 Calc c = (x, y) -> x + y; System.out.println(c.add(4.3, 2.5)); } } interface Calc{ double add(double i, double j); }
接着,前面打一个小结,接下来进行包的介绍:
包
声明包用的是 package --- 区分同名类,进行功能的划分
导入包用的是 import --- 导包的作用是用于提示代码从哪儿去找这个类(比如,我们前段时间经常使用的 Scanner、Arrays)
这个类
* 表示导入当前包下的所有的类但是不包括子包下的类
java --- 原生包,提供的一些常用的包,sun 公司自己用的
javax --- 扩展包,后阶段用的比较多
org --- 第三方厂商提供的一些常用的包,sun公司感觉比较好就收过来了
java.lang - 核心/基本包( System String) ,包含了 Java 程序运行需要的基本类,在 Java 程序启动的时候,包下的类就已经自动加载到内存中,所以使用的时候可以不用导包
java.awt
java.applet --- JDK 1.9 后大部分类已经被抛弃了
java.util - 工具包
java.math --- 数学运算
java.io ---数据传输
java.net --- 网络通信,后面介绍
java.nio --- 高并发,服务器有关的包,双十一的时候抢购处理这些数据
java.text --- 格式化,超市去买东西,结账系统,8.88,* 3 * 0.88 出现 4 位小数,现实生活中是两位,就用这个包
总结:java.lang 包下的类以及同包类在使用的时候可以不用导包
包就是一个用来声明和导入的,现阶段还没有什么更多的知识介绍,以后遇到了,再介绍吧!!接下来谈谈垃圾分代回收方面的知识:
垃圾分代回收机制
(针对的是堆内存。)
java 中的每种数据类型大小都是确定的,所以所有的内存是由 Java 自己进行分配,意味着内存的管理和回收也是由 JVM 自己进行 --- 在 Java 中一旦产生内存问题导致程序员无法处理。(在 C 和 C++ 中就是自己来定义管理的)理论上在正常情况下 Java 中的堆内存是足够使用的 --- 当堆内存使用的负荷量(堆内存的 70 %,可能有的时候又不一样)超过一定限度的时候,会启动垃圾回收器(Garbage Collector --- GC)进行堆内存的回收释放 ---
C int 只要不超过 4 个字节就行。
int i = 3; --- 3
int j = i; --- 1
扩展:eden:from :to = 8:1:1
上图的简介:对象刚创建的时候是先放入新生代中的伊甸园区;如果在伊甸园区经过一次回收依然存在,那么将这个对象挪到幸存区,在幸存区中经过多次回收这个对象依然存在则挪到老生代。在回收的时候先回收新生代,如果新生代回收之后的内存足够使用则不扫描老生代。老生代的扫描频率要低于新生代
发生在新生代的回收 --- 初代回收 minor gc
发生在老生代的回收 --- 完全回收 full gc
(对于双十一,购物完之后就产生和销毁,应该将新生代设置得大一点,方便对象的创建)
扩展:对象创建完成之后会先试图放入新生代;如果新生代经过回收之后也放不开,则直接试图将该对象放入老生代。老生代如果也放不开,则会出现错误 --- OutOfMemoryError.