Java知识复习二
相关导航
Java剑开天门(一)
Java剑开天门(二)
Java剑开天门(三)
Java剑开天门(四)
Java剑开天门(五)
Java导航
前言
本博文重在夯实Java基础的知识点,回归书本,夯实基础,学深学精
近期需要巩固项目和找实习,计划打算将Java基础从头复习一遍,夯实基础,可能对Java的一些机制不了解或者一知半解,正好借此机会深入学习,也算记录自己的一个学习总结。
Java基础参考书籍:《Java 开发实战经典 名师讲坛 第2版》与《Java核心技术 卷I》
一、数组与方法
1.1 数组定义
- 是一个线性的序列,一组数据的集合,可以快速访问其他的元素。
- 当创建了一个数组时,他的容量是不变的,而且在生命周期也是不能改变的。
- JAVA数组会做边界检查,如果发现有越界现象,会报RuntimeException异常错误,当然检查边界会以效率为代价。
- JAVA还提供其他集合,list,map,set,他们处理对象的时候就好像这些对象没有自己的类型一样,而是直接归根于Object,这样只需要创建一个集合,把对象放进去,取出时转换成自己的类型就行了。
数组是特殊的集合,是线性的集合
,集合和数组的区别如下
1) 数组声明了它容纳的元素的类型
,而集合可以不声明
。
2) 数组是静态的,一个数组实例
具有固定的大小
,一旦创建了就无法改变容量了。而集合
是可以动态扩展容量
,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
3) 数组的存放的类型只能是一种(基本类型/引用类型)
,集合存放的类型可以不是一种
(不加泛型时添加的类型是Object)。
4)数组
是Java语言中内置的数据类型,是线性排列
的,执行效率或者类型检查都是最快
的。 - 集合体系结构
1.2 数组初始化
数组的索引从0
开始,侧重学习一维数组
- 一维数组
1)动态初始化格式
数据类型 [] 数组名称 = new 数据类型[数组长度];
2)静态初始化格式
数据类型 [] 数组名称 = new 数据类型[]{元素1,元素2,元素3…};
3)静态初始化省略格式
数据类型 [] 数组名称 = {元素1,元素2,元素3…};
- 二维数组
如果把一维数组比作几何图形中的线性图形,那么二维数组就相当于一个表格
左索引代表行,右索引代表列
1)动态初始化格式
数据类型 [][] 数组名称 = new 数据类型[行的个数][列的个数];
2)静态初始化格式
数据类型 [][] 数组名称 = new 数据类型[][]{{元素1,元素2,元素3…},{元素1,元素2,元素3…},……};
//内大括号代表一行,内大括号里的每一元素代表一列;
//根据内大括号个数确定行数,根据内大括号里元素个数确定列数;
3)静态初始化省略格式
数据类型 [][] 数组名称 ={{元素1,元素2,元素3…},{元素1,元素2,元素3…},……};
- 多维数组
只需在声明数组时将索引与中括号再加一组就可提高数组的维数。
如三维数组 数据类型 [][][] 数组名称=new 数据类型 [个数][个数][个数] ;
1.3 方法定义
- 解决某件事的功能实现。
- 是一段代码块的封装,是某一功能的实现。
语法格式
修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2,......){
执行语句包括
1)方法的调用
2)变量的操作:声明 赋值 修改
3)程序结构:顺序结构 选择结构 循环结构
return 返回值;
}
- 修饰符
这是可选的,告诉编译器如何调用该方法,定义了该方法的访问类型。
访问控制权限修饰符
访问控制权限修饰符来控制元素的访问范围。
public
表示公开的,在任何位置
都可以访问。
protected
同包、子类(继承)
可以访问
缺省
表示default权限的,同包
下可以访问。
private
表示私有的,只能在本类
中可以访问。
修饰符的范围:private < 缺省 < protected < public
访问控制权限修饰符可以修饰:类、变量、方法…
非访问控制权限修饰符
Java非访问控制修饰符包括: final, abstract, static,synchronized 和 volatile。
final
表示最终的,不可被改变的, 用于修饰类、方法、属性和变量
1) 用final修饰类
时,被修饰的类不可以被继承
。final类中的所有成员方法
都会被隐式地指定为final方法
。
2)用final修饰方法
时,被修饰的方法不能被重写
。在早期的Java版本中被final修饰的方法会转为内嵌调用从而提升性能。(注:类的private方法
会隐式
地被指定为final方法
。)
3)用fina修饰的变量
,如果是基本数据类型
的变量,则其数值一旦在初始化之后便不能更改
;如果是引用类型的变量
,则在对其初始化之后便不能再让其指向另一个对象
。(注:用fianl修饰的类属性变量必须进行初始化
(包括在构造方法中初始化))
static
静态的,全局的,用于修饰类、方法、变量和代码块
static对象可以在它的任何对象创建之前访问,无需引用任何对象,可以通过类名加“.”进行直接访问。
1) 如果一个类
要被声明为static的,只有一种情况,就是静态内部类
。静态内部类可以
声明普通成员变量和方法,而普通内部类不能声明
static成员变量和方法。
2)用static修饰的代码块
也叫静态代码块,是在类中独立于类成员的static语句块,它不在任何的方法体内,可以有多个
,位置
可以随便放
,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序
依次执行它们,每个代码块只会被执行一次
。
3)用static修饰的方法,只能调用static变量
和static方法
,不能以任何形式引用this和super,是抽象方法abstract。
4)用static修饰的变量,在定义时必须要进行初始化
,且初始化时间要早于非静态变量
。
native
原生的,本地的 用于修饰方法
native关键字用来修饰方法
。被native关键字修饰的方法是一个原生态方法
,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中
。Java使用native方法来扩展Java程序的功能,通过JNI接口
调用其他语言来实现对底层的访问。
volatile
易变的,不稳定的,用于修饰变量
用 volatile 修饰的变量通常是为了保证不同线程对这个变量进行操作时的可见性,即一个线程
修改了某个变量的值
,这新值对其他线程
来说是立即可见
的。volatile是Java提供的一种轻量级的同步机制,在并发编程中,它也扮演着比较重要的角色,可以被看作是一种 “程度较轻的 synchronized ”。与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。
1) 当一个共享变量
被 volatile 修饰时,它会保证修改的值会立即被更新
到主存,当有其他线程需要读取时,它会去主存中读取新值
。也就是每次取值在都会取内存中的值,每次更新都会立即更新主存中的值。
2) volatile 关键字能禁止指令重排序
,所以 volatile 能在一定程度上保证有序性
。volatile 关键字禁止指令重排序有两层意思,
①当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的更改
肯定全部已经进行
,且结果已经对后面的操作可见
,在其后面
的操作肯定还没有进行
;
②在进行指令优化时,不能
将在对 volatile 变量访问的语句
放在其后面
执行,也不能
把 volatile 变量后面的语句
放到其前面
执行。
synchronized
同步
,在时间上一致,同步进行,用于修饰方法和代码块
。
用 synchronized 修饰方法和代码块来控制在同一时刻
只有一个线程
执行该段代码
,从而保证多并发情况下的数据安全。synchronized 就是给修饰的方法或代码块声明一个锁
。用synchronized 修饰方法和代码块通常被称为“加锁”
。
1) 类锁
:简单来说,线程执行到一个类锁,其它被synchronized修饰的此类锁不能被执行。
当 synchronized 修饰静态方法或 synchronized 修饰代码块时传入某个class对象
(synchronized (XXXX.class))时被称为类锁。某个线程
得到了一个类锁
之后,其他所有被该类锁加锁方法或代码块
是锁定
的, 其他线程是无法访问的,但是其他线程还是可以访问没有被该类锁加锁的任何代码。
2)对象锁
:简单来说,线程执行到一个对象锁,其它被synchronized修饰的此对象锁不能被执行。
当 synchronized 修饰非静态方法
或 synchronized 修饰代码块
时传入非class对象
(synchronized this))时被称为对象锁
。某个线程得到了对象锁之后,该对象的其他被 synchronized修饰的方法(同步方法)是锁定
的,其他线程是无法访问的。但是其他线程还是可以访问没有进行同步的方法或者代码;当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,只能阻止其他线程获得同一个锁。
3)区别与关系: 如同每个类只有一个class对象而类的实例可以有很多个一样,每个类只有一个类锁
,每个实例都有自己的对象锁
,所以不同对象实例的对象锁是互不干扰的。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。类锁和对象锁是不一样的锁,是互相独立的,两者不存在竞争关系
,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁
,这是允许的。
4) 因为被 synchronized 修饰的代码在同一时刻
只有一个线程
执行,所以也就保证了操作的原子性
和内存可见性
。
- 返回值类型
方法可能有返回值。返回值无则用void,有返回值则用对应的返回数据类型。
一般用一个变量来接住返回值,在最后return这个变量。 - 方法名
方法的实际名称,方法名和参数表共同构成方法签名。 - 参数类型
参数像一个占位符。
当方法被调用时,传递值给参数,这个值被称为实参或者变量。
参数表是指方法的参数类型、顺序、和参数的个数。参数是可选的,方法可以不包含任何参数。
形式参数:在方法被调用时用于接收外界输入的数据。即定义方法时的参数。
实参:调用方法时实际传给方法的数据。是调用方法时的参数。
1.4 方法重载(OverLoading)
1.4.1 定义
方法重载是指在一个类
中定义多个同名
的方法,但要求每个方法具有不同的参数的类型
或参数的个数
。
Java的方法重载,就是在类中可以创建多个方法,它们可以有相同的名字,但必须具有不同的参数,即或者是参数的个数不同,或者是参数的类型不同。
调用方法时通过传递给它们的不同个数和类型的参数,以及传入参数的顺序来决定具体使用哪个方法。
发生在同一个类中
方法重载就是方法名称重复,参数列表不同即加载参数不同,方法体可不同
1.4.2 规则
-
被重载的方法
必须
改变参数列表
; -
被重载的方法
可以
改变返回类型
; -
被重载的方法
可以
改变访问修饰符
; -
被重载的方法可以声明
新的或更广
的检查异常
; -
方法能够在
同一个类
中或者在一个子类
中被重载。
1.5 方法重写(@Override
注解)
1.5.1 定义
在Java和其他一些高级面向对象的编程语言中,子类可继承父类中的方法,而不需要重新编写相同的方法。
但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖
。
发生在不同类中
方法重写就是方法名、参数列表、返回值相同,方法体不同
1.5.2 规则
-
方法名、参数、返回值
相同; -
子类方法
不能缩小
父类方法的访问权限; -
子类方法
不能抛出
比父类方法更多的异常
(但子类方法可以不抛出异常); -
存在于
父类和子类
之间; -
方法被定义为
final不能被重写
。
1.6 重写与重载关系
重载(overload)和覆盖(overide)是 Java 多态性的不同表现方式。
1.7 方法引用
1.7.1 定义
当要传递给Lambda体的操作,已经有了实现方法,可以使用方法引用。
方法引用涉及到Lambda表达式,会在Lambda这一章节具体学习。
二、数组常用方法
1、打印数组
import java.util.Arrays;//导包
static String toString(boolean[] a)
返回指定数组的内容的字符串表示形式。
static String toString(byte[] a)
返回指定数组的内容的字符串表示形式。
static String toString(char[] a)
返回指定数组的内容的字符串表示形式。
static String toString(double[] a)
返回指定数组的内容的字符串表示形式。
static String toString(float[] a)
返回指定数组的内容的字符串表示形式。
static String toString(int[] a)
返回指定数组的内容的字符串表示形式。
static String toString(long[] a)
返回指定数组的内容的字符串表示形式。
static String toString(Object[] a)
返回指定数组的内容的字符串表示形式。
static String toString(short[] a)
返回指定数组的内容的字符串表示形式。
class example{
public static void main(Sting[] args){
Arrays.toString(数组名);
}
}
2、数组排序
import java.util.Arrays;//导包
static void sort(byte[] a)
按照数字顺序排列指定的数组。
static void sort(byte[] a, int fromIndex, int toIndex)
按升序排列数组的指定范围。
static void sort(char[] a)
按照数字顺序排列指定的数组。
static void sort(char[] a, int fromIndex, int toIndex)
按升序排列数组的指定范围。
static void sort(double[] a)
按照数字顺序排列指定的数组。
static void sort(double[] a, int fromIndex, int toIndex)
按升序排列数组的指定范围。
static void sort(float[] a)
按照数字顺序排列指定的数组。
static void sort(float[] a, int fromIndex, int toIndex)
按升序排列数组的指定范围。
static void sort(int[] a)
按照数字顺序排列指定的数组。
static void sort(int[] a, int fromIndex, int toIndex)
按升序排列数组的指定范围。
static void sort(long[] a)
按照数字顺序排列指定的数组。
static void sort(long[] a, int fromIndex, int toIndex)
按升序排列数组的指定范围。
static void sort(Object[] a)
对指定对象升序排列的阵列,根据natural ordering的元素。
static void sort(Object[] a, int fromIndex, int toIndex)
对指定对象升序排列的数组的指定范围内,根据natural ordering的元素。
static void sort(short[] a)
按照数字顺序排列指定的数组。
static void sort(short[] a, int fromIndex, int toIndex)
按升序排列数组的指定范围。
static <T> void sort(T[] a, Comparator<? super T> c)
根据指定的比较器引发的顺序对指定的对象数组进行排序。
static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c)
根据指定的比较器引发的顺序对指定的对象数组的指定范围进行排序。
class example{
public static void main(Sting[] args){
Arrays.sort(数组名);
}
}
3、数组长度
- 使用数组的length属性
import java.lang.*;//导包
class example{
public static void main(Sting[] args){
for(int i=0;i<数组名.length;i++){
……
}
}
}
- 使用Array中的getLength方法
import java.lang.reflect.Array;//导包
static int getLength(Object array)
返回指定数组对象的长度,如 int 。
class example{
public static void main(Sting[] args){
for(int i=0;i<Array.getLength(数组名);i++){//Array.getLength()
……
}
}
}
4、数组相等
- 使用数组自带的equals方法
实际上是使用Object类的equals()方法。
import java.lang.*;//导包
class example{
public static void main(Sting[] args){
数组名.equals(另一数组名);//结果为true或false
}
}
- 使用Arrays类中的equals方法
import java.util.Arrays;//导包
static boolean equals(boolean[] a, boolean[] a2)
如果两个指定的布尔数组彼此 相等 ,则返回 true 。
static boolean equals(byte[] a, byte[] a2)
如果两个指定的字节数组彼此 相等 ,则返回 true 。
static boolean equals(char[] a, char[] a2)
如果两个指定的字符数组彼此 相等 ,则返回 true 。
static boolean equals(double[] a, double[] a2)
如果两个指定的双精度数组彼此 相等 ,则返回 true 。
static boolean equals(float[] a, float[] a2)
如果两个指定的浮动数组彼此 相等 ,则返回 true 。
static boolean equals(int[] a, int[] a2)
如果两个指定的int数组彼此 相等 ,则返回 true 。
static boolean equals(long[] a, long[] a2)
如果两个指定的longs数组彼此 相等 ,则返回 true 。
static boolean equals(Object[] a, Object[] a2)
如果两个指定的对象数组彼此 相等 ,则返回 true 。
static boolean equals(short[] a, short[] a2)
如果两个指定的短裤阵列彼此 相等 ,则返回 true
class example{
public static void main(Sting[] args){
Arrays.equals(数组名,另一数组名);//结果为true或false
}
}
5、数组克隆
- new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,根据类型分配内存,再调用构造函数,填充对象的各个域,这一步就叫对象的初始化。初始化完毕后,可以把他的引用(地址)发布到外部,在外部就可以通过引用操纵这个对象。
- clone在第一步是和new相似的,都是分配内存,调用clone方法时,分配的内存和源对象一样,然后再使用源对象中对应的各个域,填充新对象的域。同样可以把这个新对象的引用发布到外部 。
复制引用【直接赋值】
复制对象【用clone方法】
- 使用基类 Object 的 clone()方法
import java.lang.*;//导包
class example{
public static void main(Sting[] args){
int[] src={1,2,3};
int[] copy=src.clone();
}
}
- 使用Arrays的copyOf……系列方法
import java.util.Arrays;//导包
class example{
public static void main(Sting[] args){
Arrays.copyOf(数组名,数组名.length);
}
}
static boolean[] copyOf(boolean[] original, int newLength)
使用 false (如有必要)复制指定的数组,截断或填充,以使副本具有指定的长度。
static byte[] copyOf(byte[] original, int newLength)
复制指定的数组,用零截取或填充(如有必要),以便复制具有指定的长度。
static char[] copyOf(char[] original, int newLength)
复制指定的数组,截断或填充空字符(如有必要),以便复制具有指定的长度。
static double[] copyOf(double[] original, int newLength)
复制指定的数组,用零截取或填充(如有必要),以便复制具有指定的长度。
static float[] copyOf(float[] original, int newLength)
复制指定的数组,用零截取或填充(如有必要),以便复制具有指定的长度。
static int[] copyOf(int[] original, int newLength)
复制指定的数组,用零截取或填充(如有必要),以便复制具有指定的长度。
static long[] copyOf(long[] original, int newLength)
复制指定的数组,用零截取或填充(如有必要),以便复制具有指定的长度。
static short[] copyOf(short[] original, int newLength)
复制指定的数组,用零截取或填充(如有必要),以便复制具有指定的长度。
static <T> T[] copyOf(T[] original, int newLength)
复制指定的数组,用空值截断或填充(如有必要),以便复制具有指定的长度。
static <T,U> T[] copyOf(U[] original, int newLength, 类<? extends T[]> newType)
复制指定的数组,用空值截断或填充(如有必要),以便复制具有指定的长度。
static boolean[] copyOfRange(boolean[] original, int from, int to)
将指定数组的指定范围复制到新数组中。
static byte[] copyOfRange(byte[] original, int from, int to)
将指定数组的指定范围复制到新数组中。
static char[] copyOfRange(char[] original, int from, int to)
将指定数组的指定范围复制到新数组中。
static double[] copyOfRange(double[] original, int from, int to)
将指定数组的指定范围复制到新数组中。
static float[] copyOfRange(float[] original, int from, int to)
将指定数组的指定范围复制到新数组中。
static int[] copyOfRange(int[] original, int from, int to)
将指定数组的指定范围复制到新数组中。
static long[] copyOfRange(long[] original, int from, int to)
将指定数组的指定范围复制到新数组中。
static short[] copyOfRange(short[] original, int from, int to)
将指定数组的指定范围复制到新数组中。
static <T> T[] copyOfRange(T[] original, int from, int to)
将指定数组的指定范围复制到新数组中。
static <T,U> T[] copyOfRange(U[] original, int from, int to, 类<? extends T[]> newType)
将指定数组的指定范围复制到新数组中。
6、合并数组
- 使用Arrays中的copyOf方法
import java.util.Arrays;//导包
public class example{
public static void main(String[] args){
int[] A=new int[]{1,3,5,7,9};
int[] B=new int[]{2,4,6,8,10};
int[] result=Arrays.copyOf(A,10);
//使result数组长度为10,并将A数组拷贝至新数组,此时result数组与原数组不是同一块内存,原来的内存还是5个元素,result的是扩容后的空间
for(int x=0;x<B.length;x++){
result[A.length+x]=B[x];
}
for(int i:result){
System.out.print(i+" ");
}
System.out.print("");//换行
}
}
7、反转数组
package test;
/**
* 数组的反转
*/
public class TestDemo {
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5,6};
for(int elem:arr){
System.out.print(elem + ",");
}
// 打印反转后的元素
arr = reverse(arr);
System.out.println();
for(int elem:arr){
System.out.print(elem + ",");
}
}
// 实现数组元素的翻转
public static int[] reverse(int[] arr){
// 遍历数组
for(int i = 0;i < arr.length / 2;i++){
// 交换元素
int temp = arr[arr.length -i - 1];
arr[arr.length -i - 1] = arr[i];
arr[i] = temp;
}
// 返回反转后的结果
return arr;
}
// 第二种方式
public static int[] reverse1(int[] arr){
int[] arr1 = new int[arr.length];
for(int i = arr.length-1;i >= 0;i--){
arr1[arr.length-i-1] = arr[i];
}
return arr1;
}
}
8、移除元素
- 通过创建新的数组,将保留的原数组中的元素赋值到新数组来实现原数组元素的删除。同理,可以实现数组添加元素。
package package1;
import java.util.Arrays;
public class example {
public static void main(String[] args) {
int[] array1 = new int[] {4, 5, 6, 7};
int num = 2;
int[] newArray = new int[array1.length-1];
for(int i=0;i<newArray.length; i++) {
// 判断元素是否越界
if (num < 0 || num >= array1.length) {
throw new RuntimeException("元素越界... ");
}
//
if(i<num) {
newArray[i] = array1[i];
}
else {
newArray[i] = array1[i+1];
}
}
// 打印输出数组内容
System.out.println(Arrays.toString(array1));
array1 = newArray;
System.out.println(Arrays.toString(array1));
}
}
9、将数组元素组成一个字符串
- 遍历数组
import java.util.Arrays;
public class example{
public static void main(String[] args){
// TODO Auto-generated method stub
//定义一个int类型的数组
int[] i ={4,5,8,6,5,8,7,4,5};
//调用自定义方法将int数组的方法转换成字符串
toStringMethod(i);
}
private static void toStringMethod(int[] arr){
// 自定义一个字符缓冲区,
StringBuilder sb = new StringBuilder();
sb.append("[ ");
//遍历int数组,并将int数组中的元素转换成字符串储存到字符缓冲区中去
for(int i=0;i<sb.length;i++){
if(i!=arr.length-1){
sb.append(arr[i]+" ,");
else{
sb.append(arr[i]+" ]");
}
System.out.println(sb);
}
}
10、检查数组是否包含某一个值
- 遍历数组
import java.util.Arrays;
public class example{
public static void main(String[] args){
String[] vowels = { "A", "I", "E", "O", "U" };
// using simple iteration over the array elements
for (String s : vowels) {
if ("E".equals(s)) {
System.out.println("E found in the vowels list.");
}
}
}
}
11、ArrayList集合与Array数组相互转化
-
Array: 读快改慢
-
Linked :改快读慢
-
Hash:介于两者之间
//集合转数组,集合中toArray()的方法
import java.util.*;
public class example {
public static void main(String[] arr) {
Object[] listArray = list.toArray();
Object[] setArray = set.toArray();
}
}
//数组转集合,Arrays.asList(数组名)方法
import java.util.*;
public class example {
public static void main(String[] arr) {
List list = new ArrayList();
list = Arrays.asList(array); //注意:对于int[]数组不能直接这样做,因为asList()方法的参数必须是对象。应该先把int[]转化为Integer[]。
Set set = new HashSet(Array.asList(array)); //使用list构造set
}
}
三、面向对象程序设计
早期的程序经历了【面向问题】、【面向过程】的阶段,以往的程序设计方法不能适应现代的软件的技术要求。故【面向对象】的程序设计方法被提出。
面向对象的概念和应用已经超越了程序设计与开发,扩展到很宽的范围,如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。
- 面向过程 (Procedure Oriented) 以
过程
为核心,强调事件的流程、顺序
,如:C语言。 - 面向对象(Object Oriented) 以
对象
为核心,强调事件的角色、主体
,如:C++、Java。 - 总结 面向过程解决简单问题效率高,面向对象有良好的继承性、拓展性、复用性。
1、面向对象三大特性
1.1 封装
- 把对象的属性(成员变量)和行为(成员方法)看成一个密不可分的一个整体,将这两者封装在一个不可分割的独立单位中(即对象)。
- 信息隐蔽,把不需要让外界知道的信息隐蔽起来,有些对象的属性和行为允许外界用户知道或使用,但不允许修改。
- 具体表现 Java中通过将属性的访问权限声明为私有的(private),再提供公共的(public)方法getXxx()和setXxx()实现对属性的操作。
- IDEA中自动生成get/set方法
lombok + @Data注解
1)IDEA中安装插件 lombok
2)在类上方加上@Data注解
package com.my.app.xxx;
@Data
public class Student {
private String name;
private int age;
private String grade;
}
IDEA自动生成
1)在类内点击 ctrl+回车
2)选中Getter and Setter
3)选中三个属性,点击 OK
4)get和set方法自动出现
package com.my.app.model;
/**
* Description TODO
*/
public class Student {
private String name;
private int age;
private String grade;
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;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
1.2 继承
- 继承又称为派生。首先拥有反映事物一般特性的类,然后在此基础上派生出反映特殊事物的类。
- 被继承的类称为父类或者超类,而经继承产生的类称为子类或派生类。
- 一个子类只允许继承一个父类,称为单继承;允许继承多个父类,称为多继承。目前许多面向对象语言不支持多继承。而Java通过
接口(interface)
来弥补由于Java不支持多继承而带来的子类不能享用多个父类的成员
的缺点。 - 关键词
extends
class 子类 extends 父类 {
}
- 子类实际上是将父类定义的更加的具体化的一种手段。父类表示的范围大,而子类表示的范围小。
1.3 多态
- 指在父类中定义的属性和方法被子类继承之后,可以具有
不同的数据类型
或表现出不同的行为
。 - JAVA中的多态可以简单的理解为一种事物的
多种形态
,当然多态是在继承的基础
上有重写
才存在。
实现多态的必要条件 - 继承
在多态中必须存在有继承关系的子类和父类。 - 重写
子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。 - 向上转型
在多态中需要将子类的引用
赋给父类对象
,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
多态的例子
1)创建 Figure 类,在该类中首先定义存储二维对象的尺寸,
然后定义有两个参数的构造方法,最后添加 area() 方法,该方法计算对象的面积。
代码如下
public class Figure {
double dim1;
double dim2;
Figure(double d1, double d2) {
// 有参的构造方法
this.dim1 = d1;
this.dim2 = d2;
}
double area() {
// 用于计算对象的面积
System.out.println("父类中计算对象面积的方法,没有实际意义,需要在子类中重写。");
return 0;
}
}
2)创建继承自 Figure 类的 Rectangle 子类,
该类调用父类的构造方法,并且重写父类中的 area() 方法。
代码如下
public class Rectangle extends Figure {
Rectangle(double d1, double d2) {
super(d1, d2);
}
double area() {
System.out.println("长方形的面积:");
return super.dim1 * super.dim2;
}
}
3)创建继承自 Figure 类的 Triangle 子类,该类与 Rectangle 相似。
代码如下
public class Triangle extends Figure {
Triangle(double d1, double d2) {
super(d1, d2);
}
double area() {
System.out.println("三角形的面积:");
return super.dim1 * super.dim2 / 2;
}
}
4)创建 Test 测试类,在该类的 main() 方法中首先声明 Figure 类的变量 figure,
然后分别为 figure 变量指定不同的对象,并调用这些对象的 area() 方法。
代码如下
public class Test {
public static void main(String[] args) {
Figure figure; // 声明Figure类的变量
figure = new Rectangle(9, 9);
System.out.println(figure.area());
System.out.println("===============================");
figure = new Triangle(6, 8);
System.out.println(figure.area());
System.out.println("===============================");
figure = new Figure(10, 10);
System.out.println(figure.area());
}
}
_ooOoo_
o8888888o
88" . "88
(| -_- |)
O\ = /O
____/`---'\____
.' \\| |// `.
/ \\||| : |||// \
/ _||||| -:- |||||_ \
| | \\\ - /'| | |
| \_| `\`---'// |_/ |
\ .-\__ `-. -'__/-. /
___`. .' /--.--\ `. .'___
."" '< `.___\_<|>_/___.' _> \"".
| | : `- \`. ;`. _/; .'/ / .' ; |
\ \ `-. \_\_`. _.'_/_/ -' _.' /
================-.`___`-.__\ \___ /__.-'_.'_.-'================
`=--=-'
输出
长方形的面积:
81.0
===============================
三角形的面积:
24.0
===============================
父类中计算对象面积的方法,没有实际意义,需要在子类中重写。
0.0
此案例很好的诠释了多态性,子类向上转型
成相同名称的对象,调用相同
的方法,输出不同
的结果。
2、类和对象基础知识
在面向对象中类和对象是最基本、最重要的组成单元。
类实际上表示一个客观世界某类群体的一些基本特征抽象。
对象是表示一个个具体的东西
- 类是对象的集合,对象是类的实例。
2.1 类的定义
类是由属性和方法组成的。
实际上一个属性就是一个变量,二方法就是一些操作的行为(也就是我们所说的函数);
类的定义格式如下
修饰符 class 类名{
修饰符 数据类型 属性;
修饰符 数据类型 属性;
修饰符 数据类型 属性;
……
修饰符 返回值类型 方法名(参数1,参数2,……){
程序语句;
[return 表达式; ]
}
}
2.2 对象的创建及使用
需要通过对象来实例化类,即使用类;
对象的创建
类名 对象名称= new 类名();//只要有关键字new就表示开辟新的堆内存空间
访问对象中的属性和方法
【访问属性】——对象名称.属性名
【访问方法】——对象名称.方法名()
创建对象的五种方法
——————————————————————————————————————————————————————————————————————
1)使用new关键字创建对象
这是我们最常见的也是最简单的创建对象的方式,
通过这种方式我们可以调用任意的构造函数(无参的和有参的)去创建对象。
比如
Student student = new Student();
——————————————————————————————————————————————————————————————————————
2)使用Class类的newInstance方法(反射机制)创建对象
我们也可以通过Java的反射机制使用Class类的newInstance方法来创建对象,
事实上,这个newInstance方法调用无参的构造器创建对象,
比如
Student student2 = (Student)Class.forName("Student类全限定名").newInstance();
或者
Student stu = Student.class.newInstance();
——————————————————————————————————————————————————————————————————————
3)使用Constructor类的newInstance方法(反射机制)创建对象
java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象,
该方法和Class类中的newInstance方法很像,但是相比之下,
Constructor类的newInstance方法更加强大些,
我们可以通过这个newInstance方法调用有参数的和私有的构造函数,
比如
public class Student {
private int id;
public Student(Integer id) {
this.id = id;
}
public static void main(String[] args) throws Exception {
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class);
Student stu3 = constructor.newInstance(123);
}
}
——————————————————————————————————————————————————————————————————————
4)使用Clone方法创建对象
无论何时我们调用一个对象的clone方法,JVM都会帮我们创建一个新的、一样的对象,
特别需要说明的是,用clone方法创建对象的过程中并不会调用任何构造函数。
简单而言,要想使用clone方法,我们就必须先实现Cloneable接口并实现其定义的clone方法,这也是原型模式的应用。
比如
public class Student implements Cloneable{
private int id;
public Student(Integer id) {
this.id = id;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
public static void main(String[] args) throws Exception {
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class);
Student stu3 = constructor.newInstance(123);
Student stu4 = (Student) stu3.clone();
}
}
——————————————————————————————————————————————————————————————————————
5)使用(反)序列化机制创建对象
当我们反序列化一个对象时,JVM会给我们创建一个单独的对象,
在此过程中,JVM并不会调用任何构造函数。为了反序列化一个对象,
我们需要让我们的类实现Serializable接口,
比如
public class Student implements Cloneable, Serializable {
private int id;
public Student(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
public static void main(String[] args) throws Exception {
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class);
Student stu3 = constructor.newInstance(123);
// 写对象
ObjectOutputStream output = new ObjectOutputStream(
new FileOutputStream("student.bin"));
output.writeObject(stu3);
output.close();
// 读对象
ObjectInputStream input = new ObjectInputStream(new FileInputStream(
"student.bin"));
Student stu5 = (Student) input.readObject();
System.out.println(stu5);
}
}
类实例化的内存分配详解
Person p = new Person(); 的过程如下
存在继承关系的Student stu = new Student(); 的过程如下
2.3 构造方法
构造方法是一个比较特殊的方法,通过构造方法可以完成对象的创建,以及实例变量的初始化。
换句话说:构造方法是用来创建对象,并且同时给对象的属性赋值。
注意:实例变量没有手动赋值的时候,系统会赋默认值。
构造方法格式
[修饰符列表] 构造方法名(形式参数列表){
构造方法体;
通常在构造方法体当中给属性赋值,完成属性的初始化。
}
注意点
1)修饰符列表目前统一写:public。千万不要写public static。
2)构造方法名和类名必须一致。
3)构造方法不需要【指定返回值类型】,
也不能写void,写上void表示普通方法,就不是构造方法了。
在对象创建时,使用new运算符来调用构造方法。
new 构造方法名(实际参数列表);
- 当一个类中没有提供任何构造方法,系统
默认提供
一个无参数
的构造方法。
这个无参数的构造方法叫做缺省构造器
。 - 当一个类中
手动的提供了构造方法
,那么系统将不再默认提供
无参数构造方法。
建议将无参数构造方法手动的写出来,这样一定不会出问题
。 - 构造方法是支持方法重载的。在一个类当中构造方法可以有多个。
并且所有的构造方法名字都是一样的。 - 如果
实例变量赋初始值
,会在new对象的时候执行赋值
,然后再执行构造方法
里的代码!!!
2.4 内部类
可以将一个类的定义放在里另一个类的内部,这就是内部类。广义上我们将内部类分为四种:成员内部类、静态内部类、局部(方法)内部类、匿名内部类。
/**
* 我是一个外部类(外部是相对内部而言)
*/
public class Outer{
/**
* 我是一个内部类
*/
class Inner{
//...
}
}
使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。——《Think in java》
也就是说内部类拥有类的基本特征。(可以继承父类
,实现接口
。)在实际问题中我们会遇到一些接口无法解决或难以解决的问题,此时我们可以使用内部类继承某个具体的或抽象的类
,间接解决类无法多继承
引起的一系列问题。(注:内部类可以嵌套内部类,但是这极大的破坏了代码的结构,这里不推荐使用。)
/**
1. Outer类继承了ClassA,实现了IFunctionA
*/
public class Outer extends ClassA implements IFunctionA{
/**
* Inner类继承了ClassB,实现了IFunctionB
*/
public class Inner extends ClassB implements IfunctionB{
//
}
}
具体来说,内部类信息(属性、方法)可以和外部类重名
;
内部类是具有类的基本特征的独立实体;
可以利用访问修饰符隐藏内部类的实施细节,提供了更好的封装;
静态内部类使用时可直接使用,不需先创造外部类。
demo讲解
成员内部类
1)外部类、内部类
/**
* 外部类、成员内部类的定义
*/
public class Outer {
private int outerVariable = 1;
private int commonVariable = 2;
private static int outerStaticVariable = 3;
//省略getter/setter
/**
* 成员方法
*/
public void outerMethod() {
System.out.println("我是外部类的outerMethod方法");
}
/**
* 静态方法
*/
public static void outerStaticMethod() {
System.out.println("我是外部类的outerStaticMethod静态方法");
}
/**
* 内部类
*/
public class Inner {
private int commonVariable = 20;
/**
* 构造方法
*/
public Inner() {
}
/**
* 成员方法,访问外部类信息(属性、方法)
*/
public void innerShow() {
//当和外部类冲突时,直接引用属性名,是内部类的成员属性
System.out.println("内部的commonVariable:" + commonVariable);
//内部类访问外部属性
System.out.println("outerVariable:" + outerVariable);
//当和外部类属性名重叠时,可通过外部类名.this.属性名
System.out.println("外部的commonVariable:" + Outer.this.commonVariable);
System.out.println("outerStaticVariable:" + outerStaticVariable);
//访问外部类的方法
outerMethod();
outerStaticMethod();
}
}
/**
* 外部类访问内部类信息
*/
public void outerShow() {
Inner inner = new Inner();
inner.innerShow();
}
}
2)其他类使用成员内部类
/*
* 其他类使用成员内部类
*/
public class Other {
public static void main(String[] args) {
//外部类对象
Outer outer = new Outer();
//创造内部类对象
Outer.Inner inner = outer.new Inner();
inner.innerShow();
/*
* 可在Outer中定义get方法,获得Inner对象,那么使用时,只需outer.getInnerInstance()即可。
* public Inner getInnerInstance(Inner类的构造方法参数){
* return new Inner(参数);
* }
*/
}
}
3)运行结果(和innerShow()方法对照)
4)小结——【成员内部类当成Outer的成员信息存在 】
- 可以是任何的访问修饰符。
- 内部类的内部不能有静态信息。
- 内部类也是类,该继承继承,该重写重写,该重载重载,this和super随便用。
- 外部类如何访问内部类信息,必须new之后打点访问。
- 内部类可以直接使用外部类的任何信息,如果属性或者方法发生冲突,调用外部类.this.属性或者方法。
- 其它类如何访问内部类:
Outer outer=new Outer();
//创造内部类对象
Outer.Inner inner=outer.new Inner();
inner.inner_show();
静态内部类
1)外部类、内部类
/**
* 外部类、内部类定义
*/
public class Outer {
private int outerVariable = 1;
/**
* 外部类定义的属性(重名)
*/
private int commonVariable = 2;
private static int outerStaticVariable = 3;
static {
System.out.println("Outer的静态块被执行了……");
}
/**
* 成员方法
*/
public void outerMothod() {
System.out.println("我是外部类的outerMethod方法");
}
/*
* 静态方法
*/
public static void outerStaticMethod() {
System.out.println("我是外部类的outerStaticMethod静态方法");
}
/**
* 静态内部类
*/
public static class Inner {
/**
* 成员信息
*/
private int innerVariable = 10;
private int commonVariable = 20;
static {
System.out.println("Outer.Inner的静态块执行了……");
}
private static int innerStaticVariable = 30;
/**
* 成员方法
*/
public void innerShow() {
System.out.println("innerVariable:" + innerVariable);
System.out.println("内部的commonVariable:" + commonVariable);
System.out.println("outerStaticVariable:"+outerStaticVariable);
outerStaticMethod();
}
/**
* 静态方法
*/
public static void innerStaticShow() {
//被调用时会先加载Outer类
outerStaticMethod();
System.out.println("outerStaticVariable"+outerStaticVariable);
}
}
/**
* 外部类的内部如何和内部类打交道
*/
public static void callInner() {
System.out.println(Inner.innerStaticVariable);
Inner.innerStaticShow();
}
}
2)其他类使用成员内部类
public class Other {
public static void main(String[] args) {
//访问静态内部类的静态方法,Inner类被加载,此时外部类未被加载,独立存在,不依赖于外围类。
Outer.Inner.innerStaticShow();
//访问静态内部类的成员方法
Outer.Inner oi = new Outer.Inner();
oi.innerShow();
}
}
3)运行结果(注意加载顺序)
4)小结——【和成员内部类对比理解(区别异同)】
- 内部可以包含任意的信息。
- 静态内部类的方法只能访问外部类的static关联的信息。
- 利用 外部类.内部类 引用=new 外部类.内部类(); 然后利用引用.成员信息(属性、方法)调用。
- 访问内部类的静态信息,直接外部类.内部类.静态信息就可以了。
- 静态内部类可以独立存在,不依赖于其他外围类。
局部内部类
1)外部类、内部类
/**
* 外部类、内部类
*/
public class Outer {
/**
* 属性和方法
*/
private int outerVariable = 1;
/**
* 外部类定义的属性
*/
private int commonVariable = 2;
/**
* 静态的信息
*/
private static int outerStaticVariable = 3;
/**
* 成员外部方法
*/
public void outerMethod() {
System.out.println("我是外部类的outerMethod方法");
}
/**
* 静态外部方法
*/
public static void outerStaticMethod() {
System.out.println("我是外部类的outerStaticMethod静态方法");
}
/**
* 程序的入口
*/
public static void main(String[] args) {
Outer outer = new Outer();
outer.outerCreatMethod(100);
}
/**
* 成员方法,内部定义局部内部类
*/
public void outerCreatMethod(int value) {
/**
* 女性
*/
boolean sex = false;
/**
* 局部内部类,类前不能有访问修饰符
*/
class Inner {
private int innerVariable = 10;
private int commonVariable = 20;
/**
* 局部内部类方法
*/
public void innerShow() {
System.out.println("innerVariable:" + innerVariable);
//局部变量
System.out.println("是否男性:" + sex);
System.out.println("参数value:" + value);
//调用外部类的信息
System.out.println("outerVariable:" + outerVariable);
System.out.println("内部的commonVariable:" + commonVariable);
System.out.println("外部的commonVariable:" + Outer.this.commonVariable);
System.out.println("outerStaticVariable:" + outerStaticVariable);
outerMethod();
outerStaticMethod();
}
}
//局部内部类只能在方法内使用
Inner inner = new Inner();
inner.innerShow();
}
}
2)运行结果
3)小结——【局部内有很多局限,应注意作用域】
- 类前不能有访问修饰符。
- 仅在此方法内使用。
- 无法创造静态信息。
- 可以直接访问方法内的局部变量和参数(有限制,下面详谈),但是不能更改。
- 可以随意的访问外部类的任何信息。
4)局部内部类访问局部变量的限制
Variable ‘xxx’ is accessed from within inner class, needs to be final or effectively final
它的意思是:变量’xxx’从内部类中访问,需要final或有效的final
具体限制如下
①直接被final修饰的变量。
②已被赋值且始终未改变的变量(有且仅有赋值一次),引用指向不能改变。JDK8以前(不包括8)只能访问被final修饰的变量。
boolean sex=false;
sex=true;
就会产生如下错误:传入局部内部类所在方法的参数同理,如果一直不变则可使用,反之则会报错。
匿名内部类
1)定义接口
/**
* 接口中方法默认为public
*/
public interface IAnimal{
void speak();
}
2)匿名内部类使用
/**
* 外部内、内部类
*/
public class Outer {
public static IAnimal getInnerInstance(String speak){
return new IAnimal(){
@Override
public void speak(){
System.out.println(speak);
}};
//注意上一行的分号必须有
}
public static void main(String[] args){
//调用的speak()是重写后的speak方法。
Outer.getInnerInstance("小狗汪汪汪!").speak();
}
}
3)结果
小狗汪汪汪!
4)小结——【匿名内部类常常被用来重写某个或某些方法】
- 匿名内部类是没有访问修饰符的。
- 使用匿名内部类时,这个new之后的类首先是要存在的,其次我们要重写new后的类的某个或某些方法。
- 匿名内部类访问方法参数时也有和局部内部类同样的限制。
- 匿名内部类没有构造方法。
3、 几个关键字
3.1 this关键字
- this关键字代表
本类
- this关键字只能在
方法内
使用,方法外使用是编译不通过的` - this不能用于
static
方法中 - this可以作为方法的
返回值
。
private rolecolor = red;
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (isSelected) {
setBackground(this.rowcolor);
setFont(getFont().deriveFont((float) (getFont().getSize() + 2)));
}
return this;//返回当前JList对象
}
1)this.属性名
this表示当前类,this.属性名可以给当前类的属性
赋值,可以在本类中除静态方法
外的任何方法(包括构造器、私有方法)中使用。
static关键字的特性
- 遵循静态调用
- static关键字
不能与this、super关键字共用
- 注意:若本类成员变量与方法内局部变量同名,this.属性名代表的是什么呢??
解答:是局部变量
,因为Java遵循就近原则
,通俗地讲,谁离我近,我就代表谁。
class Student{
String name;
public void hello(){
this.name=name;
}
}
2)this.方法
this.方法 即调用方法
,除了静态方法
中不可使用,本类其他方法
包括私有方法均可使用此格式调用其他方法,只是我们通常省略this关键字
。
class Student{
String name;
public void hello(){
this.name=name;
}
// this.name=name;this需要在方法内使用
public void hi(){
this.hello();
hello();
he();
}
private void he(){
this.name=name;
hello();
this.he();
}
}
3)this.()
此格式用于构造器内
- 比如我们可以在
无参构造
内调用含参构造
,那么这时候就需要在this()传入参数
来实现, - 同理要想在
含参构造
内调用无参构造
,只需在构造器代码第一行写this()
即可, - 注意,this()与this(参数列表)不可
同时
使用!
public Student(){
this("name");
this.name=name;
// this.();
}
public Student(String name){
// this();两个this不能一起使用
}
3.2 super关键字
- super理解为:超类的,父类的
- super可以用来调用:属性、方法、构造器
- super的语法是:
“super.属性名/方法”、“super()”
1)super()
- 创建子类对象的时候,先
初始化父类型特征
。 - 在构造方法中使用
- super(实际参数列表)的作用是:初始化当前对象的
父类型特征
。
并不是创建新对象。实际上对象只创建了一个
。 - 在构造方法执行过程中一连串调用了
父类的构造方法
,父类的构造方法
又继续向下调用它的父类的构造方法
。 - super关键字代表的就是
当前对象
的那部分父类型特征。
public class SuperTest03{
public static void main(String[] args){
CreditAccount ca1 = new CreditAccount();
System.out.println(ca1.getActno() + "," + ca1.getBalance() + "," + ca1.getCredit());
CreditAccount ca2 = new CreditAccount("1111", 10000.0, 0.999);
System.out.println(ca2.getActno() + "," + ca2.getBalance() + "," + ca2.getCredit());
}
}
// 账户
class Account extends Object{
// 属性
private String actno;
private double balance;
// 构造方法
public Account(){
//默认有以下代码
//super();
//this.actno = null;
//this.balance = 0.0;
}
public Account(String actno, double balance){
// super();
this.actno = actno;
this.balance = balance;
}
// setter and getter
public void setActno(String actno){
this.actno = actno;
}
public String getActno(){
return actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public double getBalance(){
return balance;
}
}
// 信用账户
class CreditAccount extends Account{
// 属性:信誉度(诚信值)
// 子类特有的一个特征,父类没有。
private double credit;
// 构造方法
// 分析以下程序是否存在编译错误????
public CreditAccount(String actno, double balance, double credit){
// 私有的属性,只能在本类中访问。
/*
this.actno = actno;
this.balance = balance;
*/
// 以上两行代码在恰当的位置,正好可以使用:super(actno, balance);
// 通过子类的构造方法调用父类的构造方法。
super(actno, balance);
this.credit = credit;
}
// 提供有参数的构造方法
public CreditAccount(){
//super();
//this.credit = 0.0;
}
// setter and getter方法
public void setCredit(double credit){
this.credit = credit;
}
public double getCredit(){
return credit;
}
}
2)super.属性名/方法
- 访问父类的属性和方法
super.
大部分情况下是可以省略的。super.
什么时候不能省略呢?
父中有,子中又有
,如果想在子中访问父的特征
,super.不能省略
。
在 父和子中 有同名的属性,或者说有相同的方法,
如果此时想在子类中访问父中的数据,必须使用super.
加以区分。
class Animal{
public void move(){
System.out.println("Animal move!");
}
}
class Cat extends Animal{
// 对move进行重写。
public void move(){
System.out.println("Cat move!");
}
// 单独编写一个子类特有的方法。
public void yiDong(){
this.move();//子类的move
//前面不写默认有this.
move();//子类的move
// super. 不仅可以访问属性,也可以访问方法。
super.move();//父类的move
}
}
________________________________________________________________
public class Super{
public static void main(String[] args){
Vip v = new Vip("张三");
v.shopping();
}
}
class Customer {
String name;
public Customer(){}
public Customer(String name){
super();
this.name = name;
}
public void doSome(){
System.out.println(this.name + " do some!");
System.out.println(name + " do some!");
//错误: 找不到符号
//System.out.println(super.name + " do some!");//Object类没有name属性
}
}
class Vip extends Customer{
// 假设子类也有一个同名属性
// java中允许在子类中出现和父类一样的同名变量/同名属性。
String name; // 实例变量
public Vip(){
}
public Vip(String name){
super(name);
// this.name = null;
}
public void shopping(){
/*
java是怎么来区分子类和父类的同名属性的?
this.name:当前对象的name属性
super.name:当前对象的父类型特征中的name属性。
*/
System.out.println(this.name + "正在购物!"); // null 正在购物 子类的name
System.out.println(super.name + "正在购物!"); // 张三正在购物 父类的name
System.out.println(name + "正在购物!"); //null 正在购物 子类的name
}
}
3)super关键字和this关键字重要结论
- 当一个构造方法第一行:
既没有this()又没有super()的话
,默认
会有一个super()
;
通过当前子类的构造方法调用父类的无参数构造方法。所以必须保证父类的无参数构造方法是存在的。 - 在java语言中不管是是new什么对象,最后老祖宗的
Object类
的无参数构造方法
一定会执行。 - this() 和 super()
不能共存
,它们都是只能出现在构造方法第一行
。 - 无论是怎样折腾,
父类的构造方法
是一定会执行
的。
this
是引用
。this也保存内存地址,this也指向任何对象。super
不是引用
。super也不保存内存地址,super也不指向任何对象。
super 只是代表当前对象内部的那一块父类型的特征
。
public class SuperTest06 {
// 实例方法
public void doSome(){
// SuperTest06@2f92e0f4
System.out.println(this);
// 输出“引用”的时候,会自动调用引用的toString()方法。
//System.out.println(this.toString());
//编译错误: 需要'.'
//System.out.println(super);
}
// this和super不能使用在static静态方法中。
/*
public static void doOther(){
System.out.println(this);
System.out.println(super.xxx);
}
*/
// 静态方法,主方法
public static void main(String[] args){
SuperTest06 st = new SuperTest06();
st.doSome();
}
}
_______________________________________________________
public class Super{
public static void main(String[] args){
new C();
}
}
/*
class Object{
public Object(){
}
}
*/
//不写默认继承Object
class A /*extends Object*/{
public A(){
//默认有super();不写默认存在;调用Object构造方法
super();
System.out.println("1"); //1
}
}
class B extends A{
public B(){
//默认有super();不写默认存在;调用Object构造方法
super();
System.out.println("2"); //2
}
public B(String name){
super();
System.out.println("3"); // 3
}
}
class C extends B{
public C(){ // 这个是最先调用的。但是最后结束。
this("zhangsan");
System.out.println("4");//4
}
public C(String name){
this(name, 20);
System.out.println("5");//5
}
public C(String name, int age){
super(name);
System.out.println("6");//6
}
}
3.3 instanceof关键字
- instanceof关键字,用于判断
某一个类是否继承自另一个类
,即验证两个类之间是否属于父类和子类的关系
。 - instanceof关键字判断后是有
返回值
的且返回值为boolean类型
。 - 语法格式
boolean result= child instanceof parents
public class computer {
//我们将computer类作为父类,将它定义为电脑
public static void main(String[] args) {
//在父类中写一个主函数,来对子类进行实例化
Pad ipad = new Pad();
LenovoPad lenovopad = new LenovoPad();
//我们来利用instanceof关键字判断继承关系
System.out.println("Pad是否继承自computer类?"+(ipad instanceof computer));
//判断Pad是否继承自computer类
System.out.println("LenovoPad是否继承自Pad类?"+(lenovopad instanceof Pad));
//判断LenovoPad是否继承自Pad类
System.out.println("LenovoPad是否继承自computer类?"+(lenovopad instanceof computer));
//判断LenovoPad是否继承自computer类
/*由第三条判断可以知道,instanceof关键字不仅能够判断子类是否继承父类,还能跨越级别进行判断
* 因此我们也可以举例判断这些类继承Object类(所有类的父类)如下:
*/
System.out.println("LenovoPad是否继承自Object类?"+(lenovopad instanceof Object));
}
}
class Pad extends computer{
//写一个子类Pad继承computer类,定义为平板电脑
}
class LenovoPad extends Pad{
//再写一个子类继承Pad类,定义为联想平板电脑
}
4、抽象类和接口
对于面向对象编程来说,抽象是它的一大特征之一。在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类
。这两者有太多相似的地方,又有太多不同的地方。
4.1 抽象类
1)抽象方法
- 是一种特殊的方法:只有声明,而没有具体的实现。
- 声明格式
abstract void fun();//使用abstract关键字定义修饰符
2)抽象类
- 如果
一个类
含有抽象方法
,则称这个类为抽象类
- 抽象类必须在
类前
用abstract
关键字修饰
。 - 因为抽象类中含有无具体实现的方法,所以
不能
用抽象类创建对象
。
将抽象类定义为“包含抽象方法的类” ——《JAVA编程思想》
- 语法格式
[public] abstract class ClassName {
abstract void fun();
}
- 从这里可以看出,抽象类就是为了
继承
而存在
的 - 实际需求
对于一个父类,如果它的某个方法
在父类
中实现出来没有任何意
义,必须根据子类的实际需求来进行不同的实现
,那么就可以将这个方法声明为abstract方法,此时这个类
也就成为abstract类
了。 - 包含抽象方法的类称为抽象类,但
并不意味
着抽象类中只能有抽象方法
,它和普通类一样,同样可以拥有成员变量
和普通的成员方法
。 - 与普通类的区别
抽象方法必须为public或者protected(
因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public
。
抽象类不能
用来创建对象
;
如果一个类继承于一个抽象类
,则子类必须实现
父类的抽象方法
。如果子类没有实现
父类的抽象方法
,则必须将子类
也定义为abstract类
。
4.2 接口
接口,英文称作interface
,在软件工程中,接口泛指供别人调用的方法或者函数
。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象
。
- 语法格式
[public] interface InterfaceName {
}
- 接口中可以含有 变量和方法
接口中的变量
会被隐式地指定为public static final变量
(并且只能是public static final变量,用private修饰会报编译错误)
方法
会被隐式地指定为public abstract方法
且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误)
接口中所有的方法不能有具体的实现
,也就是说,接口中的方法必须都是抽象方法
。 - 要让一个类遵循某组特地的接口需要使用
implements
关键字,具体格式如下
class ClassName implements Interface1,Interface2,[....]{
}
- 可以看出
允许一个类
实现多个特定的接口
【实现多继承】
如果一个非抽象类
遵循了某个接口,就必须实现
该接口
中的所有抽象方法
。
对于遵循某个接口的抽象类
,可以不实现
该接口
中的抽象方法
。
4.3 抽象类和接口的区别
1)语法层面
抽象类
可以提供成员方法的实现细节
,而接口
中只能
存在public abstract 方法
;抽象类
中的成员变量
可以是各种类型
的,而接口中的成员变量
只能是public static final
类型的;接口
中不能
含有静态代码块
以及静态方法
,而抽象类
可以有静态代码块
和静态方法
;一个类
只能继承一个抽象类
,而一个类
却可以实现多个接口
。
抽象类用继承
,接口用实现
2)设计层面
- 抽象类是对一种
事物的抽象
,即对类抽象,而接口是对行为的抽象
。 - 抽象类是对
整个类整体进行抽象
,包括属性、行为,但是接口却是对类局部(行为)进行抽象
。
飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。
那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,
但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。
此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),
然后Airplane和Bird分别根据自己的需要实现Fly这个接口。
然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,
对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,
继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。
如果一个类继承了某个抽象类,则子类必定是抽象类的种类,
而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),
能飞行则可以实现这个接口,不能飞行就不实现这个接口。
- 对比表
接口比抽象类更加严格,但接口是实现多继承的方式。
参数 | 抽象类 | 接口 |
---|---|---|
默认的方法实现 | 它可以有默认的方法实现 | 接口完全是抽象的。它根本不存在方法的实现 |
实现 | 子类使用extends 关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字implements 来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
与正常Java类的区别 | 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 | 接口是完全不同的类型 |
访问修饰符 | 抽象方法可以有public、protected和default这些修饰符 | 接口方法默认修饰符是public。你不可以使用其它修饰符。 |
main方法 | 抽象方法可以有main方法并且我们可以运行它 | 接口没有main方法,因此我们不能运行它。 |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现 。因此你不需要改变你现在的代码。 | 如果你往接口中添加方法,那么你必须改变实现该接口的类 。 |