Java入门学习笔记——郝斌
- 1、Java概述
- java的起源和发展
- java的特点
- java的应用领域
- java学习目标
- 环境变量的设置
- 为什么要设置path?
- path的设置
- 有关classpath设置的问题
- java中的注释
- java数据类型
- 输出数据的格式控制
- 常量
- 整型常量
- 浮点常量
- 字符常量
- 布尔类型
- 不同类型变量存储范围
- 运算符
- 算术运算符(+)
- 除法运算符(/)
- 取余运算符(%)
- 逻辑与逻辑或
- 存储单位介绍
- 位运算符(1)
- 位运算符(2)
- 流程控制
- 函数的重载
- 2、面向对象编程
- 面向对象大纲
- 面向对象定义
- 面向过程定义
- 面向过程设计思想:
- 面向过程设计的优点
- 面向过程设计的缺点
- 面向对象的设计思想
- 类的定义
- 对象的定义
- 内存分配
- 访问控制符
- private修饰符
- 类的构造函数
- 构造函数的返回值问题
- 构造函数数据成员的赋值问题
- 多个构造函数可能带来的冲突
- 关键字this
- 关键字static
- 继承
- 同包继承权限问题(重点)
- 子类访问父类成员的三种方式
- 继承
- super的使用
- 方法重写
- 多态
- 多态注意事项(难点)
1、Java概述
java的起源和发展
java的特点
- 简单易学
- java的风格类似于C++,因此C++程序员初次接触Java语言,就会感到很熟悉。从某种意义上讲,Java语言是C及C++语言的一个变种,因此,C++程序员可以很快地掌握Java编程技术。
- Java摒弃了C/C++中容易引发程序错误并且难以掌握的一些特性,如指针、结构、以及内存管理等。
- Java提供了丰富的类库,可以帮助我们很方便的开发Java程序。
- Java是完全面向对象的语言,所以他支持继承、重载、多态等面向对象的特性。C++语言是面向过程和面向对象混合的语言,C是纯面向过程的语言。
- 安全性高
- Java是一种强类型的语言,其类型检查比C/C++还要严格。类型检查帮助我们检查出许多开发早期出现的错误。
- Java提供了垃圾回收机制,有效的避免了C/C++中最头疼的内存泄漏问题
- java禁止非法内存访问,在没有授权的情况下是不能访问内存的。所有这些措施,使Java程序员不用再担心内存的崩溃。
- 总结:java去掉C和C++中影响程序健壮性的部分,使程序更安全,例如指针、内存申请和释放。
- 跨平台
- Java作为一种网络语言,其源代码被编译成一种结构中立的中间文件格式。只要有Java运行系统的机器都能执行这种中间代码。Java源程序被编译成一种与机器无关的字节码格式,在Java虚拟机上运行。
JVM的平台相关性:Java源代码和字节码是与机器无关的,JVM是与机器有关的。
- 多线程的
- Java语言的一个重要特性就是在语言级支持多线程的程序设计。多线程就是使得一个程序能够同时执行多个任务。
java的应用领域
- J2SE,主要用来开发桌面应用软件用的
- J2ME嵌入式开发,像手机里的软件、掌上电脑等等
- J2EE属于网络编程,JSP等等,就是做网站用到的编程。
java学习目标
- 了解程序语言及发展历史
- 熟练掌握java语言的语法规则
- 掌握java常用类的使用
- 掌握编程逻辑思维能力
1. 能看懂程序(流程-功能-试数)
2. 会调试程序
3. 理解并应用面向对象的设计思想 - 为将来学习J2EE做准备
环境变量的设置
为什么要设置path?
- 在dos的任何目录下我们都可以运行操作系统自带的命令
- 要想在dos下运行用户自己的程序,则必须进入到改程序的当前目录下方运行
- 如果希望在dos的任何目录下都可以运行自己创建的程序,则需要我们自己手动设置操作系统自带的环境变量path
path的设置
- 操作系统是利用path变量来寻找当前程序所存放的路径,并且以最先找到的为准
- 路径和路径之间用分号(;)隔开
- set path:表示查看当前path变量中的内容
有关classpath设置的问题
- 操作系统是利用classpath变量来寻找当前后缀为class的字节码文件所存放的路径,并且以最先找到的为准
- 从JDK1.6开始,classpath不需要再手动设置
- 初学者可以暂时不去理会classpath的设置
- 如果要导入第三方的jar包,则需要手动设置classpath
java中的注释
三种注释: // /* */ /** */
- //单行注解,不需要配对
- /* */多行注解,需要配对
- /** */用于将注解变为说明文档,用javadoc实现
java数据类型
- 基本数据类型:byte,short,int,long,float,double,char,boolean
- 引用数据类型:类(class),接口(interface),数组
输出数据的格式控制
输出控制符 | 针对的数据类型 |
%d | int, long int, short, byte |
%x, %#X, %X, %#X | int, long int |
%c | char |
%f | float, double |
%s | String |
在java中打印语句有三种,分别是println,print,printf,三者之间有区别
- print 一般的标准输出,不换行。将它的参数显示在命令窗口,并将输出光标定位在所显示的最后一个字符之后。
- println 一般的标准输出,最后会换行。将它的参数显示在命令窗口,并在结尾加上换行符,将输出光标定位在下一行的开始。
- printf 格式化输出的形式
//十六进制输出控制符
class TestHex
{
public static void main(String[] args)
{
int i = 253;
System.out.printf("%x\n", i); // 输出fd
System.out.printf("%X\n", i); // 输出FD
System.out.printf("%#x\n", i); //输出0xfd
System.out.printf("%#X\n", i); //输出0XFD
}
}
常量
整型常量
一个常量整数默认是int类型,如果数字过大,则必须要在末尾加L,否 则会出错
浮点常量
一个实数默认是double类型,如果希望一个实数是float类型,可以在数字后面加f(F)
将一个double类型数值赋给float类型变量,编译会报错
float x = 2.2; //error
float x = 2.2f; //OK
字符常量
字符常量必须用单引号括起来,Java中字符和字符串都用Unicode编码表示,在Unicode编码中一个字符占两个字节
- 特殊字符的常量表示法:
反斜线 '\\'
退格 '\b'
回车 '\r'
制表符 '\t'
换行 '\n'
单引号 '\''
//字符常量的'\uxxxx'表示
public class A
{
public static void main(String[] args)
{
char ch1 = '中';
char ch2 = '\u4e2d'; //'\uxxxx'表示十六进制的xxxx所对应的Unicode编码下的字符
System.out.printf("%c %c\n", ch1, ch2); //运行结果:中 中
}
}
布尔类型
用boolean表示,不能写成bool
布尔型数据只有两个值true和false,且他们不对应与任何整数值
布尔型变量的定义如:boolean b = true;
布尔型数据只能参与逻辑关系运算:&& || == != !
注意:if while for 中进行真假判断时只能使用逻辑表达式
不同类型变量存储范围
- byte占用一个字节,数字大小为-27 ~ 27-1
- short占用两个字节,数字大小为-215~215-1
- int占用四个字节,数字大小为-231~231-1
- long占用八个字节,数字大小为-263~263-1
- float占用四个字节,数字大小为1.4E-45 ~ 3.4E+38,-1.4E-45 ~ 3.4E+38。用二进制的质数形式表示一个浮点数的格式,如:101 * 22
- double占有八个字节
- char占用两个字节,数字大小为0~216-1, 是Unicode编码
- boolean占一个字节,其取值只有两个,true和false
在把容量大的类型转换为容量小的类型时必须使用强制类型转换
整形、实型、字符型数据可以混合运算。运算中,不同类型的数据先转换为同一类型,然后进行运算,转换从低级到高级:
低----------------------------------->高
byte -> char -> int -> long -> float -> double
运算符
算术运算符:+ - * / %
关系运算符: > >= < <= != ==
逻辑运算符: ! && ||
赋值运算符:= += * = /= %=
位运算符:& | ~ ^
算术运算符(+)
+
可以表示数值的相加,可以表示字符串的联接,如:“123” + “abc” 的结果是“123abc”,还能把非字符转换成字符串,如:“x” + 123;的结果是“x123”
System.out.println('a' + 1); //98
System.out.println("" + 'a' + 1); //a1
除法运算符(/)
除法/的运算结果和运算对象的数据类型有关,若两个数都是int,则商就是int,若商有小数,则截取小数部分;若两个对象中有一个或两个都是浮点型数据,则商也是浮点型,不截取小数部分
如:16/5 == 3, 16/5.0 == 3.20000, -13/4 == -4, -13/-3 == 4
取余运算符(%)
Java中允许取余运算符的被除数和除数是实数,但所得余数的正负只和被除数相同
double x = 1 % -0.3
System.out.println(x); // 0.10000
逻辑与逻辑或
当&& 左边表达式为假时,&&右边的表达式是不执行的。因为无论&&右边的表达式为真还是为假,整个表达式都为假
同理,当||表达式左边为真时,||右边的表达式是不会执行的
存储单位介绍
一个0或一个1就代表一位,一位只能存储一个0或一个1,8位代表一个字节,位用bit表示,字节用byte表示
位运算符(1)
- & (按位与),把两个数字所有的二进制位相与
1 & 1 = 1, 1& 0 = 0, 0 & 0 0, 0 & 1 = 0
- | (按位或), 把两个数字所有的二进制位相或
1 | 1 = 1, 1 | 0 = 1, 0 | 0 = 0, 0 | 1 = 1
- ~(按位取反), 把一个数字的所有二进制位取反
~ 1 = 0, ~ 0 = 1
- ^(按位异或), 把两个数字的所有二进制位异或
````1 ^ 1 = 0, 1 ^ 0 = 1, 0 ^ 1 = 1, 0 ^ 0 = 0``
int i = 11; // 0000 1011
int j = -2; // 0000 0010 -> 1111 1101 -> 1111 1110
System.out.printf("%d\n", i & j); //10, 0000 1010
位运算符(2)
- 右移>>,在java中右移没有任何歧义。对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位0,最高位补0,而为负数时,最高位为1,最高位补1
-
>>>
无论最高位是0还是1,左边移空的改为都补0 -
<<和<<<
同理
运算表达式书写规则:
- 不要写过于复杂的表达式
- 为了程序的可读性,可以适当的使用括号
流程控制
- 顺序
- 选择
if…else
Switch - 循环
for
while
Do…while
函数的重载
- 同名的函数通过不同的形参做类似的事情,这就是函数的重载
- 函数重载要求:函数的形参个数、函数的形参顺序、函数的形参数据类型、这三个至少有一个是不一样的
- 如果两个函数只是函数的返回值不一样,其他都一样,这构不成函数的重载,并且编译时会报错
2、面向对象编程
面向对象大纲
- 封装
- 访问控制符
- 只有
private
的不能再外部访问 - 类内部访问控制符是透明的
- 构造方法
- 定义:
- 名字和类名一样
- 无返回值
- 注意事项:
- 一个类对象只能调用一个构造方法
- 一个类至少有一个构造方法
- 如果自己定义了构造方法,编译器将不再提供默认的构造方法
- this
- 一个对象只含有属性的空间,n个对象公共一份方法的拷贝
- static
- 继承
- Java是允许单继承
- 私有成员无法被继承
- 重写:
- 方法名和参数列表和返回值必须一样
- 访问权限不能过低
- 多态
面向对象定义
面向过程定义
自顶向下的设计模式
面向过程设计思想:
分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现
以算法为核心,自顶向下设计,要求一开始必须对问题有很深的了解
将大问题转化为若干小问题来求解
表现形式:用函数来作为划分程序的基本单位
直接面向问题
面向过程设计的优点
易于掌握与理解,符合人们的思维习惯
对于需求明确,规模不大,变动较小的问题非常适合
面向过程设计的缺点
数据与操作分离开,对数据与操作的修改变得很困难
数据的安全性得不到保证
程序架构的依赖关系不合理。main函数依赖于子函数,子函数又依赖于更小的子函数;而子函数往往是细节的实现,这些实现是经常变化的,造成的结构就是:程序的核心逻辑依赖于外延的细节,一个细节上的小改动,会引起一系列的变动
对于“需求不明确、变动较大、规模很大的问题”,显得力不从心
面向对象的设计思想
确定该问题由哪些事物组成,先用类模拟出该事物,通过类间接的解决问题。自下而上设计,从问题的一部分着手,一点一点的构建出整个程序
表现形式:用类来作为划分程序的基本单位
对于需求不明确、变动较大、规模很大的问题非常适合
对于“需求明确、规模不大、变动较小的问题”则显得十分累赘
面向对象的三个基本特征:封装、继承、多态
类的定义
把一类事物的静态属性和动态可以执行的操作组合在一起所得的这个概念就是类
类是抽象的,用来模拟一类事物,是一个概念。一旦被定义,类的概念就永远存在了。
class Person
{
int age; //age是类的属性,也叫类数据成员,也叫字段,也叫域
void shout() //shout是方法,也叫类的成员函数,shout方法可以直接访问同一个类中的age变量
{
System.out.println("my age " + age);
}
}
对象的定义
类的一个个体,是具体的,实实在在存在的事物。生命周期是短暂的,会生成和消亡。
类与对象:如果将对象比作汽车,那么类就是汽车的设计图纸
内存分配
程序执行过程
//类对象实例1
class A
{
int i;
int j;
}
public class TCS
{
public static void main(String[] args)
{
A aa1 = new A(); // 相当于C语言中(A *)malloc(sizeof(A));
//new A(); 在堆中动态分配一块区域,被当做了A对象
//aa本身的内存是在栈中分配的,堆中内存的地址赋给了aa
//aa指向堆中的内存,aa代表了堆中的内存
//aa.i 代表:aa这个静态指针变量所指向的动态内存中的A对象的i这个成员
//aa.j 代表:aa这个静态指针变量所指向的动态内存中的A对象的j这个成员
aa.i = 10;
aa.j = 20;
System.out.printf("%d, %d\n", aa.i, aa.j); //输出10,20
}
}
//类对象实例2
class A
{
int i = 10;
int j = 20;
}
public class TCS
{
public static void main(String[] args)
{
A aa1 = new A();
A aa2 = new A();//在堆内存中动态分配了两块区域,aa1和aa2分别指向这两块区域
}
}
//类对象实例3
class A
{
int i = 10;
int j = 20;
}
public class TCS
{
public static void main(String[] args)
{
A aa1 = new A();
A aa2 = aa1;
aa1.j = 99;
System.out.printf("%d\n", aa2.j); //99
}
}
访问控制符
类的访问控制符有四种:public、protect、默认(不加任何修饰符default)、private
在一个类的内部,所有的成员可以相互访问,访问控制符是透明的;访问控制符是针对外部访问而言的
外部访问包括两种方式:通过类名访问类内部的成员,通过类对象名访问类内部成员
- public 可以通过 外部访问方式访问类内部的public成员
- private 不可以通过外部访问方式访问类内部的private成员
private修饰符
在一个类的内部,所有的成员可以相互访问,访问控制符是透明的
在一个类的外部:通过 类对象名.私有成员名 的方式是无法访问该对象中的私有成员的,这样写编译时会出错
类的构造函数
- 特点:
函数名与类名是相同的
不能有返回值
可以有参数,也可以没有参数
可以有多个 - 说明:声明一个对象,系统首先为该对象分配内存,然后立即自动调用该对象的构造函数
- 注意:
任何一个类对象被生成时一定会调用该类的构造函数
无论一个类有多少个构造函数,生成一个类对象时一定只会调用其中的某一个构造函数
构造函数的返回值问题
- 在一个类中可以定义多个函数名与类名相同但却有返回值的函数,返回值为void或int或double都可以,这些有返回值的函数只要能满足重载特点,就可以同时存在一个类中,不过要注意:这些有返回值的函数(包括返回值为void的函数)都不是构造函数,都不会被类对象自动调用
- 当然也可以定义多个没有任何返回值的函数,注意连void都不可以加,这些函数才是构造函数,无返回值的函数才会被对象自动调用
- 如果定义了有返回值并且方法名与类名相同的方法,程序并不会报错,但是容易产生歧义,所以强烈建议不要在构造方法前面加返回值
构造函数数据成员的赋值问题
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值
成员变量类型 | 初始值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0F |
double | 0.0D |
char | ‘\u0000’ |
boolean | false |
All reference type(所有引用类型) | Null |
- 如果在定义的时候不初始化,则它的值是系统自动分配好的默认值
- 如果在定义的同时赋初值,则是可以的,也就是说该值是生效的。注意C++中不可以,在C++中一个类的数据成员不能再定义的同时初始化,他只能在构造函数中初始化。
- 如果在定义的同时赋初值,当然生效,但如果在构造函数中又改变了定义时赋的初值,则该数据成员最终的值就是构造函数中修改之后的那个值,因为:
系统会先执行定义时赋的初值,然后再执行构造函数中赋的初值
int k;
System.out.printf("%d\n", k);//报错,局部变量编译器是不会自动进行初始化的,java要求所有的局部变量在使用之前都必须初始化
多个构造函数可能带来的冲突
- 在java每个类里都至少有一个构造方法,如果程序员没有在一个类里定义构造方法,系统会自动为这个类产生一个默认的构造方法,这个默认构造方法没有参数,在其方法体中也没有任何代码,即什么也不做。
- 由于系统提供的默认构造方法往往不能满足编程者的需求,我们可以自己定义类的构造方法,来满足我们的需要
- 一旦编程者为类定义了构造方法,系统就不再提供默认的构造方法了。
//一旦编程者为类定义了构造方法,系统就不再提供默认的构造方法了
class A
{
private int i;
public A()//如果将这个定义注释,程序就回报错
{
}
public A(int j)
{
i = j;
}
}
public class TestConst_4
{
public static void main(String[] args)
{
A aa1 = new A();
A aa2 = new A(1);
}
}
关键字this
- 是一个系统隐含的指针被自动附加在非静态的成员函数参数列表中
- 当前时刻,哪个对象调用该函数,那么this就指向当前调用该函数的对象,系统会自动在该函数的参数列表中添加一个隐藏的this指针,并且把调用该函数的对象的地址赋给this指针,这样一来,在函数的内部通过this就可访问当前正在调用该函数的对象的成员。
- 静态函数内部,没有this指针。
- 关键字this代表当前正在调用该方法的那个对象,具体可分为两种情况:
- 在普通方法中,关键字this代表方法的调用者,即本次调用了该方法的对象
- 在构造方法中,关键字this代表了该方法本次运行所创建的那个新对象
class A
{
public int i;
public A(int j)
{
i = j;
}
public void show()//this代表的是当前正在调用show方法的对象
{
System.out.println("i= " + i);//i可以换成this.i
}
}
class M
{
public static void main(String[] args)
{
A aa1 = new A(5);//aa1和aa2在内存中分别有各自的数据成员i
A aa2 = new A(8);
aa1.show();//但是aa1和aa2共用show()方法,show方法如何知道输出的i应该是哪个对象中的i
aa2.show();//实际上每个非static方法中都隐含这一个this指针,指向当前正在调用该方法的对象
}
}
class A
{
private int i;
public A(int i)
{
this.i = i; //this代表当前时刻正在创建的对象
}
public void show()
{
System.out.printf("%d\n", this.i); //this代表正在调用show方法的对象,this可以省略
}
}
public class TestThis
{
public static void main(String[] args)
{
A aa1 = new A(100);
aa1.show();
}
}
关键字static
- 静态成员属于类本身的,而不是属于对象,被类的所有对象所共有
- 即便不创建对象,也可以使用类本身的静态成员
- 静态成员分为:静态数据成员、静态方法成员
- 使用静态成员的两种方法:
类名.静态成员名
类对象名.静态成员名 - 编写使用静态变量统计一个类产生的实例对象的个数的程序
//本程序证明:A类的多个对象共用一个static属性i
class A
{
public static int i = 10;
public void show()
{
System.out.printf("%d\n", i); // 20
}
}
class M
{
public static void main(String[] args)
{
A aa1 = new A();
A aa2 = new A();
A aa3 = new A();
aa1.i = 20;
aa2.show();
System.out.printf("%d\n", aa3.i); //20
}
}
//本程序证明了:static属性i是属于类本身的,或者讲:没有对象,我们仍然可以直接通过类名的方式访问该类内部的static属性
//static方法f同理
class A
{
public static int i = 10;
public static void f()
{
System.out.printf("2020年6月");
}
}
class TestStatic_2
{
public static void main(String[] args)
{
System.out.printf("%d\n", A.i); //10
A.f();
A aa = new A();
aa.f();
System.out.printf("%d\n", aa.i); // 10
//static属性和方法虽然属于类本身,虽然可以通过类名的方式访问,
//但是static属性和方法很明显也属于类对象,当然也可以通过类对象名的方式访问
}
}
//本程序证明了:
//只有非private的static成员才可以通过类名的方式访问
//static只是表明了该成员具有了可以通过类名访问的潜在特征
//但是否可以通过类名访问,还必须满足一个条件: 该成员必须是非private
class A
{
private static int i = 10;
private static void f()
{
System.out.printf("2020年6月\n");
}
}
class TestStatic_4
{
public static void main(String[] args)
{
//A.f();//error
A.i = 22;//error
}
}
//本程序证明了:
//静态方法不能访问非静态成员
//非静态方法可以访问静态成员
class A
{
private static int i = 10;
public int j = 99;
public static void f()
{
//g(); //error 静态方法不能访问非静态成员
//j = 22; //error
System.out.printf("FFFF\n");
}
public void g()
{
//f(); //OK 说明非静态方法可以访问静态成员
System.out.printf("GGGG\n");
System.out.printf("%d\n", i);
}
}
class TestStatic_5
{
public static void main(String[] args)
{
A aa = new A();
//aa.g();
aa.f();
//A.g(); //error
}
}
- 在静态方法里只能直接调用同类中其他的静态成员(包括变量和方法),而不能直接访问类中的非静态成员。 这是因为,对于非静态的方法和变量,需要先创建类的实例对象后才可使用,而静态方法在使用前不用创建任何对象。
- 静态方法不能以任何方式引用this和super关键字。 与上面的道理一样,因为静态方法在使用前不用创建任何实例对象,当静态方法被调用时,this所引用的对象根本就没有产生。
- 静态方法只能访问类的静态成员,但是非静态方法却可以访问类中所有成员,包括静态成员
class A
{
private int i;
private static int cnt = 0;
public A()
{
++cnt;
}
public A(int i)
{
this.i = i;
++cnt;
}
public static int getCnt()
{
//return 返回的是A对象的个数;
return cnt;
}
}
public class TestStatic_1
{
public static void main(String[] args)
{
System.out.printf("当前时刻A对象的个数是: %d\n", A.getCnt());//0,能够计算A对象的个数
A aa1 = new A();
System.out.printf("当前时刻A对象的个数是: %d\n", A.getCnt());//1
A aa2 = new A();
System.out.printf("当前时刻A对象的个数是: %d\n", A.getCnt());//2
}
}
class A
{
public int i = 20;
private static A aa = new A(); //aa是否是A对象的属性,aa是A对象,也是A的属性
private A()
{
}
public static A getA() //static 一定是不能省略的
{
return aa;
}
}
public class TestStatic_2
{
public static void main(String[] args)
{
A aa1 = A.getA(); // 只生成一个对象,aa1和aa2是相等的
A aa2 = A.getA();
aa1.i = 99;
System.out.printf("%d\n", aa2.i);
if (aa1 == aa2)
System.out.printf("aa1 和 aa2相等\n");//aa1和aa2是相等的
else
System.out.printf("aa1 和 aa2不相等\n");
// A aa1 = new A(); // error 如果A类的构造方法是private的,则new A()就会报错!
// A aa2 = new A(); //同上
}
}
继承
- 一个新类从已有的类那里获得其已有的属性和方法,这种现象叫类的继承
- 这个新类被称为子类,也叫派生类,已有的那个类叫做父类,也叫做基类
- 继承的好处:
- 代码得到极大的重用
- 形成一种类的层次体系结构
- 为多态创造条件
- 继承的实现方式
class SubClass extends SuperClass
{
.......
}
利用继承可以较好的模拟出现实世界事物之间的联系
同包继承权限问题(重点)
- 子类的所有方法内部都可以访问父类除私有成员以外的所有成员,所谓子类的所有方法也包括子类的私有方法
- 通过子类对象名可以访问:
- 父类除私有成员外的所有成员
- 子类本身的除私有成员外的所有成员
附注:私有成员包括私有属性和私有方法
- 子类可以继承父类除私有成员以外的所有成员
- 父类的私有成员不可以被子类继承,其他的成员都可以被子类继承
- (java中继承的关系是一般到特殊的关系(泛化))
//子类内部可以访问父类非私有的成员,私有成员无法被子类方法访问
//通过子类对象名只能访问从父类继承过来的非私有成员
//总结:私有不能被继承,私有物理上已经被继承过来,只不过逻辑上程序员不能去访问它
//因此继承必须慎重,否则会浪费内存
class A
{
public int i;
protected int j;
private int k;
public void g(){}
private void s(){}
protected void b(){}
}
class B extends A
{
private void m()
{
i = 10;
j = 20;
//k = 30; //error 私有属性不能被继承
g();
b();
//s(); //error 私有方法不能被继承
}
public void f()
{
i = 10;
j = 20;
//k = 30; //error 私有属性不能被继承
g();
b();
//s(); //error 私有方法不能被继承
}
}
class M
{
public static void main(String[] args)
{
B bb = new B();
bb.i = 20;
bb.j = 30;
bb.b();
bb.g();
//bb.s(); //error
//bb.k = 22;
}
}
继承的原则:当B是一个A,让B做A的子类
不同访问修饰符的继承:
子类访问父类成员的三种方式
- 在子类内部访问父类成员
- 通过子类对象名访问父类成员
- 通过子类的类名访问父类成员
经验证,通过上述三种方式,父类中的私有成员都不能被访问,因此得出结论:私有成员不能被继承
class A
{
public int i;
protected int j;
private int k;
}
class B extends A
{
private void g()
{
i = 10;
j = 20;
//k = 30; // error
}
}
class Test
{
public static void main(String[] args)
{
//B bb = new B();
//bb.i = 10;
//bb.j = 20;
//bb.k = 30;//error
A.i = 90;
B.j = 10;
//B.k = 100; // error
}
}
继承
- Java只支持单继承,不允许多重继承
- 单继承就是一个类只能有一个父类
- 多继承就是一个类可以有多个父类
- 可以有多层继承,即一个类可以继承某一个类的子类,如类B继承了类A,类C又可以继承类B,那么类C也间接继承了类A
- 子类可以继承父类所有的成员变量和成员方法,但子类永远无法继承父类的构造方法。在子类的构造方法中可使用语句super(参数列表)调用父类的构造方法。
super的使用
- 调用父类的构造函数的语句必须借助于
super(参数列表)
,不能直接写父类的类名 - 每个子类构造方法的第一条语句,都是隐含地调用
super()
- 如果显示地写出
super();
语句,则必须保证该语句是第一条语句,并且必须保证父类有无参地构造函数,否则会出错 - 既可以显示写
super();
前提是父类必须有无参地构造函数
也可以显示写super(实参);
前提是父类必须有带参地构造函数 - 一个子类的任何一个构造函数中都只能出现一个
super(实参列表)
class A
{
A(){
System.out.println("AAAA");
}
A(int i){}
}
class B extends A
{
B(){
super(2); //如果把该语句注释掉的化,则编译器默认的是自动隐藏调用super(); 但如果父类没有无参的构造函数,则会报错
//一个子类的构造函数中只能出现一个 super(....)
System.out.println("BBBB");
}
}
class C extends B
{
C()
{
//int k = 10; //如果该语句生效 则会出错,因为会导致super()语句不是构造函数的第一条语句
super(); //35行 每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有无参的构造函数,那么在编译的时候就会报错。
//super();语句可以写,也可以不写,不写的化,系统会自动调用的
//如果显示写出来的话,编译器要求该语句前面不能加任何语句,也就是说该语句必须保证是第一条语句
// super()也可以改为super(2); 但前提是父类必须有带一个参数的构造函数,否则也会报错
//如果把35行改为 super(2); 编译时就会报错!
System.out.println("CCCC");
}
}
class TestSuper_1
{
public static void main(String[] args)
{
C cc = new C();
}
}
/*
运行结果:
BBBB
CCCC
*/
方法重写
- 方法重写指在子类中重新定义父类中已有的方法
- 重写方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型
- 子类中不允许出现与父类同名同参但不同返回类型的方法,如果出现了,编译时会报错
- 重写方法时,不能使用比父类中被覆盖的方法更严格的访问权限
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
}
class B extends A
{
public void f()//方法重写
{
System.out.printf("BBBB\n");
}
}
class TestOver_1
{
public static void main(String[] args){}
}
class A
{
public int f(int i)
{
System.out.printf("AAAA\n");
}
}
class B extends A
{
//把int改为long编译时会报错
//把public改为iprotected就会报错,
//不能使用比父类中被重写的方法更严格的访问权限
public int f(int i) //重写方法的权限不能低于被重写方法的访问权限
{
System.out.printf("BBBB\n");
}
}
class TestOver_2
{
public static void main(String[] args)
{
B bb = new B();
bb.f(10);//编译器无法确定f方法是父类的还是子类的
}
}
多态
- 一个父类的引用类型变量它既可以指向父类对象也可以指向子类对象,它可以根据当前时刻指向的不同,自动调用不同对象的方法,这就是多态。
class A
{
public void f()
{
System.out.printf("AAAA\n");
}
}
class B extends A
{
public void f()
{
System.out.printf("BBBB\n");
}
}
class TestPoly
{
public static void main(String[] args)
{
B bb = new B();
bb.f();
A aa = new A();
aa.f();
aa = bb; //aa可以根据它自己当前时刻指向的是A类对象还是A子类对象,
// 而自动决定调用哪个对象的f方法,这就是多态
aa.f(); // (*aa).f();
}
}
- 多态的优点:利用多态可以实现:用一段代码做不同事情
如:假设A派生出B, B派生出C,试着编写一个函数实现调用整个A类族所有对象f方法。所谓A类族就是A类及其A的子孙所形成的一个族群
class A
{
public void f(){
System.out.printf("AAAA\n");
}
}
class B extends A
{
public void f(){
System.out.printf("BBBB\n");
}
}
class C extends B
{
public void f(){
System.out.printf("CCCC\n");
}
}
class D extends C
{
public void f(){
System.out.printf("DDDD\n");
}
public void g()
{
System.out.printf("GGGG\n");
}
}
class TestPoly_1
{
//m函数可以实现调用整个A类族所有对象f方法的功能
public static void g(aa)
{
aa.f(); //类似于C语言的: (*aa).f();
}
public static void main(String[] args)
{
A aa = new A();
B bb = new B();
C cc = new C();
D dd = new D();
g(aa); // 多态
g(bb);
g(cc);
g(dd);
aa = dd;
aa.g(); // error (*aa).g();
}
}
多态注意事项(难点)
- 子类对象可以直接赋给父类引用,但父类对象在任何情况下都不可以直接赋给子类引用。 因为子类是父类的一种,但父类不是子类的一种,或者讲“子类可以当做父类来看待,但父类不可以当做子类看待”,“狗可以当做动物看待,但动物不可以当做狗来看待”
- 通过父类引用只能访问子类对象从父类继承过来的成员
- 通过父类引用不能访问子类对象所特有的成员
- 父类引用永远不可能直接赋给子类引用
只有在父类引用本身指向的就是一个子类对象时,才可以把父类引用强制转化为子类引用
其他情况下不允许把父类引用强制转化为子类引用,否则运行时会出错。
class A{}
class B extends A{}
public class TestPoly_3
{
public static void main(String[] args)
{
A aa = new A();
B bb = new B();
//bb = aa; //error,父类引用赋给子类引用
//bb = (B)aa; //24行 编译没有错误,但运行时出错! 因为aa指向的是父类对象
A aa2 = new B();
//bb = aa2; //error 永远不可以把父类引用直接赋给子类引用
bb = (B)aa2; //OK 因为aa2 本身指向的就是一个B类对象 所以可以进行强制转化,注意与24行的区别 如果父类引用指向的是个子类对象,则可以通过强制类型转化把父类引用强制转化为子类引用,注意必须强制转化,在Java中无论如何绝对不可能直接把父类引用赋给子类引用的
}
}