10、面向对象编程(高级)
类变量和类方法:
类变量:
假设我们现在有这样一个场景,有一群小孩在玩堆雪人,不时有小孩子加入,我们要统计小孩子的数量。假如我们用原始的在main函数中count++,能够实现这个功能,但是我们存在一些问题:
- count是独立于对象存在的;
- 我们访问count很麻烦,没有用到OOP的方法。
为此,我们引入了类变量(静态变量)的概念。
package com.jiangxian.static_;
public class ChildGame {
public static void main(String[] args){
Person person = new Person("1");
Person.count++;
Person person2 = new Person("2");
Person.count++;
Person person3 = new Person("3");
Person.count++;
System.out.println("现在有" + Person.count + "个小孩。");
}
}
class Person{
public static int count;
private String name;
public Person(String name){
this.name = name;
}
}
其内存布局:
在Java8以前,是放在方法区中,在Java8以后则放在堆中。
但我们其实只需要记住一点:static 变量是对象共享。
不管 static 变量在哪里,有以下两个共识:
- static 变量是同一个类所有对象共享;
- static 类变量,在类加载的时候就生成了。
类变量的定义:
类变量也叫做静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
任何使用类变量:
语法:
- 强烈推荐: 访问修饰符 static 数据类型 变量名;
- static 访问修饰符 数据类型 变量名;
如何访问类变量:
类名.类变量;
对象名.类变量。
类变量使用注意事项和细节:
- 什么时候需要用类变量?
- 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)。
- 类变量与实例变量的区别:
- 类变量是该类以及该类的所有对象共享的,而实例变量是每个类的对象所独有的;
- 加上static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量;
- 类变量可以通过 类名.变量名 或者 对象名.变量名来访问,但是比较推荐使用前者(前提是要满足访问修饰符的访问权限和范围。)
- 实例变量 不能通过 类名.变量名访问;
- 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了;
- 类变量的生命周期是随类的加载开始,随着类消亡而销毁。
- 在定义的类中,类变量只能接收静态方法的返回值。
类方法:
基本介绍:
类方法也叫静态方法,形式如下:
- 访问修饰符 static 数据返回类型 方法名(){}——推荐;
- static 访问修饰符 数据返回类型 方法名(){}。
方法调用:
使用方法:类名.类方法名 或者 对象名.类方法名(前提是满足修饰符的访问权限和范围)。
实例:
package com.jiangxian.static_;
public class StaticMethod {
public static void main(String[] args) {
System.out.println(Mytools.sum(10,30));
}
}
class Mytools{
// 求出两个数的和
public static double sum(double a, double b){
return a + b;
}
}
类方法经典使用场景:
当方法中不涉及到任何和对象相关的成员,则可以将方法设计为静态方法,提高开发效率。
比如:工具类中的方法utils:
Math类、Arrays类、Collections 集合类 看下源码。
类方法使用注意事项和细节讨论:
- 类方法和普通方法否是随着类的加载而加载,将结构信息存储在方法区
- 类方法中无this的参数;
- 普通方法中隐含着this的参数。
- 类方法可以通过类名调用,也可以通过对象名调用;
- 普通方法和对象有关,需要通过对象名调用,比如 对象名.方法名(参数),不能通过类名调用;
- 类方法中不允许使用和对象有关的关键字,比如this和super,普通方法则可以;
- 类方法中(静态方法)中,只能访问 静态变量 或静态方法;
- 普通成员方法,既可以访问静态的成员,非静态的方法,可以访问静态成员和非静态成员。
小结:
静态变量,只能访问静态的成员,非静态的方法,可以访问静态的成员和非静态的成员。
理解main方法语法:
深入理解main方法:
解释main方法:public static void main(String[] args){}
- main方法是虚拟机调用的!
- java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public;(java虚拟机在调用的时候根本不在同一个类);
- java虚拟机在执行main()方法的时候,不必创建对象,所以必须是static;
- 该方法接收的是String类型的数组参数,该数组中保存执行java命令时传递给所运行类的参数;
- java 执行的程序 参数1 参数2 参数3【例子:】
public class Hello{
public static void main(String[] args){
// args 是如何传入的
// 遍历显示:
for(int i = 0; i < args.length; i++){
System.out.println("第" + (i+1) + "个参数:" + args[i]);
}
}
}
D:\Code\Project\javacode>java Hello Tom Tim
第1个参数:Tom
第2个参数:Tim
“Hello”,“Tom”,"Tim"被打包成一个字符串数组传递给了main方法。
特别提示:
- 在main()方法中,我们可以直接调用main方法所在的静态方法和属性;
- 但是不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
package com.jiangxian.main_;
public class Main01 {
// 静态属性:
private static String name = "Java 笔记";
// 静态方法:
public static void hi(){
System.out.println("hi");
}
// 非静态的变量/属性:
private int n1 = 100;
// 非静态的方法:
private void cry(){
System.out.println("cry");
}
public static void main(String[] args) {
// 可以直接访问本类的 静态属性
System.out.println(name);
// 可以直接访问本类的 静态方法:
hi();
// 不能访问本类的非静态成员
// System.out.println(n1);
// 也不能访问本类的非静态方法
// cry();
// 想要使用只能创建一个对象,才能间接的调用:
Main01 obj = new Main01();
obj.cry();
System.out.println(obj.n1);
}
}
在IDEA中如何传值呢?在IDEA中的上方工具找到Current Files,点击,点开三个点,点击Run with Parameters,在Program arguments中输入你要添加的值,即可。(IDEA的版本不同,操作页面会不一样)。
代码块:
基本介绍:
又称为初始化块,属于类中的成员【是类的一部分】,类似于方法,将逻辑语句封装在方法体内,用{}包围起来;
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建类时隐式调用。
基本语法:
[修饰符]{
代码
};
注意:
- 修饰符 可选,但只能是 static 和 无;
- 代码块分为两类,一类是 static 修饰的称为静态代码块,一类没有 static 修饰叫做普通代码块;
- 逻辑语句可以为任何逻辑语句(输入,输出,方法调用,循环,判断等);
- ;号可以写上,也可以省略。
package com.jiangxian.CodeBlock_;
public class Movie {
private String name;
private double price;
private String director;
// 现在有三个重载的构造器:
// 下面三个构造器都有相同的语句;
// 这时候,代码块显得很冗余;
// 我们可以把这些相同的语句写入代码块中,这样当我们创建任意对象,都会调用代码块中的代码;
// 代码块的调用比构造器的优先级更高。
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始...");
};
public Movie(String name){
// System.out.println("电影屏幕打开...");
// System.out.println("广告开始...");
// System.out.println("电影正式开始...");
this.name = name;
}
public Movie(String name, double price){
// System.out.println("电影屏幕打开...");
// System.out.println("广告开始...");
// System.out.println("电影正式开始...");
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director){
// System.out.println("电影屏幕打开...");
// System.out.println("广告开始...");
// System.out.println("电影正式开始...");
this.name = name;
this.price = price;
this.director = director;
}
}
package com.jiangxian.CodeBlock_;
public class CodeBlock01 {
public static void main(String[] args) {
Movie hello = new Movie("Hello");
Movie world = new Movie("World",100);
Movie Java = new Movie("Java",1000,"JiangXian");
}
}
使用细节:
-
static 代码块也叫做静态代码块,其作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。若是普通代码块(可以理解为对构造器的补充),每创建一个对象,就执行。
package com.jiangxian.CodeBlock_; public class Movie { private String name; private double price; private String director; // 现在有三个重载的构造器: // 下面三个构造器都有相同的语句; // 这时候,代码块显得很冗余; // 我们可以把这些相同的语句写入代码块中,这样当我们创建任意对象,都会调用代码块中的代码; // 代码块的调用比构造器的优先级更高。 { System.out.println("电影屏幕打开..."); System.out.println("广告开始..."); System.out.println("电影正式开始..."); }; static { System.out.println("这是静态代码块"); } public Movie(String name){ // System.out.println("电影屏幕打开..."); // System.out.println("广告开始..."); // System.out.println("电影正式开始..."); this.name = name; } public Movie(String name, double price){ // System.out.println("电影屏幕打开..."); // System.out.println("广告开始..."); // System.out.println("电影正式开始..."); this.name = name; this.price = price; } public Movie(String name, double price, String director){ // System.out.println("电影屏幕打开..."); // System.out.println("广告开始..."); // System.out.println("电影正式开始..."); this.name = name; this.price = price; this.director = director; } }
在原来的代码中加了给静态代码块,运行一次看看效果。
-
类什么时候加载(三个时候——背下来):
- 创建对象实例的时候;
- 创建子类对象实例的时候,父类的对象实例也会被加载;
- 使用类的静态成员时。
-
普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就调用一次。若只是使用类的静态成员(因为没有创建对象),普通代码块不会执行。
package com.jiangxian.CodeBlock_; public class CodeBlockDetail01 { public static void main(String[] args) { System.out.println(B.total); A a = new A(); B b = new B(); } } class A{ private int n1 = 10; static{ System.out.println("A"); } } class B extends A{ private int n2 = 20; public static int total = 200; { System.out.println("C的普通代码块..."); } }
-
创建一个对象时,在一个类 的调用顺序:
-
调用静态代码块和静态属性初始化(这两者的优先级相同,若有多个静态代码块和多个静态属性初始化,则按定义的顺序来);
-
调用普通代码块和普通属性的初始化(这两者优先级相同,若有多个普通代码块和多个普通属性初始化,则按定义顺序来);
-
调用构造类方法。
package com.jiangxian.CodeBlock_; public class CodeBlockDetail02 { public static void main(String[] args) { A01 a01 = new A01(); } } class A01{ private static int n1 = getVal(); // 第一个执行 private int n2 = getVal2(); // 第三个执行 { // 第四个执行 System.out.println("A01的普通代码块"); }; static { // 第二个 System.out.println("A01的静态代码块"); } public A01(){ // 第五个执行 super(); System.out.println("A01的构造器。"); } public static int getVal(){ System.out.println("getVal()执行..."); return 10; } public int getVal2(){ // 第四个执行 System.out.println("getVal2()执行..."); return 20; } }
-
-
构造方法(构造器)的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,是在类加载的时候就已经完成了,所以是比普通代码块和构造器优先的;
-
创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造器的调用顺序:
-
父类构造器的静态代码块,静态属性初始化(按定义的顺序);
-
子类构造器的静态代码块,静态属性初始化(按定义的顺序);
-
父类普通代码块和普通属性初始化(按需);
-
父类构造器;
-
子类的普通代码块和普通成员方法初始化;
-
子类的构造器。
package com.jiangxian.CodeBlock_; public class CodeBlockDetail03 { public static void main(String[] args) { BB bb = new BB(); } } class AA{ static private int num1 = getVal(); private int num2 = getVal2(); { System.out.println("A的普通代码块..."); }; static{ System.out.println("A的静态代码块..."); } public AA() { System.out.println("A的构造器被调用..."); } public static int getVal(){ System.out.println("A 的 getVal被调用..."); return 20; } public int getVal2(){ System.out.println("A 的 getVal2被调用..."); return 20; } } class BB extends AA{ static private int num1 = getVal(); private int num2 = getVal3(); { System.out.println("B的普通代码块..."); }; static{ System.out.println("B的静态代码块..."); } public BB() { System.out.println("B的构造器被调用..."); } public static int getVal(){ System.out.println("B 的 getVal被调用..."); return 20; } public int getVal3(){ System.out.println("B 的 getVal3被调用..."); return 20; } }
这段代码我们也可以发现,静态方法是不会被重写的,可以把普通方法中的getVal3()改成getVal2()看看效果。(我们可以认为重写只能用于实例方法。)
-
-
静态代码块只能调用静态成员,普通代码块可以使用任意成员(可以进一步得出结论——静态只能用静态,普通全都可以用!)。
单例设计模式:
什么是设计模式?
- 静态方法和属性的经典使用;
- 设计模式是在大量的实践中总结和理论化之后==优选的代码结构、编程风格、以及解决问题的思考方式。==设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索。
什么是单例模式?
单例——单个实例
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得对象实例的方法;——当一个核心类非常耗资源时。
- 单例模式有两种方式:1)饿汉式;2)懒汉式。
典型的单例模式:
有两种:饿汉式和懒汉式。
- 将构造器私有化 =》 防止外部直接 new;
- 类的内部创建对象;
- 向外暴露一个静态的公共方法;
饿汉式(急):
有可能还没有用这个对象,但是在加载类的时候以及将对象创建好了。
package com.jiangxian.single_;
public class SingleTon01 {
public static void main(String[] args) {
Girlfriend gf = Girlfriend.getInstance();
System.out.println(gf.getName());
}
}
// 只有一个类:Girlfriend
// 一段时间内只能有一个女友
class Girlfriend{
private String name;
// 如何保证只有一个Girlfriend呢?
// 1.将构造器私有化
// 2.在类的内部,直接创建一个对象(该对象时static)
// 3.提供一个公共的static方法,返回gf对象。
private Girlfriend(String name){
this.name = name;
}
// 为什么这里要用静态?
// 因为我们创建不了对象呀,所以我们需要使用静态。
private static Girlfriend gf = new Girlfriend("小红");
public static Girlfriend getInstance(){
return gf;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
懒汉式(稳):
在使用的时候,才创建实例。为什么要有呢,因为单例设计模式所创建的对象往往是重量级的对象,为了避免没使用就创建对象。
package com.jiangxian.single_;
public class SingleTon02 {
public static void main(String[] args) {
System.out.println(Cat.n1);
System.out.println("========================================");
Cat instance1 =Cat.getInstance();
Cat instance2 =Cat.getInstance();
System.out.println(instance2 == instance1);
}
}
// 希望在程序运行的时候只能一只猫:
class Cat{
private String name;
private static Cat cat;
public static int n1;
// 步骤:
// 1、私有化构造器
// 2、定义一个static静态属性对象
// 3、通过一个public的static方法,可以返回一个Cat
private Cat(String name){
this.name = name;
System.out.println("构造器被调用...");
}
public static Cat getInstance(){
if(cat == null){
cat = new Cat("小花");
}
return cat;
}
}
饿汉式 VS 懒汉式:
- 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载的时候就创建了对象,懒汉式只有在使用的时候才创建;
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题(后面讲到线程再展开);
- 饿汉式存在浪费资源的可能,懒汉式不存在。(当程序员一个对象实例都没用时);
- 再JavaSE标准版中,java.lang.Runtime就是经典的单例模式。
final关键字:
基本介绍:
final可以修饰类、属性、方法和局部变量。
在某些情况下,程序员可能有一下需求,会用到final:
-
当不希望类被继承的时候,可以用final来修饰该类;
package com.jiangxian.final_; public class Final01 { } // 若我们要求,A类不能被其它类继承; // 此时可以使用final修饰A类即可 // 为什么要这么做呢,可能A是一个基本类,不希望其它类去重写其中方法之类。 final class A{ } // class B extends A{}
-
当不希望父类的某个方法被重写/覆盖(override)的时候,可以使用final关键字修饰;(即该方法非常重要,不能被更改)
package com.jiangxian.final_; public class Final01 { } // 若我们要求,A类不能被其它类继承; // 此时可以使用final修饰A类即可 // 为什么要这么做呢,可能A是一个基本类,不希望其它类去重写其中方法之类。 final class A{ } // class B extends A{} class C{ // 若我们要求该方法不能被子类重写,使用final修饰即可 public final void hi(){} } //class D extends C{ // @Override // public void hi() { // System.out.println("重写了C类的hi()方法..."); // } //}
-
当不希望类的某个属性的值被修改,可以用final修饰;(即该属性变为常量)
package com.jiangxian.final_; public class Final01 { public static void main(String[] args) { E e = new E(); // e.TAX_RATE = 10; } } // 若我们要求,A类不能被其它类继承; // 此时可以使用final修饰A类即可 // 为什么要这么做呢,可能A是一个基本类,不希望其它类去重写其中方法之类。 final class A{ } // class B extends A{} class C{ // 若我们要求该方法不能被子类重写,使用final修饰即可 public final void hi(){} } //class D extends C{ // @Override // public void hi() { // System.out.println("重写了C类的hi()方法..."); // } //} // 放不希望某个属性的值被修改 class E{ public final double TAX_RATE = 0.08; }
-
当不希望某个局部变量被修饰,可以用final修饰。
package com.jiangxian.final_; public class Final01 { public static void main(String[] args) { E e = new E(); // e.TAX_RATE = 10; } } // 若我们要求,A类不能被其它类继承; // 此时可以使用final修饰A类即可 // 为什么要这么做呢,可能A是一个基本类,不希望其它类去重写其中方法之类。 final class A{ } // class B extends A{} class C{ // 若我们要求该方法不能被子类重写,使用final修饰即可 public final void hi(){} } //class D extends C{ // @Override // public void hi() { // System.out.println("重写了C类的hi()方法..."); // } //} // 当不希望某个属性的值被修改 class E{ public final double TAX_RATE = 0.08; } // 当不希望局部变量被修改 class F{ public void cry(){ final double NUM = 0.01; // 此时NUM也被称为局部常量 // NUM = 0.9; System.out.println(NUM); } }
使用细节:
-
final 修饰的属性又叫常量,一般用大写加_表示:XX_XX_XX;
-
final 修饰的属性在定义时,必须要赋初值,并且以后不能再进行修改,赋值可以在以下位置之一:
-
定义时:public final double TAX_RATE = 0.08;
-
在构造器中:
-
在代码块中
package com.jiangxian.final_; public class FinalDetail01 { } class AA{ /* 1. 在定义时赋值; 2. 在构造器内赋值; 3. 在代码块中。 */ private final double TAX_RATE = 0.08; // 1. private final double TAX_RATE2; private final double TAX_RATE3; public AA(){ TAX_RATE2 = 0.08; } { TAX_RATE3 = 0.08; } }
-
-
若final 修饰的属性是静态的,则初始化的位置只能是:
-
定义时;
-
在静态代码块 不能在构造器中赋值。
class BB{ /* 若final的属性是静态的,则只能在: 1. 定义时; 2. 静态代码块中赋值。 因为静态是在类加载的时候就要初始化 */ static public final double TAX_RATE = 0.08; //1. static public final double TAX_RATE2; static{ TAX_RATE2 = 99.99; } }
-
-
final 类不能继承,但是可以实例化对象;
-
若类不是final 类,但是含有final 方法,则该类方法虽然不能重写,但是仍然可以调用(父类有个方法被final修饰,子类可以调用,但是重写不了);
-
一般来说,一个类已经是final类了,内部的方法没有必要再写为final了(因为不可能有子类,自然不可能被重写了),但是 属性在需要的时候,仍然需要被final修饰;
-
final 不能修饰构造器;
-
final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理;
package com.jiangxian.final_; public class FinalDetail01 { public static void main(String[] args) { System.out.println(CC.a); } } class CC{ public static final int a = 0; static{ System.out.println("CC的静态代码块被执行...说明类加载了..."); } }
-
包装类(Integer、Double、Float、Boolean等都是final),String也是final类。
习题:
编写一个程序,能够计算圆形的面积,要求圆周率为3.14:
package com.jiangxian.final_;
public class FinalExercise01 {
public static void main(String[] args){
CircleArea circleArea = new CircleArea(6);
System.out.println(circleArea.getArea());
}
}
class CircleArea{
public final double PI = 3.14;
private double radius;
public CircleArea(){}
public CircleArea(double radius) {
this.radius = radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double getRadius(){
return radius;
}
public double getArea(){
return PI * radius * radius;
}
}
抽象类:
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类。
抽象方法 =》没有实现的方法(即没有方法体的方法)。
举个例子:动物都有自己特定的食物,如肉食动物只吃肉,草食动物支持植物,滤食性动物只吃浮游生物等等,那我们创建动物类而没有指定时哪种动物的时候,我们是不知道它要吃什么的,这时候就可以不急着去实现这个方法,而是等子类去实现它。
package com.jiangxian.abstract_;
public class Abstract01 {
}
abstract class Animal{
private String name;
public Animal(String name) {
this.name = name;
}
// 抽象方法:
// 即没有实现的方法;
// 没有实现即没有方法体——{}
// 抽象方法需要用abstract 修饰
// 有抽象方法的类,需要修饰为抽象类;
// 一般来说,抽象类会被继承,由其子类实现继承的方法。
abstract public void eat();
}
抽象类的介绍:
-
用abstract 关键字来修饰一个类,这个类就叫做抽象类;
abstract class ClassName{}
-
用 abstract 关键字来修饰一个方法,这个方法就叫做抽象方法;
abstract 修饰符 方法返回类型 方法名();——没有{}
-
抽象类的价值主要在于设计,是设计者设计好之后,让子类去继承并实现抽象类;
-
抽象类,在框架和设计模式使用较多(面试会问的较多)。
抽象类的使用细节:
-
抽象类不能被实例化;——即它不能创建对象
-
抽象类不一定需要包含 abstract 方法,其可以没有;
-
一旦类包含了 abstract 方法,就必须用 abstract 修饰类;
-
abstract 只能修饰类与方法,不能修饰属性和其他的;
-
抽象类可以由任意的成员【抽象类还是类,所以类有什么,抽象类就可以有什么】;
-
抽象方法一定不能有方法体;
-
若一个类继承了抽象类,其必须实现抽象类的所有抽象方法,除非,它自己也是抽象类。
package com.jiangxian.abstract_; public class AbstractDetail01 { } abstract class A{ abstract public void show(); } class B extends A{ public void show(){// 所谓实现方法,就是有个方法体,且里面有什么,并不关心。 } } abstract class C extends A{ abstract void hi(); } class D extends C{ public void show(){}// 实现的时候,修饰符的范围不能缩小否则会报错 protected void hi(){} }
-
抽象类不能使用private、final和static来修饰,这些关键字都是和重写相违背的。
- 为什么私有不允许呢?因为子类无法直接访问父类的私有方法;
- final 更好理解,其不能被重写;
- static 修饰的方法属于类本身,重写是针对实例方法的。
练习:
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id(应该设置为静态,引入自增,懒得改了,在调用构造器时,自增即可),salary,构造器和抽象方法work();(偷懒我就只写了Manager)。
package com.jiangxian.abstract_;
public class AbstractExercise01 {
public static void main(String[] args) {
Manager manager = new Manager("JiangXian",1,20000,50000);
manager.work();
System.out.println("Manger's annual Salary:" + manager.annualSalary());
}
}
abstract class Employee{
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
abstract public void work();
abstract public double annualSalary();
}
class Manager extends Employee{
private double bonus;
public Manager(String name, int id, double salary, double bonus) {
super(name, id, salary);
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public void work(){
System.out.println("Manager is Managing....");
}
public double annualSalary(){
return bonus + getSalary() * 12;
}
}
抽象类的最佳实践——模板设计模式:
设计一个抽象类,能完成如下功能:
- 编写实例方法calculate(),可以计算某段代码的消耗时间;
- 编写抽象类job();
- 编写一个子类,实现job()——即要计算消耗时间的代码;
- 编写一个测试类。
Template.java:
package com.jiangxian.abstract_;
import static java.lang.Thread.sleep;
abstract public class Template {
abstract public void job();// 抽象方法
public void calculate(){ // 实现方法
// 开始时间
long start = System.currentTimeMillis();
job();
long end = System.currentTimeMillis();
System.out.println("执行时间:" + (end - start) + "ms");
}
}
AA.java:
package com.jiangxian.abstract_;
public class AA extends Template{
@Override
public void job(){
long num = 0;
// 11200000.fori
for (int i = 1; i <= 11200000; i++) {
num += i;
}
}
}
BB.java:
package com.jiangxian.abstract_;
public class BB extends Template{
@Override
public void job(){
long num = 1;
for (int i = 1; i <= 1180000; i++) {
num *= i;
}
}
}
TestTemplate.java:
package com.jiangxian.abstract_;
public class TestTemplate {
public static void main(String[] args){
AA aa = new AA();
BB bb = new BB();
aa.calculate();
bb.calculate();
}
}
由此我们看出模板设计模式对我们的代码复用具有很大的帮助。
接口:
USB插槽就是现实中的接口,我们可以把手机,相机,u盘等插入插槽,而不用担心那个插槽时专门插哪个的,原因时usb插槽厂家和各种设备的厂家都遵守了统一的规定(尺寸。排线等)。
快速入门:
这样的设计需求在Java编程/php/.net/go中也是会大量存在的,现在我们用程序来模拟一下:
UsbInterface——在创建的时候,要选择Interface
package com.jiangxian.Interface;
public interface UsbInterface {
// 是指定规范的
// 规定接口的相关方法
public void start();
public void end();
}
Phone——要插入usb插槽的设备,需要满足usb的相关协议
package com.jiangxian.Interface;
// 为了插入这个接口,Phone需要去实现接口UsbInterface;
// 1.Phone需要实现UsbInterface接口 规定的方法
// 2.Phone类可以理解为要插入的类
public class Phone implements UsbInterface{
@Override
public void start(){
System.out.println("Phone start working...");
}
@Override
public void end() {
System.out.println("Phone end working...");
}
}
Camera——同上:
package com.jiangxian.Interface;
// Camera类可以理解为要插入的类
public class Camera implements UsbInterface{
@Override
public void start() {
System.out.println("Camera start working...");
}
@Override
public void end() {
System.out.println("Camera end working...");
}
}
Computer——插槽所在的类:
package com.jiangxian.Interface;
// Computer 类可以认为是usb插槽所在的类
public class Computer {
public void work(UsbInterface usbInterface){
// 通过接口来调用方法
usbInterface.start();
usbInterface.end();
}
}
测试类
package com.jiangxian.Interface;
public class Interface {
public static void main(String[] args) {
Computer computer = new Computer();
Phone phone = new Phone();
Camera camera = new Camera();
computer.work(phone);
computer.work(camera);
}
}
基本介绍:
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。
语法:
interface 接口名{
// 属性
// 方法(1.抽象方法,2.默认实现方法,3.静态方法)
}
class 类名 implements 接口名{
自己的属性;
自己的方法;
必须实现的接口的抽象方法;
}
小结:
在JDK7.0前,接口中的所有方法都没有方法体,都是抽象方法;
JDK8.0后,接口类中可以存在静态方法,默认方法,也就是接口可以有方法的具体实现。
AInterface
package com.jiangxian.Interface;
public interface AInterface {
// 属性:
public int n1 = 10;
// public int n2;不允许属性没有被初始化
// 方法:
// 抽象方法,在接口中,可以省略abstract
public void hi();
// 在JDK8后可以有默认实现方法,需要使用default关键字修饰
default public void ok(){
System.out.println("ok.....");
}
// 在JDK8后也可以有静态方法
public static void no(){
System.out.println("Static_Method is Called...");
System.out.println("no.....");
}
}
A:
package com.jiangxian.Interface;
public class A implements AInterface{
public void hi(){
System.out.println("hi");
}
}
Interface02:
package com.jiangxian.Interface;
public class Interface02 {
public static void main(String[] args) {
A a = new A();
// a.no();会报错,等下再细看。
AInterface.no();
a.hi();
a.ok();
}
}
应用场景:
为了实现统一编程和保障软件的稳定性。若没有接口,那么一个任务给不同的人做,函数名可能会不一样,从而导致阅读的困难等。
使用细节:
-
接口不能被实例化;
-
接口中的所有方法都是 public 方法,接口中的抽象类,可以不用 abstract 修饰;
-
一个普通类实现接口,就必须将接口中所有的方法都实现;(如何快速实现呢,alt + insert——我调节为了alt + I,就是快速实现construct,getter那些的快捷键,找到Implement Method点击即可);
-
抽象类实现接口,可以不用实现接口的抽象方法;
-
一个类,同时可以实现多个接口(一个类只能直接继承一个类);
package com.jiangxian.Interface; public class InterfaceDetail01 { } interface IA{ void hi(); } interface IB{ void say(); } class Pig implements IA,IB{ @Override public void hi() {} @Override public void say() {} }
-
接口中的属性只能是final的(常量),而且是public static final 修饰的。比如:int n1 = 10;其实际上是 public static final int n1 = 10;
-
接口中属性的访问形式:接口名.接口属性
-
一个接口不能继承其它的类,但是能够继承其它的接口,且可以同时继承多个;
interface IA{ void hi(); } interface IB{ void say(); } interface IC extends IA,IB{} // interface ID implements IA{},接口不能实现接口
-
接口的修饰符只能是默认或者是public,这和类的修饰符是一样的。
实现接口 vs 继承类:
emmm,课上说的是,实现接口是对Java中单继承机制的补充,就像猴子天生会爬树(继承——先天),但是想要像鱼一样游泳就需要学习(实现接口——后天)。
父亲只能有一个,但是可以有多个老师。
子类继承父类时,自动拥有了父类的功能;当子类想要扩展功能,可以由实现接口的方式来实现。
- 解决的问题不同:
- 继承——解决代码的复用性和可维护性;
- 接口——设计,设计好各种规范,让其它类去实现这些方法,更加灵活;
- 接口比继承更加灵活
- 不用想继承一样必须满足is-a的关系,只需要满足like-a;
- 接口在一定程度上可以实现代码解耦【接口规范性 + 动态绑定】——在集合章节讲解。
接口的多态:
-
多态参数,在我们开始的时候,我们写过一个public void work(UsbInterface usbInterface){},我们传入的参数可以是实现了UsbInterface的类的对象实例——接口引用可以指向实现类的对象;
-
多态数组:
package com.jiangxian.Interface; public class InterfacePolyArr { public static void main(String[] args) { // 此处实例化的是数组!不是接口! Usb[] usb = new Usb[2]; usb[0] = new Phone_(); usb[1] = new Camera_(); for (int i = 0; i < 2; i++) { if(usb[i] instanceof Phone_){ usb[i].start(); } } } } interface Usb{ void start(); void end(); } class Phone_ implements Usb{ public void start(){ System.out.println("phone start working..."); } public void end(){ System.out.println("phone end working..."); } } class Camera_ implements Usb{ public void start(){ System.out.println("camera start working..."); } public void end(){ System.out.println("camera end working..."); } }
-
多态传递
package com.jiangxian.Interface; public class InterfacePolyPass { IG ig = new Teacher(); // 若IG继承了IH接口,Teacher实现了IG接口,那么Teacher也实现了IH接口 IH ih = new Teacher(); } interface IH{} interface IG extends IH{} class Teacher implements IG{}
内部类——难且重要(底层源码大量使用):
基本介绍:
一个类的内部又完整地嵌套了另一个类结构。被嵌套的类叫做内部类(inner class),嵌套其它类的叫做外部类(outer class)。
其最大特点:可以直接访问外部类的私有属性,并且可以体现出类与类之间的包含关系
基本语法:
class Outer{
class Inner{}
}
class Other{}
例子:
package com.jiangxian.InnerClass_;
public class InnerClass01 { // 外部其他类
public static void main(String[] args) {
}
}
class Outer{ // 外部类
private int n1 = 100;
{
System.out.println("Outer Class...");
}
public Outer(){}
public Outer(int n1){
this.n1 = n1;
}
public void hi(){
System.out.println("hi");
}
// 内部类
class Inner{
public void pr(){
System.out.println(n1);
}
}
}
内部类的分类:
- 定义在外部类的局部位置(比如方法内:)
- 局部内部类——有类名;
- 匿名内部类——没有类名,这是重点中的重点。
- 定义在外部类的成员位置上:
- 成员内部类——没用 static 修饰;
- 静态内部类——用 static 修饰。
局部内部类:
定义在外部类的局部位置,比如方法中,并且有类名——本质任然是一个类:
- 可以之间访问外部类的所有成员(包括私有);
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但可以使用final修饰;
- 作用域:仅仅在定义它的方法或代码中;
- 局部内部类——访问——》外部类的成员【访问方式:直接访问】;
- 外部类——访问——》局部内部类的成员【访问方式:先创建局部内部类,再访问(必须在作用域中)】。
- 外部其他类是不能访问局部内部类的(即外部类不能直接创建局部内部类的实例);
- 若外部类和局部内部类的成员重名时,遵循就近原则,若想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
- 为什么要加this呢? Outer.this 实际上就是外部类的一个对象实例,指向的是谁呢,谁调用Inner_class定义所在的方法,那这个 Outer.this 就指向他。
- 在我们的代码中,是 outer01 这个对象调用的 m1(),所以指向的是 outer01。
package com.jiangxian.InnerClass_;
/**
* 演示局部内部类的使用
*/
public class LocalInnerClass {
public static void main(String[] args) {
Outer01 outer01 = new Outer01();
outer01.m1();
outer01.m2();
outer01.m1();
System.out.println("outer01 的 hashcode" + outer01.hashCode());
}
}
class Outer01{
private int n1 = 10;
private int num = 20;
public void m1(){
// 局部内部类是定义在外部类的局部位置,通常在方法
class Inner01{// 局部内部类
private int num = 30;
// 可以直接访问外部类的所有成员
public void f1(){
n1 += 10;
System.out.println("n1 = " + n1);
}
public void f2(){
System.out.println("Outer's num = " + Outer01.this.num);
System.out.println("Inner's num = " + num);
System.out.println("Outer01.this 的 hashcode:" + Outer01.this.hashCode());
}
}
// 要先创建内部类的对象,才能调用里面的方法
Inner01 inner01 = new Inner01();
inner01.f1();
inner01.f2();
}
public void m2(){
System.out.println("n2 = " + n1);
}
}
小结:
记住其定义的位置,作用域,其本质任然是一个类。
匿名内部类(!!!!!!!!!):
(1)本质是类;(2)内部类;(3)该类没有类名(实际上系统会给它取一个名,是系统分配,且直接看不到系统分配的名字);(4)其同时还是一个对象
匿名内部类是定义在外部类的局部位置上的(方法、代码块),且没有类名(类名不用被关注,或者说是隐藏起来的)
基本语法:
其语法比较特殊,有两种调用方式:
第一种语法:
类或接口名 对象名 =new 类或接口(参数列表){
类体;
};
// 调用方法
对象名.方法(参数列表);
第二种语法:
new 类或接口(参数列表){
类体
}.方法(参数列表);
使用:
package com.jiangxian.InnerClass_;
import com.jiangxian.Interface.A;
/**
* 演示匿名内部类的使用
*/
public class AnonymousInnerClass { // 外部其它类
public static void main(String[] args){
Outer02 outer02 = new Outer02();
outer02.method();
}
}
class Outer02{
private int n1 = 10; // 属性
public void method(){// 方法
// 1、基于接口的匿名内部类
// (1)为什么需要呢?
// 当我想使用接口A的时候,并创建一个对象,传统写法需要写一个类实现该接口,并创建对象(如Tiger类)
// 这种写法的问题是什么呢?我们可能只用一次,结果我们定义了一个类,很浪费。
// Tiger tiger = new Tiger();
// tiger.cry();
// (2)因此我们可以使用匿名内部来实现。
// tiger 的编译类型是? 是接口类型A
// tiger 的运行类型是? 是匿名内部类XXXX => Outer02$1
/*
我们看底层:其会分配一个类名 Outer02$1
class Outer02$1 implements A{
@Override
public void cry() {
System.out.println("Tiger is Crying...");
}
}
*/
// 此时的new:在jdk的底层在创建匿名内部类后,就立刻创建了 Outer02$1实例,并且把地址返回给tiger
// 匿名内部类使用一次就不能再使用了(不是说tiger这个对象)
IA tiger = new IA(){
@Override
public void cry() {
System.out.println("Tiger is Crying...");
}
};
System.out.println("tiger的运行类型:" + tiger.getClass());
tiger.cry();
tiger.cry();
// new Outer02$1(); 是不可以的,因为使用 Outer02$1 一次就不存在了。
// 2.基于类的匿名内部类:
// 分析:
// (1)father的编译类型:Father;
// (2)father的运行类型:Outer02$2
// 在底层会创建匿名内部类:
/*
class Outer02$2 extends Father{
}
*/
Father father = new Father("jack"){ // 这个参数有什么用呢?
// 参数列表会自动传给Father的构造器。
// 匿名构造器没有名字,不能写构造器,会自动调用Father的构造器
@Override
public void test() {
System.out.println("匿名内部类重写了test方法。");
}
};
System.out.println("father 的运行类型:" + father.getClass());
father.test();
// 3.基于抽象类的匿名内部类
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("Animal is Eating...");
}
};
animal.eat();
}
}
interface IA {// 接口
void cry();
}
class Tiger implements IA{
@Override
public void cry() {
System.out.println("Tiger is Crying...");
}
}
class Father{ // 外部其他类
public Father(String name){// 构造器
}
public void test(){}
}
abstract class Animal{
abstract void eat();
}
细节:
- 可以直接访问外部类的所有成员;
- 不能添加访问修饰符,因为其是一个局部变量;
- 作用域:仅仅在定义它的方式和代码块中;
- 匿名内部类访问外部类成员,直接访问;
- 外部其它类不能访问;
- 外部类和匿名内部类重名,想使用外部类,需要使用 Outer.this.属性名。
成员内部类:
成员内部类是定义在外部类的成员位置,并且没有 static 修饰。
-
可以直接访问外部类的所有成员,包含私有的;
-
可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
package com.jiangxian.InnerClass_; public class MemberInnerClass01 { public static void main(String[] args) { Outer08 outer08 = new Outer08(); outer08.t1(); } } class Outer08{ private int n1 = 1; public String name = "JiangXian"; // 成员内部类,是定义在外部类的成员位置(当作属性或方法) public class Inner08{ public void say(){ // 可以直接使用外部类的所有成员,包括私有 System.out.println("Inside Inner08:" + "n1 = " + n1); } } // 写一个方法: public void t1(){ Inner08 inner08 = new Inner08(); inner08.say(); } }
-
作用域:和外部类的其它成员一样,为整个类体;
-
成员内部类访问外部类——直接访问;
-
外部类访问成员内部类——先创建对象,再访问;
-
外部其它类访问成员内部类(重点):有两种方式
package com.jiangxian.InnerClass_; public class MemberInnerClass01 { public static void main(String[] args) { Outer08 outer08 = new Outer08(); outer08.t1(); // 第一种: // 解释: // 我们希望去 new Inner08();但是其没有写在外面,只能作为一个成员 // 于是我们需要先创建一个Outer08的对象,再将上面的式子作为一个实例的成员 // outer08.new Inner08();——就是一个语法,不要纠结 Outer08.Inner08 inner = outer08.new Inner08(); // 第二种: // 在Outer中写一个方法,返回一个Inner08的对象实例 Outer08.Inner08 inner08Instance = outer08.getInner(); } } class Outer08{ private int n1 = 1; public String name = "JiangXian"; // 成员内部类,是定义在外部类的成员位置(当作属性或方法) public class Inner08{ public void say(){ // 可以直接使用外部类的所有成员,包括私有 System.out.println("Inside Inner08:" + "n1 = " + n1); } private int n2 = 2; } // 写一个方法: public void t1(){ Inner08 inner08 = new Inner08(); inner08.say(); // 外部类在创建对象后可以直接访问其中的私有属性 System.out.println("Inner Class's n2 = " + inner08.n2); } public Inner08 getInner(){ //返回一个 Inner08实例 return new Inner08(); } }
-
若外部类和成员内部类成员重名,也需要遵循就近原则,想要访问外部类,需要 Outer.this.成员;
静态内部类:
定义在外部类的成员位置,但是用 static 修饰。
-
其可以访问外部类的所有静态成员,但是不能访问普通成员;
-
可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员;
-
作用域:整个类体;
-
静态内部类访问外部类——直接访问外部类的所有的静态成员;
-
外部类访问静态内部类——创建对象,再访问;
-
外部其他类访问静态内部类
package com.jiangxian.InnerClass_; public class StaticInnerClass { public static void main(String[] args) { Outer10 outer10 = new Outer10(); outer10.hi(); // 外部其他类使用静态内部类: // 方式1: // 静态内部类可以直接通过外部类的类名直接访问(需要满足访问权限); Outer10.Inner10 inner10 = new Outer10.Inner10(); inner10.say(); // 方式2: // 编写一个方法,可以返回静态内部类的实例 Outer10.Inner10 inner101 = Outer10.getInner10(); inner101.say(); } } class Outer10{ private int n1 = 1; public static String name = "JiangXian"; // Inner10就是静态内部类 // 其使用 static 修饰 static class Inner10{ public void say(){ // 只能访问外部类的静态成员 // System.out.println(n1); System.out.println(name); } } public void hi(){ new Inner10().say(); } public static Inner10 getInner10(){ return new Inner10(); } }
-
若外部类和静态内部类的成员重名时,满足就近原则,想要访问外部类的成员(注意此时的外部类的成员只能时静态),我们只用 外部类.成员名即可,因为静态对象属于类而不是实例,所以不用this。
小结:
- 内部类有四种:局部内部类、匿名内部类(这两个是放在局部的);成员内部类、静态内部类(这两个是作为成员的);
- 重点还是掌握匿名内部类。
- 成员内部类和静态内部类 都是放在外部类的成员位置,本质就是一个成员。