目录
01、面向对象的基本概念
02、类与对象之间关系
03、类中的成员
04、类的构造器
05、堆和栈
06、this关键字的作用
07、static关键字的作用
08、对象之间的组合使用
09、面向对象三大特征之——封装
10、面向对象三大特征之——继承
11、面向对象三大特征之——多态
12、方法的重载与重写
13、super关键字的作用
14、final关键字的作用
15、抽象类
16、接口
17、instanceof关键字的作用
18、内部类的作用
19、案例代码
20、总结
学习面向对象的过程,实际上也是建立面向对象思维的过程。
先整体,再局部。先抽象,再具体,看透事物的本质,用java代码来解释某个事物的行为和特征,将其转变成一个可以被程序理解和调用的实例。
如何建立这种面向对象的思维呢?这里提出一个有趣的问题:这个世界是由什么组成的?
”如果是一个化学家,他也许会告诉你“这个世界是由分子、原子、离子等等的化学物质组成的”。
如果是一个画家呢?他也许会告诉你,“这个世界是由不同的颜色所组成的”。…每个人肯定都有自己的看法,这里就不一一赘述了。
但如果让一个分类学家来考虑问题就有趣的多了,他会告诉你“这个世界是由不同类型的物与事所构成的”好!作为面向对象的程序员来说,我们要站在分类学家的角度去考虑问题!是的,这个世界是由动物、植物等组成的。动物又分为单细胞动物、多细胞动物、哺乳动物等等,哺乳动物又分为人、大象、老虎……就这样的分下去了!
有了这种考虑问题的思维之后,我们来分析一下,什么是人类 ?
首先让我们来看看人类所具有的一些特征,这个特征包括属性(一些参数,数值)以及方法(一些行为,他能干什么!)。每个人都有身高、体重、年龄、血型等等一些属性。人会劳动、人都会直立行走、人会合理的使用工具等等这些方法。
人之所以能区别于其它类型的动物,是因为每个人都具有人类这个群体的属性与方法。“人类”只是一个抽象的概念,它仅仅是一个概念,所有具备“人类”这个群体的属性与方法的对象都叫人!
比如说 亚当、夏娃是具体的人,属于人类,因为他们具有人类的行为和特征。
定义一个类的对象,其实就是创建一个类的实例,表示一个具体的对象。
格式: 类名 对象名 = new 类名();
例如: 人类 亚当 = new 人类();
左边的这个人类,表示亚当的类型是人类,右边的 new 人类(); 表示亚当是一个具体的实例,拥有人类的行为和特征。
在声明一个具体对象时,如果只声明对象而没有给出具体实例,例如
People p1 =null; 或者 People p2 ;
此时p1 和p2 无法正常使用,因为没有具体的内存指向。
p1 = new People(); p2 = new People();
在有了具体的内存指向后,p1和p2 才可以正常使用。
类的定义
class 类名 {
(特征)属性名称
例如: int age ; //年龄
(行为)方法名(){ }
例如: void move(){ 直立行走 }
}
在类中描述人类特征的,就是类中的属性,也叫成员属性。在类中描述人类行为的,就是类中的方法,也叫成员方法。
如果要想访问类中的属性或方法则可以依靠以下的语法形式:
访问类中的属性:对象名.成员属性 ;
调用类中的方法:对象名.成员方法();
public class People {
//类中的成员属性
int age;//年龄
String name;//名字
double lenght;//身高
//类中的成员方法
public void move() {//移动
System.out.println("直立行走");
}
public void sleep() {
System.out.println("日出而作日落而息");
}
public static void main(String[] args) {
//创建类的实例对象 可以调用类中的属性和方法
People p1 = new People();//
p1.name = "亚当";
p1.age=30;
p1.lenght=1.85;
System.out.println("这个人类的名字是"+p1.name);
System.out.println("这个人类的年龄是"+p1.age);
System.out.println("这个人类的身高是"+p1.lenght+"m");
p1.move();
p1.sleep();
}
}
输出结果:
这个人类的名字是亚当
这个人类的年龄是30
这个人类的身高是1.85m
直立行走
日出而作日落而息
类的构造器,也称为构造方法,每个类中都默认存在一个,与类名完全相同,没有返回值类型修饰符(包括void),没有参数的方法。
public class People {
public People() {
//自动产生的构造方法
}
构造方法的作用,是给类中的成员属性初始化赋值。在类中不允许先声明变量后赋值的情况,一般情况下,都是使用构造方法来给属性赋值,这个过程就是初始化。
构造方法也是类中的方法,支持方法的重载。即方法名相同,参数列表不同。同一个类中可以有多个构造方法,在创建类的对象时, new 类名 () ,这个()实际上就是一个无参构造。也就是说,在创建类的实例对象时,实际上是执行了该类的构造方法。
public class People {
public People() {
System.out.println("我是People类构造器");
}
public static void main(String[] args) {
People p1 = new People();//执行
}
}
输出结果
我是People类构造器
注意:当在类中写了有参构造方法后。默认的无参构造将不再生效,创建类的实例对象时,需要根据构造方法传递对应的参数,否则就会报错。
(1)构造方法名称与类名相同,没有返回值声明(包括 void)
(2)构造方法用于初始化数据(属性)
(3)每一个类中都会有一个默认的无参的构造方法
(4)如果类中写了构造方法,那么默认构造方法将无效
(5)如果写了有参构造方法,还想保留默认构造方法,需要显示的写出来。
(6)构造方法可以有多个,但参数不一样,称为构造方法的重载
(7)在构造方法中调用另一个构造方法,使用this(...),该句代码必须在第一句。
(8)构造方法之间的调用,必须要有出口,否则会保错。
(9)给对象初始化数据可以使用构造方法或setter方法,通常情况下,两者都会保留。
(10)一个好的编程习惯是要保留默认的构造方法。(为了方便一些框架代码使用反射来创建对象)。
创建类的对象 : 类名 对象名 = new 类名();
如果输出这个对象名的话,看到输出的是一串乱码,其实这是这个对象在内存中的地址值。
对象名可以理解为一个引用,储存在栈中。
创建的实例对象 new People是储存在堆中,包含类中全部的属性和方法。
对象名.属性 这个调用属性的过程,实际上就是通过引用地址,找到堆内存中对应数据的过程。
注意:若不同的引用指向了内存中的同一个对象,其中一个引用改变了对象的值,其他引用都会反映出来。
this关键字指向的是当前对象的引用
调用类中的属性:this.属性名称,指的是访问类中的成员变量,用来区分成员变量和局部变量(重名问题)
调用类中的方法:this.方法名称,用来访问本类的成员方法
调用类构造方法:this();访问本类的构造方法,()中可以有参数的 如果有参数 就是调用指定的有参构造
注意:
1.this() 不能使用在普通方法中,只能写在构造方法中
public class People {
public People(int age,String name) {
this();//调用无参构造
System.out.println("我是有参构造");
}
public People() {
System.out.println("我是无参构造");
}
public static void main(String[] args) {
People p1 = new People(20,"刘禅");
}
}
运行结果
我是无参构造
我是有参构造
2.必须是构造方法中的第一条语句,构造方法之间的调用必须留出口。否则就是死循环,会报错。
3.成员变量和局部变量可以重名。局部变量只在方法内部有效,方法外部无法使用,一般作为方法参数列表中的参数,可以使用this关键字来区分 。
public int age;//成员变量
public void setAge(int age) {//局部变量
this.age = age;//调用此方法时传递参数给成员变量赋值
}
static:静态修饰符
使用static关键字修饰一个属性:该属性就是静态属性,也叫全局变量,类名.属性名可以直接访问
使用static关键字修饰一个方法:该方法就是静态方法,可以不用创建对象,直接类名.方法名调用
声明为static的方法有以下几条限制:
1、静态方法只能调用其他的静态方法,不能调用非静态方法。而在非静态方法中是可以调用静态方法的
2、静态方法中只能访问静态属性
3、静态方法不能引用this或super。
4、不允许用来修饰局部变量
public class People {
static int a;//静态属性
int b;//非静态属性
static void m1() {//静态方法
a=100;
//b=10;//编译错误,不能访问非静态属性
//m2();//编译错误,不能调用非静态方法
m3();
}
void m2() {//非静态方法
b=10;
a=100;
m3();
}
static void m3() {
}
java中对象的对应关系有很多种,比如单向一对一,双向一对一,一对多,多对一,多对多等,其实现原理相同,其实可以理解为类的组合问题,把对象当做另一个的属性来操作,使得其产生对应关系(很多设计模式就是通过类的组合实现的)。
举一个例子:孙悟空使用金箍棒打死了猪八戒。
这里出现了3个对象,孙悟空,金箍棒,猪八戒。金箍棒是一个单独的对象,同时也是孙悟空的武器,在孙悟空这个类中就是其武器属性。也可以反过来理解,武器需要使用者才能发挥作用,金箍棒这个类中也应该有个主人的属性。
在java中,类其实就是类型,一个引用类型。我们可以把一个类当成另一个类中的属性,提供对应
的get/set方法,让类与类之间产生联系。在调用时可以互相调用。下边看一下具体案例演示。
public static void main(String[] args) {
Hero h1 = new Hero("孙悟空", 10000);
Weapon w1 =new Weapon("金箍棒", 108000);
h1.setW(w1);//给英雄类的武器属性赋值
w1.setH(h1);//给武器类的英雄属性赋值
System.out.println(h1.getName()+"的武器是"+h1.getW().getName());
System.out.println(w1.getName()+"的主人是"+w1.getH().getName());
}
}
class Hero {
private int age;
private String name;
private Weapon w;
//构造方法,根据创建实例对象时传递的参数,给属性初始化
public Hero(String name,int age) {
this.name=name;
this.age=age;
System.out.println("英雄名字是"+this.name+",年龄是"+this.age);
}
//给私有属性提供对应的get(获取) 、 set(修改) 方法
public Weapon getW() {
return w;
}
public void setW(Weapon w) {
this.w = w;
System.out.println("已添加武器"+w.getName());
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Weapon {
private int size;
private String name;
private Hero h;
//构造方法,根据创建实例对象时传递的参数,给属性初始化
public Weapon(String name,int size) {
this.name=name;
this.size=size;
System.out.println("武器名字是"+this.name+",公斤数"+this.size);
}
//给私有属性提供对应的get(获取) 、 set(修改) 方法
public Hero getH() {
return h;
}
public void setH(Hero h) {
this.h = h;
System.out.println("已绑定主人"+h.getName());
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
运行结果
英雄名字是孙悟空,年龄是10000
武器名字是金箍棒,公斤数108000
已添加武器金箍棒
已绑定主人孙悟空
孙悟空的武器是金箍棒
金箍棒的主人是孙悟空
实际上就是信息隐藏,将类中的成员属性和成员方法修饰为私有化,数据被保护在对象的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系,即get/set方法。
其他对象只能通过该对象提供的get/set方法,与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节(当然也无从知道),但可以通过该对象对外提供的接口来访问该对象。
对于封装而言,一个对象它所封装的是自己的属性和方法,所以它不需要依赖其他对象就可以完成自己的操作。使用封装的好处:
1、良好的封装能够减少耦合。
2、类内部的结构可以自由修改。
3、可以对成员进行更精确的控制。
4、隐藏信息,实现细节。
举个例子:
public static void main(String[] args) {
Dog d1 = new Dog();
d1.age=3;
d1.name="旺财";
}
}
class Dog{
public int age;
public String name;
}
如果有很多代码都使用了Dog这个类;当某一天这个类的age属性需要换成String类型,那么,外部使用它的任何地方都需要需改xxx.age="xxx",这将是非常繁琐的一个过程,那该怎么办呢?很简单,使用private修饰符将属性封装,开放访问接口的方法,我们只需要修改一下set方法就能完美解决。
public static void main(String[] args) {
Dog d1 = new Dog();
//调用时发生变化
d1.setName("旺财");
d1.setAge(3);
}
}
class Dog{
private String age;//修改为String类型
private String name;
//将属性私有化,提供set方法,将int类型的值转成String
public void setAge(int age) {
this.age = String.valueOf(age);
}
public void setName(String name) {
this.name = name;
}
这样外部使用它的地方都不用修改,我们只用简单的修改对象内部就可以了,更加方便快捷。到了这里我们应该可以看出,封装确实可以使我们容易地修改类的内部实现,而无需修改使用了该类的客户代码。
这里还可以体现出一些封装属性的优势,案例如下:
public static void main(String[] args) {
Dog d1 = new Dog();
d1.setName("旺财");
d1.setAge(300);//狗的年龄赋值很明显不合理,这里就需要在set方法中给出提示
}
}
class Dog{
private String age;
private String name;
public void setAge(int age) {
if(age>100||age<0) {
System.out.println("你见过超过100岁的狗狗吗?");
}else {
this.age = String.valueOf(age);
}
}
public void setName(String name) {
this.name = name;
}
}
继承,通俗点来说就是 子承父业。是使用已存在的类的定义作为基础,建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类中的某些属性,除了父类中私有的属性和方法,子类必须全部继承。
1、被继承的类称为父类(超类),继承父类的类称为子类(派生类)使用 关键字extends 继承。
2、子类拥有父类非 private 的属性、方法。
3、子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
4、子类可以用自己的方式实现父类的方法(即方法的重写/覆盖)。
5、构造器而言,它只能够被调用,而不能被继承,子类可以使用super()调用父类构造器。
6、对于继承而已,子类会默认调用父类的无参构造,但是如果父类没有无参构造,子类必须要在其构造方法中的第一行代码调用指定父类的构造器,传递对应参数。
7、Java 的继承是单继承,但是可以多重继承,即子类只能拥有一个直接父类,但是该父类仍然可以作为其他类的子类。
class Father{
//父类中的非私有属性都会被继承
public int age;
private double money;
String home;
//父类中的非私有方法都会被继承
void eat() {} //吃东西
public void sleep() {}//睡觉
private void soner() {}//打呼噜
//父类构造方法如果有参数,子类必须在构造方法第一行中调用
public Father(int x) {
//父类如果写了有参构造,那么默认的无参构造将不再生效
}
}
class Son extends Father{
public Son(int x) {
super(x);//调用父类构造 super()
age=12;//继承后拥有父类的属性
home="王者峡谷河道下边的草丛";
//money=3.0; 父类私有属性无法访问
sleep();//可以执行父类中的非私有方法
}
}
这里补充一下,子类继承父类,默认在子类构造方法中调用父类构造,在创建子类实例对象时,实际上的执行顺序是(父类构造——子类构造)。在类的构造器中还有一段特别的代码,优先与构造器,在创建对象时优先执行,话不多说直接看代码:
public static void main(String[] args) {
Z z1 = new Z();//创建子类实例对象时,实际上的执行顺序
}
}
class F {
static {
//静态代码块,只有方法的大括号,没有方法名返回值等任何内容
System.out.println("父类静态代码块");
}
{
//构造代码块,只有方法的大括号,没有方法名返回值等任何内容
System.out.println("父类构造代码块");
}
public F() {
// 父类构造器
System.out.println("父类构造器");
}
}
class Z extends F{
static {
//静态代码块,只有方法的大括号,没有方法名返回值等任何内容
System.out.println("子类静态代码块");
}
{
//构造代码块,只有方法的大括号,没有方法名返回值等任何内容
System.out.println("子类构造代码块");
}
public Z() {
// 父类构造器
System.out.println("子类构造器");
}
}
运行结果:
父类静态代码块
子类静态代码块
父类构造代码块
父类构造器
子类构造代码块
子类构造器
面向对象三大特征,封装、继承、多态。从某种意义上来讲,封装和继承几乎就是为了多态而准备的,也是三大特征中最重要的知识点。
多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。简单来说就是不同类型的对象(父类或子类)调用同一个方法,根据发出调用的对象不同,执行的方法也就不同。
实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
多态的作用:消除类型之间的耦合关系。
这里举一个简单的小例子:父类是个农民,技能是使用锄头耕地。子类继承了父类,重写了父类的锄头耕地技能,更新换代为使用拖拉机耕地。如果是父类对象调用这个技能,就是使用锄头手动耕地,如果是子类对象调用这个技能,就是使用拖拉机耕地。
java实现多态有三个必要条件:继承、重写、父类引用指向子类对象。
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
父类引用指向子类对象(向上转型):在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
public class People {//人类 作为所有职业的父类
public int age;
public String name;
void work() {//父类的方法
System.out.println("劳动");
}
public static void main(String[] args) {
//父类引用指向子类对象就是多态性的体现
People p1 = new Doctor();// p1的类型是人类类型,但是指向的实例对象是医生
p1.work();//父类引用发出的调用,调用到的是医生类中重写父类的方法
People p2 = new Teacher();
p2.work();//父类引用发出的调用,调用到的是教师类中重写父类的方法
}
}
class Doctor extends People{
@Override
void work() {//医生类继承人类,重写工作方法
System.out.println("救死扶伤");
}
}
class Teacher extends People{
@Override
void work() {//教师类继承人类,重写工作方法
System.out.println("教书育人");
}
}
运行结果
救死扶伤
教书育人
注意:指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的。比如说方法的重载,尽管方法名称相同,但是父类只能调用到子类重写的方法,调用不到重载方法。
class Teacher extends People{
@Override
void work() {//教师类继承人类,重写work方法
System.out.println("教书育人");
}
void work(String name) {//重载了work方法,但是此方法父类引用无法调用
System.out.println("负责教导的课程是"+name);
}
}
多态的好处:
1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如:医生、律师、程序员都是人类的子类,根据使用场景不同随时可以替换为符合的职业。
2.可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在拥有了医生、律师的继承上,还可以继续添加新的职业,比如主播,运动员等,都是添加为人类的多态性。
3.接口性(interface-ability)。多态是超类通过抽象方法,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。每个子类都可以根据自身的特性去重写父类的抽象方法。
4.灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
5.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
方法的重载与重写就是方法多态性的一种表现。
重载的规则:
1、必须具有不同的参数列表;
2、可以有不同的返回类型,只要参数列表不同就可以了;
3、可以有不同的访问修饰符;
4、可以抛出不同的异常;
重写方法的规则:
1、参数列表必须完全与被重写的方法相同。
2、返回的类型必须一直与被重写的方法的返回类型相同。
3、访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
4、重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。例如:
父类的一个方法申明了一个检查异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常
5、父类的私有方法不能被子类继承,所以不能被重写
6、父类中的方法若使用private、static、final任意修饰符修饰,那么,不能被子类重写。
可以理解为对父类的引用,super可以完成以下的操作:
1、使用super调用父类中的属性,可以从父类处获得信息。
2、使用super调用父类中的方法,在子类中执行。
3、使用super调用父类中的构造方法(super(实参)形式),必须在子类构造方法的第一条语句,调用父类中相应的构造方法,若不显示的写出来,默认调用父类的无参构造方法,比如:super();
class F {
public int age;
public String name;
void m1() {
System.out.println("父类方法");
}
public F(int age) {
System.out.println("父类有参构造");
}
public F(){
System.out.println("父类无参构造");
}
}
class Z extends F{
public Z() {
super();//调用父类构造,若不写这行代码也会默认调用父类的无参构造
//若是调用父类有参构造,则需要传递对应类型的参数
super.age=30;//调用父类属性
super.m1();//调用父类方法
}
}
使用final关键字完成以下的操作:
a、使用final关键字声明一个常量
当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化。
public final int age =10;
//声明为常量时必须赋值,否则会报错
public static final String name = "亚当";
//一般和静态修饰符配合使用,修饰为静态常量,
//在编译期间知道静态常量的确切值,所以编译器会把它当做编译期常量使用进行优化,字体变成蓝色加粗
void work() {//父类的方法,此方法可以被子类重写
System.out.println("劳动");
}
final void sleep() {
//此方法不能被子类重写,但是可以被子类调用
}
;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的
b、使用final关键字声明一个方法,该方法为最终方法,且只能被子类继承,但是不能被子类重写。
c、使用final关键字声明一个类,该类就转变为最终类,没有子类的类,fianl修饰的类无法被继承。
public final int[] age = {10,20,30};
//final修饰引用类型时,内存中的地址不能更改,即不能指向其他对象
//但是该引用指向的对象中的内容是可以修改的
public People() {
//age = new int[10];//编译错误
age[0]=50;//可以修改内容
}
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,那么这样的类就是抽象类。abstract class
抽象类往往用来对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
抽象类具有以下特性:
public abstract class People {
//由abstract修饰符修饰的类就是抽象类
abstract void work();
//由abstract修饰符修饰的方法就是抽象方法,没有具体的方法执行,即方法体{}
}
class Doctor extends People{
// 非抽象的子类继承抽象父类,必须重写父类中的抽象方法
@Override
void work() {//子类重写的方法必须有方法执行体{}
System.out.println("救死扶伤");
}
}
public static void main(String[] args) {
//People p1 =new People();//编译错误,抽象类不能实例化
People p2 = new Doctor();//编译正确
}
在面向对象方法中,抽象类主要用来进行类型隐藏。构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现,则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。
Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。接口实现和类继承的规则不同,为了数据的安全,继承时一个类只有一个直接父类,也就是单继承,但是一个类可以实现多个接口,接口弥补了类的不能多继承缺点,继承和接口的双重设计既保持了类的数据安全也变相实现了多继承。
Java接口本身没有任何实现,因为Java接口不涉及表象,而只描述public行为,所以Java接口比Java抽象类更抽象化。但是接口不是类,不能使用new 运算符实例化一个接口。如 x=new comparable(......);//这个是错误来的。但是可以声明接口变量Comparable x; //这是允许的。Java接口的方法只能是抽象的和公开的,Java接口不能有构造器,Java接口可以有public、static和final属性。即接口中的属性可以定义为 public static final int value=5;
public interface Weapon {//声明一个接口
int HP=100;//接口中定义的变量就是静态常量
void kill();//接口中定义的方法就是抽象方法
//当不定义返回值类型时默认public
//private void m1();//接口中不能定义私有抽象方法,没有意义
//private int MP=100;//接口中不能定义私有属性
static void m2() { //接口中可以有具体的方法,必须声明为static或者default
System.out.println("物理攻击");
}
}
接口的使用规则:
1、定义一个接口,使用interface关键字
2、在一个接口中,只能定义常量、抽象方法,JDK1.8后可以定义默认的实现方法
3、接口可以继承多个接口:extends xxx,xxx
public interface Weapon2 extends Weapon,Weapon1{
//接口可以继承接口,可以继承多个接口
}
4、一个具体类实现接口使用implements关键字,一个类只能有一个父类,但是可以实现多个接口
class Hero implements Weapon,Weapon2{
//一个类只能继承一个父类,但是可以实现多个接口
//使用implements关键字,以逗号隔开。
//非抽象类实现接口必须重写接口中的抽象方法
@Override
public void kill() {
// TODO Auto-generated method stub
}
}
5、抽象类实现接口可以不实现接口的方法,因为二者都是抽象的
6、在接口中定义的方法没有声明 访问修饰符,默认为public
7、接口不能有构造方法
8、接口不能被实例化,不能new 接口
java8新增
9、增加了default方法和static方法,这两种方法完全可以有方法体
10、接口中的静态方法不会被继承,但是接口中的静态变量会被继承
抽象类和接口比较:
抽象类表示该类中可能已经有一些方法的具体定义,但是接口就仅仅只能定义各个方法的界面(方法名,参数列表,返回类型),并不关心具体细节。
接口是引用类型的,和抽象类的相似之处有三点:
抽象类与接口紧密相关。然而接口又比抽象类更抽象,这主要体现在它们的差别上:
抽象类里面可以有非抽象方法但接口里只能有抽象方法 声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类为。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对像上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口
instanceof是Java的一个二元操作符(运算符),也是Java的保留关键字。它的作用是判断其左边对象是否为其右边类的实例,返回的是boolean类型的数据。用它来判断某个对象是否是某个Class类的实例。
使用方法:object instanceof class
object :必选项。任意引用对象。
class:必选项。任意已定义的对象类。
说明:如果该object 是该class的一个实例,那么返回true。如果该object 不是该class的一个实例,或者object是null,则返回false。
interface A{} //接口A
class B implements A { } //B实现了接口A
class C extends B { } //C继承B
class D { }
public class People {
public static void main(String[] args) {
A a = null;
B b = null;
boolean res;
res = a instanceof A;
System.out.println("a instanceof A: " + res); // 没有给出具体指向 false
res = b instanceof B;
System.out.println("b instanceof B: " + res); // 同上
System.out.println("给出内存中的指向后");
a = new B();
b = new B();
res = a instanceof A;
System.out.println("a instanceof A: " + res);
res = a instanceof B;
System.out.println("a instanceof B: " + res);
res = b instanceof A;
System.out.println("b instanceof A: " + res);
res = b instanceof B;
System.out.println("b instanceof B: " + res);
//B类实现了A接口 ,所以以上都是true
B b2 = new C();
res = b2 instanceof A;
System.out.println("b2 instanceof A: " + res);
res = b2 instanceof B;
System.out.println("b2 instanceof B: " + res);
res = b2 instanceof C;
System.out.println("b2 instanceof C: " + res);
//C类继承了B类 因为B类实现了接口A ,所以以上都是true
D d = new D();
res = d instanceof A;
System.out.println("d instanceof A: " + res);
//D类是个单独的类,和A B C 没有任何关系,所以是false
res = d instanceof D;
System.out.println("d instanceof D: " + res);
}
}
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。
1.成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,和成员属性、成员方法一样,都是类中的成员,定义格式如下:
public class People {
int age;//成员属性
void work() {}//成员方法
class Id{//成员内部类
int id;//内部类中也可以有属性和方法
void m1() {}
}
}
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
public class People {
private int age=20;
static String name="老白";
void work() {}
class Id{//成员内部类
int id;
public Id() {
id=age;//访问外部类的私有属性
System.out.println(name);//访问外部类静态属性
work();//调用外部类方法
}
}
}
注意:当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:外部类.this.成员变量 , 外部类.this.成员方法
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
People p1 =new People();
People.Id id = p1.new Id();//必须通过People对象来创建
这里只需要先掌握这个成员内部类的使用方式就可以了。静态内部类,局部内部类,匿名内部类在接下来的学习过程中,再慢慢的去了解和掌握即可。
为什么在Java中需要内部类?总结一下主要有以下四点:
1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
3.方便编写事件驱动程序
4.方便编写线程代码
个人觉得第一点是最重要的原因之一,内部类的存在使得Java的多继承机制变得更加完善。
经过以上的讲解之后相信大家都有了一定的理解,大家可以看一下上一篇飞机大战详解,当作对面向对象以上知识点的一个总结,有不足的地方还望大家多多体谅。
面向对象是java基础中相对比较难理解的地方,可能有的人在迷茫了好久之后才豁然开朗。建立面向对象思维来看待事物的本质,清晰的思维需要不断的磨练才会愈发的锋芒毕露。
学无止境,越努力的人才越优秀!当你凝望巅峰的时候,巅峰也在凝望着你。
目录
01、面向对象的基本概念
02、类与对象之间关系
03、类中的成员
04、类的构造器
05、堆和栈
06、this关键字的作用
07、static关键字的作用
08、对象之间的组合使用
09、面向对象三大特征之——封装
10、面向对象三大特征之——继承
11、面向对象三大特征之——多态
12、方法的重载与重写
13、super关键字的作用
14、final关键字的作用
15、抽象类
16、接口
17、instanceof关键字的作用
18、内部类的作用
19、案例代码
20、总结