基本概念理解
面向对象与面向过程
二者都是一种思想,面向对象是相对于面向过程而言的。
-
面向过程(Procedure Oriented Programming),强调的是功能行为,以函数为最小单位,考虑怎么做。
-
面向对象(Object Oriented Programming),将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
理解"万事万物皆对象"
- 在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构
- Scanner,String等
- 文件:File
- 网络资源:URL
-
涉及到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,编译不通过。)
-
**在不同类中的访问机制:**先创建要访问类的对象,再用对象访问类中定义的成员。
属性(field)
在方法体外,类体内声明的变量称为属性(成员变量)。
在方法体内部声明的变量称为局部变量。
语法格式
语法格式:
修饰符 数据类型 属性名 = 初始化值 ;
说明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);
}
}
方法(method)
方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
将功能封装为方法的目的是,可以实现代码重用,简化代码。
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;
}
}
}
}
}
方法的重载(overload)
定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
总结:“两同一不同”:
同:同一个类、相同方法名
不同: 参数列表不同(参数个数不同,参数类型不同) 与形参名 权限修饰符无关!
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;
}
}
可变个数的形参 Varargs (variable number of arguments)
-
声明格式:方法名(参数的类型名 …参数名) 如
public void show(String … strs){}
- 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
- 可变个数形参的方法与同名的方法之间,彼此构成重载
- 可变参数方法的使用与方法参数部分使用数组是一致的
- 可变形参需要放在形参声明的最后(避免输入形参产生歧义)
- 在一个方法的形参位置,最多只能声明一个可变个数形参 (避免输入形参产生歧义)
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出栈。
递归方法(recursion)
递归方法:一个方法体内调用它自身。
方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
例题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();
}
构造器(constructor)
语法格式
权限修饰符 类名 (参数列表) {
初始化语句;
}
分类:
-
隐式无参构造器(系统默认提供)
-
显式定义一个或多个构造器(无参、有参)
有参 | 无参 |
---|---|
使用说明
-
如果没有显示定义类的构造器的话,则系统默认提供一个空参的构造器(该空参构造器权限修饰符与类相同)
-
一个类中定义的多个构造器,彼此构成重载
-
一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
-
一个类中,至少会有一个构造器。
非静态属性赋值的过程:
赋值的位置:
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过“对象.属性“或“对象.方法”的方式赋值
⑤通过非静态代码块赋值
赋值的先后顺序:
① - ② /⑤- ③ - ④
javabean
JavaBean是一种Java语言写成的可重用组件。 所谓javaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性(一般private权限修饰),且有对应的私有属性get、set方法
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的
JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而
不用关心任何改变。
代码块(block)
代码块(初始化块)的作用:对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,且调用父类空参构造器前先调用父类的非静态代码块初始化类。
内部类(Inner class)
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
内部类分类:
-
成员内部类:静态(static修饰)成员内部类,非静态成员内部类。
-
局部内部类 :方法内、代码块内、构造器、内部类内。
成员内部类
-
成员内部类作为类的成员的角色:
- 和外部类不同,可以声明为private或protected;
- 可以调用外部类的结构;
- 静态成员内部类不能使用外部类的非static的成员变量;
-
成员内部类作为类的角色:
- 可以在内部定义属性、方法、代码块、构造器、内部类;
- 可以声明为abstract类 ,因此可以被其它的内部类继承
- 可以声明为final的
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
-
可以被除外部类的其他类继承
- 对于静态内部类
extends 外部类.静态内部类
- 对于非静态内部类:一个内部类必须通过外部类的实例才能访问,所有得有一个带有外部类对象形参的构造方法,并且在构造方法中需要调用外部类的super方法。(很少用)
- 对于静态内部类
class AAA extends Person2.Brain{//Brain为Person2的静态成员内部类 } class BB extends Person2.Hands{//Hands为Person2的非静态内部类 public BB(Person2 person){ person.super(); } }
注:
- 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
- 外部类访问成员内部类的成员,需要“**内部类.成员”(对于静态成员内部类)或“内部类对象.成员”(对于非静态成员内部类)**的方式
- 成员内部类可以直接使用外部类的所有成员,包括私有的
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();
}
}
}
- 当想要在外部类的静态成员部分使用内部类时,内部类必须声明为静态的
局部内部类
- 只能在声明它的方法、代码块、构造器、内部类中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
- 局部内部类可以使用外部类的成员,包括私有的。
-
方法中的局部内部类可以使用外部方法的局部变量,但是必须是final的(jdk 7及之前版本:要求此局部变量显式的声明为final的
jdk 8及之后的版本:可以省略final的声明)。由局部内部类和局部变量的声明周期不同所致。
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,protected,缺省,private
- 局部内部类不能使用static修饰,因此也不能包含静态成员
局部内部类的应用:返回一个实现接口对象
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;
}
};
}
}
面向对象三大特征
封装性(Encapsulation)
我们程序设计追求==“高内聚,低耦合”==。
-
高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
-
低耦合 :仅对外暴露少量的方法用于使用。
即隐藏对象内部的复杂性,只对外公开简单的接口,便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该
暴露的暴露出来。这就是封装性的设计思想。
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的
制约。除此之外,没其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件(如客户输入自身年龄,年龄不能为
负数)。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加,避免用户再使用"对象.属性"的方式对属性进行赋值。则需
要将属性声明为私有的(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类只可以被同一个包内部的类访问。
继承性(inhertance)
① 减少了代码的冗余,提高了代码的复用性 ② 便于功能的扩展 ③ 为多态性的使用,提供了前提
语法规则
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类声明的功能)。
方法的重写(override/overwrite)
在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
class Circle{
public double findArea(){}//求面积
}
class Cylinder extends Circle{
public double findArea(){}//求表面积
}
要求:
-
子类重写的方法必须和父类被重写的方法具有相同的方法名称、形参列表(区分于重载)
-
子类重写的方法的**返回值类型不能大于**父类被重写的方法的返回值类型
-
如果父类被重写方法返回值类型是void,子类重写方法返回值类型只能是void
-
父类被重写的方法的返回值类型是==A类型,则子类重写的方法的返回值类型可以是A类或A类的子类==
-
父类被重写的方法的返回值类型是==基本数据类型(比如:double),则子类重写的方法的返回值类型==必须是
相同的基本数据类型(必须也是double)
-
-
子类重写的方法使用的**访问权限不能小于**父类被重写的方法的访问权限
- 子类不能重写父类中声明为private权限的方法
-
子类方法**抛出的异常不能大于**父类被重写方法的异常
注意:
- 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(static修饰方法不具备多态性,多态性是运行时行为,static修饰的方法随类的加载而加载,是编译期行为,不能被重写,只能被重新定义)。
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");
}
}
- 私有方法(private修饰)不可被重写
例题 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的子类对象。
多态性(Polymorphism)
即父类的引用指向子类的对象(即子类的对象赋给父类的引用)。父类 变量名 = 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){
}
注:对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)。即多态情况下调用的属性仍是父类的属性。
对象类型转换 (Casting )
向上转型(多态):从子类到父类的类型转换可以自动进行,如Object obj = "Hello";
向下转型:从父类到子类的类型转换必须通过造型(强制类型转换)实现,如 String objStr = (String) obj;
向下转型使用前提:被强转的父类对象是子类对象的实例,即**被强转的父类对象 instanceof 子类 == true
**
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。使用向下转型(强制类型转换)才能调用子类特的属性和方法。
注:
- 无继承关系的引用类型间的转换是非法的
- 使用强转时,可能出现ClassCastException的异常,在造型前可以使用instanceof操作符测试一个对象的类型。
对于多态性的理解?
① 实现代码的通用性。
② Object类中定义的public boolean equals(Object obj){ }
JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
③ 抽象类、接口的使用肯定体现了多态性。(抽象类、接口不能实例化)
多态是编译时行为还是运行时行为?
多态是运行时行为。
关键字
this
可以调用的结构:属性、方法;构造器
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;
}
}
pacakage与import
package
在源文件中显式的使用import结构导入指定包
声明格式:package 顶层包名.子包名 ;
eclipse导包快捷键:crtl+shift+o
- 使用package声明类或接口所属的包,声明在源文件的首行
- 每"."一次,就代表一层文件目录
- 包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”
应用:MVC设计模式
MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。这种将程序输入输出、数据处理,以及数据
的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
-
模型层 model 主要处理数据
数据对象封装 model.bean/domain
数据库操作类 model.dao
数据库 model.db -
视图层 view 显示数据
相关工具类 view.utils
自定义view view.ui -
控制层 controller 处理业务逻辑
应用界面相关 controller.activity
存放 fragment controller.fragment
显示列表的适配器 controller.adapter
服务相关的 controller.service
抽取的基类 controller.base
JDK中主要的包介绍
- java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
- java.net----包含执行与网络相关的操作的类和接口。
- java.io ----包含能提供多种输入/输出功能的类。
- java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。(Arrays,ArrayList,Scanner,Date)
- java.text----包含了一些java格式化相关的类
- java.sql----包含了java进行JDBC数据库编程的相关类/接口
- java.awt(目前用的较少,多用于B/S架构)----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
import
在源文件中显式的使用import结构导入指定包下的类、接口
声明格式
import 顶层包名.子包名.类/接口名
-
声明在包的声明和类的声明之间
-
可以使用**“xxx.*”**的方式,表示可以导入xxx包下的所有结构
-
java.lang包下定义的类或接口,则可以省略import结构
-
如果使用的类或接口是本包下定义的,则可以省略import结构
-
如果在源文件中,使用了不同包下的同名的类,则必须至少一个类需要以全类名的方式显示。
其中,第一个Account类为com.atguigu.exer3包下,已用import导入;第二个重名Account类为com.atguigu.exer4下,需要以全类名方式显示,而不能再用import导入;
-
使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
-
import static:导入指定类或接口中的静态结构:属性或方法。
super
在Java类中使用super来调用父类中的指定操作:
- super可用于访问父类中定义的属性
super.属性
- super可用于调用父类中定义的方法
super.方法
- super可用于在子类构造器中调用父类的构造器
super(形参列表)
- 子类构造器中
super(形参列表)
必须声明在首行; - 当父类中没有空参数的构造器时,子类的构造器必须通过
this(参数列表)
或者super(参数列表)
语句指定调用本类或者父类中相应的构造器。同时,只能”二选一” 。 - 在构造器的首行,没显式的声明
this(形参列表)
或super(形参列表)
,则默认调用的是父类中空参的构造器super()
。 - 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错;
- 在类的多个构造器中,至少一个类的构造器中使用了
super(形参列表)
,调用父类中的构造器。(因为不能所有构造器都使用this(形参列表)
)
- 子类构造器中
注:
- 当子父类出现同名成员时,可以用super表明调用的是父类中的成员
- super的追溯不仅限于直接父类
- super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
区别点 | 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;
}
}
instanceof
x instanceof A
:检验x是否为类A的对象,返回值为boolean型。
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
如果x属于类A的子类B,x instanceof A值也为true。
static(静态)
static关键字的引入:当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
适用范围: 在Java类中,可用static修饰属性、方法、代码块、内部类,不能用来声明构造器;
作用:类的成员被static修饰后
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
static修饰属性(类变量/静态变量)
-
类变量(类属性)由该类的所有实例共享;访问方式:①**
类名.类属性
,②有对象的实例时对象名.类属性
**。通过某一对象/或类修改类变量后,所有对象中该类变量均被修改。 -
类变量随着类的加载而加载,在内存中也只会存在一份:存在方法区的静态域中。
-
类变量举例:
System.out
、Math.PI
static修饰方法(静态方法/类方法)
- 随着类的加载而加载,可以通过**
类.静态方法
的方式进行调用。有对象的实例时,可以通过对象.静态方法
**调用。 - **静态方法中,只能调用静态的方法或属性。**非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
- 静态方法要使用非静态方法和属性的话,可以通过实例化对象之后,来调用非静态方法和非静态变量。
- 在静态的方法内,不能使用this关键字、super关键字。因为静态方法随类加载而加载,生命周期要长于实例对象。
- 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(static修饰方法不具备多态性,多态性是运行时行为,静态随类的加载而加载,是编译期行为,不能被重写,只能被重新定义)。
- 随着类的加载而加载,可以通过**
如何判定属性和方法应该使用static关键字:
-
关于静态属性
- 属性是需要被多个对象所共享,不会随着对象的不同而不同的。
- 类中的常量也常常声明为static。
-
关于方法
- 操作静态属性的方法,通常设置为static的。
- 工具类中的方法(不需要建立对象实例即可调用的),习惯上声明为static的。 比如:Math、Arrays、Collections。
例题 编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。(提示:利率,最小余额需要设置为静态的)
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 + "]";
}
}
注:
- 导入其他类中static变量,方法导包需要import static xxx.xxx.其他类.*;
- 静态方法只能调用静态属性和结构与在静态方法内用对象调用非静态属性和结构并不冲突,只要能明确这个对象是谁就好。
单例模式
设计模式是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方式。
23种经典的设计模式:
- 创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为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
-
final
在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。
-
final标记的类不能被继承。提高安全性,提高程序的可读性。
- String类、System类、StringBuffer类
-
final标记的方法不能被子类重写。
- Object类中的getClass()
-
final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。
- final修饰属性:可以考虑赋值的位置:显式初始化、代码块中初始化、构造器中初始化。方法内不可以初始化,因为走完构造器对象就创建完成了,此时final的属性还未被赋值是不允许的。
- final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,方法体内不能进行重新赋值。
- static 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是鼓励继承的(类的继承,方法的重写)。
抽象类
用abstract关键字来修饰一个类,这个类叫做抽象类。
- 抽象类不能被实例化。
- 抽象类中**一定有构造器**,便于子类实例化时调用。
- 抽象类的非抽象的子类必须重写父类的抽象方法,并提供方法体。
- 若没有重写全部的抽象方法,仍为抽象类,需声明为abstract。
- 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
抽象方法
用abstract来修饰一个方法,该方法叫做抽象方法。抽象类中声明抽象方法但不提供实现,该方法的实现由子类提供。
-
权限修饰符 abstract 返回值类型 方法名(形参列表);
抽象方法只有方法的声明,没有方法体,直接用分号结束,不用{}, - 权限修饰符通常用public/protected,也可以用缺省,但不能用private,私有方法无法被重写。
- abstract不能修饰静态方法(static修饰方法不具备多态性,多态性是运行时行为,static修饰的方法随类的加载而加载,是编译期行为,不能被重写,只能被重新定义)
- abstract与final相斥,不能用abstract修饰final的类和方法.
匿名类
使用形参为抽象类对象的方法时,如果只需使用一次的情况下,多使用匿名类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();
}
模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,**易变部分可以抽象出来,供不同子类实现。**这就是一种模板模式。
例 : 计算某段代码运行时间的方法
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);
}
}
}
}
模板方法设计模式应用:
- 数据库访问的封装
- Junit单元测试
- JavaWeb的Servlet中关于doGet/doPost方法调用
- Hibernate中模板程序
- Spring中JDBCTemlate、HibernateTemplate等
Object类
- Object类是所有Java类的根父类
- 如果在类的声明中未使用extends关键字指明其父类,则默认父类 为java.lang.Object类。
- Object类中的功能(属性、方法)具有通用性。
- Object类结构:
属性:无
构造器 :空参构造器
方法:equals() / toString() / getClass() /hashCode() / clone() / finalize()
wait() 、 notify()、notifyAll()
注意:数组也是Objcet类的子类,可调用Object类中方法
equals()方法
==
运算符
- 基本类型比较值:只要两个变量的值相等,即为true。
-
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,
==
才返回true。即比较地址值。
Person p1=new Person();
Person p2=new Person();
if (p1==p2){
…
}
注意:用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错。
equals()方法
- 只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象(比较地址值)。
obj1.equals(obj2)
//Objecr类中equals()源码
public boolean equals(Object obj) {
return (this == obj);
}
2.可以进行重写。像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
3.通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写,来比较两个对象的实体内容是否相同。
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与==区别
- ``== `既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址。
-
equals()
是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。 - 具体要看自定义类里有没有重写Object的equals方法来判断。
- 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
toString()方法
toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
Object类中toString()的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
特点:
- 当我们输出一个对象的引用时,实际上就是自动调用当前对象的toString()方法。
- 在进行String与其它类型数据的连接操作时,自动调用toString()方法
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
- 基本类型数据转换为String类型时,调用了对应包装类的toString()方法
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 + "]";
}
包装类(Wrapper)
为了使基本数据类型的变量具有类的特征,引入包装类。(主要作用:①调用Object类的方法 ②多态)
基本类型、包装类与String类间的转换
基本类型与包装类之间的转换
- 基本数据类型---->包装类 (装箱)
- 通过包装类的构造器实现:
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
}
- jdk5之后,自动装箱
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();
- jdk5之后,自动拆箱
// 自动装箱
Integer int1 = 3;
Float f1 = 3.0F;
// 自动拆箱
int a = int1;
float f2 = f1;
double d1 = new Double(2.4);
注:自动装箱,自动拆箱需要类型匹配;
基本类型/包装类与String类间转换
-
基本类型/包装类 ----> 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);
- **String ----> 基本类型/包装类 **
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);
}
}
}
接口(interface)
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
接口的本质是契约,标准,规范,就像我们的法律一样,制定好后实现接口的类必须要遵守。
继承类是一个 “B is a A”(B是A)的概念,而接口实现则是 “能不能”,“支不支持”,"可不可以"的关系。
接口使用说明
-
使用interface来定义
public interface 接口名{}
; -
Java中,接口和类是并列的两个结构,或理解为接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
-
接口中的成员 :
-
JDK7及以前:只能定义全局常量
public static final 常量类型 常量名
和抽象方法public abstract 返回值类型 方法名();
; - JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法
public static 返回值类型();
、默认方法;
-
JDK7及以前:只能定义全局常量
-
接口中的所有成员变量都默认是由
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("传入的对象不匹配");//抛出异常
}
}
java8接口新特性
可以为接口添加静态方法和默认方法(均默认为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();
}
}
代理设计模式的应用:
工厂模式
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
具体模式:
简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
接口和抽象类对比
在开发中,通常一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
相同点:不能实例化;都可以包含抽象方法;都通过对象的多态性产生实例化对象;
不同点:
- 抽象类是包含抽象方法的类 , 接口是抽象方法和全局常量的集合(jdk8后包含默认方法,静态方法)。
- 两者组成不同,抽象类主要由构造器、抽象方法、普通方法、常量、变量组成;接口由抽象方法、默认方法、静态方法、全局常量组成,不能带构造器、普通方法、变量、代码块;
- 使用方法上,一个是子类继承抽象类(extends) ,一个是子类实现接口(implements);
- 抽象类有单继承的局限 ,接口可以多继承,如果抽象类和接口都可以使用的话,优先使用接口避免单继承的局限;
- 抽象类可以实现多个接口,接口只能继承接口,不能继承抽象类,但允许继承多个接口。