JavaSE| 面向对象-类的五大成员
面向对象
面向对象只是其中一种编程思想,还有很多其他的编程思想:面向过程、面向切面、面向服务编程...
面向过程的思维方式:注重步骤、过程,面向过程强调的是功能行为; 面向对象的思维方式:关注的是“对象”。面向对象,将功能封装进对象,强调具备了功能的对象。面向对象更加强调运用人类在日常的思维
逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
类(class)、对象(object也称为实例instance)
对象:一个一个的具体事物
当很多个对象它们有共同的特性的时候,我们就可以抽象出一个类别,用一个Java类来表示。
完成需求时:
先去找具有所需功能的对象来用; 如果该对象不存在,那么创建一个具有所需功能的对象; 这样简化开发并提高复用。
java类及类的成员:java中用类class来描述事物。 类与类之间的关系:关联、继承、聚集、组合
属性:对应类中的成员变量,Field = 属性 = 成员变量;行为:对应类中的成员方法,Method = 成员方法 = 函数。
类与对象是的关系
类是对象的抽象的概念,类是体现了对象的共同的特性。类是创建对象的模板,设计图。对象是类的具体。
一、类的设计
类的成员的设计,类的成员包括:
1、属性;2、代码块;3、构造器;4、方法;5、内部类
* 类的成员的顺序: * 1、属性:包括静态的类变量和非静态的实例变量 * 2、代码块:包括静态代码块和非静态代码块 * 非静态代码块又称为构造块 * 3、构造器:包括无参构造和有参构造 * 4、方法:包括静态方法和非静态方法 * 5、内部类
二、类的声明
语法格式:
【修饰符】 class 类名{
}
注意:
(1)类名:命名规则和命名规范(见名知意,每一个单词首字母大写)
(2)建议,就算这个类不是public,我们也与“源文件名”一样
类对象的内存解析
new出来一个变量(实体),首地址赋给a1,通过栈空间a1引用变量
1. 类的成员之一:属性
1、声明属性的语法格式: 【修饰符】 数据类型 属性名; 属性也称为成员变量。
2、声明属性的位置:必须在类中,其他成员(方法、构造器等)的外面
类 {
【修饰符】 数据类型1 属性名1;
【修饰符】 数据类型2 属性名2;
【修饰符】 数据类型3 属性名3;
....
}
3、属性如何赋值?
(1)属性有默认值
基本数据类型:
byte,short,int,long:0
float,double:0.0
char:\u0000
boolean:false
引用数据类型:(类、数组、接口) null
(2)属性的赋值
在其他类中: 对象.属性名 = 值;
4、属性的特点
1)属性有默认值; 2)每一个对象的属性是独立的
变量的三要素:数据类型、变量名、变量值
变量的三要素:(1)数据类型(2)变量名(3)变量值
对象的创建:
类名(数据类型_引用数据类型) 对象名 = new 类名(); 如 Student stu = new Student( );
属性在类里边声明,对象创建之后,要用对象来调用属性。 stu.name;
如果创建了一个类的多个对象,对于类中定义的属性,每个对象都拥有各自的一套副本,且互不干扰。
1)匿名对象
2)有名对象
类名 对象名 = new 类名( ); 数据类型 变量名 = new 类名();
说明:类是一种数据类型,引用数据类型。
对象名也是变量名,是引用数据类型的变量,new 类名()是一个值,是一个对象。
引用数据类型的变量、元素,它应该赋值为一个“对象”
回忆:变量的声明格式 数据类型 变量名;
变量的赋值: 变量名 = 值;
注意类创建的数组与它的对象的区别:
Teacher[ ] arr = new Teacher[ 5 ] 数组; Teacher tea = new Teacher( ) 对象;
元素类型是引用数据类型的数组称为对象数组。因为元素中存储的是对象。
arr[ i ]
System.out.println(new Circle()); //new Circle()匿名对象 -->> Circle@15db9742 Circle c1 = new Circle(); //有名对象,它的名字就是c,c是对象名 c1.circle = 1.2; String name = "kris";//String也是类名,name是对象名,"张三"是一个字符串的对象 if(name.equals("kris")){ } //创建了一个java.util.Scanner的对象 //java.util.Scanner也是一种类,也是数据类型 java.util.Scanner input = new java.util.Scanner(System.in); //input是对象名 int num = input.nextInt(); int[] arr = new int[5];//创建了一个数组对象,数组的元素相当于数组的成员变量,属性 //int[] -->>数组类型 <<-->>引用数据类型 -->>默认为null //int[][] -->>int类型为基本数据类型 -->>默认为0
class TestCircle{ public static void main(String[] args){ Circle c1 = new Circle(); //创建了一个圆对象 c1.radius = 2.1; //为属性赋值; System.out.println(c1.radius); System.out.println(c1.getArea()); System.out.println(c1.getLength()); } } /* 1、声明一个类 2、用类来创建具体的对象 */ class Circle{ double radius; double getArea(){ return Math.PI * radius * radius; } double getLength(){ return 2 * Math.PI * radius; } }
变量的分类
按照变量声明的“位置”分为:
- 局部变量
- 成员变量( 1)实例变量:没有static修饰的属性。2)类变量:使用static修饰的属性 )
① 成员变量:(某个成员变量不能被外部类直接访问,使用private修饰符)
* 成员变量的声明的位置:类中方法、代码块等外面。
* 成员变量的初始化:1)有默认值; 2)显式初始化;(比如说给属性变量赋值private int i = 10; 用构造器进行初始化还是初始化的值,并不会累加 )
3)构造器; 4)set方法可以再次修改值。
*成员变量:1)实例变量:堆; 2)类变量:方法区
* 成员变量:相对长,随着对象的创建而创建,随着对象被垃圾回收而消亡。每一个对象的成员变量是独立的。
* 成员变量:权限修饰符(private,缺省,protected,public)、static、final、volatile等
② 局部变量:
* 局部变量的声明的位置:1)方法的形参列表; 2)方法体中; 3)代码块。
* 局部变量的初始化:1)形参的初始化,必须在调用时,由实参赋值;2)其他的局部变量,必须手动初始化
* 局部变量:栈
* 局部变量:短,当代码执行到局部变量的声明处开始,到它的作用域结束而结束
* 局部变量:final
java可以声明局部变量而不进行初始化;
public class TestVar {
public static void main(String[] args) {
// java可以声明局部变量而不进行初始化
String str;
int zhangsan = 10;
}
}
字节码文件:
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
3 1 2 zhangsan I
LocalVariableTable 本地栈变量表,压栈时给每一个变量一个局部变量表LocalVariableTable;
没有s变量,没有初始化只是声明了,它根本不会出现了这里边;java要求变量在使用前必须初始化;
* 区别:声明的位置、初始化的方式、值的存储的位置不同、生命周期、修饰符不同
2. 类的第二个成员:方法
1、概念
方法(method),又称为函数(function),代表的是一个独立的功能。例如:
Math.sqrt(m),这个sqrt(m)方法,返回m的平方根
System.out.println(),这个println(x)方法,打印()中的内容
把代码封装到一个方法中的目的,简单的实现功能的复用
2、方法的要求和特点
(1)必须先声明后使用
(2)不调用不执行,调用一次执行一次
(3)调用时必须遵循一定的格式
3、方法的声明
语法格式:
【修饰符】返回值类型 方法名(【形参列表】){
方法体语句块;
}
名词解释:方法 = 方法头 + 方法体 方法头:【修饰符】返回值类型 方法名(【形参列表】) 方法头在很多书或文档中也称为“方法签名”
【修饰符】 class 类名{
【修饰符】返回值类型 方法名(【形参列表】){
方法体语句块;
}
}
因此,方法的声明的位置必须在类中,方法外。
方法的声明的形式
1、无参无返回值
语法格式:
【修饰符】void 方法名(){
方法体语句块;
}
只要是void就是没有返回值的。
2、有参无返回值
语法格式:
【修饰符】void 方法名( 数据类型 形参名 ) { //有void就没有返回值,在调用时也不用写[ 变量= ] 了。 (变量 = 方法名(实参列表))
方法体语句块;
}
形参列表:形参本质上就是一个变量。它只是个形式,没有具体值。只有在方法被“调用”时,形参才能确定值。
方法的调用
1、大多数时候都是在方法体中,偶尔可能在为属性显式赋值时调用
2、格式 【变量 = 】 方法名(【实参列表】);
调用方法时()中的参数列表我们称为“实参列表”,因为它是有具体的值,实参的作用就是给形参赋值的。
要求:
(1)调用时()中是否要写【实参列表】看声明时()中有没有【形参列表】,并且【实参列表】的个数、顺序、类型与【形参列表】一一对应。
3、无参有返回值
语法格式:
【修饰符】返回值类型 方法名(){
方法体语句块;
}
(1)返回值类型可以是任意Java类型(包括基本数据类型和引用数据类型)
(2)但是只能返回一个值,当然这个值可以是一个简单值(例如:整数5),也可以是一个复杂的值(例如:是一个对象,或者多个对象组成的集合等)
(3)方法体中,必须有“return 值;”
这种“无参有返回值”形式的方法,一般常见于,键盘输入、产生随机值、get值()等这样的功能。
方法的调用
1、大多数时候都是在方法体中,偶尔可能在为属性显式赋值时调用
2、格式 【变量 = 】 方法名(【实参列表】);
要求:
(1)调用时()中是否要写【实参列表】看声明时()中有没有【形参列表】,并且
要求【实参列表】的个数、顺序、类型与【形参列表】一一对象
(2)前面是否需要【变量=】,看声明时方法的返回值类型是否是void,如果是void,就不能写【变量=】;
如果返回值类型不是void,就可以使用“变量=”,要求这个“变量”和被调用方法的“返回值类型”应该一致或兼容。
4、有参有返回值
语法格式:
【修饰符】返回值类型 方法名(形参列表){
方法体语句块;
}
方法的调用
1、大多数时候都是在方法体中,偶尔可能在为属性显式赋值时调用
2、格式 【变量 = 】 方法名(【实参列表】);
(1)调用时()中是否要写【实参列表】看声明时()中有没有【形参列表】,并且
要求【实参列表】的个数、顺序、类型与【形参列表】一一对象
(2)前面是否需要【变量=】,看声明时方法的返回值类型是否是void,如果是void,就不能写【变量=】;
如果返回值类型不是void,就可以使用“变量=”,要求这个“变量”和被调用方法的“返回值类型”应该一致或兼容。
class TestMethod2{ public static void main(String[] args){ printJiuJiu();//无参无返回值 printRectangle(5,5,'*'); //有参无返回值 System.out.println(getNum()); //有参无返回值 int max = getMaxNum(7,9); System.out.println("最大值为:" + max); System.out.println("三个数最大值为:" + getThreeNum(5,9,12)); } public static void printJiuJiu(){ //无参无返回值 for(int i = 1;i <= 9;i++){ for(int j = 1;j < i;j++){ System.out.print(j + "*" + i + "=" + j*i + "\t"); }System.out.println(); } } public static void printRectangle(int m, int n, char x){ //有参无返回值 //第一轮:i=1, j=1,2,3,4,5,6循环6次; for(int i = 1;i <= m; i++ ){ for(int j = 1; j <= n; j++ ){ System.out.print(x); }System.out.println(x); } } public static int getNum(){ //无参有返回值 int num = (int)(Math.random() * 100); return num; } public static int getMaxNum(int a,int b){ //有参有返回值 if(a > b){ return a; }else{ return b; } } public static int getThreeNum(int a, int b, int c){ int max = getMaxNum(a, b); max = getMaxNum(c, max); return max; } }
class TestMethod{ public static void main(String[] args){ System.out.println("数组为:"); int[] arr = {3,6,2,9,0,8,4,5}; printShuZu(arr); for(int i = 0;i < arr.length; i++){ System.out.print(arr[i] + "\t"); } //System.out.println(printSum(1,100)); System.out.println("阶乘为:"); System.out.println(printJieCheng(10)); } //声明一个方法,可以为所有的int[]数组实现从小到大排序 //冒泡排序; 有参数没有返回值 public static void printShuZu(int[] arr){ //数组,长度为8,排序 // 第一次 i=1,j=0,1,2,3,4,5,6; for(int i = 1;i < arr.length; i++){ for(int j = 0;j < arr.length - i; j++){ if(arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } } //声明一个方法,可以累加[m,n]之间的和 //有参数有返回值的; public static int printSum(int m , int n){ int sum = 0; for(int i = m;i<=n; i++){ sum += i; } return sum; } //(3)声明一个方法,可以实现求任意正整数的阶乘 public static int printJieCheng(int m){ int jieCheng = 1; for(int i = 1;i < m; i++){ jieCheng *= i; } return jieCheng; } }
4、类的成员方法
(1)本类中方法,可以直接使用本类的属性
(2)本类中方法,可以直接调用本类的方法
(3)如果在别的类中使用,需要用“ 对象. ”调用
(4)? static 有影响
/*声明一个圆类,有属性:半径radius 给圆类增加成员方法: (1)求圆的面积的方法 */ class Circle{ double radius; //(1)求圆的面积的方法; 返回值:面积 //形参:是否需要?这里不需要,因为对象中已经有半径值 double getArea(){ double area = Math.PI * radius * radius; return area; } double getPerimeter(){ return 2 * Math.PI * radius; } String getInfo(){ return "圆的半径是:" + radius + ",它的面积是:" + getArea() + ",它的周长:" + getPerimeter(); } } class TestCircle{ public static void main(String[] args){ //(1)先创建圆对象 Circle c1 = new Circle(); //(2)求面积,求哪个圆的面积 double d = c1.getArea(); System.out.println("面积:" + d); //(3)设置半径的值 c1.radius = 2.5; d = c1.getArea(); System.out.println("面积:" + d); System.out.println("周长:" + c1.getPerimeter()); System.out.println(c1.getInfo()); } }
5、方法的参数传递机制
实参给形参传值
形参:在声明方法的()中的参数列表,
【修饰符】 返回值类型 方法名(数据类型 形参名, 数据类型 形参名, 。。。){}
实参:在调用方法的()中的参数列表,
【变量 = 】 方法名(值1,值2,值3, 。。。); 这个值可能是常量值,例如:(4,6),也可能是表达式,例如:(2*3),也可能是变量,例如(a,b)
1、形参的数据类型是基本数据类型
实参给形参传递的是“数据值”,“形参”是“实参”的一个“副本”,对形参的修改不会影响实参。
方法的参数传递机制:实参给形参传值
主方法和你所定义的方法在内存中是两个互相独立的空间,当你调用自己定义的方法执行完之后,它就会撤掉,变成垃圾准备回收了。
{ public static void main(String[] args) { int i = 0; change(i);//i=0,调用change函数,完成之后,它在内存中就会撤掉;主方法和change方法是两个独立的内存。 System.out.println("调用方法后" + i); //i=0 i = i++; //i=0,先把他load到操作栈里边,i再自增i=1,再把操作栈里的值赋值给i; //System.out.println(i++); //如果运行这一步,输出0,则下面输出i=1 System.out.println("i = " + i); //i=0 } public static void change(int i){ i++; System.out.println(i); //i=1 } }
2、形参的数据类型是引用数据类型
实参给形参传递的是“地址值”,意味着“形参”和“实参”同时指向“同一个”对象,
那么“形参”修改了它的属性,也意味着“实参”的属性也被修改了。
实参把它的地址值传给了形参,new出来的对象是放到堆里边的,主方法和自己定义的方法还是两个互相独立的内存空间,但是它们有相同的地址值,通过地址值把变量修改之后,实参和形参指向的是同一个对象,修改都会改变。它撤离之后,主方法中的那个变量还是修改之后的变量。
特殊情况:如果形参是String,Integer等不可变对象时,那么无论怎么修改形参都和实参无关的。
因为形参已经指向新的对象。
String对象是不可变的,但当它发生改变比如字符串的拼接时,它就会产生一个新的对象(y = x,跟这种是一样的,改变的是地址值,对象不一样了),新的字符串跟原来无关的,这时自己定义的方法执行完之后它还是会撤离的,新的对象跟着一起撤离,原来主方法中的字符串还是在的,它不会变。
class TestPassValue{ public static void main(String[] args){ toDouble1(3); MyDate d = new MyDate(); //把变量MyDate d放到栈里边,一个单独的内存空间,会有一个地址值如0x666; 而new产生对象是放在堆里边的,x默认为0,把它改成3; d.x = 3; toDouble2(d); //主方法和自己定义的方法是两个独立的内存空间,但有一个相同的地址值,通过myCanShu找到堆里边的x,把它改成原来的3倍大,执行完后撤走。 //System.out.println(d.x); int[] array = {3,2,5,9,1,4,0}; sort(array);//array实参把数组的首地址给形参arr,意味着arr和array指向同一个数组,对arr的排序就是对array的排序。 //发现可以实现排序 String s = "I Love"; //虽然没有new关键字,但它还是对象,它不是存在栈里边,在常量池里边,value=”I Love”,字符串的对象 change(s); //实参s把地址值给了形参String str = s;但String对象是不可变的,一旦改变如拼接、截取等,就会产生一个新的字符串对象。--> value=I LoveChina System.out.println("s = " + s); //产生新对象之后,原来的value=I Love就不要了,自己定义的方法执行完就会撤离,这时候你原来在主方法中的s=I Love还是在的。 } //产生的是新对象,跟原来的没有关系,这是个陷阱。 public static void toDouble1(double num1){ //形参为基本数据类型时,形参的改变不会影响到实参 System.out.println(num1); num1 *= 2; System.out.println(num1); } public static void toDouble2(MyDate myCanShu){ //形参为引用数据类型时,形参修改了它的属性,实参也要改变。 System.out.println(myCanShu.x); myCanShu.x *= 3; //变量MyDate myCanShu,会有一个地址值0x666,这个方法单独开一个内存空间,隐含了一个MyDate myCanShu = d ; System.out.println(myCanShu.x); } //定义一个对数组排序的方法 //假如是有3个元素的数组 //i=1, j=0,1 //i=2, j=0 public static void sort(int[] arr){ //形参为数组(数据类型+变量名) //foreach循环 for(int num : arr){ System.out.print(num); } for(int i = 1; i < arr.length; i++){ for(int j = 0; j < arr.length-i; j++){ if(arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1];; arr[j+1] = temp; } } } System.out.println(); for(int num : arr){ System.out.print(num); } } public static void change(String str){ //形参为String引用数据类型时 //特殊情况:如果形参是String,Integer等不可变对象时,那么无论怎么修改形参都和实参无关的。 //因为形参已经指向新的对象。 System.out.println(); str += "China"; System.out.println(str); } } class MyDate{ int x; }
6、命令行参数
/* 命令行参数:(了解) 给main()传递的实参,称为命令行参数 如果要给main()的形参传值,可以用如下的格式: java 类名 实参1 实参2 实参3... 参数: 形参 实参 命令行参数 */ class TestCommandParam{ //public static是修饰符 //void:表示main没有返回值 //main:方法名 //String[] args:形参 public static void main(String[] args){ System.out.println("参数的个数:" + args.length); //参数的个数:0 for(int i=0; i<args.length; i++){ System.out.println(args[i]); } } }
参数--可变参数
* 参数: * (1)形参 * 在方法的声明时,方法签名中()声明额参数就是形参 * 形参的形式:(数据类型 形参名) * (数据类型1 形参名1,数据类型2 形参名2) * ... * (2)实参 * 在方法的调用时,方法()中传的值、变量、表达式都是实参,作用是给形参赋值 * * 实参列表的个数、数据类型、顺序必须与形参列表一一对应 * * (3)命令行参数:给main()传递的实参,称为命令行参数 * java 类名 参数值1 参数值2 ... * * (4)可变参数: * 在声明方法时,某个形参的形式: 数据类型... 形参名 * 在调用方法时,这个形参对应的实参,可以传递0~n个值 * * 如何声明可变参数? * 【修饰符】 返回值类型 方法名(数据类型... 可变参数名){ * } * 【修饰符】 返回值类型 方法名(其他形参列表, 数据类型... 可变参数名){ * } * * 要求: * (1)声明时: * 一个方法,只能有一个可变参数,并且可变参数必须是最后一个 * (2)调用时: * 非可变参数的部分,原来该怎么传还怎么传; * 可变参数的部分,(A)可以传对应数据类型的0~n个的值(B)还可以传对应类型的数组 * * 需求:声明一个方法,可以找出1~n个整数中的最大值
public class TestVariableParam { public static void main(String[] args) { //例如:找出5,3,2,8中的最大值 /*int[] array = {5,3,2,8}; int max = getMax(array); System.out.println("最大值:" + max);*/ int max = getMax(5,3,2,8); System.out.println("最大值:" + max); max = getMax(9); System.out.println("最大值:" + max); int[] nums = {6,7,8,2}; max = getMax(nums[0], nums); System.out.println("最大值:" + max); } //声明一个方法,可以找出1~n个整数中的最大值 //返回值类型:int,因为要返回“最大值” //形参列表: //方式二:int a, int... args //int... args,在声明它的方法中,和“数组”一样使用即可 public static int getMax(int a, int... args){ //(1)先假设a最大 int max = a; //(2)用max中与args中的值一一比较 for(int i=0; i<args.length; i++){ if(max < args[i]){ max = args[i]; } } return max; } //声明一个方法,可以找出1~n个整数中的最大值 //返回值类型:int,因为要返回“最大值” //形参列表: //方式一:int[] /* public static int getMax(int[] arr){ //判断数组不为空 if(arr!=null && arr.length>0){ //(1)假设第一个元素最大 int max = arr[0]; //(2)用max中的值与剩下的元素一一比较 for (int i = 1; i < arr.length; i++) { if(max < arr[i]){ max = arr[i]; } } //(3)返回max return max; }else{ //抛出异常 throw new RuntimeException("数组不能为空"); } }*/ }
public class TestVariableParam2 { public static void main(String[] args) { System.out.println(getSum()); System.out.println(getSum(1,2)); System.out.println(getSum(0,1,2,3,4,5,6)); int[] arr = {4,5,6,7,2}; System.out.println(getSum(arr)); } //需求:求n个整数的和,n可以是0个,也可以是很多个 public static int getSum(int... nums){ int sum = 0; for (int i = 0; i < nums.length; i++) { sum += nums[i]; } return sum; } }
/* * 按照方法的重载:不属于重载 * 编译器认为它们是一样的,int[]...和int[]是一样的 */ public class TestOverload { public static void main(String[] args) { int[] arr = {1,2,3,4}; //getSum(arr);//如果假设下面是对的, 那么这句就不知道调用哪个了 } /*public static int getSum(int... args){ return 0; } public static int getSum(int[] args){ return 0; }*/ }
/* * 非严格意义说,是重写,但不推荐这么,int[]和int...不完全等价 */ public class TestOverride { public static void main(String[] args) { Base b = new Sub(); int[] arr = {1,2,3,4,5}; b.test(arr);//如果传数组,没问题,编译时类型和运行时类型都可以处理 // b.test(1,2,3,4);//编译时按父类编译,就会报错 Sub s = new Sub(); // s.test(1,2,3,4); } } class Base{ public void test(int[] args){ System.out.println("父类的test"); } } class Sub extends Base{ public void test(int... args){ System.out.println("子类的test"); } }
方法的重载:Overload
在同一个类中,出现了两个或多个“方法名称相同”、“形参列表不同”的方法,
这些方法我们称为“方法的重载”,和返回值类型无关。
形参列表不同:个数、数据类型不同
package day05; /*一、面向对象思想的落地法则一 * 1.设计类,并设计类的成员(成员变量&成员方法) * 2.通过类,来创建类的对象(也称作类的实例化) * 3.通过“对象.属性” 或 “对象.方法”来调用,完成相应的功能 * * 二、创建的多个对象,彼此各拥有一套类的属性。当对其中一个类的对象进行修改时, * 不会影响到其他对象的属性值。 * * 三、类的属性(成员变量) * 成员变量 vs 局部变量 * 相同点:1.遵循变量声明的格式:数据类型 变量名 = 初始值 * 2.都有作用域 * 不同点:1.声明的位置的不同:成员变量:声明在类里边,方法外 * 局部变量:声明在方法内,方法的形参部分,代码块内 * 2.成员变量的修饰符有四个:public private protected 缺省 * 局部变量没有修饰符,与所在的方法修饰符相同。 * 3.初始化值:一定会有初始化值。 * 成员变量:如果在声明的时候,不显示的赋值,那么不同数据类型会有不同的默认初始值。 * byte short int long ===>>0 * float double ===>> 0.0 * char ===>空格 * boolean ==> false * 引用类型变量 ==> null * 局部变量:一定要显式的赋值。(局部变量没有默认初始值) * 4.二者在内存中存放的位置不同:成员变量存在于堆空间中;局部变量:栈空间中 * 总结: * 关于变量的分类:1)按照数据类型的不同:基本数据类型(8种) & 引用数据类型 * 2)按照声明的位置不同:成员变量 & 局部变量 * * 四:方法 * 1)实例:public void eat(){//方法体} * public String getName(){} * public void setName(String n){} * 格式:权限修饰符 返回值类型(void:无返回值/具体的返回值) 方法名(形参){} * 2)关于返回值类型:void:表明次方法不需要返回值 * 有返回值的方法:在方法的最后一定要有return + 返回值类型对应的变量 * 记忆:void与return不可以同时出现一个方法内,像一对“冤家” * 3)方法内可以调用本类的其他方法或属性,但是不能在方法内在定义方法! * */ public class Zoo { public static void main(String[] args){ //基本数据类型的声明:数据类型 变量名 = 初始化值 int i = 10; //类的实例化:如下的a1就是一个实实在在的对象 Animal a1 = new Animal(); //int[] arr = new int[10]; //通过对象调用属性 a1.name = "kris"; a1.age = 3; System.out.println("name:" + a1.name + "age:" + a1.age); //name:krisage:3 //通过对象调用方法 a1.eat(); //动物进食 a1.sleep(); //动物休眠 //再创建一个类的对象 Animal a2 = new Animal(); System.out.println("name:" + a2.name + " age:" + a2.age ); //name:null age:0 a2.name = "小花"; System.out.println("name:" + a1.name + " age:" + a1.age); //name:kris age:3 System.out.println("name:" + a2.name + " age:" + a2.age); //name:小花 age:0 //a3不意味着相较于a1重新创建的一个对象,而是a1与a3共同一个对象的实体。 Animal a3 = a1; System.out.println("name:" + a3.name + " age:" + a3.age);//与a1一样 name:kris age:3 a3.name = "小熊熊"; System.out.println("name:" + a1.name + " age:" + a1.age); //name:小熊熊 age:3 System.out.println(a2.getName()); //a2.name; System.out.println(a2.desc()); } } class Animal{ //1.属性 String name; int age; //2.方法 public void eat(){ System.out.println("动物进食"); } public void sleep(){ System.out.println("动物休眠"); } public String getName(){ return name; } public int getAge(){ return age; //它后边不能声明语句了如System.out.println("hello"); } //当通过对象调用此方法时,会将方法的方法的返回值提供给方法的调用者,也就是当前的对象。 public String desc(){ if(age > 2){ return "洽同学少年"; }else{ return "回去看电视吧"; } } public void setName(String n){ //局部变量 name = n; } public void addAge(){ int i = 2; //局部变量 age += i; } public void info(){ //可以在方法内调用本类的其他方法,但是不可以在方法内定义新的方法 eat(); sleep(); } // System.out.println("hello"); 不能在类里边单独语句输出,要输出也得在方法内 }
3. 类的第三个成员:构造器
类的成员:属性、方法、构造器、代码块、内部类 构造器,又称为构造方法 构造器的作用: (1)和new一起使用时,创建对象用 (2)可以在创建对象的同时,为属性初始化,或赋初始值。 构造器的特点: (1)所有的类都有构造器, 如果你自己没有声明,那么编译器将会自动生成一个默认的无参构造器。 如果你手动声明了构造器,那么编译器就不会再自动生成无参构造了,如果你需要无参构造,必须自己写。 (2)构造器可以重载 (3)构造器的名称必须与类名相同,并且没有返回值类型,也不写void 构造器的声明语法格式 【修饰符】 class 类名{ 【修饰符】 类名(){ .... } 【修饰符】 类名(形参列表){ .... } } */
标准的get/set: public xxx的类型 getXxx(){ return xxx; } public void setXxx(xxx的类型 xxx){ this.xxx = xxx; } 这里xxx表示属性名
构造器的标准格式: //无参构造 public 类名(){ } //有参构造 public 类名(属性1的数据类型 属性1的名称,属性2的数据类型 属性2的名称,.。。){ this.属性1 = 属性1; this.属性2 = 属性2; .... }
this关键字
this关键字:当前对象 用法: (1)this.属性 为了解决,局部变量(例如,形参)和成员变量(例如:属性)重名时, 可以在成员变量的前面加“this.”用于区别 (2)this.方法 基本不用,但是可以这样用;表示访问当前对象的方法 (3)this()或this(实参列表) 表示访问本类的其他的构造器 要求:this()或this(实参列表)必须在构造器的首行
class TestThis{ public static void main(String[] args){ Employee emp1 = new Employee(); //无参构造创建对象 System.out.println(emp1.getInfo()); Employee emp2 = new Employee(1002,"李四"); //两个参数的创建对象 System.out.println(emp2.getInfo()); Employee emp3 = new Employee(1003,"王五",25,"1111","1245555","beijing"); //6个参数的创建对象 System.out.println(emp3.getInfo()); } } class Employee{ private int eid; private String name; private int age; private String tel; private String cid;//身份证号 private String address; public Employee(){ System.out.println("一个新员工入职了!"); //不管我从下面两个哪个构造器进来,我都想执行下这句话,则在下面两个构造器下面加this();而this(eid,name)是先 } //去找它上边的构造器,它再去执行这句话;不能跳过这一步。 public Employee(int eid, String name){ //创建对象的时候我不想要这么多,比如说eid和name是员工必填的 this();//它必须在构造器的首行 this.eid = eid; this.name = name; } public Employee(int eid, String name, int age, String tel, String cid, String address){ //要么用无参构造,要么就要用6个参数构造 //this.eid = eid; //this.name = name; //这两句跟前边两句是一样的 this(eid,name);//调用自己的另一个构造器为属性赋值,//它必须在构造器的首行;;把它俩写一块,先传给eid、name,然后它再传给上边的int eid和String name;它再赋值 this.age = age; this.tel = tel; this.cid = cid; this.address = address; } public String getInfo(){ return eid + "\t" + name + "\t" + age + "\t" + tel + "\t" + cid + "\t" + address; } }
static关键字
static方法不可以访问非static的变量;不可以从一个static方法内部发出对非static方法的调用。
* 类的成员:属性、方法、构造器、代码块、内部类 * * 关键字:static * * static是一个修饰符 * 一、static可以用来修饰什么? * 可以修饰成员(属性、方法、代码块、成员内部类),不能修饰最外面的类,也不能修饰构造器; * * 二、用它修饰后有什么不同? * 1、static修饰属性 * (1)static修饰的属性是该类所有对象“共享”的 * * 为了区别,把static修饰的属性,称为“类变量”,没有static修饰的属性,称为“实例变量” ; 类变量是共享的,实例变量是独立的 * * (2)static修饰的静态变量的值存储在“方法区”; 类变量是存储在“方法区”,实例变量存储在“堆”,局部变量存储在“栈”
static修饰的成员变量和成员方法独立于该类的任何对象。 * * (3)static修饰的属性,初始化的时机不同,在“类初始化”时初始化的,比创建对象要早,并且只初始化一次 * (4)static修饰的属性,它的get/set也是static的; * * 如何学习修饰符? * (1)它可以用来修饰什么? * (2)用它修饰后有什么不同? * * 权限修饰符: * private:可以修饰成员(属性、方法、构造器、成员内部类),不能修饰最外面的类 * 缺省:可以修饰外部类和成员(属性、方法、构造器、成员内部类) * protected:可以修饰成员(属性、方法、构造器、成员内部类),不能修饰最外面的类 * public:可以修饰外部类和成员(属性、方法、构造器、成员内部类) * * 范围不同: * ..... *
* 2、static修饰方法 * (1)可以不用“对象."就可以调用,可以使用"类名.”进行调用 * 例如:java.lang.Math * public static double sqrt(double a):double d = Math.sqrt(x); * public static double random():double d = Math.random(); * * 例如:java.util.Arrays * public static void sort(int[] a):Arrays.sort(数组); * public static int[] copyOf(int[] original,int newLength): 新数组名 = Arrays.copyOf(数组,新数组的长度) * * 例如:java.util.Scanner * public String next():没有static,所以不能用“类名." * 发现Scanner没有无参构造 * * Scanner input = new Scanner(System.in); * String name = input.next(); * * (2)static修饰的方法中,不能使用this,super关键字 * (3)static修饰的方法中,不能直接使用非static的属性和方法 * (4)static修饰的方法,不能被重写
public static void main(String[] args){ Teacher tea = new Teacher(); System.out.println(this.name);//编译错误,this不能在static方法中使用的 }
4. 类的第四个成员:代码块
* 类的成员: * 1、属性:存储对象的数据、信息 * 2、方法:代表对象的功能、行为特征 * 3、构造器:(1)和new一起创建对象(2)在创建对象时给属性赋值 * * 类的第四个成员:代码块 * 1、代码块的作用:为属性初始化 ① 非静态代码块:为非静态属性初始化,或者说辅助实例(对象)初始化 ② 静态代码块:为静态属性初始化,或者说辅助类初始化 * * 2、语法格式 * * 【修饰符】 class 类名{ { //非静态代码块 } static{ //静态代码块 } } * * 3、什么时候执行代码块 * (1)静态代码块:在类初始化时执行,只执行一次 * (2)非静态代码块:在实例初始化时执行,创建一个对象,执行一次 * (3)子类的初始化时,如果父类没有初始化,会先初始化父类 * (4)子类的实例初始化会导致父类的实例初始化 * 先执行父类的<init>,然后执行子类的<init> * * * 类初始化:每一个类,编译器会自动生成一个<clinit>(),称为类初始化方法。这个方法的方法体由以下两部分组成: * (1)静态变量的显式赋值语句 * (2)静态代码块中的语句 * 这两个部分,谁在上面谁先执行。 * * 实例初始化:每一个构造器,编译器会自动生成一个对应的<init>(),称为实例初始化方法。这个方法的方法体由以下三部分组成: * (1)非静态变量的显式赋值语句 * (2)非静态代码块中的语句 * (3)对应构造器的语句 * (1)和(2)仍然是谁在上面谁先执行,构造器永远是最后执行 * * 成员变量:我们通常说的属性,它分为实例变量和类变量,大多数人在说成员变量时,一般他说的是实例变量。
父类初始化< clinit >---->>>子类初始化< clinit >--->> 父类的实例初始化< init > --->>子类的实例初始化< init >;一开始都是静态的先初始化。
* 类初始化和实例初始化 * (1)先类初始化 * A:如果父类没有初始化,会先初始化父类 * B:类的初始化执行的是<clinit>方法,它由两部分组成: * (a)静态变量的显式赋值语句(b)静态代码块的语句,(a)(b)谁在上谁先执行 * * (2)实例初始化 * A:创建子类对象时,也会导致父类的实例初始化方法执行 * B:每一个构造器都会有对应的实例初始化方法 * C:每一个实例初始化有三部分组成: * (a)非静态变量的显示初始化代码(b)非静态代码块的语句(c)构造器 * (a)(b)谁谁在上谁先执行,构造器后执行
5. 类的第五个成员--内部类
* 内部类:定义在另一个类里面的类叫做内部类。把外面的这个类称为外部类。
* 例如:
* 【修饰符】 class 外部类{
//成员内部类,在外部类方法的外边。
【修饰符】 class 内部类{
}
【修饰符】 返回值类型 方法名(【形参列表】){
//局部内部类,在方法体的里边
【修饰符】 class 内部类{
}
}
}
* 内部类分为:
* 1、成员内部类
* 在外部类的里面,在外部类的方法的外面。
* (1)静态成员内部类:有static修饰
* (2)非静态成员内部类:没有static修饰
*
* 2、局部内部类
* 在外部类的方法体里面。
* (1)有名局部内部类
* (2)匿名局部内部类
*
* 为什么要用内部类?
* 类的概念:一类具有相同特性的事物,用一个Java类进行描述。例如:学生类,身体类
* 内部类也是类,当某个事物的内部,存在另一个事物,发现也需要一个Java类进行描述,而且这个内部的事物只为外部类服务,
* 基本上不会单独使用,那么这个时候用内部类比较合适。定义内部类还有一个好处,内部类可以使用外部类的私有成员。
成员内部类--静态内部类
* 一、静态内部类 * 1、语法格式: * 【修饰符】 class 外部类 { * //静态成员内部类 * 【修饰符】 static class 内部类{ * } * } * * 2、静态内部类也是一个类 * (一)静态内部类的成员(都可以) * (1)属性:类变量和实例变量 * (2)方法:静态方法和非静态方法,抽象方法(只有静态内部类是个抽象类就可以) * (3)构造器:无参构造,有参构造 * (4)代码块:静态代码块和非静态代码块 * (5)内部类:各种内部类 * * (二)静态内部类可以继承父类实现接口 * * (三)有自己的字节码文件 * 外部类名$静态内部类名.java * * 3、如何使用静态内部类 (必须在方法中才能调用) * (1)在外部类中使用静态内部类 和原来使用其他类一样。 静态内部类作为外部类的一个静态成员; * 在外部类的静态方法中使用静态内部类(调用内部类的静态方法):如Inner.inTest(); 在外部类的非静态方法中,要创建对象才能调用内部类的成员。 * (2)在外部类的外面(如测试类中)使用静态内部类; 使用静态内部类,前面要加“前缀”,“外部类名.内部类名”;
如调用内部类的静态方法(类名.直接调用):Outer.Inner.intest();
如调用内部类的非静态方法(要创建对象):Outer.Inner in = new Outer.Inner(); in.method(); * * (3)在静态内部类中,使用外部类的成员;在静态内部类中,不能使用外部类的非静态成员(属性和方法),其他的都能用(如在内部类里边的静态方法可以直接调用外部类的静态方法)
public class TestInnerStatic { public static void main(String[] args) { //调用内部类的静态方法 Outer.Inner.inTest();//Inner是Outer的静态成员,所以可通过Outer.访问; //inTest()是Inner的静态成员,所以可以通过Inner.访问 //调用内部类的非静态方法 Outer.Inner in = new Outer.Inner(); //需要对象来调用; //左边:Outer.Inner是表示类型名 //右边:调用Inner的构造器,其实这里静态内部类的构造器,“相当于是一个静态的构造器” in.method(); } } class Outer{ static class Inner{ public static void inTest(){ System.out.println("静态内部类Inner的静态方法inTest"); staticTest(); //调用外部类的静态方法 //outMethod();//错误的,静态内部类成员不能使用外部类的非静态成员 } public void method(){ System.out.println("静态内部类Inner的非静态方法"); } } public Inner getInner(){ Inner in = new Inner(); //创建内部类的对象 return in; } public static void outTest(){ Inner.inTest(); //调用Inner的静态方法 } public static void staticTest(){ System.out.println("外部类Outer的静态方法staticTest"); } public void outMethod(){ System.out.println("外部类Outer的非静态方法outMethod"); } }
成员内部类-非静态内部类
* 二、非静态的成员内部类 * 1、语法格式: * 【修饰符】 class 外部类{ * //非静态成员内部类 * 【修饰符】 class 内部类{ * } * } * * 2、非静态内部类也是一个类, 里边不能有静态相关的 * (一)非静态内部类的成员(都可以) * (1)属性:实例变量 * (2)方法:非静态方法,抽象方法(只有非静态内部类是个抽象类就可以) * (3)构造器:无参构造,有参构造 * (4)代码块:非静态代码块 * (5)内部类:各种非静态内部类 * * 和静态无缘了,里面不能有静态成员了,其他都可以。(特殊) * * (二)非静态内部类可以继承父类实现接口 * * (三)有自己的字节码文件 * 外部类名$非静态内部类名.java * * * 3、如何使用非静态内部类 * (1)在外部类中使用非静态内部类;在外部类的静态成员中不能使用非静态的内部类(特殊);外部类的静态成员不能使用非静态内部类的方法和属性等;
就是在外部类中,静态方法不能使用非静态内部类。
* (2)在外部类的外面使用非静态内部类(如在测试类中使用) 第一步:先创建外部类的对象 第二步:通过外部类的对象才能访问内部类的构造器,创建内部类的对象 或者 通过外部类的某个方法来返回内部类的对象 第三步:才能通过内部类的对象访问它的成员
如 Outer out = new Outer(); out对象可以调用外部类的所有方法和属性;
Outer.Inner in = out.new Inner(); 等价于 Outer.Inner in = new Outer().new Inner(); (非静态成员内部类的实例化)
in.inMethod(); (用in对象调用里边的非静态方法。)
但在开发中一般都是这样写:Outer.Inner in2 = out.getInner(); //在外部类中写一个getInner()方法
public Inner getInner(){
Inner in = new Inner();
return in;}
* (3)在非静态内部类中,使用外部类的成员; 直接使用,没有限制
如在非静态内部类中,非静态方法可以直接调用 外部类的静态方法或者非静态方法或者属性。
public class TestNonstaticInner { public static void main(String[] args) { Outer out = new Outer(); //因为Inner是外部类Outer的非静态成员,需要先创建外部类的对象; Outer.Inner in = out.new Inner(); //左边是Inner类型; 右边是通过外部类的对象才能访问内部类的构造器; in.inMethod(); //非静态内部类的对象in //out.new Inner();写太古怪了---> 如果要获取非静态内部类的对象,开发中一般这么写: /*Outer.Inner in2 = out.getInner(); //写一个getInner()方法 in2.inMethod();*/ } } class Outer{ private String name = "Outer类"; class Inner{ //成员非静态内部类 public void inMethod(){ outerMethod(); //成员非静态内部类可以使用外部类的 静态方法和非静态方法. outerTest(); } } public Inner getInner(){ Inner in = new Inner(); return in; } public void outerMethod() { System.out.println("外部类的非静态方法"); } public static void outerTest() { System.out.println("外部类的静态方法"); // Inner in = new Inner();//静态方法outTest是不能使用非静态的成员内部类Inner } }
//要求编写一个Sub类,它继承Inner类,在测试类中,创建Sub对象,并且调用method() /* * 1、继承的语法格式 * 【修饰符】 class 子类名 extends 父类的类型名{ * } * * 问题:Inner的类型名是什么?包名.Outer.Inner * * 2、父类是抽象类,子类要重写父类的抽象方法 * * 3、子类继承父类时,一定会调用父类的构造器,默认调用父类的无参构造 * super() * * 问题: * (1)父类Inner是否有无参构造器?有,所有类没有写构造器,都有默认的无参构造 * (2)父类Inner的构造器要如何调用?需要外部类的对象 */ public class TestOuter { public static void main(String[] args) { Outer out = new Outer(); Sub s = new Sub(out); s.method(); } } class Outer{ public abstract class Inner{ public abstract void method(); //定义一个抽象方法 } } class Sub extends Outer.Inner{ public Sub(Outer out) { out.super(); } @Override public void method() { System.out.println("重写父类的抽象方法"); } }
局部内部类 -- 有名的局部内部类
* 三、有名字的局部内部类(很少)(了解) * 1、语法格式 * 【修饰符】 class 外部类{ * 【修饰符】 返回值类型 方法名(【形参列表】){ * 【修饰符】 class 局部内部类{ * } * } * } * * 局部内部类:在方法体、代码块中声明的,和方法的局部变量很多方面是一样 * * 2、局部内部类也是一个类 * (1)有自己的字节码文件 * 外部类名$编号局部内部类名 * 这里用编号原因,在不同的方法中,局部内部类可能同名 * (2)成员 * A:属性 * B:方法 * C:构造器 * D:代码块 * E:内部类 * 不能有静态的成员 跟非静态内部类是一样的 * * 即唯一能声明静态成员的内部类只有一个“静态内部类”,其他内部类统统都不可以有静态成员。 * * 3、使用 * (1)在外部类中使用局部内部类 * 只能在声明局部内部类的下面才能使用,即先声明后使用(即要先 class,再实例化),并且有作用域的限制(在这个方法内)。
* (2)在外部类的外面 肯定不行
* (3)在局部内部类中(只有非静态方法)使用外部类的成员
* 局部内部类是否可以使用外部类的非静态的成员,取决于所在方法是否是静态的,如果方法是静态的就不可以使用了,如果是非静态的可以使用。
如果是非静态方法里的局部内部类:可以使用外部类的静态属性、方法或者非静态的都可以;(没有静态方法)
* (4)在局部内部类中可以使用外部类的“局部常量”,即有final修饰 (必须有final修饰,即常量,不能使用变量。)
* JDK1.8之前:必须手动加final; JDK1.8之后,自动加final
*/
public class TestLocalInner { private static int i; private int j; public static void main(String[] args) { // Inner i = new Inner(); // a = 10; int b = 20; class Inner{ public void inTest(){ System.out.println(i); // System.out.println(j);//不能使用外部类的非静态成员,原因main是静态的 System.out.println(b); } } int a = 10; } public void method(){ // Inner in = new Inner(); // a = 20; Object obj = test();//obj编译时Object,运行时是Inner } //返回值类型Object,返回值Inner的对象 public Object test(){ int c = 10; class Inner{ public void inTest(){ System.out.println(i); System.out.println(j);//这里可以使用j,因为test()是非静态的 System.out.println(c); //此c非彼c,因为上面的c是局部变量,它的生命周期很短和存储的位置在栈,而我们的Inner对象生命周长,它的信息是在堆中 //堆中是无法直接使用栈中的变量,所以必须在堆中重新弄一个c的副本 } } Inner in = new Inner(); return in; } } class Other{ }
局部内部类--匿名内部类
* 四、匿名内部类(重要) * 1、语法格式: * 外部类{ * 方法{ new 父类(【实参列表】){ 成员 } new 父接口(){ 成员 } * } * } * * 2、特点 * (1)没名字 所以不写构造器; 不能有构造方法 * (2)匿名内部类必须在声明类的同时就要创建对象,也只有唯一的一个对象 * (3)必须指定匿名内部类的父类或父接口 * * 3、匿名内部类也是类 * (1)有字节码文件; 外部类名$编号.class * (2)也有成员 (不能定义任何静态成员、方法、类; 匿名内部类不能是public、protected、private、static) * 属性、方法、代码块、内部类、构造器都有,不能有静态的,一般不会写这么多。 * 一般匿名内部类都是重写父类的方法或实现接口的抽象方法 * * 4、在匿名内部类中要使用外部类的成员的话 * (1)是否可以使用外部类的非静态成员,要看所在的方法是否是静态的 * (2)只能使用外部类的局部常量,必须有final *
public class TestAnoymousInner { public static void main(String[] args) { final int A = 10; //匿名内部类的匿名对象调用test() new Father(){ //重写父类的方法 public void test(){ System.out.println("子类的方法1" + A); } }.test(); //多态引用 Father f = new Father("尚硅谷"){ //重写父类的方法 public void test(){ System.out.println("子类的方法2"); } }; f.test(); //右边用匿名内部类的形式实现了MyInter接口,并创建了实现了对象 MyInter m = new MyInter(){ @Override public void method() { System.out.println("实现接口的抽象方法"); } }; m.method(); } } class Father{ private String info; public Father(){ } public Father(String info){ this.info = info; } public void test(){ System.out.println("父类的方法"); } } interface MyInter{ void method(); }
* java.util.Arrays数组工具类有sort方法 * * public static void sort(Object[] a):根据元素的自然顺序对指定对象数组按升序进行排序。数组中的所有元素都必须实现 Comparable 接口 * public static void sort(Object[] a,Comparator c):根据指定比较器产生的顺序对指定对象数组进行排序。 * 指定比较器就是Comparator的对象,用它的c.compare(元素1,元素2) * * java.util.Comparator接口:有一个抽象方法int compare(Object o1, Object o2) * * 当某个接口的实现或某个子类,只用一次,而且代码比较简洁,可以考虑使用匿名内部类 *
//要求用Arrays.sort对arr进行排序 //Arrays.sort(arr); //匿名内部类的匿名对象作为sort的实参 Arrays.sort(arr, new Comparator() { @Override public int compare(Object o1, Object o2) { Employee e1 = (Employee) o1; Employee e2 = (Employee) o2; return e1.getId() - e2.getId();//如果e1.getId()>e2.getId()返回正整数,如果小于,返回负整数,等于返回0 } }); for (Employee employee : arr) { System.out.println(employee); } //按照薪资排序 /* Arrays.sort(arr, new Comparator() { @Override public int compare(Object o1, Object o2) { Employee e1 = (Employee) o1; Employee e2 = (Employee) o2; if(e1.getSalary() > e2.getSalary()){ return 1; }else if(e1.getSalary() < e2.getSalary()){ return -1; }else{ return 0; } } });*/ Arrays.sort(arr, new MyComparator()); for (Employee employee : arr) { System.out.println(employee); } } } class MyComparator implements Comparator{ @Override public int compare(Object o1, Object o2) { Employee e1 = (Employee) o1; Employee e2 = (Employee) o2; if(e1.getSalary() > e2.getSalary()){ return 1; }else if(e1.getSalary() < e2.getSalary()){ return -1; }else{ return 0; } } } class Employee { private int id; private String name; private double salary; ... }
例如:
public static void main(String[] args) { printArea(new Rectangle("矩形", 2.0, 2.0)); //把矩形 //一次传用匿名内部类实现Graphic的圆对象(包含属性半径,实现抽象方法) printArea(new Graphic("圆") { @Override public double getArea() { double radius = 1.0; return Math.PI * radius * radius; } }); Graphic[] gra = new Graphic[3]; gra[0] = new Rectangle(2.1, 2.1); gra[1] = new Rectangle(2.2, 3.1); gra[2] = new Rectangle(1.2, 4.0); for (Graphic graphic : gra) { System.out.println(graphic); } //调用Arrays.sort(数组,Comparator)的方法排序 Arrays.sort(gra, new Comparator() { @Override public int compare(Object o1, Object o2) { Graphic g1 = (Graphic) o1; Graphic g2 = (Graphic) o2; if(g1.getArea() > g2.getArea()){ return 1; }else if(g1.getArea() < g2.getArea()){ return -1; }else{ return 0; } } }); for (Graphic graphic : gra) { System.out.println(graphic); } } public static void printArea(Graphic g){ //System.out.println(g);//自动调用g.toString(),打印对象,自动调用toString() //自己拼 System.out.println("图形名称:" + g.getName() +",面积:" + g.getArea()); }