这一篇文章内容不太容易理解,但是同时也是java最为重要的一块理论知识,需要大家好好琢磨清楚。
1.编程语言的发展(这儿大致了解就行):发展是朝着人类更内容理解的方向。
机器语言:直接由计算机的指令组成,指令、数据、地址都以“0”和“1”的符合串组成;可以被计算机直接执行。
汇编语言:用容易理解和记忆的符号表示指令、数据以及寄存器等,抽象层次很低,程序员需要考虑大量的机器细节。
高级语言:屏蔽了机器细节,提高了语言的抽象层次接近人的自然语言,60年代出现的结构化编程语言提出了结构化数据和语句,数据和过程抽象等概念。
面向对象的语言:与以往的各种语言的根本不同是,它的设计出发点就是为了更能直接的描述问题域中客观存在的事物。
2.面向过程的设计思想和面向对象的设计思想
面向过程就是分析出解决问题需要的步骤,然后用函数把这些步骤一个个实现,使用的时候依次调用,面向过程的核心是过程。
面向对象就是把构成问题的事物分解成一个个对象,建立对象不是为了实现一个步骤,而是为了描述某个事物在解决问题中的行为,面向对象的核心是对象。
3.java面向对象的基本概念
(1)对象和类的概念(有了类再去找对象)
对象用计算机语言对问题域中事物的描述,对象通过“属性attribute”和“方法method”来分别对应事物所具有的静态属性和动态属性。
类是用于描述同一类型的对象的一个抽象的概念,类中定义了这一类对象所因具有静态和动态属性。
类可以看成一类对象(object)的模版,对象可以看成该类的一个具体实例(instance)。
举例:如瓶子(一类事物的抽象)类比于类(类中包括成员变量(静态属性,也叫属性),方法(动态属性));这个啤酒瓶就是瓶子的一个实例,也就是我们常说的实例化后的对象。
(2)类(对象)之间的关系
1)关联关系(最弱):类与类之间的连接,它使得一个类知道另外一个类的属性和方法。
2)继承关系(能用“XX是一种XX”,这种形式来描述),理论上一个类可继承多个父类(但ava不允许多继承,只能继承一个父类,但是可以实现多个接口)
3)聚合关系(整体和部分)
聚集:松耦合(并不是说离了哪个不行)
组合:密不可分
组合与聚合几乎完全相同,唯一区别就是对于组合,“部分”不同脱离“整体”单独存在,其生命周期应该是一致的。
4)实现关系:实现是指一个class实现interface接口(一个或者多个),表示类具备了某种能力,实现是类与接口中最常见的关系,在Java中通过implements关键字来表示。
(3)java与面向对象
对象是java程序的核心,在java程序中“万事万物皆对象”。
对象可以看成是静态属性(成员变量)和动态属性(方法)的封装体。
类是用来创建同一类型的对象的“模版”,在一个类中定义了该类对象所应具有的成员变量以及方法。
jdk提供了很多类供程序员使用,编程人员也可以定义自己的类。
(6)成员变量
Java类定义
public class Person {
//成员变量的定义
private int id;
private int age=20;
private String sex;
// 方法定义
public int getId() {
return id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
成员变量:成员变量可以是java语言中任何一种数据类型(包括基本类型和引用类型)。
在定义成员变量时可以对其初始化,如果不对其初始化,java使用其类型默认的初始化值(局部变量必须自己对其初始化,java不会默认对其初始化)。
成员变量类型 | 初始化默认取值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
char | '\u0000' |
float | 0.0F |
double | 0.0D |
boolean | false |
所有引用类型 | null |
成员变量的作用范围为整个类体。
(7)引用(提到引用二字,脑海中要想到它在内存中是一小块区域指向一大块区域,这对分析一个程序的内存变化有好处)
java语言中除了八种基本类型之外的变量类型都称之为引用类型(引用类型在内存中占两块)。
java中的对象是通过引用对其操作的。
/*
声明了一个String类型是的引用变量,
但并没有使它指向一个对象
*/
String str;
/*
使用new语句创建了一个String类型的对象并用str指向它
以后听过str完成对其的操作
*/
str = new String("hello world");
过程内存分配图:
如何在内存中区分类和对象?
1)类是一个静态的概念,它是放在代码区里面的。
2)对象是new出来的,位于堆内存(用于在运行期间动态分配内存,堆的内存区比较大,如果分配的内存不用了,则会被GC回收)
3)类的每个成员变量在不同的对象中都有不同的值(除了静态变量),而方法只有一份,执行的时候才占用内存。
4.对象的创建和使用
1)必须用new关键字来创建对象。
2)使用对象(引用).成员变量来引用对象的成员变量。
3)使用对象(引用).方法(参数列表)来调用对象的方法。
4)同一类的每个对象有不同的成员变量存储空间(new一个新对象就产生一块新内存)。
5)同一类的每个对象共享该类的方法。
非静态方法是针对每个对象进行调用。
6)成员变量(成员变量在new的时候才会在堆内分配)分配在堆内存局部变量分配在在栈(stack)内存。
(8个原生数据类型的局部变量储存在栈中,成员变量储存在堆中。)
以下程序的内存分析:
public class C {
int i;
int j;
public static void main(String[] args) {
C c1=new C();
C c2=new C();
}
}
内存分配分析图(重要:建议好好琢磨清楚这张图):
5.构造方法
1)使用new+构造方法 创建一个新对象。
2)构造方法是定义在java类中的一个用来初始化对象的方法。
3)构造方法与类同名且没有返回值(注意,连void都没有)。
4)当没有指定构造函数时,编译器为类自动添加无参构造方法,形如:ClassName(){}的构造函数。
无参构造方法的例子:
/**
* 说明:无参构造方法举例
*
* @author huayu
* @date 2018/8/2 下午9:17
*/
public class Student {
/*下面注释掉的在这个代码中可有可无,没有的话编译器会默认加一个无参构造方法
(换句话说,类中若有参构造和无参构造都不写,后期编译器会默认给加一个无参构造方法)
public Student(){}。所以若不定义有参构造方法,无参构造可写可不写(不写后期默认也会加上)。
反之,一旦自己定义了构造方法,系统就不会给默认添加任何构造方法了。
*/
/*public Student() {
}*/
private int id;
private int age;
public static void main(String[] args) {
/*调用无参构造时,可不传参数。调用下列语句(无参构造)时,id,age会被默认初始化为0。(不传
参数时,各个类型默认值以前讲过啦,忘了的可以翻一下博客文章看一下)
*/
Student tom=new Student();
}
}
本例内存分配分析图:
有参构造方法例子:
/**
* 说明:有参构造方法举例
*
* @author huayu
* @date 2018/8/2 下午9:17
*/
public class Student {
private int id;
private int age;
//构造方法
public Student(int _id, int _age) {
this.id = _id;
this.age = _age;
}
public static void main(String[] args) {
Student tom =new Student(1,25);
}
}
本例内存分配分析图:
6.内存分析详解(这个太重要了,一定要好好学)
代码:
package oop;
/**
* 说明:实体类
*
* @author huayu
* @date 2018/8/3
*/
public class Birthday {
private int day;
private int month;
private int year;
//有参构造方法
public Birthday(int day, int month, int year) {
this.day = day;
this.month = month;
this.year = year;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
@Override
public String toString() {
return "Birthday{" +
"day=" + day +
", month=" + month +
", year=" + year +
'}';
}
}
import oop.Birthday;
/**
* 说明:测试类
*
* @author huayu
* @date 2018/8/3
*/
public class TestBirthday {
public static void main(String[] args) {
//new TestBirthday();相当于调用了TestBirthday类的无参构造方法(编译器默认加的)
1. TestBirthday test=new TestBirthday();
//调到这句在栈内存分配一块名为date,值为9的内存
2. int date=9;
//new Birthday(7,7,1970);相当于调用了Birthday类中的有参构造方法
3. Birthday d1=new Birthday(7,7,1970);
4. Birthday d2=new Birthday(1,1,2000);
5. test.change1(date);
6. test.change2(d1);
7. test.change3(d2);
8. System.out.println("date="+date);
9. d1.toString();
10. d2.toString();
}
public void change1(int i){
11. i=1234;
}
public void change2(Birthday b){
12. b=new Birthday(22,2,2004);
}
public void change3(Birthday b){
13. b.setDay(22);
}
}
内存过程分析:
在做分析以前我们应该预备的知识有:
1)栈内存储的是局部变量,基础类型的局部变量也分配在栈中,而且它只占一块内存:如下图栈中的局部变量date,一个int类型变量分配了一块int类型空间,四个字节,里面装了个值9,名字叫做date。
2)引用类型在内存中占两大块(栈中跟堆中),一块在栈中存的是指向对象的引用(对象在堆中的地址),一块在堆中存的是new出来的对象。(凡是new出东西来,则在堆内存中做分配):如下图栈中的局部变量test,一个引用类型分配了一块内存用于存test的引用(即对象在堆中的存储地址,后期利用这个存储地址去找到并操作堆中new出来这个引用的对象),它指向了在堆中new出来的对象的一块内存(即图中那块未存东西的空位置)。
3)方法中的形参与局部变量等同
4)方法调用的时候是值传递
5)方法执行完毕后,在栈中为这个方法分配的局部变量的空间全部消失。若方法调用有基本类型返回值,则栈中会为其提供一块暂存值的空间,返回值暂存在栈中,同样,当方法调用完毕后内存也会一同被收回。
6)在堆中new出来的对象若栈中没有了它的引用(也就是不用它了),后期会被GC清理掉那部分堆中的内存,而GC回收的具体时间不可控。
上面代码中当执行到TestBirthday中main方法第四句时,内存里面的布局是这个样子的(下图1中所示堆中的d1对象显示的好像是3块,其实就是一个块,也就是堆中就一个d1对象,我为了看起来清楚才这么画的自己知道就可以了,d2同理也是就一个对象)。
(1)执行change1(int i)方法
接下来我们开始执行第五句chage1(int i)方法,而里面有个形参i,当我们调用这个方法的时候,首先应该在栈里面为形参(与局部变量等同)分配一块空间,如下分配了一个空间名字叫i,值的话是实参传了多少就是多少,在这实参是date且值为9,所以形参i的值是9(方法调用的时候是值传递,相当于把date的值9复制给了i),此时的内存布局为图2:
执行到第11句,i=1234;到这时i的值由9变为了1234,但是date值不会遍,因为i当时的值9是date复制了一份给它的,所以i的改变对date无影响。此时的内存布局为图3:
当执行完change1方法后,形参i在栈中分配的空间被收回(注意这时date依然不受影响哈),此时的内存布局为图4
(2)执行change2(Birthday b)方法
首先,形参要求传一个Birthday b的引用类型,所以我们将d1传进来。Birthday b是一个局部变量,二话不说在栈内存空间内分配一个变量b,b中装的是实参中传的内容(即d1中的内容),当执行了这个方法之后,它会把d1中的值复制给b。此时b中的地址跟d1是一样的,他们指向的是同一个对象(堆中的d1对象)。此时的内存布局为图5:
当执行第12句b=new Birthday(22,2,2004);这一句时,堆中这是又会new出一个新的对象(凡是new出东西来,则在堆内存中做分配),此时栈中的引用地址指向新new出来的b的对象(注意到栈中b的引用地址也变化了)。此时的内存布局为图6:
当方法change2执行完毕后,为其在栈中分布的局部变量空间也随之消失。注意,此时栈中为方法执行分布的局部变量被收回了,但是它在堆上new出来的对象b不一定消失,它会等待GC来回收它,具体回收时间不可控,所以我们也不确定它什么时候能消失,但是弱栈中没有了指向它的引用,这个b对象迟早会被GC清理掉,早晚会消失。此时的内存布局为图7:
(3)执行change3(Birthday b)方法
首先,形参要求传一个Birthday b的引用类型,所以我们将d2传进来。Birthday b是一个局部变量,二话不说在栈内存空间内分配一个变量b,b中装的是实参中传的内容(即d2中的内容),当执行了这个方法之后,它会把d2中的值复制给b。此时b中的地址跟d2是一样的,他们指向的是同一个对象(堆中的d2对象)。此时的内存布局为图8:
当执行到第13句b.setDay(22);(是Birthday类中的public void setDay(int day) {this.day = day;}这个方法),它会利用b这个引用去操作d2对象,当传入实参22给int day后它会把值传给this.day,则day的值真正被改变了(注意,此时的栈中b引用跟d2引用的值都没变,还是指向同一个对象)。此时的内存布局为图9:
当方法change3执行完毕后,为其在栈中分布的局部变量空间也随之消失。注意,虽然此时栈中b引用虽然消失了,但它指向的对象d2还有栈中的d2引用在,所以堆中对象d2不会消失。此时的内存布局为图10:
change1,change2方法调用完后,或早或晚的栈内存,堆内存中分配的空间的都会被回收,没起任何实质性作用,相当与白调了。而change3方法调用了实体类Birthday中的具体方法,确实并对堆内存中仍然被栈空间的引用d2所引用的堆内对象做了修改产生了实质性作用。
7.方法的重载:重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载。
注:1)参数列表不同包括两种:参数类型,参数个数有一个不同就可以。
2)重载对返回类型没有特殊的要求。
3)方法重载的本质是在重载方法被调用时,会根据不同的参数表选择对应的方法,只要能区分开来不会混淆则他们两个方法 就是重载。
4)调用时,会根据不同的参数选择对应的方法,实现的是编译时的多态性。
5)构造方法也可以被重载,但不可以被重写,因为构造方法不能被继承。
举例:
/**
* 说明:方法重载举例
*
* @author huayu
* @date 2018/8/3
*/
public class Overload {
public void max(int a){
System.out.println(a);
}
public int max(int a,int b){
System.out.println(a>b?a:b);
return a>b?a:b;
}
public void max(int a,int b){
System.out.println(a>b?a:b);
}
public void max(short a,short b){
System.out.println(a>b?a:b);
}
public void max(float a,float b){
System.out.println(a>b?a:b);
}
}
/**
* 说明:构造方法重载举例
*
* @author huayu
* @date 2018/8/3
*/
public class Overload {
private int a;
private int b;
public Overload() {
}
public Overload(int a) {
this.a = a;
}
public Overload(int a, int b) {
this.a = a;
this.b = b;
}
}
8.this关键字
1)在类的方法定义中使用this关键字代表代表使用该方法的对象的引用
2)当必须指出当前使用方法的对象是谁时要用this。
3)有时使用this可以处理方法中成员变量和参数重名的情况。
4)this可以看作是一个变量,它的值是当前对象的引用。
强调:this一般出现在方法里面,当这个方法还没调用的时候,this指的是谁并不知道,但是实际当中你如果new一个对象出来的话,this指的就是当前这个对象。你对那个方法调这个对象,那么this指的就是谁。
/**
* 说明:this用法举例
*
* @author huayu
* @date 2018/8/3 下午5:39
*/
public class Leaf {
int i=0;
public Leaf(int i) {
1. this.i = i;
}
public Leaf increament(){
2. i++;
3. return this;
}
void print(){
4. System.out.println("i="+i);
}
public static void main(String[] args) {
5. Leaf leaf=new Leaf(100);
6. leaf.increament().increament().print();
}
}
上面代码内存分析:
第一步:通过执行main方法第5句,调到了Leaf类的构造方法Leaf(int i),这时栈空间为形参i开辟了一块内存,实参传入的值为100,又将栈内存中形参i的值赋值给了堆内存成员变量i,所以this.i=100。
第二步:构造方法完成,为它分配的局部变量形参i消失。
第三步:执行第6句leaf.increament().increament().print(); leaf.increament()执行leaf对象的increament()方法,执行i++;
第四步:执行第3句return this;return会在栈空间分配一块临时的内存,这块空间是this的内容,而this指向它自身,所以这块临时内存也指向了对象leaf。leaf.increament()执行完成之后内存分布如下图:
第五步:对leaf.increament()结果再调用increament()方法,即还是对原来的对象调用,执行完i++;后,成员变量i值变为102。
第六步:再执行return this;return又会在栈空间分配一块临时的内存,这块空间是this的内容,而this指向它自身,所以这块临时内存也指向了对象leaf。
第7步,调用完print()方法,整个方法执行完成后,栈中局部变量的内存空间回收
9.static关键字
1)在类中,用static声明的成员变量为静态成员变量,它为该类的公用变量。在第一次使用时候被初始化,对于该类的所有对象来说,static成员变量只有一份。
2)用static声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员。(静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能对象并没有被初始化)。
3)可以通过对象引用或类名(不需要实例化)访问静态成员。
4)static静态变量存在在data seg数据区,就算是不new它,它也会在data seg部分保留一份。静态变量是属于整个类的,它不属于某一个对象。
知识链接:字符串常量在内存中分配也是在data segment部分。
/**
* 说明:静态变量内存分析举例
*
* @author huayu
* @date 2018/8/3
*/
public class Cat {
//静态成员变量,就算不new对象它也会在data seg里面保存一份,它属于整个类
//不属于某个对象。int静态变量可以用来计数。
//对静态值访问:1.任何一个对象通过对象的引用都可以访问这个静态对象,访问的时候都是同一块内存
//2.即便是没有对象,也可以通过 类名. 来访问 如:System.out out是个静态变量
1. private static int sid=0;
//非静态成员变量 new对象的时候在堆内存对象中保存,每new一个对象产生一块
2. private String name;
//非静态成员变量 new对象的时候在堆内存对象中保存,每new一个对象产生一块
3. private int id;
public Cat(String name) {
4. this.name = name;
5. id=sid++;
}
public void info(){
6. System.out.println("My name is "+name+" No."+id);
}
public static void main(String[] args) {
//静态变量sid属于整个Cat类,不属于某个对象,可以用类名.来访问,所以这儿没有new任何对
//象,直接用类名.(Cat.sid)来访问的。
7. Cat.sid=100;
//字符串常量分配在data seg
8. Cat mimi=new Cat("mimi");
9. Cat pipi=new Cat("pipi");
10. mimi.info();
11. pipi.info();
}
}
打印结果:
My name is mimi No.id=100 sid= 102
My name is pipi No.id=101 sid= 102
执行完7Cat.sid时,静态变量sid值为100。内存分布状态如下:
(1)执行第7句构造方法
第一步:执行第7句Cat mimi=new Cat("mimi");字符串常量“mimi”分布在data segment部分,内存分布如下(这儿看不懂的再回去看以前我的java程序内存分析的博客):
第二步:调到上面后就该到Cat的构造方法了,执行第4句this.name = name;这时根据传入构造方法的name形参,栈中就会为其开辟一块名为name的空间,实参“mimi”传了进来,这时候栈中name的引用指向了data segment中的字符串常量“mimi”,因为this.name = name,所以自身成员变量的this.name也指向了data segment中的字符串常量“mimi”。
第三步:执行id=sid++;mimi对象的成员变量i值为原来sid的值100,接下来sid++,将sid的值改为101,内存状态如下图:
第四步:构造方法执行完成后,为执行这个方法在栈中分配的形参变量的内存空间收回,name在栈中的空间消失(当然,为执行方法而在栈中分配的局部变量空间,方法执行完毕后都应该被收回了)
(2)执行Cat pipi=new Cat("pipi"); 方法跟执行上面那个构造方法原理是一样的(当然,为执行方法而在栈中分配的局部变量空间,方法执行完毕后都应该被收回了),大家自己画一下,我这边把最后的内存分布状态给一下大家:
从以上sid(static id)的变化不难看出,int的静态变量可以用作计数用。
(3)将以上程序的static静态变量static int sid;再改为非静态变量 int sid;后做内存分析对比
代码:
/**
* 说明:将上面代码静态变量改为非静态的再做内存分析
*
* @author huayu
* @date 2018/8/3 下午6:32
*/
public class Cat1 {
//成员变量在new的时候才会在堆内分配
private int sid=0;
private String name;
private int id;
public Cat1(String name) {
this.name = name;
id=sid++;
}
public void info(){
System.out.println("My name is "+name+" No.id="+id+" sid= "+sid);
}
public static void main(String[] args) {
// Cat1.sid=100;
Cat1 mimi=new Cat1("mimi");
Cat1 pipi=new Cat1("pipi");
mimi.info();
pipi.info();
}
}
输出结果:
My name is mimi No.id=0 sid= 1
My name is pipi No.id=0 sid= 1
对以上程序进行内存分析:这儿上面以前详细的讲过,大家亲自动手去画一画,感受了解一下静态变量跟非静态变量的区别,下面我给贴个最终状态的图(记得,为执行方法而在栈中分配的局部变量空间,最终方法执行完毕后都应该被收回了):
从以上过程不难看出当静态变量static int sid;改为非静态变量int sid;后,每new一个对象,sid不再发生变化,故用它来计数是不可能了。
10.package和import语句
(1)package (包)例如:package com.jd;
1)为了便于管理大型软件系统中数目众多的类,解决类的命名冲突问题,java引入包(package)机制,提供类的多重类命名空 间。
2)约定俗称的包起名方式:把公司的域名倒过来。如:package com.jd;这个包包了两层(建包可不是只能建两层哈,别误会,只是这个例子中有两层),公司域名不是一样的,这样起名避免类名产生重复。
3)package语句作为java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。
它的格式为package pkg1[.pkg2[.pkg3....]];
4)java编译器把包对应于文件系统的目录管理,package语句中,用“.“来指明包(目录)的层次,例如package com.jd;则该文件中所有的类位于.\com\jd 目录下
5)class文件的最上层包的父目录应位于classpath下(在公司不同的项目一般设置不同的classpath)。
6)要想执行一个类必须写全包名。如:java com.jd.TestPackage
(2)import(引入):用于引入非同包类,同胞类不需要引入,直接用就可以。
例:import oop.Birthday; //引入oop包下的Birthday类
11.j2sdk中主要的包介绍
java.lang-包含一些java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。这里面的类不需要引入就直接能用,除了他之外其它包在用的时候必须要先引入。
java.awt-包含了构成抽象窗口工具类(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界 面(GUI)。
java.applet-包含applet运行所需的一些类。
java.net-包含执行与网络相关的操作的类。
java.io-包含能提供多种输入/输出功能的类。
java.util-包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。
知识补充:将自己写的类打成jar包备用。打jar包命令:jar -cvf
192:java huayu$ jar
用法: jar {ctxui}[vfm0Me] [jar-file] [manifest-file] [entry-point] [-C dir] files ...
选项包括:
-c 创建新的归档文件
-t 列出归档目录
-x 从档案中提取指定的 (或所有) 文件
-u 更新现有的归档文件
-v 在标准输出中生成详细输出
-f 指定归档文件名
-m 包含指定清单文件中的清单信息
-e 为捆绑到可执行 jar 文件的独立应用程序
指定应用程序入口点
-0 仅存储; 不使用情况任何 ZIP 压缩
-M 不创建条目的清单文件
-i 为指定的 jar 文件生成索引信息
-C 更改为指定的目录并包含其中的文件
如果有任何目录文件, 则对其进行递归处理。
清单文件名, 归档文件名和入口点名称的指定顺序
与 'm', 'f' 和 'e' 标记的指定顺序相同。
示例 1: 将两个类文件归档到一个名为 classes.jar 的归档文件中:
jar cvf classes.jar Foo.class Bar.class
示例 2: 使用现有的清单文件 'mymanifest' 并
将 foo/ 目录中的所有文件归档到 'classes.jar' 中:
jar cvfm classes.jar mymanifest -C foo/ .
用法举例:jar -cvf Test.jar *.*
192:java huayu$ jar -cvf Test.jar *.*
已添加清单
正在添加: HelloWord.class(输入 = 409) (输出 = 280)(压缩了 31%)
正在添加: HelloWord.java(输入 = 124) (输出 = 106)(压缩了 14%)
正在添加: Test.class(输入 = 187) (输出 = 160)(压缩了 14%)
192:java huayu$ ls
HelloWord.class Test.class
HelloWord.java Test.jar//这就是那个打包完后的jar包
12.访问控制
java权限修饰符public protected private置于类的成员定义前,用来限定其他对象对该类对象成员的访问权限。
修饰符 | 类内部 | 同一个包 | 子类 | 任何地方 |
private | yes(可以访问) | |||
default | yes(可以访问) | yes(可以访问) | ||
protected | yes(可以访问) | yes(可以访问) | yes(可以访问) | |
public | yes(可以访问) | yes(可以访问) | yes(可以访问) | yes(可以访问) |
注意:1)对于class的权限修饰只可以用public和default。
2)类的成员(包括内部类)的修饰符可以是以上四种。
3)类的成员不写访问修饰时默认为 default,默认对于同一个包中的其他类相当于公开 (public),对于不是同一个包中的其 他类相当于私有(private)。
4)受保护(protected)对子类 相当于公开,对不是同一包中的没有父子关系的类相当于私有。
13.类的继承与权限控制(继承关系:能说通XX是XX就可以看作继承关系,比如,狗是动物)
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、 基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续 性,同时继承也是封装程序中可变因素的重要手段。
1)java中使用extends关键字实现类的继承机制,其语法规则为:<modifier>class<name>[extends<superclass>]{... ...}
2)通过继承,子类自动拥有了父类(基类superclass)的所有成员(成员变量和方法)。
3)java只支持单继承,不允许多继承(一个子类只能有一个基类,一个基类可以派生出多个子类)。
代码举例:
package oop;
/**
* 说明:
*
* @author huayu
* @date 2018/8/5 1:03 PM
*/
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
/**
* 说明:
*
* @author huayu
* @date 2018/8/5 1:05 PM
*/
public class Student extends Person{
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
import oop.Student;
/**
* 说明:
*
* @author huayu
* @date 2018/8/5 1:06 PM
*/
public class TestStudent {
public static void main(String[] args) {
1. Student student=new Student();
2. student.setName("yuhua");
3. student.setAge(18);
4. student.setSchool("BJTU");
5. System.out.println("name: "+student.getName()+" age:"+student.getAge()+" School:"+student.getSchool());
}
}
以上程序的内存分析过程:
第一步:执行第1句 Student student=new Student();这句后的内存状态:
执行完main方法后,最终内存分布图(当然,为执行方法而在栈中分配的局部变量空间,方法执行完毕后都应该被收回了),这儿我给个贴图,过程不难自己分析,如果再不会就再好好看一下以前讲的内存分析的那部分知识:
14.方法的重写(overwrite)
1)在子类中可以根据对从基类中继承来的方法进行重写。
2)重写方法必须和被重写方法具有相同的方法名称、参数列表和返回类型。
3)重写方法不能使用比被重写方法更严格的访问权限(子类的权限不能小于基类,要大于等于才行)。
4)变量的重写看引用,方法的重写看对象。
/**
* 说明:重写代码举例,基类
*
* @author huayu
* @date 2018/8/5 1:03 PM
*/
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/**
* 说明:重写代码举例,子类
*
* @author huayu
* @date 2018/8/5 1:05 PM
*/
public class Student extends Person{
private String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" + "name='" + getName() + '\'' +
" age='" + getAge()+'\''+
" school='" + school + '\'' +
'}';
}
}
/**
* 说明:测试类
*
* @author huayu
* @date 2018/8/5 1:06 PM
*/
public class TestStudent {
public static void main(String[] args) {
Student student=new Student();
Person person=new Person();
person.setName("john");
person.setAge(19);
student.setName("yuhua");
student.setAge(18);
student.setSchool("BJTU");
System.out.println(person.toString());
System.out.println(student.toString());
}
}
测试类输出结果:
Person{name='john', age=19}
Student{name='yuhua' age='18' school='BJTU'}
重写与重载的区别:
方法的重写和重载都是实现多态的方式。区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。
15.super关键字
在java类中使用super来引用基类的成分;
/**
* 说明:super关键字引用父类成员演示,基类
*
* @author huayu
* @date 2018/8/5 2:57 PM
*/
public class Foo {
public int value;
public void f(){
value=100;
System.out.println("Foo.value="+value);
}
}
/**
* 说明:super关键字引用父类成员演示,子类
*
* @author huayu
* @date 2018/8/5 2:58 PM
*/
public class Coo extends Foo{
public int value;
public void fcoo(){
//引用父类的f(0)方法
super.f();
value=200;
System.out.println("Coo.value="+value);
System.out.println(value);
System.out.println(super.value);
}
}
import oop.Coo;
/**
* 说明:测试方法继承
*
* @author huayu
* @date 2018/8/5 3:05 PM
*/
public class TestInherit {
public static void main(String[] args) {
Coo coo = new Coo();
coo.fcoo();
}
}
测试结果:
Foo.value=100
Coo.value=200
200
100
内存分析图:
把这个子类继承父类程序并子类调用父类成员的内存分配过程,这儿常出面试题:子类是怎么对父类的成员进行调用的。因为以前教过内存分析的具体过程,这儿我就只给了个最后的内存分布图,大家自己好好分析一遍。(当然,方法调用完,栈内为其分配的空间应被收回)。
16.继承中的构造方法
1)子类的构造的过程中必须调用其基类的构造方法(也就是在子类的构造方法中应首先调用父类的构造方法)。
2)子类可以在自己的构造方法中使用super(argument_list参数列表)调用基类的构造方法。(1.使用this(argument_list)调用本类的另外的构造方法 2.如果调用了super,必须写在子类构造方法的第一行“先父后子”)。
3)如果子类的构造方法中没有显示调用基类构造方法,则系统默认调用基类无参数的构造方法。
4)如果子类构造方法中既没有显示调用基类构造方法,而基类中又没有无参构造方法,则编译出错。
/**
* 说明:继承中的构造方法测试用例,基类
*
* @author huayu
* @date 2018/8/5 3:42 PM
*/
public class SuperClass {
private int n;
//验证第一条的时候,SuperClass()构造方法注释掉
//验证第四条的时候,SuperClass()构造方法注释掉
public SuperClass() {
System.out.println("SuperClass()");
}
public SuperClass(int n) {
System.out.println("SuperClass("+n+")");
this.n = n;
}
}
/**
* 说明:继承中的构造方法测试用例,子类
*
* @author huayu
* @date 2018/8/5 3:44 PM
*/
public class SubClass extends SuperClass{
private int n;
public SubClass(int n) {
//验证第三条的时候,相当于加了一个在这super();语句。
System.out.println("SubClass("+n+")");
this.n = n;
}
public SubClass() {
//验证第一条的时候,super(300);这条注释掉
//验证第二条的时候,将super(300);与 System.out.println("SubClass)");调换个位置
super(300);
System.out.println("SubClass)");
}
}
import oop.SubClass;
/**
* 说明:继承中的构造方法测试用例,测试类
*
* @author huayu
* @date 2018/8/5 3:48 PM
*/
public class TestSuperSub {
public static void main(String[] args) {
//验证三、四条的时候注释掉 subClass这句
SubClass subClass=new SubClass();
//验证一、二条的时候注释掉 subClass1这句
SubClass subClass1=new SubClass(400);
}
}
验证结果:
1.验证第一条:如果在编译期,它会报找不到SuperClass的错误(如果你用的是ide编辑的,其实在你注释掉代码的时候,代码就报错了)。将注释打开后输出结果:
SuperClass(300)
SubClass()
2.验证第二条:如果在编译期,它会报对super的调用必须是构造函数的第一个语句(如果你用的是ide编辑的,其实在你注释掉代码的时候,代码就报错了)。
3.验证第三条:打印结果
SuperClass()
SubClass(400)
4.验证第四条:如果在编译期,它会报找不到SuperClass的错误(如果你用的是ide编辑的,其实在你注释掉代码的时候,代码就报错了)。
17.Object类
1)Object类是所有Java类的根基类(源码在jdk rt.jar java的lang包里面,没事了可以看一看)
2)如果在类的声明中未使用extends关键字指明其基类,则默认基类为Object类
public class Person {
//代码...
}
等价于:
public class Person extends Object{
//代码...
}
3)Object类里常用的方法(学会看api文档,能看懂英文版更好)
api在线文档地址https://blog.fondme.cn/apidoc/jdk-1.8-baidu/
~1.toString方法
Object类中定义有public String toString()方法,其返回值是String类型,描述当前对象的有关信息。
在进行String与其它类型数据的连接操作时(如:System.out.println("info"+person)),将自动调用该对象类的toString()方法。
可以根据需要在用户自定义类型中重写toString()方法。
~2.equals方法
1)public boolean equals(Object obj) { return (this == obj); }提供定义对象是否“相等”的逻辑。
2)Object的equals方法定义为:x.equals(y)当x和y是同一个对象的应用时返回true否则返回false
3)J2SDK提供的一些类,如String,Date等,重写了Object的equals方法,调用这些类的equals方法,x.equals(y),当x和y所引用的对象是同一类对象且属性内容相等时(并不一定是相同对象),返回true否则返回false。
4)可以根据需要在用户自定义类型中重写equals方法。
知识链接:"=="和 equals 方法究竟有什么区别
==:运算符,适合于基本数据类型和引用数据类型。如果是基本数据类型,比较两个变量的值是否相等。 如果是引用数据类型变量,判断两个变量是否指向内存中的同一个对象(引用,内容都一样)。
equals():是 Object 类中定义过的一个方法。只适用于对象来调用。如果各个对象所在的类没有重写 Object 中的 equals(),则与==使用相同。如果所在的类重写了 equals(),一般重写的规则是判断两个对象中包含的所有属性的值是否相等。
/**
* 说明:未重写equals方法时候
* 如果各个对象所在的类没有重写 Object 中的 equals(),则与==使用相同。判断的是引用跟值都得相同
* @author huayu
* @date 2018/8/5 7:39 PM
*/
public class Dog {
int color;
int height,weight;
public Dog(int color, int height, int weight) {
this.color = color;
this.height = height;
this.weight = weight;
}
}
/**
* 说明:equals方法的测试类
*
* @author huayu
* @date 2018/8/5 7:41 PM
*/
public class TestEquals {
public static void main(String[] args) {
Dog dog=new Dog(1,2,3);
Dog dog1=new Dog(1,2,3);
System.out.println(dog==dog1);
System.out.println(dog.equals(dog1));
}
}
结果:
false
false
/**
* 说明:重写equals方法时候
* 如果所在的类重写了 equals(),一般重写的规则是判断两个对象中包含的所有属性的值是否相等。
* @author huayu
* @date 2018/8/5 7:39 PM
*/
public class Dog {
int color;
int height, weight;
public Dog(int color, int height, int weight) {
this.color = color;
this.height = height;
this.weight = weight;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
} else {
if (o instanceof Dog) {
Dog dog = (Dog) o;
if (dog.color == this.color && dog.height == this.height && dog.weight == this.weight) {
return true;
}
}
}
return false;
}
}
/**
* 说明:equals方法的测试类
*
* @author huayu
* @date 2018/8/5 7:41 PM
*/
public class TestEquals {
public static void main(String[] args) {
Dog dog=new Dog(1,2,3);
Dog dog1=new Dog(1,2,3);
System.out.println(dog==dog1);
System.out.println(dog.equals(dog1));
//下面验证第三条String类是否真的重写了equals方法
String string1=new String("hello");
String string2=new String("hello");
System.out.println("String判==结果: "+(string1==string2));
System.out.println("String判equals结果 "+(string1.equals(string2)));
}
}
结果:
false
true
String判==结果: false
String判equals结果: true
18.对象转型(casting):可以增强程序的可扩展性,比定义好多个方法要好一些。
1)一个基类的引用类型变量可以“指向”其子类的对象。
2)一个基类的引用不可以访问其子类对象新增的成员(属性和方法)。
3)可以使用 引用 变量instanceof类名 来判断该引用型变量所“指向”的对象是否属于该类或该类的子类。
4)子类的对象可以当作基类的对象来使用称作向上转型(upcasting),反之称为向下转型(downcasting)。
(1)释例一:演示对象转型
/**
* 说明:
*
* @author huayu
* @date 2018/8/6 1:00 PM
*/
public class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
/**
* 说明:用于演示对象转型,pig类
*
* @author huayu
* @date 2018/8/6 1:01 PM
*/
public class Pig extends Animal{
public String eyesColor;
public Pig(String name,String eyeColor) {
super(name);
this.eyesColor = eyeColor;
}
}
package oop;
/**
* 说明:用于演示对象转型,tiger类
*
* @author huayu
* @date 2018/8/6 1:03 PM
*/
public class Tiger extends Animal{
public String furColor;
public Tiger(String name ,String furColor) {
super(name);
this.furColor = furColor;
}
}
import oop.Animal;
import oop.Pig;
import oop.Tiger;
/**
* 说明:用于演示对象转型,测试类
*
* @author huayu
* @date 2018/8/6 1:08 PM
*/
public class TestCasting {
public static void main(String[] args) {
Animal animal=new Animal("");
Pig pig=new Pig("xiaozhu","black");
Tiger tiger=new Tiger("xiaohu","yellow");
System.out.println(animal instanceof Animal);
/*pig instanceof Animal 这句话用到的instanceof关键字,表达的意思是pig是不是
Animal的一个实例
*/
System.out.println(pig instanceof Animal);
System.out.println(tiger instanceof Animal);
System.out.println(animal instanceof Pig);
//向下造型,父类引用指向子类对象,自动转
animal=new Tiger("tigername","white");
System.out.println(animal.name);
//父类拿不到访问子类自己成员的权限,故无法访问子类特有的成员furColor,这儿会报错
//1. System.out.println(animal.furColor); //!error
System.out.println(animal instanceof Animal);
System.out.println(animal instanceof Tiger);
//向上造型,子类引用指向父类对象,需强转
2. Tiger tiger1=(Tiger) animal;
//通过上面句2强转后,就可以访问它的特有成员furColor了
System.out.println(tiger1.furColor);
}
}
测试类输出结果:
true
true
true
false
tigername
true
true
white
为了能让大家更好的理解 1.System.out.println(animal.furColor); //!error 这一句,我对animal=new Tiger("tigername","white");这一句做了一个内存分析图,希望可以有助于大家的理解:
执行句2Tiger tiger1=(Tiger) animal;后的内存分布图:
(2)释例二:用于演示对象转型对程序可扩展性的有利影响
import oop.Animal;
import oop.Pig;
import oop.Tiger;
/**
* 说明:用于演示对象转型扩展性,测试类
* 以上Animal,Pig,Tiger的类依然沿用释例一部分的
* @author huayu
* @date 2018/8/6 1:08 PM
*/
public class TestCasting {
public static void main(String[] args) {
TestCasting testCasting=new TestCasting();
Animal animal=new Animal("name");
Pig pig=new Pig("xiaozhu","black");
Tiger tiger=new Tiger("laohu","yellow");
testCasting.f(animal);
testCasting.f(pig);
testCasting.f(tiger);
/*有了通过对象造型对程序的扩展性提供了很大的帮助,比如这个程序内除了上面两种...
其它的也照样可以测,比如再加什么苍蝇蚊子等等,只需要写方法调用方法就可以了。
*/
}
public void f(Animal animal){
System.out.println("name: "+animal.name);
if(animal instanceof Pig){
Pig pig=(Pig) animal;
System.out.println(" "+pig.eyesColor);
}else if(animal instanceof Tiger){
Tiger tiger= (Tiger) animal;
System.out.println(" "+tiger.furColor+" fur");
}
}
}
19.多态polymorphism(动态绑定dynamoc binding,池绑定late binding):动态绑定是指“在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。”,
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样 的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A 系 统访问 B 系统提供的服务时,B 系统有多种提供服务的方式,但一切对 A 系统来说都是透明的 (就像电动剃须刀是A系统,它的供电系统是B系统,B系统可以使用电池供电或者用交流电, 甚至还有可能是太阳能,A 系统只会通过 B 类对象调用供电的方法,但并不知道供电系统的底 层实现是什么,究竟通过何种方式获得了动力)。
方法重载(overload)实现的是编译时的多态 性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。
运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1). 方法重写(子类继承父类 并重写父类中已有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样 的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
多态(动态绑定,池绑定)使扩展性达到最好。
释例
package oop;
/**
* 说明:用于演示多态,动物类
*
* @author huayu
* @date 2018/8/6 1:00 PM
*/
public class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void enjoy(){
System.out.println("叫声...");
}
}
package oop;
/**
* 说明:用于演示多态,pig类
*
* @author huayu
* @date 2018/8/6 1:01 PM
*/
public class Pig extends Animal{
public String eyesColor;
public Pig(String name,String eyeColor) {
super(name);
this.eyesColor = eyeColor;
}
@Override
public void enjoy(){
System.out.println("猪叫声...");
}
}
package oop;
/**
* 说明:用于演示多态,tiger类
*
* @author huayu
* @date 2018/8/6 1:03 PM
*/
public class Tiger extends Animal{
public String furColor;
public Tiger(String name ,String furColor) {
super(name);
this.furColor = furColor;
}
@Override
public void enjoy(){
System.out.println("老虎叫声...");
}
}
package oop;
/**
* 说明:用于演示多态,GF类
*
* @author huayu
* @date 2018/8/6 3:11 PM
*/
public class GF {
private String name;
//定义成父类引用是为了更灵活
private Animal pet;
public GF(String name, Animal pet) {
this.name = name;
this.pet = pet;
}
public void myPetEnjoy(){
pet.enjoy();
}
}
import oop.GF;
import oop.Pig;
import oop.Tiger;
/**
* 说明:用于演示多态,测试类
*
* @author huayu
* @date 2018/8/6 3:14 PM
*/
public class TestPolymoph {
public static void main(String[] args) {
Pig pig=new Pig("xiaozhu","black");
Tiger tiger=new Tiger("laohu","yellow");
GF gf=new GF("xiaohong",pig);
GF gf1=new GF("xiaolv",tiger);
gf.myPetEnjoy();
gf1.myPetEnjoy();
}
}
结果:
猪叫声...
老虎叫声...
内存分析(以new Pig为例):
多态的存在三个必要条件:
1)要有继承;
2)要有重写;
3)父类引用指向子类对象。new的是哪个对象则就会动态绑定它里面重写的方法。
20.抽象类
1)用abstract关键字来修饰一个类时,这个类叫做抽象类;用abstract来修饰一个方法时,该方法叫做抽象方法。
2)含有抽象方法的类必须被声明为抽象类,抽象类必须被继承,抽象方法必须被重写(继承他的子类去重写)。
3)抽象类不能被实例化(new不出来)。
4)抽象方法只需声明,而不需实现。明知道父类的一个方法肯定要被子类实现,所以它没有必要实现它的方法,只声明就可以。
5)抽象类中可以含有非抽象的方法,但是类中必有抽象的方法在里面。
释例:
package oop;
/**
* 说明:用于演示抽象类,动物类
* 抽象类:残缺不全的类,被继承后,子类需要重写它所有的抽象方法
* @author huayu
* @date 2018/8/6 1:00 PM
*/
public abstract class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
//以下enjoy方法,若子类继承这个类,这个方法将会被重写,所以它没必要实现它的方法
/*public void enjoy(){
System.out.println("叫声...");
}*/
//注释掉上面的方法把它改进为public abstract void enjoy();相当于C++的虚函数
//注意public void enjoy(){};这叫空的实现,但这个要被重写,也每必要写它。
//所以有了以下的这种抽象方法,只有定义,没有任何实现。而这个方法加了abstract这个类也需
//要被声明为抽象类。在类名前加abstract关键字。
public abstract void enjoy();
}
package oop;
/**
* 说明:用于演示抽象类,pig类
*
* @author huayu
* @date 2018/8/6 1:01 PM
*/
public class Pig extends Animal{
public String eyesColor;
public Pig(String name,String eyeColor) {
super(name);
this.eyesColor = eyeColor;
}
@Override
public void enjoy(){
System.out.println("猪叫声...");
}
}
package oop;
/**
* 说明:用于演示抽象类,tiger类
*
* @author huayu
* @date 2018/8/6 1:03 PM
*/
public class Tiger extends Animal{
public String furColor;
public Tiger(String name ,String furColor) {
super(name);
this.furColor = furColor;
}
@Override
public void enjoy(){
System.out.println("老虎叫声...");
}
}
package oop;
/**
* 说明:用于演示抽象类,GF类
*
* @author huayu
* @date 2018/8/6 3:11 PM
*/
public class GF {
private String name;
//定义成父类引用是为了更灵活
private Animal pet;
public GF(String name, Animal pet) {
this.name = name;
this.pet = pet;
}
public void myPetEnjoy(){
pet.enjoy();
}
}
import oop.GF;
import oop.Pig;
import oop.Tiger;
/**
* 说明:用于演示抽象类,测试类
*
* @author huayu
* @date 2018/8/6 3:14 PM
*/
public class TestPolymoph {
public static void main(String[] args) {
Pig pig=new Pig("xiaozhu","black");
Tiger tiger=new Tiger("laohu","yellow");
GF gf=new GF("xiaohong",pig);
GF gf1=new GF("xiaolv",tiger);
gf.myPetEnjoy();
gf1.myPetEnjoy();
}
}
结果:
猪叫声...
老虎叫声...
知识拓展:
1)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。 抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
2)抽象类(abstract class)和接口(interface)有什么异同?
3)抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了 某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被 声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具 体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是 private、默认、protected、public 的,而接口中的成员全都是 public 的。抽象类中可以定义成员 变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽 象类未必要有抽象方法。
4)抽象的(abstract)方法不能同时分别被static,native,synchronized修饰?
抽象的(abstract)方法不能同时是静态的(static),因为抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。
抽象的(abstract)方法不能同时是本地方法(native),因为本地方法是由本地代码(如 C 代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。
抽象的(abstract)方法不能同时被 synchronized 修饰, 因为synchronized 和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的
21.final关键字
final:最终的 可以用来修饰类、变量、方法
1)final的变量的值不能够被改变(final的成员变量,final的局部变量(形参))
final 修饰变量:此变量就"退化"为一个常量。比如:Math 类的 PI。
可以如下三个位置对 final 的变量初始化:显式的初始化/代码块中初始化/构造器中初始化
/**
* 说明:测试final的值不能被改变
*
* @author huayu
* @date 2018/8/6 8:15 PM
*/
public class FinalDemo {
final int i=1;
}
import oop.FinalDemo;
/**
* 说明:测试final的值不能被改变
*
* @author huayu
* @date 2018/8/6 8:14 PM
*/
public class TestFinal {
public static void main(String[] args) {
FinalDemo finalDemo=new FinalDemo();
//试图修改final变量i,如果进了编译期,编译期报这个值不能被改变,若用ide多数代码还
//没敲完就会报错了
finalDemo.i=1;
}
}
2)final 修饰方法:表明此方法不可以被重写.比如:Object 类的 getClass();
package oop;
/**
* 说明:测试方法不能被重写
*
* @author huayu
* @date 2018/8/6 8:15 PM
*/
public class FinalDemo {
public int i=1;
public final void m(){
}
}
/**
* 说明:测试方法不能被重写
*
* @author huayu
* @date 2018/8/6 8:24 PM
*/
public class TT extends FinalDemo{
//如果进入了编译器,编译器会报无法重写m方法的错误,若使用ide多数一般代码刚写完就已经报错了
public void m(){
}
}
3)final 修饰类:表明此类不可以被继承.比如:String 类、StringBuffer 类
/**
* 说明:测试类不能被重写
*
* @author huayu
* @date 2018/8/6 8:15 PM
*/
public final class FinalDemo {
public int i=1;
public void m(){
}
}
/**
* 说明:测试类不能被重写
*
* @author huayu
* @date 2018/8/6 8:24 PM
*/
//如果进入了编译期,编译器会报无法继承FinalDemo类的错误,若使用ide多数一般代码刚写完就已经
//报错了
public class TT extends FinalDemo{
}
知识拓展:
1)内部类访问外部属性为什么加 final?
局部内部类能访问方法中的所有的局部变量,其生命周期与局部内部类的对象的生命周期是不一致的。如何才能实 现访问呢?当变量是 final 时,通过将 final 局部变量"复制"一份,复制品直接作为局部内部中的数据成员。这样,当局 部内部类访问局部变量时,其实真正访问的是这个局部变量的"复制品”。那么使用 final 修饰,表示其复制品与原始的量是一样。
2)final, finally,finalize()的区别
-final:修饰符(关键字)有三种用法:如果一个类被声明为 final,意味着它不能再派生出新的 子类,即不能被继承,因此它和 abstract 是反义词。将变量声明为 final,可以保证它们在使用中 不被改变,被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。 被声明为 final 的方法也同样只能使用,不能在子类中被重写。
- finally:通常放在 try...catch...的后面构造总是执行代码块,这就意味着程序无论正常执行还是 发生异常,这里的代码只要 JVM 不关闭都能执行,可以将释放外部资源的代码写在 finally 块中。
- finalize:Object 类中定义的方法,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中 清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。
22.接口(interface):是抽象方法和常量值的定义的集合。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
接口的特性:
1)接口可以多重实现。
2)接口中声明的属性默认为public static final的,也只能是public static final的
3)接口中只能定义抽象方法,而且这些方法默认为public的,也只能是public的
4)接口可以继承其它接口,并添加新的属性和抽象方法。
5)多个无关的类可以实现同一个接口,接口中抽象方法必须全被重写。
6)一个类可以实现多个无关的接口,接口与接口之间用“,"隔开,接口中抽象方法必须全被重写。
7)与继承关系类似,接口与实现类之间存在着多态性。
8)定义java类的语法格式:
<modifier> class <name> [extends <superclass> ]
[implements <interface> [,<interface>*] {
<declarartions>*
}
释例一
/**
* 在这段代码中我们模拟写一个跳远的运动员的接口
*/
public interface InterfaceDemo {
/*为了修正C++多继承多个父类之间相同成员变量引用特麻烦跟运行易出错的问题,
java在接口中将成员变量定义为static final的
*/
public static final int count = 1;
/*
其实接口中成员变量也可以不写static final因为它默认就是static final的
*/
int id = 1;
//接口中的方法不用写abstract它也全都是abstract的
public void start();
//接口中的方法也可以不加public,因为它默认就是public的
void jump();
void stop();
}
释例二:
package oop;
/**
* 在这段代码中我们模拟写一个运动员的接口
*/
public interface InterfaceDemo {
/*为了修正C++多继承多个父类之间相同成员变量引用特麻烦跟运行易出错的问题,
java在接口中将成员变量定义为static final的
*/
public static final int count = 1;
/*
其实接口中成员变量也可以不写static final因为它默认就是static final的
*/
int id = 1;
//接口中的方法不用写abstract它也全都是abstract的
public void start();
//接口中的方法也可以不加public,因为它默认就是public的
void jump();
void stop();
}
package oop;
/**
* 说明:定义了一个跳远运动员的类,实现运动员接口
*
* @author huayu
* @date 2018/8/7 1:31 PM
*/
public class Jumper implements InterfaceDemo {
private String name;
public Jumper(String name) {
this.name = name;
}
public void start() {
System.out.println("起跳的动作准备");
}
public void jump() {
System.out.println("起跳过程中的身形变化控制");
}
public void stop() {
System.out.println("快结束时入坑姿势调整");
}
}
知识拓展:
接口是否可继承(extends)接口?抽象类是否可实现(implements) 接口?抽象类是否可继承具体类(concrete class)?
接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承 具体类也可以继承抽象类。