[Java基础]2. Java面向对象

[Java基础]2. Java面向对象

一、Java中的类

定义类的基本格式:

[修饰符] class 类名
{
    //零到多个构造器...
	//零到多个成员变量...
	//零到多个方法...
	//零到多个初始化块...
}

修饰符 可以为 publicfinalabstract 或者不写
类名:大驼峰命名

(一)构造器

构造器也叫构造方法或者构造函数,构造器与类名相同,没有返回值,连void都不能写。
构造器定义格式:

[修饰符] 类名 (形参列表)
{
	//构造器方法体代码
}
  • 名称与类名相同,没有返回值,不能写void
  • 如果类中没有手动添加构造器,编译器会默认再添加一个无参构造器
  • 如果手动添加了构造器(无论什么形式),默认构造器就会消失
  • 构造器的重载:形参列表不同。

(二)成员变量

成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。

[修饰符] type 成员变量名称 [= 默认值];
  • 修饰符: (public protected private)三选一、 staticfinal

(三)方法

[修饰符] 方法的返回值类型 方法名称 (形参列表)
{
	//方法体代码
	[return 返回值;]
}
  • 修饰符 (public protected private)三选一、 staticfinalsynchronizenative

(四)初始化代码块

二、Java中的对象

(一)创建对象

对象是根据类创建的。在Java中,使用关键字new来创建一个新的对象。创建对象需要以下三步:

  1. 声明:声明一个对象,包括对象名称和对象类型。
  2. 实例化:使用关键字new来创建一个对象,只是为对象在内存中开辟空间。
  3. 初始化:使用new创建对象时,会调用构造方法初始化对象,对象中的值赋初始值。

格式:

classTpye 对象名称 = new classTpye();

对象的引用意思是“定义一个变量,这个变量指向的是一个对象“
从对象引用出发:一个对象引用可以指向零个或一个对象
从对象出发:一个对象可以被一个或多个对象引用

(二)访问实例变量和方法

/* 访问类中的变量 */
objName.variableName;
/* 访问类中的方法 */
objName.methodName();

三、成员变量与局部变量

在这里插入图片描述
静态变量:static修饰的成员变量叫做静态变量(类变量),静态变量是属于这个类,而不是属于是对象。
实例变量:没有被static修饰的成员变量叫做,实例变量是属于这个类的实例对象。

static是不允许用来修饰局部变量

四、方法的重载

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。

重载规则:

  • 被重载的方法必须形参列表不一样(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 返回值类型不能作为重载函数的区分标准。

五、Java封装

封装的优点

  • 良好的封装能够减少耦合。
  • 类内部的结构可以自由修改。
  • 可以对成员变量进行更精确的控制。
  • 隐藏信息,实现细节。

实现Java封装的步骤:

  1. 修改属性的可见性来限制对属性的访问(一般限制为private
  2. 对每个值属性提供对外的公共方法访问,也就是创建gettersetter方法(将实例变量的首字母大写,在前面添加get或者set, 变成getter 和 setter方法名)
public class Person {
	private String name;
	private int age;
	//===getter setter方法
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		if (age<0 || age>120) {
			System.out.println("年龄不符合要求");
			return;
		} else {
			this.age = age;
		}
	}
}

六、Java继承

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法。
父类又称为超类或者基类。而子类又称为派生类
在这里插入图片描述
子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super 关键字。
子父类中成员方法如果方法名相同返回值类型、参数列表却不相同,则优先在子类查找,没找到就去父类。
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。

继承的构造方法

class liGang4 {
     // 父类的无参构造方法。
     public liGang4(){
         System.out.println("父类构造方法执行了。。。");
     }
 }
 class LiXiaoGang4 extends liGang4 {
     // 子类的无参构造方法。
    public LiXiaoGang4(){
        System.out.println("子类构造方法执行了====");
    }
 }
 public class ConstructionDemo {
     public static void main(String[] args) {
         // 创建子类对象
         LiXiaoGang4 z = new LiXiaoGang4();
     }
 }
输出:父类构造方法执行了。。。
子类构造方法执行了====

在main方法中实例化了子类对象,接着会去执行子类的默认构造器初始化,这个时候在构造方法中默认会在第一句代码中添加super(),表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。注意 super() 不写也默认存在,而且只能是在子类第一行代码中。

继承的多态性支持
父类做为参数类型,直接传子类的参数进去是可以的,反过来,子类做为参数类型,传父类的参数进去,就需要强制类型转换。

七、Java多态

多态前提条件:

  1. 继承或者实现【二选一】
  2. 方法的重写【意义体现】:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  3. 父类引用指向子类对象(也可以说向上转型)【体现在格式上】

多态体现的格式: 父类/父接口类型 变量名 = new 子类对象; 变量名.方法名();

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误,如果有,执行的是子类重写后的方法

动态绑定与静态绑定

绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来
静态绑定(前期绑定):可以理解为程序编译期的绑定;java当中的方法有finalstaticprivate(不会被继承) 和构造方法是前期绑定。
动态绑定(后期绑定):编译器在编译阶段不知道要调用哪个方法,在运行时根据具体对象的类型进行绑定。

静态、动态绑定本质区别:

  1. 静态绑定是发生在编译阶段;而动态绑定是在运行阶段;
  2. 静态绑定使用的是类信息,而动态绑定使用的是对象信息
  3. 重载方法(overloaded methods)使用的是静态绑定,而重写方法(overridden methods)使用的是动态绑定
向上转型与向下转型

当父类引用指向一个子类对象时,便是向上转型。Father f= new Son()
向下转型:父类类型向子类类型向下转换的过程,需要强制类型转换。Son s =(Son) father

为什么要向下转型:因为不能调用子类拥有,而父类没有的方法,因此想要调用子类特有的方法,必须做向下转型。

变量名 instanceof 数据类型:变量属于该数据类型,返回true,否则返回false。

//  f1 引用指向一个Son对象
Father f1 = new Son();   // 这就叫 upcasting (向上转型)
// f1 还是指向 Son对象
Son s1 = (Son)f1;   // 这就叫 downcasting (向下转型)
--------------------------
// f1现在指向father对象
Father f2 = new Father();
Son s2 = (Son)f2;       // 出错,子类引用不能指向父类对象

例题

package Polymorphic;

class EatKaoShanYao {
    EatKaoShanYao () {
        System.out.println("吃烤山药之前...");
        eat();
        System.out.println("吃烤山药之后(熊孩子懵逼中)....");
    }
    public void eat() {
        System.out.println("7岁半就喜欢吃烤山药");
    }
}
public class KaoShanYao extends EatKaoShanYao {
    private String Weight = "110斤";
    public KaoShanYao(String Weight) {
        this.Weight = Weight;
        System.out.println("熊孩子的体重:" + this.Weight);
    }

    public void eat() { // 子类覆盖父类方法
        System.out.println("熊孩子吃烤山药之前的体重是:" + this.Weight);
    }

    //Main方法
    public static void main(String[] args) {
           EatKaoShanYaok = new KaoShanYao("250斤");
                      
    }
}
 运行结果:
                吃烤山药之前...
                熊孩子吃烤山药之前的体重是:null
                吃烤山药之后(熊孩子懵逼中)....
                熊孩子的体重:250斤

因为在创建子类对象时,会先去调用父类的构造器,而父类构造器中又调用了被子类覆盖的多态方法,由于父类并不清楚子类对象中的属性值是什么(先初始化父类的时候还没开始初始化子类),于是把String类型的属性暂时初始化为默认值null,然后再调用子类的构造器(这个时子类构造器已经初始Weight属性,所以子类构造器知道熊孩子的体重Weight是250)。

package Polymorphic;
//爷爷类
class Ye {
    public String show(Sun obj) {
        return ("Ye and Sun");
    }
    public String show(Ye obj) {
        return ("Ye and Ye");
    }
}
//爸爸类
class Fu extends Ye {
    public String show(Fu obj) {
        return ("Fu and Fu");
    }

    public String show(Ye obj) {
        return ("Fu and Ye");
    }
}
//儿子类
class Zi extends Fu {
}
//孙子类
class Sun extends Fu {
}
public class PolymorphicTest {
    public static void main(String[] args) {
        Ye y = new Ye();
        Ye y2 = new Fu(); //向上
        Fu f = new Fu();
        Zi z = new Zi();
        Sun s = new Sun();
        System.out.println("第一题 " + y.show(f));
        System.out.println("第二题 " + y.show(z));
        System.out.println("第三题 " + y.show(s));
        System.out.println("第四题 " + y2.show(f));  //到这里挂了???
        System.out.println("第五题 " + y2.show(z));
        System.out.println("第六题 " + y2.show(s));
        System.out.println("第七题 " + f.show(f));
        System.out.println("第八题 " + f.show(z));
        System.out.println("第九题 " + f.show(s));
    }
打印结果:
    第一题 Ye and Ye
	第二题 Ye and Ye
	第三题 Ye and Sun
	第四题 Fu and Ye
	第五题 Fu and Ye
	第六题 Ye and Sun
	第七题 Fu and Fu
	第八题 Fu and Fu
	第九题 Ye and Sun
}

当父类对象引用变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,引用变量类型决定可调用的方法。
首先会先去可调用的方法的父类中寻找,找到了就执行子类中覆盖的该方法,就算子类中有现成的该方法,它同样也会去父类中寻找,早到后未必执行子类中有现成的方法,而是执行重写在父类中找到的方法的子类方法(这里的子类也就是最后决定调用的类方法)。

向上转型的简单理解:子类类型默认向父类类型向上转换的过程

Father f = new Son()Father f = (Father)new Son()f是父类对象引用变量,子类对象指的就是new Son()
f引用new Son()时,Son决定了最后调用的成员方法在Son类中,Father决定可调用Father中的方法,即new后面的类型决定了最后调用谁的方法。前提必须是向上转型。

第四题,首先Ye y2 = new Fu(); 向上转型了,所以首先会去Fu类的父类Ye类中找show(f)方法,找到了show(Ye obj)方法,之后回到Fu类中看是否有show(Ye obj)重写方法,发现Fu类有show(Ye obj)方法(重写),所以最后执行了"Fu and Ye"
第五题,fzYe类中找的都是show(Ye obj)方法,所以,最终第四题和第五题结果一致
第六题,y2.show(s),到Ye类中找到show(Sun obj),之后在子类中看有没有重写,发现并没有show(Sun obj)重写方法,但是由于子类继承了父类,因此子类Fu中默认有着父类Ye的方法show(Sun obj)

七、方法的重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。

重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。

方法的重写规则:

  • 参数列表与返回类型必须完全与被重写方法的相同;
  • 子类方法覆盖父类方法时,必须要保证子类权限 >= 父类权限。
  • 方法重写时,子类的返回值类型必须要 <= 父类的返回值类型。
  • 方法重写时,子类抛出的异常类型要 <= 父类抛出的异常类型。
  • 父类的成员方法只能被它的子类重写。
  • 声明为final的方法不能被重写。
  • 声明为static的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有除了声明为private和final的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
  • 构造方法不能被重写(因为构造方法的名字是与类名一致)
  • 如果不能继承一个方法,则不能重写这个方法。

八、super 与 this 关键字

父类空间优先于子类对象产生

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员

super关键字:用来引用当前对象的父类。
this关键字:代表当前对象的引用(谁调用就代表谁)。

总结:子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

九、重写与重载之间的区别

区别点 重载方法 重写方法
参数列表 必须修改 一定不能修改
返回类型 可以修改 一定不能修改
异常 可以修改 可以减少或删除,一定不能抛出新的或者更广的异常
访问权限 可以修改 一定不能做更严格的限制(可以降低限制)
  • 方法重载(Overloading)是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同。
  • 方法重写(Overriding)是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法。
  • 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

十、初始化块与执行顺序

初始化块根据是否使用static修饰分为两类:

  1. 不使用static修饰的是初始化块
  2. 使用static修饰的是静态初始化块
  • 初始化块
    • 初始化块相当于是对构造器的补充,用于创建对象时给对象的初始化,在构造器之前执行
    • 如果一段初始化代码对所有构造器完全相同,且无需接收参数,那就可以将其提取到非静态初始化代码块中
    • 实际上,经过编译后,非静态块已经添加到构造器中,且位于所有构造器代码的前面
  • 静态初始化块
    • 静态初始化块用static修饰,又叫类初始化块
    • 静态初始化块负责对类进行初始化,因此类初始化块是在类初始化阶段就执行
    • 静态块跟静态方法一样,不能访问非静态成员
    • 因为静态块是在类的初始化阶段完成的,因此在创建某个类的第二个对象时,该类的静态块就不会执行了

(一)单个类中初始化块、静态初始化块、构造器的执行顺序

在单个类中,静态初始化块(多个时从上往下执行),初始化块(多个时从上往下执行),构造器

class ClassDemo1{
	public ClassDemo1() {
		System.out.println("ClassDemo1构造器");
	}
	{
		System.out.println("ClassDemo1 初始化块1");
	}
	{
		System.out.println("ClassDemo1 初始化块2");
	}
	{
		System.out.println("ClassDemo1 初始化块3");
	}
	static {
		System.out.println("ClassDemo1 静态初始化块1");
	}
	static {
		System.out.println("ClassDemo1 静态初始化块2");
	}
}
public class Demo1 {
	public static void main(String[] args) {
		ClassDemo1 cDemo1=new ClassDemo1();
	}
}
输出
ClassDemo1 静态初始化块1
ClassDemo1 静态初始化块2
ClassDemo1 初始化块1
ClassDemo1 初始化块2
ClassDemo1 初始化块3
ClassDemo1构造器

(二)多个类的继承中初始化块、静态初始化块、构造器的执行顺序

在继承中,先后执行父类A的静态块,父类B的静态块,最后子类的静态块,然后再执行父类A的非静态块和构造器,然后是B类的非静态块和构造器,最后执行子类的非静态块和构造器

//‐‐‐‐BaseOne.java
public class BaseOne {
	static {
		System.out.println("BaseOne静态初始化块");
	}
	{
		System.out.println("BaseOne初始化块");
	}
	public BaseOne(){
		System.out.println("BaseOne构造器");
	}
}

//‐‐‐‐BaseTwo.java
public class BaseTwo extends BaseOne {
	static {
		System.out.println("BaseTwo静态初始化块");
	}
	{
		System.out.println("BaseTwo初始化块");
	}
	public BaseTwo(){
		System.out.println("BaseTwo构造器");
	}
}

//‐‐‐‐ BaseThree.java
public class BaseThree extends BaseTwo {
	static {
		System.out.println("BaseThree静态初始化块");
	}
	{
		System.out.println("BaseThree初始化块");
	}
	public BaseThree(){
		System.out.println("BaseThree构造器");
	}
}

//‐‐‐‐Demo2.java
public class Demo2 {
	public static void main(String[] args) {
		BaseThree baseThree=new BaseThree();
		System.out.println("‐‐>第二次创建BaseThree对象<‐‐");
		BaseThree baseThree2=new BaseThree();
	}
}
BaseOne静态初始化块
BaseTwo静态初始化块
BaseThree静态初始化块
BaseOne初始化块
BaseOne构造器
BaseTwo初始化块
BaseTwo构造器
BaseThree初始化块
BaseThree构造器

‐‐>第二次创建BaseThree对象<‐‐
BaseOne初始化块
BaseOne构造器
BaseTwo初始化块
BaseTwo构造器
BaseThree初始化块
BaseThree构造器

十一、Java单例模式

适用场景:

  1. 需要生成唯一序列的环境
  2. 需要频繁实例化然后销毁的对象。
  3. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  4. 方便资源相互通信的环境

Java 单例模式有五种实现方式:

(一)饿汉式

特点:线程安全,调用效率高,但是不能延时加载

public class SingletonOne {
	//实例化这个类
	private static final SingletonOne INSTANCE = new SingletonOne();
	//1、隐藏构造器
	private SingletonOne(){
	}
	//创建静态工厂方法 ,让外部可以获取实例
	public static SingletonOne getInstance(){
		return INSTANCE;
	}
}

(二)懒汉式

特点:线程安全,调用效率不高,但是能延时加载

public class SingletonTwo {
	//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
	private static SingletonTwo instance;
	// 私有化构造器
	private SingletonTwo() {
	}
	//方法同步,调用效率低
	// 创建静态工厂方法 ,让外部可以获取实例
	public static synchronized SingletonTwo getInstance() {
		if (instance == null) {
		instance = new SingletonTwo();
		}
		return instance;
	}
}

(三)Double CheckLock实现单例

Double CheckLock也就是双重锁判断机制(由于JVM底层模型原因,偶尔会出问题,不建议使用),是在懒汉式单例上发展而来。

public class SingletonThree {
	private volatile static SingletonThree instance;
	//私有化构造器
	private SingletonThree() {
	}
	//静态工厂方法,双重锁判断机制
	public static SingletonThree newInstance() {
		if (instance == null) {
			synchronized (SingletonThree.class) {
				if (instance == null) {
					instance = new SingletonThree();
				}
			}
		}
	return instance;
	}
}

(四)静态内部类模式

特点:线程安全,调用效率高,可以延时加载

public class SingletonFour {
	// 静态内部类
	private static class SingletonClassInstance {
		private static final SingletonFour instance = new SingletonFour();
	}
	// 私有化构造器
	private SingletonFour() {
	}
	//静态工厂方法
	public static SingletonFour getInstance() {
		return SingletonClassInstance.instance;
	}
}

(五)枚举类

特点:线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用。

public class SingletonFive {
	//私有化构造器
	private SingletonFive(){
	}
	//使用枚举
	private static enum Singleton{
		INSTANCE;
		private SingletonFive singleton;
		//JVM会保证此方法绝对只调用一次
		private Singleton(){
			singleton = new SingletonFive();
		}
		public SingletonFive getInstance(){
			return singleton;
		}
	}
	//静态工厂方法
	public static SingletonFive getInstance(){
		return Singleton.INSTANCE.getInstance();
	}
}

(六)如何选择

单例对象占用资源少,不需要延时加载时:枚举 好于 饿汉
单例对象占用资源多,需要延时加载时:静态内部类 好于 懒汉式

十二、final修饰符

final主要用法有以下四种:

一、用来修饰成员变量,该变量只能被赋值一次且它的值无法被改变。对于成员变量来讲,我们必须在声明时、构造方法或者初始化块中对它赋值;

如果被final修饰的是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则指向的对象不可变但该对象的内容可变

public class FinalMemberVariables {
	// final 修饰的实例变量
	// 必须在非静态初始化块、声明该实例变量或构造器中指定初始
	final int INT_A=10; // final 修饰的实例变量	
	final int INT_B; //初始化块指定初始值
	{
	INT_B=11;
	}
	final int INT_C;
	public FinalMemberVariables(){  //构造器中指定初始值
	INT_C=12;
	}
	
	//final 修饰 类变量
	//必须在静态初始化块中指定初始值或声明该类变量时指定初始值,
	final static String STR_A="123";
	final static String STR_B;
	static{
	STR_B="123456";
	}
}

二、用来修饰局部变量,表示在变量的生存期中它的值不能被改变

public class FinalLocalVariable {
	//final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。
	public static void main(String[] args) {	
		final int INT_A=10; //在定义时指定默认值,不能再后面的代码中为变量赋值
		final String STR_A; //定义时不指定默认值,可以在后面的代码中赋值一次
		STR_A="123";
	}
	public void test(final int num){
		// 不能对final修饰的形参赋值
	}
}

final修饰的成员变量必须初始化。否则编译失败。可以在声明时、构造方法或者初始化块对他赋值。
final修饰局部变量需要保证在使用之前被初始化赋值

三、修饰方法,表示该方法无法被重写;
四、 修饰类,表示该类无法被继承。

十三、Java抽象类

  • 抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。
  • 抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。
  • 抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。
  • 抽象类的构造器不能用于创建实例,主要是用于被其子类调用。
  • 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
  • abstract static不能同时修饰一个方法
public abstract class Animal {	
	protected int weight =0; //成员变量
	{
		System.out.println("初始化块");
	}	
	public Animal(){ //构造器 
		System.out.println("Animal 的构造器");
	}
	public Animal(int weight) {
		this.weight=weight;
		System.out.println("Animal 的构造器");
	}	
	public void sleep() { // 定义一个普通方法 休息
		System.out.println("休息");
	}
	public abstract void running(); //抽象方法 没有方法体
	public abstract String sayWeiget();
}

十四、Java接口

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。

在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

(一)接口的定义

[修饰符] interface 接口名 extends 父接口1,父接口2,...... //修饰符 public、abstract
{
	//零到多个静态常量定义...
	//零到多个抽象方法定义....
}

接口有以下特性:

  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
  • 接口中的方法都是公有的。
  • 编译时自动为接口里定义的方法添加public abstract修饰符
  • Java接口里的成员变量只能是public static final共同修饰的,并且必须赋初值,
  • 可以不写public static final,编译的时候会自动添加
public interface InterfaceOne {
	int INT_A=11; //编译时自动为接口里定义的成员变量增加public static final修饰符
	public final static int INT_B=11;	
	void sleep(); //编译时自动为接口里定义的方法添加public abstract修饰符
	public abstract void running();
	void test();
}

(二)接口的实现

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。

类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。

Java中类可以实现多个接口。

[修饰符] class 类名 implements 接口1[, 接口2, 接口3..., ...]
{
}
public class InterfaceOneImpl implements InterfaceOne{
	@Override
	public void sleep() {
		System.out.println("休息");
	}
	@Override
	public void running() {
		System.out.println("运动");
	}
	@Override
	public void test() {
	// TODO Auto‐generated method stub
	}
}

(三)接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
接口允许多继承

public interface InterfaceThree extends InterfaceOne,InterfaceTwo{
}

十五、抽象类和接口的对比

抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的抽象方法的实现。 类使用关键字impements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与普通类的区别 除了不能实例化抽象类之外,它和普通Java类没有任何区别 接囗是完全不同的类型
访问修饰符 抽象方法可以有pubic、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。
多继承 抽象类可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

十六、修饰符总结

(一)四个访问控制符:

注意:在不写访问控制符的情况下,就是friendly(默认)修饰符

访问范围 private friendly protected pubic
同一个类中
同一个包中
子类中
全局范围内

(二)类修饰符:

  • public(访问控制符),将一个类声明为公共类,他可以被任何对象访问,一个程序的主类必须是公共类。
  • friendly,默认的修饰符,只有在相同包中的对象才能使用这样的类。
  • abstract,将一个类声明为抽象类,没有实现的方法,需要子类提供方法实现。
  • final,将一个类生命为最终(即非继承类),表示他不能被其他类继承。

(三)成员变量修饰符:

  • public(公共访问控制符),指定该变量为公共的,他可以被任何对象的方法访问。
  • private(私有访问控制符)指定该变量只允许自己的类的方法访问,其他任何类(包括子类)中的方法均不能访问。
  • protected(保护访问控制符)指定该变量可以别被自己的类和子类访问。在子类中可以覆盖此变量。
  • friendly ,在同一个包中的类可以访问,其他包中的类不能访问。
  • final,最终修饰符,指定此变量的值不能变。
  • static(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类。

(四)方法修饰符:

  • public(公共控制符),指定该方法可以从所有类访问
  • private(私有控制符)指定此方法只能有自己类等方法访问,其他的类不能访问(包括子类)
  • protected(保护访问控制符)指定该方法可以被它的类和子类进行访问。
  • final,指定该方法不能被重载。
  • static,指定不需要实例化就可以激活的一个方法。
  • synchronize,同步修饰符,在多个线程中,该修饰符用于在运行前,对他所属的方法加锁,以防止其他线程的访问,运行结束后解锁。
  • native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。

(五)初始化块:

  • static(可选),使用static修饰的初始化块被称为静态代码块

static关键词

两个作用:

  • 创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法。
  • 用来形成静态代码块以优化程序性能。因为static块只会在类加载的时候执行一次,可以将一些只需要进行一次的初始化操作都放在static代码块中进行。

特点:

  • 被static修饰的变量或者方法不属于任何一个实例对象,而是被类的实例对象所共享。
  • 被static修饰的部分在且只在该类被第一次加载的时候进行初始化,在以后创建类对象的时候不会重新分配内存空间。
  • 被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。

静态导包import static:以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。

注意:

  • 静态只能访问静态。
  • 非静态既可以访问非静态的,也可以访问静态的。

final进阶

public class FinalAndVariableDifference {
    public static void main(String[] args)  {
        String a = "helloWord1";
        final String b = "helloWord";
        String F = "helloWord";

        String c = b + 1;
        String e = F + 1;
        System.out.println((a == c));
        System.out.println((a == e));
    }
}
true
false

当final变量修饰基本数据类型以及String类型时,编译期间能知道它的确切值时,编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。类似于C语言的宏。

分析上面代码:由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的值(这种情况我们成为编译器的优化)。而对于变量F的访问却需要在运行时才能连接确定,所以返回false。

注意:只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化

public class FinalAndVariableDifference {
    public static void main(String[] args)  {
        String a = "helloWord2";
        final String b = getHello();  //尽管是final修饰,但不会进行优化,因为它要运行时初始化才被确定
        String c = b + 2;
        System.out.println((a == c));
    }
    public static String getHello() {
        return "helloWord";
    }
}
运行结果:false
发布了54 篇原创文章 · 获赞 3 · 访问量 3650

猜你喜欢

转载自blog.csdn.net/magic_jiayu/article/details/104124938