[基础]]Java基础之面向对象(封装、多态、继承)

面向对象:一切以对象的方式来思考问题

OO:面向对象

OOA:面向对象分析

OOD:面向对象设计

OOP:面向对象编程

 

1、类与对象

(1)什么是类?

       现实生活中是由很多很多对象组成的,基于对象抽出了类

       生物学分类:界门纲目科属种 举例:六界中 --> 生物学分类:界门纲目科属种 -- 类

       人:动物界-脊索动物门-哺乳纲-灵长目-人科-人属-智人种 玫瑰:植物界-被子植物门-双子叶植物纲-蔷薇目-蔷薇科-蔷薇属-玫瑰种

       类:类型/类别,代表一类个体(种类) 类是抽象的,用来描述一类具有共同属性和行为事物的统称 类的组成:属性(具体值)、行为(可使用)

(2)什么是对象?

       软件中真实存在的单个个体/东西 对象是根据类来创建的,类中有什么属性和行为,对象就有什么属性和行为。

(3)类是对象的模子(模板),对象是类的具体的实例(个体)

          类            对象

    月饼模子            月饼

    学生 student        张三、李四

    女朋友 girlFriend      小红,小芳

    汽车 car                         奔驰,宝马

(4)类中包含:

       • 对象所共有的属性/特征--------------成员变量

       • 对象所共有的行为/动作--------------方法

(5)如何创建类?如何创建对象?如何访问成员?

       类是我们自己创造的一个数据类型(引用类型),创建出来的变量叫引用类型变量,简称引用

      格式:数据类型 引用类型变量 指向 new 对象 Airplane a1 = new Airplane();

 //举例:
 class girlFriend{
     //成员变量/属性
     String name;
     int age;
     String address;
  //方法/行为
     void eat(){}
     void sleep(){}
 }
 
 class car{
     //成员变量/属性
     String type;
     String color;
     double price;
  //方法/行为
     void run(){}
     void stop(){}
 }

(6)一个类可以创建多个对象

       同一类型所创建的对象,结构相同,数据不同。即:具体创建的对象不同。

       对象是先声明,声明后会在给对象在内存里开辟空间,之后初始化,再进行使用。

 //举例:
     girlFriend xh = new girlFriend();
     girlFriend xf = new girlFriend();
     xh.name = "小红";
     xh.age = 18;
     xh.address = "心里";
     xh.salary = 10000;//编译错误,没有这个属性
     xh.eat();
 
     xf.name="小芳"
    ...

飞机大战涉及到的相关问题:

(1)为什么在main的外面创建引用?

       答:因为若将引用设计在main中,则引用就只能在main中被使用,其它方法都无法使用了,而World类中的会包含很多方法,这些方法中都会用到那一堆引用,所以将引用设计在main的外面,来扩大作用范围。

(2)为什么要单独创建action()方法来测试?

       答:因为main()方法是static的,在static的方法中是无法访问那一堆引用的,所以单独创建一个非static的action()的来测试

(3)为什么在main中得先创建World对象,而后再调用action()方法?

       答:因为main()方法是static的,在static的方法中是无法直接调用action()方法的,所以得先创建World   对象,而后再调用action()方法。

数据类型默认值:

byte,short,int,long,char-----------------0 

float,double-----------------------------0.0

boolean----------------------------------false

引用类型---------------------------------null

 

2、方法:函数、过程

  • 封装一段特定的业务逻辑(功能)

  • 尽可能独立,一个方法只干一件事

  • 方法可以被重复多次的调用

           减少代码的重复,有利于代码的维护,有利于团队的协作。

(1)方法的定义

  修饰词        返回值类型       方法名称(参数列表)

  public static       void          methodName(String str)

       {

     方法体(具体代码)

  }

(2)方法可以有返回值也可以没有返回值

  • 无返回值,将返回值类型: void

  • 有返回值,将返回值类型: 特定的数据类型(int,double)

  • 注:何时有返回值?何时没有返回值?

    a.若还需要用到方法中的某个数据 --- 有返回值

    b. 若不需要用到方法中的某个数据 --- 无返回值

(3)方法的调用

  • 无返回值:方法名(有参传参);

  • 有返回值:数据类型 变量 = 方法名(有参传参);//要接收返回值

(4)return

  • return ; 结束方法的执行(还可以在条件语句中跳出,结束方法)

       • return 值; 结束方法的执行,返回结果给调用方

(5)方法的签名:方法名+参数列表

  注意:java规定,在同一类中,不能出现签名相同的方法

(6)方法的重载(overload):-------------便于用户的访问

  • 发生在同一类中,方法名相同,参数列表不同,方法体不同,与返回值类型无关 。

  • 编译器在编译时会根据方法的签名自动绑定调用的方法 。

(7)方法的重写(override):重新写、覆盖

  • 发生在父子类中,方法名相同,参数列表相同,方法体不同

  • 重写方法被调用时,看对象的类型(就近原则:先看派生类,再看超类)

 //实例:
 public class Demo05 {
  public static void main(String[] args) {
  Noo noo = new Noo();
  noo.demo();
  }
 }
 /*
  * 1.我还想做中餐---不需要重写
  * 2.我想改为西餐---需要重写
  * 3.我想改成中西合璧---需要重写(先super中餐再加西餐)
  */
 class Moo{
  public void demo(){
  System.out.println("中餐");
  }
 }
 class Noo extends Moo{
 // public void demo(){//重写demo方法(改做西餐)
 // System.out.println("西餐");
 // }
  public void demo(){//重写demo方法(先super中餐再加西餐---中西合璧)
  super.demo();//调用父类的中餐
  System.out.println("西餐");//重写写的内容:西餐
  }
 }
  • 遵循 " 两同两小一大 " 原则:

    • 两同:

      • 方法名相同

      • 参数列表相同

    • 两小:

      • 派生类方法的返回值类型小于或等于超类方法的(一般用等于)

        • void时,必须相同

        • 基本类型时,必须相同

        • 引用类型时,必须小于或等于

      • 派生类方法抛出的异常小于或等于超类方法的(一般用等于)

    • 一大

      • 派生类方法的访问权限大于或等于超类方法的(一般用等于)

 //实例:
 class Xoo{
  void show(){}
 
  double test(){return 3.14;}
 
  Yoo say(){return null;}
 
  Xoo sayHi(){return null;}
 }
 class Yoo extends Xoo{
  void show(){}//void时,必须相同
  //int show(){return 1;}
 
  double test(){return 4.13;}//基本数据类型时,必须相同
  //int test(){return 110;}
 
  Yoo say(){return null;}//引用类型时,必须小于或等于:等于
  //Xoo say(){return null;}//引用类型时,必须小于或等于:不能大于
 
  Xoo sayHi(){return null;}//引用类型时,必须小于或等于:可等于
  Yoo sayHi(){return null;}//引用类型时,必须小于或等于:可小于
  //(注意上面两种重复定义了,实际使用用一行即可)
 }

总结:重写与重载的区别

  • 重写(Override/Overriding):(重新写,覆盖)

    • 发生在父子类中,方法名相同,参数列表相同,方法体不同

    • “运行期绑定”,看对象的类型绑定方法

  • 重载(Overload/Overloading):

    • 发生在同一类中,方法名相同,参数列表不同,方法体不同

    • “编译期绑定”,看参数/引用类型的绑定方法

 

3、构造方法:构造函数、构造器、构建器

作用:复用给成员变量赋初始值代码

  • 每个类都有的,java已经提供好的

  • 方法名与类同名,没有返回值类型(连void都没有)

    举例:方法名(类名一致)(有参传参){方法体}

  • 给成员变量赋初始值

  • 在创建(new)对象时被自动调用

  • 若自己不写构造方法,则编译器默认提供一个无参的构造方法;

    若自己写了构造方法,则编译器不再默认提供

  • 构造方法可以重载(方法名相同,参数列表不同---方法签名不同)

 

4、this:指代当前对象

指代当前对象,哪个对象调用方法它指的就是哪个对象

只能用在方法中,方法中访问成员变量之前默认都有this.

this的用法:

  • this.成员变量名-----------访问成员变量

    zs.study();-------------study()中的this指的就是zs

    ls.study();-------------study()中的this指的就是ls

    ww.study();-------------study()中的this指的就是ww

    注意:当成员变量与局部变量同名时,若想访问成员变量则this不能省略

  • this.方法名()-------------调用方法(一般不用)

  • this()--------------------调用构造方法(应用率低)

 

5、成员变量与局部变量之间的关系

java规定:

  • 成员变量和局部变量是可以同名的

    • 用的时候默认采取的是就近原则

    • 若想访问成员变量,则this不能省略

    结论:当成员变量与局部变量同名时,若想访问成员变量,则this不能省略。

 //举例:
 class Student {
  String name; //成员变量(整个类)
  int age;
  String address;
          Student(){
  this("无名氏",0,"未知"); //调用构造方法
  }
  Student(String name,int age,String address){ //局部变量(仅在当前方法中)
  this.name = name;
  this.age = age;
  this.address = address;
  }
  void study(){
  System.out.println(name+"在学习...");
  }
  void sayHi(){
  System.out.println("大家好,我叫"+name+",今年"+age+"岁了,家住"+address);
  }
 }

 

6、空 null

表示空,没有指向任何对象

若引用的值为null,则该引用不能再进行任何点操作了,若操作,则发生NullPointerException空指针异常

注意:创建对象时,引用类型的成员变量默认值为null。

引用类型之间画等号:(借房子,有两把钥匙)

(1)指向同一个对象

(2)通过一个引用对象的修改 会影响另一个引用的访问

基本类型之间画等号:(打印 复制)

(1)赋值

(2)对一个变量的修改,不会影响到另一个变量

 

7、数组

  • 相同 数据类型 元素的集合

  • 是一种数据类型(引用类型)

(1)数组的定义

声明整形数组arr,包含10个元素(每个元素int类型,默认值 0)

 int[] arr = new int[10];
 double[] b = new double[9];//默认值:0.0
 boolean[] f = new boolean[5];//默认值:false
 Ball[] ball = new Ball[100];//默认值:null
 //举例:两个完全不同的数据类型
 int a;
 int[] arr;
 //扩展:
 /*
 Java 中定义数组的语法有两种:
 1.type arrayName[];
 2.type[] arrayName;
 其中,type为Java中的任意数据类型,包括基本类型和组合类型;
      arrayName为数组名,必须是一个合法的标识符;
      []指明该变量是一个数组类型变量。
      例如:int demoArray[];
 */      

(2)数组的初始化

 int[] arr = new int[4];//0,0,0,0  byte、short与int同理
 int[] arr = {1,4,6,8};//1,4,6,8
 int[] arr = new int[]{2,4,6,8};//2,4,6,8
 int[] arr;
 arr = {1,3,5,7};//编译错误,此方式仅限于声明同时初始化
 arr = new int[]{1,3,5,7};//正确

(3)数组的访问:

 //a.通过(数组名.length)可以获取数组的长度(元素的个数)
 int[] arr = new int[10];
 System.out.println(arr.length);
 //b.通过下标/索引来访问数组中的元素。
 int[] arr = new int[3];
 arr[0] = 100;//给第一个元素赋值为100
 arr[1] = 200;//给第二个元素赋值为200
 arr[2] = 300;//给第三个元素赋值为300
 arr[3] = 400;//(编译不错误)数组下标越界异常
 System.out.println(arr[arr.length-1]);//输出最后一个元素的值

(4)数组的遍历

  int[] arr = new int[10]; //0
  for(int i=0;i<arr.length;i++){
     arr[i] = (int)Math.random()*100;
  }
  for(int i=0;i<arr.length;i++){
     System.out.println(arr[i]);
  }
 for(int i=arr.length-1;i>=0;i--){
  System.out.println(arr[i]);
 }

(5)引用类型数组

 //a.直接声明引用数组类型长度,再逐个元素依次初始化赋值
 Student[] stus = new Student[3]; //创建Student数组对象
 stus[0] = new Student("zs",25,"LF"); //创建Student对象
 stus[1] = new Student("ls",26,"JMS");
 stus[2] = new Student("ww",27,"SD");
 System.out.println(stus[0].name); //输出第1个学生的名字
 stus[1].age = 24; //修改第2个学生的年龄为24
 stus[2].sayHi();   //第3个学生跟大家问好
 for(int i=0;i<stus.length;i++){ //遍历所有学生
  System.out.println(stus[i].name); //输出每个学生的名字
     stus[i].sayHi(); //每个学生跟大家问好
 }
 //b.不指定数组长度,直接进行初始化赋值
 Student[] stus = new Student[]{
  new Student("zs",25,"LF"),
     new Student("ls",26,"JMS"),
     new Student("ww",27,"SD")
 };

(6)数组的复制

 //逐个复制
 int[] a = {1,2,3,4,5};
 int[] a1 = new int[9];
 a1[0] = a[0];
 a1[1] = a[1];
 a1[2] = a[2];
 a1[3] = a[3];
 a1[4] = a[4];
 
 for(int i=0;i<a.length;i++){ //元素位置不一致,操作麻烦
     a1[i] = a[i];
 }
 
 /*
 数组的复制:
 a.System.arraycopy(a,1,a1,0,4); //灵活性好
   a: 源数组
   1: 源数组的起始下标
   a1: 目标数组
   0: 目标数组的起始下标
   4: 要复制的元素个数
 b. Arrays.copyOf(a,6);//灵活性差,需要导包,产生新数组,一般用来对数据进行缩容、扩容处理
   a: 源数组
   6:目标数组的长度(不会报错,多则补默认值,少则截取)  
   特殊用法:(扩容/缩容)
     这种方式是重新创建了一个数组
     Arrays.copyOf(a,a.length+1);
     +: 扩容   -:缩容
 */

(7)数组的排序:冒泡排序、插入排序、选择排序

Arrays.sort(arr); //升序、效率高

冒泡原理:

  • 四个数冒三轮

  • 每一轮都是从第1个元素开始冒,每一次都是和它下一个元素比

  • 冒出来的就不带它玩了

冒泡排序

 package arraysort;
 
 import java.util.Arrays;
 
 public class BubbleSort {
     public static void main(String[] args) {
         int[] arr = {1,3,5,7,9,0,2,4,6,8};
         System.out.println(Arrays.toString(bubbleSort(arr)));
    }
     //冒泡排序
     public static int[] bubbleSort(int[] arr){
         //每两个数比较一次,依次从第一个(0)到倒数第二个(arr.length-2)开始比较
         for (int i = 0; i < arr.length-1; i++) {
             //进行一趟冒泡排序,每次比较的次数减1
             for (int j = 0; j < arr.length-1-i; j++) {
                 //如果前一个元素比后一个元素大,交换元素位置
                 //if(arr[j]>arr[j+1]){//升序
                if(arr[j]<arr[j+1]){//降序
                     int temp = arr[j];
                     arr[j] = arr[j+1];
                     arr[j+1] = temp;
                }
            }
        }
         return arr;
    }
 }
 

插入排序

 package arraysort;
 
 import java.util.Arrays;
 
 public class InsertSort {
     public static void main(String[] args) {
             int[] arr = {1,3,5,7,9,0,2,4,6,8};
             System.out.println("交换之前:"+ Arrays.toString(arr));
             int[] newArr = insertionSort(arr);
             System.out.println("交换之后:"+Arrays.toString(newArr));
    }
     //插入排序:从第二个元素开始,每趟遍历前i个元素,将最小值插入到前面已排序好的序列
     public static int[] insertionSort(int[] arr){
         //第i趟排序,从第二个元素开始
         for (int i = 1; i < arr.length; i++) {
             int temp = arr[i];//将每趟开始的元素作为待插入元素
             int j;//最终要插入元素的下标
             //每一趟排序,从第i个元素向前遍历,并且前面的元素要比插入元素大
             for (j = i; j > 0 && arr[j-1]>temp; j--) {//升序
             //for (j = i; j > 0 && arr[j-1]<temp; j--) {//降序
                     arr[j] = arr[j-1];//把大于需要插入的元素往后移
                     // 最后不大于temp的元素就空出来arr[j](for循环243243……,不满足2时退出)
            }
             arr[j] = temp;//将需要插入的数放到要插入的位置
        }
         return arr;
    }
 }
 

选择排序

 package arraysort;
 
 import java.util.Arrays;
 
 public class SelectionSort {
     public static void main(String[] args) {
         int[] arr = {1,3,5,7,9,0,2,4,6,8};
         System.out.println("交换之前:"+Arrays.toString(arr));
         int[] newArr = selectionSort(arr);
         System.out.println("交换之后:"+Arrays.toString(newArr));
    }
     //选择排序:每趟选出最小的元素放在该趟的第一个位置,每趟循环的元素个数减1
     public static int[] selectionSort(int[] arr){
         //做第i趟排序(共进行:数组长度-1 次)
         for (int i = 0; i < arr.length; i++) {
             int index = i;//默认最小元素的下标为每趟开始的第1个元素
             //求出该趟排序中最小元素的下标,每趟从起始下标的后一个元素开始
             for (int j = index+1; j < arr.length; j++) {
                 if(arr[j]<arr[index]){//如果该元素比默认元素小(升序)
                 //if(arr[j]>arr[index]){//如果该元素比默认元素大(降序,求最大下标)
                     index = j;//用此元素下标替换默认最小元素下标
                }
            }
             //在内层循环结束后,找到了本轮循环的最小元素及所对应的下标
             //接下来进行交换,将最小元素放到该轮排序的第一个元素位置
             if(i!=index){//如果该轮的第一个元素就是最小元素,就不用交换了
                 int temp = arr[i];
                 arr[i] = arr[index];
                 arr[index] = temp;
            }
        }
         return arr;
    }
 }

 

8、继承 extends

  • 作用:代码复用

  • 通过extends(扩展)来实现继承

(1)超类/父类:派生类所共有的属性和行为**

         派生类/子类:派生类所特有的属性和行为**

(2)先有的派生类,再将派生类中的共有属性和行为泛化出超类。

         子类继承父类的一切,除了私有方法和构造方法

         派生类可以访问: 超类的+派生类的,超类只能访问超类的

(3)一个超类可以有多个派生类, 一个派生类只能有一个超类------单一继承

(4)继承具有传递性

举例:

  程序中的继承:代码不用自己写,自己也能用。

  生活中的继承:钱不用自己挣,自己也能花。

           继承皇位:江山不用自己打,自己也能坐。

 //举例:
 class Aoo{//可以访问a
  int a;
 }
 class Boo extends Aoo{//可以访问b+a
  int b;
 }
 class Coo extends Boo{//可以访问c+b+a
  int c;
 }

(5)java规定:构造派生类之前必须先构造超类

  • 在派生类的构造方法中,若自己没有调用超类的构造方法,则默认super()调用超类的无参构造方法

  • 在派生类的构造方法中,若自己调用了超类的构造方法,则不再默认提供

  • 说明:super()调用超类构造方法必须位于派生类构造方法的第一行

 //举例:
 class Student extends Person{
  String stuId;
  /*public Student(){//无参构造
  super();//无参构造
  }*/
  public Student(){//有参构造
  super("zs");//有参构造,必须传参
  }
  public Student(String name){
  super(name);//间接传参
  }
  void study(){}
 }

 

9、super:指代当前对象的超类对象

super的用法:(区别调用超类还是派生的变量/方法)

  • super.成员变量名-----------访问超类的成员变量

  • super.方法名()---------------调用超类的方法(重名时,必须加super.;不重名时,可不写super,前提是子类已经继承父类)

  • super()-------------------------调用超类的无参构造方法

 //实例1:
 public class SuperDemo {
     public static void main(String[] args) {
         Boo o = new Boo();
    }
 }
 //超类无参数
 class Aoo{
     Aoo(){
         System.out.println("超类构造方法");
    }
 }
 class Boo extends Aoo{
     Boo(){
         //super(); //默认的--调用超类的无参构造
         System.out.println("派生类构造方法");
    }
 }
 //超类有参数
 class Coo{
     Coo(int a){
    }
 }
 class Doo extends Coo{
     Doo(){
         super(5); //调用超类的有参构造
    }
 
     /*
     //如下代码为默认的:
     Doo(){
         super();
     }
     *///使用默认无参的超类构造会报错
 }
 
 //实例2:
 class Person{
  String name;
  int age;
  String address;
 
  public Person(){}//无参
  public Person(String name){//有参构造
  this.name = name;
  }
  void eat(){}
  void sleep(){}
 }
 class Student extends Person{
  String stuId;
  /*public Student(){//无参构造
  super();//无参构造
  }*/
  public Student(){//有参构造
  super("zs");//有参构造,必须传参
  }
  public Student(String name){
  super(name);//间接传参
  }
  void study(){
  super.address="123";
  super.sleep();//可写可不写,前提是不重名
  eat();
  }
 }
 
 //实例3:
 public FlyingObject(double x, double y, double width, double height) {
  super();//默认每个类都有一个父类Object(顶级超类),调用此父类无参构造方法,可不写(删除此行),默认调用无参构造方法。
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
 }

 

10、package:包

对类进行分类,方便管理代码,package还定义了命名空间,减少了命名冲突,不同包中可以定义同名类。

(1)作用:避免类的命名冲突

(2)一个包中可以定义多个类,同包中的类不能同名,包名常常有层次结构

(3)类的全称(全限定名、完全限定名、全名=包名+类名): 包名.类名

Java程序编译和运行期间都采用全名。

在同一包中:

  • 可以省略包名直接写类名。

不同包中:

  • 在不同包中要用import导入其它包中的类,可以省略包名直接写类名。 注意:在Java源文件中不能同时导入同名类。

  • 在不同包中可以用全限定名访问其他包中的类。

   • 如果同时使用了不同包中的同名类就不能省略包名,必须都用全限定名访问。

总结:同包中的类可以直接访问,不同包中的类不能直接访问,若想访问:

  • 先import声明类再访问类---------建议,常用

  • 类的全称-----------------太繁琐、不建议

(4)包名要符合Java命名规范,建议包名采用小写字母,多个单词用“.”隔开。

域名(全球唯一)反写   . 项目名称   . 模块名称          . 类名

  cn.tedu                          . aproject   . stumanager      .

  cn.tedu          . aproject   . teachmanager   .  

注意:

  • 必须在java源文件中的第一行使用package声明包,package语句只能有一行。

  • package可以不写,不写表示使用默认包,Java官方建议类一定有package。

  • package相同的类放在同一包中(若Demo01属于package,Demo02属于package,将Demo01、Demo02都放到package下)

  • 包编译之后变成多对应的文件夹。

 

11、访问控制修饰符:保护数据的安全

访问控制:用于设定类、属性、方法等资源的可见范围。

       利用访问控制可以控制数据及方法的可见范围,避免意外篡改,保护软件组件(对象)的安全问题。Java提供的访问控制修饰符,实现软件组件之间的访问控制。

Java提供4个访问修饰词:建议:数据/属性(变量)私有化,行为(方法)公开化

  public:公有的,任何类,修饰类、修饰属性、修饰方法、修饰构造器

  private:私有的,本类, 修饰类中的成员:属性、方法、修饰构造器

  protected:受保护的,本类、派生类(子类)、同包类,修饰类中的成员:属性、方法、构造器等。

       在开发中很少使用包作为访问控制的范围,所以保护的的修饰资源主要是为子类使用。特别是从子类“泛化”到父类中的属性和方法,经常定义为保护的,这样子类就可以通过继承得到这些属性和方法。

  默认的:缺省的,不写任何修饰词,本类、同包类,修饰类、修饰属性、修饰方法、修饰构造器------java特别不建议默认权限。

注意:

  • 类的访问权限只能是public或默认的,但针对具体的问题仍要具体分析

  • 类中成员的访问权限如上四种都可以

  • 类内部资源始终是可以访问的

  • 公有的可以在任何位置访问

 //举例:
 public class Aoo{
  public int a;//公开的
  protected int b;//受保护的
  int c;//缺省的
  private int d;//私有的
  void show(){
  a=1;//本类均可以访问
  b=2;
  c=3;
  d=4;
  }
 }
 class Boo{
  void show(){
  Aoo o = new Aoo();
  o.a=1;
  o.b=2;
  o.c=3;
  //o.d=4;//private只能在本类访问
  }
 }
 class Coo extends Aoo{
  void show(){
  a=1;
  b=2;
  c=3;//默认的,虽属于子类,但在同包内,故也可以访问
  //d=4;
  }
 }

 

12、final:最终的、不可改变的

Java中的final可以修饰变量,修饰方法,修饰类,单独应用几率低。

(1)修饰变量:可以初始化,但不能再次更改

局部变量:在方法中声明的变量

       局部变量加final修饰以后,只能初始化一次,不能再次修改了。使用final的目的是保护局部变量的值不变,避免在方法运算期间被意外篡改。 如果一个需要反复改变的局部变量就不要使用final修饰。

 //a.final修饰的基本类型变量
 final int a;//声明
 a=8;//第一次为变量赋值,称为初始化
 //a=9;//编译错误,final变量不能被再次初始化(赋值)
 final int b = 10;//声明并初始化
 //b = 11;//编译错误,final变量不能被再次初始化(赋值)
 
 //b.final修饰的引用类型变量
 //引用类型变量初始为一个地址值后不能被再次修改
  //final修饰的变量arr只能初始化一次,不能再修改
 //arr是引用变量,值是数组对象的首地址
  final int[] arr = {5,6};//arr中存储的数组地址(arr引用)不能被再次修改
 //arr引用不能修改,但是被引用对象的内容可以修改
 arr[0] =9;
 System.out.println(Arrays.toString(arr));//[9,6]
 //arr = new int[10];//不可以更改arr变量的值,因为arr是final类型的。
 
 final Ball ball = new Ball();
 ball.d = 10;//引用对象的内容可以修改
 //ball = new Ball();//引用对象不可以被更改,不能更换ball的地址值
 //ball = null;//引用对象不可以被更改,不能更换ball的地址值
 
 final Girl gf = new Girl();
 gf.age++;//引用对象的内容可以修改
 System.out.println(gf.age);
 //gf = new Girl();//引用对象不可以被更改,不能更换gf的地址值
 /*
    混淆点:当引用类型变量被设置为final的时候,表示其值就是运用对象的地址值不能再次被修改了,但是此时被引用对象的属性或者元素是可以被修改的。
 /*
 
 /*
 c.final修饰的方法参数
    Java中方法参数也是一种局部变量,只是其声明位置是方法参数,在接收到传递参数时候初始化。
    在方法参数上可以使用final修饰,修饰以后也是初始化以后不能再次修改,由于方法参数是在调用方法传递参数值时候初始化的,所以在方法运行期间参数变量的值不能修改了。
    使用final修饰方法参数的好处也是保护变量的值,避免在方法运行期间参数变量的值被恶意篡改,比如使用“汇率”作为方法参数就不能在运行期间对其进行修改。
 */
 public static void main(String[] args) {
  //a.方法参数是局部变量,在传递参数时初始化;
  //b.final的方法参数,在接收参数初始化以后不能被再次修改.
  test(3,4);//a=1,b=4
         test(5,6);//a=1,b=6
    }
     public static void test(int a,final int b){
         a=1;
         //b=2;//编译错误,final变量不能再次被初始化
         System.out.println("a="+a+",b="+b);
    }
 }
 
 /*
 d.final修饰实例变量
    在类中声明的对象属性,由于是属于每个对象实例的变量,所以也被成为“实例变量”。final可以修饰实例变量,在final修饰实例变量时候只能有两种初始化方式:
  • 声明同时初始化
  • 在构造方法中初始化
 并且实例变量也是在初始化以后就不能再次修改了。
    final修饰局部变量,只要在用之前初始化即可。
    使用final修饰实例变量的目的是保护实例变量,使其值在初始化以后不能改变,避免程序的意外篡改。比如希望一个对象的唯一ID编号,在初始化以后就不能改了,就可以利用final修饰。
    在实际开发中很少使用final修饰的实例变量!主要原因是不方便对象的使用。很多Java底层框架都会利用对象池重复使用对象,避免反复创建销毁对象的性能开销,如果对象属性是final的,就无法再次进行赋值重用对象了。
 */
 public class FinalDemo02 {
  public static void main(String[] args) {
  Eoo eoo = new Eoo(8);
     System.out.println(eoo.a);//5
     System.out.println(eoo.b);//8
     //eoo.a = 10;//编译错误,初始化以后不能再修改
     //eoo.b = 20;//编译错误,初始化以后不能再修改
     Eoo eoo1 = new Eoo(10);
     System.out.println(eoo1.a);//5
     System.out.println(eoo1.b);//10
     //eoo1.a = 11;//编译错误,初始化以后不能再修改
     //eoo1.b = 22;//编译错误,初始化以后不能再修改
  }
 }
 class Eoo{
  //final的属性必须初始化
  //1、直接初始化
  final int a = 5;
  final int b;
  public Eoo(int b){
     //2、在构造方法中初始化
     this.b = b;
  }
 }

(2)修饰方法:方法不能在子类中重写

       方法上可以使用final修饰,final的方法在子类中不能被重写修改了。简单理解:final方法不能被重写。

       final修饰的方法不会影响方法在当前类中的使用,但是如果派生了子类,则在子类中不能重写父类中定义的final方法。

       final方法的好处是避免被子类使用重写语法修改方法的功能,保护方法的功能是“最终”版本。如果需要保护方法的功能,避免在子类中重写修改,就可以使用final进行声明。

       但是在实际工程项目中很少使用final方法,原因是很多框架工具都会采用“动态代理”技术代理(重写)对象的功能,实现灵活的软件功能,如果使用final的方法将会直接影响这些框架功能!很多软件开发企业在编程规范中明确规定:不能声明final方法。

 public class FinalDemo03 {
     //final方法可以被子类继承,但是不能被子类重写
     public static void main(String[] args) {
         Foo foo = new Foo();
         foo.test();
         SubFoo subFoo = new SubFoo();
         subFoo.test();
    }
 }
 class Foo{
     public final void test(){
         System.out.println("Foo.test()");
    }
 }
 class SubFoo extends Foo{
 //   public void test(){//编译错误,不能重写Foo中的final方法
 //       System.out.println("SubFoo.test()");
 //   }
 }

(3)修饰类:不能再被继承派生出子类

        类名也可以使用final修饰,被final修饰的类将不能再派生子类了,也就是终结了类的继承。简单理解:final类不能被继承。但子类前可以加final修饰。

       final类的好处和final方法类似,也是可以避免被继承和重写,避免被子类修改功能。Java的很多核心API,都是final类型,这样就保护了这些非常重要的API功能。这些API包括:String、Math、Integer、Double、Long等。这些API都不能派生子类。

       在开发中也不允许使用final声明类,原因是因为声明final类之后,造成很多框架无法采用“动态代理”技术代理扩展对象的功能,很对软件开发企业在编程规范中明确规定:不能声明final类!

 public class FinalDemo04 {
    public static void main(String[] args) {
        /*
        测试final类
          */
        Goo goo = new Goo();
        goo.test();
    }
 }
 final class Goo{
    public void test(){
        System.out.println("test()");
    }
 }
 //class SubGoo extends Goo{}//编译错误,不能继承final类

final总结:

  • final修饰的变量:可以初始化,不能再更改,很常用

  • final修饰的方法:不能在子类中重写,不常用

  • final修饰的类:不能在被继承派生出子类了,不常用。

 

13、static:静态的

在Java中可以修饰成员变量、修饰方法,可以与final联合使用修饰常量。

成员变量:作为类的成员,在类体中声明的变量,成员变量有三种:

       a.实例变量:没有static修饰,属于每个对象属性,存在堆中,每个对象中都有一份,通过对象名点访问,一般在构造方法中初始化。

       b.静态变量(类变量):有static修饰,属于类的变量,存在方法区中,只有一份,全体对象共享的同一份变量,通过类名点访问,一般在静态块中初始化。

       c.常量(Constant):是不变的常数。(static、final、static+final修饰) 初始化后不能再改变值,不会变动的值。 常量可以理解为一种特殊的变量,它的值被设定后,在程序运行过程中不允许被改变。    

          final 常量名(大写) = 值;

          final double PI = 3.141592653589793;

补充

        实例变量:

    1.类中,方法外

    2.创建对象时存储在堆中,对象被回收时一并被回收

      3.有默认值

        局部变量:

    1.方法中

    2.调用方法时存储在栈中,方法调用结束时与栈帧一并被清除

    3.没有默认值

(1)静态变量:类变量

  • 由static修饰的成员变量

  • 属于类的变量,存储在方法区中,只有一份,可以被全体对象共享(始终唯一)

  • 常常通过类名点来访问:类名.变量名

  • 何时用:所有对象所共享的数据(图片、音频、视频等) (软件中只有一分的数据应该使用static修饰)

 //举例:
 class Loo{//演示静态变量
  int a; //成员变量
  static int b; //静态变量
  public Loo() {
  a++;
  b++;
  }
  void show() {
  System.out.println(a+","+b);
  }
 }
 public static void main(String[] args) {
  Loo o1 = new Loo();
  o1.show();//1,1
  Loo o2 = new Loo();
  o2.show();//1,2
  System.out.println(Loo.b);//2
  System.out.println(o1.b);//2
  System.out.println(o2.b);//2
 }
 
 //举例:
 public class StaticDemo {
     public static void main(String[] args) {
         /*
             实例变量a:每个对象都有一个实例变量a
             静态变量b:属于类的一份变量,全体对象共享同一个b
          */
         Foo foo = new Foo();
         Foo foo1 = new Foo();
         foo.a = 1;//使用对象名.访问对象属性
         foo1.a = 2;//使用对象名.访问对象属性
         Foo.b = 3;//使用类名.访问静态变量(类的属性)
         Foo.b = 4;//静态变量可以被修改
         System.out.println("Foo.b="+Foo.b);//获取静态变量
         // 静态变量通过实例引用访问,但不建议这样使用,静态变量一律采用“类名.静态变量名”来访问
         System.out.println("foo.a="+foo.a+",foo.b="+foo.b);//编译正确
         System.out.println("foo1.a="+foo1.a+",foo1.b="+foo1.b);//编译正确
    }
 }
 class Foo{
     int a;//实例变量(对象属性)
     static int b;//静态变量(类的属性)
 }
 

静态变量工作原理: 静态变量在类加载期间在方法区中分配,静态变量是属于类的变量。

       1、Java源文件经过编译得到字节码文件,每个类编译为一个class字节码文件。

       2、当执行Java程序的时候,每用到一个类,Java就会自动将对应的字节码加载到方法区

    a.创建对象时会自动加载类

    b.访问类的静态属性时会自动加载类

    c.执行类中的静态方法时会自动加载类

3、如果类中有静态变量,Java就会在加载类期间将其在方法区中分配出来,静态变量也初始化一次,只有一份。

4、创建对象时按照类中声明的实例变量分配对象的属性,每创建一个对象,就会分配一组对象属性。

注意:

  a.字节码文件只加载一次

  b.静态变量不会分配给对象,无论创建多少对象,静态变量只有一份;

  c.因为静态变量属于类的变量,所以建议使用类名引用静态变量;

  d.因为实例变量属于对象,所以建议使用引用(对象)访问实例变量;

  e.在类内部访问静态属性和实例变量时候可以省略类名或者当前对象引用。

(2)静态方法

  • 由static修饰的方法

  • 属于类的方法,存储在方法区中,只有一份

  • 常常通过类名点来访问:类名.静态方法名,例如:Math.random()

  • 静态方法没有隐含的局部变量this(或者说:没有隐式this传递),在静态方法中不能直接访问实例变量和对象方法。

    对象方法中是保护隐含局部变量this,通过this引用访问当前对象的属性和方法。

    因为main方法也是静态方法,所以main方法也不能访问当前类型的实例变量和对象方法。

  • 何时用:方法的操作仅与参数有关,与对象数据(成员变量)无关

 /*
 举例:
     Math.sqrt(25);
     假设sqrt方法不是静态的
     Math m = new Math();
     double b = m.sqrt(25);//5.0
     Math m2 = new Math();
     double b2 = m2.sqrt(25);//5.0
     无论m1,m2...m100中的哪一个对象,去sqrt(25),最终结果一样。
     说明sqrt操作与对象无关,与参数相关。
 */
 
 //实例:
 public class StaticDemo01 {
     int c;//实例变量
     static int d;//静态变量
     public static void main(String[] args) {
         //1、以下针对Person类
         Person person = new Person("Tom");
         System.out.println(person.name);//用引用访问对象的属性
         person.whoru();//用引用访问对象的方法
         Person.add(1,2);//静态方法通过类名.来访问
         person.add(3,4);//静态方法也可以通过对象.来访问,但不建议
 
         //2、以下针对StaticDemo01当前类
         StaticDemo01 main = new StaticDemo01();
         System.out.println(main.c);//用引用访问对象的属性
         System.out.println(StaticDemo01.d);//用类名访问类的变量
         StaticDemo01.sum(5,6);//静态方法通过类名.来访问
         main.sum(7,8);//静态方法也可以通过对象.来访问,但不建议
 
         //test(6);//非静态方法不能在静态方法中使用
         sum(9,10);//静态方法可以在静态方法中使用
 
         //非静态变量不能在静态方法中使用,静态变量可以在静态方法中使用
         //c = 11;//编译错误,非静态字段'c'不能从静态上下文中引用
         d = 12;//在类内部访问静态属性和实例变量时候可以省略类名或者当前对象引用
    }
     public void test(int c){//普通方法
         this.c = c;
    }
     public static void sum(int a,int b){//静态方法
         System.out.println(a+b);
    }
 }
 
 class Person{
     String name;//实例变量
     public Person(String name) {//构造方法
         this.name = name;
    }
     public void whoru(){//普通方法(使用了成员变量)
         //对象方法中包含隐含局部变量this
         System.out.println("我是"+this.name);
    }
     //如果方法中没有用到当前对象的属性或方法就声明为static
     public static void add(int a,int b){//静态方法
         //静态方法中没有隐含局部变量this
         //System.out.println(a+b+","+this.name);
         System.out.println(a+b);
    }
 }
 

(3)静态块:被static修饰的代码块

  • 由static修饰

  • 属于类,在类被加载期间自动执行,一个类只被加载一次,所以静态块也只执行一次

  • 何时用:加载/初始化静态资源(图片、音频、视频等)

    注意 实例变量:一般情况下在构造方法中做初始化 静态变量:一般情况下在静态块中做初始化 代码块执行时机:静态块、代码块、构造方法

 class Too{
  Too(){
  System.out.println("构造方法");
  }
  {
  System.out.println("代码块");
  }
  static{
  System.out.println("静态块");
  }
 }
 public static void main(String[] args) {
  Too t1 = new Too();
  Too t2 = new Too();
 }
 /*
 输出结果:
  静态块
  代码块
  构造方法
  代码块
  构造方法
 结果分析:在创建对象的过程中,会优先调用静态块,对静态变量进行初始化,且只调用一次,最先执行。由于代码块只包含方法体,没有方法名,无法被类或对象调用,所以执行完静态块后,会先执行代码块,再执行构造方法。即:每创建一次对象,代码块和构造方法各分别执行一次,静态块不再执行。
 */

(4)static final:用于声明常量

常量:不会变化的量,软件中指固定不变的数值

  • 必须声明同时初始化,两个关键字顺序可以调换

  • 由类名点来访问,不能被改变

  • 建议:常量名所有字母都大写,多个单词用_分隔:

  • Java API中提供了很多常量:Math.PI、Math.E、Integer.MAX_VALUE、Integer.MIN_VALUE、Long.MAX_VALUE、Long.MIN_VALUE

  • 编译器在编译时会将常量直接替换为具体的值,效率高

  • 何时用:数据永远不变,并且经常使用。软件中不能改变的数据,应该都定义为常量。

 //实例:
 public static final int WIDTH = 400;//必须声明同时初始化
 public static final int HEIGHT = 700;
 
 //静态变量与常量区别:
  public static int num = 5;
  public static final int COUNT = 10;
  System.out.print(Boo.num);
  System.out.println(Boo.COUNT);
 /*
 静态变量:
  1.加载Boo.class到方法区
  2.num加载到方法区中
  3.到方法区中获取num的值
 常量:编译时将常量直接替换为具体的值
 */

(5)import static:静态导入

Java8提供了静态导入语法,用于简化静态资源的编码。

 //实例:
 import static java.lang.Math.*;//import static静态导入
 public class StaticImportDemo {
     public static void main(String[] args) {
         /*
             输出30、45、60、90度的正弦值
             Math.sin()正弦函数,参数必须是弧度
             Math.toRadians()角度转换函数,将角度转换为弧度
             PI是180°角
          */
         //1、没有静态导入前
 //       System.out.println(Math.sin(Math.toRadians(30)));
 //       System.out.println(Math.sin(Math.toRadians(45)));
 //       System.out.println(Math.sin(Math.toRadians(60)));
 //       System.out.println(Math.sin(Math.toRadians(90)));
 //       System.out.println(Math.sin(Math.PI/2));
 
         //2、有静态导入后,简化代码
         System.out.println(sin(toRadians(30)));
         System.out.println(sin(toRadians(45)));
         System.out.println(sin(toRadians(60)));
         System.out.println(sin(toRadians(90)));
         System.out.println(sin(PI/2));
    }
 }

 

14、抽象:abstract

(1)抽象类

       在面向对象设计过程中,会利用“泛化”将子类的共同属性和方法抽取出来设计出父类,此时的父类往往是半成品类,只包含部分属性和方法,甚至属性值都没有合理初始化,如果直接创建父类对象并且使用有可能造成各种不理想结果,甚至是异常故障。

       超类的作用是为子类提供代码复用,但这种“半成品类”应该使用abstract声明为抽象类(半成品类),Java编译器就会限制超类类型,不允许其直接实例化创建对象,不能被实例化,就不可能出现不合理的结果了。因此,面向对象设计时候根据子类泛化得到的半成品父类,应该定义为抽象类,这样可以限制创建半成品类的对象,减少意外的错误发生。

  • 由abstract修饰

  • 包含抽象方法的类必须是抽象类,抽象类中的方法不一定是抽象方法,抽象类可以不包含抽象方法。

  • 抽象类不能被实例化创建对象(new对象)

  • 抽象类是需要被继承的,可以定义变量,派生类:

    • 重写所有抽象方法----------变不完整为完整 常用

    • 也声明为抽象类(作用:不想被实例化)------------一般不这么用

  • 抽象类的意义:

    • 封装派生类所共有的属性和行为----------代码复用

    • 为所有派生类提供统一的类型-------------向上造型

    • 可以包含抽象方法,为所有派生类提供统一的入口(能点出来),派生类的具体行为实现不同,但入口是一致的。

  • 注意:

    • 抽象类可以拥有构造方法

    • abstract不能和以下关键字共用:final、private、static

 //实例:
 public class AbstractDemo {
  public static void main(String[] args) {
  //Person p = new Person();//抽象类不能被实例化
  Student s = new Student();
  s.whoru();
  //Teacher t = new Teacher();//抽象类不能被实例化
  }
 }
 //包含抽象方法的类一定是抽象类
 abstract class Person{
  public abstract void whoru();
 }
 class Student extends Person{
  public void whoru(){//派生类必须重写抽象方法
  System.out.println("我是学生");
  }
 }
 //抽象类不一定要包含抽象方法
 //当声明为抽象类时,就是不想被实例化(new对象)
 abstract class Teacher extends Person{
  public void whoru(){//派生类必须重写抽象方法
  System.out.println("我是老师");
  }
 }
 class Worker extends Person{
  public void whoru(){//派生类必须重写抽象方法
  System.out.println("我是工人");
  }
 }

(2)抽象方法

抽象使用规则:每个子类都有,但是每个子类实现都不同的方法泛化为抽象方法。 抽象方法的语法

  • 由abstract关键字定义

  • 只有方法的定义,没有具体的实现(连{}都没有,即:不包含方法体)

  • 包含抽象方法的类必须声明为抽象类,因为包含抽象方法的类一定是不完整的半成品类。抽象类可以不包含抽象方法(该 抽象类不想被实例化)。

  • 子类继承抽象类时候必须重写(实现)抽象方法,否则出现编译错误。可以将抽象方法看做父类对子类的行为约定,必须被子类重写实现。

  • 使用抽象方法的好处 可以将抽象方法看作是父类对子类声明的行为约定,必须由子类遵守实现。由于Java编译器的语法检查子类是否实现了方法,这样则可以避免开发者实现子类的时候意外落下没有重写的方法。

 

问题补充

 

 

a.问:抽象方法可不可以不写?因为派生类总归得重写,抽象方法并达不到复用的效果。

       答:不可以,若不写抽象方法,在向上造型时,通过超类的引用就点不出来这个方法了。抽象方法存在的意义,就是为了当向上造型时,通过超类引用能点出这个方法。

b.问:做成普通方法,在向上造型时,也能点出来,那为什么不做成普通方法?

       答:普通方法,可以被重写也可以不被重写,而抽象方法是必须被重写的,所以设计为抽象方法可以达到强制必须重写的效果。

 

 

15、内部类

分类:

  成员内部类:(比较常用) 在类中定义

  局部内部类:(不常用) 在方法中定义

  匿名内部类:(常用) 在方法中使用匿名内部类语法声明

(1)成员内部类:应用率低,了解即可

  • 类中套类,外面的称为Outer外部类,里面的称为Inner内部类;

  • 内部类通常只服务于外部类,对外不具备可见性;

  • 内部类对象通常在外部类中创建

  • 内部类中可以直接访问外部类的成员(包括私有的)

  • 内部类中有个隐式的引用指向了创建它的外部类对象 语法: 外部类名.this------------------------重点记住

 //实例:
 public class InnerClassDemo {
  public static void main(String[] args) {
  Foo foo = new Foo();
  foo.demo();
  }
 }
 class Foo{//外部类
  int a=9;
  public void demo(){
  Koo koo = new Koo();
  koo.test();
  }
  class Koo{//内部类
  int b=8;
  public void test(){
  System.out.println("Koo.test(),a="+a);
  }
  }
 }

(2)匿名内部类:简化代码的操作,应用率高

       在一行代码上继承父类并且创建出子类实例(对象)的语法

       前提条件:有一个可以被继承的父类型,这个父类型可以是类、抽象类、接口。

      格式:       new         Super()     {…}

                  new运算符 父类类型 子类类体

  • 若想创建一个类(派生类)的对象,并且对象只被创建一次,此时该类不必命名,可以直接声明为匿名内部类。

  • 在匿名内部类中不能修改外面变量的值,因为在此处会默认变量为final的。

  • 匿名内部类中若想访问外部的变量,该变量必须是final的(jdk1.8之前)。

 //举例:
 public class NamelessDemo {
  public static void main(String[] args) {
  Animal dog = new Animal(){
  public void shout(){
  System.out.println("汪汪汪...");
  }
  };
  Animal cat = new Animal(){
  public void shout(){
  System.out.println("喵喵喵...");
  }
  };
  }
 }
 abstract class Animeal{
  public abstract void shout();
 }

匿名内部类简洁地省略自类类名,也因为没有类名造成不能再复用类名创建更多的对象。

使用匿名内部类时候要注意

  • 如果只是简洁地继承父类,并且只需要创建一个子类对象,就采用匿名内部类;

  • 如果子类需要反复使用创建一组子类对象就采用普通的子类。

  • 匿名内部类一定是子类,一定需要有父类型时候才能使用。

  • 匿名内部类的最大优点是语法简洁,在一行上继承子类并且创建了子类对象。匿名内部类使用非常广泛,比如:事件监听器、鼠标事件监听、键盘事件监听等。

 /*
 附:鼠标事件
    鼠标事件是Java提供的一套响应鼠标动作的API,利用这个API只需要简单添加少量代码就可以根据鼠标动作执行软件功能,实现利用鼠标控制软件的执行流程。
 */        
 //实现步骤:
 //1.创建鼠标事件处理类MouseAction,继承MouseAdapter,编写鼠标事件处理方法
 public class MouseAction extends MouseAdapter {
     /**
      * 重写MouseAdapter类中约定的鼠标移动事件方法
      * 这个方法会在鼠标事件发生时候执行
      * @param e
      */
     public void mouseMoved(MouseEvent e) {
         //super.mouseMoved(e);
         System.out.println("鼠标移动了");
    }
 }
 //2.将鼠标处理事件类创建为对象,注册到发生鼠标事件到面板
 //创建鼠标事件处理对象
 MouseAction action = new MouseAction();
 //将鼠标事件处理对象注册到面板中,处理鼠标移动事件
 this.addMouseMotionListener(action);
 //3.在发生鼠标事件时候,会自动触发MouseAction的事件处理方法
 //改为匿名内部类形式创建:
 MouseAdapter action = new MouseAdapter(){
         public void mousemoved(MouseEvent e){
                 System.out.pritnln("鼠标移动了");
        }
 }
 this.addMouseMotionListener(action);
 //代码简化:
 this.addMouseMotionListener(new MouseAdapter() {
  public void mouseMoved(MouseEvent e) {
        //鼠标事件发生时候,e对象中包含鼠标相关的数据,如:x、y等
        int x = e.getX();//获取鼠标位置移动时的x、y坐标
        int y = e.getY();
        //System.out.println("鼠标移动了:"+x+","+y);
      }
 });
 

常见面试题: 问:内部类有独立的.class吗? 答:有

做功能的套路:

(1)先写行为/方法:

  • 若为对象所特有的行为,就将方法设计在特定的类中

  • 若为对象所共有的行为,就将方法设计在超类中

(2)窗口调用:

  • 若为定时发生的,则在定时器中调用

  • 若为事件触发的,则在侦听器中调用

 

16、 封装(Encapsulation)

       一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。同时,它也是一种防止外界调用客户端,去访问对象内部实现细节的手段,这个手段是由编程语言本身来提供的。

简单理解:封装数据和算法细节,暴露可以安全访问的方法和数据。如:利用对象封装数据,利用方法封装计算过程。

Java中的封装就是采用访问修饰词实现的:

  1)需要隐藏的属性和方法定义为私有的private

  2)需要留给子类使用的属性和方法定义为保护的protected

  3)确实需要公开访问的属性和方法定义为公有的public

  4)不清楚使用哪种访问控制修饰符修饰的时候,优先选择私有private。

设计规则:数据(成员变量)私有化(private)、行为(方法)公开化(public)

成员变量分两种:

  • 实例变量:没有static修饰,属于对象的,存储在堆中,有几个对象就有几份,通过对象点来访问

  • 静态变量:有static修饰,属于类的,存储在方法区中,只有一份,通过类名点来访问

 

17、多态

       多态的定义是计算机程序执行时,相同的消息可能会送给多个不同的类别的对象 ,而系统可依据对象所属类别引发对应类别的方法,从而有不同的行为。

       使用多态编程可以使不同类型个体进行统一管理,可以实现不同行为的功能统一处理。比如:可以利用一个FlyingObject数组统一管理全部的敌机,可以使用一个for循环统一执行各种敌机的move方法。

(1)对象的多态:向上造型、向下转型(强转)

       同一个对象,被造型为不同的类型时,有不同的功能。

a.向上造型(自动类型转换):将子类型对象赋值给父类型变量

好处:父类型变量可以引用各种子类型的实例。

  1. 超类型引用指向派生类对象: 超类大,派生类小

  2. 能造型成什么数据类型: 超类+所实现的接口

  3. 能点(调用)什么,看引用的数据类型 动物 o1 = new 老虎();//向上造型/自动类型转换 老虎 o2 = new 动物();//编译错误 向上造型后,子类将不再具备自己定义的方法,只有父类的方法。 但若重写了父类的方法,向上造型的对象的方法为重写后的新方法。

 public class UpDemo {
  public static void main(String[] args) {
  Animal o1 = new Animal();//动物是动物
  o1.a = 1;
  o1.test();
  //o1.b =2;//超类不可以访问派生类的属性和行为
  //o1.demo();
  Tiger o2 = new Tiger();//老虎是老虎
  o2.a = 1;
  o2.test();
  o2.b =2;//派生类可以访问超类的属性和行为
  o2.demo();
  Animal o3 = new Tiger();//老虎是动物
  o3.a = 1;//只能调用超类的的属性和行为
  o3.test();
  //o3.b =2;//编译错误,能调用什么,看引用的类型
  //o3.demo();
  //Tiger o4 = new Animal();//动物是老虎,语义不通
  }
 }
 class Animal{
  int a;
  void test(){
  System.out.println("Animal.test()");
  }
 }
 class Tiger extends Animal{
  int b;
  void test(){
  System.out.println("Tiger.test()");
  }
 }
 /*
 输出结果:
 Animal.test()
 Tiger.test()
 Tiger.test()
 */

b.向下转型(强制类型转换):将父类型引用的对象赋值给子类型变量

  基本数据类型强转 -- 不会报错,可能溢出或者丢精度

  引用数据类型强转 -- 有可能发生异常

成功的条件只有两种:

  1. 引用所指向的对象,就是该类型: 老虎(对象)是动物(类型) --> 老虎(对象)是老虎(类型)

  2. 引用所指向的对象,继承了该类或者实现了该接口: 老虎(对象)是动物(类型) --> 老虎(对象)是(接口类型)

强转时若不满足上述的条件,则发生ClassCastException(类型转换异常)(运行错误而非编译错误)

建议:在强转之前先通过 instanceof 来判断引用所指向的对象是否是该类型

格式:引用(对象) instanceof 引用类型

  测试一个对象是否为一个类的实例

  a.是指定类型对象返回true

  b.是指定类型的子类型对象返回true

  c.不是指定类型的对象返回false

 public class DownClassDemo {
  public static void main(String[] args) {
  Y o = new Y();//new本类对象
  X o1 = new Y();//向上造型
  Inter o2 = new Y();//向上造型
  //使用instanceof来判断引用所指向的对象是否是该类型,
    满足时自动转换
  if(o1 instanceof Y){
  Y o3 = (Y)o1;//强转---本类型转本类型
  }
  if(o1 instanceof Inter){
  Inter o4 = (Inter)o1;//强转---实现了该接口
  }
  if(o1 instanceof Z){
  Z o5 = (Z)o1;//不判断时,发生ClassCastException类型转换异常
  }
  }
 }
 //定义接口
 interface Inter{}
 //定义超类
 class X{}
 //派生类继承超类并实现接口
 class Y extends X implements Inter{}
 //派生类继承超类
 class Z extends X{}

(2)行为的多态:重写方法,抽象方法都是多态的

同一类型的引用,指向不同的对象时,有不同的实现。

何时使用 多态的前提是继承,在有继承关系存在的时候才能使用多态,其次是对子类型进行统一处理。具体在开发中要不要考虑多态,先按照具体类型编程,当发现各种子类型都需要相同处理的时候,再重构代码提升到父类型统一进行处理,这样就是多态处理了。

1. 意义:

  1.1)同一类型的引用指向不同的对象时,具有不同的实现----所有抽象方法都是多态的---行为的多态:cut()、step()、getImage()...

  1.2)同一个对象被造型为不同的类型时,具有不同的功能----所有对象都是多态的 ----对象的多态:我、水...

2.向上造型/自动类型转换:

  2.1)超类型的引用指向派生类的对象

  2.2)能造型成为的数据类型有: 超类+所实现的接口

  2.3)能点出来什么,看引用的类型

3.强制类型转换,成功的条件只有如下两种:

  3.1)引用所指向的对象,就是该类型

  3.2)引用所指向的对象,实现了该接口或继承了该类

4.强转时若不符合如上条件,则发生ClassCastException类型转换异常

  建议:在强转之前先通过instanceof来判断引用指向的对象是否是该类型

 

18、接口:(特殊的抽象类:只有抽象方法的抽象类)

(1)是一种数据类型(引用类型)

(2)由interface定义

(3)只能包含常量和抽象方法(JDK1.8以前)

(4)接口不能被实例化(new)

(5)接口是需要被实现/继承的,实现类/派生类:implements 必须重写接口中的所有抽象方法

(6)一个类可以实现多个接口,用逗号分隔 若又继承又实现时,应先继承后实现

(7)接口可以继承接口 类与类---继承、类与接口---实现、接口与接口---继承

区别 抽象类:同一种类的共有行为和属性做抽取 接口: 不同种类的共有行为做抽取

设计规则:

  1. 将所有派生类所共有的属性和行为,抽到(泛化)超类中-------------抽共性

  1. 若所有派生类的行为都一样,设计为普通方法 若所有派生类的行为都不一样,设计为抽象方法

  1. 将部分派生类所共有的属性和行为,抽到接口中 接口是对继承单根性的扩展--------------------实现多继承(多实现)

 //实例:
 public class InterfaceDemo {
  public static void main(String[] args) {
  Interface i1;//声明接口引用
  //i1 = new Interface();//编译错误,接口不能被实例化
         InterfaceDemo o1 = new InterfaceDemo();//普通类new对象
         Ioo i = new Ioo();//实现接口后new对象
         i.test();
         Interface2 i2 = new Ioo();//向上造型
         i2.test();
         Interface i3 = new Ioo();//向上造型
         i3.show();
         i3.demo();
  }
 }
 //定义接口:里面只能包含常量和抽象方法
 interface Interface{
  public static final int NUM = 1;
  public abstract void show();
  int COUNT = 2;//可省略public static final
  void demo();//可省略public abstract
  //int sum;//编译错误,常量声明时必须同时初始化
  //void say(){}//编译错误,抽象方法不能有方法体
 }
 //定义接口
 interface Interface2{
  void test();
 }
 //接口继承接口
 interface Interface3 extends Interface2{
  void eat();
 }
 class Pooo{}
 //类先继承类再实现接口,要重写接口中的所有抽象方法
 class Ioo extends Pooo implements Interface3, Interface{
  //重写抽象方法必须写public
  public void test() {
  System.out.println("Ioo.test()");
  }
  public void show() {
  System.out.println("Ioo.show()");
  }
  public void demo() {
  System.out.println("Ioo.demo()");
  }
  public void eat() {
  System.out.println("Ioo.eat()");
  }
 }

 

19、内存管理:由JVM来管理

(1)堆

  • 存储new出来的对象(包括实例变量)

(2)GC:垃圾回收器

垃圾:没有任何引用所指向的对象

垃圾回收:JVM提供的机制,可以自动化回收垃圾对象占用的内存空间。

垃圾回收器工作时候

2.1 垃圾回收会在适当的时候自动启动

  • 定时自动启动 • 内存消耗过多时候启动

  • 利用System.gc()方法通知启动

2.2 利用算法检测对象,是否为内存垃圾

  •对象不被引用判断为垃圾

2.3 将标记为垃圾的对象回收,释放内存空间

  • 回收对象时候会调用其finalize方法,如果JVM很快退出可以来不及执行此方法。

垃圾回收器(GC):不定时到内存中(主要查看堆中的数据)清扫“垃圾”,回收过程是透明的(看不到的),不一定一发现垃圾就立即回收,通过调用System.gc()可以建议JVM尽快调度GC来回收垃圾。

(3)实例变量的生命周期

  一个对象从创建到使用直到最终销毁的过程,创建对象时存储在堆中,对象被回收时一并被回收

(4)内存泄漏

  不再使用的内存还没有被及时的回收,严重的泄漏会导致系统的崩溃。

(5)内存溢出

  没有内存可以使用,建议对象不再使用时,应及时将引用设置为null(飞机大战删除越界对象就是避免内存泄露/溢出)

 //实例:
 public class GCDemo {
  public static void main(String[] args) {
  System.out.println("程序开始了");
  Poo poo = new Poo();
  poo = null;
  System.gc();
  System.out.println("程序结束了");
  }
 }
 class Poo{
  int a = 10;
  //对象销毁时,此方法将会被调用
  protected void finalize() throws Throwable {
  System.out.println("我跟随GC走了");
  }
 }
 /*
 输出结果:
  程序开始了
  程序结束了
  我跟随GC走了
 */

(6)栈: 先入后出

6.1 存储正在调用的方法中的局部变量(包括方法的参数)

6.2 调用方法时,会在栈中为该方法分配一块对应的栈帧,栈帧中存储方法中的局部变量以及方法的参数,方法调用结束时,栈帧自动被清除,局部变量一并被清除

6.3 局部变量的生命周期: 调用方法时存储在栈中,方法调用结束时与栈帧一并被清除

(7)方法区

7.1 存储.class字节码文件(包括静态变量、所有方法)

7.2 方法只有一份,通过this来区分具体的调用对象

 

20、总结

面向对象三大特征:

1.封装

  • 类:封装的是对象的属性和行为

  • 方法:封装的是具体的业务功能实现

  • 访问控制修饰符:封装的是具体的访问权限

2.继承

  • 作用:代码复用

  • 接口:部分派生类所共有的属性和行为 派生类:派生类所特有的属性和行为

  • 单一继承、多接口实现,具有传递性

3.多态

  • 行为多态(所有抽象方法都是多态的,通过重写来表现多态) 对象多态(所有对象都是多态的,通过向上造型来表现多态)

  • 向上造型、强制类型转换、instanceof

 

Object:所有类的鼻祖---所有类都直接或间接的继承了Object

 

补充:Timer定时器

 import java.util.Timer;
 import java.util.TimerTask;
 
 public class TimerDemo {
  public static void main(String[] args) {
  Timer timer = new Timer();//定时器对象
  MyTask task = new MyTask();//自定义任务
  //把任务交给定时器执行(自定义任务,延时时间(单位/ms),间隔时间(单位/ms)
  timer.schedule(task, 1000,1000);//每隔1s执行一次定时器任务
  }
 }
 /**
  * 自定义任务
  * @author cjn
  *
  */
 class MyTask extends TimerTask{
  @Override
  public void run() {//任务计划
  System.out.println("HelloWorld!");
  }
 }
 

补充:Linux

  1.开源的操作系统、免费的 主要是服务器操作系统,而Java主要是服务器端开发

  2.Linux与Windows目录结构区别:

    • 文件系统不同: Linux:目录 Windows:盘符

    • 外部设备映射不同: Linux:挂载点 Windows:盘符

    • 安全级别不同: Linux:高 Windows:低

  3.基于命令操作的:

    • pwd: 显示当前工作目录

    • ls: 查看当前工作目录的内容

    • cd: 改变当前工作目录

    绝对路径:相对于根目录的位置,以/开头

    相对路径:相对于当前目录的位置

    . : 当前目录

    .. : 上一级目录

程序 = 算法 + 数据结构

  (1)算法: 解决问题的流程/步骤(顺序、分支、循环)

  (2)数据结构: 将数据按照某种特定的结构来保存---数怎么存 设计良好的/合理的数据结构会导致好的算法

 

21、常见面试题

1.问:switch分支语句可以作用于哪些数据类型上?

  答:switch是分支语句中的一种,可以作用于整型数据的分支语句中,其可以使用的数据类型有byte、short、int、char、枚举、String,其中String从JDK 1.7开始支持。

2.问:重写与重载的区别?

  答: 重写(override):发生在父子类中,方法名相同,参数列表相同

            重载(overload):发生在同一类中,方法名相同,参数列表不同

3.问:简述public、private、protected以及默认权限的访问范围?

  答: 以上几种修饰符都是封装的一种手段,用来控制访问权限,其中:

   public:公开的,任何类,包括同包名、不同包名等

   private:私有的,只有本类内可以使用,即使是子类也没权使用

   protected:受保护的,本类、派生类、同包类,继承中经常用到

   默认的:什么也不写,本类、同包类,但Java不建议使用默认权限

   附:类的访问修饰符:只能是 public或默认不写两种,不能使用protect和private(具体问题具体分析)

4.问:简述java的八种基本数据类型

  答:整型:byte字节型(1)、short短整型(2)、int整型(4)、long长整型(8)

      浮点型: float单精度浮点数(4)、double双精度浮点数(8)

      布尔型:boolean(1)

           字符型:char(2)

5.问:抽象类与接口的区别?

  答:1)抽象类中可以包含变量、常量、普通方法和抽象方法,而接口中只能包含常量和抽象方法

            2)一个类只能继承一个抽象类,但是一个类可以实现多个接口

            3)实现类在实现接口后,必须重写接口中的所有抽象方法 派生类继承抽象类后,可以也声明为抽象类,而并非必须重写所有抽象方法 同一种类的共有行为和属性可以抽取到抽象类,不同种类的共有行为抽取到接口中。

 

posted @ 2021-06-15 15:36  Coder_Cui  阅读(76)  评论(0编辑  收藏  举报