Java面向对象编程学习笔记(不完整)

一、面向对象的基本概念

面向对象是最早在七十年代的时候由IBM的Smalltalk语言最先推广的,后来C语言经过了面向对象设计的修改,形成了C++,后来又由C++产生了今天的Java。

面向对象是一种技术的开发模式,但是最早的时候所使用的模式是面向过程。

面向过程:指的是针对于某一个问题单独提出解决方案以及代码开发。只能完成一次任务。

面向对象:以一种组件化的形式进行代码的设计,这样开发出来的代码有一个最大的好处就是可以重用。

在面向对象的程序里面包含有如下几个特征:

  • 封装性:保护内部的定义结构安全性;
  • 继承性:在已有的程序结构上继续扩充新的功能;
  • 多态性:指的是在某一个概念范围内的满足。

而对于面向对象开发分为步骤:OOA(面向对象分析)、OOD(面向对象设计)、OOP(面向对象编程)。面向对象是一个组件化的设计思想。

二、类与对象

1、认识类与对象

类与对象是整个面向对象之中最为基础的组成单元,如果需要给出划分定义的话,类就是共性的集合,而对象是某一个性的产物。

所有的类实际上都是用来描述出的对象的结构,例如:每一个人的姓名、年龄、身高、长相、等等一系列的因素。每一个类的共性的集合对象,除了具备以上的特征(属性)之外,实际上还包括很多行为(功能),所有根据此类产生出的对象都具备相同的行为。对象所能够操作的行为都是由类来决定的,超过类定义范畴的操作是不能够使用的。

类实际上是对象操作的模板,但是类不能够直接使用,必须通过实例对象来使用。

2、类与对象的基本定义

如果要在程序之中定义类可以使用“class类名称{}”的语法结构完成

  • Field(属性、成员、变量),就是一堆变量的集合;
  • Method(方法、行为),此时的方法是由对象调用的。

范例:创建Book类

class Book{
    String title;
    double price;
    public void getInfo(){
        System.out.println("图书名称:"+title+",价格:"+price);
    }
}

此时已经定义了一个类,类里面包含了两个属性和一个方法。

类虽然已经产生了,但是如果要想使用这个类必须要有对象,那么对象的定义格式有如下两种:

  • 声明并实例化对象:类名称 对象名称 = new 类名称();
  • 分布完成:

|-声明对象:类名称 对象名称 = null;

|-实例化对象:对象名称 = new 类名称()。

引用数据类型与基本数据类型最大的不同在于需要内存的开辟及使用,所以new即开辟内存空间。

当一个对象实例化之后那么久可以按照如下的方式利用对象来操作类的结构:

  • 对象.属性:表示要操作类中的属性内容;
  • 对象.方法():表示要操作

范例:使用类——在主类中使用Book类

声明并实例化对象:

public class TestDemo{
    public static void main(String args[]){
        Book bk = new Book();    //声明并实例化对象bk;
        bk.title = "Java开发";
        bk.price = "89.9";
        bk.getInfo();
    }
}

分布完成:

public class TestDemo{
    public static void main(String args[]){
        Book bk = null;    //声明对象
        bk = new Book();    //实例化对象(开辟了堆内存);
        bk.title = "Java开发";
        bk.price = "89.9";
        bk.getInfo();
    }
}

假如不给bk设置属性内容,则结果将显示数据类型的默认值,即title为null,price为0.0。

如果想要对以上的程序进行内存分析,可以首先给出两块内存空间的概念:

  • 堆内存:保存每一个对象的属性内容,堆内容需要用关键字new才可以开辟。关注的是属性内容。
  • 栈内存:保存的是 一块堆内存的地址,但是我们为了分析方便,可以简单地理解为保存的是对象名称。

Java面向对象编程学习笔记(不完整)_第1张图片

对象除了可以利用一行语句定义之外,也可以分布完成。

任何情况下只要看见了关键字new,都表示要开辟新的堆内存空间,一旦堆内存空间开辟了,里面就一定会有类中定义的属性,当然所有的属性内容都是其对应属性的数据类型的默认值。

Java面向对象编程学习笔记(不完整)_第2张图片

以上的代码是分为两步的方式实现的了对象的实例化(开辟了堆内存的对象称为实例化对象),那么如果说现在使用了没有实例化的对象。

public class TestDemo{
    public static void main(String args[]){
        Book bk = null;    //声明对象
        //bk = new Book();    //实例化对象(开辟了堆内存);
        bk.title = "Java开发";
        bk.price = "89.9";
        bk.getInfo();
    }
}

此时由于使用了没有实例化的Book类对象,所以程序在运行的时候出现了空指针异常的错误,此类异常只要是引用了数据类型都有可能出现。

3、引用数据的初步分析

引用是整个Java开发之中的核心精髓所在,只有掌握了引用这一基础概念之后才可以深入学习。

范例:声明两个对象

public class TestDemo{
    public static void main(String args[]){
        Book bookA = new Book();  
        Book bookB = new Book();    
        bookA .title = "Java开发";
        bookA .price = "89.9";
        bookB .title = "Jsp开发";
        bookB .price = "69.9";
        bookA .getInfo();
        bookB .getInfo();
    }
}

Java面向对象编程学习笔记(不完整)_第3张图片

这个时候给出的代码,是声明并实例化对象,用一行代码完成的。

范例:对象引用传递

public class TestDemo{
    public static void main(String args[]){
        Book bookA = new Book();  
        Book bookB = null;    
        bookA .title = "Java开发";
        bookA .price = "89.9";
        bookB = bookA;    //引用传递
        bookB .price = "69.9";
        bookA .getInfo(); 
    }
}

严格来讲bookA和bookB里面保存的是对象的地址信息,所以以上引用的过程就属于将bookA的地址赋给了bookB;

Java面向对象编程学习笔记(不完整)_第4张图片

由于此时两个对象指向的同一块堆内存空间,所以任何一个对象修改了堆内存中的数据后,那么都会影响到其他对象。在引用的操作过程中,一块堆内存可以同时被多个栈内存所指向,但是反过来,一块栈内存只能保存一块堆内存的空间地址。

范例:继续观察引用传递

public class TestDemo{
    public static void main(String args[]){
        Book bookA = new Book();  
        Book bookB = new Book();    
        bookA .title = "Java开发";
        bookA .price = "89.9";
        bookB .title = "Jsp开发";
        bookB .price = "69.9";
        bookB = bookA;    //引用传递
        bookB .price = "69.9";
        bookA .getInfo(); 
    }
}

this关键字

class Book{
    private String title;
    private double price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public Book() {
        System.out.println("一个新的Book类对象生成");
    }

    public Book(String title) {
        this(); //调用本类的无参构造
        this.title = title;
    }

    public Book(String title, double price) {
        this(title); //调用本类的单参构造
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", price=" + price +
                '}';
    }
}

public class TestDemo{
    public static void main(String args[]){
        Book bk=new Book("java开发",89.9);
        System.out.println(bk.toString());
    }
}

限制:

使用This()调用构造方法只能放在首行。

构造可以调用普通,但是普通不能调用构造。

进行构造方法互相调用的时候,一定要保留出口,至少保留一个构造没有使用this调用其他构造。

class Emp{
    private int empno;
    private String ename;
    private double sal;
    private String dept;

    public Emp() {
        this.empno = 0;
        this.ename = "无名氏";
        this.sal = 0.0;
        this.dept = "未定";
    }

    public Emp(int empno){
        this.empno = empno;
        this.ename = "临时工";
        this.sal = 800.0;
        this.dept = "后勤部";
    }

    public Emp(int empno,String ename){
        this.empno = empno;
        this.ename = ename;
        this.sal = 2000.0;
        this.dept = "技术部";
    }

    public Emp(int empno, String ename, double sal,String dept) {
        this.empno = empno;
        this.ename = ename;
        this.sal = sal;
        this.dept = dept;
    }

    public int getEmpno() {
        return empno;
    }

    public void setEmpno(int empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public String getDept() {
        return dept;
    }

    public void setDept(String dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", sal=" + sal +
                ", dept='" + dept + '\'' +
                '}';
    }
}
public class shizhan {
    public static void main(String arg[]){
        Emp e1=new Emp();
        Emp e2=new Emp(7369);
        Emp e3=new Emp(5659,"allen");
        Emp e4=new Emp(2655,"king",5000,"财务部门");
        System.out.println(e1.toString());
        System.out.println(e2.toString());
        System.out.println(e3.toString());
        System.out.println(e4.toString());
    }
}

Java面向对象编程学习笔记(不完整)_第5张图片

    public Emp() {
        this(0,"无名氏",0.0,"未定");
    }

    public Emp(int empno){
        this(empno,"临时工",800.0,"后勤部");
    }

    public Emp(int empno,String ename){
        this(empno,ename,2000.0,"技术部");
    }

    public Emp(int empno, String ename, double sal,String dept) {
        this.empno = empno;
        this.ename = ename;
        this.sal = sal;
        this.dept = dept;
    }

构造方法间的相互调用解决了代码重复问题。

当前对象

当前正在调用类中方法的对象

class Book{
    public void print(){
        //哪个对象调用了print方法,this就自动与此对象指向同一个地址
        //this就是当前调用方法的对象
        System.out.println("this="+this);
    }
}

public class TestDemo{
    public static void main(String args[]){
        Book booka=new Book();
        System.out.println("booka:"+booka);
        booka.print();
        Book bookb=new Book();
        System.out.println("bookb:"+bookb);
        bookb.print();
    }
}

Java面向对象编程学习笔记(不完整)_第6张图片

那么“this.属性”实际上就属于当前对象中的属性,一定是堆内存保存的内容。

class A{
    private B b;
    public A(){ //2、执行A类构造
        this.b = new B(this); //3、为B类对象b实例化,当前对象就是temp,这边的this就是temp
        this.b.get();   //6、调用B类的get方法
    }
    public void print(){    // 9、调用此方法
        System.out.println("Hello World!"); //10、输出
    }
}
class B{
    private A a;
    public B(A a){  //4、执行B类构造函数,传入的参数就是temp,相当于把temp赋给了a
        this.a=a;   //5、保存a对象(保存temp)
    }
    public void get(){  //7、调用此方法
        this.a.print(); //8、this.a=temp,调用A类的print方法
    }
}

public class TestDemo{
    public static void main(String args[]){
        A temp=new A(); //1、实例化A类对象temp(因为代码是从右到左进行的,这句话还没赋值,实际上还不是temp,为了理解,就当这边已经声明好了对象temp)
    }
}

引用传递

同一块堆内存可以被不同的栈内存所指向,不同的栈内存可以对同一堆内存进行内容修改。

范例:第一题

class Message{
    private int num=10;
    public Message(int num){
        this.num=num;
    }
    public void setNum(int num){
        this.num=num;
    }
    public int getNum(){
        return this.num;
    }
}
public class TestDemo{
    public static void main(String args[]){
        Message msg=new Message(30);
        fun(msg);
        System.out.println(msg.getNum());
    }
    public static void fun(Message temp){
        temp.setNum(100);
    }
}

输出:100

int型是基本型,基本型跟引用型的最大差别没有内存的匹配

Java面向对象编程学习笔记(不完整)_第7张图片

范例:第二题

public class TestDemo{
    public static void main(String args[]){
        String msg="Hello";
        fun(msg);
        System.out.println(msg);
    }
    public static void fun(String temp){
        temp="world";
    }
}

输出:Hello

String类对象的内容一旦声明则不可改变,对象改变是通过引用对象的改变。

Java面向对象编程学习笔记(不完整)_第8张图片

范例:第三题

class Message{
    private String info="nihao";
    public Message(String info){
        this.info=info;
    }

    public void setInfo(String info) {
        this.info=info;
    }

    public String getInfo() {
        return this.info;
    }
}
public class TestDemo{
    public static void main(String args[]){
        Message msg=new Message("Hello");
        fun(msg);
        System.out.println(msg.getInfo());
    }
    public static void fun(Message temp){
        temp.setInfo("world");
    }
}

输出:world

  Java面向对象编程学习笔记(不完整)_第9张图片

以上的内容描述不严格,下图才是严格的画法

Java面向对象编程学习笔记(不完整)_第10张图片

虽然String属于类,属于引用类型,但是由于其内容不可改变的特点,很多时候就直接把String当作基本数据类型,每一个String类型只能保存一个数据,改变了原来的就没了。

引用传递实际应用

每个人都有一辆汽车或没有汽车

CREATE TABLE member(
    mid NUMBER,
    name VARCHAR(50),
    CONSTRAINT pk_mid PRIMARY KEY(mid)
);
CREATE TABLE car(
    mid NUMBER,
    pname VARCHAR(50),
    CONSTRAINT fk_mid FOREIGN KEY(mid) PEFERENCES member(mid),
    CONSTRAINT pk_mid PRIMARY KEY(mid)
);

简单Java类的编写原则:

  • 类名称=表名称
  • 属性名称(类型)=表字段(类型)
  • 一个实例化对象=一行记录
  • 多个实例化对象(对象数组)=多行记录(外键)
  • 引用关系=外键约束

两个独立的类,表中可以通过外键描述关系,那么在类中可以采用引用来描述关系

class Member{
    private int mid;
    private String name;
    //car有实例化对象表示有车,为null则表示没有车
    private Car car;    //表示属于人的车
}
class Car{
    private Member member;
    private String pname;
}

Member和Car都是独立的对象,通过引用发生关系。

class Member{
    private int mid;
    private String name;
    //car有实例化对象表示有车,为null则表示没有车
    private Car car;    //表示属于人的车
    public Member(int mid,String name){
        this.mid=mid;
        this.name=name;
    }
    public String getInfo(){
        return "人员编号:"+this.mid+"人员姓名:"+this.name;
    }
}

class Car{
    private Member member;
    private String pname;
    public Car(String pname){
        this.pname=pname;
    }
    public String getInfo(){
        return "车的名字:"+this.pname;
    }
}

当两个对象产生之后,就应该为这两个对象设置彼此的关系

class Member{
    private int mid;
    private String name;
    //car有实例化对象表示有车,为null则表示没有车
    private Car car;    //表示属于人的车
    public Member(int mid,String name){
        this.mid=mid;
        this.name=name;
    }
    public String getInfo(){
        return "人员编号:"+this.mid+"人员姓名:"+this.name;
    }
    public void setCar(Car car){
        this.car=car;
    }
    public Car getCar(){
        return this.car;
    }
}

class Car{
    private Member member;
    private String pname;
    public Car(String pname){
        this.pname=pname;
    }
    public String getInfo(){
        return "车的名字:"+this.pname;
    }
    public void setMember(Member member){
        this.member=member;
    }
    public Member getMember(){
        return this.member;
    }
}

测试:

第一步:根据定义的结构关系设置数据

第二部:根据定义的结构关系取出数据

public class TestDemo{
    public static void main(String args[]){
        //第一步:根据定义的结构关系设置数据
        Member m=new Member(1,"张三");
        Car c=new Car("宝马");
        m.setCar(c);
        c.setMember(m);
        //第二部:根据定义的结构关系取出数据
        System.out.println(m.getCar().getInfo());
        System.out.println(c.getMember().getInfo());
    }
}

进一步设计:每个人都有自己的孩子,孩子还可能有车。

方法一:设计一个孩子类,如果有孙子,再一个孙子类...

方法二:一个人的孩子一定还是一个人,在Member类中设置一个属性

class Member{
    private int mid;
    private String name;
    private Member child;
    //car有实例化对象表示有车,为null则表示没有车
    private Car car;    //表示属于人的车
    public Member(int mid,String name){
        this.mid=mid;
        this.name=name;
    }
    public String getInfo(){
        return "人员编号:"+this.mid+"人员姓名:"+this.name;
    }
    public void setChild(Member child){
        this.child=child;
    }
    public Member getChild(){
        return this.child;
    }
    public void setCar(Car car){
        this.car=car;
    }
    public Car getCar(){
        return this.car;
    }
}

class Car{
    private Member member;
    private String pname;
    public Car(String pname){
        this.pname=pname;
    }
    public String getInfo(){
        return "车的名字:"+this.pname;
    }
    public void setMember(Member member){
        this.member=member;
    }
    public Member getMember(){
        return this.member;
    }
}
public class TestDemo{
    public static void main(String args[]){
        //第一步:根据定义的结构关系设置数据
        Member m=new Member(1,"张三");
        Member chd=new Member(2,"小小张");
        Car c=new Car("宝马");
        Car cc=new Car("奔驰");
        m.setCar(c);
        chd.setCar(cc);
        c.setMember(m);
        cc.setMember(chd);
        m.setChild(chd);
        //第二部:根据定义的结构关系取出数据
        System.out.println(m.getCar().getInfo());
        System.out.println(c.getMember().getInfo());
        System.out.println("孩子是:"+m.getChild().getInfo()+m.getChild().getCar().getInfo());
    }
}

现实中有很多这样的操作:一个人有一张身份证、驾照...

进一步描述,例如:要求描述电脑由主机、内存、cpu、显卡、硬盘、鼠标、键盘、显示器...组成

Java面向对象编程学习笔记(不完整)_第11张图片

这样的设计思路在Java之中称为合成设计模式。

引用是实现两个不同类型之间互相关联的主要手段。

数据表与Java类映射

要求通过Java程序描述出dept和emp关系,使用字段:

  • dept:deptno、dname、loc
  • emp:empno、ename、job、sal、comm、deptno、mgr

一个部门有多个雇员

一个雇员有一个或零个领导

第一步:

class Dept{
    private int deptno;
    private String dname;
    private String loc;
    //setter、getter、无参构造略
    public Dept(int deptno,String dname,String loc){
        this.deptno=deptno;
        this.dname=dname;
        this.loc=loc;
    }
    public String getInfo(){
        return "部门编号:"+this.deptno+"名称:"+this.dname+"位置:"+this.loc;
    }
}
class Emp{
    private int empno;
    private String ename;
    private String job;
    private double sal;
    private double comm;
    //setter、getter、无参构造略
    public Emp(int empno, String ename, String job, double sal, double comm) {
        this.empno = empno;
        this.ename = ename;
        this.job = job;
        this.sal = sal;
        this.comm = comm;
    }

    public String getInfo(){
        return "雇员编号:"+this.empno+"姓名:"+this.ename+"工作:"+this.job+"工资:"+this.sal+"奖金:"+this.comm;
    }
}
public class TestDemo{
    public static void main(String args[]){
    Dept d=new Dept()
    }
}

第二步:解决外键关系

一个雇员属于一个部门

    private Dept dept;    

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    public Dept getDept() {
        return this.dept;
    }

一个部门有多个雇员

    private Emp emps [];
    public void setEmps(Emp [] emps){
        this.emps=emps;
    }
    public Emp [] getEmps(){
        return this.emps;
    }

一个雇员有一个或零个领导

    private Emp mgr;
    public void setMgr(Emp mgr){
        this.mgr=mgr;
    }
    public Emp getMgr(){
        return this.mgr;
    }

所有匹配的映射关系已经描述出来了

第三步:设置并取得数据

  • 设置数据
    Dept dept=new Dept(10,"ACCOUNTING","New York");
    Emp ea=new Emp(7839,"KING","PRESIDENT",8000.0,0.0);
    Emp eb=new Emp(7369,"SMITH","CLERK",800.0,0.0);
    Emp ec=new Emp(7902,"FORD","MANAGER",2450.0,0.0);
    eb.setMgr(ec);
    ec.setMgr(ea);
    ea.setDept(dept);
    eb.setDept(dept);
    ec.setDept(dept);
    dept.setEmps(new Emp[]{ea,eb,ec});
  • 取得数据
for (int i=0;i

总的代码

class Dept{
    private int deptno;
    private String dname;
    private String loc;
    private Emp emps [];
    public void setEmps(Emp [] emps){
        this.emps=emps;
    }
    public Emp [] getEmps(){
        return this.emps;
    }
    //setter、getter、无参构造略
    public Dept(int deptno,String dname,String loc){
        this.deptno=deptno;
        this.dname=dname;
        this.loc=loc;
    }
    public String getInfo(){
        return "部门编号:"+this.deptno+"名称:"+this.dname+"位置:"+this.loc;
    }
}
class Emp{
    private int empno;
    private String ename;
    private String job;
    private double sal;
    private double comm;
    private Dept dept;
    private Emp mgr;
    public void setMgr(Emp mgr){
        this.mgr=mgr;
    }
    public Emp getMgr(){
        return this.mgr;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    public Dept getDept() {
        return this.dept;
    }

    //setter、getter、无参构造略
    public Emp(int empno, String ename, String job, double sal, double comm) {
        this.empno = empno;
        this.ename = ename;
        this.job = job;
        this.sal = sal;
        this.comm = comm;
    }

    public String getInfo(){
        return "雇员编号:"+this.empno+"姓名:"+this.ename+"工作:"+this.job+"工资:"+this.sal+"奖金:"+this.comm;
    }
}
public class TestDemo{
    public static void main(String args[]){
    Dept dept=new Dept(10,"ACCOUNTING","New York");
    Emp ea=new Emp(7839,"KING","PRESIDENT",8000.0,0.0);
    Emp eb=new Emp(7369,"SMITH","CLERK",800.0,0.0);
    Emp ec=new Emp(7902,"FORD","MANAGER",2450.0,0.0);
    eb.setMgr(ec);
    ec.setMgr(ea);
    ea.setDept(dept);
    eb.setDept(dept);
    ec.setDept(dept);
    dept.setEmps(new Emp[]{ea,eb,ec});
    System.out.println("领导信息:"+eb.getMgr().getInfo());
    System.out.println("部门信息:"+eb.getDept().getInfo());
    System.out.println("-------------------------");
    System.out.println(dept.getInfo());
    for (int i=0;i

数据表转换

Java面向对象编程学习笔记(不完整)_第12张图片

class Province{
    private int pid;
    private String pname;
    private City citys [];

    public Province(int pid,String pname){
        this.pid=pid;
        this.pname=pname;
    }

    public void setCitys(City [] citys){
        this.citys=citys;
    }

    public City [] getCitys(){
        return this.citys;
    }

    public String getInfo(){
        return "省份编号:"+this.pid+"名称:"+this.pname;
    }
}
class City{
    private int cid;
    private String cname;
    private Province province;

    public City(int cid,String cname){
        this.cid=cid;
        this.cname=cname;
    }

    public void setPid(Province province){
        this.province=province;
    }

    public Province getProvince(){
        return this.province;
    }

    public String getInfo(){
        return "城市编号:"+this.cid+"名称:"+this.cname;
    }

}
public class TestDemo{
    public static void main(String args[]) {
        Province p1=new Province(1001,"浙江省");
        City c1=new City(101,"宁波市");
        City c2=new City(102,"江苏市");
        City c3=new City(103,"绍兴市");
        c1.setPid(p1);
        c2.setPid(p1);
        c3.setPid(p1);
        p1.setCitys(new City[]{c1,c2,c3});
        System.out.println(p1.getInfo()+"有部分城市如下:");
        for (int x=0;x

Java面向对象编程学习笔记(不完整)_第13张图片

以上的确是实现了一个基础的一对多的关系,问题来了:

  • 如果说现在又增加了几个城市,那么一定要修改数组的引用关系
  • 如果删除了两个城市信息,又增加了两个城市信息
  • 查询某个城市是否存在,遍历数组
  • 要求替换某一个城市的数组的保存索引号
  • 替换掉某一个城市的内容

造成所有困难的关键就在于数组的使用上,但是又不可以不用数组

一对多

Java面向对象编程学习笔记(不完整)_第14张图片

对象比较

判断两个数字是否相等“==”;

字符串比较“equals”;

自定义的类,要想判断两个对象是否相等,对象=数据集合,那么比较一定要依次比较各个属性。

class Book{
    private String  title;
    private double price;

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    public String getTitle() {
        return title;
    }

    public double getPrice() {
        return price;
    }
}
public class TestDemo{
    public static void main(String args[]) {
        Book b1=new Book("Java开发",79.8);
        Book b2=new Book("Java开发",79.8);
        if(b1.getTitle().equals(b2.getTitle())&&b1.getPrice()==(b2.getPrice())){
            System.out.println("是同一个对象");
        }else{
            System.out.println("不是同一个对象");
        }
    }
}

主方法就是一个客户端,客户端的程序逻辑越简单越好,最好隐藏所有的细节逻辑。

如果要想进行信息比较,那么所有的具体的比较细节必须由每个对象自己完成,对象所在的类一定 要提供有对象比较的方法。

假如说这个方法名称暂定为compare()

额外讲解:

如果说现在一个类中的属性使用了private封装了,那么在类的外部不能通过

但是如果说将一个对象传递回到类的方法里面,就相当于取消了封装的形式,可以直接通过对象访问属性。

ps:本类接受本类对象:程序是先编译后执行的,编译以后一定会有一个Info方法先生成,所以可以在Info类中调用Info 方法。

x.fun(x):x是一个Info对象,fun()是一个Info类的方法,那么x可以调用fun()方法,fun()方法又接收一个Info类的对象。

Java面向对象编程学习笔记(不完整)_第15张图片

此类形式的代码会在对象比较中出现,但是也是一种常见形态,一个类可以接收本类对象。

 范例:对象比较实现

equals方法具备了空的判断

class Book{
    private String  title;
    private double price;

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }
    //对象可以直接访问属性,不需要使用getter方法
    //两个功能:带回了需要比较的信息,方便访问
    public boolean compare(Book book){
        //执行“b1.compare(b2)”的时候会有两个对象
        //当前对象this(调用此方法的对象b1,b1引用)
        //传递的对象book(引用传递,b2引用)
        if(book==null){
            return false;
        }
        if(this==book){
            return true;
        }
        if(this.title.equals(book.title)
            &&this.price==book.price){
            return true;
        }else{
            return false;
        }
    }

    public String getTitle() {
        return title;
    }

    public double getPrice() {
        return price;
    }
}
public class TestDemo{
    public static void main(String args[]) {
        Book b1=new Book("Java开发",79.8);
        Book b2=new Book("Java开发",79.8);
        if(b1.compare(null)){
            System.out.println("是同一个对象");
        }else{
            System.out.println("不是同一个对象");
        }
    }
}

总结:

  • 对象比较一定是某一个类自己定义的功能
  • 对象比较时一定要判断是否为null、地址、属性是否相同

static关键字

static在使用上可以声明属性、方法

class Book{
    private String  title;
    private double price;
    //为了方便使用,暂不封装
    String pub="清华大学出版社";

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    public String getInfo(){
        return "图书名称:"+this.title+"价格:"+this.price+"出版社:"+this.pub;
    }
}
public class TestDemo{
    public static void main(String args[]) {
        Book b1=new Book("Java开发",79.8);
        Book b2=new Book("ios开发",78.8);
        Book b3=new Book("Android开发",75.8);
        b1.pub="北京大学出版社";
        System.out.println(b1.getInfo());
        System.out.println(b2.getInfo());
        System.out.println(b3.getInfo());
    }
}

疑问:为什么值改变了修改对象的出版社呢?

Java面向对象编程学习笔记(不完整)_第16张图片

通过内存关系可以发现问题:既然这个类中所有图书的出版社都是同一个,那么还有必要每个对象都各自站有重复的属性信息吗?

如果说现在已经产生了1000W个Book类对象,要修改每个对象的出版社信息,所以发现如果将属性定有为普通属性,最终就是i每一块堆内存都要保存该属性。既然每个对象的pub内容都是一样的,应该共享同一个属性。

可以用static来定义属性:static String pub="清华大学出版社";

只要有一个对象修改了属性的内容,那么所有的对象的pub属性都会改变。

Java面向对象编程学习笔记(不完整)_第17张图片

static是一个公共属性的概念,如果通过一个对象去修改属性是不行的,必须有一个所有对象的公共代表来进行访问,就是类,所以static定义的属性是可以用类名称来直接调用的:“Book.pub="北京大学出版社";”

static属性与非static属性最大的区别:static属性不受实例化对象的控制,在没有产生实例化对象产生的时候依然可以使用。

public class TestDemo{
    public static void main(String args[]) {
        System.out.println(Book.pub);
        Book.pub="北京大学出版社";
        System.out.println(Book.pub);
        Book b1=new Book("Java开发",10.2);
        System.out.println(b1.getInfo());
    }
}

输出:

清华大学出版社
       北京大学出版社
       图书名称:Java开发价格:10.2出版社:北京大学出版社

虽然定义在类结构里面,但是并不受对象的控制,是独立于类存在的。

什么时候使用static?

在编写类的时候,选择首要的修饰符一定不是static。只有想要描述共享的时候,才使用static(方便集体修改,可以不重复开辟内存空间)。

static定义方法

static定义类和方法,都不受对象的控制,是独立于类存在的。此时会出现一个问题:类中的方法就变成了两组:static方法、非static方法,两组方法间的访问会受到限制:

static方法不能够直接访问非static属性或方法,非static方法能够直接访问static属性或方法。

分析:为什么会存在该问题?

所有的非static定义的结构,必须在类已经产生实例化对象才会分配堆内存空间,才可以使用。而所有的static定义的结构不需要实例化对象就可以使用,所以在static对象调用非static方法的时候可能对象还没实例化,当然不可以使用。

解决:

如果一个方法定义在了主类之中,并且由主方法直接进行调用的话,语法是:

public static 返回值类型 方法名称(参数类型 参数,...){

     [return [返回值];]

}

而后来编写类的时候,方法的定义格式改变(方法由对象调用了)

public 返回值类型 方法名称(参数类型 参数,...){

     [return [返回值];]

}

为什么呢?

public class TestDemo{
    public static void main(String args[]) {
        fun();
    }
    public static void fun() {
        System.out.println("Hello world!");
    }
}

如果去掉static,那么主函数想要调用fun方法必须要在对象实例化以后调用

public static void main(String args[]) {
        new TestDemo().fun();
    }

定义类的时候首先考虑的是非static方法,因为所有的类如果保存的属性多,那么每一对象执行同一个方法的时,就可以利用自己的属性实现方法的不同调用。

class MyMath{
    public static int add(int x,int y){
        return x+y;
    }
}
public class TestDemo{
    public static void main(String args[]) {
        System.out.println(MyMath.add(10,20));
    }
}

类保存的是属性,如果一个类连属性都没有,就不需要产生对象,有了static方法,不需要对象就可以用。

static定义方法

主方法的组成:

  • public :主方法是程序的开始,所以这个方法对任何的操作都一定是可见的,用public 描述一个公共的概念;
  • static :证明此方法是由类名称调用的;

我们在执行一个类的时候使用的是“java TestDemo”,如果他不是一个静态方法,“java TestDemo”是不会有类对象生成的。

  • void :主方法是一切执行的开始点,不能够回头,执行完毕才可以;
  • main:是一个系统规定好的方法名称,不能修改;
  • String arg[]:指的是程序运行的时候传递的参数。

Java面向对象编程学习笔记(不完整)_第18张图片

static实际应用

范例:实现实例化对象个数的统计

新的实例化对象产生的前提是调用构造函数,那么在构造函数里面增加一个计数的就可以了

class Book {
    private String  title;
    private double price;
    static int i=0;

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
        i++;
    }
}
public class TestDemo{
    public static void main(String args[]) {
        Book b1=new Book("Java开发",79.8);
        Book b2=new Book("ios开发",78.8);
        Book b3=new Book("Android开发",75.8);
        System.out.println(Book.i);
    }
}

输出:3

范例:实现属性的自动设置

某一个类,有一个无参构造和一个有参构造,不管调用有参还是无参的都可以给title设置内容。

class Book {
    private String title;
    private static int num=0;
    public Book() {
        this("NOTITLE - "+ num ++);
    }

    public Book(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
public class TestDemo{
    public static void main(String args[]) {
        System.out.println(new Book("Java开发").getTitle());
        System.out.println(new Book().getTitle());
        System.out.println(new Book().getTitle());

    }
}

输出:

Java开发

NOTITLE - 0

NOTITLE - 1

此时就算是没有给title设置属性,也会有默认名称,这就是static公共属性。

内存区:栈内存(地址)、堆内存(普通属性)、全局数据区(stati数据)、全局代码区(所有的方法)

代码块

Java面向对象编程学习笔记(不完整)_第19张图片

程序是由{}来定义范围的,全局变量和局部变量是相对的。

普通代码块

Java面向对象编程学习笔记(不完整)_第20张图片

作用:防止再方法里面编写代码过多时,有可能产生的变量重名。

构造快:将代码块写在一个类里面,那么就称为构造块

Java面向对象编程学习笔记(不完整)_第21张图片

Java面向对象编程学习笔记(不完整)_第22张图片虽然执行的是构造方法,构造块优先于构造方法

如果说产生多个对象Java面向对象编程学习笔记(不完整)_第23张图片

静态块

1、在非主类中定义:

Java面向对象编程学习笔记(不完整)_第24张图片

静态块将优先于构造块执行,且只执行一次,为了类中的static初始化,初始化的复杂操作

Java面向对象编程学习笔记(不完整)_第25张图片

2、在主类中定义:

静态块将优先于主方法执行。

代码块能不用就不用,唯一好用的只有静态块

内部类

内部类能少用就少用。优先考虑的还是普通类。

内部类指的就是在一个类的内部继续定义了其他内部结构类的情况。

范例:观察内部类的基本形式

class Outer {
    private String msg = "Hello world!";
    class Inner{
        public void print(){
            System.out.println(msg);
        }
    }
    public void fun(){
        //实例化内部类对象,并且调用print()方法
        new Inner().print();
    }
}
public class TestDemo{
    public static void main(String args[]) {
        Outer outer=new Outer();    //实例化外部类对象
        outer.fun();    //调用外部类方法
    }
}

输出:Hello world!

结构上不是很合理,从类的结构本身就意味着,属性与方法的组成,可是一个类里面又定义了另一个类。

把内部类拿出来:

class Outer {
    private String msg = "Hello world!";

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public void fun(){
        new Inner(this).print();
    }
}
class Inner{
    private Outer outer;
    public Inner(Outer outer){
        this.outer=outer;
    }
    public void print(){
        System.out.println(this.outer.getMsg());
    }
}
public class TestDemo{
    public static void main(String args[]) {
        Outer outer=new Outer();   
        outer.fun();    
    }
}

内部类有一个最大的优点:可以方便的访问外部类的私有操作。同理,外部类也可以访问内部类的私有属性。

class Outer {
    private String msg = "Hello world!";
    class Inner{
        private String info="世界,你好!";
        public void print(){
            System.out.println(msg);
        }
    }
    public void fun(){
        Inner in=new Inner();
        System.out.println(in.info);
    }
}
public class TestDemo{
    public static void main(String args[]) {
        Outer outer=new Outer();    //实例化外部类对象
        outer.fun();    //调用外部类方法
    }
}

一旦使用内部类以后,私有属性的访问就变得很简单了。

如果要想访问属性前面一定要加上this,但是如果直接在print()方法里面加上的话表示的是查找本类属性,但是此时我们要访问的是外部类的属性,应该使用“外部类.this.属性”。

class Outer {
    private String msg = "Hello world!";
    class Inner{
        public void print(){
            //外部类.this=外部类的当前对象
            System.out.println(Outer.this.msg);
        }
    }
    public void fun(){
        new Inner().print();
    }
}
public class TestDemo{
    public static void main(String args[]) {
        Outer outer=new Outer();    //实例化外部类对象
        outer.fun();    //调用外部类方法
    }
}

以上代码的特点:通过外部类的一个fun()方法访问了内部类的操作。内部类能不能像普通类一样直接在外部直接产生实例化对象调用呢?

如果要想解决此类问题,那么必须通过内部类的文件形式来观察。发现内部类的class文件形式是:Outer$Inner.class。所有的“$”是在文件中的命名,如果在程序中就变为了“.”,内部类的名称就是“外部类.内部类”,内部类可以访问外部类的属性,前提条件是外部类必须实例化,所以内部类对象的实例化语法:

外部类.内部类 对象 =new 外部类().new 内部类();

内部类不可能离开外部类的实例化对象,所以必须实例化外部类对象才可以。真正使用到内部类的时候,也基本不会像以上的操作那样进行的,一定是通过外部类访问到内部类。

如果现在一个内部类只希望被一个外部类访问,不能被外部调用“private class Inner{}”。

使用static定义内部类

如果一个内部类使用了static定义的话,那么这个内部类就变成了一个外部类

class Outer {
    private static String msg = "Hello world!";
    static class Inner{
        public void print(){
            System.out.println(msg);
        }
    }
}

但是此时如果要想取得内部类的实例化对象,使用的语法如下:

外部类.内部类 对象 =new 外部类.内部类();

此时不再需要先产生外部类对象,变成了一个独立的对象(相当于一个外部类)

class Outer {
    private static String msg = "Hello world!";
    static class Inner{
        public void print(){
            System.out.println(msg);
        }
    }
}
public class TestDemo{
    public static void main(String args[]) {
        Outer.Inner in=new Outer.Inner();
        in.print();
    }
}

以后看见了“类.类.类....”的时候一定要知道,这是一个使用了static的内部类。

定义内部类

1、方法中定义

class Outer {
    private  String msg = "Hello world!";
    public void fun(){
        class Inner{
            public void print(){
                System.out.println(Outer.this.msg);
            }
        }
        new Inner().print();
    }
}
public class TestDemo{
    public static void main(String args[]) {
        new Outer().fun();
    }
}

方法里面会接受参数,会定义变量。

范例:访问方法中定义的参数或者是变量

class Outer {
    private  String msg = "Hello world!";
    public void fun(int num){   //方法参数
        double score = 99.9;    //方法变量
        class Inner{
            public void print(){
                System.out.println("属性:" + Outer.this.msg);
                System.out.println("方法参数:" + num);
                System.out.println("方法变量:" + score);
            }
        }
        new Inner().print();
    }
}
public class TestDemo{
    public static void main(String args[]) {
        new Outer().fun(100);
    }
}

输出:

属性:Hello world!

方法参数:100

方法变量:99.9

继承性

引入概念

写一个简单Person类

class Person{
    private String name;
    private int age;
    public void setname(String name){
        this.name=name;
    }
    public void setAge(int age){
        this.age=age;
    }
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }
}

写一个简单Student类

class Student{
    private String name;
    private int age;
    private String school;
    public void setname(String name){
        this.name=name;
    }
    public void setAge(int age){
        this.age=age;
    }
    public void setSchool(String school){
        this.school=school;
    }
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }
    public String getSchool() {
        return this.school;
    }
}

以上代码出现了重复的代码,在自然的关系上,Student是属于Person的类型,并且学生范围更小,所以在这种情况下就可以用继承来表述学生。

继承的实现

class 子类 extends 父类 {}

子类也被称为派生类,父类也被称为基类(超类、super())

class Person{
    private String name;
    private int age;
    public void setname(String name){
        this.name=name;
    }
    public void setAge(int age){
        this.age=age;
    }
    public String getName(){
        return this.name;
    }
    public int getAge(){
        return this.age;
    }
}
class Student extends Person{
    private String school;
    public void setSchool(String school){
        this.school=school;
    }
    public String getSchool() {
        return this.school;
    }
}

public class TestDemo {
    public static void main(String args[]){
        Student student=new Student();
        student.setAge(21);
        student.setname("zhangsan");
        student.setSchool("北大");
        System.out.println("姓名:"+student.getName()+"年龄:"+student.getAge()+"学校:"+student.getSchool());
    }
}

子类可以直接将父类的操作继续使用,属于代码重用;

子类可以继续扩充属于自己的标准。

继承限制

1、Java不允许多重继承(一个子类可以继承多个父类)

如果想要实现这样的效果只能使用多层继承,最多不能超过三层继承关系。

class A{

}
class B extends A{

}
class C extends B{

}

2、子类在继承父类的时候,严格来讲会继承父类的所有操作,但是对于所有的私有操作属于隐式继承,非私有的属于显式继承。

class A{
    private String msg;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
class B extends A{

}
public class TestDemo {
    public static void main(String args[]){
        B b=new B();
        b.setMsg("Hello");
        System.out.println(b.getMsg());
    }
}

在B类中不能够对msg属性进行访问,因为msg属性是A类私有属性,只能够间接的进行私有属性。

3、在子类对象构造之前一定会默认调用父类的无参构造,以保证父类的对象先实例化,而后再实例化子类对象。

class A{
    public A(){
        System.out.println("A类的构造方法");
    }
}
class B extends A{
    public B(){
        System.out.println("B类的构造方法");
    }
}
public class TestDemo {
    public static void main(String args[]){
        new B();
    }
}

输出:

A类的构造方法

B类的构造方法

实例化子类对象前先去实例化了父类对象(调用了父类的无参构造方法)。

那么此时对于子类构造而言,相当于隐含了一个“super()”。

class B extends A{
    public B(){
        super();    //父类中有无参构造时,加或不加没有区别
        System.out.println("B类的构造方法");
    }
}

如果父类中没有无参构造方法,此时必须使用super()明确调用父类的有参构造方法。

class A{
    public A(String title){    //A类中没有无参构造函数
        System.out.println("A类的构造方法");
    }
}
class B extends A{
    public B(String title){    
        super(title);    //此时必须有一个super()调用且必须传入对应参数
        System.out.println("B类的构造方法");
    }
}
public class TestDemo {
    public static void main(String args[]){
        new B("Hello");
    }
}

这是“简单Java类中必须包含有无参构造函数”的其中一个原因。

 

Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

super()主要是由子类调用父类中的构造方法,那么这行语句必须要放在子类构造方法的首行,这点跟this()类似。

//this()方法调用本类的无参构造方法,那么就是自己调用自己
class B extends A{
    public B(){
        this();
        System.out.println("B类的构造方法");
    }
}

//修改后,无参调用有参,有参还是要通过父类的无参,就进入了一个递归问题
class B extends A{
    public B(){
        this("A");
        System.out.println("B类的构造方法");
    }
    public B(String title){
        super();
    }
}

super()与this()不能同时出现,子类对象的构造调用前一定要先执行父类构造,为父类对象初始化后才轮到子类对象初始化。

覆写

1、方法的覆写

class A{
    public void fun(){
        System.out.println("A类中的方法");
    }
}
class B extends A{
    public void fun(){
        System.out.println("B类中的方法");
    }
}
class C extends A{
    public void fun(){
        System.out.println("C类中的方法");
    }
}
public class TestDemo {
    public static void main(String args[]){
        B b=new B();
        b.fun();
        C c=new C();
        c.fun();
    }
}

输出:

B类中的方法

C类中的方法

什么时候使用覆写?

如果发现父类中的方法名称功能不足,但是又必须使用此方法。

权限问题,被子类所覆写的方法不能够拥有比父类更严格的访问控制权限。

对于访问控制权限已经学习过三个:public >default>private,如果父类使用的方法使用的是public声明,那么子类覆写方法的时候只能是public,如果父类用的是default,子类只能使用default或public。大多数情况下只要是方法都是用public声明。

错误的示范:父类中使用了public声明,子类中用了default声明。
 

class A{
    public void fun(){
        System.out.println("A类中的方法");
    }
}
class B extends A{
    void fun(){
        System.out.println("B类中的方法");
    }
}
public class TestDemo {
    public static void main(String args[]){
        B b=new B();
        b.fun();
    }
}

疑问:父类使用private声明,子类使用public声明,会有什么样的结果呢?

class A{
    public void fun(){
        print();
    }
    public void print(){
        System.out.println("A类中的方法");
    }
}
class B extends A{
    public void print(){
        System.out.println("B类中的方法");
    }
}
public class TestDemo {
    public static void main(String args[]){
        B b=new B();
        b.fun();
    }
}

输出:B类中的方法

修改访问控制权限后:

class A{
    public void fun(){
        print();
    }
    private void print(){
        System.out.println("A类中的方法");
    }
}
class B extends A{
    public void print(){
        System.out.println("B类中的方法");
    }
}
public class TestDemo {
    public static void main(String args[]){
        B b=new B();
        b.fun();
    }
}

输出:A类中的方法

发现此时根本没有覆写print()方法,也就是说父类中的print()对子类而言是不可见的,就算子类定义了一个与之完全相同的符合覆写要求的方法,也不能进行覆写。这个时候子类所写的覆写方法相当于新定义了一个方法。

如果想要调用父类的方法进行一次输出

class B extends A{
    public void print(){
        print();
        System.out.println("B类中的方法");
    }
}

这段代码中的print()相当于this.print(),那么这句话是什么意思呢,从本类中查找print()方法,确实能找到,那么就进入了递归死循环。

super.print();

注意:关于super.方法()与this.方法()的区别?

使用“this.方法()”首先查找本类中是否存在有调用的方法名称,如果存在则直接调用,如果不存在则查找父类中的此方法,如果有就调用,如果没有就会发生编译错误。

使用“super.方法()”,明确的表示调用父类中的指定方法。

面试题:请解释重载与覆写的区别?(Overloading与Override)

在使用Overloading的时候返回值能否不同?

No. 区别 重载 覆写
1 英文单词 Overloading Override
2 发生范围 一个类里面 继承关系中
3 定义 方法名称相同、参数及个数不相同 方法名称相同、参数的类型、个数相同、方法返回值相同
4 权限 没有权限的限制 子类覆写的方法不能拥有比父类更严格的权限

在发生重载关系的时候,返回值可以不同,但是考虑到程序设计的统一性,重载时尽量保证返回值类型相同。

属性覆盖

在任何开发中,类中的属性必须封装,一旦封装,父类定义的私有属性子类根本看不见,更不会相互影响。

class A{
    String info="hello";
}
class B extends A{
    int info=100;
    public void print(){
        System.out.println(super.info);
        System.out.println(this.info);
    }
}
public class TestDemo {
    public static void main(String args[]){
        B b=new B();
        b.print();
    }
}

输出:

hello

100

比较super和this?

No. 区别 this super
1 功能 调用本类构造、本类方法、本类属性 子类调用父类构造、父类方法、父类属性
2 形式 先查找本类中是否存在有指定的调用结构,如果有则直接调用,如果没有则调用父类定义 不查找子类,直接调用父类定义
3 特殊 表示本类的当前对象 -

本类或者是父类的操作中最好加上“this.”、“super.”来区别。

综合实战

整型数组的操作,由外部传入数组的大小,数据的保存以及数据的输出。排序类反转类。

final

1、使用final定义的类不能再有子类

一般情况下系统类会使用final,如果要进行架构类的开发也会用到,但是再一般开发中很少用到final

错误示范:

 final class A{

     public void fun(){}

}

class B extends A{

     public void fun(){}

}

2、使用final定义的方法不能被子类所覆写

在一些时候由于父类中的某些方法具备某些因此的特性,那么并且子类必须使用此方法操作的时候就加上final,意思是子类不要去使用该方法。

错误示范:

class A{

     public final void fun(){}     //返回值跟方法名称是在一起的

}

class B extends A{

     public void fun(){}

}

3、使用final定义的变量就成了常量,常量必须在定义的时候就设置好内容,并且不能修改

使用常量可以利用一些变量名称来描述一些数值

为了让程序中的常量可以与变量进行有效的区分,所有的常量名称都要求使用大写字母表示。

常量STUDENT_NAME

变量studentName

全局常量:

public static final 声明的就是全局常量

public static final String MSG="HELLO";

static的数据保存在公共数据区,所以此处的常量就是一个公共常量。

多态性

class A{
    public void print(){
        System.out.println("A中的方法");
    }
}
class B extends A{
    public void print(){
        System.out.println("B中的方法");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        B b=new B();
        b.print();
    }
}

输出:B中的方法

多态性有两种描述形式:

     方法的多态性:

          方法的重载:同一个方法名称,会根据传入的参数类型和个数执行不同的方法

          方法的覆写:同一个方法,会根据子类的不同,实现不同的功能

     对象的多态性:发生在继承关系类之中,子类和父类的转换

          向上转型(自动完成):父类 父类对象 = 子类实例;

          向下转型(强制完成):子类 子类对象 = (子类)父类实例;

          父类是大范围,子类是小范围

范例:向上转型

class A{
    public void print(){
        System.out.println("A中的方法");
    }
}
class B extends A{
    public void print(){
        System.out.println("B中的方法");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        A a=new B();//向上转型
        a.print();
    }
}

输出:B中的方法      

只要看new的是哪个类,这边new的是B类,B中又有print方法覆写   

范例:向下转型

public class TestDemo{
    public static void main(String arg[]){
        A a=new B();//向上转型
        B b=(B) a;//向下转型
        a.print();
    }
}

输出:B中的方法   

向上转型:由于所有的子类对象实例都可以自动的向上转型,功能在于参数的统一上。

class A{
    public void print(){
        System.out.println("A中的方法");
    }
}
class B extends A{
    public void print(){
        System.out.println("B中的方法");
    }
}
class C extends A{
    public void print(){
        System.out.println("C中的方法");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        //只要是A类的子类,都可以用A类来接收
        A a1=new B();//向上转型
        A a2=new C();//向上转型
        a1.print();
        a2.print();
    }
}

输出:

B中的方法

C中的方法

只要是A类的子类,都可以用A类来接收。参数统一之后,还可以调用子类覆写后的方法体,即:同一个方法针对于不同的子类可以有不同的实现。不管怎么实现,就关注里面的方法。

向下转型:父类要调用子类自己特殊的方法

class A{
    public void print(){
        System.out.println("A中的方法");
    }
}
class B extends A{
    public void print(){
        System.out.println("B中的方法");
    }
    public void funB(){
        System.out.println("B类中特殊的方法");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        A a1=new B();//向上转型
        B b=(B) a1;//向下转型
        b.funB();
    }
}

输出:B类中特殊的方法

疑问:以上的代码与直接通过实例化B类调用funB()方法输出内容一样,为什么还要先执行向上转型呢?

对数据的操作分为两步:

     第一步:设置数据(保存数据),最需要的是参数统一功能

     第二步:取得数据

public class TestDemo{
    public static void main(String arg[]){
        fun(new B());   //A a=new B()向上转型
    }
    public static void fun(A a){    //统一参数
        //调用个性化的特征
        B b=(B) a;//向下转型,因为要调用子类的特殊功能
    }
}

此时fun方法只允许A类对象调用此方法,那么必须统一参数A a=new B()向上转型后,而此时发现A类对象无法使用B类对象的特殊功能,只能进行向下转型来获取特殊功能,B b=(B) a。

总结:

向上转型(80%)可以得到参数的统一,向下转型(5%)可以调用子类的特殊方法,(15%)不转型。

不要过多的扩充功能,导致需要向下转型,而向下转型并不是一帆风顺的

public class TestDemo{
    public static void main(String arg[]){
        A a=new A();
        B b=(B) a;
        b.print();
    }
}

Exception in thread "main" java.lang.ClassCastException: A cannot be cast to B
    at TestDemo.main(TestDemo.java:17)

表示的是类转换异常,指的是两个没有关系的类对象强制发生向下转型时所发生的异常。

所以向下转型会有风险存在,为了保证转型的顺利进行,在Java里面提供过了一个关键字:instanceof

对象 instanceof 类  返回 boolean型

如果某个对象是某个类的实例就返回true,否则就返回false;

public class TestDemo{
    public static void main(String arg[]){
        A a=new A();
        if(a instanceof B){
            B b=(B) a;
            b.print();
        }
    }
}

“A a=new A();”只是实例化了A对象,所以A类对象a并没有和B类发生关系,对于向下转型如果要发生前,一定要首先发生向上转型,“A a=new B()”这句话将A类对象和B类发生了关系,那么就可以进行向下转型。

总结:

开发之中尽量使用向上转型,以统一参数类型,同时只有发生了向上转型才可以发生向下转型。

子类尽量不要过多的扩充与父类无关的操作方法。子类的方法要跟父类的方法功能保持一致。

抽象类

普通类可以直接产生实例化对象,并且在普通类之中可以创建普通方法、构造方法、常量、变量等内容。而抽象类就是指在普通类的结构里面增加抽象方法的组成部分。

所有的普通方法都会有一个“{}”,这个表示方法体,有方法体的方法可以直接被对象使用,而抽象方法是没有方法体的方法,同时还必须使用abstract关键字定义。

拥有抽象方法的类一定属于抽象类,必须使用abstract。

范例:定义一个抽象类

abstract class A{
    public void fun(){
        System.out.println("普通方法");
    }
    public abstract void print();
}

产生对象:A a = new A();

出现错误:'A' is abstract; cannot be instantiated

当一个类的对象实例化之后就意味着这个对象可以调用属性和方法,但是抽象类中有抽象方法,抽象类没有方法体,那么怎么调用呢?那么不能调用方法又怎么实例化呢?

抽象类的使用原则如下:

     抽象类必须有子类,即:每一个抽象类一定要被子类所继承;

     抽象类的子类(子类不是抽象类)必须要覆写抽象类中的全部抽象方法(强制子类覆写);

     抽象类的对象实例化需要依靠子类完成,采用向上转型的方法处理。

范例:正确使用抽象类

abstract class A{
    public void fun(){
        System.out.println("普通方法");
    }
    public abstract void print();
}
//一个子类只能够继承一个抽象类,属于单继承局限
class B extends A{
    public void print(){
        System.out.println("抽象类的子类必须覆写抽象类父类的方法");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        A a=new B();
        a.print();
    }
}

输出:抽象类的子类必须覆写抽象类父类的方法

虽然一个子类可以去继承任意一个普通类,但实际开发中,普通类不要去继承另外一个普通类,只能继承抽象类。

抽象类的限制

1、抽象类里面由于会存在一些属性,那么在抽象类之中一定会存在构造方法,目的是为属性初始化,子类对象初始化的时候,依然满足先执行父类构造,再调用子类构造的情况。

2、抽象类不能使用final定义,因为抽象类必须有子类,final定义的不能有子类。

3、外部的抽象类不允许使用static声明,而内部的不允许,使用static声明的内部抽象类就相当于是一个外部抽象类,继承的时候使用“外部类.内部类”表示内部类名称。

abstract class A{
    abstract static class B{
        public abstract void print();
    }
}
class X extends A.B{
    public void print(){
        System.out.println("!!!!!!");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        A.B ab=new X();
        ab.print();
    }
}

4、对于static,任何情况下,都可以在没有对象的时候直接调用,对于抽象类也是一样。

abstract class A{
    public static void print(){
        System.out.println("!!!!!!");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        A .print();
    }
}

5、有些时候,由于抽象类有些时候只需要一个特定的系统子类操作,所以可以忽略掉外部子类。

abstract class A{
    public abstract void print();
    private static class B extends A{  //内部抽象类子类
        public void print(){
            System.out.println("hello");
        }
    }
    //这个方法不受实例化对象的控制
    public static A getInstance(){
        return new B();
    }
}
public class TestDemo{
    public static void main(String arg[]){
        //此时取得抽象类对象的时候完全不需要知道B的存在
        A.getInstance().print();
    }
}

这样的设计在系统类库中比较常见,目的是为用户隐藏不需要知道的子类。

abstract class A{
    public A(){     //2、父类构造方法
        this.print();   //3、调用print()方法
    }
    public abstract void print();
}
class B extends A{
    private int num=100;
    public B(int num){  //
        this.num=num;
    }
    public void print(){    //4、调用覆写方法
        //num还没初始化,内容是其对应的数据类型的默认值即0
        System.out.println("num="+num); //5、输出0
    }
}
public class TestDemo{
    public static void main(String arg[]){
        new B(30);  //1、执行构造
    }
}

 输出:num=0

 解决思路:在任何一个类的构造执行完之前,所有属性的那日同都是其对应的数据类型的默认值,且在子类构造之前一定先执行父类构造,那么此时子类构造还没有执行,所以num=0。

抽象类应用——模板设计

现在有三类事务:

     机器人:充电、工作

     人:吃饭、睡觉、工作

     猪:吃饭、睡觉

可以任意的控制人、机器人、猪

abstract class Action{
    public static final int EAT=1;
    public static final int SLEEP=5;
    public static final int WORK=7;
    public void command(int flag){
        switch (flag){
            case EAT:
                this.eat();
                break;
            case SLEEP:
                this.sleep();
                break;
            case WORK:
                this.work();
                break;
            case EAT+WORK:
                this.work();
                this.eat();
                break;
            case EAT+SLEEP:
                this.sleep();
                this.eat();
                break;
        }
    }
    public abstract void eat();
    public abstract void sleep();
    public abstract void work();
}
class Robot extends Action{
    public void eat(){
        System.out.println("机器人正在充电");
    }
    public void sleep(){

    }
    public void work(){
        System.out.println("机器人正在工作");
    }
}
class Human extends Action{
    public void eat(){
        System.out.println("人正在吃饭");
    }
    public void sleep(){
        System.out.println("人正在睡觉");
    }
    public void work(){
        System.out.println("人正在工作");
    }
}
class Pig extends Action{
    public void eat(){
        System.out.println("猪正在吃饭");
    }
    public void sleep(){
        System.out.println("猪正在睡觉");
    }
    public void work(){

    }
}
public class TestDemo{
    public static void main(String arg[]){
        fun(new Robot());
        fun(new Human());
        fun(new Pig());
    }
    public static void fun(Action act){
        act.command(Action.EAT);
        act.command(Action.SLEEP);
        act.command(Action.WORK);
    }
}

输出:

Java面向对象编程学习笔记(不完整)_第26张图片

接口

接口基本定义

如果一个类之中只是由抽象方法和全局常量所组成的,那么这种情况下不会将其定义为抽象类,而只会将其定义为接口,所以所谓的接口严格来讲就属于一个特殊的类,而且这个类里面只有抽象方法与全局常量连构造都没有。

要定义一个接口使用interface关键字完成。

interface A{
    public static final String MSG="Hello"; //全局常量
    public abstract void print();
}

由于接口里面存在有抽象方法,所以接口对象不可能直接使用new来进行实例化操作。

  • 接口必须要有子类,但是此时一个子类可以使用implements关键字实现多个接口
  • 接口的子类(如果不是抽象类),那么必须要覆写接口中的全部抽象方法
  • 接口的对象可以利用子类对象的向上转型来进行实例化操作。

范例:实现多个接口

interface A{
    public static final String MSG="Hello"; //全局常量
    public abstract void print();
}
interface B{
    public abstract void get();
}
class X implements A,B{
    public void print(){
        System.out.println("A接口的抽象方法");
    }
    public void get(){
        System.out.println("B接口的抽象方法");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        X x=new X();
        A a = x;
        B b = x;
        a.print();
        b.get();
    }
}

实例化了A和B对象,现在由于X是A和B的子类,那么X类的对象可以变为A接口或B接口的对象。

public class TestDemo{
    public static void main(String arg[]){
        A a = new X();
        B b=(B) a;
        b.get();
        System.out.println(a instanceof A);
        System.out.println(a instanceof B);
    }
}

输出:

B接口的抽象方法

true

true

在定义结构上来讲A和B接口没有任何直接的关系,但是这两个接口却同时拥有一个子类,千万不要被它的类型和名称所迷惑("A a = new X();"这句话中看起来它是A类型的但实际上就是看new X()),因为我们最终实例化的是X子类,而这个子类属于B类对象,所以以上代码行得通。

但是对于子类而言,除了接口之外,还有可能要去继承抽象类,先extends后implements

Java面向对象编程学习笔记(不完整)_第27张图片

对接口而言,里面的组成就是抽象方法和全局常量,所以为了省略可以不写abstract和public static final,并且在方法上是否编写public结果都是一样的。

interface A{
    public static final String MSG="Hello"; //全局常量
    public abstract void print();
}
interface A{
    String MSG="Hello"; //全局常量
    void print();
}
class X implements A{
    void print(){     //相当于default void print()
        System.out.println("A接口的抽象方法");
    }    
}

public >default>private,在接口里面没有写上public,其最终访问权限也是public,绝对不是default。为了防止某些不熟悉语法的开发者出现错误,所以强烈建议在接口定义方法的时候一定写上public。

interface A{
    String MSG="Hello"; //全局常量
    public void print();
}

对于接口的组成,大部分都是以抽象方法为主,很少有接口只是单纯的定义全局常量。

一个抽象类只能继承一个抽象类,但是一个接口却可以用extends来同时继承多个接口(但是接口不能继承抽象类)。

范例:观察接口的继承

interface A{
    public abstract void funA();
}
interface B{
    public abstract void funB();
}
interface C extends A,B{
    public abstract void funC();
}
class X implements C{
    public void funA(){}
    public void funB(){}
    public void funC(){}
}
public class TestDemo{
    public static void main(String arg[]){
    }
}

从继承关系来讲抽象类的限制要比接口多了太多:

一个抽象类只能够继承一个抽象父类,而接口没有这个限制;

一个子类只能继承一个抽象类,而却能继承多个接口;

在整个Java里面,接口的主要功能就是解决单继承局限问题

虽然从接口本身的概念上来讲只能够由抽象方法和全局常量组成,但是所有内部接口不受到这些要求的限制,也就是说在接口里面可以定义普通内部类、抽象内部类、内部接口。

范例:在接口里定义抽象类(接口内的抽象类必须要写abstract)

interface A{
    public void funA(); //只有接口内的抽象方法不需要写abstract
    abstract class B{   //接口内的抽象类必须要写abstract
        public abstract void funB();
    }
}
class X implements A{
    public void funA(){

    }
    class Y extends B{  //内部抽象类的子类
        public void funB(){

        }
    }
}
public class TestDemo{
    public static void main(String arg[]){
    }
}

以上的写法在实际开发中不可能用到,只是想说明可以这么写

范例:在一个接口内如果使用了static来定义了一个内部接口(是一个外部接口)

interface A{
    public void funA();
    static interface B{
        public abstract void funB();
    }
}
class X implements A.B{
    public void funB(){
    }
}

大部分情况,只要求清楚内部接口的定义即可。

总结:接口在实际开发之中有三大核心作用:

  • 定义不同层之间的操作标准;
  • 表示一种操作的能力;
  • 表示将服务器端的远程方法视图暴露给客户端。

接口的实际应用(标准)

Java面向对象编程学习笔记(不完整)_第28张图片

电脑和U盘等是没有直接关系的,而是通过中间的接口来取得联系的。所以要先开发出接口标准。

定义USB标准

//标准可以连接不同层的操作类
interface USB{    //定义标准一定是接口
    public void start();
    public void stop();
}
interface USB{
    public void start();
    public void stop();
}
class Computor{ 
    public void plugin(USB usb){
        usb.start();
        usb.stop();
    }
}
class Flash implements USB{
    public void start(){
        System.out.println("U盘开始使用");
    }
    public void stop(){
        System.out.println("U盘停止使用");
    }
}
class Print implements USB{ 
    public void start(){
        System.out.println("打印机开始使用");
    }
    public void stop(){
        System.out.println("打印机停止使用");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        Computor com = new Computor();  
        com.plugin(new Print());    
    }
}

在现实生活之中,标准的概念随处可见,而在程序之中标准就是由接口来定义的。

接口的应用(工厂设计)

interface Fruit{
    public void eat();
}
class Apple implements Fruit{
    public void eat(){
        System.out.println("吃苹果");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        Fruit f=new Apple();
        f.eat();
    }
}

个人疑问:

Fruit f=new Apple();
f.eat();
Apple a=new Apple();
a.eat();

这两种写法都是输出同样的结果,为什么还要用向上转型呢?

以上的程序可以通过主方法得到Fruit接口对象,但是是否存在问题呢?

如果要想确认一个代码是否好,有几个标准:

  • 客户端调用简单,不需要关注具体的细节;
  • 客户端之外的代码修改,不影响用户的使用,客户不需要关心代码是否变更。

以上的程序没有任何语法错误,但是这次的问题就出在了new上。一个接口不可能只有一个子类。

现在新增一个橘子类,如果要想输出吃橘子,还要修改代码Fruit f=new Orange();

class Orange implements Fruit{
    public void eat(){
        System.out.println("吃橘子");
    }
}

发现如果现在直接在客户端上产生了实例化对象,那么每一次要想跟换代码,需要修改代码。

在整个代码过程之中,我们最需要关系的就是如何取得一个Fruit接口对象,然后直接调用,至于这对象是谁实例化的,那不是我客户端的工作。

所以最大的问题就在于关键字new,代码耦合度太高,代码不方便维护,就相当于A一直要与B绑定在一起。可以参考Java虚拟机的思想:程序->JVM->适应不同的操作系统。

解决方法:增加一个过渡

interface Fruit{
    public void eat();
}
class Apple implements Fruit{
    public void eat(){
        System.out.println("吃苹果");
    }
}
class Orange implements Fruit{
    public void eat(){
        System.out.println("吃橘子");
    }
}
class Factory{
    public static Fruit getInstance(String className){
    //大部分情况下,类中的方法是由static组成,基本上就表示这个类中不需要定义属性
    //抽象类中,取得实例化对象
        if("apple".equals(className)){
            return new Apple();
        }else if("orange".equals(className)){
            return new Orange();
        }else{
            return null;
        }
    }
}
public class TestDemo{
    public static void main(String arg[]){
        Fruit f=Factory.getInstance("orange");
        f.eat();
    }
}

现在的客户端不会看见具体的子类,因为所有的接口对象都是通过Factory类取得的,如果日后要扩充新的子类对象,只需要修改Factory类即可,但是客户端的调用不需要改变。

Java面向对象编程学习笔记(不完整)_第29张图片

接口的应用(代理设计)

interface Subject{
    public void make();
}
class RealSubject implements Subject{
    public void make(){
        System.out.println("核心主题操作");
    }
}
class ProxySubject implements Subject{
    private Subject subject;
    public ProxySubject(Subject subject){
        this.subject=subject;
    }
    public void prepare(){
        System.out.println("核心主题操作前的准备");
    }
    public void make(){
        this.prepare();
        this.subject.make();
        this.destory();
    }
    public void destory(){
        System.out.println("核心主题操作前的收尾");
    }
}
public class TestDemo{
    public static void main(String arg[]){
        Subject sub=new ProxySubject(new RealSubject());
        sub.make();
    }
}

输出:

代理设计模式的核心精髓就在于有一个主题操作接口(接口里面可能有多种方法),只完成核心业务,而代理主题负责完成与核心主题有关的操作。

Java面向对象编程学习笔记(不完整)_第30张图片

接口与抽象类的区别

抽象类和接口在使用的形式上是非常相似的

No. 区别 抽象类 接口
1 关键字 abstract class interface
2 组成 构造方法、普通方法、抽象方法、static方法、常量、变量 抽象方法、全局常量
3 子类使用 class 子类 extends 抽象类 class 子类 implements 接口,接口...
4 关系 可以实现多个接口 接口不能继承抽象类,却可以继承多个父接口
5 权限 可以使用各种权限 只能使用public权限
6 限制 单继承局限 无单继承局限
7 子类 抽象类和接口都必须有子类,子类必须覆写所有的抽象类方法
8 实例化对象 依靠子类的向上转型进行对象的实例化

当抽象类和接口都可以使用的时候,优先考虑接口,因为它没有单继承局限。

不成文的规定(50%):

  • 在进行某些公共操作的时候一定要定义接口;
  • 有了接口就需要利用子类完善方法;
  • 如果是自己写的接口,那么绝对不要使用关键字new实例化子类,而使用工厂设计来实例化对象。

Java面向对象编程学习笔记(不完整)_第31张图片

 

接口可以继承接口吗,抽象类可以继承接口吗,抽象类可以继承实体类吗?

1、接口可以继承接口,抽象类不可以继承接口,但可以实现接口。

2、抽象类可以继承实体类。抽象类可以实现(implements)接口,抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。

3.抽象类可以继承实体类,就是因为抽象类的可以继承性和有方法。

4、一个接口可以继承多个接口. interface C extends A, B {}是可以的. 一个类可以实现多个接口: class D implements A,B,C{} 但是一个类只能继承一个类,不能继承多个类 class B extends A{} 在继承类的同时,也可以继承接口: class E extends D implements A,B,C{} 这也正是选择用接口而不是抽象类的原因。

 

抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。

需要了解的知识点:

1子类用无参的构造函数实例化:

子类继承父类,如果子类和父类中都没有明确的构造函数,那么就会有一个默认的无参的构造函数

当创建子类无参的实例的时候默认会在子类无参的构造函数中调用父类的无参的构造函数

如果父类中的无参的构造函数明确的把访问修饰符改成了private,即子类对于父类的无参的构造函数没有访问权限,所以就会报错

2子类用有参的构造函数实例化:

如果子类在实例化时是带参构造函数,那么调用顺序为 先调用父类中的无参的构造函数再调用父类中的有参数的构造函数再调用子类中的构造函数

3抽象类中不能有公共的构造函数可以有product访问修饰符修饰的构造函数,因为抽象类不能实例化,抽象类存在的意义就是为了子类去继承,根据1得知子类如果有实例化父类中必须有一个无参的构造函数

4疑点

     如果明确的就是显式的意思,那么我就想不通了,如果明确不是显式的意思,那么就很好理解了,写这段的意思就是为了解决这个问题,可看来还是没有能够解决,希望有大牛能帮我解答,在此先拜谢了,感觉这个方面的访问并不简单,往深入想还有很多深奥的东西没有去思考,还需要去好好的花费时间的去思考一下
 

 

 

 

 

你可能感兴趣的:(java)