本文内容是根据毕晓东老师的视频教程总结而得。包括面向对象概念、类与对象的关系、封装、构造函数、this关键字、static关键字、单例设计模式、继承、多态、内部类、异常、包等java基础知识。
1、面向对象
- 面向对象是相对面向过程而言
- 面向对象和面向过程都是一种思想
- 面向过程强调的是功能、行为
- 面向对象:将功能封装进对象,强调具备了功能的对象
- 面向对象是基于面向过程的
面向过程例子:
把大象放进冰箱里分为以下步骤:把冰箱门打开;把大象放进去;关上冰箱门(强调过程和过程中所涉及的行为(强调行为、动作、过程))。
用面向对象思想考虑:无论是打开冰箱,放进大象,关闭冰箱,所有操作都是操作冰箱这个对象,所以只需要将所有功能都定义在冰箱这个对象上,冰箱上就有打开、存储、关闭得所有功能 。
由上可知,
面向对象是一种思想,能让复杂问题简单化,程序员不需要了解具体的实现过程,只需要指挥对象去实现功能。
例,面试官面试面试者就是面向对象的体现,面试官需要找具有编程功能的对象(面试者),而我就是一个具有编程功能的对象,面试完后,让面试者去编程,面试者就去实现编程功能。
面向过程和面向对象图示:
2、类与对象的关系
面向对象三大基本特征:封装、继承、多态。而面向对象的过程就是找对象、建立对象、使用对象、维护对象的关系的过程。
2.1类和对象的关系:
类:是对现实生活中事物的描述。
对象:就是这类事物,实实在在存在的个体。
如现实生活中的对象:张三、李四。想要描述对象张三和李四,就需要提取对象中的共性内容。即对具体对象的共性的抽取。在描述时,这些对象的共性有:姓名、性别、年龄、学习java功能。而每个学员又有自己独有的姓名、性别、年龄、学习方式。
在java中,描述是用类的方式实现,而类是通过new操作符所产生的实体来实现,而这个实体在堆内存中再映射到java中去。简单的说,描述就是class定义的类,具体对象就是对应java在堆内存中用new建立的实体
描述事物其实就是在描述事物的属性和行为(方法),属性对应的是类中的变量,行为对应的是类中的函数(方法)。其实定义类,就是在描述事物,就是在定义属性和行为,属性和行为共同成为类中的成员(成员变量和成员方法)。
示例:描述汽车。
package com.vnb.javabase;
public class Car {
//描述颜色
String color="red";
//描述轮胎数
int num = 4;
//运行行为
void run(){
System.out.println("color:"+color+"; 轮胎数:"+num);
}
}
class CarDemo{
public static void main(String[] args) {
//生产汽车。在java中通过new操作符来完成,其实就是在堆内存中产生一个实体
//car引用型变量(句柄),car就是一个类类型变量。类类型变量指向对象(该类产生的实体)
//堆内存中有默认初始化值(color=null);而"red"称为显示初始化值;
Car car = new Car();
//需求:将已有车的颜色改为蓝色,指挥该对象做使用。在java中指挥方式是,对象.对象成员
car.color="blue";
//让车行驶起来
car.run();
}
}
生产汽车类在内存中的图示及解析:
如上图,首先在栈内存中有一个Car c;然后在堆内存中new Car(),默认初始化color为null,num为0;再将color设置显示初始化值为”red”,将num设置显示初始化值为4;将new Car()产生的地址值0x0099赋给栈内存,再由栈指向堆中的0x0099的地址。
生产两辆车(建立多个对象)在内存中的图示及解析:
如上图所示,建立多个对象的过程:首先在栈中初始化c,通过new Car()方式在堆中建立Car对象,初始化color为null,num为0,再将color赋值为”red”,将num赋值为4。将new Car()产生的地址值0x0099赋给栈内存,再由栈指向堆中的0x0099的地址。
在栈中初始化C1,通过new Car()方式在堆中建立Car对象,初始化color为null,num为0,再将color赋值为”red”,将num赋值为4。将new Car()产生的地址值0x0045赋给栈内存,再由栈指向堆中的0x0045的地址。
此处产生的是两个对象对应的两个地址值。
多个引用指向同一个对象的图示及解析:
如上图所示,首先在栈中初始化c,通过new Car()方式在堆中建立Car对象,初始化color为null,num为0,再将color设置显示初始化值为”red”,将num设置显示初始化值为4。将new Car()产生的地址值0x0078赋给c的栈内存,再由栈指向堆中的0x0078的地址。再将堆中num的值改为5(
因为成员变量在堆内存中
);
建立第二个引用时,首先在栈中初始化c1,然后将c的地址值0x0078赋给c1,并且c1指向堆中的0x0078所引用的对象,再将堆中的对象的color改为green。由此,无论在c还是c1引用上修改值后,指向的都是堆内存中的对象地址值0x0078。所以打印出的值为:5,green。
2.2成员变量和局部变量的区别:
作用范围:成员变量作用于整个类中。局部变量作用于方法中或者语句中。
在内存中的位置:成员变量在堆内存中,因为对象的存在,才在内存中存在;局部变量存在栈内存中。
2.3匿名对象:
匿名对象是对象的简化形式。有两种使用情况:
当对对象方法仅进行一次调用时;匿名对象可以作为实际参数进行传递
。
例如:
Car c = new Car();
c.num = 5;
可简化为:new Car().num = 5;
new Car().color = “red”;
new Car().run();
匿名对象和非匿名对象在内存中存储方式区别:
如上图,匿名对象和非匿名对象区别(
注:匿名对象在栈中无数据
)。使用匿名对象时,当new Car().num = 5;执行完后创建的对象就成为垃圾;new Car().color = “red”执行完后,此对象也会成为垃圾,因此匿名对象调用属性是没有意义的。但是,new Car().run()有意义。
综上所述,
匿名对象调用属性无意义,调用方法有意义
。
匿名对象的两种使用方式:
- 匿名对象使用方式一:当对对象的方法只调用一次时,可以用匿名对象来完成,这样比较简化;但是,如果对一个对象进行多个成员调用,必须给这个对象起名字。例如:
Car c = new Car();
c.run();
c.num=4;
new Car().run();
- 匿名对象使用方式二:可以将匿名对象作为实际参数进行传递。例如:
需求:汽车修配厂,对汽车进行改装,将来的车都改成黑色且只有三个轮胎。
main(){
Car c = new Car();
show(c);
}
public static void show(Car c){
c.num = 3;
c.color = “black”;
c.run();
}
上例的内存图示及解析:
执行main方法后,首先会再栈内存中有一个Car c的引用,然后会在堆内存中new一个Car对象,生成对应的地址值0x0034;当执行show()方法时,会在show方法中再建立一个引用c(注意连个引用不同),再将main方法中c的地址值通过匿名类参数形式传递给show()方法,因此两个引用指向的是同一个地址0x0034。匿名对象传入show()方法时,当show方法执行完后,栈中show()指向的内存空间将会成为垃圾,其对应指向的对象地址和对象也成为垃圾。
所以就有了使用java程序写缓存机制:用java定义空间存储缓存型的数据,需要的时候就用,不需要的时候就不用。此时需保证其的生命周期,不指定则虚拟机会直接回收,而虚拟机回收垃圾是不定时的,且有些对象不会回收。所以写缓存的时候,在释放对象空间时,必须考虑对象的强引用、弱引用、软引用、虚引用。
3、封装
封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。(在使用对象时,没有必要知道对象内容是如何完成对应功能的,我们只需要指挥对象去执行即可)。
封装的好处:将变化隔离(内部功能细节变化不影响使用);便于使用(不用了解内部的具体实现);提高重用性;提高安全性(只对外暴露一些简单的内容供使用)。
封装的原则:将不需要对外提供的内容都隐藏起来;把属性都隐藏起来,只提供公共方法对其访问。
函数
本身就是java代码中的最简单的封装体。
类
也是封装体,类中方法不一定全部暴露出去,可通过权限修饰符进行隐藏。
包
也是封装体,如框架,拿来即用。
函数封装体:
封装具体示例:
如上图,当p.age=-20时,就会出现安全隐患,人的年龄不可能为负数,所以可以使用修饰符进行控制权限。使用private int age;私有化后,类以外即使建立了对象也不能再直接访问该属性。但是属性或方法一旦私有了,就需要对外提供访问方式(即提供getter和setter方法等操作)。因此需要给这个人的年龄提供对外访问方式。
之所以对外提供访问方式,是因为可以在这种访问方式中加入逻辑判断等语句,对访问的数据进行操作。提高代码的健壮性。如下例所示:
在设置人的年龄时,set方法中需要满足条件,才可继续执行,否则提示非法。
该示例在内存中表示,如下图:
执行main()方法后,在栈内存中产生了一个p的引用,然后在堆内存中建立Person对象,并分配内存空间0x0012,并将这个地址值赋给p引用,然后由p再指向Person对象中的这个地址0x0012。new Person对象,执行到private int age时,在堆内存(成员变量在堆内存中分配空间)中产生了一个age,并有了默认初始化值0。当p引用设置年龄为40时(p.setAge(40)),会通过setAge()方法中的this.age = a(this即代表p引用)将堆中的age值改为40。当执行p.speak()时,拿到成员变量age的值40并输出(成员变量age由局部(setAge())接收进来并改变其值后,其他方法(speak())也可以用(拿到的还是setAge()执行后的40))。
私有仅仅是封装的一种表现形式,只要权限在别人访问不到的范围都是封装。
4、构造函数
构造函数特点:
- 函数名与类名相同
- 不用定义返回值类型
- 不可以写return语句
作用:给对象进行初始化。
构造函数示例:
构造函数对象一建立就会调用与之对应的构造函数,可用于给对象进行初始化。当一个类中没有定义构造函数时,系统会默认给该类加入一个空参数的构造函数,当自己定义了构造函数后,默认的空构造函数就不存在了。
注意:默认构造函数的特点;
多个构造函数是以重载的形式存在的
。
多个构造函数以重载形式存在的示例:
构造代码块:
在构造代码块中,对象一建立就立即运行,而且优先于构造函数执行。构造代码块中定义的是不同对象具有共性的初始化内容
。
构造代码块和构造函数的区别:构造代码块是给所有对象进行统一初始化;而构造函数是给对应的对象初始化。
5、this关键字
5.1this关键字的基本阐述
如上图,发现Person(String a)构造函数中,a变量是无意义的,不能见名知意。因此将a改成name表示姓名,即局部变量和成员变量名称相同。但如下图,发现执行结果name并没有赋值成功:
因此如何区分局部和成员变量?即使用this关键字。
如上图,this关键字看上去,是用于区分局部变量和成员变量同名情况。其实,this代表本类的对象。
那么this到底代表哪一个对象?如下图:
this代表它所在函数所属对象的引用。简单的说,哪个对象在调用this所在的函数,this就代表哪个对象。例如,当执行到Person p = new Person(“lisi”);时,建立Person对象,执行到Person对象的构造函数Person(String name)后,此时的this代表的是p引用。而当代码执行到Person p1 = new Person(“zhangsan”);时,执行到Person对象的构造函数Person(String name)后,this代表的是p1引用。
5.2this关键字的应用:
需求:给人定义一个判断是否是同龄人的功能。
如上图,this和p1的地址值是指向同一个对象。当执行到boolean b = p1.compare(p2);时,会调用到compare(Person p)方法,此时,p2会以参数形式传给compare()方法,而由于是由p1调用的compare()方法,所以p1引用即当前对象this引用。即his和p1的地址值是指向同一个对象。
this的应用:当定义类中方法时,该方法内部要用到调用该方法的对象时,这时用this表示这个对象。但凡本类功能内部使用到了本类对象,都用this表示。
5.3this关键字在构造函数中的应用:
构造函数间调用,只能使用this进行互相调用,this函数不能用在一般函数间
。
this(name);对Person对象进行姓名初始化,this代表本对象。this.name = name是将值传递过去。
this语句(不是this关键字)只能定义在构造函数的第一行。如下图:
初始化动作要先执行,如先执行this(name)必须在this.name=name前才行。
6、static关键字
6.1概述
static关键字:用于修饰成员(成员变量和成员函数)
被修饰后的成员具备以下特点:
- 随着类的加载而加载(类一加载到内存中时,静态static就已经加载到内存空间(方法区)中,反之随着类的消失而消失,说明它的生命周期最长)
-
优先于对象存在(静态是先存在的,对象是后存在的)
-
被所有对象所共享
- 可以直接被类名调用
使用注意:
-
静态方法只能访问静态成员
-
静态方法中不可以写this,super关键字
- 主函数是静态的
6.2为什么要使用静态static关键字?
如以上两图所示,创建了两个对象引用P和P1,如果只在中国范围内,而不使用静态关键字static,则需要在堆内存中开辟两个country=”cn”空间(多个空间存在共同数据),那么对象创建得越多占用的内存就越多,如下图,将country=”cn”定义成静态的,则只需要在
方法区
中定义一次即可(static String country = “CN”),如下图:
方法区(共享区、数据区):存放共享数据,方法及方法体等内容。
当成员(包括成员变量和方法)被静态修饰后,就多了一个调用方式,除了可以被对象调用外,还可以直接被类名调用。格式:类名.成员。
静态static优先于对象的存在,即静态会随着类的加载而加载,消失而消失(说明生命周期最长)。
静态的成员变量也叫类变量,非静态的成员变量也叫实例变量。
6.3为什么不把所有的成员都定义为静态static?
要区分什么数据是对象特有的,什么数据是多个对象共享的,这样才符合生活中的描述;
对象在被用完后会被回收,但是静态static的数据生命周期特别长,用完后还会一直存在,因此会存在垃圾
。
6.4实例变量和类变量的区别
-
存放位置:类变量(静态成员变量)随着类的加载而存在于方法区中;实例变量随着对象的建立而存在于堆内存中
-
生命周期:类变量生命周期最长,随着类的消失而消失;实例变量生命周期随着对象的消失而消失。
6.5静态的使用注意事项
-
静态方法只能访问静态成员(成员变量和方法),非静态方法既可以访问静态成员也可以访问非静态成员
-
静态方法中不可以定义this,super关键字。因为静态优先于对象存在
。
如下图,this.name不可用,this代表对象,而静态方法优先于对象创建,在执行静态方法show()时,对象Person还未创建,所以此时this 还未初始化过,所以不可用。
6.6静态有利有弊
利:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中存储一份;可以直接被类名调用。
弊:生命周期过长(可能会有垃圾不能被回收);访问出现局限性(静态虽好,但只能访问静态)。
6.7主函数中的静态
主函数是静态的:主函数是一个特殊的函数,作为程序的入口,可以直接被JVM调用。
主函数的定义:
- public:代表着该函数访问权限是最大的
- static:代表主函数随着类的加载就已经存在了
- void:代表主函数没有具体的返回值返给虚拟机JVM
- main:不是关键字,但是是一个特殊的单词,可以被JVM识别,且是主函数特有的。
- String[] args: 函数的参数,参数类型是一个数组,该数组中的元素是字符串,字符串类型的数组。
主函数是固定格式的:JVM识别
public static void main(int x){}也可以写(主函数被重载),但是JVM会优先从public static void main(String[] args){}开始执行。JVM在调用主函数时,传入的是 new String[0],作用:可以往参数里传入数据,在主函数里就可以拿到参数。
传递参数给主函数并由主函数获取:
- 使用命令传: java 类名 参数
- 通过另一个类的主函数传值,然后由测试类主函数进行获取:
6.8什么时候使用静态?
要从两个方面下手:因为静态修饰的内容包括成员变量和成员函数。
什么时候定义静态变量(类变量)?
当对象中出现共享数据时,该数据需要被静态所修饰;而对象中的特有数据要定义成非静态存在堆内存中。
6.9什么时候定义静态函数?
当功能内部没有访问到非静态数据(对象的特有数据(非静态成员变量))时,该功能可以定义成静态的。
例:对象是为了封装数据的,但是下例中对象里的特有数据name并没有使用到,所以show()方法可以使用静态。
class Person{
String name;
public static void show(){
System.out.println(“haha”);
}
public static void main(String[] args){
Person.show();
}
}
但是下例中有使用到对象中的非静态成员name,所以show()方法绝对不能使用静态:
class Person{
String name;
public static void show(){
System.out.println(name+“haha”);//因为静态先于对象存在,在创建静态方法show时,对象Person还不存在,所以name也不存在
}
public static void main(String[] args){
Person p = new Person();
p.show();
}
}
6.10静态的应用—工具类
如上图,一个获取数组中最大值的类。可以发现,如果有很多类,都需要使用到获取数组最大值这个方法时,每个类都需要写一遍这些代码,因此可以将获取数组最大值的代码统一封装到一个静态工具类中,需要使用时调用即可。如下图所示:
因此,静态工具类即将每一个应用程序中都有共性的功能,进行抽取,独立封装,以便复用。
原始的ArrayTool.java类:
package com.vnb.javabase;
/**
*
* Description:数组工具类
* @author li.mf
* @date 2018年8月15日
*
*/
public class ArrayTool {
/**
* 获取数组最大值
* @param arr
* @return
*/
public int getMax(int[] arr){
int max = 0;
for (int i = 1; i < arr.length; i++) {
if(arr[max]<arr[i]){
max = i;
}
}
return arr[max];
}
/**
* 获取数组最小值
* @param arr
* @return
*/
public int getMin(int[] arr){
int min = 0;
for (int i = 1; i < arr.length; i++) {
if(arr[min]>arr[i]){
min = i;
}
}
return arr[min];
}
/**
* 选择排序
* @param arr
*/
public void selectSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1; j < arr.length; j++) {
if(arr[i]>arr[j]){
swap(arr,i,j);
}
}
}
}
/**
* 冒泡排序
* @param arr
*/
public void bubbleSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
public void swap(int[] arr,int a,int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public void printArray(int[] arr){
System.out.print("{");
for (int i = 0; i < arr.length; i++) {
if(i!=arr.length-1){
System.out.print(arr[i]+",");
}else{
System.out.print(arr[i]+"}");
}
}
}
}
数组工具类的测试类ArrayToolTest.java:
package com.vnb.javabase;
public class ArrayToolTest {
public static void main(String[] args) {
int[] arr = {1,2,6,8,3};
ArrayTool arrayTool = new ArrayTool();
int max = arrayTool.getMax(arr);
System.out.println("最大值为:"+max);
int min = arrayTool.getMin(arr);
System.out.println("最小值为:"+min);
arrayTool.printArray(arr);
arrayTool.selectSort(arr);
arrayTool.printArray(arr);
}
}
由例可见,ArrayTool类都没有用到ArrayTool中特有的数据(非静态的成员变量),都是由用户传参的arr进去,所以可以写成static。虽然可以通过建立ArrayTool的对象使用这些工具方法,对数组进行操作。但是,对象是用于封装数据的,而ArrayTool对象并未封装特有数据;操作数组的每一个方法都没有用到ArrayTool对象中的特有数据。所以,这时就可以考虑,让程序更严谨而不需要对象。可以将ArrayTool中的方法都定义成static的,直接通过类名调用即可。
ArrayTool.java中所有方法写成静态,以便调用:
package com.vnb.javabase;
/**
*
* Description:数组工具类
* @author li.mf
* @date 2018年8月15日
*
*/
public class ArrayTool {
/**
* 获取数组最大值
* @param arr
* @return
*/
public static int getMax(int[] arr){
int max = 0;
for (int i = 1; i < arr.length; i++) {
if(arr[max]<arr[i]){
max = i;
}
}
return arr[max];
}
/**
* 获取数组最小值
* @param arr
* @return
*/
public static int getMin(int[] arr){
int min = 0;
for (int i = 1; i < arr.length; i++) {
if(arr[min]>arr[i]){
min = i;
}
}
return arr[min];
}
/**
* 选择排序
* @param arr
*/
public static void selectSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1; j < arr.length; j++) {
if(arr[i]>arr[j]){
swap(arr,i,j);
}
}
}
}
/**
* 冒泡排序
* @param arr
*/
public static void bubbleSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
public static void swap(int[] arr,int a,int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public static void printArray(int[] arr){
System.out.print("{");
for (int i = 0; i < arr.length; i++) {
if(i!=arr.length-1){
System.out.print(arr[i]+",");
}else{
System.out.print(arr[i]+"}");
}
}
}
}
ArrayToolTest.java
package com.vnb.javabase;
public class ArrayToolTest {
public static void main(String[] args) {
int[] arr = {1,2,6,8,3};
int max = ArrayTool.getMax(arr);
System.out.println("最大值为:"+max);
int min = ArrayTool.getMin(arr);
System.out.println("最小值为:"+min);
ArrayTool.printArray(arr);
ArrayTool.selectSort(arr);
ArrayTool.printArray(arr);
}
}
将方法都写成静态后,可以方便与使用,但是仍然发现问题,即该类还是可以被其他程序建立对象的。所以为了更为严谨,强制该类不可以实例化以建立对象,所以可以考虑通过将构造函数私有化完成(私有构造)。
package com.vnb.javabase;
/**
*
* Description:数组工具类
* @author li.mf
* @date 2018年8月15日
*
*/
public class ArrayTool {
private ArrayTool(){
}
/**
* 获取数组最大值
* @param arr
* @return
*/
public static int getMax(int[] arr){
int max = 0;
for (int i = 1; i < arr.length; i++) {
if(arr[max]<arr[i]){
max = i;
}
}
return arr[max];
}
/**
* 获取数组最小值
* @param arr
* @return
*/
public static int getMin(int[] arr){
int min = 0;
for (int i = 1; i < arr.length; i++) {
if(arr[min]>arr[i]){
min = i;
}
}
return arr[min];
}
/**
* 选择排序
* @param arr
*/
public static void selectSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
for (int j = i+1; j < arr.length; j++) {
if(arr[i]>arr[j]){
swap(arr,i,j);
}
}
}
}
/**
* 冒泡排序
* @param arr
*/
public static void bubbleSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
private static void swap(int[] arr,int a,int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public static void printArray(int[] arr){
System.out.print("{");
for (int i = 0; i < arr.length; i++) {
if(i!=arr.length-1){
System.out.print(arr[i]+",");
}else{
System.out.print(arr[i]+"}");
}
}
}
}
ArrayToolTest.java
package com.vnb.javabase;
public class ArrayToolTest {
public static void main(String[] args) {
int[] arr = {1,2,6,8,3};
int max = ArrayTool.getMax(arr);
System.out.println("最大值为:"+max);
int min = ArrayTool.getMin(arr);
System.out.println("最小值为:"+min);
ArrayTool.printArray(arr);
ArrayTool.selectSort(arr);
ArrayTool.printArray(arr);
}
}
6.11帮助文档的制作(javadoc)
如果从别的地方拿来的java文件且不在同一目录下,则执行时会报错:找不到文件。如下图,
所以可以在dos命令行中设置classpath:set classpath-.i;c:\myclass 。-.i;先在当前目录下找,再C盘指定目录myclass下去找。
/** */ 里面写类的描述信息、开发人、开发时间、版本号等。java通过javadoc.exe程序来进行帮助文档的制作。通过命令可以将帮助文档存放到指定位置:javadoc –d myhelp –author –version ArrayTool.java。(如果无此文件夹,会自动创建)。
要将一个文件写出帮助文档,需要将类修饰符写为public或者protected 。以供外部读取。
ArrayTool.java帮助文档的查看:
从索引页面开始看即可(index.html)。
帮助文档示例:
一个类中默认会有一个空参数的构造函数,这个默认的构造函数的权限和所属类一致,如果类被public修饰,那么默认的构造函数也带public修饰,如果没有,默认的空构造也没有。注意:默认的构造函数不是自己写的空构造函数。
6.12静态代码块
格式:
static{
静态代码块的执行语句
}
示例:
static{
System.out.println(“asdf”);
}
静态代码块特点:静态代码块随着类的加载而执行,且执行一次(类加载完后,执行完了,就已经在内存中了),用于给类进行初始化,且优先于主函数执行。
静态代码块和主函数执行,示例:
打印顺序:b c a。
如上图,想让show方法执行,必须先加载StaticCodeDemo类。
’
如上图,该类类变量没有任何实体指向,所以StaticCode类不会进行加载,所以没有打印结果。
如上图,执行顺序:a c d ; 注:如果想要执行特定的构造函数需要在调用的时候进行指定,如上例new StaticCode(4);指定调用的是StaticCode(int x){}构造方法,而不是空参数的构造方法。
-
a表示静态代码块给
类
初始化; - b不会执行,因为还未创建过与之对应的对象(已经指定调用其他的构造方法);
-
c表示构造代码块给
对象
初始化 -
d表示构造函数给
对应对象
初始化
{}表示构造代码块给对象初始化的,所以可以用this
6.13对象初始化的过程
对象建立过程:
当类的主函数入口main()方法执行到Person p = new Person(“zhangsan”,20);时,会有一个p引用加载到栈中:
1)new Person()时会将Person.class文件从硬盘中通过java的虚拟机JVM加载进内存;
2)执行静态代码块
3)并开辟了堆内存空间。(非静态成员变量,如属性:name、age)
4)初始化动作,构造代码块
执行结果null,0。
所以会先有默认初始化null和0,再有显示初始化(成员属性的初始化),如下图:
再进行构造代码块初始化(此代码中无),再构造函数初始化(赋值zhangsan,20)。
总结:Person p = new Person(“zhangsan”,20);的执行过程?
- 因为new用到了Person.class,所以会先找到Person.class文件并加载到内存中
- 执行该类中的static代码块,如果有的话,给Person.class类进行初始化
- 在堆内存中开辟空间,分配内存地址
- 在堆内存中建立对象的特有属性,并进行默认初始化(对象为null,整数为0)
- 对属性进行显示初始化(类中声明成员变量初始化值)
- 对对象进行构造代码块初始化
- 对对象进行对应的构造函数初始化
- 将内存地址值赋给栈内存中的p引用
- p引用再指向堆内存中的地址值
6.14对象调用成员过程
Person p = new Person(“zhangsan”,20);
p.setName(“lisi”);
1)在栈中有main主函数的p引用生成
2)然后在方法区中初始化Person类的
方法
(showCountry()、setName()、speak())、
方法体、静态变量
3)在堆中开辟空间
4)在默认初始化(对象为null,整数为0)
5)再到堆中new Person(“Zhangsan”,20)
6)再将地址值0x0023赋给栈,再从栈中指向堆中的地址
7)setName()在栈中开辟空间,非静态的只要一调用this就会有值(指向),this的引用用于给对象赋值(哪个对象调用就代表哪个对象,这里this代表p),即this为0x0023,即this指向堆中的0x0023,因此 setName中this.name = name一执行时,局部变量name的lisi赋值给了堆内存中的this.name。
调用过程的内存图示:
当再创建一个对象p1时,在内存中的调用过程,如下:
当再创建一个对象p1后,会重新在栈内存中初始化p1引用,然后在堆内存中重新new 一个Person对象,设置默认初始化值,然后设置显示初始化值name为ahah,age为90,并开辟空间设置地址值,并将地址值赋给p1,当setName(“qq”)时,会在栈中开辟一块空间,通过this.name = name将堆内存中的name值设置为qq。
如上图,静态方法showCountry()方法一被调用,在开辟完空间后,没有找到this关键字,所以showCountry()方法就会所属于Person类,不会再进堆内存。调用showCountry()方法时也会用“类名.showCountry()”执行里面的静态方法。
成员只有被调用才能用,静态的本类省略了类名.method。非静态对象省略this.方法; 静态省略类名.静态方法。
7、单例设计模式
7.1概念
设计模式: 解决某一类问题最行之有效的方法。java中共有23种设计模式。
单例模式:解决一个类在内存中只存在一个对象(不能new多个实例)
想要保证对象唯一:
- 为了避免其他程序过多建立该类对象,先要控制禁止其他程序建立该类对象
- 为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象
- 为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式
代码实现:
- 将构造函数私有化(以控制其他程序建立该类对象)
- 在类中创建一个本类对象
- 提供一个方法可以获取到该对象(方便其他程序对自定义对象的访问)
7.2饿汉式单例模式
package com.vnb.javabase;
/**
*
* Description:单例模式
* @author li.mf
* @date 2018年8月16日
*
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
class SingleDemo{
public static void main(String[] args) {
Singleton ss = Singleton.getInstance();
}
}
内存中实现过程:
再获取一次单例,发现获取的实例时一致的。如下图:
测试是否获取的是同一个实例:
class SingleDemo{
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
s1.setNum(20);
System.out.println("num值为"+s2.getNum());
}
}
结果发现,s2获取的值是s1 set进的值。
单例模式,对于事物该怎么描述,还怎么描述,当需要将该事物的对象保证在内存中唯一时,就将以上者三步加上即可。
7.3懒汉式单例模式
package com.vnb.javabase;
public class Single {
private static Single instance = null;
private Single(){
}
public static Single getInstance(){
if(instance == null){
instance = new Single();
}
return instance;
}
}
对象在被调用时,才进行初始化,就叫做对象的延时加载,也称为懒汉式。
如下图,Single类进内存时,对象还没有存在,而是只有调用了getInstance方法时,才建立对象。
懒汉式单例模式在内存中表示:
开发一般使用饿汉式,因为其简单且安全,且懒汉式当有多个人来调用这个方法时会出现问题。当A执行到if(s==null) 后挂掉了,突然去执行其他程序了,此时其他程序B来判断后new了一个对象,之后A又继续执行了,但是A并不知道B已经创建了对象,所以B又new了一个对象,这样就new了多个对象不唯一了。
为解决上述问题:可以在方法上加同步锁synchronized
package com.vnb.javabase;
public class Single {
private static Single instance = null;
private Single(){
}
public static synchronized Single getInstance(){
if(instance == null){
instance = new Single();
}
return instance;
}
}
但是加上同步锁后,每次进getInstance()方法后都要执行同步锁,程序的效率会变低。
为解决低效率问题:可以使用双重判断形式(多线程时进行讲解),使用同步代码块。如下图。但是此方法代码会较多。所以定义单例时建立使用饿汉式。
package com.vnb.javabase;
public class Single {
private static Single instance = null;
private Single(){
}
public static Single getInstance(){
if(instance == null){
synchronized (Single.class) {
if(instance == null){
instance = new Single();
}
}
}
return instance;
}
}
8、继承
本节主要内容:继承的概述;继承的特点;super关键字;函数覆盖;子类的实例化过程;final关键字。
8.1继承的概述
有以下两个类:
将学生和工人两个类的共性描述提取出来,单独进行描述,只要让学生和工人与单独描述的这个类有关系,就可以了。
继承的好处:提高了代码的复用性;继承让类与类之间产生了关系,有了这个关系,才有了多态的特性。
注意:千万不能为了获取其他类的功能,简化代码而继承。必须是类与类之间有所属关系才可以继承。所属关系即 is a。java语言中,只支持单继承,不支持多继承,因为多继承容易带来安全隐患(接口与接口之间可以多继承(因为接口之间没有方法体,方法之间不会冲突))。
如上图,继承多个类,当多个父类中定义了相同功能,当功能内容不同时,子类对象不知道运行哪个功能,就会出现问题。但是java保留另一种体现形式来完成这种表示,叫接口多实现。
java支持多层继承。也就是一个继承体系。如下图所示:
C类也可以使用到A类中的功能。
如何使用一个继承体系中的功能呢?
想要使用体系,先查阅体系中父类的描述,因为父类中定义的是该体系中的共性功能,通过了解共性功能,就可以知道该体系的基本功能。也可以基本使用了。在具体调用时,为什么要创建最子类的对象?一是因为有可能父类不能创建对象;二是创建子类对象可以使用更多的功能,包括父类的也包括特有的。简单的一句话:查阅父类功能,创建子类对象使用功能。
8.2类与类之间关系—聚集关系(组合关系)
事物之间不一定是继承关系,也有聚集关系 has a,简单的说就是谁里面有谁。
聚合:球员和球队(球队中有球员)
组合:事物之间的联系比聚合关系更高,比如,心脏和人;手和人,心脏和手必不可少。
8.3子父类中变量的特点
以上当父类子类成员变量相同时,打印的是4,因为子类中的num 即为this.num。要打印父类的变量,使用super.num即可,打印子类的变量直接使用num即可。
new 子类class文件时,会先加载父类的class文件
:
变量:如果子类中出现非私有的同名变量时,子类要访问本类中的变量,用this;子类要访问父类中的同名变量,用super。super的使用和this的使用几乎一致,this代表本类对象的引用;super代表父类对象的引用。
如上图,num前省略super和this打印结果都是4,原因:子类继承父类,子类也能拿到父类的num 4(相当于子类里有自己的num),所以用子类自己的this能拿到num=4;super是指父类引用,this是指本类引用,由于现在没有父类对象只有子类对象(Zi z = new Zi()),所以现在super和this引用指向的是同一个对象(Zi子类对象)即new Zi(),所以只有一个num=4,即同一个子类对象的num(这就是面向对象的多态即父类引用指向子类对象List list = new ArrayList())。
8.4子父类中函数的特点—覆盖(重写)
如上图,若子父类中的方法一致,子类运行就会覆盖了父类中的方法。子类出现和父类一模一样的函数时,当子类对象调用该函数,会运行子类函数的内容,如同父类的函数被覆盖一样,这种情况是函数的另一个特性,重写(覆盖)。
当子类继承了父类,沿袭了父类的功能到子类中,但是子类虽具备该功能,但是功能的内容却和父类却不一致,这时,就没有必要定义新功能,而是使用覆盖特性,保留父类功能定义,并重新父类内容。这就叫重写,也叫做覆盖。如下图:
用重写特性提高程序扩展性:
new的时候只要new NewTel即可,而不需要修改Tel类的任何内容,但是重写时会有代码重复以实现父类原有的功能,所以只要使用super调用原有内容即可。如下图:
覆盖:
子类覆盖父类,必须保证子类权限要大于或等于父类权限,才可以覆盖,且父类不能为private修饰
(如果是private修饰,子类都不知道父类有此方法存在),否则编译失败;静态只能覆盖静态(虽然此做法无意义);
重载和重写的区别:
- 重载只看同名函数的参数列表;
- 重写是子父类方法要一模一样包括返回值类型。
如上图,是不允许存在的。因此重写要求子父类中的方法名和返回值类型必须相同。
8.5子父类中构造函数的特点—子类实例化过程
子父类构造函数不能覆盖
,因为覆盖必须函数名和返回值等都一模一样,但是构造函数的名字必须和类名一致;
子类的构造函数有一句隐示的语句super();
如下图:
在对子类对象进行初始化时,父类构造函数也会运行,是因为子类的构造函数默认第一行有一条隐式的语句super(),super()会访问父类中空参数的构造函数
,而且子类中所有的构造函数默认第一行都是super()。如下图所示:
如下图,当父类中没有空参数的构造时,系统会报错,所以必须手动指定子类引用父类的哪个非空的构造函数。
为什么子类一定要访问父类中的构造函数?见下图:
总结:(如上图)因为父类中的数据子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的,所以子类在对象初始化时,要先访问一下父类中的构造函数,如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式进行指定。如上图,在建立子类对象后后去查看父类有没有改变num的值,这里父类已经将num值设置为60,所以子类的num也对应改了,最后打印的z.num即父类中设置的60。
应用:
父类已经给name赋过值了,子类继承父类后,直接调用(super(name))父类的方法即可获取到父类中设置的值。
总结:
父类已经定义过的方法属性,子类想要使用,直接通过super即可拿到
。
如下图,super()语句必须写在子类构造函数的第一行,因为需要先把父类的数据初始化完成后,再初始化自己的。
当子类构造函数中写了this()方法后,就不能再用super()了,this和super不能同时存在。
此时zi(int x)中的this()会访问本类中的zi()空构造,而zi()构造函数中默认有super()就能调用到父类中的属性和方法。
结论(子类的实例化过程):子类中所有的构造函数,默认都会访问父类中的空参数的构造函数,因为子类每一个构造函数内的第一行都有一句隐式的super(),当父类中没有空参数的构造函数时,子类必须手动通过super语句或者this语句形式来指定要访问的父类中的构造函数。子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数,子类中至少要有一个构造函数会访问父类中的构造函数(没写也会有默认的)。
this和super为什么不能同时存在?因为this和super都必须存在第一行。
为什么this和super都必须存在第一行?因为初始化动作必须先做。
8.6 final关键字
final关键字:
- 作为一个修饰符,可以修饰类、函数、变量。
- 被final修饰的类不可以被继承(继承的弊端,打破了封装性,可以复写掉父类的东西,很多基类就会很不安全,可能会被修改),为了避免被继承,被子类复写功能,在类上写final即可(叫做最终类,最终类是不可以被继承的)。
- 被final修饰的方法,不可以被复写。
- 被final修饰的变量是一个常量,只能赋值一次,既可以修饰成员变量,也可以修饰局部变量。如下图:
只要在内存中,x和y的值都是固定的,不可以更改。
当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字,方便阅读,而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范所有的字母都大写,如果有多个单词组成,单词间通过下划线连接。
2.
内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。
8.7抽象类
如上图,发现两个方法都一致,所以需要抽取相同功能。但发现虽然两者都在学习,但是学习内容却不一样。解决:可以进行向上抽取,只抽取功能定义,不抽取功能主体(即抽象类)。对于这种方法,必须使用abstract进行修饰。抽象方法必须定义在抽象类中。如下图:
抽象:即看不懂的事物。
抽象类的特点:
-
抽象方法一定在抽象类中
-
抽象方法和抽象类都必须被abstract关键字修饰
-
抽象类不可以用new创建对象,因为调用抽象方法没意义(里面的抽象方法连方法体都没有)
-
抽象类中的抽象方法要想被使用,必须由子类复写其所有的抽象方法后,建立子类对象调用,如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
抽象类和一般类没有太大的不同。只是要注意该如何描述事物就如何描述事物,只不过该事物中出现了一些看不懂的东西。这些不确定的部分也是该事物的功能,需要明确出来,但是无法定义主体。而通过抽象方法来表示。
- 抽象类比一般类多了抽象方法。就是在类中可以定义抽象方法,也可以不定义抽象方法。
- 抽象类不可以实例化。
- 抽象类也可以不定义抽象方法,这样只是为了不让该类创建对象。
抽象类练习:
假如我们在开发一个系统时需要对员工进行建模,员工包含3个属性:姓名、工号、工资。经理也是员工,除了含有员工的属性外,另外还有一个奖金属性,请使用继承的思想设计出员工类和经理类。要求类中提供必要的方法进行属性访问。
分析:
员工类:name、id、pay
经理类:继承了员工并有自己特有的奖金属性bonus
会有一个抽象员工类,员工属于这一类,经理也属于这一类
8.8模板方法设计模式:
需求:获取一段程序运行的时间
原理:获取程序开始和结束的时间并相减即可。获取本机系统时间:System.currentTimeMillis()获取当前时间的毫秒值,返回当前时间减去1970年1月1日0点0分0秒之间的时间差。
问题:想获取另一段代码的运行时间。解决:直接子类复写掉getTime方法
但是发现代码重复性很高。
解决:可以将需要运行代码提取出来形成另一个方法,子类复写时,只需要复写提取出来的代码即可。如下图:
但是却发现提取出来的需要运行的代码是不确定的,解决:只要将方法写成抽象方法并由子类继承后进行具体实现即可,且getTime方法不可复写所以使用final进行修饰。如下图:
当代码完成优化后,就可以解决这类问题,这种方式叫做模板方法设计模式。
什么是模板方法?
在定义功能时,功能的一部分是确定的,但是一部分是不确定的,而确定的部分在使用不确定的部分时就将不确定的部分暴露出去,由该类的子类去完成。这样可以提高扩展性和复用性。注意:暴露出去的方法不一定抽象,有时会有默认的实现方式。
8.9接口
接口:初期理解,可以认为是一个特殊的抽象类,当抽象类中的方法都是抽象的,那么该类可以通过接口的形式表示。
接口定义时,格式特点:
- 接口中常见定义:常量,抽象方法
- 接口中的成员都有固定修饰符(常量:public static final;方法:public abstract)
总结:
接口中的成员(包括成员变量和方法)都是public的
如下,即使少写了成员上的,interface也会进行默认的补全。
但是,一般建议写全:
接口也是不可能创建对象的,因为接口中的方法都是抽象的,而抽象方法需要被子类实现。即子类对接口中的抽象方法全都覆盖后,子类才可以实例化,若子类只是实现了父类,却没有覆盖父类的方法,那么子类仍是一个抽象类。如下图:
如上图中,实现类Test实现了Inter接口后,覆盖了父类接口中的show()方法。因此Test类才可以被实例化。
接口可以被类多实现,也是对多继承不支持的转换形式,java不支持多继承,但是支持多实现。如下图:
java不支持多继承是因为父类当中的方法有重复,多继承会导致子类调用时出现冲突
,为什么接口不会出现这种问题?
因为多实现接口中,接口没有方法主体,实现类在实现时,可以重写方法体,即使出现接口中出现多个相同方法,实现类中一个方法就可以全部重写多个接口中的那些相同方法。
如下图所示:
注意:多个接口的相同方法的返回值必须一致。
类和接口相互之间的关系:
- 类与类之间:继承
- 类与接口之间:实现
-
接口与接口之间关系:继承关系
接口与接口之间可以多继承(因为接口之间没有方法体,方法之间不会冲突)。
所以java中也存在多继承,只是多继承只存在在接口与接口之间,而不存在与类与类或者类与接口之间。
接口的特点:
- 接口是对外暴露的规则
- 接口是程序的功能扩展
- 接口可以用来多实现(降低了耦合性)
- 类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口
- 接口与接口之间可以有继承关系
9、多态
9.1概念
多态:可以理解为事物存在的多种体现形态
人:男人、女人
动物:猫、狗
猫 x = new 猫();
动物 x = new 猫();
函数也具有多态性:重载和覆盖
本节主要内容:多态的体现;多态的前提;多态的好处;多态的应用等。
9.2多态—扩展性
需求:动物(猫、狗)
package com.vnb.javabase;
abstract class Animal{
abstract void eat();
}
class Cat extends Animal{
@Override
void eat() {
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
@Override
void eat() {
System.out.println("啃骨头");
}
public void kanJia(){
System.out.println("看家");
}
}
public class DuotaiDemo {
public static void main(String[] args) {
Cat c = new Cat();
function(c);
function(new Dog());
}
public static void function(Cat c){
c.eat();
}
public static void function(Dog d){
d.eat();
}
}
由例可发现,例中只有继承,如果再要加其他动物,还得写其他动物的类并继承动物类,且还要重写动物的吃方法。
分析发现,所有动物都具有吃的行为,因为他们都是动物。如下图:
package com.vnb.javabase;
abstract class Animal{
abstract void eat();
}
class Cat extends Animal{
@Override
void eat() {
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
@Override
void eat() {
System.out.println("啃骨头");
}
public void kanJia(){
System.out.println("看家");
}
}
public class DuotaiDemo {
public static void main(String[] args) {
/*Cat c = new Cat();
function(c);
function(new Dog());*/
Animal a = new Cat();
a.eat();
}
public static void function(Cat c){
c.eat();
}
public static void function(Dog d){
d.eat();
}
}
总结:多态的体现:父类的引用指向了自己的子类对象(Animal a = new Cat())。
父类的引用也可以接收自己的子类对象(提高代码的扩展性:以后也其他的猪、老虎类,只要有这个类继承动物类即可)。所以可以通过动物类,去new其子类(即多态),传入什么动物,便由其自己去调用自己的吃方法。如下图:
package com.vnb.javabase;
abstract class Animal{
abstract void eat();
}
class Cat extends Animal{
@Override
void eat() {
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
@Override
void eat() {
System.out.println("啃骨头");
}
public void kanJia(){
System.out.println("看家");
}
}
public class DuotaiDemo {
public static void main(String[] args) {
function(new Cat());
function(new Dog());
}
public static void function(Animal a){
a.eat();
}
}
多态的好处:大大提高了程序的扩展性
多态的前提:必须是类与类之间有关系(继承或者实现);通常还有一个前提,存在覆盖。
多态的弊端:提高了扩展性,但是只能使用父类的引用访问父类的成员(如Animal的eat()方法)。
9.3多态—转型
package com.vnb.javabase;
abstract class Animal{
abstract void eat();
}
class Cat extends Animal{
@Override
void eat() {
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
@Override
void eat() {
System.out.println("啃骨头");
}
public void kanJia(){
System.out.println("看家");
}
}
public class DuotaiDemo {
public static void main(String[] args) {
Animal a = new Cat();//引用数据类型的类型提升,也称为向上转型
}
public static void function(Animal a){
a.eat();
}
}
多态:Animal a = new Cat();存在引用数据类型的类型提升,即向上转型。
如果想要调用猫的特有方法时,要如何操作?
解决:可以向上转型,也可以向下转型,强制将父类的引用,转成子类类型。如下例:
Animal a =
new Cat();
a.eat();
Cat c = (Cat)a;
a.catchMouse();//调用子类特有方法
注意:Animal a =
new Animal();
Cat c = (Cat)a;//这里是不能强转的,这里的动物类是不明确到底是哪个动物类的
千万不要出现以下操作:即将父类对象转成子类类型。能转换的是父类引用指向了自己的子类对象时,该引用可以被提升(运行结果是子类结果),也可以被强转转换(向下转型)
规律:多态自始至终都是子类对象在变化。
package com.vnb.javabase;
abstract class Animal{
abstract void eat();
}
class Cat extends Animal{
@Override
void eat() {
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
@Override
void eat() {
System.out.println("啃骨头");
}
public void kanJia(){
System.out.println("看家");
}
}
public class DuotaiDemo {
public static void main(String[] args) {
Animal a = new Cat();//引用数据类型的类型提升,也称为向上转型
}
public static void function(Animal a){
a.eat();
if(a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();
}else if(a instanceof Dog){
Dog d = (Dog)a;
d.kanJia();
}
}
}
a instanceof Cat 判断a是否属于Cat这个引用类型。
一般只用于传的类型是有限的;或传的类型的该类的所属类型,并对此类型有所使用。
9.4 多态—示例(应用)
需求:基础班的学生:学习、睡觉等
高级班的学生:学习、睡觉等
可以将这两类事物进行抽取。
9.5多态中成员的特点
运行结果:
在多态中(父类有指向子类对象时)成员函数的特点:
-
在编译时期,参阅引用型变量(
Fu
类)所属的类中是否有调用的方法,如果有编译通过,如果没有编译失败;
-
在运行时期,参阅对象(
Zi
类)所属的类中是否有调用的方法(静态方法除外)。
对于Fu f = new Zi();后调用子父类中的方法说明:子父类中都有此方法,使用子类中的方法(子类覆盖了父类方法);子类中没有此方法,调用父类的此方法(子继承父类);Fu类中没有此方法,编译失败。如下图(method3会编译失败):
多态中,成员变量的特点,如下图:
输出结果:
在多态中,成员变量的特点:无论编译还是运行,都参考左边
(引用型变量所属的类)(此情况一般只会面试出现)。因为有父类是,会先加载父类去拿num。
打印结果:(开发一般不会出现这种情况)
所以静态时不会出现多态中成员函数的特点。
总结:在多态中,静态成员函数的特点:无论是编译时期还是运行时期,都是参考左边。
因为静态方法存在方法区中,不需要对象,只需要Fu.method4()即可,看的是引用型对象加载的内容(Fu),而不是对象(Zi),只要父类型引用还在,用的还是父类引用,而不是对象。如下图:
当调用f.method1()时,f确实执行的是对象,method1()被运行的时候是被对象运行,对象在调用非静态方法时,访问的是对象中的数据;但是静态方法method4()本身不访问对象特有的数据,用类名调用的话,它只看所属引用型(Fu)变量中的数据类型,即静态区中的方法,它不参考右边非静态区的方法。如下图:
动态静态绑定:
当method4()方法一进内存,因为是静态方法,就已经被绑定在方法所属的类的引用上(Fu),即method4属于Fu。而静态区上,this是动态的,this指向哪个对象就代表哪个对象。而现在写的是Fu f = new Zi(),f.method1(), new的还是Zi,所以运行的还是子类的方法(动态绑定);当Fu f = new Fu()此时就找的是Fu类上的方法。
多态总结:
9.6多态的电脑的主板示例
需求:电脑运行示例,电脑运行是基于主板
上图中,主板就可以跑起来了
但是发现问题:运行一段时间后,想上网,想听音乐,主板没有此功能,因此就需要给主板提供扩展性的功能(原来功能毫无扩展性)。如下图:
该类在内存中表示:
为降低网卡声卡等和主板的耦合性,会在主板上留一个槽,并约定这些槽的规则,此后,只要符合这个规则,就可以在主板上运行。而这些插槽即接口的编写。如上图的PCI即设定的某些规则。代码如下图所示:
PCI p = new NetCard();//接口型引用指向自己的子类对象。
如上图,网卡声卡等都需要符合规则才能用,所以都需要实现PCI接口。
9.7多态的扩展示例
需求:对数据库的操作
数据是:用户信息
- 连接数据库 JDBC Hibernate
- 操作数据库 CRUD
- 关闭数据库连接
问题:某天发现JDBC太麻烦,需要使用某种框架进行连接数据库,按照以上方法,就需要修改所有方法。如下图:
内存中:
但是发现,主程序也需要改,这样代码耦合性太高,所以需要重新设计代码。
分析:无论如何连接数据库,最后要的只是CRUD,无论采用哪种方式连接,内部的实现方式仍然相同,所以可以专门定义出数据库连接的规则。如下图,interface接口,事先定义好CRUD的接口。之后再需要使用其它数据库连接方式时,也只需要实现该接口即可。
由此发现,连接数据库的程序和主程序不再有强的耦合关系。
9.8 Object类 —equals()
Object:是所有对象的直接或者间接父类,该类中定义的肯定是所有对象都具备的功能。
java指定所有对象都具备比较性,都能比较两个对象是否相同。此方法定义在Object中,即equals()方法。
例如:
equals()方法比较的是对象的地址值。equals()里的参数不确定,所以使用Object,即可多态实现比较。
自定义比较方法:
使用“==”对象不同,数据相同即为真,如下图:
问题:Demo中已经有了比较的方法equals()(超类Object),就不需要重新比较了(功能一致,可以沿袭父类,写自己特有的功能(复写(覆盖)equals()))。如下图:
Object中没有定义过num,如果不向下转型,是不能进行编译的。
问题,当传入的对象不同时,运行时出现不可转换错误。解析:一般是自己的对象和自己比较,不是则直接返回false。如下图:
输出false。
9.9Object类—toString()
java 任意对象都可以通过toString()方法返回字符串,所以类.toString()就可以拿到对象的hash值。而hashCode方法也可以返回对象的hash值。
图中Demo如何得到?
一个对象的建立要根据类文件进行建立。一个类文件中包含名称、很多构造函数、一般方法等很多东西,如何获取到某个构造函数,只有这个对象最明确,所以可以通过Class来描述这些class文件,通过 类.getClass() 进行获得。
类中有Class.getClass()方法,可以通过类.getMethods()获取到类文件中所有的方法。
10、内部类
10.1内部类概念
将一个类定义在另一个类的里面,而里面那个类就称为内部类(内置类、嵌套类)。
内部类访问特点:
内部类可以直接访问外部类中的成员,包括私有成员;而外部类要访问内部类中的成员必须要建立内部类的对象。
好处:可以直接访问外部类中的成员(包括私有成员),而不用创建对象。
外部能不能直接访问内部类中的方法(function)?
分析:inner不是独立存在,而是在Outer中,所以使用inner所在类.inner即可(很少用,因为内部类很可能会被private修饰(当内部类是外部类的成员时))。如下图:
为什么内部类能直接访问外部类中的内容?
输出结果为6。
当内部外部存在变量相同时,优先获取内部变量;
想拿到4(内部类的变量),使用this.x;想要拿到外部类的x=3,可用Outer.this.x。如下图:
之所以可以直接访问外部类中的成员是因为内部类中持有了一个外部类的引用,该引用写法是”外部类名.this”。
10.2静态内部类
访问格式:
- 当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中,直接建立内部对象。格式:外部类名.内部类名 变量名 = 外部类对象.内部类对象(Outer.Inner in = new Outer().new Inner())
-
当内部类在成员位置上,就可以被成员修饰符所修饰。比如,private:将内部类在外部类中进行封装,Static:内部类就具备了静态的特性。
当内部类被静态修饰后,只能直接访问外部类中的静态成员
了(外部成员也要用static修饰才行)。修饰出现限制了。如下图:
修改后:在外部其他类中,如何直接访问static内部类的非静态呢?new Outer.Inner().function();如下图:
在外部其他类中,如何直接访问static内部类的静态呢?
new Outer.Inner.function();但是这种方式使用得很少。
注意
当内部类中定义了静态成员,该内部类必须是静态的(static)
。如下例:
即当function()方法被static修饰时,Inner也必须是静态修饰
当外部类中的静态方法访问内部类时,静态类也必须是静态的
。如下图:
改为以下即可:
10.3内部类定义原则
类是用来描述现实中事物的,当描述事物时,事物内部还有事物,该事物就用内部类来描述,因为内部事物在使用外部事物中的内容(成员和函数等)。
示例:描述人体,在描述心脏时,认为心脏也是一个功能,但是心脏描述比较复杂,有跳动、血液等等众多属性,对于这些属性就得用类进行描述,因为,心脏可以访问人体中其他部分,所以,将心脏定义成内部类最合适(内部类:类中包含另一个类),而且心脏不能直接被外部访问,但是提供外部访问的功能。
10.4匿名内部类
只有定义在成员位置上,才能被私有或静态所修饰。内部类可以写在类的任意位置上(成员、局部成员(变量或方法)等)
。
如下例,访问规则没变:
上例已经不能被静态私有修饰,Inner在局部方法里,在这里也不能使用static。局部内部类不能用static修饰,非静态没对象不会运行(必须有调用的地方new Inner().function(),如下)。
方法中的局部变量,内部类在局部,那么内部类就能访问局部变量?
修改后:
有上例可知,
-
内部类定义在局部时,不能被成员修饰符修饰;
-
可以直接访问外部类中的成员,因为还持有外部类中的引用,但是不可以访问它所在的局部变量,只能访问被final修饰的局部变量
如上图,方法中的参数也必须是final。
如何只有一个对象实现多次调用method(),如下图:
调用method()时a=7进栈,执行完后,会释放掉,再调用method()a =8,所以不会冲突。
匿名内部类:
- 匿名内部类其实就是内部类的简写格式
-
定义匿名内部类的前提:内部类必须继承一个类,或者实现接口
将下列例子简化成匿名内部类:
简化后:匿名了,没有Inner类了,怎么new对象?如下:
可以使用 new AbsDemo(){
void show();
}
AbsDemo本身是抽象类,不能new,但是可以像下面这样写,在复写掉里面的方法。红框的内容是一个对象,是AbsDemo的子类对象(只有子类才可以复写父类的抽象方法)。
匿名内部类调用本匿名内部类中的方法:
-
总结:匿名内部类的格式,new 父类或者接口(构造函数,可以往其传参){定义子类的内容}
-
其实匿名内部类就是一个匿名子类对象,可以理解为带内容的对象
匿名内部类中也可以有子类特有的方法,不一定是覆盖父类的方法,但是,不可以同时调用show和abc方法(匿名内部类只能调用一次)。如下图:
但是可以,建立多次匿名对象:
简化,可以将多个匿名内部类取名(利用多态),如下图:
d.show()可以是父类的方法,d.abc()不能调用,多态中的父类没有abc()方法。
注:匿名内部类中定义的方法最好不要超过3个。可读性差。
匿名内部类练习:
如上图,Test.function:Test类中有一个静态方法function,.method():function这个方法运算后的结果是一个对象。而且是一个Inter类型的对象。因为是Inter类型的对象,才可以调用method()方法。
Test.function().method();即等同于:
Inter in = Test.function();
in.method();
什么时候用匿名内部类?
当使用的方法参数类型是接口时,可以再调用方法时传一个匿名内部类(如AWT匿名监听器等)。如下图:
若没有父类也没有抽象类或接口,想写个匿名内部类,怎么写?
使用所有类的父类(Object对象),如下图:
11、异常
11.1异常概述
异常:程序运行时出现不正常情况。
异常由来:问题也是现实生活中的一个具体事物,也可以通过java的类的形式来进行描述,并封装成对象。异常其实就是java对不正常情况进行描述后的对象体现。
对于问题的划分:一种是严重的问题;一种是非严重的问题。
对于严重的,java通过Error类进行描述。对于Error类问题一般不编写针对性的代码进行处理;对于非严重的问题,java通过Exception类进行描述,对于Exception类的问题可以使用针对性的处理方式进行处理。
无论Error或者是Exception都具有一些共性内容:不正常情况的信息、引发原因等,会向上抽取,抽取出来的即为:
Throwable
|–Error
|–Exception
API解释:
Error异常:
Exception异常:
11.2异常try catch
java 有内置的异常处理方式,只要有某些异常就会进行处理。java提供了特有的语句处理方式,即
try{
需要检测的代码
}catch(异常类 变量){
处理异常的代码(处理方式)
} finally{
一定会执行的语句
}
例如:
在方法div()中,捕获到异常后封装成new AritchmeticException()后,会抛给main中的有try进行异常捕获newAritchmeticException()异常,捕获到后不会继续往下执行,而是有catch到(Exception e = new ArithmeticException();),再执行catch中的语句。
对捕获到的异常对象进行常见方法操作:
- String getMessage() 打印异常信息
- String toString() 打印异常名及信息
- void printStackTrace(); 异常信息、类名及异常出现位置
其实JVM默认的异常处理机制,就是在调用printStackTrace方法,打印异常的堆栈的跟踪信息。
11.3异常声明throws
编写功能时,不清楚是否调用者会传入正确的参数,所以,可以在功能上通过throws的关键字声明该功能有可能会出现问题。例:
编译时会出现以下错误:
但是如果不抛出,编译时不会出现问题,而运行会报错:
如果一直抛出,直至抛给JVM,则会使用JVM默认的异常处理机制:
抛异常和捕捉异常是有使用场景的。
11.4多异常处理
对多异常的处理:
- 声明异常时,建议声明更为具体的异常,这样处理得更具体
- 原则:对方声明几个异常就有几个catch块,不要定义多余的catch块,如果多个catch块中的异常出现继承关系,则父类异常catch块放在最下面。建议在进行catch处理时,catch一定要定义具体的处理方式,不要简单定义依据e.printStackTrace(),也不要简单的书写一条输出语句。做法:用硬盘文件记录异常日志文件。
也可以抛出父类异常Exception:
若出现了,意料之外的异常,应该是停止执行,让我们发现问题,进行针对性处理,而不是通过Exception进行。如下图:
11.5自定义异常
因为项目中会出现特有的问题,而这些问题并未被java所描述并封装对象,所以对这些特有的问题,可以按照java的对问题封装的思想,对本项目中的特有问题进行自定义异常封装。
需求:在本程序中,对于除数是-1,也视为是错误的无法进行运算。那么就需要对这个问题进行自定义的描述。
当函数内部出现了throw抛出异常对象,那么就必须要给对应的处理动作,要么在内部try catch;要么在函数上声明让调用者处理。一般情况在函数内出现异常,函数上需要声明。
打印结果:
发现打印结果中只有异常名称却没有异常信息,因为自定义异常并未定义所属信息,那么如何定义异常信息?
打印结果:
问题:发现父类构造函数中有个带message的方法
父类中已经定义了构造函数接收异常信息,所以就会有变量接收这个信息,子类在继承这个类时,只要new Exception()就能获取到这些信息。类似下例:
所以,异常类如下:
总结:因为父类中已经把异常信息的操作都完成了,所以子类只要在构造时将异常信息通过super语句传递给父类,就可以直接通过getMessage()获取异常信息。
自定义类,根据特有数据定义异常,如异常类名、异常的值等,如下图:
输出结果:
自定义异常:必须是自定义类继承Exception类。
为什么继承Exception:
异常体系有一个特点,因为异常类和异常对象都需要被抛出,他们都具备可抛性,这个可抛性是Throwable这个体系中的独有特点,只有这个体系中的类和对象才可以被throws和throw操作。只有继承Throwable、Exception、Error,只有在这个体系中才能使用throws和throw进行抛出异常。
11.5异常—throws和throw的区别
throws和throw的区别:
- throws使用在函数上,throw使用在函数内;
- throws后面跟的异常类,可以跟多个,用逗号隔开。throw后面跟的是异常对象(throw new 异常类)
11.6RuntimeException
ArithmeticException类构造函数有定义message的方法,可以直接使用这个构造方法打印错误信息:
打印结果:发现函数内抛了,但是函数上并没有声明过,但是编译通过了。
但是,当抛Exception时发现有安全隐患,编译时直接报错。
原因:
由API可发现ArithmeticException异常类的父类是RuntimeException异常,即运行时异常。该异常很特殊。RuntimeException或者RuntimeException的子类如果在函数内抛出了该异常,函数上可以不用声明异常,编译一样通过。如果在函数上声明了该异常,调用者可以不用进行处理(catch或throws),编译一样通过(如下例)。
原因:之所以不用再函数上声明是因为不需要让调用者处理,当该异常发生,希望程序直接停止。因为在运行时出现了无法继续运算的情况,希望停止程序后由程序员对代码进行修正。如int x = 3/0,一旦3/0被允许执行了,那么x将无法得到正确的值,因此必须对3/0进行正确处理才行
示例:
如上例,发现当name传null时,很容易出现空指针异常,我们再出现name传null时,必须要程序员进行修改才行。因此要让其抛异常才行。而且不能在方法上使用throws,让其抛出去。如下图:
总结:自定义异常时,如果该异常的发生,无法继续进行运算,就让自定义异常继承RuntimeExcetion。
所以自定义异常示例可改为:
输出结果:
对于异常分两种:
- 编译时被检测的异常(javac编译时,发现方法中抛出了非RuntimeException及其子类,而方法上没有标识throws,就会认为有安全隐患。此异常时可处理的,要标识出去,让调用者进行对应的处理;如果函数上标识了throws,函数的调用者也必须进行处理,try或抛);
- 编译时不被检测的异常(运行时异常:RuntimeException及其子类)(方法内抛throw,但是方法上不标识throws,它会拿着异常对象去判断是否是运行时异常(e instanceof RuntimeException),如果是,无论标识与否都不管)
11.7 异常练习(应用)
需求:毕老师用电脑上课
异常:电脑宕机、电脑蓝屏
要对问题进行描述,封装成对象
根据电脑操作状态的不同,导致不同问题的产生。
电脑冒烟了,不能抛出去,因为抛出去了其他老师也处理不了,但是当冒烟出现问题后,讲课进度无法继续,出现了讲课进度无法完成的问题,所以虽然捕捉到的是MaoYanExcetion,但是要把它封装成我的问题再跑出去(讲课无法完成问题)再抛出去
注意:throw后面不会继续执行,throw是函数结束的标志。
11.7异常—finally
finally块的代码无论如何都会执行,通常用于释放资源
示例:数据库连接(数据库连接有限,一旦占用不释放,其他人就会一直连不上)
连接数据库;数据库操作(throw new SQLException());关闭数据库(无论操作是否成功一定要关闭资源)
数据没有存储成功也是异常,SQLException我们处理不了,但是数据没有存储成功是可以处理的(分层思想,模块式开发):
11.8异常—处理语句其他格式
以下,因为throw new Exception()语句,已经被catch捕捉处理掉,所以语句正确
catch是处理异常,如果没有catch就代表异常没有被处理,如果该异常时检测时异常,那么必须声明。
11.9异常—覆盖时异常的特点
异常在子父类覆盖中的体现:
- 子类在覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或者该异常的子类。
父类已经有问题了,子类要继承父类的话不能比分类还有问题,只能跑父类或者父类的子类的异常
有一个子类覆盖父类,继承CException,且Test中传入子类对象:
这样程序会挂,编译就是失败。
- 如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集
- 如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常;如果子类发生了异常,就必须要进行try处理,绝对不能抛
10.10异常—练习
有一个圆形和长方形,都可以获取面积,对于面积如果出现非法的数值,视为获取面积出现问题,问题通过异常来表示。
基本设计:
当传入负数时,面积为负数,无意义
之前处理方式:
会发现正常流程代码和问题处理代码结合比较紧密,阅读性差。异常的产生可以让流程处理代码和问题处理代码分离。
输出:
如果真的出现问题,一旦为负数,就没意义,不会继续执行,所以直接使用RuntimeException即可,也不用进行标识。主函数中也就不需要catch了
求圆的面积:写RuntimeException也可以,但是,此名称跟程序意义无关,不能见名知意
所以还是写自定义异常比较直观:
11.11异常—总结
异常:是对问题的描述,将问题进行对象的封装。
异常体系:
Throwable
|–Error
|–Exception
|–RuntimeException
异常体系的特点:异常体系中的所有类以及建立的对象都具备可抛性,也就是说可以被throw和throws关键字所操作。只有异常体系具备这个特点。
throw和throws的用法:
throw定义在函数内,用于抛出异常对象,throws定义在函数上,用于抛出异常类,可以抛出多个用逗号隔开。
当函数内容有throw抛出异常对象,并未进行try处理。必须在函数上声明,否则编译失败。注意RuntimeException除外。也就是说,如果函数内抛出的是RuntimeException异常,函数上可以不用声明。
如果函数声明了异常,调用者需要进行处理,处理方式可throws可以try。
异常有两种:一种叫做编译时被检测异常(该异常在编译时,如果没有处理(没有抛也没有try),那么编译失败,该异常被标识,代表着可以被处理);一种叫做运行时异常(编译时不检测)(在编译时,不需要处理,编译器不检查,该异常的发生,建议不处理,让程序停止,需要对代码进行修正)
异常处理语句:
try{
需要被检测的代码
}catch(){
处理异常的代码
}finally{
一定会处理的代码
}
有三种结合格式:
try{
}catch(){
}
try{
}finally{
}
try{
}catch(){
}finally{
}
注意:
- finally中定义的通常是关闭资源代码,因为资源必须要释放。
- finally有一种情况读不到:即系统退出(System.exit(0)虚拟机结束时,finally不会再执行)
自定义异常:
定义类继承Exception或者RuntimeException
- 为了让该自定义类具备可抛性
- 让该类具备操作异常的共性方法
当要定义自定义异常的信息时,可以使用父类已经定义好的功能。异常信息传递给父类的构造函数
class MyException extends Exception{
MyException(String message){
super(message);
}
}
自定义异常:
按照java的面向对象思想,将程序中出现的特有问题进行封装
自定义异常好处:
- 将问题进行封装
- 将正常流程代码和问题处理代码相分离,方便阅读
异常的处理原则:
- 处理方式有两种:try 或者throws
- 调用到抛出异常的功能时,抛出几个,就处理几个(不多抛不多处理)。一个try对应多个catch的情况。
- 多个catch,父类的catch放到最下面
- catch内需要定义针对性的处理方式,不要简单的定义printStackTrace输出语句,也不要不写。当捕获到的异常,本功能处理不了时,可以继续在catch中抛出即:
try{
throw new AException();
}catch(AException e){
throw e;
}
如果该异常处理不了,但并不属于该功能出现的异常,可以将异常妆花后,再抛出和该功能相关的异常。
或者异常可以处理,当需要将异常产生的和本功能相关的问题提供出去,让调用者知道,并处理。也可以将捕获异常处理后,转换为新的异常
try{
throw new AException();
}catch(AException e){
//捕获到AException但是AException处理不了,可以转换为新的异常处理类
throw new BException();
}
比如,汇款的例子
异常的注意事项:
在子父类覆盖时:
- 子类抛出的异常必须是父类的异常的子类或者子集;
- 如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能try不能抛
参阅:ExceptionTest.java 老师用电脑上课
ExceptionTest1.java图形的面积
11.12异常—练习四
如上图,首先执行main中的func()方法,调用到func(),执行try抛异常,执行finally中B,再由main()中捕捉到异常执行main中catch得到C,A在func()有异常后不再执行,再执行D,所以,输出为B C D。
如上图,main方法中首先执行new Demo后,执行到默认的super()方法(默认有,没有写而已),从而执行到其父类Test类,输出Test,然后输出Demo,在执行main中new Test(),输出Test。
在多态中(父类有指向子类对象时)成员函数的特点:
在编译时期,参阅引用型变量(Fu类)所属的类中是否有调用的方法,如果有编译通过,如果没有编译失败;//此处A接口中没有定义func()方法,所以编译失败
在运行时期,参阅对象(Zi类)所属的类中是否有调用的方法。
如上图,首次执行到for循环时,f.show(‘A’)执行Demo类中Demo()方法,打印A输出false,而f.show(‘B’)也执行Demo类中的Demo()方法打印B输出false满足条件,(i<2)不再执行,因此循环直接结束。(在运行时期,参阅对象(Zi类)所属的类中是否有调用的方法。)。
如上图,A接口中没有定义Test方法,编译失败。
如上图,执行main中new Demo(“A”)后,执行到Demo类中构造方法,而其中会有个默认的super()方法,从而执行到父类Super中的狗仔函数Super(),从而打印B,执行i+=2,然后继续执行Demo类中Demo()构造函数中代码,打印C,将i赋值为5,然后继续执行mian(),从而打印d.i 为5
如上图,定义匿名内部类的前提:内部类必须继承一个类,或者实现接口。
interface Inter{
void show(int a,int b);
void func();
}
class Demo {
public static void main(String[] args){
Inter in = new Inter(){
public void show(int a,int b){}
public void func(){}
}
in.show();
in.func();
}
}
匿名内部类一定要在方法上加上public标识共有;
要调用匿名内部类的多个方法,使用
Inter in = new Inter(){}
格式后,用实例名
in
调用即可
如上图,编译失败,非静态内部类,不可以定义静态成员;
内部类中如果定义了静态成员,该内部类必须被静态修饰
如上图,读清楚题目,是存在于Demo的子类中,不是再同一个函数的重载
A 可以,覆盖
B 不可以 权限不够
C 可以 和父类不是一个函数。没有覆盖相当于重载
D 不可以,因为该函数不可以和给定函数出现在同一类中,或者子父类中
E 不可以,静态只能覆盖静态
this:代表本类对象,哪个对象调用this所在函数,this就代表哪个对象
final:
- 修饰类,变量(成员变量、静态变量、局部变量),函数
- 修饰的类不可以被继承
- 修饰的方法不可以被覆盖
- 修饰的变量是一个常量,只能赋值一次
- 内部类只能访问局部的final形式变量
11.
如上图,输出结果:4 5 showZi showZi
成员变量看左边,变量不存在覆盖;
方法看右边
如上图,定义成员变量,然后计算和
局部接收进来的,值改变后,其他方法也可以用
如上图,输出BCD
如上图,编译失败,应该父类中缺少空参数的构造函数;或者子类应该通过super语句指定要调用的父类中的构造函数
如上图,编译失败:因为子父类中的get方法没有覆盖,子类调用的时候不能明确返回值是什么类型,所以这样的函数不能存在在子父类中。
如上图,编译失败:因为打印字符串“A”的输出语句执行不到。
如上图,16题和13题区:把抛出异常封装在方法中,代表有可能有问题,有可能没有问题,所以13题中的A是有可能执行到的,但是16题中A绝对不可能执行到。
如上图,A ok
B 不可以,因为主函数是静态的,如果要访问inner需要被static修饰
C 错误,格式错误
D 错误 因为inner不是静态的
编译失败,多个catch时,父类的catch要放在最下面。
如上图,方法时静态的,所以调用多次后,前面一次方法不会销毁,且output也是共享的不会销毁,foo(0)执行后,在执行foo(1)时继续累加
return语句后,finally中的语句继续执行,但是finally外的不会继续执行
所以结果是 13423
已经做过
输出4
已经做过
如上图,数组查找,数组可能无序,所以不要折半
如上图,用到本类对象this
前面的cir用this表示,cir2传入
12包
12.1包package
- 对类文件进行分类管理
- 给类提供多层命名空间
- 写在程序文件的第一行
- 类名的全称是 包名.类名
- 包也是一种封装形式
命令中加参数用于创建包的目录
javac –d . PackageDemo.java
-d 指定包所存放的位置
. 表示当前目录
PackageDemo.java 类名
执行时:
java pack.PackageDemo
不存在当前目录(而存放到指定目录):
javac –d c:\ PackageDemo.java
set classpath–c:\ 指向包(pack)的父目录即可
java pack.PackageDemo
包的出现可以让java的类文件和class文件相分离,当别人要执行时,可以只给class运行文件,而不用给源文件
12.2包与包之间的访问
先编译DemoA,再编译PackageDemo
编译时将编译文件存在存一个路径
发现报错。
错误原因:类名写错
因为类名的全名是:包名.类名
改正后:
发现又报错:
错误原因:软件包不存在
packa包不再当前目录下,需要设置classpath,告诉JVM去哪里找指定的packa包
包找到了,但是又发现其他错误
有了包,范围变大,一个保重的类要被访问,必须要有足够大的权限,所以要被访问的类需要被public修饰
因为DemoA类的修饰符没有权限
类的修饰符有两个:protected、public
改完后发现其他错误:
错误原因:类共有后,被访问的成员也要共有才可以被访问
改正后,运行正确
总结:
- 包与包之间进行访问,被访问的包中的类以及类中的成员,需要public修饰
- 不同包中的子类还可以直接访问父类中被protected权限修饰的成员(java给包与包中的方法提供权限protected权限(默认),使其不继承也可以拿到某类中的方法)
包与包之间使用的权限只有两种:public和protected(只能给子类用)
|
public |
protected |
default |
private |
同一类中 |
Ok |
Ok |
Ok |
Ok |
同一包中 |
Ok |
Ok |
Ok |
|
子类 |
Ok |
Ok |
|
|
不同保重 |
Ok |
|
|
|
一个java文件里不能出现两个公有类或接口:(以下代码错误)
可以将两个文件放在同一个包下
12.3导入import
为了简化类名的书写,使用一个关键字:import
同一目录下有多个类时,使用*,导入到文件
import导入的是包中的类,如果有包是不能导入的
建议不要写通配符*,需要用到包中的那个类就导入哪个类,否则占用内存太多
导入不同包的同名的包时,必须写清楚类的全名(包名+类名)
如上例,packa和backb包中存在同名的DemoC类时,使用时,必须写DemoCracy的全名packa.DemoC c = new packa.DemoC();
建议定义包名时不要重复,可以使用URL来完成定义,URL是唯一的
12.4jar包
java的压缩包
- 方便项目的携带
- 方便与使用,只要在classpath设置jar路径即可
- 数据库驱动,SSH框架等都是以jar包体现的
java打jar包需要借助java JDK工具java.exe
jar命令的用法:
如:
-c 创建新的归档文件
-f 创建的归档文件名
haha.jar jar包名字
packa pack 需要打成包的文件夹(注:命令必须在当前文件夹下执行,如例两个文件夹在myclass文件下)
jar包和打成其他包的区别:
jar –tf haha.jar 查看归档目录
存放了java特有的配置文件,且
目录下存放的文件都删除了,双击jar包时仍然可以执行的。如下,
使用jar –cvf a.jar packa pack 可以显示打印时的详细信息
jar –tvf a.jar packa pack 可以显示时间等详细信息
数据重定向
将打包时的数据定向到1.txt文件夹内:
将jar包中所有的类重定向放到rt.txt文件中