本文将以我个人对于该部分内容的理解,重新对整个体系进行归纳和划分,以一种不太常见的方式把面向对象的基础内容全部涵盖在内。我把整个面向对象的知识分成两大部分:第一部分是“功能”,第二部分是“关系”。功能部分包括:类的定义、类的初始化、类的实例化、类的修饰、类的扩展几个部分。关系部分包括:实例与类的关系、子类与父类的关系、类与类的关系几个部分。具体内容将在下文详述。
“异常处理”这部分的内容也常常被一些国内外教科书划分在面向对象的章节中,不过由于我认为异常处理部分内容的体系比较有特点,基本上属于自成体系,也有不少内容,将单独再写一篇文章来进行总结和演练。
class staticInnerClassDemo{
//在外部其他类中,直接访问非static内部类的methods比较
Outer.Inner in = new Outer().new Inner();
in.function();
//在外部其他类中,直接访问static内部类的非静态methods
new Outer.Inner().function();
//在外部其他类中,直接访问static内部类的静态methods
Outer.Inner.function();
}
内部类必须是static的两种情况
1.当内部类中定义了静态成员
2.外部类中的静态方法需要访问内部类
当描述事物时,事物的内部还有事物,该事物用内部类来描述,因为内部事物在使用外部事物的内容。
内部类可以定义在成员位置上,也可以定义在方法内部。定义在成员位置上时,就可以被私有化,也可以被static所修饰。
如果内部类定义在内部,则不能被静态所修饰,也不能私有化。
定义在方法内部的内部类
内部类定义在局部时,
1,不可以被成员修饰符修饰
2,可以直接访问外部类中的成员,因为还持有外部类中的引用。但是不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。
class Outer
{
int x = 3;
void method(final int a)
{
final int y = 4;
//内部类定义在局部时,不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。
//定义在该method内部的内部类
class Inner
{
void function()
{
System.out.println(y);
}
}
new Inner().function();//如果不new一个Inner的对象,没有这句语句,将不会运行。没对象,不运行
}
}
class InnerClassDemo
{
public static void main(String[] args)
{
Outer out = new Outer();
out.method(7);//出栈后释放
out.method(8);//又进栈后开辟新的a
}
}
匿名内部类
匿名内部类:
1,匿名内部类其实就是内部类的简写格式。
2,定义匿名内部类的前提:内部类必须是继承一个类或者实现接口。
3,匿名内部类的格式: new 父类或者接口(){定义子类的内容}
4,其实匿名内部类就是一个匿名子类对象。而且这个对象有点胖。
可以理解为带内容的对象。
5,匿名内部类中定义的方法最好不要超过3个。
class Outer
{
int x = 3;
//没有使用匿名内部类时的情况:以下
/*
class Inner extends AbsDemo
{
int num = 90;
void show()
{
System.out.println("show :"+num);
}
}
public void function()
{
new Inner.show();
}
*/
//使用匿名内部类时的情况:以下
public void function()
{
//匿名内部类,直接用AbsDemo创建对象,后面还带着代码块{}
new AbsDemo()//创建的是AbsDemo的匿名子对象
{
void show(){
System.out.println("show :"+num);
}
}.show();//注意:分号在这里,整个代码块是一个整体,是AbsDemo的子类对象,最后是方法调用。匿名对象对方法只能调用一次
}
}
类的初始化
1.构造函数
对象一建立就会调用与之对应的构造函数。
构造函数的作用:可以用于给对象进行初始化。
构造函数的小细节:
1.当一个类中没有定义构造函数时,那么系统会默认给该类加入一个空参数的构造函数。
2.当在类中自定义了构造函数后,默认的构造函数就没有了
一个对象建立,构造函数只运行一次。
而一般方法可以被该对象调用多次。
什么时候定义构造函数呢?
当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。
/*
程序输出结果:
学号:003 姓名:张飞
学号:004 姓名:关羽
学号:001 姓名:刘备 职务:班长
学号:002 姓名:诸葛亮 职务:学习委员
*/
public class constructorDem {
public static void main(String[] args) {
Students stu1 = new Students("003","张飞");//1号张飞,学号如果设置成int,则001将会输出1,所以要用String来存学号
Students stu2 = new Students("004","关羽");//2号关羽
StuLeader sl1 = new StuLeader("001","刘备","班长");//班长刘备
StuLeader sl2 = new StuLeader("002","诸葛亮","学习委员");//学习委员诸葛亮
//打印输出,否则控制台没有显示结果
System.out.println(stu1);
System.out.println(stu2);
System.out.println(sl1);
System.out.println(sl2);
}
}
class Students{
private String number;//学号
private String name;//姓名
//构造函数
public Students(String number, String name){
this.setNumber(number);
this.setName(name);
}
//学号的getter和setter
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
//姓名的getter和setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString(){
return "学号:" + number + "\t" + "姓名:" + name ;
}
}
class StuLeader extends Students{//学生干部是一个特殊的学生的类,继承自学生,多一个职务的属性
//Implicit super constructor Students() is undefined for default constructor. Must define an explicit constructor
//因为在父类Students中没有定义默认的构造器,所以必须要在该类中定义一个显式的构造器。因为在子类隐式的构造器开头隐含一个super();
private String duty;
public StuLeader(String number, String name, String duty){
super(number,name);//不能写成super(int number,String name);
this.duty = duty;
}
@Override
public String toString(){
return super.toString() + "\t" + "职务:" + duty;//如果学号不对齐,使用tab键并调用super的时候会发生一些问题
}
}
2.构造代码块
作用:给对象进行初始化
对象一建立就运行,并优先于构造函数运行
和构造函数的区别:
构造代码块是给所有对象进行统一初始化,
而构造函数是给对应的对象初始化。
构造代码快中定义的是不同对象共性的初始化内容。
/*
运用构造代码块,小孩不管有名字没名字,生下来都会哭了
小孩生下来哭了
小孩:Bob
小孩生下来哭了
*/
class Baby
{
private String name;
{
System.out.println("小孩生下来哭了");
}
Baby()
{
}
Baby(String name)
{
this.name =name;
}
@Override //重写的toString不能放在构造函数中间,还需要在主函数中打印输出对象
public String toString(){
return "小孩:" + name + "\n";
}
}
class ConDem2
{
public static void main(String[] args)
{
//有名字的小孩
Baby b = new Baby("Bob");
System.out.println(b);
//没名字的小孩
new Baby();
}
}
类的实例化
匿名对象
匿名对象使用方式一:当对对象的方法只调用一次时,可以用匿名对象来完成
匿名对象使用方式二:可以将匿名对象作为实际参数进行传递。
匿名对象调用field没意义
class Car{
String name = "奥迪";
String color = "红色的";
void run(){
System.out.println(color + name + "在欢乐地奔跑");
}
}
class AnonymousInstance {
public static void main(String[] args) {
//正常创建对象的方法
Car redAudi = new Car();
redAudi.run();
//匿名对象使用方式一:当对对象的方法只调用一次时,可以用匿名对象来完成
new Car().name = "被黑客改了名字的奥迪";
new Car().run();//即使改了名字,这句话输出的结果还是:红色的奥迪在欢乐地奔跑
//因为new Car().run();在创建之前,new Car().name = "被黑客改了名字的小土车";在内存中就已经被销毁了,匿名对象调用field没意义
changeCar(redAudi);//程序将会输出:黑色的奥迪在欢乐地奔跑
}
//匿名对象使用方式二:可以将匿名对象作为实际参数进行传递。
public static void changeCar(Car c){
c.color = "黑色的";
c.run();
}
}
Person p = new Person("张飞",20);这句话都做了什么
1,因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
2,执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
3,在堆内存中开辟空间,分配内存地址。
4,在堆内存中建立对象的特有属性。并进行默认初始化。
5,对属性进行显示初始化。
6,对对象进行构造代码块初始化。
7,对对象进行对应的构造函数初始化。
8,将内存地址付给栈内存中的p变量。
单例设计模式
单例设计模式的作用:解决一个类在内存只存在一个对象。
保证对象唯一的目的:
1,为避免其他程序过多建立该类对象,先禁止其他程序建立该类对象
2,又必须u让其他程序可以访问到该类对象,只好在本类中,自定义一个对象
3,为方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
创建单例设计模式的步骤:
1,将构造函数私有化
2,在类中创建一个本类对象
3,提供一个可以获取到该对象的方法
1.饿汉式(Eager)
这个是先初始化对象,称为:饿汉式。
class Singleton{
//1,将构造函数私有化。
private Singleton(){
}
//2,在类中创建一个本类对象
private static Singleton s = new Singleton();//静态方法只能访问静态变量,故此处只能是static。类和对象都可以属于field
//3,提供一个可以获取到该对象的方法
public static Singleton getInstance()
//需要通过该方法才能访问对象,但是方法被调用只能通过对象、类名两种方式
//现在没有对象,故需要用类名调用,必须是static方法
{
return s;
}
}
class SingleDemo {
Singleton sgt = Singleton.getInstance();//不能再通过new来创建对象了,只能通过调用Singleton中的getInstance方法
}
2.懒汉式(Lazy)
对象是方法被调用时,才初始化,也叫做对象的延时加载。称为:懒汉式。
Single类进内存,对象还没有存在,只有当调用了getInstance方法时,才建立对象。
class Singleton2
{
//1,将构造函数私有化。
private Singleton2(){}
//2,在类中创建一个本类对象
private static Singleton2 s = null;
//3,提供一个可以获取到该对象的方法
public static Singleton2 getInstance()
{
if(s==null)
{
synchronized(Singleton2.class)//多线程部分的内容
{
if(s==null)
s = new Singleton2();
}
}
return s;
}
}
类的修饰符
1.Static
Static一言以蔽之:是属于类自带的
用法:是一个修饰符,用于修饰成员(成员变量,成员函数).
当成员被静态修饰后,就多了一个调用方式,除了可以被对象调用外,
还可以直接被类名调用。类名.静态成员。
static特点:
1,随着类的加载而加载,随着类的消失而消失。说明它的生命周期最长。
2,优先于的对象存在(静态先存在,对象后存在)
3,被所有对象所共享
4,可以直接被类名所调用
实例变量和类变量的区别:
1,存放位置。
类变量:随着类的加载而存在于方法区中
实例变量:随着对象的建立而存在于堆内存中
2,生命周期:
类变量:生命周期随着类的消失而消失
实例变量:生命周期随着对象的消失而消失
静态使用注意事项:
1,静态方法只能访问静态成员(非静态方法既可以访问静态也可以访问非静态)
2,静态方法中不可以定义this,super关键字。(因为静态优先于对象存在。所以静态方法中不可以出现this)
3,主函数是静态的。
静态的利弊
利处:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中都存储一份。
可以直接被类名调用。
弊端:生命周期过长。
访问出现局限性。(静态虽好,只能访问静态。)
class Bike{
public static String color = "red";
public static void run(){
System.out.println("自行车跑啊跑");
}
}
public class StaticDemo {
public static void main(String[] args) {
//普通调用方式调用Bike的field中的color
Bike giant = new Bike();
System.out.println(giant.color);
//类变量的另一种调用方式
System.out.println(Bike.color);//效果与第一种调用方式相同
//调用类变量的方法
Bike.run();
}
}
2.Static Initializer:静态代码块
静态代码块。
格式:
static
{
静态代码块中的执行语句。
}
特点:随着类的加载而执行,只执行一次,并优先于主函数。
用于给类进行初始化的。
即使构造代码块定义在静态代码块之前,还是先执行静态代码块的内容
class StaticCode
{
int num = 9;
//构造函数
StaticCode()
{
System.out.println("构造函数的内容被打印了");
}
//构造代码块
{
System.out.println("构造代码块的内容被打印了 " + this.num);
}
//静态代码块
static
{
System.out.println("静态代码块的内容被打印了");
}
//构造函数超载
StaticCode(int x)
{
System.out.println("构造函数超载的内容被打印了");
}
//静态方法
public static void show()
{
System.out.println("show run");
}
}
class StaitcInitializer
{
static
{
//System.out.println("b");
}
public static void main(String[] args)
{
new StaticCode(4);//因为没有创建过StaticCode的对象,"构造函数的内容被打印了"这行不会被打印
}
}
/*
程序输出结果
静态代码块的内容被打印了:即使构造代码块定义在静态代码块之前,还是先执行静态代码块的内容
构造代码块的内容被打印了 9
构造函数超载的内容被打印了
*/
类的扩展
1.抽象类
抽象类的特点:
1,抽象方法一定在抽象类中。
2,抽象方法和抽象类都必须被abstract关键字修饰。
3,抽象类不可以用new创建对象。因为调用抽象方法没意义。
4,抽象类中的抽象方法要被使用,必须由子类复写起
所有的抽象方法后(非抽象方法可以不用实现),建立子类对象调用。
5,如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
abstract 关键字旁边,和哪些关键字不能共存?
final:被final修饰的类不能有子类。而被abstract修饰的类一定是一个父类。
private: 抽象类中的私有的抽象方法,不被子类所知,就无法被复写。而抽象方法出现的就是需要被复写。
static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。可是抽象方法运行没意义。
模版方法:
在定义功能时,功能的一部分是确定的,但是有一部分是不确定,而确定的部分在使用不确定的部分,
那么这时就将不确定的部分暴露出去。由该类的子类去完成。
abstract class GetTime//把GetTime的功能定义成抽象类,模板设计模式
{
public final void getTime()//之所以要使用final关键字,是为了不让getTime被复写
{
long start = System.currentTimeMillis();
runcode();
long end = System.currentTimeMillis();
System.out.println("毫秒:"+(end-start));
}
public abstract void runcode();//要求子类复写该抽象方法,以得到不同代码相对应的运行时间
}
class SubTime extends GetTime
{
public void runcode()//开始复写父类方法
{
for(int x=0; x<4000; x++)
{
System.out.print(x);
}
}
}
class AbstractDemo
{
public static void main(String[] args)
{
SubTime gt = new SubTime();
gt.getTime();
}
2.接口
接口定义时,格式特点:
1,接口中常见定义:常量,抽象方法。
2,接口中的成员都有固定修饰符。
常量:public static final
方法:public abstract
注意:
1,接口中的成员都是public的。
2,接口是不可以创建对象的,因为有抽象方法。
3,接口需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化。否则子类是一个抽象类。
4,接口可以被类多实现,也是对多继承不支持的转换形式。java支持多实现。
abstract class Student
{
abstract void study();
void sleep()
{
System.out.println("sleep");
}
}
interface Smoking//抽烟不是学生都具有的行为,定义为接口
{
void smoke();
}
class ZhangSan extends Student implements Smoking
{
void study(){}
public void smoke(){}
}
关系
子父类关系特点
1.子父类中变量的特点
如果子类中出现非私有的同名成员变量时,
子类要访问本类中的变量,用this。this:代表的是本类对象的引用。
子类要访问父类中的同名变量,用super。super:代表的是父类对象的引用。
class Fu
{
public String str = "爹的成员";
}
class Zi extends Fu
{
String str = "儿的方法";//一般不会这么定义,因为子类可以从父类中获取
void show()
{
System.out.println(str);//儿的成员
System.out.println(this.str);//儿的成员
System.out.println(super.str);//爹的成员
}
}
class ExtendsDemo {
public static void main(String[] args)
{
Zi z = new Zi();
z.show();
}
}
2.子父类中方法的特点
当子类出现和父类一模一样的函数时,子类对象调用该函数,会运行子类函数的内容。如同父类的函数被覆盖一样。
这种情况是函数的另一个特性:Override(覆盖)
当子类继承父类,沿袭了父类的功能,到子类中,但是子类虽具备该功能,但是功能的内容却和父类不一致,
这时,没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。
覆盖:
1,子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。
2,静态只能覆盖静态。
class Father
{
void methods()
{
System.out.println("爹的方法");
}
}
class Son extends Father
{
void methods()
{
System.out.println("儿的方法");
}
}
class ExtendsDemo2
{
public static void main(String[] args)
{
Son z = new Son();
z.methods();//儿的方法
}
}
3.子父类中构造函数的特点
在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句 super();
super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super();
为什么子类一定要访问父类中的构造函数?
因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。所以子类在对象初始化时,要先访问一下父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。
注意:super语句一定定义在子类构造函数的第一行。
多态(Polymorphism)
1.多态的概念
多态:可以理解为事物存在的多种体现形态。
多态的体现
父类的引用指向了自己的子类对象。
父类的引用也可以接收自己的子类对象。
多态的前提
必须是类与类之间有关系。要么继承,要么实现。
通常还有一个前提:存在覆盖。
多态的利弊
利:多态的出现大大的提高程序的扩展性。
弊:只能使用父类的引用访问父类中的成员。
多态的应用
2.多态引入的目的
最原始版本:为了让Cat类中的不同实例调用eat方法,需要反复创建该类的实例
abstract class Animal
{
abstract void eat();
}
class Cat extends Animal
{
public void eat()
{
System.out.println("吃鱼");
}
public void catchMouse()
{
System.out.println("抓老鼠");
}
}
class Dog extends Animal
{
public void eat()
{
System.out.println("吃骨头");
}
public void watchHome()
{
System.out.println("看家");
}
}
public class PolyDemo {
public static void main(String[] args) {
Cat heimao = new Cat();
heimao.eat();
Cat lanmao = new Cat();
lanmao.eat();
}
}
改进版本:将cat类作为参数,传递给一个专门用来调用Cat类中的eat()方法的方法,提高代码复用性
public class PolyDemo {
public static void main(String[] args) {
Cat heimao = new Cat();
//heimao.eat();
getEatmethod(heimao);
Cat lanmao = new Cat();
//lanmao.eat();
getEatmethod(lanmao);
}
public static void getEatmethod(Cat c){//将cat类作为参数,传递给一个专门用来调用Cat类中的eat()方法的方法
c.eat();
}
}
改进版本2:将Dog类作为参数,传递给一个专门用来调用Dog类中的eat()方法的方法,运用方法的重载
public class PolyDemo {
public static void main(String[] args) {
Cat heimao = new Cat();
getEatmethod(heimao);
Dog xiaohuang = new Dog();
getEatmethod(xiaohuang);//或者直接getEatmethod(new Dog());
}
public static void getEatmethod(Cat c){//将cat类作为参数,传递给一个专门用来调用Cat类中的eat()方法的方法
c.eat();
}
public static void getEatmethod(Dog d){//将Dog类作为参数,传递给一个专门用来调用Dog类中的eat()方法的方法,运用方法的重载
d.eat();
}
}
改进版本3:利用多态性,直接运用父类
public class PolyDemo {
public static void main(String[] args) {
getEatmethod(new Cat());
getEatmethod(new Dog());
}
public static void getEatmethod(Animal a){//直接传入父类即可
//Animal a = new Cat();
//Animal a = new Dog();
a.eat();
}
3.多态的转型
向上转型:类型提升,引用数据类型的提升
Animal a = new Cat();//类型提升。 向上转型。猫的类型提升为Animal
向下转型:猫提升为Animal后,失去了猫的特有方法,如果此时想要再调用猫的特有方法时,如何操作?
Cat c = (Cat)a;//强制将父类的引用a。转成子类类型Cat。向下转型。此时就可以使用c.catchMouse()了
错误代码:把动物转成猫的类型
Animal a = new Animal();
//千万不要出现这样的操作,就是将父类对象转成子类类型。
//我们能转换的是父类引用指向了自己的子类对象时,该引用可以被提升,也可以被强制转换。
//多态自始至终都是子类对象在做着变化。
Cat c = (Cat)a;
4. instanceof
假如没有instanceof,出现类型转换异常时的情况:
public class PolyDemo2 {
public static void main(String[] args) {
method(new Cat());
method(new Dog());//传入method后,等于是把狗强制转换成了猫,会报类型转换异常
}
public static void method(Animal a){
a.eat();
Cat c = (Cat)a;//向下转型,转型后就可以抓老鼠了
c.catchMouse();
}
}
/*
吃鱼
Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to Cat
抓老鼠
吃骨头
at PolyDemo2.method(PolyDemo2.java:40)
at PolyDemo2.main(PolyDemo2.java:35)
*/
有instanceof 时的处理方法
public static void method(Animal a)//Animal a = new Cat();
{
a.eat();
/*小细节:不要把父类写在上面
if(a instanceof Animal)
{
System.out.println("haha");
}
else
*/
if(a instanceof Cat)
{
Cat c = (Cat)a;
c.catchMouse();
}
else if(a instanceof Dog)
{
Dog c = (Dog)a;
c.kanJia();
}
/*
instanceof : 用于判断对象的类型。 对象 intanceof 类型(类类型 接口类型)
*/
}
5.多态中成员的特点