Java - 面向对象

初学者如果不理解面向对象的概念可以看看我的另一篇博客:https://www.cnblogs.com/yangyuanhu/p/11287038.html

对象的内存

image-20191118185432821

变量定义在栈中,而具体的对象数据则在堆区

构造方法

构造函数,构造器

语法要求

1.函数名称与类名一致
2.不允许有返回值类型 
3.不允许使用return语句返回数据

特点:

new 对象时会自动执行 
1.可以重载多个构造方法
2.当我们没有定义任何构造方法时,会自动生成无参的空构造方法
3.一旦我们定义类构造方法,系统将不会自动生成

构造方法只能通过两种方式来调用:

1.在构造方法中可通过this去调用其他的构造方法
2.通过new 关键字  
//在普通方法中也不能去掉用构造方法 
public class Constructer {
    public Constructer(){
    }
    public Constructer(String name){
        this();  //调用空参数构造方法
    }
}

注意注意:调用其他构造方法只能位于方法的第一行语句;

方法参数查找采取就近原则

当对象创建时对象的所有属性都会被自动初始化

================================

封装

特点:

1.只能通过规定的接口(方法)访问内部数据

2.隐藏了内部实现细节,为了隔离复杂度,和提高安全性,提高了类内部的维护性

使用:

修改方法或属性的权限为private 私有化

为私有的属性提供对外开放(public)的setter 和getter方法;

public class Test {

    private String name;//该属性被私有化 此时该属性尽在该类内部可以访问

    public Test(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

概念:

包的本质是一个文件夹

当一个项目中出现大量的类是,维护性会降低,我们可以采取分包的方式来管理 ,将相关的类放到同一个包中,方便管理维护代码,

命名规范:

采用域名倒叙,+模块名称+功能名称

全小写

image-20191119101847961

注意:不同包中可以存在相同名称的类

import

import用于导入某个类

语法:

1.导入某个包下的某个类 import com.yh.package1.Cat;
2.导入某个包下的所有类 import com.yh.package2.*;

注意:
1.当上述两条语句出现了相同的类名称时(package2中也有Cat),优先使用的是语义更清晰的即import com.yh.package1.Cat
2.第二种语法,仅能导入包下的类,不能导入包下的子包

我们也可以在代码中使用完全限定类名来避免冲突:

com.yh.package1.Cat c1 = new com.yh.package1.Cat();
c1.show();

static

static修饰的成员称之为静态成员

特点:

1.可以使用类名直接调用(当然对象也可以)

2.并且static修饰的成员属于类名称空间中,也就是说所有该类对象,共享该资源,相反的如果非static修饰的成员是每个对象独有的,例如每个人的name属性不同;

3.静态成员的生命周期跟随的类,类加载时被加载,虚拟机结束时被销毁;

4.在静态方法中不可以使用this关键字,因为static修饰的成员比对象先被加载

5.静态方法中只能访问静态成员

代码块

使用一对大括号产生一个代码块,当然了方法也是代码块

1.构造代码块

我们还可以在类中直接使用{}来定义出一个构造代码快 像下面这样

class Person(){
  {
    //构造代码块,会在创建类的时候先于构造函数执行,没创建一个对象就执行一次
  }  
}

2.普通代码块

class Person{
  void show(){
    //方法体    
    
    {
      // 普通代码块
    }
    
    //方法体
  }
}

3.静态代码块

在类中使用static来修饰的代码块就是静态代码块,其执行时机是在类被加载时,执行一次,后续不在执行,

什么时候使用?

当我们需要在使用类之前做一些初始化操作时

问题?

1.代码块是否是局部作用域 ? 是的每个代码块都是局部的

2.静态代码块中是否可以存在方法? 不可以局部代码块创建方法没有意义

代码块的执行顺序

静态代码块 -> 构造代码块 -> 构造方法

===============================================================

继承

一个类和另一个类之间的关系,是一种什么是什么的关系(A is aB)

如猪是动物,

好处:子类继承父类可以直接使用父类开放(非private)的已存在成员

image-20191119142825009

使用

extends关键字建立继承关系,一个类只能有一个父类

class Animal{
  String name;
}
class Person extends Animal{
 public void show(){
   System.out.println(this.name);//直接访问父类成员
 }
}

重写/覆盖

override

什么是覆盖:

​ 当子类出现了与父类完全一致(相同返回值类型,相同名称,相同参数列表)的方法时将产生覆盖

何时使用覆盖:

​ 当父类的方法 无法直接满足需求时子类可以覆盖父类已有方法

注意:

1.子类覆盖时方法的返回值类型必须与原始方法返回值类型一致,或者是原始方法返回值类型的子类
2.子类覆盖方法时要求方法的权限必须大于等于原始方法的权限
3.父类的静态成员不会被覆盖,当子类定义了与父类完全一致的方法时,这个方法实际是属于子类的与父类没有关系
4.当子类出现方法名称与参数列表与父类方法一致时则认为是覆盖,要求返回值类型必须一致或是其子类

也就是说如果子类想定义新的方法要么名称不同,要么参数列表不同

补充:子类也可以定义与父类相同的属性,将覆盖父类相同的属性

权限修饰符

修饰符 本类 同包 子类 其他包
public yes yes yes yes
protected yes yes yes no
default yes yes no no
private yes no no no

继承注意事项:构造方法无法被继承

super

当子类覆盖了父类的方法时,按照顺序将优先执行子类中的方法,当我们需要执行父类方法时

可以在子类中使用super关键字来调用父类的方法

class A{
  public A(){
 		   
  }
 	public show(){
    System.out.println("hello java!");
  }
}

class B extends B{
  public B(){
 		   super().show();
  }
}
注意:在子类的构造方法中系统会默认添加super(); 如果父类不存在空参构造函数将编译错误,我们也可以手动的使用super来调用其他的构造函数;
强调:super()必须放在第一行

对象的创建过程

1.加载类(仅发生一次)

2.加载父类静态资源

3.加载子类静态资源

4.执行父类构造代码块

5.执行父类构造函数

6.执行子类构造代码块

7.执行子类构造函数

8.完成

this与super

this:

​ 本类的,属性,方法,构造器

super:

​ 父类的,属性,方法,构造器

注意:this和super不能同时出现在构造函数中

image-20191119212436052

final

翻译为最终,可以修饰类,变量,方法

1.在继承关系中,被final修饰的方法无法被覆盖

2.final修饰变量时,就变成了常量,一旦初始化值不允许修改

3.final修饰类时,表示该类不能被继承

注解

JDK1.5 推出

用于对方法或变量进行说明

image-20191120001010853

源码注解尽在编译完成时自动去除,用于对元素进行标记,方便编译器识别,例如@override

编译时注解指的是编译成class后依然存在的注解

运行时注解指的是,会对程序逻辑产生影响的注解,例如autowrite,transaction等..

=============================================

单例模式

常见23中设计模式,及其分类;

image-20191120005925881

什么是单例:

某个类有且仅有一个实例,那ta就是单例类

image-20191120010309895

java中的实现方式:

1.将构造函数私有化 以禁止外界自己初始化

2.利用static仅加载一次的特性,创建一个静态的对象作为类的私有属性

3.提供访问这个静态对象的方法

按照创建对象时机不同可以分为两种

1.饿汉式: 直接在声明static属性时创建对象 这种方法是线程安全

class SingleInstance{
  private static SingleInstance obj = new SingleInstance();
  private SingleInstance(){
    //私有化的构造函数 
  }
  public SingleInstance getInstance(){
    return obj;
  }
}

2.懒汉式:在获取对象是如果发现对象为空才创建 这种方法是非线程安全

 public class Emperor {
     //定义私有构造方法
    private Emperor(){
        System.out.println("create instance!");
    }
	 //定义私有静态类对象
    private static Emperor obj = null;
	 //定义公有静态方法返回类内的私有静态对象
	 public static Emperor get_instance(){
	     if(obj == null){
	         obj = new Emperor();
	     }
	     return obj;
	 }
}

优缺点:

优点:

​ 节省空间,提高性能

缺点:

​ 扩展困难

​ 当对象长期不使用,困难会被回收导致异常

使用场景:

当程序需要共享同一份对象的数据时

当每个对象数据都相同时,则没有必要创建对个对象

当需要保证某些数据的一致性时,例如: 要生成唯一的id,如果每个对象都有一份自己的生成方式则可能造成数据错误

image-20191120094929672

================================

多态

定义:

​ 多个不同对象可以响应同一个方法产生各自不同的行为

分类:

1.编译时多态

​ 指的是在编译阶段就能识别具体要执行的方法是哪一个,通常是方法重载,通过参数不同决定调用哪个方法

2.运行时多态

​ 只有在运行时才能确定到底调用哪个方法

image-20191120095446497

注意下面的内容都是针对运行时多态,是任何OOP语言的共同特性

特点:

出现多态的两个必要条件

1.必须具备继承关系

2.父类指针指向子类对象

ps:在python中没有这两个限制,python是动态类型,编译期间不会检查对象具体类型,更加灵活,但是也增加了风险

向上转型

指的是用父类指针引用子类实例,会自动将子类转换为父类,转换后将隐藏子类特有的成员,只能使用父类中定义的方法和属性,

向下转型

当明确某个对象就是某个子类对象时可以强制转换为子类,如此就可以重新访问子类中独有的成员

当不能明确对象是否是某个类型时可以使用 instanceof

if(obj instanceof Objcet){
  System.out.println("obj is  ObjectClass instance");
}else{
  System.out.println("obj not  ObjectClass instance");
}
注意:由于静态方法不能被重写,当子类和父类存在完全相同的静态方法时,向上转型后对象默认调用的是父类中的静态方法,也就是说不存在多态,调用的方法是明确的,跟随类型的

abstract 抽象类

什么是抽象类:

抽象类指的是类中包含抽象方法的类,

抽象方法指的是没有任何方法体的方法

使用abstract关键字类表示抽象方法 或抽象类

什么时候使用抽象类:

当父类需要规定子类应该具备某些方法,但父类本身不清楚方法具体如何实现时使用抽象类,

用于提前告诉子类,你应该具备哪些方法

特点:

存在抽象方法的类无法直接实例化产生对象,必须由子类继承并实现所有抽象方法才能实例化子类

如子类没有实现所有抽象方法,那么子类也只能作为抽象类

实例:

abstract class Person{
		 abstract public void show();
}

class Student extends Person{
  @Override
  public void show(){
    System.out.println("show");
  }
}

注意:

1.如果设计子类的人清楚的知道自己应该做的事情时(方法),可以不用抽象类,抽象类本质就是限制子类必须怎么怎么滴

2.需要注意final 不能与 abstract 同时出现

interface 接口

接口是一组功能(方法)的定义

接口的作用:

定义一组协议方法,让子类遵循协议中的要求去实现具体的操作,对于使用者而言,只需要掌握接口中定义的方法的使用即可,无需关心,具体是哪一个类实现的,更不用关心是如何实现的, 这将类的设计者和类的使用者之间的耦合度大大的降低了,

定义语法:

interface Name {
  
}

特点:

接口本质是一个类,但是与普通类有着众多区别

1.接口中的方法默认都是抽象的不能有方法体

2.接口中定义的变量默认都是静态常量

3.接口中的成员必然是public修饰的,即时没有明确声明

4.接口无法直接实例化

5.接口可以继承一个或多个其他接口

6,一个类可以同时实现多个接口

JDK1.8之后

某些情况下,接口中声明了很多抽象方法,然而子类不需要实现全部,而是想仅实现部分需要的,这种情况在1.8之前是不允许的,除非把这个子类变成抽象的,这不够灵活;

  • 1.8之后,增加了新的方法修饰符,default,可以在接口中直接编写方法体,被default修饰的方法,子类可以选择性的重写
  • 新增特性,可以为方法加上static修饰符

新特性的测试

案例:

package com.yh.src;

interface HDMI {
    static int a = 0;
    // 上述代码等价于public static final int a = 0;

    //抽象方法
    void generalMethod();
    //上述代码等价于public void test();

    //默认方法 子类可选重写
    default void defaultMethod(){
        System.out.println("默认的方法体");
    }
    //静态方法 子类不可重写
    static void staticMethod(){
        System.out.println("静态方法体");
    }
}

class AA implements HDMI {
    @Override
    public void generalMethod() {
        System.out.println("AA Implements generalMethod!");
        HDMI.staticMethod();//静态方法只能有接口名称调用
    }

    @Override
    public void defaultMethod() {
        //super.test1();//无法通过super调用接口中的默认方法
        HDMI.super.defaultMethod();//需要使用接口名称.super来调用
    }
}

public class InterfaceRunner {
    public static void main(String[] args) {
        AA a = new AA();
        a.generalMethod();
        a.defaultMethod();//调用未被重写的默认方法
        //a.staticMethod();//无法直接调用接口中的静态方法
        HDMI.staticMethod();//静态方法只能由接口名称调用

    }
}

default方法总结:
  • default方法子类可以选择是否重写,调用顺序与普通继承相同,优先调用对象重写的方法,
  • 当需要在重写方法中调用接口中的默认实现时使用接口名称.super.方法名称HDMI.super.defaultMethod();
static方法总结:
  • static方法无法被重写,也不能使用super调用,无论在什么位置只能使用接口名称来调用

PS:其实取消抽象方法和接口一样可以实现设计-使用松耦合,直接使用普通类作为父类,子类自己实现该实现的方法,当然了java为什么占领企业开发也正是因为其严谨,标准规范,

你会发现,为了提高灵活性接口好像变得和抽象类非常相似,.......干脆像python一样别整这么多约束,,,

接口多实现

与普通类不同的是一个类可以同时实现多个接口,增加了子类的扩展性

但是也带来了访问不确定性,

  • 情况1:多个接口存在相同的方法(名称与参数列表相同)
interface IA{
    default void test(){
        System.out.println("IA test!");
    }
}

interface IB{
    default void test(){
        System.out.println("IB test!");
    }
}
//实现类直接编译失败,
class IMA implements IA,IB{

}
解决方案:
//在子类中重写冲突的方法,即可 当需要调用默认方法时,使用`接口名称.super.xx`来调用
class IMA implements IA,IB{

    @Override
    public void test() {
        IA.super.test();
        IB.super.test();
    }
}
  • 情况2:父类中存在相同的方法
  interface IA{
      default void test(){
          System.out.println("IA test!");
      }
  }
  
  interface IB{
      default void test(){
          System.out.println("IB test!");
      }
  }
  
  class P{
      public void test(){
          System.out.println("IB test!");
      }
  }
  
  class IMA extends P implements IA,IB{
  }

​ 这种情况下不会出现问题,将直接调用父类中的方法

  • 情况3:多个接口中出现了重复的常量
interface IA{
    int a =10;
    int b =100;
}

interface IB{
    int a =20;
}

class P{
    int a = 20000;
}

class IMA extends P implements IA,IB{
    public void  func(){
        System.out.println(a);//编译错误
        System.out.println(IA.a);//使用接口名称来明确
        System.out.println(super.a);//使super来指定访问父类
        System.out.println(b);//不存在重复的时直接可以访问
        
    }
}

此时子类无法明确选择一个要访问的常量,必须使用接口名称来明确

要访问父类时使用super来明确

补充:

子类对象可以直接访问接口中没有冲突的静态变量,但是无法直接访问静态方法需要使用接口名称调用

===================================

内部类

什么是内部类:

当一个类定义在一个类的范围中,则这个类称之为内部类,与之对应的是包含这个内部类的外部类

普通内部类(较少用)

实例:

class A{//外部类
  class B{ //内部类
    }
}

实例化内部类

public class InnerClass {
    public static void main(String[] args) {
        //实例化内部类方法1
        A.B obj; //定义
        obj = new A().new B();
        //实例化内部类方法2
        A aobj = new A();
        obj = aobj.getInner();
        //实例化内部类方法3
        obj = aobj.new B();
    }
}
class A{
    public B getInner() {
        return new B();
    }
    class B{
    }
}

内部类访问外部类的成员

public class InnerClass {
    public static void main(String[] args) {
        //实例化内部类方法1
        A.B obj; //定义
        obj = new A().new B();//实例化
        obj.test();//调用方法访问外部类的成员
    }
}
class A{
    int inta = 100;
    int intb = 200;
    public B getInner() {
        return new B();
    }
    class B{
        int intb = 300;
        public void test(){
            System.out.println(inta);//100 可以直接访问外部类成员
            System.out.println(intb);//300 冲突时优先访问内部
            System.out.println(A.this.intb);//200 冲突时指定访问外部
        }
    }
}

外部类访问内部类成员

class A{
    int inta = 100;
    int intb = 200;
    public void accessInner() {
        B b = new B();//实例化
        b.test();//访问
    }

    class B{
        int intb = 300;
        public void test(){
            System.out.println(inta);
            System.out.println(intb);
            System.out.println(A.this.intb);
        }
    }
}

外部类访问内部类成员时需要先实例化内部类的对象,通过对象访问即可

注意:普通内部类中不能包含任何static修饰的成员

静态内部类 (不常用)

可以将内部类使用static修饰,此时他就是静态内部类

特点:

  • 与普通内部类的不同之处,与static修饰的其他成员有着相同的特征:

  • 在类被加载时就一起加载,所以,可以直接实例化,不需要先实例化外部类;

  • 同样的在静态内部类中不可以直接访问非静态修饰的外部类成员,也不可以访问外部类的this;

  • 静态内部类中的可以包含静态成员并且也可以直接调用(外部类.内部类.属性);普通类中不可以有静态成员;

实例:

public class InnerClass2 {
    public static void main(String[] args) {
        System.out.println(OUT.IN.a);//直接访问静态内部类的静态成员
        OUT.IN.test();//访问静态内部类方法
    }
}

class OUT{
    static int a = 10;
    String name = "jack";
    static class IN{
        static int a = 100;
        static public void test(){
            System.out.println(a);//优先访问内部类
            System.out.println(OUT.a);//指定访问外部类
            System.out.println(new OUT().name);//访问外部非静态成员
        }
    }
}

局部内部类 (不常用)

定义在方法中的类称为局部内部类

意义不大..没啥用..略过了

class A{
	public void test(){
    class B{
      //局部内部类
		}
  }
}

特点:不允许使用static修饰局部内部类,和其中的任何成员,要使用该类必须作为返回值return出去

匿名内部类 (很常用)

什么是匿名内部类

字面意思就是没有名字的类

通过new 实例化接口或是抽象类,同时提供相应的方法实现

实例:

interface USB{
  void open();
}

class PC {
	public void working(){
		//此处需要一个实现了USB接口的对象来完成操作
    //我们可以定义一个类实现USB接口先下面的A类一样
    //然后实例化A对象来完成OPEN操作
    new A().open();
		//我们也可以使用匿名内部类的方式来简化操作 
    new USB(){
			  public void open(){
    		System.out.println("OPEN......");
	  	}
    }.open;
  }
}
class A implements USB{
  public void open(){
    System.out.println("OPEN......");
  }
}

在上面的例子中我们可以看出匿名内部类有以下优点:

  • 代码更加紧凑

  • 使用更便捷

当然匿名内部类的缺点也很明显:

  • 这个类产生的对象也是没有名字的,只能在定义的地方使用一次
  • 语法格式看起来比较乱

什么时候使用匿名内部类

1.临时需要某个接口或是抽象类的独享完成某个功能

2.当整个任务的部分代码已经完成,但是剩下一部分关键代码需要由使用这个功能的人来完成

案例:

PC类的working方法需要传入一个USB接口对象才能完成整个任务

interface USB{
    void open();
}
public class PC {
    public static void main(String[] args) {
        new PC().working(new AP());//以前的方式:定义类实现方法然后实例化
        new PC().working(new USB() {//匿名内部类的方式:在当前位置完成上述全部操作
            @Override
            public void open() {
                System.out.println("open on NonNameClass");
            }
        });
    }
    public void working(USB usb){
        usb.open();
    }
}
class AP implements USB{
    public void open(){
        System.out.println("OPEN......on AP");
    }
}

PS:个人觉得匿名内部类+匿名对象其实 与OC中的block,python中的函数对象要完成的事情是一样的,即方法的使用者实现部分代码,然后作为参数传给某个方法(本质就是回调机制),但是JAVA中定义方法必须借助类,所以才有了匿名内部类这么一说,归根结底是因为JAVA强制面向对象导致的;

补充:匿名内部类中可以使用构造代码块

代码设计规范----单一功能职责:

有且只有一个引起功能变化的原因
如果在一个类当中,承担的职责越多,耦合度越高,这意味着这个类的重用性越低
posted @ 2019-11-20 22:49  CoderJerry  阅读(...)  评论(...编辑  收藏