黑马程序员----java基础--面向对象
一、面向对象概述
1:将复杂的事情简单化。
2:面向对象将以前的过程中的执行者,变成了指挥者。
3:面向对象这种思想是符合现在人们思考习惯的一种思想。
以后开发:其实就是找对象使用。没有对象,就创建一个对象。找对象,建立对象,使用对象,维护对象的关系。
面向对象的三大特征:封装、继承、多态。
二、类与对象的关系
类就是:对现实生活中事物的描述。对象:就是这类事物,实实在在存在个体。
映射到java中,描述就是class定义的类。具体对象就是对应java在堆内存中用new建立实体。
描述事物其实就是在描述事物的属性和行为。属性对应是类中变量,行为对应的类中的函数。
其实定义类,就是在描述事物,就是在定义属性和行为。属性和行为共同成为类中的成员(成员变量和成员方法)。
在定义java类的时候可以使用名词提炼法,进行创建类。
三、局部变量和成员变量的区别
作用范围:成员变量作用于整个类中。局部变量变量作用于函数中,或者语句中。
在内存中的位置: 成员变量在堆内存中,因为对象的存在,才在内存中存在,随着对象的建立而建立,随着对象的消失而消失。局部变量存在栈内存中,作用的范围结束,变量空间会自动释放。
初始化值:成员变量有默认初始化值。而局部变量没有默认初始化值。
四、对象内存结构
只要是用new操作符定义的实体就在会堆内存中开辟一个新的空间。
并每一个对象中都有一份属于自己的属性。
通过 对象.对象成员 的方式操作对象中的成员,
对其中一个对象的成员进行了修改。和另一个对象没有关系。
五、匿名对象
匿名对象使用场景:
1.当对方法只进行一次调用的时候,可以使用匿名对象。如:new Person().name = "zhangsan";//使用一次之后就被销毁了。
2.当对象对成员进行多次调用时,不能使用匿名对象。必须给对象起名字。
3.可作为实际参数进行传递;只在堆里面开辟存储区域, 如:method(new Person());
六、基本数据类型参数及引用数据类型参数传递
1.基本数据类型值传递
1 class Demo 2 { 3 public static void main(String[] args) 4 { 5 int x = 4; 6 show(x); 7 System.out.println(x);//4 8 } 9 public static void show(int x) 10 { 11 x = 2; 12 } 13 }
分析说明:主函数在调用show方法,把x=4传递给show方法的形式参数,但是局部变量都存放在show方法的栈内存中,随着函数的结束而结束。所以show方法中的x=2,并没有修改main方法中x的值,故打印的x的值仍然为4。
2.引用数据类型值传递
1 class Demo 2 { 3 int x = 3; 4 public static void main(String[] args) 5 { 6 Demo d = new Demo(); 7 d.x = 10; 8 show(d); 9 System.out.println(d.x);//6 10 } 11 public static void show(Demo d) 12 { 13 d.x = 6; 14 } 15 }
因为show方法修改的是堆内存中的同一对象,自始至终堆里只有一个对象。虽然show方法结束了,但是main方法中的d还指向了对象Demo。
3.数组类型参数传递
1 class Demo 2 { 3 public static void main(String[] args) 4 { 5 int[] arr = new int[2]; 6 show(arr); 7 System.out.println(arr[0]);//1 8 } 9 public static void show(int[] arr) 10 { 11 arr[0]++; 12 } 13 }
因为数组也是引用数据类型,和上面的情况一样。
总结:
注意:私有仅仅是封装的一种表现形式。
之所以对外提供访问方式,就因为可以在访问方式中加入逻辑判断等语句。对访问的数据进行操作。提高代码健壮性。
1 class Person{ 2 //private:私有,是一个权限修饰符,用于修饰 3 //不希望别人直接访问赋值,需要通过私有化把属性进行隐藏 4 private int age ; 5 6 //通过提供set、get公共方法对其访问 7 public void setAge( int a){ 8 //在set方法内可以对属性的赋值进行限制 9 if (a > 0 && a < 100){ 10 age = a; 11 } 12 else 13 System.out .println("非法数值"); 14 } 15 16 public int getAge(){ 17 return age ; 18 } 19 20 void speak(){ 21 System.out .println("age = " + age); 22 } 23 } 24 25 class PersonDemo{ 26 public static void main(String[] args){ 27 Person p = new Person(); 28 //通过其他方式访问 29 p.setAge(56); 30 p.speak(); 31 //赋值不合法,set方法就不允许成功赋值 32 p.setAge(-10); 33 } 34 }
八、构造函数
对象一建立就会调用与之对应的构造函数。
(1)、构造函数特点:
(2)、构造函数的作用:
给对象进行初始化。
注意:
构造函数的小细节:当一个类中没有定义构造函数时,那么系统会默认给该类加入一个空参数的构造函数。当在类中自定义了构造函数后,默认的构造函数就没有了。
(3)、构造函数和一般函数的区别
构造函数是在对象一建立就运行。给对象初始化。而一般方法是对象调用才执行,给是对象添加对象具备的功能。
一个对象建立,构造函数只运行一次。而一般方法可以被该对象调用多次。
(4)、什么时候定义构造函数呢?
当分析事物时,该事物一存在具备一些特性或者行为,那么将这些内容定义在构造函数中。
(5)、构造代码块
作用:给对象进行初始化。对象一建立就运行,而且优先于构造函数执行。
和构造函数的区别:
构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化。构造代码块中定义的是不同对象共性的初始化内容。
九、this关键字
1 class Person 2 { 3 private String name; 4 private int age; 5 Person(int age) 6 { 7 this.age = age; 8 } 9 Person(String name) 10 { 11 this.name = name; 12 } 13 Person(String name,int age) 14 { 15 this.name = name; 16 this.age = age; 17 } 18 } 19 20 class PersonDemo3 21 { 22 public static void main(String[] args) 23 { 24 25 Person p1 = new Person(20); 26 Person p2 = new Person(25); 27 } 28 }
(1)、this关键字
this代表其所在函数所属对象的引用。换言之,this代本类对象的引用。this还可以用于区分局部变量和成员变量同名情况。
this:就代表本类的对象,代表它所在函数所属对象的引用。
简单说:哪个对象在调用this所在的函数,this就代表哪个对象。
this的应用:当定义类中功能时,该函数内部要用到调用该函数的对象时,这时用this来表示这个对象。但凡本类功能内部使用了了本类对象,都用this表示。
(2)、this语句 :用于构造函数之间进行互相调用。
this语句只能定义在构造函数的第一行。因为初始化要先执行。
(3)、总结
this对象后面跟上 . 调用的是成员属性和成员方法(一般方法);
this对象后面跟上 () 调用的是本类中的对应参数的构造函数。
1 class Person{ 2 private String name ; 3 private int age ; 4 5 Person(String name){ 6 //通过this区分成员变量和局部变量 7 this.name = name; 8 } 9 10 Person(String name, int age){ 11 //this语句用于在构造函数中调用其他构造函数 12 this(name); 13 this.age = age; 14 } 15 16 public void speak(){ 17 System.out.println(name + ":" + age); 18 } 19 } 20 21 class PersonDemo{ 22 public static void main(String[] args){ 23 Person p1 = new Person("zhangsan" ); 24 p1.speak(); 25 Person p2 = new Person("lisi" ,23); 26 p2.speak(); 27 } 28 }
十、static(静态)关键字
利处:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中都存储一份。可以直接被类名调用。
弊端:生命周期过长。访问出现局限性。(静态虽好,只能访问静态。)
(5)、成员变量和静态变量的区别:
1,成员变量所属于对象。所以也称为实例变量。
静态变量所属于类。所以也称为类变量。
2,成员变量存在于堆内存中。
静态变量存在于方法区中。
3,成员变量随着对象创建而存在。随着对象被回收而消失。
静态变量随着类的加载而存在。随着类的消失而消失。
4,成员变量只能被对象所调用 。
静态变量可以被对象调用,也可以被类名调用。
所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。
(6)、什么使用静态?
要从两方面下手:因为静态修饰的内容有成员变量和函数。
1.什么时候定义静态变量(类变量)呢?
当对象中出现共享数据时,该数据被静态所修饰。对象中的特有数据要定义成非静态存在于堆内存中。
2.什么时候定义静态函数呢?
当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义成静态的。
(7)、解析主函数(main方法)
主函数:是一个特殊的函数。作为程序的入口,可以被jvm调用。
主函数的定义:
public:代表着该函数访问权限是最大的。
static:代表主函数随着类的加载就已经存在了。
void:主函数没有具体的返回值。
main:不是关键字,但是是一个特殊的单词,可以被jvm识别。
(String[] arr):函数的参数,参数类型是一个数组,该数组中的元素是字符串。字符串类型的数组。
主函数是固定格式的:jvm识别。
jvm在调用主函数时,传入的是new String[0]。
(8)、静态代码块
1、格式:
static
{
静态代码块中的执行语句。
}
2、特点:随着类的加载而执行,只执行一次,并优先于主函数。用于给类进行初始化的。
执行顺序:(优先级从高到低。)静态代码块>mian方法>构造代码块>构造方法。
其中静态代码块只执行一次。构造代码块在每次创建对象是都会执行。
十一、对象初始化过程
Person p = new Person("zhangsan",20);
该句话都做了什么事情?
1,因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
2,执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
3,在堆内存中开辟空间,分配内存地址。
4,在堆内存中建立对象的特有属性。并进行默认初始化。
5,对属性进行显示初始化。
6,对对象进行构造代码块初始化。
7,对对象进行对应的构造函数初始化。
8,将内存地址付给栈内存中的p变量。
十二、单例设计模式
设计模式:解决某一类问题最行之有效的方法。
单例设计模式:解决一个类在内存只存在一个对象。
比如:多程序读取一个配置文件时,建议配置文件封装成对象。会方便操作其中数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。
(1)、如何保证对象唯一性呢?
1,为了避免其他程序过多建立该类对象。先禁止其他程序建立该类对象
2,还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象。
3,为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
(2)、代码体现
1,将构造函数私有化。
1 private Single(){}
2,在类中创建一个静态的本类对象。
1 private static Single s=new Single();
3,提供一个静态的方法可以获取到该对象。之所以静态因为类名调用。
public static Single getInstance() { return s; }
对于事物该怎么描述,还怎么描述。当需要将该事物的对象保证在内存中唯一时,就将以上的三步加上即可。
(3)、单例设计模式的两种表现形式
1.饿汉式。类一进内存,就已经创建好了对象。
1 class Single 2 { 3 private Single(){}//私有化构造函数。 4 private static final Single s=new Single();//创建私有并静态的本类对象。 5 public static Single getInstance(){//定义公有并静态的方法,返回该对象。 6 return s; 7 } 8 }
之所以不用Single.s;的方式获取Single对象,而采用getInstance获取是因为在getInstance方法中我们可以做一些判断来决定是否返回Single的对象,也就是实现了对单例对象的可控。所以,给Single的构造方法加上了private限制,禁止使用者直接采用Single.s;的方式获取。
2.懒汉式。
对象是方法被调用时,才初始化,也叫做对象的延时加载。成为:懒汉式。Single类进内存,对象还没有存在,只有调用了getInstance方法时,才建立对象。
1 class Single 2 { 3 private Single(){} 4 private static Single s=null; 5 public static Single getInstance(){ 6 if(s==null) 7 s=new Single(); 8 return s; 9 } 10 }
注意:
定义单例时,建议使用饿汉式。因为,饿汉式线程安全。
3.懒汉式在同步中的应用
因为懒汉式在操作数据的时候有多条语句在操作,因此有可能会出现安全隐患:线程不同步。通过下面的代码既可以解决线程不同步问题又可以提高代码执行效率
1 class Single 2 { 3 private Single(){} 4 private static Single s=null; 5 public static Single getInstance(){//如果在函数上加上synchronized就出现了每次都判断锁,比较低效,可以通过下面的方法解决。 6 7 if(s==null)//双重判断是否为空可以解决低效问题。 8 { 9 synchronized(Single)//加锁效率降低 10 { 11 if(s==null) 12 s=new Single(); 13 return s; 14 } 15 } 16 } 17 }
十三、继承
(1)、继承的概述
继承是面向对象的重要特征之一。多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承单独的那个类即可。多个类可以称为子类,单独这个类称为父类或者超类。子类可以直接访问父类中的非私有的属性和行为。通过 extends 关键字让类与类之间产生继承关系。继承的出现提高了代码的复用性。继承的出现让类与类之间产生了关系,提供了多态的前提。
注意:千万不要为了获取其他类的功能,简化代码而继承。必须是类与类之间有所属关系才可以继承。所属关系 is a。
(2)、继承注意事项
1、子类可以直接访问父类中的非私有的属性和行为。
2、子类无法继承父类中私有的内容。
3、父类怎么来的?共性不断向上抽取而来的。
(3)、继承的特点
1.Java只支持单继承,不支持多继承。一个类只能有一个父类,不可以有多个父类。
因为多继承容易出现问题。两个父类中有相同的方法。子类到底要执行哪一个是不确定的。所以java不支持多继承,但将这种机制换了另一个种安全的方式来体现,多实现。
2.Java支持多层继承(继承体系)。 C继承B,B继承A,就会出现继承体系。
多层继承出现的继承体系中,通常看父类中的功能,了解该体系的基本功能,建立子类对象,即可使用该体系功能。
(4)、如何使用一个继承体系中的功能呢?
想要使用体系,先查阅体系父类的描述,因为父类中定义的是该体系中共性功能。通过了解共性功能,就可以知道该体系的基本功能。那么这个体系已经可以基本使用了。
那么在具体调用时,要创建最子类的对象,为什么呢?一是因为有可能父类不能创建对象,二是创建子类对象可以使用更多的功能,包括基本的也包括特有的。
简单一句话:查阅父类功能,创建子类对象使用功能。
十四、super关键字和函数覆盖
当子父类中出现一样的属性时,子类类型的对象,调用该属性,值是子类的属性值。
注意:子父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了。
1 class Fu{ 2 private int num = 4; 3 4 public int getNum(){ 5 return num ; 6 } 7 } 8 9 class Zi extends Fu{ 10 private int num = 5; 11 12 void show(){ 13 System.out.println(this.num + "..." + super.getNum()); 14 } 15 } 16 17 class ExtendDemo{ 18 public static void main(String[] args){ 19 Zi z = new Zi(); 20 z.show(); 21 } 22 }
运行结果:5.....4.
2.成员函数
当子类出现和父类一模一样的函数时,当子类对象调用该函数,会运行子类函数的内容。如同父类的函数被覆盖一样。这种情况是函数的另一个特性:重写(覆盖)
当子类继承父类,沿袭了父类的功能,到子类中,但是子类虽具备该功能,但是功能的内容却和父类不一致,这时,没有必要定义新功能,而是使用覆盖特殊,保留父类的功能定义,并重写功能内容。
在子类覆盖方法中,继续使用被覆盖的方法可以通过super.函数名获取。
覆盖:
1.子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。
2.静态只能覆盖静态。
3.父类中的私有方法不可以被覆盖。父类为static的方法无法覆盖。
4.重载:只看同名函数的参数列表。重写:子父类方法要一模一样。
什么时候使用覆盖操作?
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以复写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容。
1 class Tel 2 { 3 void show() 4 { 5 System.out.println("number"); 6 } 7 8 } 9 10 class NewTel extends Tel 11 { 12 void show() 13 { 14 super.show(); 15 System.out.println("name"); 16 System.out.println("pic"); 17 } 18 } 19 class ExtendDemo{ 20 public static void main(String[] args){ 21 NewTel t = new NewTel (); 22 t.show(); 23 } 24 }
3.构造函数
在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的所有构造函数默认第一行有一条隐式的语句 super();
super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super()。
为什么子类一定要访问父类中的构造函数?
因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。所以子类在对象初始化时,要先访问一下父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。
注意:super语句一定定义在子类构造函数的第一行。
结论:
子类的所有的构造函数,默认都会访问父类中空参数的构造函数。因为子类每一个构造函数内的第一行都有一句隐式super();
当父类中没有空参数的构造函数时,子类必须手动通过super语句形式来指定要访问父类中的构造函数。
当然:子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。
子类中至少会有一个构造函数会访问父类中的构造函数。
1 class Fu{ 2 int num ; 3 Fu(){ 4 num = 10; 5 System.out.println("A fu run" ); 6 } 7 Fu(int x){ 8 System.out.println("B fu run..." + x); 9 } 10 } 11 12 class Zi extends Fu{ 13 Zi(){ 14 //super();//默认调用的就是父类中的空参数的构造函数 15 System.out.println("C zi run " + num); 16 } 17 Zi(int x){ 18 super(4); 19 System.out.println("D zi run " + x); 20 } 21 } 22 23 class ExtendDemo{ 24 public static void main(String[] args){ 25 new Zi(); 26 System.out.println("-------------------" ); 27 new Zi(6); 28 } 29 }
运行结果:
A fu run
C zi run 10
B fu run...4
D zi run 6
注:
1.子类构造函数中如果使用this调用了本类构造函数,那么默认的super();就没有了,因为super和this都只能定义在第一行,所以只能有一个。
但是可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数。
2.super语句必须要定义在子类构造函数的第一行!因为父类的初始化动作要先完成。
1 class Fu{ 2 Fu(){ 3 show(); 4 } 5 void show(){//被子类覆盖了,所以运行的是子类的show方法 6 System.out.println("fu show" ); 7 } 8 } 9 class Zi extends Fu{ 10 int num = 8; 11 Zi(){ 12 //隐式的super(); 13 //通过super初始化父类内容时,子类的成员变量并未显示初始化, 14 //等super()父类初始化完毕后,才进行子类的成员变量显示初始化 15 } 16 void show(){ 17 System.out.println("zi show..." + num); 18 } 19 } 20 class ExtendsDemo{ 21 public static void main(String[] args){ 22 Zi z = new Zi(); 23 z.show(); 24 } 25 }
运行结果:
zi show...0
zi show...8
(2)、当继承出现后,子类的实例化过程
以Person p = new Person();为例:
1. JVM会读取指定的路径下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有直接的父类的情况下)。
2. 在内存中开辟空间,并分配地址。
3. 并在对象空间中,对对象的属性进行默认初始化。
4. 调用对应的构造函数进行初始化。
5. 在构造函数中,第一行会先到调用父类中构造函数进行初始化。
6. 父类初始化完毕后,再对子类的属性进行显示初始化。
7. 再进行子类构造函数的特定初始化。
8. 初始化完毕后,将地址值赋值给引用变量。
十五、final关键字
继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是需要被继承,或者复写的。这时如何解决问题呢?介绍一个关键字,final。
当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字。方便于阅读。而这个值不需要改变,所以加上final修饰。
作为常量:常量的书写规范所有字母都大写,如果由多个单词组成。单词间通过_连接。
final : 最终。作为一个修饰符,
1.可以修饰类,函数,变量。
2.被final修饰的类不可以被继承。为了避免被继承,被子类复写功能。
3.被final修饰的方法不可以被复写。
4.被final修饰的变量是一个常量只能赋值一次,既可以修饰成员变量,有可以修饰局部变量。
5.内部类定义在类中的局部位置上是,只能访问该局部被final修饰的局部变量。
十六、抽象类
5.抽象方法只有方法声明,没有方法体。
格式:修饰符 abstract 返回值类型 函数名(参数列表) ;
需求:公司中普通员工有姓名,工号,薪水,工作内容。项目经理除了有姓名,工号,薪水,还有奖金,工作内容。
1 abstract class Employee 2 { 3 private String name; 4 private String id; 5 private double pay; 6 Employee(String name,String id, double pay){ 7 this.name=name; 8 this.id=id; 9 this.pay=pay; 10 } 11 public abstract void work();//因为每种员工的具体工作内容不一样,所以可以定义成抽象方法,让每个子类去复写具体内容。 12 } 13 class Pro extends Employee 14 { 15 Pro(String name,String id,double pay){ 16 super(name,id,pay); 17 } 18 public void work(){ 19 System.out.println("Pro...work!!"); 20 } 21 } 22 class Manager extends Employee 23 { 24 private double bonus;//Manager特有的属性 25 Manager(String name,String id,double pay,double bonus){ 26 super(name,id,pay); 27 this.bonus=bonus; 28 } 29 public void work(){ 30 System.out.println("Manager....run!!"); 31 } 32 } 33 class AbstractDemo 34 { 35 public static void main(String[] args){ 36 new Pro("小王","1024531",4568).work(); 37 new Manager("赵总","124561",4515648,45126).work(); 38 } 39 }
(5)、抽象类和一般类的区别
抽象类和一般类没有太大的不同。该如何描述事物,就如何描述事物,只不过,该事物出现了一些看不懂的东西。这些不确定的部分,也是该事物的功能,需要明确出现。但是无法定义主体。通过抽象方法来表示。抽象类比一般类多个了抽象函数。就是在类中可以定义抽象方法。抽象类不可以实例化。特殊:抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。
抽象类中是否有构造函数?
有,用于给子类对象进行初始化。
抽象关键字abstract不可以和哪些关键字共存?
final:被final修饰的类不能有子类。而被abstract修饰的类一定是一个父类。
private: 抽象类中的私有的抽象方法,不被子类所知,就无法被复写。而抽象方法出现的就是需要被复写。
static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。可是抽象方法运行没意义。
抽象类中可不可以没有抽象方法?
可以,但是很少见。目的就是不让该类创建对象,AWT的适配器对象就是这种类。通常这个类中的方法有方法体,但是却没有内容。
十七、接口
接口:初期理解,可以认为是一个特殊的抽象类。当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。
class用于定义类
interface 用于定义接口。
格式:interface {}
(1)、接口的特点
接口中的成员修饰符是固定的:
成员常量:public static final
成员函数:public abstract
由此得出结论,接口中的成员都是公共的权限。
接口是对外暴露的规则。接口是程序的功能扩展。接口可以用来多实现。类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口。接口与接口之间是继承关系,而且接口可以多继承。
接口:是不可以创建对象的,因为有抽象方法。需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化。否则子类是一个抽象类。
一个类在继承另一个类的同时,还可以实现多个接口。
(2)、抽象类和接口的异同点
相同点:
都是不断向上抽取而来的。
不同点:
1. 抽象类需要被继承,而且只能单继承。
接口需要被实现,而且可以多实现。
2. 抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法。
接口中只能定义抽象方法,必须由子类去实现。
3. 抽象类的继承,是is a关系,定义该体系的基本共性内容。
接口的实现是like a关系。
4.抽象类的成员修饰符可以自定义。
接口中的成员修饰符是固定的。全都是public的。
1 //抽象学生类 2 abstract class Student 3 { 4 //抽象的学习方法 5 abstract void study(); 6 //共性内容非抽象的睡觉方法 7 void sleep() 8 { 9 System.out.println("sleep"); 10 } 11 } 12 13 //接口,吸烟 14 interface Smoking 15 { 16 void smoke(); 17 } 18 19 //Zhangsan这个对象继承学生类,实现吸烟接口 20 class Zhangsan extends Student implements Smoking 21 { 22 //复写学习方法 23 void study() 24 { 25 System.out.println("Zhangsan_study"); 26 } 27 28 //复写吸烟方法 29 public void smoke() 30 { 31 System.out.println("Zhangsan_smoking"); 32 } 33 } 34 35 //Lisi是好学生,不吸烟 36 class Lisi extends Student 37 { 38 //复写学习方法 39 void study() 40 { 41 System.out.println("Lisi_study"); 42 } 43 } 44 45 46 class InterfaceDemo 47 { 48 public static void main(String[] args) 49 { 50 Zhangsan z = new Zhangsan(); 51 z.study(); 52 z.smoke(); 53 new Lisi().study(); 54 } 55 }
十八、多态
定义:可以理解为事物存在的多种体现形态。
(1)、多态的体现
父类的引用指向了自己的子类对象。
父类的引用也可以接收自己的子类对象。
(2)、多态的前提
必须是类与类之间有关系。要么继承,要么实现。
通常还有一个前提:存在覆盖。
(3)、多态的好处和弊端
多态的出现大大的提高程序的扩展性。前期定义的代码可以使用后期的内容。但是前期定义的内容不能使用(调用)后期子类的特有内容。
1 abstract class Animal 2 { 3 public abstract void eat(); 4 } 5 class Cat extends Animal 6 { 7 public void eat() 8 { 9 System.out.println("吃鱼"); 10 } 11 public void catchMouse() 12 { 13 System.out.println("抓老鼠"); 14 } 15 } 16 17 class Dog extends Animal 18 { 19 public void eat() 20 { 21 System.out.println("吃骨头"); 22 } 23 public void kanJia() 24 { 25 System.out.println("看家"); 26 } 27 } 28 29 class Pig extends Animal 30 { 31 public void eat() 32 { 33 System.out.println("饲料"); 34 } 35 public void gongDi() 36 { 37 System.out.println("拱地"); 38 } 39 } 40 class Function 41 { 42 public static void function(Animal a){ 43 a.eat(); 44 if(a instanceof Cat){ 45 Cat c=(Cat)a; 46 c.catchMouse(); 47 } 48 if(a instanceof Dog){ 49 Dog d=(Dog)a; 50 d.kanJia(); 51 } 52 if(a instanceof Pig){ 53 Pig p=(Pig)a; 54 p.gongDi(); 55 } 56 } 57 } 58 class DuoTaiDemo 59 { 60 public static void main(String[] args) 61 { 62 Function.function(new Cat()); 63 Function.function(new Dog()); 64 Function.function(new Pig()); 65 66 //Animal a = new Cat();//类型提升。 向上转型。 67 //a.eat(); 68 69 //如果想要调用猫的特有方法时,如何操作? 70 //强制将父类的引用。转成子类类型。向下转型。 71 //Cat c = (Cat)a; 72 //c.catchMouse(); 73 74 //千万不要出现这样的操作:就是将父类对象转成子类类型。 75 //我们能转换的是父类引用指向了自己的子类对象时,该应用可以被提升,也可以被强制转换。 76 //多态自始至终都是子类对象在做着变化。 77 } 78 }
instanceof :用于判断对象的具体类型,只能用于引用数据类型判断,通常在向下转型前用于健壮性的判断。
(4)、多态时,成员的特点
1. 成员变量
编译时:参考引用型变量所属的类中是否有调用的成员变量。有,编译通过,没有,编译失败。
运行时:参考引用型变量所属的类中是否有调用的成员变量,并运行该所属类中的成员变量。
简单说:编译和运行都参考等号的左边。
1 class Fu{ 2 int num = 3; 3 } 4 5 class Zi extends Fu{ 6 int num = 4; 7 } 8 9 class DuoTaiDemo{ 10 public static void main(String[] args){ 11 Zi f1 = new Zi(); 12 System.out.println(f1.num); 13 Fu f2 = new Zi(); 14 System.out.println(f2.num); 15 } 16 }
运行结果:
4
3
2. 成员函数(非静态)
编译时:参考引用型变量所属的类中是否有调用的函数。有,编译通过。没有,编译失败。
运行时:参考的是对象所属的类中是否有调用的函数。
简单说:编译看左边,运行看右边。
1 class Fu{ 2 void show(){ 3 System.out.println("fu show"); 4 } 5 } 6 7 class Zi extends Fu{ 8 void show(){ 9 System.out.println("zi show"); 10 } 11 } 12 13 class DuoTaiDemo{ 14 public static void main(String[] args){ 15 Fu f = new Zi(); 16 f.show();//子类的方法把父类的方法覆盖了 17 } 18 }
运行结果:
zi show
3. 静态函数
编译时:参考的是对象所属的类中是否有调用的函数。
运行时:参考的是对象所属的类中是否有调用的函数。
简单说:编译和运行看左边。
1 class Fu{ 2 static void method(){ 3 System.out.println("fu static method"); 4 } 5 } 6 7 class Zi extends Fu{ 8 static void method(){ 9 System.out.println("zi static method"); 10 } 11 } 12 13 class DuoTaiDemo{ 14 public static void main(String[] args){ 15 Fu f = new Zi(); 16 f.method();// fu static method 17 Fu.method(); 18 } 19 }
运行结果:
fu static method
fu static method
十九、内部类
1.内部类可以直接访问外部类中的成员,包括私有。之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式 外部类名.this
2.外部类要访问内部类,必须建立内部类对象。
1.当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中。可以直接建立内部类对象。
格式:
外部类名.内部类名 变量名 = 外部类对象.内部类对象;
Outer.Inner in = new Outer().new Inner();
2.当内部类在成员位置上,就可以被成员修饰符所修饰。比如,private:将内部类在外部类中进行封装。static:内部类就具备static的特性。
当内部类被static修饰后,只能直接访问外部类中的static成员。出现了访问局限
在外部其他类中,如何直接访问static内部类的非静态成员呢?
new Outer.Inner().function();
在外部其他类中,如何直接访问static内部类的静态成员呢?
Outer.Inner.function();
注意:当内部类中定义了静态成员,该内部类必须是static的。
当外部类中的静态方法访问内部类时,内部类也必须是static的。
1、不可以被成员修饰符修饰
2、可以直接访问外部类中的成员,因为还持有外部类中的引用。但是不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。
1 //内部类直接访问外部类成员 2 class Outer{ 3 private int x = 5; 4 5 class Inner 6 { 7 void show(){ 8 System.out.println("show run..." + x); 9 } 10 } 11 } 12 13 class InnerClassDemo{ 14 public static void main(String[] args){ 15 //直接访问外部类中的内部类中的成员 16 Outer.Inner in = new Outer().new Inner(); 17 in.show(); 18 } 19 }
1 被static修饰的内部类只能访问外部类中的静态成员。静态只能访问静态 2 class Outer{ 3 private static int num = 3; 4 5 static class Inner 6 { 7 void show(){ 8 System.out.println("show run..." + num); 9 } 10 } 11 } 12 13 class InnerClassDemo{ 14 public static void main(String[] args){ 15 //如果内部类是静态的,相当于一个外部类 16 Outer.Inner in = new Outer.Inner();//类名调用 17 in.show(); 18 } 19 }
1 如果内部类是静态的,内部类成员也是静态的,可以不用创建内部类对象,直接调用。 2 class Outer{ 3 private static int x = 8; 4 5 static class Inner 6 { 7 static void show(){ 8 System.out.println("show run..." + x); 9 } 10 } 11 } 12 13 class InnerClassDemo{ 14 public static void main(String[] args){ 15 Outer.Inner.show();//类名调用 16 } 17 }
1 内部类定义在局部时,只能访问被final修饰的局部变量 2 class Outer 3 { 4 int x = 7; 5 6 void method(final int a) 7 { 8 final int y = 10; 9 class Inner 10 { 11 void function() 12 { 13 System.out.println(y); 14 } 15 } 16 17 new Inner().function(); 18 19 } 20 } 21 22 class InnerClassDemo3 23 { 24 public static void main(String[] args) 25 { 26 Outer out = new Outer(); 27 out.method(7); 28 out.method(8); 29 } 30 31 }
(3)、什么时候用内部类?
当描述事物时,事物的内部还有事物,该事物用内部类来描述。因为内部事务在使用外部事物的内容。
二十、匿名内部类
1.匿名内部类其实就是内部类的简写格式。
2.定义匿名内部类的前提:
内部类必须是继承一个类或者实现接口。
3.匿名内部类的格式: new 父类或者接口(){定义子类的内容}
4.其实匿名内部类就是一个匿名子类对象。而且这个对象有点胖。 可以理解为带内容的对象。
5.匿名内部类中定义的方法最好不要超过3个。
1 interface Inter{ 2 void show1(); 3 void show2(); 4 } 5 6 class Outer{ 7 public void method(){ 8 Inter in = new Inter(){//当要实现的接口中的方法有多个的时候要定义一个对象名,多态:父类引用指向子类对象 9 public void show1(){ 10 System.out.println("...show1...." ); 11 } 12 public void show2(){ 13 System.out.println("...show2...." ); 14 } 15 }; 16 in.show1(); 17 in.show2(); 18 } 19 } 20 21 class InnerClassDemo{ 22 public static void main(String[] args){ 23 new Outer().method(); 24 } 25 }
练习:
1 interface Inter 2 { 3 void method(); 4 } 5 class Test 6 { 7 //用匿名内部类补足代码 8 } 9 class InnerClassDemo 10 { 11 public static void main(String[] args){ 12 Test.function().method(); 13 } 14 }
答案:
1 interface Inter 2 { 3 void method(); 4 } 5 class Test 6 { 7 static Inter function(){ 8 return new Inter(){ 9 public void method(){//这里的方法权限修饰符必须为public。因为Inter接口中的method方法权限是public 10 System.out.println("mehtod run"); 11 } 12 }; 13 } 14 } 15 class InnerClassDemo 16 { 17 public static void main(String[] args){ 18 /* 19 经过分析可得: 20 Test.function()直接类名调用一个function方法,可以得到function方法是个静态的。 21 而Test.function()可以再调用一个method()方法,可以得到Test.function()运算完返回的必定是个对象。 22 而method()方法是Inter接口中的方法,可以得到Test.function()返回的是Inter对象。 23 */ 24 Test.function().method(); 25 } 26 }
二十一、异常
(1)、异常的概述
异常:就是程序在运行时出现不正常情况。
异常由来:问题也是现实生活中一个具体的事物,也可以通过java的类的形式进行描述。并封装成对象。
其实就是java对不正常情况进行描述后的对象体现。
(2)、异常的分类
对于问题的划分:两种:一种是严重的问题,一种非严重的问题。
对于严重的,java通过Error类进行描述。对于Error一般不编写针对性的代码对其进行处理。
对与非严重的,java通过Exception类进行描述。对于Exception可以使用针对性的处理方式进行处理。这里我们主要学习可处理的部分。
(3)、异常体系
Throwable:该体系的特点就在于Throwable及其所有的子类都具有可抛性。该体系子类的后缀名都是用其父类名作为后缀。
|--Error:一般不针对性处理,直接修改程序
|--Exception:可处理
Throwable中的常用方法:
1 String getMessage();//返回异常详细消息 2 void printStackTrace();//获取异常类名和异常信息,以及异常出现在程序中的位置。jvm默认的异常处理机制,就是在调用printStackTrace方法。 3 void printStackTrace(PrintStream s);//将异常内容保存在本地日志文件中 4 void printStackTrace(PrintWriter s);//将异常内容保存在本地日志文件中 5 String toString();//获取异常类名和异常信息
(3)、异常处理方式
一般异常处理方式有两种:捕捉或者抛出。
1.进行捕捉:
try
{
需要被检测的代码;
}
catch(异常类 变量名)
{
异常处理代码;
}
finally
{
程序一定会执行的代码。主要用于关闭资源。
//System.exit(0);退出jvm,只有这种情况finally不执行
}
2.问题自己也处理不了就需要抛出
直接用throws关键字在函数上声明就可以。
如果捕获到的异常,本功能也处理不了时,可以继续在catch中抛出,最后会抛给java虚拟机的。
try
{
throw new AException();
}
catch(AException e)
{
throw e;
}
如果该异常处理不了,但并不属于该功能出现的异常。可以将异常转换后,在抛出和该功能相关的异常。
或者异常可以处理,当需要将异常产生的和本功能相关的问题提供出去,当调用者知道。并处理。也可以将捕获异常处理后,转换新的异常。
try
{
throw new AException();
}
catch (AException e)
{
// 对AException处理。
throw new BException();//比如电脑冒烟转换成老师课时无法讲课的异常
}
在函数上声明异常:便于提高安全性,让调用出进行处理。不处理编译失败。
(4)、对多异常的处理
1,声明异常时,建议声明更为具体的异常。这样处理的可以更具体。
2,对方声明几个异常,就对应有几个catch块。不要定义多余的catch块。如果多个catch块中的异常出现继承关系,父类异常catch块放在最下面。
建立在进行catch处理时,catch中一定要定义具体处理方式。不要简单定义一句 e.printStackTrace(),也不要简单的就书写一条输出语句。
(5)、throws和throw的区别
throws:用在函数上,用于声明异常类,后面跟的是异常类,用于标识函数暴露出的异常类。可以跟多个异常类,用逗号隔开。
throw:用在函数内,用于抛出异常对象,后面跟的是异常对象。
(6)、Exception异常的分类
对于异常分两种:
1. 编译时被检测异常:只要是Exception和其子类都是,除了特殊子类RuntimeException体系。
这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式。
这样的问题都可以针对性的处理。
2. 编译时不检测异常(运行时异常):就是Exception中的RuntimeException和其子类。
这种问题的发生,无法让功能继续,运算无法运行,更多是因为调用的原因导致的或者引发了内部状态的改变导致的。
那么这种问题一般不处理,直接编译通过,在运行时,让调用者调用时的程序强制停止,让调用者对代码进行调整。
所以自定义异常时,要么继承Exception,要么继承RuntimeException。
(7)、RuntimeException类
Exceptoin中有一个特殊的子类异常RuntimeException 运行时异常。
如果在函数内容抛出该异常,函数上可以不用声明,编译一样通过。如果在函数上声明了该异常。调用者可以不用进行处理。编译一样通过。
之所以不用在函数上声明,是因为不需要让调用者处理。当该异常发生,希望程序停止。因为在运行时,出现了无法继续运算的情况,希望停止程序后,对代码进行修正。
总结:
编译时异常:该异常在编译时,如果没有被处理(没有抛也没有try),编译失败,因为,该异常被标识,代表这里可以被处理。
运行时异常:在编译时,不需要处理,编译器不检查。该异常的发生,建议不处理,让程序直接停止,需要对代码进行修正。
两者的区别是:
编译时异常在函数内被抛出,函数上必须要声明,否则编译失败。声明的原因是:需要调用者对该异常进行处理。
运行时异常如果在函数内被抛出,在函数上不需要声明。不声明的原始:不需要调用者处理,运行时异常发生已经无法再让程序继续运行。所以直接让程序停止,由调用者对代码进行修正。
什么时候用Exception?什么时候用RuntimeException呢?
如果遇到异常的时候能自己处理或者想让调用者处理后程序继续运行就用throws声明出去用Exception。
如果自己处理不了或者不想让调用者处理直接让程序停止掉就在函数内部抛出RuntimeException异常,函数上不用声明让程序停止,让用户修改程序代码。
(8)、自定义异常
因为项目中会出现特有的问题,而这些问题并未被java所描述并封装对象。所以对于这些特有的问题可以按照java的对问题封装的思想。将特有的问题。进行自定义的异常封装。
当在函数内部出现了throw抛出异常对象,那么就必须要给对应的处理动作(RuntimeException除外)。要么在内部try catch处理。要么在函数上声明让调用者处理。
一般情况在,函数内出现异常,函数上需要声明(RuntimeException除外)。
自定义异常的步骤:
1.定义一个子类继承Exception或者RuntimeException,让该类也具备可抛性,让该类具备操作异常的共性方法。可抛性是Throwable这个体系中独有特点。只有这个体系中的类和对象才可以被throws和throw操作。
2.通过throw或者throws进行操作。
当要定义自定义异常信息的时候,可以使用父类已经定义好的功能,把异常信息通过构造函数传递给父类。这时候我们会直接在子类的构造函数中用super()传递给父类。
那么就可以直接通过getMessage方法获取自定义的异常信息。
如:
1 class MyException extends Exception 2 { 3 MyException(String msg){ 4 super(msg); 5 } 6 }
自定义异常时:如果该异常的发生,无法在继续进行运算,就让自定义异常继承RuntimeException。
(9)、try catch finally的几种组合形式
第一个格式:
try
{
}
catch ()
{
}
第二个格式:
try
{
}
catch ()
{
}
finally
{
}
第三个格式:
try
{
}
finally
{
}
记住一点:catch是用于处理异常。如果没有catch就代表异常没有被处理过,如果该异常是检测时异常。那么必须声明。
(10)、异常在子父类覆盖中的体现
1.子类在覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或者该异常的子类。
2.如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集。
3.如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常。如果子类方法发生了异常。就必须要在内部进行try处理。绝对不能抛。
(11)、异常的好处
1,将问题进行封装。
2,将正常流程代码和问题处理代码相分离,方便于阅读。
1 /* 2 毕老师用电脑上课。 3 4 比如问题是: 电脑蓝屏;电脑冒烟。 5 6 可是当冒烟发生后,出现讲课进度无法继续。 7 8 冒烟产生后转换成讲师的问题:课时计划无法完成。 9 */ 10 class LanPingException extends Exception 11 { 12 LanPingException(String msg){ 13 super(msg); 14 } 15 } 16 class MaoYanException extends Exception//这里可以继承RuntimeException,因为异常发生后我们无法处理 17 { 18 MaoYanException(String msg){ 19 super(msg); 20 } 21 } 22 class NullPlanException extends Exception 23 { 24 NullPlanException(String msg){ 25 super(msg); 26 } 27 } 28 class Computer 29 { 30 private int flag=2; 31 public void run()throws LanPingException,MaoYanException{ 32 if(flag==2) 33 throw new LanPingException("电脑蓝屏了"); 34 if(flag==3) 35 throw new MaoYanException("电脑冒烟了"); 36 System.out.println("电脑正常运行"); 37 } 38 public void reset(){ 39 flag=1; 40 System.out.println("电脑重新启动"); 41 } 42 43 } 44 class Teacher 45 { 46 private String name; 47 private Computer comp; 48 Teacher(String name,Computer comp){ 49 this.name=name; 50 this.comp=comp; 51 } 52 public void speak()throws NullPlanException{ 53 try 54 { 55 comp.run(); 56 } 57 catch (LanPingException e)//对电脑蓝屏的处理方式 58 { 59 System.out.println(e.toString()); 60 comp.reset(); 61 try{comp.run();}catch(Exception ex){ex.printStackTrace();} 62 } 63 catch(MaoYanException e)//对电脑冒烟的处理方式 64 { test(); 65 throw new NullPlanException("课时无法继续"+e.getMessage());//把电脑的问题转换成老师的问题 66 } 67 System.out.println("老师开始讲课"); 68 } 69 public void test(){ 70 System.out.println("上自习"); 71 } 72 } 73 class ExceptionDemo 74 { 75 public static void main(String[] args) 76 { 77 Teacher t=new Teacher("王老师",new Computer()); 78 try 79 { 80 t.speak(); 81 } 82 catch (NullPlanException e) 83 { 84 System.out.println(e.toString()); 85 System.out.println("换老师或者放假"); 86 } 87 88 } 89 }
1 /* 2 有一个圆形和长方形。 3 都可以获取面积。对于面积如果出现非法的数值,视为是获取面积出现问题。 4 问题通过异常来表示。 5 */ 6 abstract class Acreage//定义一个抽象接口,因为每种形状的面积计算方式不一样 7 { 8 abstract void getArea(); 9 } 10 class Circle extends Acreage 11 { 12 private static final double PI=3.14; 13 private double radius; 14 Circle(double radius){ 15 if(radius<=0) 16 throw new NoValueException("出现非法数值:radius="+radius); 17 this.radius=radius; 18 } 19 public void setRadius(double radius){ 20 this.radius=radius; 21 } 22 public double getRadius(){ 23 return radius; 24 } 25 public void getArea(){ 26 System.out.println("圆的面积="+radius*radius*PI); 27 } 28 } 29 class Rec extends Acreage 30 { 31 private double len; 32 private double wid; 33 Rec(double len,double wid){ 34 if(len<=0 || wid<=0) 35 throw new NoValueException("出现非法数值:len="+len+", wid="+wid); 36 this.len=len; 37 this.wid=wid; 38 } 39 public void setLen(double len){ 40 this.len=len; 41 } 42 public double getLen(){ 43 return len; 44 } 45 public void setWid(double wid){ 46 this.wid=wid; 47 } 48 public double getWid(){ 49 return wid; 50 } 51 public void getArea(){ 52 System.out.println("长方形的面积="+len*wid); 53 } 54 } 55 class NoValueException extends RuntimeException 56 { 57 NoValueException(String message) 58 { 59 super(message); 60 } 61 } 62 class ExceptionTest 63 { 64 public static void main(String[] args) 65 { 66 Circle c=new Circle(-42.5);//RuntimeException异常程序不用处理,直接让程序停掉 67 c.getArea(); 68 Rec r=new Rec(4,6); 69 r.getArea(); 70 } 71 }