Java SE 03(数组、面向对象)
Java SE 03
一、数组
数组的特点
- 数组是一种引用数据类型
- 数组当中的多个数据,类型必须统一
- 数组的长度在运行期间不可以改变
- 直接打印数组名得到的是内存地址的哈希值
首先必须声明数组变量, 才能在程序中使用数组
dataType[] arrayRefVar;//首选的方法
dataType arrayRefVar[];//效果相同,但不是首选的方法
Java语言使用new操作符来创建数组
dataType[] arrayRefVar = new dataType[arraySize];
获取数组的长度
数组一旦创建,程序运行期间,长度不可改变
arrayRefVar.length
数组的三种初始化
-
静态初始化(指定内容)
-
动态初始化(指定长度)
-
默认初始化
-
数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配,其中的每个元素也被按照实例变量同样的方式被隐式初始化。
-
使用动态初始化数组时,元素会拥有一个默认值,规则如下:
如果是整数类型,默认值为0
如果是浮点类型,默认值为0.0
如果是字符类型,默认值为'\u0000'
如果是布尔类型,默认值是false
如果是引用类型,默认值是null
-
静态初始化的过程中也有默认值的过程,只不过系统自动马上将默认值替换为了大括号中的具体数值
-
-
注意事项:
-
静态初始化没有直接指定长度,但是仍然会直接推算得到长度
-
静态初始化基本格式可以拆分成两个步骤
int[] a; a = new int[]{5, 15, 25, 30}; -
动态初始化也可以拆分成两个步骤
int[] a; a = new int[10]; -
静态初始化一旦使用省略格式,就不能拆分成两个步骤了
-
-
使用建议:若不确定数组的具体内容用动态初始化,确定数组的具体内容,用静态初始化
//静态初始化:创建+赋值
int[] a = {1, 2, 3, 4, 5};//数组元素为基本类型
Man[] mans = {new Man(), new Man()};//数组元素为引用类型
int[] arrayA = new int[] {5, 15, 25, 30};//基本格式的静态初始化,右边new int[]可省
String[] arrayB = new String[] {"Apple", "Banana", "Peach"};//基本格式的静态初始化, 右边new String[]可省
//动态初始化:包含默认初始化
int[] b = new int[10];
b[0] = 1;
System.out.println(b[2]); //output: 0 默认初始化
数组的基本特点
- 数组中的元素可以是任何数据类型,包括基本类型和引用类型。
- 数组变量类型属于引用类型,数组变量也可以看作是对象,数组中的每个元素相当于该对象的成员。数组本身就是对象,Java的对象是在堆中, 因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中。
打印一个二维数组
package ArrayDemo;
public class demo03 {
public static void main(String[] args) {
int[][] arr = {{1, 2}, {2, 3}, {4, 5}};
arrPrint(arr);
}
public static void arrPrint(int[][] arr) {
for(int i = 0; i < arr.length; i++){
for(int j = 0; j < arr[i].length; j++){
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
}
}
/*
output:
1 2
2 3
4 5
*/
Java内存划分成5部分
-
栈(Stack):存放的是方法中的局部变量。方法的运行一定要在栈上运行。
局部变量:方法的参数,或者是方法{}内部的变量
作用域:一旦超出作用域,立刻从栈中消失
-
堆(Heap): 凡是new出来的东西,都在堆上
堆内存里面的东西都有一个地址值: 16进制
堆内存中的数据,都有默认值,规则:
如果是整数类型,默认值为0
如果是浮点类型,默认值为0.0
如果是字符类型,默认值为'\u0000'
如果是布尔类型,默认值是false
如果是引用类型,默认值是null
-
方法区(Method Area):存储.class相关信息,包含方法的信息
-
本地方法栈(Native Method Stack):与操作系统相关
-
寄存器(PC Register):与CPU相关
Arrays类
- 数组的工具类java.util.Arrays,这是一个与数组相关的工具类,里面提供了大量的静态方法,用来实现数组常见的操作。
- 常用方法:
- public static String toString(数组):将参数数组转换成字符串(格式:[element1, element2, element3...])
- public static void sort(数组):按照默认的升序对数组的元素进行排序
- 如果是数值,sort默认按照升序从小到大
- 如果是字符串,sort默认按照字母升序
- 如果是自定义的类型,这个自定义的类型需要有Comparable或者Comparator接口
- 数组对象本身没有什么方法可以供我们使用,API中提供了一个工具类Arrays供我们使用,从而对数据对象进行一些基本的操作。
- 查看JDK帮助文档
- Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,“不用”使用对象来调用(是“不用”而不是“不能”)
- 常用功能:
- 对数组赋值:通过fill方法
- 对数组排序:通过sort方法,按升序
- 比较数组:通过equals方法比较数组中的元素值是否相等
- 查找数组元素:通过binarySearch方法对排序好的数组进行二分查找的操作
冒泡排序
package ArrayDemo;
import java.util.Arrays;
public class demo04 {
public static void main(String[] args) {
int[] arr = {2, 4, 1, 6, 5, 11, 3};
System.out.println(Arrays.toString(arr));
System.out.println(Arrays.toString(bubbleSort(arr)));
}
public static int[] bubbleSort(int[] arr) {
int temp = 0;
for(int i = 0; i < arr.length-1; i++) {
boolean flag = false;
for (int j = 0; j < arr.length-i-1; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
}
}
if(flag == false){
break;
}
}
return arr;
}
}
/*
output:
[2, 4, 1, 6, 5, 11, 3]
[1, 2, 3, 4, 5, 6, 11]
*/
稀疏数组生成与还原
public class demo04 {
public static void main(String[] args) {
int[][] arr = new int[10][10];
arr[5][6] = 1;
arr[8][7] = 2;
arr[9][1] = 2;
printArr(arr);
printArr(toSparseMatrix(arr));
printArr(toNormalMatrix(toSparseMatrix(arr)));
}
public static void printArr(int[][] arr) {
for (int[] ints : arr) {
for (int ints1 : ints) {
System.out.print(ints1 + "\t");
}
System.out.println();
}
}
public static int[][] toSparseMatrix(int[][] arr) {
int cnt = 0;
for(int i = 0; i < arr.length; i++) {
for(int j = 0; j < arr[i].length; j++) {
if(arr[i][j] != 0) {
cnt++;
}
}
}
int[][] newArr = new int[cnt+1][3];
newArr[0][0] = arr.length;
newArr[0][1] = arr[0].length;
newArr[0][2] = cnt;
int newArrCol = 0;
for(int i = 0; i < arr.length; i++) {
for(int j = 0; j < arr[i].length; j++) {
if(arr[i][j] != 0) {
newArrCol++;
newArr[newArrCol][2] = arr[i][j];
newArr[newArrCol][0] = i;
newArr[newArrCol][1] = j;
}
}
}
//System.out.println(cnt);
return newArr;
}
public static int[][] toNormalMatrix(int[][] arr) {
int[][] newArr = new int[arr[0][0]][arr[0][1]];
for(int i = 1; i < arr.length; i++) {
newArr[arr[i][0]][arr[i][1]] = arr[i][2];
}
return newArr;
}
}
/*
output:
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 0 0
0 2 0 0 0 0 0 0 0 0
10 10 3
5 6 1
8 7 2
9 1 2
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 0 0
0 2 0 0 0 0 0 0 0 0
*/
二、面向对象
导包、创建和使用对象
通常情况下,一个类并不能直接使用,需要根据类创建一个对象,才能使用
-
导包:也就是指出需要使用的类,在什么位置
import 包名称.类名称;
对于和当前类属于同一个包的情况,可以省略导包语句不写。
只有java.lang包下的内容不需要导包,其他的包都需要import语句
-
创建格式:
类名称 对象名 = new 类名称();
Student stu = new Student();
-
使用:分两种情况
使用成员变量:对象名.成员变量名
使用成员方法:对象名.成员方法名(参数)
注意事项:
如果成员变量没有进行赋值,那么将会有一个默认值,规则和数组一样
如果是整数类型,默认值为0
如果是浮点类型,默认值为0.0
如果是字符类型,默认值为'\u0000'
如果是布尔类型,默认值是false
如果是引用类型,默认值是null
使用对象类型作为方法的参数和返回值
- 当使用一个对象(引用类型)作为方法的返回值类型时,返回值其实就是对象的地址值
- 当使用一个对象(引用类型)作为方法的参数类型时,参数值其实就是对象的地址值
局部变量和成员变量
区别:
-
定义的位置不一样
局部变量在方法内,成员变量在类的内部且方法的外部
-
作用范围不一样
局部变量作用域是方法内,成员变量作用域是类的内部
-
初始值不一样
局部变量定义后必须要赋初始值,否则error,不存在自动初始值
成员变量如过没有手动赋初值,初始值为其类型的默认值,规则和数组相同
-
内存的位置不一样(程序运行时)
局部变量:方法在栈上运行,局部变量也是在栈内存上
成员变量:成员变量属于类,类新建对象后,对象在堆上,成员变量也是在堆内存上
-
生命周期不一样
局部变量:随方法进栈而产生,随着方法出栈而消失
成员变量:随着对象创建而诞生,随着对象被JVM垃圾回收而消失
方法的调用
-
静态方法(加static)和非静态方法
- 静态方法和非静态方法都可以写在与main方法平行的类中或是其他文件的类中,非静态方法使用时要先生成对象才可调用,静态方法在main方法平行的类中可以直接调用,静态方法在其他文件的类中不能直接调用,要先生成对象才可调用这个非静态方法。
- 静态方法(加static)是和类一起加载的,非静态方法是和对象一起加载的(类实例化后才可使用)
-
值传递和引用传递
-
Java一般是值传递的
-
引用传递一般是传递一个对象,但本质还是值传递
-
生成对象的过程中使用new返回一个对象(本质是一个指针变量),传入方法(函数)
中可以修改其成员变量的值,而像基本数据类型传入方法(函数中)拷贝到形参等方法结束时形参也消失了所以不能改变其值
-
类与对象的创建
-
使用new关键字创建对象
-
使用new关键字创建的时候,除了分配内存空间外,还会给创建好的对象进行默认初始化(String类型-->null, int类型-->0)以及对类中构造器的调用
-
类中的构造器也成为构造方法,是在进行创建对象的时候必须要调用的。(使用new关键字本质是在调用构造器,必须要有构造器,没有会自动生成一个空的构造器)并且构造器有以下两个特点:
- 必须和类的名字相同
- 必须没有返回值类型,也不能写void
-
一个类即使什么都不写,它也会存在一个方法(无参构造器)
无参构造器的作用:实例化初始值(可选)
public class Person { String name; public Person() { this.name = "Jeff";//this指针表示指向当前类的指针变量 } }public class Application { public static void main(String[] args) { Person p1 = new Person(); System.out.println(p1.name);//没有构造函数的初始化输出为null, 有初始化输出jeff } } -
有参构造器(构造器的重载):一旦定义了有参构造,无参就必须显示定义
public class Person { String name; public Person() { } public Person(String nameStr) { this.name = nameStr; } }public class Application { public static void main(String[] args) { Person p1 = new Person("Jeff"); System.out.println(p1.name);//output: jeff } } -
构造器的主要作用:
- 用来初始化对象的值
- new本质是在调用构造方法
- 定义有参构造之后,如果想使用无参构造,显式定义一个无参构造器
封装
-
封装在Java中的体现:
- 方法就是一种封装
- 关键字private就是一种封装
-
“高内聚,低耦合”: 高内聚就是类的内部数据操作细节自己完成,不允许外部的干涉;低耦合:仅暴露少量的方法给外部使用
-
封装的解释:通常,应禁止直接访问一个对象数据的实际表示,而应通过操作接口来表示,称为信息隐藏
-
封装的核心:属性私有(成员变量使用private修饰),get/set(类中使用getXxx获取属性,SetXxx设置属性值,Xxx为属性名(成员变量名))
-
封装的作用:
- 提高程序的安全性
- 隐藏代码的实现细节
- 统一接口
- 系统可维护性增加
-
一旦使用了private进行修饰,那么本类中仍然可以随意访问,但是超出本类范围之外就不能再访问了(在对象中不可访问了),只能通过成员方法间接访问getXxx/SetXxx
-
对于getter来说,不能有参数,返回值类型和成员变量对应;
对于setter来说,不能有返回值,参数类型与成员变量对应;
对于基本类型中的boolean值,getter方法一定要写成isXxx的形式,而setXxx规则不变
-
示例:
package Oop; public class Demo02 { private String name;//属性私有 private int age; public Demo02() { this.name = "Taco"; } public Demo02(String nameStr) { this.name = nameStr; } public void setName(String name)// 设置属性 this.name = name; } public void setAge(int age) { if(age > 120 || age < 0){ this.age = 3; } else { this.age = age; } } public void getName() {//获取属性 System.out.println(this.name); } public void getAge() { System.out.println(this.age); } }
this关键字
super关键字用来访问父类的内容,this关键字用来访问本类的内容
- this关键字的三种用法
- 在本类的成员方法中,访问本类的成员变量(无同名变量可省略)
- 在本类的成员方法中,访问本类的另一个成员方法(只是强调调用的方法是本类的,可省略)
- 在本类的构造方法中,访问本类的另一个构造方法(不可省略,一般是有参构造方法中用this();或无参构造方法中用this(args);有参构造方法中用this(args)😉,一个构造方法中只能调用一次this且必须是第一个
- super和this两种构造调用,不能同时使用
- this在多种重载的构造方法中调用不能形成循环调用
作用:重名情况下,起到区分的效果
当方法的局部变量和类的成员变量重名时,根据"就近原则", 优先使用局部变量。
如果需要访问本类当中的成员变量,需用格式:this.成员变量
this本质:是成员方法的隐式形参,传入对象的地址,存放了对象的地址值
注意:this一定是写在类内的某个方法中,不可写在方法的外部
public class Person2 {
private int age;
public void setAge(int age) {
this.age = age;
System.out.println(this);//output: JavaBasics.Day06OOP.Person2@1b6d3586
}
public void compareAge(int age) {//参数列表的参数也属于局部变量
if(age > this.age) {//这里age是传入的参数是局部变量,this.age是类的成员变量
System.out.println("you entered a bigger age");
} else {
System.out.println("you entered the same age or smaller age");
}
}
}
public static void main(String[] args) {
Person2 person = new Person2();
person.setAge(15);
person.compareAge(20);
System.out.println(person);//output: JavaBasics.Day06OOP.Person2@1b6d3586
}
this代表当前对象
public class Person {
private int age;
private String name;
public Person() {
System.out.println("this " + this);
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo10 {
public static void main(String[] args) {
Person person = new Person();//this JavaBasics.Day08StringStaticArraysMath.Person@1b6d3586
System.out.println("person " + person);//person JavaBasics.Day08StringStaticArraysMath.Person@1b6d3586
}
}
构造方法
-
构造方法是专门用来创建对象的方法,当我们通过关键字new来创建对象的时候,其实就在调用构造方法
-
格式:
public 类名称(参数类型 参数名称){
方法体
}
-
注意事项:
- 构造方法的名称必须和所在类的名称完全一样,就连大小写也一样
- 构造方法不要写返回值类型,就连void也不能写
- 构造方法不能return返回值
- 如果没有编写构造函数,编译器会默认生成一个,没有参数、方法体什么也不做
- 一旦编写了至少一个构造方法,编译器将不再生成
- 写了有参构造方法,建议把无参构造方法也加上,编译器不会在这种情况下加无参构造方法
- 构造方法也可以重载
一个标准的类
标准的类的组成:
- 所有的成员变量都要使用private关键字修饰
- 为每一个成员变量编写一对儿Getter/Setter方法
- 编写一个无参构造方法
- 编写一个全参构造方法
idea快捷键:在写完成员变量后,可以使用alt + Insert添加无参构造方法、全参构造方法、所有成员变量的getter/setter
这样的标准类也叫做Java Bean
匿名对象
-
匿名对象就是只有右边的对象,没有左边的名字和赋值运算符
-
格式:new 类名称();
-
匿名对象只能使用唯一的一次,下次再用不得不再创建新的对象。
-
使用建议:如果确定有一个对象只需要使用唯一的一次,就可以使用匿名对象
new Person().name = "Kim"; new Person().showName(); -
匿名对象也可以作为方法的对象的和返回值
public class Anonymous { public static void main(String[] args) { int num = anAsRet().nextInt(); System.out.println(num); anAsArg(new Scanner(System.in)); } public static Scanner anAsRet() { return new Scanner(System.in); } public static void anAsArg(Scanner sc) { int num = sc.nextInt(); System.out.println(num); } }
Random类
Random用来产生随机数字, 用法和Scanner类似,nextXxx成员方法可支持无参和有参
import java.util.Random;
public class Demo02 {
public static void main(String[] args) {
Random rdNum = new Random();
System.out.println("random num1 is :" + rdNum.nextInt());
Random rdNum2 = new Random();
System.out.println("random num2 is :" + rdNum2.nextInt(15));
}
}
对象数组
-
对象数组中每个元素为某个类的对象
-
对象数组的初始化和使用示例
public class Demo04 { public static void main(String[] args) { Person[] per = new Person[10]; Person person01 = new Person("Kim"); Person person02 = new Person("Jeff"); per[0] = person01; per[1] = person02; per[0].showName(); per[1].showName(); } }
ArrayList集合
-
数组的长度不可以发生改变,ArrayList集合的长度是可以随意变化的
-
对于ArrayList来说,有一个尖括号
代表泛型 泛型:装在集合中的所有元素,全都是统一的什么类型
注意:泛型只能是引用类型,不能是基本类型
-
从JDK1.7+开始,右侧尖括号内部可以不写内容,但是左边必须要写
-
对于ArrayList集合,直接打印得到的不是地址值,而是内容,若内容为空得到的是空的中括号
public class Demo05 { public static void main(String[] args) { ArrayList<String> al = new ArrayList<>(); System.out.println(al); al.add("Hello"); al.add("Beach"); System.out.println(al); } } /*output: [] [Hello, Beach] */ -
ArrayList当中的常用方法有:
-
public boolean add(E e):
向集合中添加元素,参数类型和泛型相同,返回值代表添加是否成功。对于ArrayList集合来说,add添加动作一定是成功的,所以返回值可用可不用。但是对于其他集合来说,add添加动作不一定成功。
-
public E get(int index):
从集合中获得元素,参数是索引编号,返回值是对应位置的元素。
-
public E remove(int index):
从集合当中删除元素,参数是索引编号,返回值是被删除的元素。
-
public int size():
获取集合的尺寸长度,返回值是集合中包含的元素个数。
-
-
如果希望向集合ArrrayList当中存储基本类型,必须使用基本类型对应的包装类
基本类型 包装类型(引用类型,包装类都位于java.lang包下)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
从JDK1.5+ 开始,支持自动装箱、自动拆箱
自动装箱: 基本类型->包装类型
自动拆箱:包装类型->基本类型
-
ArrayList
是引用类型可以做为方法的参数和返回值,传递和返回的是对象的地址
String类
-
java.lang.String类代表字符串
-
Java程序中所有的双引号字符串,都是String类的对象,(就算没有new,也照样是)
-
字符串特点
- 字符串的内容永不可变
- 正是因为字符串不可改变,所以字符串是可以共享使用的
- 字符串效果上相当于是char[]字符数组,但底层原理是byte[]字节数组
-
创建字符串的常见3+1种方法
三种构造方法:
public String()创建一个空白字符串,不含有任何的内容
public String(char[] array) //根据字符数组的内容创建对应的字符串
public String(byte[] array) //根据字节数组的内容创建对应的字符串
一种直接创建:
String str = "Hello" //右边直接用双引号
注意:直接写上双引号就是字符串对象
public static void main(String[] args) { String str1 = new String(); System.out.println(str1);//输出空字符,相当于"" char[] arr = new char[] {'a', 'b', 'c'}; String str2 = new String(arr); System.out.println(str2);//输出abc byte[] arr2 = {97, 98, 99}; String str3 = new String(arr2); System.out.println(str3);//输出abc String str4 = "hello"; System.out.println(str4);//输出hello } -
字符串的常量池
- 对于引用类型来说,==进行的是地址值的比较
- 双引号直接写的字符串在常量池当中,new的不在池当中
-
字符串比较方法
==是进行对象的地址值比较,如果确实需要字符串内容的比较,可以使用两个方法:
-
public boolean equals(Object obj):参数可以是任何的对象,只有参数是一个字符串并且内容相同的才会给true;否则返回false
注意事项:
-
任何对象都能用Object进行接收
-
equals方法具有对称性,也就是a.equals(b)和b.equals(a)效果一样
-
如果比较双方一个是常量一个是变量,推荐把常量字符串写在前面
推荐:“abc”.equals(str) 不推荐: str.equals("abc")
若变量str是null则str.equals会报空指针异常
-
-
public boolean equalsIgnoreCase(Object obj):忽略大小写,进行内容比较
-
-
字符串获取相关常用方法
public int length() 获取字符串当中含有的字符个数,拿到字符串长度
public String concat(String str) 将当前字符串和参数字符串拼接成为返回值新的字符串
作用等同于两个字符串相加
public char charAt(int index) 获取指定索引位置的单个字符(索引从0开始)
public int indexof(String str) 查找参数字符串在本字符串当中首次出现的索引位置,若没有返回-1
-
字符串的截取方法
public String substring(int index): 截取从参数位置一直到字符串的末尾,返回新的字符串,包含开始的index这个字符
public String substring(int begin, int end):截取从begin开始,一直到end结束,返回中间这段字符串,左闭右开,包含左边不包含右边
-
字符串的内容在String方法使用后是不可改变的,一般是返回了一个新的字符串
-
字符串转换相关的常用方法
-
public char[] toCharArray() 将字符串转换成字符数组作为为返回值
-
public byte[] getBytes() 获得当前字符串底层的byte数组
-
public String replace(CharSequence oldString, CharSequence newString) 将所有出现的老的字符串替换成新的字符串,返回替换之后的结果新字符串
CharSequence是一个接口,可以接收字符串
-
-
字符串分割的常用方法
public String[] split(String regex) 按照参数的规则,将字符串切分成若干部分,返回值是切分后的String数组
注意:split方法的参数其实是一个正则表达式,若以"."为切分规则,必须写"\\."(反斜杠)
数学工具类Math
-
java.util.Math类是数学相关的工具类,里面提供了大量的静态方法,可以完成相关的数学运算操作。
-
常用方法:
- public static double abs(double num):获取绝对值
- public static double ceil(double num):向数轴正方向
- public static double floor(double num):向数轴负方向
- public static long round(double num):四舍五入
-
常用常量:
- Math.PI是近似圆周率常量(double)
继承
-
继承的本质是对一批类的扩展,从而实现对现实世界的建模
-
extends是继承的关键字,子类是父类的扩展
-
Java中类只有单继承,没有多继承(一个父类可有多个子类,一个子类只能有一个父类)
-
- 继承是类和类之间的一种关系。除此之外,类和类之间还有依赖、组合、聚合等
- 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类用关键字extends来表示。
- 子类和父类之间,从意义上讲应该有"is a"的关系,例如父类是员工,子类是老师,那么“老师就是一个员工”
- 继承关系中,子类就是一个父类,子类可以被当作父类看待,子类继承父类就会拥有父类的全部方法
- 在Java中,所有的类,都默认直接或间接继承Object类, 所以直接可以继承一个,间接可以继承多个,例如Object->Person->Student
-
示例
//父类 public class Person { }//子类 public class Teacher extends Person { }//子类 public class Student extends Person { } -
继承关系当中的特点
- 子类可以拥有父类的内容
- 子类还可以拥有自己专有的内容
-
继承中成员变量的访问特点
-
若成员变量不重名,则子类对象可访问父类方法(除构造方法,但可以用到父类构造方法生成的成员变量值),子类对象可访问父类变量(只能是public变量)
-
若成员变量重名:
-
直接通过子类对象访问成员变量:等号左边是谁(对象的类型),就优先用谁,没有则向上找
(前提条件是要访问的成员变量都是public的,private的无法直接方法,只能靠成员方法间接访问)
public class Application { public static void main(String[] args) { //new一个子类,可以是父类型的对象,也可以是子类型的变量(子类包含父类所有内容) Parent child = new Child(); Child child2 = new Child(); System.out.println(child.birthDate);//获取的成员变量是父类类型的对象下的 System.out.println(child2.birthDate);//获取的成员变量是子类类型的对象下的 //new一个父类,只能是父类型的对象 Parent parent = new Parent(); //Child parent2 = new Parent();//Error System.out.println(parent.birthDate);//获取的成员变量是父类类型的对象下的 } } -
间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找
(例子:父类和子类的成员变量age重名,父子类都有一个xxxPrintAge)
public class Application { public static void main(String[] args) { Parent parent = new Parent(); parent.parentPrintAge();//该方法是Parent下的(方法属于父),使用父类下的成员变量 parent.childPrintAge();//Error,父类对象不能调用子类独有的成员方法 Child child = new Child(); child.parentPrintAge();//该方法是Parent下的(方法属于父),使用父类下的成员变量 child.childPrintAge();//该方法是Child下的(方法属于子),使用子类下的成员变量 } }
-
-
-
子类方法中父类成员变量、子类成员变量、子类方法局部变量三种重名解决方法:
-
super.父类成员变量
-
this.子类成员变量
-
子类方法局部变量直接写
public class Child extends Parent{ int age; private String name; int salary; String birthDate; public void childParentLocalAge() { int age = 0; System.out.println(this.age); System.out.println(super.age); } } -
-
继承中成员方法的访问特点
-
成员方法不重名:
子类对象可以直接访问父类的成员方法和子类的成员方法,父类对象只能访问父类的成员方法
-
成员方法重名:
创建的对象(new后面)是谁就优先用谁的成员方法,没有就向上找
//假设父子类中都有printInfo() public class Application { public static void main(String[] args) { Parent parent = new Parent(); parent.printInfo();//执行父类的成员方法 Child child = new Child(); child.printInfo();//执行子类的成员方法,若子类中没有printInfo(),就执行父类中的该方法 Parent child2 = new Child(); child.printInfo();//执行子类的成员方法,若子类中没有printInfo(),就执行父类中的该方法 } }
注意事项:
无论是成员方法还是成员变量,如果没有都是向上找父类的,不会向下找子类的
-
-
继承关系中,父子类构造方法的访问特点
- 子类构造方法当中有一个默认隐含的“super()”调用,新建子类的对象时,先调用父类构造,后执行子类的构造
- 在子类构造方法中可以通过super关键字调用父类重载(Overload)构造,super(args)
- super的父类构造调用,必须是子类构造方法的第一个语句
- super的父类构造调用super(),只能在子类构造方法中不能在子类其他方法中,不能一个子类构造调用多次super()
总结:子类必须调用父类的构造方法,不写默认生成一个super();写了则用指定的super();调用,super();只能有一个,还必须是第一个
-
Java继承的三个特点:

super关键字
-
super关键字的三种用法:
- 在子类的成员方法中,访问父类的成员变量
- 在子类的成员方法中,访问父类的成员方法
- 在子类的构造方法中,访问父类的构造方法
-
super调用父类的构造方法,必须在构造方法的第一个
public class Demo02 { protected String name; private int age; public Demo02() { System.out.println("Demo02"); } }public class Demo03 extends Demo02{ public Demo03(){ super();//这里super()默认生成也可省略不写,但不能与子类的构造函数语句调换顺序 System.out.println("Demo03");//这里是子类构造函数的内容,不能与上句的父类的构造super()调换顺序,先要执行父类的构造函数 } }public class Application { public static void main(String[] args) { Demo03 s1 = new Demo03(); } } /* output: Demo02 Demo03 输出顺序是先进入了父类的构造函数 然后才是子类的构造函数 */ -
super必须只能出现在子类的方法或构造方法中
-
super和this不能同时调用构造方法
-
this和super的区别
-
代表的对象不同:
this: 本身调用者这个对象
super: 代表父类对象的引用
-
前提
this: 在没有继承情况下也可使用
super: 只能在继承条件下使用
-
构造方法:
this() :本类的构造
super() :父类的构造
-
-
this和super的内存图解

方法重写Override
-
重写(Override)概念:继承关系中,方法的名称一样,参数列表也一样,也叫覆写、覆盖
方法重写的特点:创建子类的对象,就优先用子类的方法
-
重载(Overload)概念:方法名称一样,参数列表不一样,和重写是完全两个概念
-
重写都是方法的重写和属性无关
-
重写:在有继承关系的前提下,在子类中重写父类的方法
-
静态方法在子类中不能进行重写,方法调用只和左边,定义的数据类型有关
非静态方法可以进行重写,是否是静态方法主要决定了该方法能否重写
-
重写的条件:
-
需要有继承关系,子类中重写父类的方法
-
父子类的方法名称相同,参数列表必须相同
-
修饰符:范围可以扩大: public > protected > (default) > private
(default)不是关键字,是什么都不写,留空的状态
父类方法public - 子类方法private-(X)
-
抛出的异常:范围,可以被缩小,但不能扩大
-
-
重写的结果:子类和父类的实现的方法一致,但方法体不同
-
重写的意义:父类的有些方法,子类不需要,就需要进行重写
-
idea快捷键:Alt + Insert :
@override注解可写可不写,注解用来安全检测是否有覆盖重写
-
注意事项:
-
子类方法的返回值必须小于等于父类方法的返回值范围
假设父类方法返回值是Object, 子类方法返回值可以是Object或Object子类、间接子类如String,但子类方法的返回值不能是void
-
子类方法的权限必须大于等于父类方法的权限
父类方法public - 子类方法private-(X)
-
实例:
public class Demo02 { public void print() {//非静态方法 System.out.println("this is d02"); } }public class Demo03 extends Demo02{ @Override//注解是有功能的注释,不写不影响功能,但推荐写 public void print() {//非静态方法可以重写 System.out.println("this is d03"); } }public class Application { public static void main(String[] args) { Demo03 s1 = new Demo03(); s1.print();//output: this is d03 Demo02 s2 = new Demo03(); s2.print();//output: this is d03, 若此处未进行重写,则方法调用只和左边定义的数据类型相关输出this is d02 } }
多态
-
多态:同一个方法可以根据发送对象的不同而采用多种不同的行为方式
对象的多态:一个对象拥有多种形态
-
多态的前提是:有extends继承或implements实现
-
代码中体现多态性:父类引用指向子类对象
格式:父类名称 对象名 = new 子类名();或接口名称 对象名 = new 实现类名称();
public class Application2 { public static void main(String[] args) { //没有使用多态,但可以访问重写父类的方法和子类独有方法。 Cat cat2 = new Cat(); cat2.printInfo(); cat2.printCat(); Dog dog2 = new Dog(); dog2.printInfo(); dog2.printDog(); System.out.println("=============="); //对象的向上转型实现多态,父类重写的方法输出结果同上。不过这样生成的对象不能访问子类的独有方法。 Animal animal = new Animal(); animal.printInfo(); Animal cat = new Cat(); cat.printInfo(); //cat.printCat();//Error Animal dog = new Dog(); dog.printInfo(); //dog.printDog();//Error } } -
对象的向上转型就是多态的写法
格式:父类名称 对象名 = new 子类名();
含义:右侧创建一个子类的对象,把他当作父类来看待使用
注意事项:向上转型一定是安全的。
-
对象的向下转型,其实是还原的动作
格式:子类名称 对象名 = (子类名称)父类对象
含义:将父类对象,还原成本来的子类对象
注意事项:
- 向下转型不一定是安全的
- 必须保证对象本来创建对象类型和向下转换的类型一致
- 创建对象的时候如果和向下转换的类型不一致,就会精度损失,报错
public class Application2 { public static void main(String[] args) { Cat cat2 = new Cat(); cat2.printInfo(); Dog dog2 = new Dog(); dog2.printInfo(); System.out.println("=============="); Animal animal = new Animal(); animal.printInfo(); Animal cat = new Cat(); cat.printInfo(); //cat.printCat();//Error,printCat()是子类Cat独有的方法,不能用父类型的对象访问 Animal dog = new Dog(); dog.printInfo(); //dog.printDog();//Error,printDog()是子类Dog独有的方法,不能用父类型的对象访问 System.out.println("=============="); //对象的向下转型: Cat cat3 = (Cat)cat;//cat对象在上面是由new Cat()所创建的,所以这里可以转为Cat类型 //Animal -> Cat是高转低,需要强制类型转换 cat3.printInfo(); cat3.printCat(); Dog dog3 = (Dog)dog;//dog对象在上面是由new Dog()所创建的,所以这里可以转为Dog类型 //Animal -> Dog是高转低,需要强制类型转换 dog3.printInfo(); dog3.printDog(); } } -
一个对象的实际类型是确定的,但可以指向对象的引用有多种类型(引用一般指的是父类和有关系的类)
-
一个对象的实际类型是确定的new Student(), new Person()
可以指向的引用类型就不确定了:父类的引用指向子类
Student s1 = new student(), Student能调用的方法都是自己或者继承父类的
Person s2 = new Student(), Person父类型,可以指向子类,但是不能调用子类独有的方法
Object s3 = new Student()
-
多态的弊端:无法使用子类特有的内容(属性、方法)
解决方法:可以使用向下转型(强转),将父类对象,还原成本来的子类对象
-
注意事项:
- 多态是方法的多态,属性(成员变量)没有多态, 属性(成员变量)不能进行重写
- 父类和子类,有联系,若无关系则类型转换异常
- 多态存在的必要条件:要有继承关系,方法需要重写(不能实现重写的情况: 1. static方法(属于类,不属于实例), 2. final 常量, 3. private方法),父类的引用指向子类对象,实例 parent f1 = new child()
instanceof关键字和类型转换
-
问题:如何才能知道父类引用的对象,本来是什么子类
-
格式: 对象 instanceof 类名称
会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例
-
这将会得到一个boolean值结果,也就是判断前面的对象能不能当作后面类型的实例
-
boolean result = obj instanceof Class, 其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
-
子类转父类,是低转高,自动转换,但会丢失子类的原来的方法
Demo03 s6 = new Demo03();//生成一个子类对象 s6.go();//子类中的一个方法 Demo02 s66 = s6;//子转父类 s66.go();//无法调用,子类方法丢失父类转子类,是高转低,要强制转换,用简便写法可以省去中间变量
Demo02 s5 = new Demo03();//可以用父类引用指向一个生成的子类的对象 // Demo03 s51 = (Demo03)s5;//父转子,高转低,强制转换,中间引用一个临时变量,可以不改变原来的父类型变量 // s51.go(); ((Demo03)s5).go();//实际生成了临时的子类型引用变量,但是未写出,方便直接调用子类的方法 s5.say();//s5类型未改变,仍可调用父类方法
static关键字
-
一旦用了static关键字,那么这样的内容不再属于对象自己,而是属于类的,所以凡是本类的对象都会共享一份
-
如果一个成员变量使用了static关键字,那么这个变量不再属于对象自己,而是属于所在的类。多个对象共享同一份数据。
-
一旦使用了static修饰成员方法,那么就成了静态方法。静态方法不属于对象,而是属于类。如果没有static需要创建对象才能使用它。如果有static关键字,不需要创建对象,可以直接使用类名称来调用它。对于静态方法,可以使用对象名来调用也可以使用类名称(推荐)来调用。使用对象名调用的静态方法,经过javac编译过后也会被翻译为“类名称.静态方法名”。
-
无论是成员变量,还是成员方法,如果有了static,都推荐用类名称来调用
静态变量:类名称.静态变量
静态方法:类名称.静态方法()
注意:对于在本类之中的静态方法,可以省略类名称,直接写静态变量,或静态方法
-
注意事项
-
静态不能直接访问非静态,因为在内存中是先有静态,后有非静态的内容
public class Demo09 { private int age; private static String name; public void print() {//非静态方法中可以访问静态或非静态的变量 System.out.println(age); System.out.println(name); } public static void print2() {//静态方法中只能访问静态变量 System.out.println(age);//Error System.out.println(name); } public static void main(String[] args) { Demo09 demo09 = new Demo09(); demo09.print(); print2(); } } -
静态方法中不能用this,this代表当前对象,通过谁调用的方法,谁就是当前对象
-
-
静态static的内存图
根据类名称访问静态成员变量的时候,全程和对象没有关系,只和类有关系

-
静态变量和非静态变量
在类中被static修饰的静态变量,在main方法使用可以是:类名.静态变量,不用生成类的对象再使用(不用不等于不能);非静态变量在main方法中必须先new一个类的对象才可以使用: 类对象名.非静态变量
-
静态方法和非静态方法
在同一个类中的静态方法在main方法中可以直接调用静态方法名 或者 类名.静态方法名
非静态方法需要先new生成一个类对象,才能调用方法:类对象名.非静态方法名
在同一个类中,静态方法是和类一起加载的,所以在非静态方法中可以直接调用静态方法,反过来静态方法中不能直接调用非静态方法
public class Student { public void run() { go(); } public static void go() { System.out.println("go for trip") } public static void main(String[] args) { Student.go(); go(); Student stu = new Student(); stu.go(); stu.run(); } } -
代码块的执行顺序:静态代码块->匿名代码块->构造方法
静态代码块:第一次用到本类时,静态代码块唯一的执行一次,第二次生成对象,不再执行了
静态代码块的作用:用来一次性地对静态成员变量进行赋值
{ //匿名代码块,作用是赋初始值,它在构造方法之前产生 } static { //静态代码块,只执行一次,生成对象后,不会再执行 } public Person() { //构造方法 } -
静态导入包
import static java.lang.Math.random; import static java.lang.Math.PI; public class demo04 { public static void main(String[] args) { System.out.println(random()); System.out.println(PI); } } -
若一个类被final修饰了,它将不能用于继承了(不能当父类了)
抽象类
抽象方法:如果父类当中的方法不确定如何进行{}方法体实现,那么这就是一个抽象方法
具体实现:返回值类型前加abstract,去掉{},分号结束(格式类似C语言函数声明)
前提条件:抽象方法所在类必须是抽象类(在class之前写abstract)
public abstract class Demo01 {//抽象类
public abstract void printInfo();//抽象方法
public void printInfo2() {//普通成员方法
};
}
-
子类必须覆盖重写(override)抽象父类当中所有的抽象方法(重写抽象类的方法,子类重写的方法不加abstract修饰),否则,编译无法通过而报错。除非该子类也是抽象类。
-
子类重写覆盖抽象方法:去掉abstract关键字,加上方法体{}
-
不能new抽象类,必须使用一个子类去继承它
-
一个抽象类不一定含有抽象方法,只要保证抽象方法所在的类是抽象类即可。没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途(设计模式-适配器模式)
-
抽象类不能new实现,但可以有构造方法,是供子类创建对象时,用于初始化父类成员使用的
在子类的构造方法中有默认的super()用来访问父类的构造方法
-
抽象类中的抽象方法,只有方法名字,没有方法的实现
-
抽象类也是类,只能单继承,但接口可以多继承
-
抽象方法(加了abstract的方法)必须在抽象类中,抽象类还可以写普通的方法
public abstract class Action { public abstract void doSomething(); }
接口的定义与实现
-
接口中的所有定义的方法都是抽象的public abstract(可以省略不写),只写返回值和方法名参数列表,不写方法体
-
接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
这两个关键字可以选择性省略(省略两个或一个)
-
interface是接口的关键字,不用再写class,换成abstract后编译生成的字节码文件仍然是:.java-->.class
public interface 接口名称 { //接口内容 } -
接口中只有方法声明,不能存在方法的具体实现
-
类可以实现接口要用implements关键字,写法类似于子类继承父类的extends,extends只能实现单继承,implements可以实现多继承(给多个接口实现其方法,有implements必须要实现接口中的方法)
//接口不能直接使用,必须有一个实现类来实现该接口 public class 实现类名称 implements 接口名称 { } //idea快捷键,Alt+Enter接口名称自动添加重写的接口方法接口的实现类必须覆盖重写(override)接口的所有方法(同抽象类的子类),然后创建实现类的对象,进行使用。建议实现类的命名:接口名+impl
-
如果实现类并没有覆盖重写接口中的所有的抽象方法,那么这个实现类自己就必须是抽象方法
-
接口中可以除了声明方法,还可以定义变量,默认是public static final修饰的(可以省略),一般不推荐在接口中定义变量
-
接口不能被实例化,要使用接口中的抽象方法,要使用实现类进行实例化。接口中也没有构造方法
-
如果是Java7,接口中可以包含:
- 常量
- 抽象方法
如果是Java8,还可以额外包含:
- 默认方法
- 静态方法
如果是Java9,还可以额外包含:
- 私有方法
-
Java8中的默认方法
格式:public default 返回值类型 方法名称(参数列表){方法体}
public可省略,default不能省略
作用:解决接口升级的问题。问题:当接口中新添加了抽象方法,实现类中会有没有重写该方法的报错
解决:把添加的方法从abstract改成default
//在接口中与抽象方法不同,可以写方法体 public default void methodDefault() { ... }默认方法实现:默认方法会被实现类继承,所以使用实现类的对象调用来使用默认方法。
注意:接口的默认方法,也可以被接口的实现类进行覆盖重写,此时用实现类的对象调用该方法,执行的是在实现类中重写之后的接口默认方法
在实现类重写默认方法时不加default关键字
-
Java8中的静态方法
格式:public static 返回值类型 方法名称(参数列表){方法体}
public可省略,static不能省略
静态方法的使用:不能通过接口实现类的对象来调用接口当中的静态方法,正确方法是通过接口名称,直接调用其中的静态方法
使用格式:接口名称.静态方法名(args)
-
Java9中的私有方法
-
私有方法有普通私有方法和静态私有方法
-
普通私有方法的作用:解决在接口中多个默认方法之间存在的重复的代码,在接口中使用
格式:private 返回值类型 方法名称(参数列表){方法体}
注意:普通私有方法在接口的实现类的对象中无法被调用,只能在接口中被默认方法来调用
-
静态私有方法的作用:解决在接口中多个静态方法的之间存在的重复代码,在接口中使用
格式:private static 返回值类型 方法名称(参数列表){方法体}
注意:静态私有方法在实现类的对象中无法被调用,也无法在main方法中直接被接口调用,只能在接口中被静态方法来调用
-
-
接口的常量定义和使用
-
接口中也可以定义成员变量,但这个成员变量必须使用public staitc final修饰
从效果上看这其实是接口的常量
-
格式:public static final 数据类型 常量名称 = 数据值;
-
接口的常量使用方法:接口名.常量名
-
一旦使用final关键字修饰,说明不可改变
-
接口当中的常量,可以省略public staic final,注意:不写也是这样
-
接口当中的成员变量其实是常量,必须进行赋值,而且一旦赋值不能改变
-
接口中的常量名必须全部大写,若有多个单词组成中间加下划线分割
-
-
使用接口时的注意事项:
-
接口没有静态代码块和构造函数
-
一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
格式:public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
覆盖重写所有的方法
}
-
如果实现类的所实现的多个接口中,存在重复的抽象方法,那么只需要重写覆盖一次即可
-
如果实现类没有重写覆盖所有接口当中的所有抽象方法,那么实现类必须是一个抽象类
-
如果实现类实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行重写覆盖
-
一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,实例化这个实现类时调用这个方法此时会优先调用父类当中的方法
public class Child extends Parent implements MyInterface { } -
接口之间的多继承
- 类与类之间是单继承的,直接父类只有一个
- 类与接口之间是多实现的,一个类可以实现多个接口
- 接口与接口直接是多继承的
- 注意事项:
- 多个父接口当中的抽象方法如果重复,没关系,因为抽象方法在接口中都没有具体实现的方法体
- 多个父接口当中的默认方法如果重复了,那么子接口必须进行默认方法的重写,并且要带default关键字,因为接口当中默认方法或默认方法的重写都必须加default关键字
-
-
接口可以作为成员变量类型;接口也可作为方法返回值和方法参数
接口可作为方法返回值和方法参数
import java.util.ArrayList; import java.util.List; //List是接口,ArrayList是其实现类 public class Demo01 { public static void main(String[] args) { List<String> list = new ArrayList<>(); List<String> newList = addName(list); for(int i = 0; i < newList.size(); i++) { System.out.println(newList.get(i)); } } public static List<String> addName(List<String> list) { list.add("Jake"); list.add("Jade"); list.add("Jeff"); list.add("Jack"); return list; } }
接口可以作为成员变量类型
//接口定义 public interface Skill { public abstract String useSkill(); }//接口的实现类,匿名对象和匿名内部类不用这个 public class SkillImpl implements Skill{ public String useSkill() { return "niu~niu"; } }//接口作为成员类的成员变量 public class Hero { private int age; private String name; private Weapon weapon; private Skill skill; public void useHeroSkill() { System.out.println(name + " is using skill:" + skill.useSkill()); } public Skill getSkill() { return skill; } public void setSkill(Skill skill) { this.skill = skill; } public Hero(int age, String name, Weapon weapon, Skill skill) { this.age = age; this.name = name; this.weapon = weapon; this.skill = skill; } public Hero() { } public Weapon getWeapon() { return weapon; } public void setWeapon(Weapon weapon) { this.weapon = weapon; } public Hero(int age, String name, Weapon weapon) { this.age = age; this.name = name; this.weapon = weapon; } public Hero(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void useWeapon() { System.out.println(name + " is using " + weapon.getCode()); } }//三种方式访问接口类型的成员变量变量 public class HeroWeaponMain { public static void main(String[] args) { Weapon weapon = new Weapon("Sword"); Skill skill = new Skill() { @Override public String useSkill() { return "piu~piu"; } }; Hero hero = new Hero(16, "King", weapon, skill); hero.useHeroSkill(); System.out.println("====="); hero.setSkill(new Skill() { public String useSkill() { return "qiu~qiu"; } }); hero.useHeroSkill(); System.out.println("====="); hero.setSkill(new SkillImpl()); hero.useHeroSkill(); } } /* output: King is using skill:piu~piu ===== King is using skill:qiu~qiu ===== King is using skill:niu~niu */
final关键字
-
final关键字修饰的内容代表最终、不可改变的
常见四种用法:
- 用来修饰一个类
- 用来修饰一个方法
- 用来修饰局部变量
- 用来修饰成员变量
-
final关键字用来修饰类
格式:
public final class 类名称 {
}
含义:当前这个类不能有任何子类
注意:一个类如果是final的,那么其中所有的成员方法都无法被覆盖重写(override),但它可以作为子类重写它的父类成员方法
-
final关键字修饰成员方法
作用:当final关键字修饰成员方法时,这个方法是最终方法,不能被覆盖重写
格式:修饰符 final 返回值类型 方法名(参数列表){方法体}
注意:
- 当前类可以有子类,但被修饰的方法不能再被子类重写
- 对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾
-
final关键字修饰局部变量
作用:一旦使用final修饰局部变量,那么这个变量就不能进行改变。只能赋值一次,值一直不变。
public static void main(String[] args) { final int a = 10; a = 100;//Error a = 10;//Error //正确写法,只要保证有一次赋值 final int num; num = 3; }不同数据类型:
- 对于基本类型来说,不可变是指变量中的数据值不可改变
- 对于引用类型来说,不可变是指变量中的地址值不可改变
public class TestFinal { public static void main(String[] args) { //两次指向不同的地址 Student1 stu1 = new Student1("Kim"); System.out.println(stu1); stu1 = new Student1("Jeff"); System.out.println(stu1); //加了final修饰后引用类型变量不能再指向其他的地址 final Student1 stu2 = new Student1("Jake"); System.out.println(stu2); //stu2 = new Student1("Jade"); //Error, //加了final修饰后引用类型变量可以修改指向的那个地址空间中存的内容 System.out.println(stu2.getName());//Output:Jake stu2.setName("NewName"); System.out.println(stu2.getName());//Output:NewName } } -
final关键字修饰成员变量
作用:对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变的
- 由于成员变量具有默认值,所以使用了final关键字修饰后,必须手动赋值(包括构造方法赋值和变量直接赋值这两种),不会再给默认值了
- 对于final修饰的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
- 必须保证类当中所有重载(overload)的构造方法,都最终会对final修饰后的成员变量进行赋值
public class Student1 { //private final int age = 15; //或者直接通过定义变量直接赋值,这样不用再写所有重载的构造方法了 private final int age; private String name; public Student1() { this.age = 15; } public Student1(int age, String name) { this.age = age; this.name = name; } public Student1(String name, int age) { this.name = name; this.age = age; } public Student1(int age) { this.age = age; } public Student1(String name) { this.name = name; this.age = 10; } public int getAge() { return age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
四种权限修饰符
-
Java中的四种权限修饰符:
public > protected > (default) > private
注意:(default)不是关键字,而是不写修饰符
-
四种情况:
- 同一个类:四种权限的成员变量都可在成员方法中访问
- 同一个包(两个类无继承关系):要实例化对象后访问成员变量,除了private修饰的,其他三种可直接访问
- 不同包子类(有继承关系,package中有package也属于不同包,继承时也要import导包):要使用super关键字可访问父类的成员变量,其中(default)和private修饰的成员变量不可直接访问
- 不同包非子类(无继承关系,测试访问成员变量前需要先导包,再实例化对象访问成员变量):只有public修饰的成员变量才可以访问
- 定义一个类的时候,权限修饰符规则:
- 外部类:public / (default)
- 成员内部类:public / protected / (default) / private
- 局部内部类:什么都不能写,效果和default不一样,(default)是本包当中可以访问,而局部内部类只能在外部类的方法中访问
内部类
-
一个Java文件中只能有一个public class,但可以有多个class
-
内部类就是在一个类的内部再定义一个类,比如,A类中定义一个B类,那么B类相当于A类就是内部类,而A类相对于B类就是外类
-
内部类分类
- 成员内部类
- 局部内部类(包含匿名内部类)
-
成员内部类
-
定义格式:
修饰符 class 外部类名称 { 修饰符 class 内部类名称 { // ... } // ... } -
内部类用外部类可以直接访问(不受权限修饰符影响);
外部类用内部类,需要内部类对象
-
成员内部类的使用:
-
间接方式:在外部类的方法当中,使用内部类。main只是调用内部类的方法
public class Animal { public void eat () { System.out.println("animal eat"); } public void accessInner() {//外部类方法调用内部类方法,需要用内部类的对象 SmallAnimal smallAnimal = new SmallAnimal(); smallAnimal.smallAnimalEat(); } public void accessInner2() {//外部类方法调用内部类方法,需要用内部类的对象 new SmallAnimal().smallAnimalEat(); } public class SmallAnimal { public void smallAnimalEat() { eat();//内部类方法中可直接调用外部类方法 System.out.println("small animal eat"); } } }public class Application2 { public static void main(String[] args) { //使用外部类对象间接访问内部类的方法 //这里是匿名对象访问方法,也可用引用类型变量存储对象再访问成员方法 new Animal().accessInner(); new Animal().accessInner2(); } } -
直接方式:格式:
类名称 对象名 = new 类名称();
外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
public class Application2 { public static void main(String[] args) { //下面两种方式不用再使用外部类的方法间接访问内部类了,而是直接实例化内部类对象 //引用类型变量存储对象的访问 Animal.SmallAnimal smallAnimal = new Animal().new SmallAnimal(); smallAnimal.smallAnimalEat(); //匿名对象方式访问 new Animal().new SmallAnimal().smallAnimalEat(); } }
-
-
内部类的同名变量访问
如果出现了重名现象,内部类方法访问外部类成员变量格式:外部类名称.this.外部类成员变量名
内部类方法访问内部类成员变量格式:this.内部类成员变量名
内部类方法访问内部类局部变量格式:内部类局部变量名
public class Outer { public int num = 10; public class Inner { public int num = 15; public void printInfo() { int num = 20; System.out.println(Outer.this.num); System.out.println(this.num); System.out.println(num); } } }public class Application2 { public static void main(String[] args) { new Outer().new Inner().printInfo(); } } /* output: 10 15 20 */
-
-
局部内部类
-
如果一个类是定义在方法内部的,那么这就是一个局部内部类。
-
局部:只有当前所属的方法才能使用它,出了这个方法外面就不能用了
-
定义格式:
修饰符 class 外部类名称 { 修饰符 返回值类型 外部类方法名称(参数列表){ class 局部内部类名称 { } } }-
权限修饰符:
public > protected > (default) > private
-
定义一个类的时候,权限修饰符规则:
- 外部类:public / (default)
- 成员内部类:public / protected / (default) / private
- 局部内部类:什么都不能写,效果和default不一样,(default)是在本包中可访问,而局部内部类只能在外部类的成员方法中才可访问
-
局部内部类示例:
//只有当前所属的方法才能使用它,出了这个方法外面就不能用了 //调用局部内部类中的方法,首先要在外部类方法中实例化内部类并访问局部内部类的成员方法 //在其他类中使用这个方法只能是以间接访问外部类对象的成员方法这种形式 public class Parent { public void parentInnerMethod() { class ParentInner {//局部内部类只有当前方法中才能用,所以什么都不能写 public int num = 223; public void printInfo() { System.out.println(num); } } new ParentInner().printInfo();//只有当前所属的方法才能使用它,出了这个方法外面就不能用了 } }public class TestLocalInnerClass { public static void main(String[] args) { new Parent().parentInnerMethod();//output:223 } } -
-
局部内部类的final问题
- 局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是有效final的
- JDK7或之前的版本要求是局部内部类访问的方法的局部变量必须是final修饰过的,之后的版本也是要求这个局部变量不变,不过final可以省略不写
- 原因:new出来的对象是在堆内存中的,局部变量是跟着方法走的,在栈内存当中。方法运行结束之后,立刻出栈,局部变量就会立刻消失。但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。
public class Parent { public void parentInnerMethod() { final int num = 223; class ParentInner {//局部内部类只有当前方法中才能用,所以什么都不能写 public void printInfo() { System.out.println(num); } } new ParentInner().printInfo();//只有当前所属的方法才能使用它,出了这个方法外面就不能用了 } } -
匿名内部类
-
如果接口的子类(或者是父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义(继承父类的子类或者是接口的实现类这是可以省略),而是使用匿名内部类
-
匿名内部类的格式:
接口名称 对象名 = new 接口名称() { //覆盖重写的所有方法 }; //接口名称 对象名 = new 接口名称();//错误写法//匿名内部类 //前面的接口名称不是类名,这个{}是一个没有名字的类,也不用加implents或extends { //覆盖重写的所有方法 } -
示例
public class DemoMain02 { public static void main(String[] args) { //MyInterface myInterface = new DemoMain01(); //myInterface.printInfo(); //new DemoMain01().printInfo(); MyInterface myInterface1 = new MyInterface() { @Override public void printInfo() { System.out.println("This is from anonymous Inner class"); } }; myInterface1.printInfo();//同一个对象可以调用多次方法 myInterface1.printInfo(); myInterface1.printInfo(); } } -
注意事项:
-
对new 接口名称() {...}进行解析
- new代表创建对象的动作
- 接口名称就是匿名内部类需要实现哪个接口
- {...}这才是匿名内部类的内容
-
匿名内部类,在创建对象的时候,只能使用唯一的一次(每次创建相同的对象(不同对象名),又要多写一遍匿名内部类的全部方法重写)。如果希望多次创建对象,而且类的内容一样,那么就必须使用单独定义的实现类了(以后创建多个对象不用再写所有的方法重写)
-
使用匿名内部类,可以省略对象名称,也是匿名对象
//既是匿名内部类又是匿名对象 new MyInterface() { @Override public void printInfo() { System.out.println("This is from anonymous inner class 2"); } }.printInfo(); -
匿名对象,在调用方法的时候,只能使用唯一的一次。如果希望同一个对象,调用多次方法,那么必须给对象起个名字
-
使用场景:匿名内部类适合在只创建一次对象时候使用,匿名对象适合只调用一次成员方法时候使用
-
匿名内部类是省略了子类的名称或实现类的名称,匿名对象是省略了对象名称
-
-

浙公网安备 33010602011771号