JAVA基础语法——面向对象思想


前言

提示:这里主要是我学习Java的基础内容,后续我会把学到的知识补充到里面:


提示:以下是本篇文章正文内容,下面案例可供参考

一、面向对象思想

1.面向对象(OOP)概述

OOP一开始我也不知道是什么东西,平时看到他们提起过,提的次数多了,百度查了一下,原来OOP就是面对对象编程,还有OOD(面向对象的设计),OOA(面向对象的分析)。
学过C的都知道C语言是面向过程的,Java和C语言不同是面向对象的。也可以理解为面向过程是机器思维,面向对象才更适合人类思维。
例如:面对过程就像是一个舔狗,一步一步的分析,一步一步的实现,一步一步的去舔那个女神,得到女神的认可,所以面向过程在开销上比面向对象大而且舔狗在舔的过程中自我感觉非常好,所以面向过程的性能比面向对象的性能好。
反之,面向对象就像是个海王,他不会像面向过程那样一直舔,死脑筋(不易维护、不易复用、不易扩展),他会查询到所有的女神信息,单独把女神抽象成一个类,针对所有女神的兴趣、爱好等的一系列进行攻略,所有面向对象相比于面向过程极大降低了,女神多的是,你算老几,但毕竟面对对象是一对多进行,所以他的性能比面向过程差些

在举一个例子:烤鸭:

      面向过程:先养小鸭子养大
               烤箱
               拔毛洗鸭子掏内脏
               配料涂抹鸭子
               扔进烤箱拷
               出炉吃熟的鸭子

      面向对象:
            请去全聚德
             给钱
             吃鸭子
  • 总结
    • 面向过程
      • 优点:
      • 性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。
      • 缺点:不易维护、不易复用、不易扩展
    • 面向对象
      • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
      • 面向对象思想更贴近于现实生活
      • 面向对象可以讲复杂事物简单化,因为面向对象只注重结果
      • 缺点:性能比面向过程差

2.面向对象三大特性五大原则

  • 面向对象的三大特性

1、封装
隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。

2、继承
提高代码复用性;继承是多态的前提。

3、多态
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。

  • 五大基本原则

单一职责原则SRP(Single Responsibility Principle)
类的功能要单一,不能包罗万象,例如鸟这个类里面都要是鸟,鸵鸟,麻雀都行,但是坚决不能放一直狗进去。

开放封闭原则OCP(Open-Close Principle)
一个模块对于拓展是开放的,对于修改是封闭的
在设计一个模块时,应当使得这个模块可以在不被修改的前提下被扩展(这就要求我们不能在一个类中写死所有的功能)。也就是说,应当可以在不必修改源代码的情况下修改这个模块的行为。设计的目的便在于面对需求的改变而保持系统的相对稳定,从而使得系统可以很容易的从一个版本升级到另一个版本,也许我们做一个项目不难,难就难在后期不断变态的需求上把你搞死,如果设计不好的话会大量修改原来的代码,这样会造成一个类中的代码巨大,而且还容易导致新的bug,所以在写代码的时候最好不要写死,像我们写代码把一些需要变化的参数使用前端传参或者放在一个配置文件中,方便扩展。

里式替换原则LSP(the Liskov Substitution Principle LSP)
子类可以扩展父类的功能,但不能改变父类原有的功能。

依赖倒置原则DIP(the Dependency Inversion Principle DIP)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。其核心思想是:要面向接口编程,不要面向实现编程。

接口分离原则ISP(the Interface Segregation Principle ISP)
要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。

二、类和对象

1.对象和类的概念

类可以生成对象,类是对象的抽象
对象:对一类事物的描述,是抽象的、概念上的定义 。(类就是具体的事物 )
:是实际存在的该类事物的每个个体,因而也称为实例(instance) 。对象就是对对象的抽象(抽象 抽出像的部分)
例如:有一群对象,家猪,野猪,公猪,母猪这些对象都是猪我们就可以根据这些对象去创建猪这个类
公母就是性别相当于这个类的声明属性 在家里呆着和在野外呆着就相当于猪的方法动作
先有具体的对象,然后抽象各个对象间相似的部分,归纳出类通过类再认识其他对象。

2.对象的特征

属性:对象具有的各种特征

  • 每个对象的每个属性都拥有特定值
    例如:小明今年18岁,这个人就是个对象,小米这个名字和他的身高就是他的属性
  • 方法——对象执行的操作
    例如:小明出去打篮球,踢足球,这个人就是个对象,打篮球,踢足球就是他的方法

对象和类的总结
对象和类的关系:特殊到一般,具体到抽象。
类:我们叫做class。
对象:我们叫做Object, instance(实例)。以后我们说某个类的对象,某个类
的实例。是一样的意思。
类可以看成一类对象的模板,对象可以看成该类的一个具体实例。
类是用于描述同一类形的对象的一个抽象的概念,类中定义了这一类对象所对应具有的静态和动态属性。
对象是Java程序的核心,在Java程序中“万事万物皆对象”。
JDK提供了很多类供编程人员使用,编程人员也可定义自己的类。

3.构造方法

  • 写一个简单的类与对象
package com.tu.Demo01;

public class PersonDemo {
    public static void main(String[] args) {
        //创建Person类的对象(实例)
        Person person = new Person();//注意思考:Person()到底是什么?
        //给成员变量赋值,类中的方法依赖于成员变量的值
        person.name = "小涂";
        person.age = 15;
        System.out.println("有一个人名字叫:" + person.name + "年龄是" + person.age);  //有一个人名字叫:小涂年龄是15  好好学习!
        //调用方法
        person.study();

    }
}

class Person {
    //定义属性:姓名与年龄就是这个对象的特征  学习这个动作就是这个对象的方法
    String name;  //姓名
    int age;         //年龄

    void study() { //学习有关的功能,不需要返回值和参数
        System.out.println("好好学习!");
    }
}

  • 继续修改,注释掉属性赋值,重点介绍构造函数,查看输出结果。
package com.tu.Demo01;

public class PersonDemo {
    public static void main(String[] args) {
        //创建Person类的对象(实例)
        Person person = new Person();//注意思考:Person()到底是什么?
        //给成员变量赋值,类中的方法依赖于成员变量的值
//        person.name = "小涂";
//        person.age = 15;
        System.out.println("有一个人名字叫:" + person.name + "年龄是" + person.age);  //有一个人名字叫:null年龄是0  好好学习!
        //调用方法
        person.study();

    }
}

class Person {
    //定义属性:姓名与年龄就是这个对象的特征  学习这个动作就是这个对象的方法
    String name;  //姓名
    int age;         //年龄

    void study() { //学习有关的功能,不需要返回值和参数
        System.out.println("好好学习!");
    }

上面的例子说明了:当在类中定义一些属性后,当实例化创建对象时,没有手动的给这些对象的属性赋值,系统会自动赋值(初始值),原因:在创建对象时,要在堆空间存储对象的数据,这些数据必须有初始值。赋值的依据是属性的数据类型决定,String类型赋初值为Null,int类型赋初值为0,float、double赋初值为0.0。

思考一:现在,我们知道如何通过一个类创建一个人,new出person后,我们可以为这个人的属性赋值,如年龄和姓名。但是否有这样一种情况,某个人,一生下来就具备了年龄和姓名,且这个年龄和名字不能是默认初始值,如何处理??
答案:使用构造方法!!!
上步骤中,虽然没有写构造方法,但其实构造方法也存在。在编译的时候,编译器会自动为每个类添加默认构造方法(但如果手写了构造方法,则编译器将不再添加默认构造方法)。默认的构造方法格式如下

//默认的构造方法
	Person(){}

构造方法特点:

  • (1)构造方法与类同名,首字母也要大写
  • (2)构造方法必须没有返回值(void也不行)
  • 继续修改,手写构造方法,则编译器将不再自动为类添加默认构造方法。
 package com.tu.Demo01;

public class PersonDemo {
    public static void main(String[] args) {
        //创建Person类的对象(实例)
        Person person = new Person();//注意思考:Person()到底是什么?
        System.out.println("有一个人名字叫:" + person.name + "年龄是" + person.age);  
        //有一个人名字叫:tom年龄是20 好好学习!

        //调用方法
        person.study();

    }
}

class Person {
    //定义属性:姓名与年龄就是这个对象的特征  学习这个动作就是这个对象的方法
    String name;  //姓名
    int age;         //年龄
    Person(){// 手写构造方法,为成员变量赋值
        name = "tom";
        age = 20;
    }
    void study() { //学习有关的功能,不需要返回值和参数
        System.out.println("好好学习!");
    }
}

说明:新的默认构造方法,在创建person的时候都会调用,在哪儿调用的呢?在new Person();的时候调用!!!所以new 类名();的本质是调用构造方法,构造方法的调用必须用new调用

思考二:构造方法也是方法,能够也向普通方法一样传递参数呢?
答案:可以,一个类可以有多个构造方法。

  • 继续修改,为Person类,添加带参的构造方法。
package com.tu.Demo01;
public class PersonDemo {
    public static void main(String[] args) {
        Person person1 = new Person();//自动调用无参的构造方法
        Person person2 = new Person("jerry",28);//自动调用参数一致的构造方法
        //Person person3 = new Person("petter");//报错,因为没有一个参数,且参数类型为String的构造方法
        System.out.println("有一个人名字叫:"+person1.name+"  年龄是:"+person1.age);
        //输出结果为"有一个人名字叫:tom  年龄是:20"

        person1.study();

        System.out.println("有一个人名字叫:"+person2.name+"  年龄是:"+person2.age);
//        有一个人名字叫:jerry  年龄是:28 好好学习!

        person2.study();

    }
}

class Person {
    //定义属性:姓名与年龄就是这个对象的特征  学习这个动作就是这个对象的方法
    String name;  //姓名
    int age;         //年龄
    Person(){// 手写构造方法,为成员变量赋值
        name = "tom";
        age = 20;
    }
    Person(String name,int age){//带参数的构造方法,括号内的为形参
        this.name = name;//左侧this.name值的是成员变量,右侧的name为传递过来的形参
        this.age = age;//出现了,this代词,this保存调用它的对象的地址址。测试如果左侧没有this的输出结果
    }
    /*编译不通过,报错!
     * 原因是:虽然形参命名不同,但类型及顺序相同,编译器就认为同一方法
     */
     /* Person(String name1,int age1){
    	   this.name = name;
    	   this.age = age;
    }*/
    void study() { //学习有关的功能,不需要返回值和参数
        System.out.println("好好学习!");
    }
}

上面的示例,充分证明,一个类的构造方法可以有多个,建立对象的时候根据实际情况,选择调用合适的构造方法,但是不允许调用不存在的构造方法。如:在Person.java中没有Person(String name);的构造方法。调用的时候采用Person person3 = new Person(“petter”);程序会报错!
一个类的构造方法可以有多个,同一个名字,参数不同。现在Person类中已经有了Person(String name,int age)的构造方法,如果再定义一个Person(String name1,int age1)的构造方法可以不?不可以!虽然形参的名不一样,但是类型是一样,形式上还是一样的。但定义Person(int age)是可以的,参数虽然同样只有一个,但是参数的类型不同,形式上,还是不一样的。在同一个类里面,允许存在一个以上同名方法,只要他们参数类型和参数个数不同即可
如果一个类中,方法名相同名,但参数个数、类型不同,则构成了方法的重载。多个构造方法也形成了方法的重载
方法重载特点:与返回值无关,只看参数列表
方法重载(overload)和方法重写(override)有什么区别?

方法重载(overload)
位置:重载要求同类中的同名方法
格式:重载要求方法名相同,参数个数或参数类型不同,与返回值无关
案例:
void study(int A ,char B,floatC){}把这个方法叫做A (对比方法)
void study(int x ,char y,float z){}把这个方法叫做B (不是重载,因为B与A是同一个方法)
void study(int x ,float z,char y){}把这个方法叫做C (是重载,顺序不一样也重载)
int study(int A ,char B,floatC){}把这个方法叫做D (是重载)
int study(int A ){}把这个方法叫做E (是重载)
总结:
方法重载的特点:
与返回值无关,只看参数列表

方法重写(override)
位置:重写要求不同类中的同名方法(不同类要求有继承关系的两个类)
格式:重写要求方法名相同,参数个数、参数类型相同,返回值相同

构造方法总结
作用:为类中的属性初始化
存在性

  • 一个类中一定存在构造方法,但不一定看到,如果类中没有明确的定义构造方法,系统会自动分配默认的构造方法(无参),如果系统中明确的定义了构造方法,系统将不再分配。
  • 一个类中可以有多个构造方

调用:在创建对象时,使用new调用
格式:构造方法与类同名,构造方法必须没有返回值(void也不行),没有参数的构造方法是默认构造方法
方法重载

  • 在同一个类出现名字相同,但参数不同(个数不同,类型不同)的多个方法,这种应用叫做重载。
  • 方法的重载和方法的返回值是无关的
  • 当调用一个类中重载的方法时,可以根据传递的实参情况自动寻找匹配形参的方法调用。

3.This关键字

this指示代词说明
注意:上例中的有参构造方法,我们使用了this.name, 如果去掉this,编译没问题,但输出结果为
有一个人名字叫:tom 年龄是:20
好好学习!
有一个人名字叫:null 年龄是:0
好好学习!

class Person {
    
    String name;
    int age;       
    Person(){// 手写构造方法,为成员变量赋值
        name = "tom";
        age = 20;
    }
    Person(String name,int age){//带参数的构造方法,括号内的为形参
        name = name;  //左侧this.name值的是成员变量,右侧的name为传递过来的形参
        age = age;  //出现了,this代词,this保存调用它的对象的地址址。测试如果左侧没有this的输出结果
    }
    void study() { 
        System.out.println("好好学习!");
    }
}

第一个结果是无参的构造方法是没有问题的直接获取数据
第二个构造方法是有参数的构造方法,如果去掉this指示代词就会出现名字变为null,年龄变为0的问题 why?

分析原因:去掉了this,编译器以为name = name 左右两侧都是形式参数,成员变量没有赋值,仍然是默认初始值。因此name输出为null,年龄输出为0.
那么this到底是什么呢?
this是一个引用,记录着对象的地址。哪个对象调用成员变量、及方法(包括构造方法),就记录那个对象的地址。
This的作用:
1.this表示的是当前对象本身,更准确地说,this代表当前对象的一个引用。
2.通过this,可以初始化调用不同构造方法的对象
3.通过this,可以区分同名变量,this.变量,则这个变量为类的成员变量。不写this的变量有可能是局部变量,也可能是类的成员变量。
也就是说:this就代表对象,那个对象调用了this所在的成员变量、方法,this就代表那个对象。默认情况下,类中的成员变量及方法都自带this,但如果成员变量和形参名称相同,则必须显式的在成员变量前加this,否则编译器无法区分那个是类级别的成员变量,那个一个是形式参数,最后都按形参处理了。

普通方法中使用this。
– 区分类成员属性和方法的形参.
– 调用当前对象的其他方法(可以省略)
– 位置:任意

构造方法中使用this。
– 使用this来调用其它构造方法
– 位置:必须是第一条语句
this不能用于static方法。

public class TestThis {
			int a,b,c;
			TestThis(){
			System.out.println("正要new一个Hello对象");
}
			TestThis(int a,int b){  //但如果成员变量和形参名称相同,则必须显式的在成员变量前加this
			//Hello(); // //这样是无法调用构造方法的!
			this(); //调用无参的构造方法,并且必须位于第一行!
			a = a;//这里都是指的局部变量而不是成员变量
			this.a = a;//这样就区分了成员变量和局部变量. 这种情况占了this使用情况的大多数!
			this.b = b;
}
			TestThis(int a,int b,int c){
			this(a,b); //调用无参的构造方法,并且必须位于第一行!
			this.c = c;
}
			void sing(){}
			void chifan(){
			this.sing(); //sing();
			System.out.println("你妈妈喊你回家吃饭!");
}
public static void main(String[] args){
			TestThis hi = new TestThis(2,3);
			hi.chifan();
}
}

4.static 关键字

  • 在类中,用static声明的成员变量为静态成员变量 ,或者叫做: 类属性,类变量.
  • 它为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显式初始化,
    对于该类的所有对象来说,static成员变量只有一份。被该类的所有对象共享!!(可以理解为作用域提高,扩大到类的范围)
  • 可以使用”对象.类属性”来调用。不过,一般都是用“类名.类属性”
  • static变量置于方法区中!
  • 用static声明的方法为静态方法,不需要对象,就可以调用(类名.方法名)
  • 在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员。
  • 静态方法不能以任何方式引用this和super关键字
public class TestStatic {
				int a;
				static int width;
				static void gg(){
				System.out.println("gg");
}
			void tt(){
				System.out.println("tt");
}
public static void main(String[] args){
				TestStatic hi = new TestStatic();
				TestStatic.width = 2;
				TestStatic.gg(); //gg();
				hi.gg(); //通过引用也可以访问static变量或static方法。不过,一般还是使用类名.static成员名来访问。
				gg();
}
}

使用static声明的成员变量称为静态变量,
使用static声明的方法称为静态方法
静态变量和静态方法又称为类变量和类方法

//使用static统计在类中一共产生多个对象
public class StaticDemo //声明类
{
		static int count;//声明静态属性
	public StaticDemo(){//无参构造方法
		count++;
		System.out.println("创建了"+count+"个对象");
}
public static void main(String[] args) 
{
		new StaticDemo();//创建匿名对象
		new StaticDemo();//创建匿名对象
		new StaticDemo();//创建匿名对象
}
}
  • 静态属性的访问形式
    • (1)对象名.属性
    • (2)类名.属性
  • 静态方法
    • 访问修饰符 static 返回值类型 方法名(){}
  • 访问形式
    • (1)对象名.方法名();
    • (2)类名.方法名()

常见错误

public void showInfo(){
		System.out.println("姓名:"+this.name+"\t年龄:"+this.age+"\t城
市:"+this.country);
}
public static void welcome(){
	this.showInfo();//调用本类的非静态方法
	System.out.println("HELLOWORD......");
}
class Dog {
 	private String name = "旺财"; // 昵称
 	private int health = 100; // 健康值
 	private int love = 0; // 亲密度
 	public void play(int n) {
 	static int localv=5; //在方法里不可以定义static变量
 	health = health - n;
 	System.out.println(name+" "+localv+" "+health+" "+love);
 }
 public static void main(String[] args) {
 	Dog d=new Dog();
 	d.play(5);
 }
} 

static修饰与非static修饰的区别
在这里插入图片描述

5.代码块

▪ 概念:使用”{}”括起来的一段代码
▪ 分类:根据位置可分类
▪ 普通代码块——直接在方法戒语句中定义的代码块
▪ 构造代码块——直接写在类中的代码块
▪ 静态代码块——使用static声明的代码块
▪ 同步代码块——多线程的时候会学到
静态初始化块
如果希望加载后,对整个类进行某些初始化操作,可以使用
static初始化块。

  • 类第一次被载入时先执行static代码块;
  • 类多次载入时,static代码块只执行一次;
  • Static经常用来进行static变量的初始化。是在类初始化时执行,不是在创建对象时执行。
  • 静态初始化块中不能访问非static成员。
public class TestStaticBlock { 
		static {
			System.out.println("此处,可执行类的初始化工作!");
}
public static void main(String[] args) {
			System.out.println("main方法中的第一句");
}
}

6.基础内存解析

  • 主要用于存储main(),引用变量名等,就是我们数据结构中使用的栈,栈满足先进后出的原则,所以我们程序中main方法最先压入栈,但是最后进行执行。
    Java栈的区域很小 , 特点是存取的速度特别快,栈存储的特点是, 先进后出,存储速度快的
  • 原因: 栈内存, 通过 栈指针’来创建空间与释放空间,指针向下移动, 会创建新的内存, 向上移动, 会释放这些内存。这种方式速度特别快 , 仅次于PC寄存器,但是这种移动的方式, 必须要明确移动的大小与范围 ,明确大小与范围是为了方便指针的移动 , 这是一个对于数据存储的限制, 存储的数据大小是固定的 , 影响了程序的灵活性。 所以我们把更大部分的数据 存储到了堆内存中存储的是:基本数据类型的数据 以及 引用数据类型的引用。
  • 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
int a =10;
Student p = new Student ();
10存储在栈内存中 , 第二句代码创建的对象的引用也存在栈内存中

  • 堆存放的是类的对象,我们一开始创建的对象就是放在堆里面的。
  • Java是一个纯面向对象语言, 限制了对象的创建方式:所有类的对象都是通过new关键字创建new关键字, 是指告诉JVM, 需要明确的去创建一个新的对象 , 去开辟一块新的堆内存空间。
  • JVM只有一个堆区(heap)被所有线程共享,堆中不会存放基本类型和对象引用,只存放对象本身
  • 堆内存与栈内存不同, 优点在于我们创建对象时 , 不必关注堆内存中需要开辟多少存储空间 , 也不需要关注内存占用时长,堆内存中内存的释放是由GC(垃圾回收器)完成的垃圾回收器
  • 回收堆内存的规则:当栈内存中不存在此对象的引用时,则视其为垃圾 , 等待垃圾回收器回收 。
int a =10;
Student p = new Student ();
new Student ()的引用名称放在栈内存中,而创建的对象放在堆内存中,通过引用指向,相当于指针类似。

方法区

  • 又叫静态区,跟堆一样,被所有的线程共享
  • 方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
  • 运行时常量池都分配在 Java 虚拟机的方法区之中
  • 方法区存放的是:
    • 类信息
    • 静态的变量
    • 常量
    • 成员方法
    • 方法区中包含了一个特殊的区域 ( 常量池 )(存储的是使用static修饰的成员)

静态方法区,和类一起加载,可以被对象直接使用

先看下方一段代码
下面代码定义一个Student类,通过main方法创建这个对象并且进行赋值操作

public class Student {
    public String name;
    public int age;
    public void study(){
        System.out.println("在学习");
    }
    public Student(){
 
    }
    public static void main(String[] args) {
        Student student1 = new Student();
        student1.name  ="小明";
        student1.age = 13;
        student1.study();
        System.out.println(student1);
 
    }

下面是他的内存图
在这里插入图片描述
以上程序内存加载的执行步骤:
第1步 —— 加载Application启动类。这个类中有一个main()方法。同时类中存在一个常量池,专门存放一些常量,例如现在存在一个常量:小明。 // age中的13不是常量,是int类型的数字,也在该类中。
第2步 —— 执行main()方法。首先将main方法压入栈中,在栈内存中开辟一个空间。main()方法会存在于栈里,目前位于最底层。
第3步 —— 通过new实例化对象,会加载Student类,生成具体的对象Student1。Student1(引用变量名)即Student对象的引用存放在栈中,只是一个变量名。真正的对象存在于堆中,占用一个内存地址,假定为0x001(这个创建的对象是调用的无参构造的方法创建的,所以它的属性都为默认值)即name = null ,age = 0
第4步 —— 赋值:给堆中的对象赋值,它会先在常量池中找字符串都放在常量池中,如果有这直接赋值,如果没有则重新开辟一块区域新建一块(由于栈内存中存储了地址,地址指向堆内存里的对象,获取了对象Student的属性name、age,并赋值为"小明",13)
第5步 ——study()执行完毕,变量name与age立即释放,空间消失。但是main()函数空间仍存在,main中的变量name与age仍然存在,不受影响。而Student在堆中对应的地址,所指的age已经赋值=13,name= “小明”
第6步 —— 由于栈先进行后出一步步执行完最后执行main方法,执行结束。
注意:
类就相当于一个模板,new出来一个对象名在栈中,对象名指向的具体的对象存在于堆中,一开始模板为空
静态方法static是和类一起被加载的,所有的对象都可以去直接调用(由于这个特性就可以对static修饰的方法可以进行跨类进行调方法
方法区也属于堆。堆一般存放我们具体创建的对象,而栈里都是一些方法和变量的引用
我们同时new创建的都是Student,但是创建的是不一样的对象,是因为内存地址不同,引用的对象名也不同。例如:

        Student student1 = new Student();  student1 内存地址为0x001
        Student student2 = new Student();  student2 内存地址为0x002

三、三大特性

面向对象三大特征:继承、封装、多态。

  • 封装:将类的属性变为私有属性后,外界不能对类内部的私有属性进行访问,而通过set()赋值、get()访问的方式叫封装,解决了数据安全问题。public、protected、private等访问权限。
  • 继承:一个类可以继承另一个类,继承的类则具备了被继承类的所有成员变量及方法。解决了代码复用问题,同时让类之间产生了关系,为“多态”提供了前提。
  • 多态:一个引用在不同情况下的多种状态就是多态,解决了功能扩展问题。方法重写就是多态的一个实现,同一个方法不同的实现。

1.封装

在定义一个对象的特性的时候,有必要决定这些特性的可见性,即哪些特性对外部是可见的,哪些特性用于表示内部状态。
通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
使用private 修饰需要封装的成员变量
提供一个公开的方法设置或者访问私有的属性
设置 通过set方法,命名格式: set属性名(); 属性的首字母要大写
访问 通过get方法,命名格式: get属性名(); 属性的首字母要大写

  //对象不仅能再类中方法,还能在类的外部"直接"访问
          public class Student{
              public String name;
              public void println(){
                  System.out.println(this.name);
              }
          }
          public class Test{
              public static void main(String[] args){
                  Student s = new Student();
                  s.name = "tom";
              }
          }
 
      在类中一般不会把数据直接暴露在外部的,而使用private(私有)关键字把数据隐藏起来
      例如:
          public class Student{
              private String name;
          }

          public class Test{
              public static void main(String[] args){
                  Student s = new Student();
                  //编译报错,在类的外部不能直接访问类中的私有成员
                    s.name = "tom";
              }
          }
  如果在类的外部需要访问这些私有属性,那么可以在类中提供对于的get和set方法,以便让用户在类的外部可以间接的访问到私有属性
      例如:
          //set负责给属性赋值
          //get负责返回属性的值
          public class Student{
              private String name;
              public void setName(String name){
                    this.name = name;
              }
              public String getName(){
                  return this.name;
              }
          }

          public class Test{
              public static void main(String[] args){
                  Student s = new Student();
                  s.setName("tom");
                  System.out.println(s.getName());
              }
          }

2.继承

继承:生活中继承,如孩子继承父亲(人和人的继承)类和类之间的继承

继承:一个类可以继承另一个类,继承的类则具备了被继承类的所有成员变量及方法。解决了代码复用问题,同时让类之间产生了关系,为“多态”提供了前提。
父类和子类:有继承关系的两个类称为父类和子类。子类继承父类,父类派生子类。Java是单一继承。

子类继承父类后

  • 子类可以拥有父类的全部属性和自定义方法。(见程序1)
  • 子类可以重写(override)从父类继承下来的自定义方法,重写时可以使用super.父方法()调用父类中的自定义方法。子类的重写的方法必须和父类方法名字相同、参数相同、返回值相同。注意:如果子类重写了父类的方法,子类重写的方法的权限必须大于或等于父类被重写的方法。说明:重载(overload)
  • 子类可以新增自己独有的属性和方法。
  • 子类不能继承父类的构造方法。(因为子类有子类的构造方法,new的时候已经指明),虽然不能继承,但是能调用父类的构造方法,子类的构造方法在调用时,会自动的执行父类默认的构造方法(见程序3)。子类的构造方法中可以使用super(),手动指定父类中构造方法(见程序4)。

Java中的继承是单一继承,即一个父类可以有多个子类,一个子类只能有一个父类,不支持多继承。但java支持多重继承,即B extends A,C extends B 。
在程序2中可以再建立一个Teacher类,继承Person类,这时Docotor类和Teacher类共同的父类都是Person,Doctor类和Teacher类是兄弟关系。
在Java中只允许单一继承,C++可以多继承(继承多个父类)
super关键字

  • super.方法名();调用父类的自定义方法,写在子类的自定义方法中。
  • super();调用父类的默认构造构造方法,默认构造方法是隐式的。
  • super(* ,*):如果父类中不存在默认构造方法,则必须显式的调用有参的构造方法

无论是显式的还是隐式的,super()必须写在子类构造方法的第一行
方法重载(overload)和方法重写(override)有什么区别?

  • 位置:重载要求同类中的同名方法(举例Math.round()方法)
    重写要求不同类中的同名方法(不同类要求有继承关系的两个类)
  • 格式:重载要求方法名相同,参数个数或参数类型不同,与返回值无关
    重写要求方法名相同,参数个数、参数类型相同,返回值相同

【程序1】 举例说明继承的由来:

package com.tu.Demo01;
public class ExtendsDemo {
    public static void main(String[] args) {
        Student student = new Student();//调用带参构造方法,创建Student对象
        student.name = "tom";
        student.age = 16;
        student.study();
        Doctor doctor = new Doctor();//调用带参构造方法,创建Doctor对象
        doctor.name = "jerry";
        doctor.age = 28;
        doctor.operate();
    }
}
class Student {
    String name;
    int age;

    public void study() {
        System.out.println("学生" + this.name + "今年" + this.age + ":学习刻苦!");
    }
}
class Doctor {
    String name;
    int age;

    public void operate() {
        System.out.println("医生" + this.name + "今年" + this.age + ":医术精湛!");
    }
}

上面的程序,编译、执行都没有问题,但我们Student和Doctor类都具备name、age成员变量,出现了代码的重复使用,按照编程经验,重复的代码应该向上抽取,因此形成了继承。能否让学生继承医生、或医生继承学生呢?不行,因为医生有自己特有的operate()方法,这个方法学生不需要;学生拥有自己的特有study()方法,这个方法医生也不需要。我们需要向上抽取,如建立Person类,这个类具备Student和Doctor共有的属性或共有的方法,这样解决的了代码的复用问题。
【程序2】 程序1改进如下:初步认识继承

public class ExtendsDemo {
	public static void main(String[] args) {
		Student student = new Student();//调用带参构造方法,创建Student对象
		student.name = "tom";
		student.age = 16;
		student.study();
		
		Doctor doctor = new Doctor();//调用带参构造方法,创建Doctor对象
		doctor.name = "jerry";
		doctor.age = 28;
		doctor.operate();;
    }

}

class Person{ //将Student和Doctor类中共性的内容抽取出来,形成Person类
	 String name;
	 int age;
	
}

class Student extends Person{
//extends是java继承的关键字,Student继承Person,则Student成为Person的子类(也叫派生类),
//Person称为Student的父类(也叫基类、或超类)
	public void study(){
		System.out.println("学生"+this.name+"今年"+this.age+":学习刻苦!");
	}
}

class Doctor extends Person{
	public void operate(){
		System.out.println("医生"+this.name+"今年"+this.age+":医术精湛!");
	}
}

【程序3】 检验“子类可以拥有父类的全部属性和方法”

public class Test {
	public static void main(String[] args) {
		Doctor d1 = new Doctor(); //使用Doctor类默认的构造方法
这里实例化对象之后出现了父类的三个属性,子类一旦继承了父类就拥有了父类的全部属性与方法
		d1.name = "小刚";
		d1.age =18;
		d1.sex ="男";
		System.out.println("现有一名医生,信息如下:");
		d1.show(); //继承父类的方法
	}
}
class Person {
	//属性
	String name;
	int age;
	String sex;
	//构造方法
	Person(){
	}
	Person(String name){
	}
	Person(String name,int age,String sex){
	}
	void work(){
	}
	
	//输出三个属性
	void show(){
		System.out.println("名字:"+name);
		System.out.println("年龄:"+age);
		System.out.println("性别:"+sex);
	}
}
//创建子类doctor,继承父类Person
//一旦extends Person类,就拥有了Person全部的属性和方法
class Doctor extends Person {

}

【程序3】 的基础上,增加Doctor子类的属性和方法。

public class Doctor extends Person {
     //新增子类的属性
	String email;
}

注意:
上面是修改子类
下面是修改父类方法
//同时用父类的show方法调用子类的变量
//输出三个属性
	void show(){
		System.out.println("名字:"+name);
		System.out.println("年龄:"+age);
		System.out.println("性别:"+sex);
		System.out.println("邮箱:"+email); //这里这直接报错
	}

父类中的show()方法并不能满足子类输出的需求(因为父类的show()方法不能输出子类的email属性)!!!
解决办法:
在Test类中,增加输出: d1.email
问题得以解决,但是这样破坏了类的封装性,希望show()方法能解决的问题,绝不麻烦外界来做—
解决方案:在子类中,重写从父类继承下来的show()方法,
重写的要求:子类中重写父类方法的返回值、方法名、参数必须完全一致,重写类似于,从父亲那里继承了一辆车,车比较旧了,想重新喷个漆、换个脚垫……重写后,子类的对象再调用show()方法,就会调用子类重写后的show()方法,不会再调用父类的show()方法了,这样这个show即有了父类的方法也可以在父类的方法就行修改哦。
在子类中,重写show()方法:如下:

public class Doctor extends Person {
     //新增子类的属性
	String email;
	
	void show(){
		System.out.println("邮箱:"+email); //这里这直接报错
	}
}

此时,主方法调用,已经提示出调用的是子类的show()方法了,不再会调用父类的show()方法了。
此时,运行程序,又出现了问题,由于子类的show()方法只显示了子类的email属性的信息,父类的信息没有显示。现在想既保留父类show()方法的功能,又增加email属性的输出。可以把父类的三行显示粘贴到子类的show()方法里
如下:

public class Doctor extends Person {
     //新增子类的属性
	String email;
	
	void show(){
		System.out.println("名字:"+name);
		System.out.println("年龄:"+age);
		System.out.println("性别:"+sex);
		System.out.println("邮箱:"+email); 
	}
}

但这么写冗余,上面的三行代码既然已经被父方法写好了,直接拿过来用就好。
----解决方案:使用super关键字

public class Doctor extends Person {
     //新增子类的属性
	String email;
	
	void show(){
		super.show();  //调用父类中的show()方法
		System.out.println("邮箱:"+email);
}
}

后续会有@Override 来进行重写方法
【程序5】 强化重写:说明内存图,并说明重写的条件:如不能缩小父类的权限,方法名、参数、返回值相同

public class PersonDemo {

	public static void main(String[] args) {
		Doctor doctor = new Doctor();
		doctor.show();//Doctor重写了父类Person的show方法,输出结果为"Doctor show.."
		
		Student student = new Student ();
		/**
		 *   student没有重写父类Person的show方法,则调用的是父类的show()方法,
		     输出结果为"Person show.."。
		     原因是子类在加载前要先加载父类,因为子类继承了父类的全部成员变量及成员属性,
		     不加载父类就无法使用父类的相关数据,且子类中持有父类的引用,这个引用关键字是super
		 */
		student.show();
			}

}
class Person{
	void show(){
		System.out.println("person show...");
	}
	
}

class Doctor extends Person{
	void show(){//注,形成了重写,但子类的权限如果改成private的会编译不通过,因为缩小了父类的权限
		System.out.println("Doctor show....");
	}
}
class student extends Person{// student类什么都没有,但继承了父类Person的show()方法
	
}

说明:如子类与父类有相同的方法,且方法名,参数类型、参数个数相同、返回值也相同,则形成了重写,重写后,子类执行的是自己的方法!如没有形成重写,调用的才是父类的方法,原因是子类中持有父类的引用super。
原理图如下:
在这里插入图片描述
【程序6】 说明,子类构造方法自动调用父类的构造方法。

public class PersonDemo {

	public static void main(String[] args) {
		/**
		 * 输出结果:
		 * person contructor run...
         * doctor contructor run...
         * 依据:子类的构造方法,有一个隐式的super(),即在执行自身构造方法代码前,先调用父类的构造方法。
         * 原因是子类继承父类,父类要初始化其成员变量;否则子类继承的成员变量(尤其是私有的),没有办法初始化
         */
		Doctor doctor = new Doctor();
	}

}
class Person{
	Person(){
		System.out.println("person contructor run...");
	}
	
}

class Doctor extends Person{
	Doctor(){
		System.out.println("doctor contructor run...");
	}
}

在这里插入图片描述
【程序7】创建一个“学生”类,本程序主要的关注点是子类构造方法自动调用父类的构造方法。

public class Test {
	public static void main(String[] args) {
		Student b1 = new Student ();
		Student b2 = new Student ("坏人");
		
		//输出属性
		System.out.println(b1.name+","+b1.weight+","+b1.age+","+b1.decription);
		System.out.println("------------------------------");
		System.out.println(b2.name+","+b2.weight+","+b2.age+","+b2.decription);	}
}

class Person {
   String name;
   double weight;
   int age;
   
   //构造方法:默认的构造方法
   Person (){
	   name = "tom";
	   weight = 130.5;
	   age = 20;
   }
   
   //构造方法:带三个参数的构造方法
   Person (String name,double weight,int age){
	   this.name =  name;
	   this.weight = weight;
	   this.age = age;
   }
}
class Student extends Person{
	String decription;
	
	Student(){ // Student目前有4个属性
		//自动的调用了父类默认的构造方法
		decription = "好人";
	}
	
	Student(String s){
		//自动的调用了父类默认的构造方法
		decription = s;
	}
}

现象解释:Student b1 = new Student ();实例化b1时,调用的是Student类默认的构造方法Student (),默认的构造方法,给属性decription赋初始值“好人”,但是再此之前,还做了一件重要的事情,既:自动的调用父类默认的构造方法,只有调用父类默认的构造方法才能把人名赋值为“tom”,体重“130.5”,年龄:“20”。 Student b2= new Student (“坏人”)调用的是有一个String参数的构造方法;
【程序8】 添加Student类的构造方法,演示“子类的构造方法中可以使用super(),手动指定父类中构造方法”。

public class Test {
	public static void main(String[] args) {
		Student b1 = new Student ();
		Student b2 = new Student ("坏人");
		
		//输出属性
		System.out.println(b1.name+","+b1.weight+","+b1.age+","+b1.decription);
		System.out.println("------------------------------");
		System.out.println(b2.name+","+b2.weight+","+b2.age+","+b2.decription);	
		
		Student b3 = new Student("jerry",90.5,21,"漂亮女孩");
		System.out.println("------------------------------");
		System.out.println(b3.name+","+b3.weight+","+b3.age+","+b3.decription);
	
	}
	
}

class Person {
   String name;
   double weight;
   int age;
   
   //构造方法:默认的构造方法
   Person (){
	   name = "tom";
	   weight = 130.5;
	   age = 20;
   }
   
   //构造方法:带三个参数的构造方法
   Person (String name,double weight,int age){
	   this.name =  name;
	   this.weight = weight;
	   this.age = age;
   }
}
class Student extends Person{
	String decription;
	
	Student(){ // Student目前有4个属性
		//自动的调用了父类默认的构造方法
		decription = "好人";
	}
	
	Student(String s){
		//自动的调用了父类默认的构造方法
		decription = s;
	}
	Student(String bName,double bWeight,int age,String s){
		//手动的指定父类的构造方法
		super(bName,bWeight,age);
		decription = s;
	}

}

总结:子类的构造方法执行时一定会先调用父类的构造方法(从而保证对继承的成员变量初始化),如果子类的构造方法没有指定哪个构造方法,系统会自动的调用父类的默认构造方法(前提是父类存在默认的构造方法,否则编译出错)如果子类的构造方法手动的写了super();就要看子类构造方法中super()方法参数的形式,与父类构造方法哪个参数形式一样。一样的那个父类的构造方法就是被调用的。

3.多态

多态定义:一个引用在不同情况下的多种状态就是多态。方法重载就是多态的一个实现,同一个方法不同的实现
首先看多态存在的必要条件
继承
重写 (子类重写父类方法,签名必须相同)
父类引用指向子类对象:如Animal为父类,Cat为猫类。父类引用指向子类对象 Animal cat=new Cat();

多态的优势:提高代码的复用性(通过继承来实现),提高代码的扩展性(通过面向父类型传参来实现)。
多态的弊端:父类引用可以访问被子类重写的方法,但不能直接访问子类的特有功能。(可以通过向下转型去访问)
多态其实就是向上转型。子类转为父类

多态特征
1.父类有,子类没有调用父类
2.父类有,子类重写调用子类
3.父类无,子类有编译失败
4.父类只能调用自己的成员变量
多态优点:
1.如修改程序,只需修改右侧即可;左侧引用类型、引用名称无需改变
2.如有参数传递,形参指定为父类类型,则实参可以为任意子类类型,提高了程序的扩展性。

向上转型(多态)

父类引用指向子类对象 Animal cat=new Cat();
【程序9】多态形式访问成员变量,编译运行看左边(这里的左边指,new对象的语句中等号的左边)

package com.tu.Java基础.demo;

public class MyTest {
    public static void main(String[] args) {
        //以多态的形式来访问成员变量 使用的还是父类的变量
        //编译看左边,运行也看左边
        Fu f = new Zi();//多态 父类引用指向子类空间
        System.out.println(f.num);//访问的是父类变量中的num
    }
}
class Fu{
    int num = 100;
    public void show(){
        System.out.println("父类的show方法");
    }
}
class Zi extends Fu{
    int num=10;
    @Override
    public void show() {
        System.out.println("子类重写过后的show方法");
    }
}
//运行结果:100

【程序10】多态形式访问成员方法,编译看左边,运行看右边(这里的右边指,new对象的语句中等号的右边)

package com.tu.Java基础.demo;

public class MultiStateTest {
    public static void main(String[] args) {
        Animal cat = new Cat();
        MyUtils.eatTest(cat);
        Animal dog = new Dog();
        MyUtils.eatTest(dog);
        Animal sheep = new Sheep();
        //使用多态传入sheep(属于animal类),先进入父类,再调用子类重写后的方法(运行看右边)
        MyUtils.eatTest(sheep);
    }
}
class Animal{
    public void eat(){
        System.out.println("吃饭");
    }
}
class Cat extends Animal{
    @Override//检查方法重写 快捷键ctrl+O
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }
}
class Sheep extends Animal{
    @Override
    public void eat() {
        System.out.println("羊吃草");
    }
}
class MyUtils{
    private MyUtils() {//将工具包私有化,让外部不能使用空参构造创建对象,也不需要
    }
    public static void eatTest(Animal animal){//用static修饰该方法,可以不创建对象,通过类名直接调用调用该方法。
        animal.eat();//根据传入的参数,执行方法重写机制
    }
}
//运行结果:猫吃鱼
//        狗吃肉
//        羊吃草

【程序11】 多态体现 编译看左边,运行看右边

public class PersonDemo {

	public static void main(String[] args) {
		/**
		 * 输出结果:
		 */
		Student student1 = new Student();
		//定义对象的方式:左侧为: 引用类型 引用名称  = new 引用类型的构造方法()
		
		Person student2 = new Student();
		//多态体现:左侧为:父类类型 引用名称  = new 子类类型的构造方法()
		/**
		 * (1)如果子类重写了父类的show方法,输出结果为student run... 前提是;
		 * (2)如果子类没有重写父类的show()方法,则输出结果为person run...
		 * (3)如果子类有show()方法,父类没有show()方法,则编译失败!原因是引用属于父类类型,而父类类型没有show方法,编译无法通过
		 */
		student2.show();
	}
}

class Person{
	void show(){
		System.out.println("person run...");
	}
	
}

class Student extends Person{
	void show(){
		System.out.println("student run...");
	}
}

【程序12】 多态复杂分析

class A {//10.A类虽然有show方法,但是C类对show方法进行了覆盖,因此执行C.show
        public void show() {//3.子类B没有show方法,因此执行A.show
            show2();//4./12.执行show2   
        }
        public void show2() {//5.由于子类B重写了show2,因此不执行该方法 13.子类C重写了show2,因此执行C.show2
            System.out.println("我");
        }
    }
    class B extends A {//9.父类B没有show方法,但是继承的A类有show方法
        public void show2() {//6.执行B.show2,打印爱
            System.out.println("爱");
        }
    }
    class C extends B {
        public void show() {
            super.show();//11.执行父类show方法,即执行父类B继承的父类A的show方法
        }
        public void show2() {//13.执行C.show2,打印你
            System.out.println("你");
        }
    }
    public class DuoTaiTest4 {
        public static void main(String[] args) {
            A a = new B();//1.父类a引用指向子类B
            a.show();//2.调用父类a的show
            B b = new C();//7.父类b引用执行子类C
            b.show();//8.调用父类b的show
        }
    }
//运行结果:爱 你

【程序13】 多态的优点,代码体现:

public class PersonDemo {

	public static void main(String[] args) {
		/**
		 * 如果右侧调用的是Worker子类的构造方法,执行的就是Worker的show方法
		 * 如果右侧调用的是Doctor子类的构造方法,执行的就是Doctor的show方法
		 * 多态:父类的引用可以指向任何一个子类对象,
		 * 优点:
		 * 1.如修改程序,只需修改右侧即可;左侧引用类型、引用名称无需改变
		 * 2.如有参数传递,形参指定为父类类型,则实参可以为任意子类类型,提高了程序的扩展性
		 * 
		 */
		Person person = new Doctor();
		person.show();//执行结果为"Doctor run..."
		
		/**
		 * 给worker对象,则p.show()输出为"Worker run...";
		 * 给Doctor对象,则p.show()输出为"Doctor run...";
		 */
		printShow(new Worker());//
		
		
	}
	
	public static void printShow(Person p){//形参定义为父类类型,则可以接收任意子类类型
		p.show();
	}
}

class Person{
	void show(){
		System.out.println("person run...");
	}
	
}

class Doctor extends Person{
	void show(){
		System.out.println("Doctor run...");
	}
	
}

class student  extends Person{
	void show(){
		System.out.println("student run...");
	}
	
}

向下转型

父类只能访问子类的重写方法,如果想要父类能够访问子类的特有方法,需要进行向下转型
向下转型可理解为父类强制转换为子类,语法为 子类名 对象名=(子类名) 父类对象。
在这里插入图片描述
【程序14】向下转型

package com.tu.Java基础.demo;

public class MultiStateTest01 {
    public static void main(String[] args) {
        Humanbeing s= new Student1();//父类引用指向子类,其实就是向上转型,即多态
        s.eat();//父类可以访问父类的方法,及子类的重写方法
        s.sleep();//父类可以直接访问子类的重写方法
        Student1 s1=(Student1)s;//父类引用指向子类
        s1.learning();//向下转型后,父类才能调用子类的特有方法
        Humanbeing t=new Techer();
        t.eat();//父类可以直接访问子类的重写方法
        t.sleep();//父类可以直接访问子类的重写方法
        ((Techer) t).tech();//省略写法,
        // ((Teacher)t)表示向下转型。向下转型后,父类才能调用子类的特有办法
    }
}
class Humanbeing{
    public void sleep(){
        System.out.println("go to sleep");
    }
    public void eat(){
        System.out.println("eat foods");
    }
}
class Techer extends Humanbeing{
    @Override
    public void sleep() {
        System.out.println("sleep with his wife");
    }
    public void tech(){
        System.out.println("teaching");
    }
}
class Student1 extends Humanbeing{
    @Override
    public void eat() {
        System.out.println("eat hot-pot");
    }
    public void learning(){
        System.out.println("study");
    }
}
//运行结果:eat hot-pot
//        go to sleep
//        study
//        eat foods
//        sleep with his wife
//        teaching

总结:多态发生的前提是“继承”、“重写”。
父类有,子类没有调用父类
父类有,子类重写调用子类
父类无,子类有编译失败
父类只能调用自己的成员变量
多态优点总结:
1.如修改程序,只需修改右侧即可;左侧引用类型、引用名称无需改变
2.如有参数传递,形参指定为父类类型,则实参可以为任意子类类型,提高了程序的扩展性

4.final关键字

1)修饰属性:最终属性,不能被修改(赋值)声明的同时必须被初始化,使用的过程中不能被修改值
2)修饰方法:最终方法,不能被子类重写
3)修饰类:最终类,不允许被继承
4)修饰对象:对象的内容能够改变,对象的地址无法修改

5.抽象类和接口

抽象类概述:一个没有方法体的方法应该定义为为抽象方法,而类中如果有抽象方法该类必须定义为抽象类。
抽象类和抽象方法必须用abstract关键字修饰
抽象类格式:权限修饰符 abstract class 类名{}
抽象方法格式:权限修饰符 abstract 返回值类型 方法名();
使用abstract关键字修饰的类就是抽象类,抽象类不能创建对象,主要是用来给子类继承的
普通类和抽象类的区别:

  • 普通类可以创建对象,抽象类不可以创建对象
  • 普通类没有抽象方法,抽象类有抽象方法

抽象方法:
抽象方法是没有方法体的,通过abstract进行修饰的方法就是抽象方法
抽象方法要求必须强制重写子类的重写
注意事项

  1. 抽象类不能被创建对象,只能作为父类,被子类继承
  2. 抽象类不能直接创建对象,可以采用多态间接的去实例化抽象类
  3. 抽象类中可以有构造方法,但是抽象类不能直接通过构造方法进行实例化,需要使用多态对抽象类进行实例化。抽象类的构造方法用于子类访问父类数据时的初始化
  4. 抽象类中可以没有抽象方法,但是抽象方法必须在抽象类中
  5. 子类继承抽象类,必须重写抽象类中的所有方法,否则子类必须也是一个抽象类
  6. 抽象方法必须被重写,否则会报错(要么将子类也定义为抽象类)
    【程序】
package com.tu.Java基础;

public class MyTest {
    public static void main(String[] args) {
        Animal an=new Cat();//抽象类不能直接创建对象,可以采用多态间接的去实例化抽象类 多态
        an.eat();
        an.sleep();
        an.show();
    }
}
abstract class Animal {
    public Animal() { //抽象类可以有“构造方法”,为成员变量初始化使用
        System.out.println("父类的构造方法执行了");
    }
    public abstract void eat(); //抽象方法,此方法没有具体方法实现
    public abstract void sleep();
    //抽象类中既可以有抽象方法,也可以非抽象方法
    public void show(){
        System.out.println("这是父类的一个非抽象方法");
    }
}
class Cat extends Animal{
    @Override
    public void eat() {//抽象类的子类必须重写所有抽象方法
        System.out.println("猫爱吃鱼");
    }
    @Override
    public void sleep() {
        System.out.println("猫白天睡觉");
    }
}
父类的构造方法执行了
猫爱吃鱼
猫白天睡觉
这是父类的一个非抽象方法

【程序】抽象类可以有“构造方法”,为成员变量初始化使用

package com.tu.Java基础;

abstract class Father {
 public Father(){
        System.out.println("抽象类的无参构造");
    }
    public Father(String param){
        System.out.println("抽象类的构造参数" + param);
    }
}

class Son extends Father {
    public Son(String params) {
        super(params);
        System.out.println("子类的构造参数" + params);
    }

}

public class Test {
    public static void main(String[] args) {
        Father father = new Son("aaaa");

    }
}

抽象类的面试题

一个类如果没有抽象方法,可不可以定义为抽象类?如果可以,有什么意义?

答:抽象类中可以没有抽象方法。一个抽象类中没有抽象方法的意义在于该类不用也不能被实例化。比如Java中的有些工具类,虽然被定义为抽象类,但是类中的方法都用static修饰,可以直接通过类名调用,没必要进行实例化,用abstract修饰类名可以防止由该类生成实例。

abstract不能和关键字private/final/static并存,为什么?

答:被abstract修饰的方法需要被子类重写,而被private修饰的类或方法只能在本类中被访问不能被继承,因此冲突。被final修饰的类不能被继承,方法不能被重写,因此冲突。static静态方法存放在方法区的静态区,无需实例化,可用类名调用。而被abstract修饰的方法需要通过多态进行实例化,如果两者并存,通过类名调用抽象的静态方法将是无意义的。

6.接口

1.接口不能直接实例化,需要通过多态的方式进行实例化。
2.接口中的子类可以是抽象类,但这样做的意义不大。接口中的子类也可以是具体类,具类要重写接口中的所有抽象方法。
3.接口用关键字interface表示。
4.类实现接口用implements表示

interface 接口名{}
class 类名 implements 接口名{}
类实现多个接口可以表示为(接口可以多继承):
class 类名 implements 接口名1,接口名2{}

Java中有多继承吗?

答:分情况,接口与接口之间可以实现多继承,但是类与类之间不可以多继承,只能单继承或者多层继承。

接口成员的特点

interface interface{
    //没有构造方法
    public static final int NUM=100;//成员变量只能是常量,默认修饰符为public static final
    public abstract void test1(){}//JDK1.7之前成员方法只能是抽象方法,默认修饰符为public abstract
    public default void test2(){}//JDK1.8之后成员方法可以具体实现,但是该方法必须被default修饰。
}

总结:
1.成员变量只能是常量,并且是静态的。
2.没有构造方法。
3.成员方法只能是抽象方法(JDK1.8之后有默认方法)

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Animal animal1=cat;//抽象父类引用指向子类,实现实例化
        animal1.eat();
        Cat c=(Cat)animal1;//向下转型
        Calc ca1=c;//接口引用指向子类,实现实例化
        ca1.calc();
        Jump j=c;//接口引用指向子类,实现实例化
        j.jumpFireCircle();
        Dog dog = new Dog();
        Animal animal2=dog;
        animal2.eat();
        Dog d=(Dog)animal2;//向下转型
        d.guardHome();
        Calc ca2=d;//接口引用指向子类,实现实例化
        ca2.calc();
    }
}
abstract class Animal {
    //成员变量
    private String name;
    private int age;
    //空参构造方法
    public Animal() {
    }
    //有参构造方法
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public abstract void eat();
}
interface Calc {
    public abstract void calc();
}
interface Jump {
    public abstract void jumpFireCircle();
}
class Dog extends Animal implements Calc{//类实现单接口
    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }
    @Override
    public void calc() {
        System.out.println("狗学会了10以内的加减法");
    }
    public void guardHome(){
        System.out.println("狗的个性本领是守家");
    }
}
class Cat extends Animal implements Calc,Jump {//类实现多接口
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
    @Override
    public void calc() {
        System.out.println("猫学会了10以内的加减法");
    }
    @Override
    public void jumpFireCircle() {
        System.out.println("猫学会了跳火圈");
    }
}

接口和抽象类的语法区别
1)接口不能有构造方法,抽象类可以有。
2)接口不能有方法体,抽象类可以有。
3)接口不能有静态方法,抽象类可以有。
4)在接口中凡是变量必须是public static final,而在抽象类中没有要求。
类与类,类与接口,接口与接口的关系
类与类:继承关系,只能单继承,可以多层继承。
类与接口:实现关系,可以单实现,也可以多实现。并且还可以在继承一个类的同时实现多个接口。
接口与接口:继承关系,可以单继承,也可以多继承。

总结

提示:持续更新中。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值