二者都是一种思想,面向对象是相对于面向过程而言的。
面向过程(Procedure Oriented Programming),强调的是功能行为,以函数为最小单位,考虑怎么做。
面向对象(Object Oriented Programming),将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
理解"万事万物皆对象"
涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象。
类(class)是对一类事物的描述,是抽象的、概念上的定义。对象(object)是实际存在的该类事物的每个个体,因而也称为实例(instance)。
可以理解为:类 = 抽象概念的人;对象 = 实实在在的某个人 。
面向对象思想落地实现的规则
创建类,设计类的成员
修饰符 class 类名 {
属性声明;
方法声明; }
class Person{ //属性 int age = 18; String name; boolean isMale; //方法 public void walk(){ System.out.println("去走走吧"); } public void study(){ System.err.println("学习"); } public void talk(String language){ System.out.println("使用的语言是" + language); } }
创建类的对象
类名 对象名 = new 类名();
调用对象的结构
“对象.属性”
“对象.方法”
public class PersonTest { public static void main(String[] args) { //2 创建类的对象=类的实例化 //Scanner scan = new Scanner(system.out); //类 对象 = new 类();别忘了() Person p1 = new Person(); //3 调用属性(成员变量)对象.属性 p1.name = "Tom"; p1.isMale = true; System.out.println(p1.age); //4 调用方法:对象.方法 p1.walk(); p1.talk("中文"); } }
编写完源码利用javac.exe编译完以后,生成一个或多个字节码文件。我们使用JVM中的类的加载器和解释器对生成的字节码文件进行解释运行。
意味着,需要将字节码文件对应的类加载到内存中,涉及到内存解析。
栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。 局部变量表存放了编译期可知长度的各种基本数据类型(boolean
、byte
、 char
、short
、 int
、 float
、 long
、 double
)、对象引用(reference类型, 它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
堆(Heap),此内存区域的唯一目的就是存放对象实例(存放new出来的),几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
总结:
栈:局部变量存储在栈结构中
堆:new出来的结构(比如:数组、对象)加载在堆空间中。补充:对象的属性(非static的)加载在堆空间中。
方法区:类的加载信息、常量池、静态域
**类的成员构成:**属性,构造器,方法;代码块,内部类。
类的访问机制:
**在一个类中的访问机制:**类中的方法可以直接访问类中的成员变量。(例外:static方法访问非static,编译不通过。)
**在不同类中的访问机制:**先创建要访问类的对象,再用对象访问类中定义的成员。
在方法体外,类体内声明的变量称为属性(成员变量)。
在方法体内部声明的变量称为局部变量。
语法格式:
修饰符 数据类型 属性名 = 初始化值 ;
说明1: 修饰符
常用的权限修饰符有:private、缺省、protected、public
其他修饰符:static、final (暂不考虑)
**说明2:**数据类型任何基本数据类型(如int、Boolean) 或任何引用数据类型。
**说明3:**属性名属于标识符,需符合命名规则和规范。
public class Person{ private int age; //声明private变量age public String name = “Lila”; //声明public变量 name }
成员变量类型 | 初始值 |
---|---|
byte /short /int | 0 |
long | 0L |
float | 0.0F |
double | 0.0 |
char | 0 或写为:’\u0000’(表现为空) |
boolean | false |
引用类型 | null |
按数据类型分类
按在类中的声明位置分类
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参或内部、代码块内、构造器内等 |
修饰符 | private、public、static、final等 | 不能用权限修饰符修饰,可以用final修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显式赋值,方可使用 |
内存加载位置 | 堆空间 或 静态域内(static) | 栈空间 |
public class OOPTest1 { public static void main(String[] args) { User u1 = new User(); //属性的默认初始值 System.out.println(u1.name);//null System.out.println(u1.age);//0 System.out.println(u1.ismale);//false //******************* u1.talk("Japanese"); } } class User{ //属性=成员变量 String name; int age; boolean ismale; //方法 public void talk(String language){//language: 形参,属于局部变量,形参调用时赋值即可,较特别 System.out.println("使用" + language +"交流"); } public void eat(){ String food = "烙饼";//方法内也是局部变量,局部变量没有初始默认值,要先赋值再调用 System.out.println("北方人超喜欢吃" + food); } }
方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
将功能封装为方法的目的是,可以实现代码重用,简化代码。
Java里的方法不能独立存在,所有的方法必须定义在类里。
方法的使用中,可以调用当前类的属性或方法。
方法的声明:权限修饰符 返回值类型 方法名(形参列表){
方法体
}
无返回值 | 有返回值 | |
---|---|---|
无形参 | void 方法名(){} | 返回值的类型 方法名(){} |
有形参 | void 方法名(形参列表){} | 返回值的类型 方法名(形参列表){} |
无返回值VS有返回值
**如果方法返回值,**则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量:``return 数据`。
如果方法没返回值,则方法声明时,使用void来表示。通常,没返回值的方法中,就不需要使用return。但是,如果使用的话,只能“return;
表示结束此方法的意思。
例题1
定义类Student1,包含三个属性:学号number(int),年级state(int),成绩score(int)。 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
public class Exer { public static void main(String[] args) { //声明一个Student类型的数组 String[] arr = new String[10]; Student1[] group = new Student1[20];//此时堆中每个位置都是null for(int i = 0;i < group.length;i++){ //给对象数组赋值,先给每一个位置new对象; group[i] = new Student1(); group[i].number = i + 1; group[i].state = (int)(Math.random() * 3) + 1; group[i].score = (int)(Math.random() * 101); } Student1 test = new Student1();//group不是Student1类的对象,是Student类对象的数组,所以不能用group.方法来调用Student1类中的方法,要用匿名对象 //遍历数组 new Student1.print(group); System.out.println("****************"); //问题1.打印出3年级(state值为3)的学生信息。 new Student1.specialInfo(3,group); // 问题2.使用冒泡排序按学生成绩排序,并遍历所有学生信息 // 冒泡排序循环 new Student1.BubbleSort(group); System.out.println("****************"); //遍历数组 test.print(group); } } class Student1{ int number; int state; int score; /** * * @Description 显示学生信息的方法: * @author jqyu * @date 2022年1月15日下午6:17:34 * @return 学生的年纪 学号 成绩 */ public String info(){ return state + "年级学号为" + number + "的成绩为" +score; } /** * * @Description 遍历数组 * @author jqyu * @date 2022年1月15日下午6:16:36 */ public void print(Student1[] arr){ for(int i = 0; i < arr.length;i++){ System.out.println(arr[i].info()); } } /** * * @Description 输出指定年纪学生的信息 * @author jqyu * @date 2022年1月15日下午6:22:06 * @param state 指定的年纪数 * @param arr 查找的数组 */ public void specialInfo(int state,Student1[] arr){ for(int i = 0;i <arr.length;i++){ if(arr[i].state == state){ System.out.println(arr[i].info()); } } } public void BubbleSort(Student1[] arr){ for (int i = 0; i < arr.length - 1;i++){ for (int j = 0; j < arr.length - 1 - i;j++){ if(arr[j].score > arr[j + 1].score){ Student1 temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } }
定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
总结:“两同一不同”:
同:同一个类、相同方法名
不同: 参数列表不同(参数个数不同,参数类型不同) 与形参名 权限修饰符无关!
public class Test { public static void main(String[] args) { //匿名对象的使用 new Method().moL(10); new Method().moL(10,20); new Method().moL("a"); System.out.println(new Method().max(10,20)); System.out.println(new Method().max(10.0, 20.0)); System.out.println(new Method().max(10.0,20.0,30.0)); } } class Method{ /*1.编写程序,定义三个重载方法并调用。方法名为mOL。 三个方法分别接收一个int参数、两个int参数、一个字符串参数。分别 执行平方运算并输出结果,相乘并输出结果,输出字符串信息。 在主类的main ()方法中分别用参数区别调用三个方法。*/ public void moL(int a){ System.out.println(a * a); } public void moL(int a,int b){ System.out.println(a * b); } public void moL(String a){ System.out.println(a); } /*2.定义三个重载方法max(),第一个方法求两个int值中的最大值,第二个方 法求两个double值中的最大值,第三个方法求三个double值中的最大值, 并分别调用三个方法。*/ public int max(int a,int b){ int c; c = a > b? a: b; return c; } public double max(double a,double b){ double c; c = a > b? a : b; return c; } public double max(double a,double b,double c){ double temp; if(a > b && a >c){ temp = a; }else if(b > c){ temp = b; }else{ temp = c; } return temp; } }
public void show(String … strs){}
public class VaragrsTest { public static void main(String[] args) { String[] a = new String[4]; new Args().method("a");//地址值 new Args().method(a);//地址值 new Args().method(new String[]{"a","b","c"});//地址值 //想输出数组中数据需要for循环输出 /*public void method(String ... str){ for(int i = 0;i <str.length;i++){ System.out.print(str[i]); } }*/ } } class Args{ //调用不需要加new String[],如method("a","b","c"),但加了也可以; public void method(String ... str){ System.out.print(str); } //调用需要加new String[]{},如method(new String[]{"a","b","c"}); // public void method(String[] str){ // System.out.println(str); // } }
形参:方法声明时的参数
实参:方法调用时实际传给形参的参数值
Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
形参是基本数据类型:将实参基本数据类型变量的==“数据值”==传递给形参
实参的值赋给形参后,方法swap内完成形参值的交换,方法结束后形参出栈,main方法中m,n值不变
形参是引用数据类型:将实参引用数据类型变量的==“地址值”==(包含数据类型的传递给形参
main方法中data的地址值赋给swap方法data的形参,两者都指向堆空间中存储值m,n。swap方法交换m,n值后,由于与main方法指向同一m,n,main方法中对象data的m,n值是交换后的,swap方法结束后局部变量data出栈。
递归方法:一个方法体内调用它自身。
方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
例题1 输入n,求1+……+n的和
public class RecursionTest { public static void main(String[] args) { int n = new Scanner(System.in).nextInt(); System.out.println(new RecursionTest().getsum(n)); } //递归方法 public int getsum(int n){ if(n == 1){ return 1; }else{ return n + getsum(n - 1); } } }
例题2 已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),其中n是大于0的整数,求f(10)的值。
public class RecursionTest2{ public static void main(String[] args) { RecursionTest2 test = new RecursionTest2(); System.out.println(test.f(10)); } public int f(int n){ if(n == 0){ return 1; }else if(n == 1){ return 4; }else{ return 2 * f(n - 1) + f(n - 2); } } }
例题3 输入一个数据n,计算斐波那契数列(Fibonacci)的第n个值
1 1 2 3 5 8 13 21 34 55
规律:一个数等于前两个数之和
要求:计算斐波那契数列(Fibonacci)的第n个值,并将整个数列打印出来
public class Recursion1 { public static void main(String[] args) { method(5); } public static void method(int n){ int[] arr = new int[n]; for(int i= 0;i < n;i++){ arr[i] = f(i); System.out.print(arr[i] + " "); } } public static int f(int n){ if (n == 0 ||n == 1){ return 1; }else{ return f(n - 1) + f(n -2); } } }
例题4 已知一个数列:f(20) = 1,f(21) = 4,f(n+2) = 2*f(n+1)+f(n), 其中n是大于0的整数,求f(10),f(30)的值。
public class Recursion{ public static void main(String[] args) { System.out.println("f(10) = " + f(10)); System.out.println("f(30) = " + f(30)); } public static int f(int n){ if(n == 20){ return 1; }else if(n == 21){ return 4; }else if(n > 20){ return 2 * f(n - 1) + f(n - 2); }else{ return f(n + 2) - 2 * f(n + 1); } } }
我们创建的对象,没显式的赋给一个变量名。即为匿名对象。
特点:匿名对象只能调用一次,调用结束后匿名对象出堆,该匿名对象的对象引用(局部变量)出栈。
如:
mall.show(new Phone());
其中
class PhoneMall{ public void show(Phone phone){ //方法体内调用方法 phone.sendEmail(); phone.playGame(); }
权限修饰符 类名 (参数列表) {
初始化语句;
}
分类:
隐式无参构造器(系统默认提供)
显式定义一个或多个构造器(无参、有参)
有参 | 无参 |
---|---|
如果没有显示定义类的构造器的话,则系统默认提供一个空参的构造器(该空参构造器权限修饰符与类相同)
一个类中定义的多个构造器,彼此构成重载
一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
一个类中,至少会有一个构造器。
非静态属性赋值的过程:
赋值的位置:
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过“对象.属性“或“对象.方法”的方式赋值
⑤通过非静态代码块赋值
赋值的先后顺序:
① - ② /⑤- ③ - ④
JavaBean是一种Java语言写成的可重用组件。 所谓javaBean,是指符合如下标准的Java类:
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的
JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而
不用关心任何改变。
代码块(初始化块)的作用:对Java类或对象进行初始化。
代码块(或初始化块)的分类:
静态代码块: static修饰,static{代码块体}
(代码块只能用static修饰)
非静态代码块: 无static修饰,{代码块体}
可以有输出语句。
可以对类的属性、类的声明进行初始化操作。
除了调用非静态的结构外,还可以调用静态的变量或方法。
若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
每次创建对象的时候,都会执行一次。且==先于构造器执行==。
例题 创建子类对象的构造过程:
class Root{ static{ System.out.println("Root的静态初始化块"); } { System.out.println("Root的普通初始化块"); } public Root(){ System.out.println("Root的无参数的构造器"); } } class Mid extends Root{ static{ System.out.println("Mid的静态初始化块"); } { System.out.println("Mid的普通初始化块"); } public Mid(){ System.out.println("Mid的无参数的构造器"); } public Mid(String msg){ //通过this调用同一类中重载的构造器 this(); System.out.println("Mid的带参数构造器,其参数值:" + msg); } } class Leaf extends Mid{ static{ System.out.println("Leaf的静态初始化块"); } { System.out.println("Leaf的普通初始化块"); } public Leaf(){ //通过super调用父类中有一个字符串参数的构造器 super("尚硅谷"); System.out.println("Leaf的构造器"); } } public class LeafTest{ public static void main(String[] args){ new Leaf(); new Leaf(); } }
输出结果为
//由父及子,执行静态代码块 Root的静态初始化块 Mid的静态初始化块 Leaf的静态初始化块 //静态代码块执行完后,由父及子,执行非静态代码块,构造器,代码块优先于构造器 Root的普通初始化块 Root的无参数的构造器 Mid的普通初始化块 Mid的无参数的构造器 Mid的带参数构造器,其参数值:尚硅谷//Mid的带参构造器调用了无参构造器 Leaf的普通初始化块 Leaf的构造器 ********* //静态代码块只执行一次 Root的普通初始化块 Root的无参数的构造器 Mid的普通初始化块 Mid的无参数的构造器 Mid的带参数构造器,其参数值:尚硅谷 Leaf的普通初始化块 Leaf的构造器
结论:由父及子 静态先行 代码块先于构造器
由父及子的原因:建立子类对象前,继承性使得父类被加载,其静态构造器就随着一起加载。随后建立子类对象一定会调用到父类的空参构造器(通过n轮super()
),直到Object,且调用父类空参构造器前先调用父类的非静态代码块初始化类。
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
内部类分类:
成员内部类:静态(static修饰)成员内部类,非静态成员内部类。
局部内部类 :方法内、代码块内、构造器、内部类内。
成员内部类作为类的成员的角色:
成员内部类作为类的角色:
extends 外部类.静态内部类
class AAA extends Person2.Brain{//Brain为Person2的静态成员内部类 } class BB extends Person2.Hands{//Hands为Person2的非静态内部类 public BB(Person2 person){ person.super(); } }
注:
class Person{ String name = "小明"; public void eat(){ } //非静态成员内部类 class Bird{ String name = "杜鹃"; public void display(String name){ System.out.println(name);//方法的形参 System.out.println(this.name);//内部类的属性: this.内部类属性 System.out.println(Person.this.name);//外部类的属性 : 外部类名.this.外部类属性 Person.this.eat(); } } }
public class InnerTest { public void method() { // 局部变量 int num = 10; class AA { int num1 = num; public void show() { // num = 20;//报错 必须为final System.out.println(num); } } } }
局部内部类的应用:返回一个实现接口对象
public class InnerTest { //返回一个实现了Comparable接口的对象的方法 public Comparable getComparable(){ //方式1:内部类 //创建一个实现了Comparable接口的类 class MyComparable implements Comparable{ @Override /* public int compareTo(Object o) { // TODO Auto-generated method stub return 0; } } return new MyComparable();*/ //方式2:匿名类 return new Comparable(){ @Override public int compareTo(Object o) { // TODO Auto-generated method stub return 0; } }; } }
我们程序设计追求==“高内聚,低耦合”==。
高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
低耦合 :仅对外暴露少量的方法用于使用。
即隐藏对象内部的复杂性,只对外公开简单的接口,便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该
暴露的暴露出来。这就是封装性的设计思想。
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的
制约。除此之外,没其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件(如客户输入自身年龄,年龄不能为
负数)。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加,避免用户再使用"对象.属性"的方式对属性进行赋值。则需
要将属性声明为私有的(private).
–>此时,针对于属性就体现了封装性。
属性的封装
public class PersonTest { public static void main(String[] args) { Person p = new Person(); //属性赋值 p.setAge(1); //属性调用 System.out.println(p.getAge()); } } class Person{ //属性修饰符private私有化后,不能用类.属性(Person.age)在类以外赋值和调用 private int age; //属性赋值方法,一般修饰public public void setAge(int i){ if(i <= 0){//年龄不能为1以下 System.out.println("年领非法!"); }else{ age = i; } } //获取属性值方法,一般修饰public public int getAge(){ return age;//age私有化后只能在类内调用 } }
不对外暴露的私有的方法
单例模式(将构造器私有化)
如果不希望类在包外被调用,可以将类设置为缺省的。
对于类的成员
Java权限修饰符public
、protected
、default(缺省)、private
置于类的成员(仅限属性、方法、构造器、内部类,不包括代码块
)定义前, 用来限定对象对该类成员的访问权限。属性用private修饰私有化后,再别的类中创建对象仍然开辟内存存放属性,只是属性不可
见(unvisible),不能调用了。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
缺省 | Yes | Yes | ||
protected | Yes | Yes | ||
public | Yes | Yes | Yes | Yes |
对于类
对于class的权限修饰只可以用public和default(缺省)。
public类可以在任意地方被访问。
default类只可以被同一个包内部的类访问。
① 减少了代码的冗余,提高了代码的复用性 ② 便于功能的扩展 ③ 为多态性的使用,提供了前提
class A extends B{ }
A: 子类、派生类、subclass
B: 父类、超类、基类、superclass
一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法(包括private权限的属性和方法)。
构造器是无法被继承的,所以不能重写,但是可以重载。但是在子类实例化对象时候,如果子类的构造器没有显式的调用父类构造器,则自动调用父类的默认无参构造器,相当于默认省略了super();
子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
一个类可以被多个子类继承。
子类不能直接访问父类中私有的(private)的成员变量和方法。(可通過getter,setter访问私有属性)
子类直接继承的父类,称为:直接父类;间接继承的父类称为:间接父类。
Java只支持单继承和多层继承,不允许多重继承。
class SubDemo extends Demo{ } //ok
class SubDemo extends Demo1,Demo2...//error
如果我们没显式的声明一个类的父类的话,则此类继承于java.lang.Object类;
所的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类(所的java类具有java.lang.Object类声明的功能)。
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
class Circle{ public double findArea(){}//求面积 } class Cylinder extends Circle{ public double findArea(){}//求表面积 }
要求:
子类重写的方法必须和父类被重写的方法具有相同的方法名称、形参列表(区分于重载)
子类重写的方法的**返回值类型不能大于**父类被重写的方法的返回值类型
如果父类被重写方法返回值类型是void,子类重写方法返回值类型只能是void
父类被重写的方法的返回值类型是==A类型,则子类重写的方法的返回值类型可以是A类或A类的子类==
父类被重写的方法的返回值类型是==基本数据类型(比如:double),则子类重写的方法的返回值类型==必须是
相同的基本数据类型(必须也是double)
子类重写的方法使用的**访问权限不能小于**父类被重写的方法的访问权限
子类方法**抛出的异常不能大于**父类被重写方法的异常
注意:
public class StaticTest { public static void main(String[] args) { A.a();//输出为A B.a();//输出为B } } class A { public static void a() { System.out.println("A"); } } class B extends A { public static void a() { System.out.println("B"); } }
例题 1.写一个名为 Account 的类模拟账户。该类的属性和方法如下图所示。该类包括的属性:账号 id,余额 balance,年利率 annualInterestRate;包含的方法:访问器方法(getter 和setter 方法),返回月利率的方法 getMonthlyInterest(),取款方法 withdraw(),存款方法deposit()。
public class Account { private int id;//id private double balance;//本金 private double annualInterestRate;//年利率 //构造器 public Account(int id, double balance, double annualInterestRate) { this.annualInterestRate = annualInterestRate; this.balance = balance; this.id = id; } //gette、setter public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public double getAnnualInterestRate() { return annualInterestRate; } public void setAnnualInterestRate(double annualInterestRate) { this.annualInterestRate = annualInterestRate; } public int getID() { return id; } public void setID(int id) { this.id = id; } //月利率getter public double getMonthlyInterest() { return annualInterestRate / 12; } //方法withdraw public void withdraw(double amount) { if(amount < balance){ balance -= amount; }else { System.out.println("您的余额不足!"); } } //方法deposit public void deposit(double amount) { balance += amount; } }
2.创建 Account 类的一个子类 CheckAccount 代表可透支的账户,该账户中定义一个属性
overdraft 代表可透支限额。在 CheckAccount 类中重写 withdraw 方法,其算法如下:
如果(取款金额<账户余额),可直接取款
如果(取款金额>账户余额),计算需要透支的额度判断可透支额 overdraft 是否足够支付本次透支需要。如果可以
将账户余额修改为 0,冲减可透支金额。如果不可以提示用户超过可透支额的限额。
public class CheckAccount extends Account { // 透支额度声明 double overdraft; public CheckAccount(int id, double balance, double annualInterestRate, double overdraft) { // super调用父类构造器 super(id, balance, annualInterestRate); // 透支额度赋值 this.overdraft = overdraft; } public void withdraw(double amount) { if (amount < getBalance()) {// balance为父类私有化变量,子类只可用get、set方法调用 // super调用父类被重写方法 super.withdraw(amount);// setBalance(getBalance() - amount); } else { double neededOverdraft = amount - getBalance();//声明需要透支的钱数 if (neededOverdraft <= overdraft) { setBalance(0); overdraft -= neededOverdraft; } else { System.out.println("超过可透支额的限额"); } } } }
① 二者的概念:重载指在一个类中存在两个或者两个以上的同名不同形参的方法。重写指子类的方法将父类的同名同参方法覆盖。
② 重载和重写的具体规则,重载对返回值类型、权限修饰符、抛出的异常没有要求,重载方法之间为并列关系。重写要求子类重写方法返回值类型不大于父类被重写方法返回值类型(void,基本数据类型两者一致;引用数据类型子类的返回值类型必须和父类返回值类型相同或是其子类)。重载要求子类重写方法的访问权限不小于父类被重写方法,父类重写方法为private时不能被重写。子类方法**抛出的异常不能大于**父类被重写方法的异常。
③ 重载:不表现为多态性。
重写:表现为多态性。
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;而对于多态,只等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
从结果上看:(继承性)
从过程上看:当我们通过子类的构造器创建子类对象时,我们一定会直接(直接通过super(形参列表)
)或间接(通过this(形参列表1)
------>this(形参列表n)
------>super(形参列表)
)的调用其父类的构造器,进而调用父类的父类的构造器,……… 直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所的父类的结构,所以才可以看到内存中父类中的结构,子类对象才可以考虑进行调用。
注:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
即父类的引用指向子类的对象(即子类的对象赋给父类的引用)。父类 变量名 = new 子类();
多态性提高了代码的通用性,常称作接口重用。
Person p = new Man();
Object obj = new Date();
**使用前提:**① 类的继承关系 ② 方法的重写
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
举例1:
//增强代码的通用性,减少了方法重载的使用 public static void show(Animal animal){ animal.eat(); animal.shout(); } /* public void show(Dog dog){ dog.eat(); dog.shout(); } public void show(Cat cat){ dog.eat(); dog.shout(); } */ public class test{//Dog,Cat均为Animal子类,都重写了eat(),short()方法 public Static void main(String[] args){ show(new Dog()); show(new Cat()); shou(new Animal()) }
举例2:
public void method(Object obj){ }
注:对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)。即多态情况下调用的属性仍是父类的属性。
向上转型(多态):从子类到父类的类型转换可以自动进行,如Object obj = "Hello";
向下转型:从父类到子类的类型转换必须通过造型(强制类型转换)实现,如 String objStr = (String) obj;
向下转型使用前提:被强转的父类对象是子类对象的实例,即**被强转的父类对象 instanceof 子类 == true
**
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。使用向下转型(强制类型转换)才能调用子类特的属性和方法。
注:
对于多态性的理解?
① 实现代码的通用性。
② Object类中定义的public boolean equals(Object obj){ }
JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
③ 抽象类、接口的使用肯定体现了多态性。(抽象类、接口不能实例化)
多态是编译时行为还是运行时行为?
多态是运行时行为。
可以调用的结构:属性、方法;构造器
this理解为:当前对象 或 当前正在创建的对象
this**调用属性、方法**:
格式:this.属性
或this.方法
在类的方法(构造器)中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,通常情况下都择省略"this."。特殊情况下,如果方法(构造器)的形参和类的属性同名时,必须显式的使用"this.变量"的方式,表明此变量是当前对象的(当前正在创建对象的)属性,而非形参。
class Person { private int age; private String name; //构造器中使用this调用属性,方法 public Person(int age,String name){ this.age = age; this.setName(name); } //方法中使用this调用属性 public void setAge(int age) { this.age = age; } public void setName(String name) { this.name = name; }
this==调用构造器==
格式 : this(形参列表)
① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
② 构造器中不能通过"this(形参列表)"方式调用自己
③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
④ 规定:"this(形参列表)"必须声明在当前构造器的首行
⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器。
class Person { private String name; private int age; public Person() { // 无参构造器 System.out.println("新对象实例化"); } public Person(String name) { this(); // 调用本类中的无参构造器 this.name = name; } public Person(String name, int age) { this(name); // 调用有一个参数的构造器 this.age = age; } public String getInfo() { return "姓名:" + name + ",年龄:" + age; } }
在源文件中显式的使用import结构导入指定包
声明格式:package 顶层包名.子包名 ;
eclipse导包快捷键:crtl+shift+o
应用:MVC设计模式
MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。这种将程序输入输出、数据处理,以及数据
的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
JDK中主要的包介绍
在源文件中显式的使用import结构导入指定包下的类、接口
声明格式
import 顶层包名.子包名.类/接口名
声明在包的声明和类的声明之间
可以使用**“xxx.*”**的方式,表示可以导入xxx包下的所有结构
java.lang包下定义的类或接口,则可以省略import结构
如果使用的类或接口是本包下定义的,则可以省略import结构
如果在源文件中,使用了不同包下的同名的类,则必须至少一个类需要以全类名的方式显示。
其中,第一个Account类为com.atguigu.exer3包下,已用import导入;第二个重名Account类为com.atguigu.exer4下,需要以全类名方式显示,而不能再用import导入;
使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
import static:导入指定类或接口中的静态结构:属性或方法。
在Java类中使用super来调用父类中的指定操作:
super.属性
super.方法
super(形参列表)
super(形参列表)
必须声明在首行;this(参数列表)
或者super(参数列表)
语句指定调用本类或者父类中相应的构造器。同时,只能”二选一” 。this(形参列表)
或super(形参列表)
,则默认调用的是父类中空参的构造器super()
。super(形参列表)
,调用父类中的构造器。(因为不能所有构造器都使用this(形参列表)
)注:
区别点 | this | super |
---|---|---|
调用属性 | 访问本类中的属性,如果本类没有 此属性则从父类中继续查找 | 直接访问父类中的属性 |
调用方法 | 访问本类中的方法,如果本类没有 此方法则从父类中继续查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
父类Cirle
public class Circle { private double radius; public Circle() { } public Circle(int radius) { this.radius = radius; } public void setRadius(double radius) { this.radius = radius; } public double getRadius() { return radius; } public double findArea() {// 求圆表面积 return Math.PI * radius * radius; } }
子类Cylinder
public class Cylinder extends Circle { private double length; public Cylinder() { } // super调用构造器 public Cylinder(int length, int radius) { super(radius); this.length = length; } public void setLength(double length) { this.length = length; } public double getLength() { return length; } // 方法重写,求圆柱表面积 public double findArea() { // super调用方法 return super.findArea() + 2 * getRadius() * length; } //super调用方法 public double findVolume() { return super.findArea() * length; } }
x instanceof A
:检验x是否为类A的对象,返回值为boolean型。
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
如果x属于类A的子类B,x instanceof A值也为true。
static关键字的引入:当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
适用范围: 在Java类中,可用static修饰属性、方法、代码块、内部类,不能用来声明构造器;
作用:类的成员被static修饰后
类变量(类属性)由该类的所有实例共享;访问方式:①**类名.类属性
,②有对象的实例时对象名.类属性
**。通过某一对象/或类修改类变量后,所有对象中该类变量均被修改。
类变量随着类的加载而加载,在内存中也只会存在一份:存在方法区的静态域中。
类变量举例:System.out
、Math.PI
类.静态方法
的方式进行调用。有对象的实例时,可以通过对象.静态方法
**调用。 如何判定属性和方法应该使用static关键字:
关于静态属性
关于方法
例题 编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。(提示:利率,最小余额需要设置为静态的)
class Account { private int id; private String passWord; private double balance; private static double interestRate = 0.03; private static double minBalance = 1.0; private static int idInit = 1000;// id初始值 Account() { id = idInit++;//id自动赋值 } Account(String passWord, double balance) { this();// 调用空参构造器,相当于id = idInit++; this.passWord = passWord; this.balance = balance; } //getter & setter public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } public static double getInterestRate() { return interestRate; } public static void setInterestRate(double interestRate) { Account.interestRate = interestRate; } public static double getMinBalance() { return minBalance; } public static void setMinBalance(double minBalance) { Account.minBalance = minBalance; } public int getId() { return id; } public double getBalance() { return balance; } @Override public String toString() { return "Account [id=" + id + ", passWord=" + passWord + ", balance=" + balance + "]"; } }
注:
设计模式是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方式。
23种经典的设计模式:
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
代码的实现:
class Bank { // 1.私有化的构造器 private Bank() { } // 2.内部创建类的私有对象(因为提供的调用对象方法是静态的,所以对象声明为静态) private static Bank instance = new Bank(); // 3.提供公共的方法,返回类的私有对象(返回对象的方法为静态方法) public static Bank getInstance() { return instance; } }
class Order { // 1.私有化的构造器 private Order() { } // 2.声明当前类的私有对象,不初始化 private static Order instance = null; // 3.提供公共的方法,返回类的私有对象(返回对象的方法为静态方法) public static Order getInstance(){ if(instance == null){ instance = new Order(); } return instance; } }
解决线程安全方式一:同步方法
class Bank{ private Bank(){ }; private static Bank instance = null; //解决线程安全方式一:同步方法 public static synchronized Bank getInstance(){//同步监视器为Bank.class; synchronized (Bank.class) { if(instance == null){ instance = new Bank(); } return instance; } }
解决线程安全方式二:同步代码块
class Bank{ private Bank(){ }; private static Bank instance = null; //同步方法方式一,效率较低 public static synchronized Bank getInstance(){ synchronized (Bank.class) {//同步监视器设置为Bank.class if(instance == null){ instance = new Bank(); } return instance; } //同步方法方式二,效率高 public static synchronized Bank getInstance(){ if (instance == null) {//将同步代码块用if判断语句包裹 synchronized (Bank.class) {//同步监视器设置为Bank.class if(instance == null){ instance = new Bank(); } } } return instance; }
饿汉式与懒汉式区别:饿汉式对象加载时间过长,但线程是安全的。懒汉式延迟对象的创建。
单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
如: java.lang.Runtime
main()
方法由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
main()方法也是一个普通的静态方法
main()方法可以作为我们与控制台交互的方式。(使用Scanner也可以)
方式:编译 javac 类名.java
运行 java 类名 "参数1" "参数2" "参数3" "参数4"
参数为字符串类型,之间用空格连接,可不加双引号。
public class CommandPara { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println("args[" + i + "] = " + args[i]); } } }
编译后运行 java CommandPara “Tom" “Jerry" “Shkstart"
输出为 :args[0] = Tom
args[1] = Jerry
args[2] = Shkstart
在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。
final标记的类不能被继承。提高安全性,提高程序的可读性。
final标记的方法不能被子类重写。
final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。
注:final修饰的属性可通过构造器赋值使不同对象拥有不同的final属性值;
public FinalField { final int PROPERTY; public FinalField(int property) { this.PROPERTY = property; } }
排错题
class Something { public static void main(String[] args) { Other o = new Other(); new Something().addOne(o); } public void addOne(final Other o) { //o = new Other();//编译报错,形参对象成为常量,不能再new了; o.i++;//可以执行,对象成为常量,但其属性可以修改; } } class Other { public int i; }
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
abstract可以用来修饰 :类、方法
用abstract关键字来修饰一个类,这个类叫做抽象类。
用abstract来修饰一个方法,该方法叫做抽象方法。抽象类中声明抽象方法但不提供实现,该方法的实现由子类提供。
权限修饰符 abstract 返回值类型 方法名(形参列表);
抽象方法只有方法的声明,没有方法体,直接用分号结束,不用{},使用形参为抽象类对象的方法时,如果只需使用一次的情况下,多使用匿名类new 抽象类(形参){重写的抽象方法}
。
public class NonnameType { public static void nonnameMethod(A a) { } public static void main(String[] args) { //匿名类的匿名对象 nonnameMethod(new A() { void method() { System.out.println("匿名类"); } }); } } abstract class A { abstract void method(); }
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
例 : 计算某段代码运行时间的方法
abstract class Template { //计算某段代码运行的时间 public final void spendTime() { long start = System.currentTimeMillis(); code();//需要执行的代码是不确定的,不确定的易变的部分抽象化,由子类实现 long end = System.currentTimeMillis(); System.out.println(end - start); } //不确定的易变的部分抽象化,由子类实现 public abstract void code(); } class SubTemplate extends Template { //输出1000以内所有质数的方法 public void code(){ for(int i = 2; i <= 1000;i++){ boolean isFlag = true; for(int j = 2; j <= Math.sqrt(i);j++){ if (i % j == 0){ isFlag = false; break; } } if(isFlag){ System.out.println(i); } } } }
模板方法设计模式应用:
属性:无
构造器 :空参构造器
方法:equals() / toString() / getClass() /hashCode() / clone() / finalize()
wait() 、 notify()、notifyAll()
注意:数组也是Objcet类的子类,可调用Object类中方法
==
运算符==
才返回true。即比较地址值。Person p1=new Person(); Person p2=new Person(); if (p1==p2){ … }
注意:用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错。
obj1.equals(obj2)
//Objecr类中equals()源码 public boolean equals(Object obj) { return (this == obj); }
2.可以进行重写。像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
3.通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写,来比较两个对象的实体内容是否相同。
Customer类,属性:String name;int age
自己重写:
public boolean equals(Object obj) { // 先看两个对象地址值是否相同 if (this == obj) { return true; } // 看形参对象obj是否是改写方法的类的实例 if (obj instanceof Customer) {//应该用if (getClass() != obj.getClass()) return false; 用instanceof有弊端 // 强转obj Customer newCust = (Customer) obj; // 比较两个对象每个属性是否都相同,基本数据类型用==,引用数据类型用equals return this.name.equals(newCust) && this.age == newCust.age; } return false;
自动重写:
public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Customer other = (Customer) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; }
手动重写弊端(instanceof弊端)
public class OverWriteTest { public static void main(String[] args) { Person1 person = new Person1("李华", 18); Student1 student = new Student1("李华", 18); System.out.println(person.equals(student));//true,两者类型不同理应为false } } // 父类Person1 class Person1 { String name; int age; public Person1() { } public Person1(String name, int age) { this.age = age; this.name = name; } //系统改写后person.equals(student)输出为false /*@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person1 other = (Person1) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; }*/ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof Person1) { Person1 person = (Person1) obj; if (this.name.equals(person.name) && this.age == person.age) { return true; } } return false; } } // 子类Student1 class Student1 extends Person1 { String name; int age; public Student1() { } public Student1(String name, int age) { super(name, age); } }
解释:
父类对象person,属性 name = “李华”, age = 18。
子类对象student,通过父类带参构造器创建,内含属性:①来源于父类:name = “李华”, age = 18 ②来源于自身 name = null, age = 0(未赋值,默认值)。person.equals(student)
执行时,if (obj instanceof Person1)
通过,进入内部,Person1 person = (Person1) obj;
,将student向上转化为父类,随后调用的为来源于父类的name,age作比较,与父类对象person一致,返回true。
而用系统自动重写的equals方法在if (getClass() != obj.getClass())
两者类型不同就return了false。
equals()
是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
Object类中toString()的定义:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
特点:
Date now=new Date(); System.out.println(“now=”+now); //相当于 System.out.println(“now=”+now.toString());
3.像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,**返回"实体内容"**信息。
如String 类重写了toString()方法,返回字符串的值。
s1=“hello”; System.out.println(s1);//相当于System.out.println(s1.toString()); 输出为hello
int a=10; System.out.println(“a=”+a) //相当于 System.out.println(“a=”+ new Integer(a).toString() )
toString方法的重写:
Customer类,属性:String name;int age
@Override public String toString() { return "Customer [age=" + age + ", name=" + name + "]"; }
为了使基本数据类型的变量具有类的特征,引入包装类。(主要作用:①调用Object类的方法 ②多态)
int num1 = 10; Integer int1 = new Integer(num1); //字符串也是可以的 Integer int2 = new Integer("123"); Float f = new Float(“4.56F”); Float f = new Float(“4.56”); Long l = new Long(“asdf”);报错 NumberFormatException,自动装箱,自动拆箱需要类型匹配
特殊的,Boolean类型的构造器
只要形参不是明确的true或"true"(字符串内不区分大小写),返回值即为false;
@Test public void test9() { Boolean isTrue = new Boolean("TruE"); Boolean isTrue1 = new Boolean("TruEsaf123"); System.out.println(isTrue);//true System.out.println(isTrue1);//false }
Integer int1 = 3; Float f1 = 3.0F;
xxxValue()
方法:Integer int1 = new Integer(12); int a = int1.intValue(); Float f1 = new Float(123.4); float f = f1.floatValue();
// 自动装箱 Integer int1 = 3; Float f1 = 3.0F; // 自动拆箱 int a = int1; float f2 = f1; double d1 = new Double(2.4);
注:自动装箱,自动拆箱需要类型匹配;
基本类型/包装类 ----> String
1.1 连接符+
(自动调用其包装类toString()方法)
float f1 = new Float(1.43); String s1 = "" + f1; //相当于 String s1 = "" + f1.toString();
1.2 String重载的方法valueOf(Xxx xxx)
String fstr = String.valueOf(2.34f); String s2 = String.valueOf(1.43);
2.1 通过包装类的构造器实现
int i = new Integer(“12”); Float f = new Float("12F"); Float f1 = new Float("12")
2.2 调用包装类的**parseXxx(String s)
**静态方法
String s = "567"; int i = Integer.parseInt(s); Float f = Float.parseFloat(“12.1”);
例题1
public void test6() { Object o1 = true ? new Integer(1) : new Double(2.0); System.out.println(o1);// 1.0 , 三目运算符自动类型提升 }
public void test7() { Object o2; if (true) o2 = new Integer(1); else o2 = new Double(2.0); System.out.println(o2);// 1 }
例题2
public void test8() { /* * Integer内部定义了IntegerCache内部类,IntegerCache中定义了Integer[], * 保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在 * -128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率 */ Integer i = new Integer(1); Integer j = new Integer(1); System.out.println(i == j);// false,相当于new了i和j两个对象,两者指向堆中不同地址 Integer m = 1; Integer n = 1; System.out.println(m == n);// true,m和n均指向方法区中同一地址值 // 内部类IntegerCache中Integer[]数组范围[-128,127] Integer x = 128; Integer y = 128; System.out.println(x == y);// false ,相当于new了两个对象x和y,两者指向堆中不同地址 }
例题3
利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。
提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需要动态伸缩。
创建Vector对象:Vector v=new Vector();
给向量添加元素:v.addElement(Object obj); //obj必须是对象
取出向量中的元素:Object obj=v.elementAt(0);
注意第一个元素的下标是0,返回值是Object类型的。
计算向量的长度:v.size();
若与最高分相差10分内:A等;20分内:B等;30分内:C等;其它:D等
import java.util.Scanner; import java.util.Vector; public class ScoreTest { public static void main(String[] args) { // 1.实例化Scanner Scanner scan = new Scanner(System.in); // 2.创建Vector对象 Vector v = new Vector(); int max = 0; // 3.学生成绩填充向量v System.out.println("请输入学生成绩(以负数代表输入结束)"); while (true) { int grade = scan.nextInt(); if (grade < 0) { break; } if (grade > 100) { System.out.println("成绩非法,请重新输入"); continue; } // 4.求成绩最大值 if (grade > max) { max = grade; } v.addElement(grade);// 自动装箱 } System.out.println("最高成绩:"+ String.valueOf(max)); // 5.遍历v向量,得到每个学生成绩,划分成绩等级并输出 for (int i = 0; i < v.size(); i++) { //obj强转为Integer类,自动拆箱为int int grade = (Integer) v.elementAt(i); String level; if (max - grade <= 10) { level = "A"; } else if (max - grade <= 20) { level = "B"; } else if (max - grade <= 30) { level = "C"; } else { level = "D"; } System.out.println("第" + (i + 1) + "个学生的成绩为" + grade + ", 等级为" + level); } } }
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
接口的本质是契约,标准,规范,就像我们的法律一样,制定好后实现接口的类必须要遵守。
继承类是一个 “B is a A”(B是A)的概念,而接口实现则是 “能不能”,“支不支持”,"可不可以"的关系。
使用interface来定义 public interface 接口名{}
;
Java中,接口和类是并列的两个结构,或理解为接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
接口中的成员 :
public static final 常量类型 常量名
和抽象方法public abstract 返回值类型 方法名();
;public static 返回值类型();
、默认方法;接口中的所有成员变量都默认是由public static final
修饰的,意味着接口内声明全局变量可以省略public static final
;
接口中的所有抽象方法都默认是由public abstract
修饰的,意味着接口内抽象方法可省略public abstract
;
接口不可以实例化,不能定义构造器,代码块;
接口采用多继承机制 inteface 接口A extends 接口B,接口C,接口D{}
;
通过implements定义类完成接口的实现:先写extends,后写implements。且一个类可以实现多个接口;
class SubClass extends SuperClass implements InterfaceA, InterfaceB , InterfaceC{ }
//接口USB interface USB { void start(); void stop(); } class Computer { //接口的多态性 public void transferDate(USB usb) { usb.start(); System.out.println("传输数据"); usb.stop() ; } } //接口的实现类 class Flash implements USB { public void start() { System.out.println("U盘启动"); } public void stop() { System.out.println("U盘停止"); } } class Printer implements USB { public void start() { System.out.println("开始打印"); } public void stop() { System.out.println("停止打印"); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rfptFY2Q-1643773660067)(G:\aaa笔记\面向对象 .assets\image-20220123174123432.png)]
接口的主要用途就是被实现类实现。我们在应用程序中,调用的结构都是JDBC中定义的接口,不会出现具体某一个数据库厂商的API。
排错题
interface A { int x = 0; } class B { int x = 1; } class C extends B implements A { public void pX() { System.out.println(x);//编译不通过,x是不明确的 System.out.println(super.x);//1 System.out.println(A.x);//全局常量0 } public static void main(String[] args) { new C().pX(); } }
interface Playable { void play(); } interface Bounceable { void play(); } interface Rollable extends Playable, Bounceable { Ball ball = new Ball("PingPang"); } class Ball implements Rollable { private String name; public String getName() { return name; } public Ball(String name) { this.name = name; } public void play() {//对实现的三个接口的同名方法都进行了重写,没问题 ball = new Ball("Football");//ball在接口中定义,是全局常量,不能再new了 System.out.println(ball.getName()); } }
代码题 定义一个接口用来实现两个对象的比较。 interface CompareObject{public int compareTo(Object o);
//若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小}
定义一个Circle类,声明redius属性,提供getter和setter方法
定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
public class InterfaceTest { public static void main(String[] args) { ComparableCircle cir1 = new ComparableCircle(); ComparableCircle cir2 = new ComparableCircle(); cir1.setRedius(1.4); cir2.setRedius(1.5); System.out.println(cir1.compareTo(cir2)); } } interface CompareObject { public int compareTo(Object o);// 若返回值是 0, 代表相等; 若为正数,代表当前对象大;负数代表当前对象小 } class Circle { private double redius; public double getRedius() { return redius; } public void setRedius(double redius) { this.redius = redius; } } class ComparableCircle extends Circle implements CompareObject{ @Override public int compareTo(Object o) { if(this == o) return 0; if(o instanceof Circle){ //Object o强转为Circle Circle circle = (Circle) o ; if(this.getRedius() == circle.getRedius()) return 0 ; if(this.getRedius() > circle.getRedius()) return 1; if(this.getRedius() < circle.getRedius()) return -1 ; } throw new RuntimeException("传入的对象不匹配");//抛出异常 } }
可以为接口添加静态方法和默认方法(均默认为public)。
1.静态方法
使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体接口名.静态方法名
。
2.默认方法
默认方法使用 default 关键字修饰。可以通过实现类对象来调用。java 8 API中对Collection、List、Comparator等接口提供了丰富的默认
方法。
如果实现类重写了接口中的默认方法,调用时调用的是重写以后的方法。
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法。–>类优先原则(不适用于属性,若父类和接口存在同名同类型属性,则子类(实现类)中都包含父类和接口中的同名同类型属性)
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么该实现类必须重写默认方法–>否则报错,接口冲突。
在子类(或实现类)的方法中可以调用父类、接口中被重写的方法
public void myMethod(){ method3();//1.调用自己定义的重写的方法 : 方法名(); super.method3();//2.调用的是父类中声明的 : super.方法名(); //3.调用接口中的默认方法 接口名.super.方法名(); CompareA.super.method3(); CompareB.super.method3(); }
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为被代理对象提供一个代理对象以控制对被代理对象的访问。
//接口 interface NetWork{ void browse(); } //被代理类 class Server implements NetWork{ @Override public void browse(){ System.out.println("真实的服务器访问网络"); } } //代理类 class ProxyServer implements NetWork{ //声明被代理类的对象 private NetWork netWork; //建立构造器初始化被代理的对象 public ProxyServer(NetWork network){ this.netWork = network; } //代理类的工作方法 public void check(){ System.out.println("联网之前的检查工作"); } @Override public void browse() { //代理类的工作 check(); //代理类的工作执行完后,通过代理类调用被代理类执行代理类该自己做的工作 netWork.browse(); } }
代理设计模式的应用:
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
具体模式:
简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
在开发中,通常一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
相同点:不能实例化;都可以包含抽象方法;都通过对象的多态性产生实例化对象;
不同点:
抽象类是包含抽象方法的类 , 接口是抽象方法和全局常量的集合(jdk8后包含默认方法,静态方法)。
两者组成不同,抽象类主要由构造器、抽象方法、普通方法、常量、变量组成;接口由抽象方法、默认方法、静态方法、全局常量组成,不能带构造器、普通方法、变量、代码块;
使用方法上,一个是子类继承抽象类(extends) ,一个是子类实现接口(implements);
抽象类有单继承的局限 ,接口可以多继承,如果抽象类和接口都可以使用的话,优先使用接口避免单继承的局限;
抽象类可以实现多个接口,接口只能继承接口,不能继承抽象类,但允许继承多个接口。
Object{
@Override
public int compareTo(Object o) {
if(this == o)
return 0;
if(o instanceof Circle){
//Object o强转为Circle
Circle circle = (Circle) o ;
if(this.getRedius() == circle.getRedius())
return 0 ;
if(this.getRedius() > circle.getRedius())
return 1;
if(this.getRedius() < circle.getRedius())
return -1 ;
} throw new RuntimeException("传入的对象不匹配");//抛出异常
}
}
### java8接口新特性 可以为接口添加静态方法和默认方法(均默认为public)。 **1.静态方法** 使用 **static** 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体`接口名.静态方法名`。 - 与类的静态方法不同,**接口的静态方法==只能通过接口来调用==**,不可以通过实现接口的对象调用。 **2.==默认方法==** 默认方法使用 **default** 关键字修饰。可以**通过实现类对象来调用**。java 8 API中对Collection、List、Comparator等接口提供了丰富的默认 方法。 - 如果实现类**重写了**接口中的**默认方法**,调用时**调用的是重写以后的方法**。 - 如果**子类(或实现类)继承的父类和实现的接口**中声明了==同名同参数==的默认方法,那么**子类在没重写此方法的情况下**,**默认调用的是==父类==中的同名同参数的方法。**-->**类优先原则**(不适用于属性,若父类和接口存在同名同类型属性,则子类(实现类)中都包含父类和接口中的同名同类型属性) - **如果**实现类**实现了多个接口**,而这多个接口中定义了**同名同参数的默认方法**,那么**该实现类必须重写默认方法**-->**否则报错,接口冲突。** - 在**子类(或实现类)的==方法中==可以==调用==父类、接口中==被重写的方法==** ```java public void myMethod(){ method3();//1.调用自己定义的重写的方法 : 方法名(); super.method3();//2.调用的是父类中声明的 : super.方法名(); //3.调用接口中的默认方法 接口名.super.方法名(); CompareA.super.method3(); CompareB.super.method3(); }
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为被代理对象提供一个代理对象以控制对被代理对象的访问。
//接口 interface NetWork{ void browse(); } //被代理类 class Server implements NetWork{ @Override public void browse(){ System.out.println("真实的服务器访问网络"); } } //代理类 class ProxyServer implements NetWork{ //声明被代理类的对象 private NetWork netWork; //建立构造器初始化被代理的对象 public ProxyServer(NetWork network){ this.netWork = network; } //代理类的工作方法 public void check(){ System.out.println("联网之前的检查工作"); } @Override public void browse() { //代理类的工作 check(); //代理类的工作执行完后,通过代理类调用被代理类执行代理类该自己做的工作 netWork.browse(); } }
代理设计模式的应用:
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
具体模式:
简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
在开发中,通常一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
相同点:不能实例化;都可以包含抽象方法;都通过对象的多态性产生实例化对象;
不同点: