自用的学习笔记,若有错误欢迎指出。
回顾方法
方法的定义
修饰符
break和return的区别
- break:跳出switch循环,结速循环
- return:代表方法的结束,返回一个结果
方法名
参数列表
方法的调用
静态方法 static
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Demo02{ public static void main(String[] args){ Student student = new Student(); student.say(); } }
public class Student{ public void say(){ System.out.println("hello"); } }
|
- 非静态方法
1 2 3 4 5 6 7 8 9 10
| public class Demo02{ public static void main(String[] args){ Demo02 demo02 = new Demo02(); demo02.add(1, 3); } public int add(int a, int b){ return a + b; } }
|
java值传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Demo04{ public static void main(String[] args){ int a = 1; System.out.println(a); Demo04.change(a); System.out.println(a); } public static void change(int a){ a = 10; } }
|
Java引用传递
引用传递的是对象,其本质还是值传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Demo01 { public static void main(String[] args) { Student student = new Student(); System.out.println(student.name);
Demo01.change(student); System.out.println(student.name); }
public static void change(Student student){
student.name = "Ben"; } }
class Student{ String name; }
|
类与对象的关系
- 类似一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物。
- Person类、Pet类、Car类等,这些类都是用来描述/定义某一类具体的事物应该具备的特点和行为。
- 对象是抽象概念的具体实例
- 能够体现出特点,展现出功能的是具体的实例,而不是一个抽象的概念。
- 张三就是一个人的具体实例,张三家里的旺财就是狗的一个具体实例。
创建与初始化对象
- 使用
new
关键字来创建对象
- 使用
new
关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中的构造器的调用。
- 类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写
void
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Application { public static void main(String[] args) { Student josh = new Student(); Student tom = new Student();
josh.name = "josh"; josh.age = 23;
System.out.println(josh.name); System.out.println(josh.age); } }
public class Student { String name; int age;
public void study(){ System.out.println(this.name + "在学习中"); } }
|
构造器
- 使用
new
关键字,必须要有构造器。其本质是在调用构造器。
- 有参构造:一旦定义了有参构造,无参就必须显示定义
- 构造器的定义:
- 构造器的作用:
- 注意:
- 定义了有参构造器,如果想使用无参构造,需要去定义一个无参数的构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class Application { public static void main(String[] args) {
Person person = new Person();
System.out.println(person.name); } }
public class Person {
String name; public Person(){ }
public Person(){ this.name = "josh"; }
}
|
面向过程 & 面向对象
- 面向过程思想
- 面向对象思想
- 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题。
- 对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
面向对象
- 面向对象编程(Object-Oriented Programming,OOP)
- 面向对象编程的本质就是:以类的方式组装代码,以对象的组织(封装)数据
- 抽象
- 三大特性
从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象。
从代码运行角度考虑是先有类后有对象。类是对象的模板。
类与对象的定义以及关系
类
:类是对现实生活中一类具有共同属性和行为的事物的抽象
对象
:如果将类理解为一张设计图, 根据设计图,可以创建出具体存在的事物
类和对象关系:总结来说, 类是对象的模板,可以根据类来创建对象。
成员变量局部变量的区别
区别:
- 类中编写
位置
不同
成员变量: 方法外
局部变量: 方法里
初始化值
不同
成员变量: 有默认初始化值
局部变量: 没有默认初始化值, 使用之前必须完成赋值
内存位置
不同
成员变量: 堆内存
局部变量: 栈内存
生命周期
不同
成员变量: 随着对象的创建而存在, 随着对象的消失而消失
局部变量: 随着方法的调用而存在, 随着方法的弹栈而消失
封装
封装的定义:
- 就是用类设计对象处理某一个事物的数据时,应该把要处理的数据,以及处理这些数据的方法,设计到一个对象中去。
- 面向对象的三大特征:
封装
、继承
、多态
。
封装的设计规范:
合理隐藏,合理暴露
- 在设计对象的时候,对象的
成员变量
我们应该隐藏起来,并且暴露合适的get & set
方法来供外界访问。
- 对象的成员方法,只暴露业务必须用到的方法即可。
代码层面控制对象的成员公开或者隐藏:
- 公开成员,可以使用
public
进行修饰。
- 隐藏成员,可以使用
private
进行修饰。
我们程序设计要追求:“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合就是仅暴露少量的方法给外部使用。
- 封装(数据的隐藏)
- 通常,应该禁止直接访问一个对象中数据的实际表示,而应该通过操作接口来访问,这称为信息隐藏。
- 属性私有:get/set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class Application{ public static void main(String[] args){ Student s1 = new Student(); s1.setName("josh"); System.out.println(s1.getName()); s1.setAge(23); System.out.println(s1.getAge()); } }
public class Student{ private String name; private int age; private char sex; public String getName(){ return this.name; } public void setName(String name){ this.name = name; } public int getAge(){ return this.age; } public void setAge(int age){ if(age > 120 || age < 0){ System.out.println("你输入的年龄有误"); } else { this.age = age; } } }
|
- 封装的意义:
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 提高了系统的可维护性
继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
extends
的意思是“扩展”,子类是父类的扩展。
java中类只有单继承,没有多继承
继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends
来表示。
子类和父类之间,子类继承父类:subClass extends superClass
。
继承的特点:
- 子类可以继承父类种所有
非私有
的成员(成员变量、成员方法)
- 继承后子类的对象是子类和父类共同完成的
- Java是
单继承
的,Java中的类不支持多继承,但是支持多层继承
涉及:
子类继承了父类,就会有父类的全部方法。
在java中,所有的类都默认直接或间接继承Object类
注意点:
super
调用父类的构造方法,必须在构造方法的第一个
super
必须只能出现在子类的方法或者构造方法中
super
和this
不能同时调用构造方法
super
和this
的区别:
- 代表的对象不同
this
为本身调用者这个对象
super
代表父类对象的引用
- 前提:
this
没有继承也可以使用
super
只能在继承条件下使用
- 构造方法:
this();
在本类里面的构造
super();
父类的构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public class Application{ public static void main(String[] args){ Student student = new Student(); student.speak(); System.out.println(student.money);
student.test("Ben");
student.test1(); } }
public class Person{ public int money = 10000; protected String name = "josh"; public void speak(){ System.out.println("say hello"); } public void print(){ System.out.println("Person"); } }
public class Student extends Person{ private String name = "fm"; public void test(String name){ System.out.println(name); System.out.println(this.name); System.out.println(super.name); } public void print(){ System.out.println("Student"); } public void test1(String name){ print(); this.print(); super.print(); } }
|
权限修饰符
修饰符的作用范围:private < 缺省 < protected < public
修饰符 |
在本类中 |
同一个包下的其他类里 |
任意包下的子类里 |
任意包下的任意类里 |
private |
T |
|
|
|
缺省 |
T |
T |
|
|
protected |
T |
T |
T |
|
public |
T |
T |
T |
T |
方法的重写
概述:当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
注意事项:
重写小技巧:使用Override注解
,该注解可以指定Java编译器,检查我们方法重写的是否正确,代码可读性也会更好
子类重写父类方法时,访问权限必须大于或者等于父类该方法的权限。
- 例如:如果父类的是
public
,我们把它变成private
就不行
public > protected > 缺省(default) > private
重写方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
私有方法、静态方法不能被重写,如果重写会报错
重写都是方法的重写,和属性无关
静态方法和非静态方法的区别很大!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Application{ public static void main(String[] args){ A a = new A(); a.test(); B b = new A(); b.test(); } }
public class B{ public static void test(){ System.out.println("B->test()"); } }
public class A extends B{ public static void test(){ System.out.println("A->test()"); } }
|
当我们使用@Override
来重写,可以看到子类重写了父类的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class Application{ public static void main(String[] args){ A a = new A(); a.test(); B b = new A(); b.test(); } }
public class B{ public static void test(){ System.out.println("B->test()"); } }
public class A extends B{ @Override public void test(){ System.out.println("A->test()"); } }
|
重写需要有继承关系,子类重写的是父类的方法。
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以扩大,不能缩小。(如果父类的是
public
,我们把它变成private
就不行 (public
> protected
> default
> private
))
- 抛出异常:范围可以被缩小,但不能扩大。
- 子类的方法必须和父类的一致,但是方法体不同。
为什么需要重写:
- 父类的功能,子类不一定需要,或者不一定满足。
多态
多态即同一个方法可以根据可以根据发送对象的不同而采用多种不同的行为方式。
一个对象的实际类型是确定的,但可以指向对象的引用类型有很多
- 多态存在的条件
- 有继承关系
- 子类重写父类方法
- 父类引用指向子类对象
- 多态注意事项:
- 多态是方法的多态,属性没有多态
- 父类和子类必须要有联系,不是同一类型进行转换就会报错(类型转换异常:
ClassCastException
)
- 无法实现多态:
static
方法,这个是属于类,它不属于实例
final
常量
private
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class Application{ public static void main(String[] args){ Student s1 = new Student(); Student s2 = new Student(); Person s3 = new Student(); Object s4 = new Student();
s3.run(); s1.run(); } }
public class Person{ public void run(){ System.out.println("run"); } }
public class Student extends Person{ @Override public void run(){ System.out.println("play"); } public void eat(){ System.out.println("eat"); } }
|
多态的好处
在多态形式下,右边对象是解耦合的,更便于扩展和维护。
1 2
| People p1 = new Student(); p1.run();
|
定义方法时候,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利。
例如:可以直接传参设为父类People类,以便于接受所有子类的传入。
1 2 3
| public void go(People p) { p.run(); }
|
类型转换
多态会产生的问题:多态下不能使用子类独有的功能。需要用到强制类型转换来实现。
自动类型转换
多态向上转型格式:
父类 变量名 = new 子类()
People p = new Teacher();
强制类型转换
多态向下转型格式:
子类类型 变量名 = (子类类型) 父类变量名;
Teacher t = (Teacher) P;
多态形式下不能直接调用子类特有方法,但是转型后是可以调用的。这里所说的转型就是把父类变量转换为子类类型。格式如下:
1 2 3 4 5
| if(父类变量 instance 子类){ 子类 变量名 = (子类)父类变量; }
|
强制类型转换的注意事项:
- 存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错
- 运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常
ClassCastException
的错误出来。
关于多态转型问题,我们最终记住一句话:原本是什么类型,才能还原成什么类型
在强制类型转换之前,我们可以通过instanceof
关键字来判断该对象指向的真实类型,再进行强制转换。
instanceof 关键词
在instanceof
关键字中用来判断类和类之间是否拥有父子关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class Application { public static void main(String[] args) {
Object object = new Student();
System.out.println(object instanceof Person); System.out.println(object instanceof Student); System.out.println(object instanceof Object); System.out.println(object instanceof Teacher); System.out.println(object instanceof String);
Person person = new Student(); System.out.println(person instanceof Object); System.out.println(person instanceof Student); System.out.println(person instanceof Person); System.out.println(person instanceof Teacher); } }
public class Person {
}
public class Student extends Person{
}
public class Teacher extends Person{
}
|
注意点:
- 父类引用指向子类的对象。
- 把子类转换为父类,向上转型,可以直接转换。
- 把父类转换为子类,向下转型,需要进行强制转换。
- 进行转换是为了方便方法的调用,减少重复的代码,保证了代码的简洁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class Application { public static void main(String[] args) {
Person student = new Student();
Student student1 = (Student)student; student1.play();
Student student = new Student(); student.play();
Person person = student;
} }
public class Person { public void run(){ System.out.println("run"); } }
public class Student extends Person{ public void play(){ System.out.println("play"); } }
|
static关键字详解
成员变量按照有无static修饰,分为两种:
类变量
: 有static修饰,属于类,在计算机里只有一份,会被类的全部对象共享。
实例变量(对象的变量)
: 无static修饰,属于每个对象。
关于static
关键字的小结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Student { private static int age; private double score;
public static void main(String[] args) { Student s1 = new Student();
System.out.println(Student.age); System.out.println(s1.score); System.out.println(s1.age);
Student.go(); s1.run(); }
public void run(){ run(); }
public static void go(){
} }
|
其实一个类里面还存在有一个匿名代码块。同时,需要注意的是在类里面每一个代码块的加载时间是不同的。从上到下分别是:
static代码块
是和类一起加载的,同时也是加载速度最快的。并且该代码块也只能执行一次。
匿名代码块
是可以用来赋予初始值的,为第二个被加载。
- 构造方法,最后被加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Person {
public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person();
}
{ System.out.println("匿名代码块"); }
static { System.out.println("静态代码块"); }
public Person(){ System.out.println("构造方法"); } }
|
static的应用知识
代码块
概述:代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类)
代码块分为两种:
- 静态代码块:
- 格式:
static{}
- 特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次
- 作用:完成类的初始化,例如:对类变量的初始化赋值
- 实例代码块:
- 格式:
{ }
- 特点:每次创建对象时,执行实例代码块,并在构造器前执行
- 作用:和构造器一样,都是用来完成对象的初始化。例如:对实例变量进行初始化赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class Student{ static int number = 18; static String schoolName; static { System.out.println("静态代码块执行了一次"); schoolName = "华一"; } { System.out.println("实例代码块被执行了"); System.out.println("有人创建了对象"); } public Student() { System.out.println("无参数构造器被执行了"); } public Student(String name) { System.out.println("有参数构造器被执行了" + name); }
}
public class Test { public static void main(String[] args) { System.out.println(Student.number); System.out.println(Student.number); Student s1 = new Student(); Student s2 = new Student("Josh"); } }
|
单例设计模式
什么是设计模式:
- 一个问题通常有N种方法来解答,其中肯定有一种解法是
最优
的,这个最优的解法被人总结出来了,称之为设计模式
- 设计模式有20多种,对应20多种软件开发种会遇到的问题
- 是具体问题的最优解决方案
主要关注该设计模式:1. 可以解决什么问题; 2. 设计模式的写法
单例设计模式:
示例:
饿汉式单例
拿对象时,对象就创建好了
- 私有类的构造器
- 定义一个类变量记住类的一个对象
- 定义一个类方法,返回对象
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class A { private static A a = new A(); private A() { } public static A getObject() { return a; } }
|
单例设计模式的应用场景
查看Java的Runtime
类源码。Java在运行时候,只有一套运行环境,所以该Runtime类只需要一个对象。
电脑的任务管理器也是单例模式,由于任务管理器只需要一个窗口来显示,创建多个也没有意义还占用电脑的内存影响性能。所以也做成单例模式来优化避免浪费内存。
懒汉式单例
拿对象时,对象才开始创建
写法:
- 私有类的构造器
- 定义一个类变量记住类的一个对象
- 定义一个类方法,保证返回的是同一个对象
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class B { private static B b; private B() { } public static A getObject() { if (b == null) { b = new B(); } return b; } }
|
总结
当该对象被频繁的使用时候,使用饿汉式单例。如果一个对象使用次数用的不多则可以使用懒汉式单例。
this关键字
this代表当前类对象的引用 (地址) 哪一个对象调用的方法, 方法中的this, 就代表的是哪一个对象
a1.show() —> this —> a1的地址
a2.show() —> this —> a2的地址
this关键字使用场景:
当成员变量和局部变量出现了重名问题, Java会使用就近原则, 优先使用局部变量,这时候要想做区分的话, 可以使用this关键字.
final类
- final关键字是最终的意思,可以修饰(类、方法、变量)
- 修饰类:该类被称为最终类,特点是不能被继承了
- 修饰方法:该方法被称为最终方法,特点是不能被重写了。
- 修饰变量:该变量只能被赋值一次
抽象类
abstract
修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
- 抽象类可以没有抽象方法,但是抽象方法的类一定要申明为抽象类。
- 抽象类不能使用
new
关键字来创建对象,它是用来让子类继承的。
- 抽象方法,只有方法的申明,没有方法的实现,它是用来让子类去实现的。
- 子类继承抽象类,那么就必须要去实现抽象类中没有实现的抽象方法,否则该子类也要去申明为抽象类。
- 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
- 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
public abstract class Action { public abstract void doSomeThing();
}
public class A extends Action{ @Override public void doSomeThing(){
} }
|
思考?:
- 抽象类不能
new
对象出来,所以抽象类不存在构造器。
- 抽象类的存在是为了方便,提高了开发的效率。
抽象类的运用
总结:
用抽象类可以把父类中相同的代码,包括方法声明都抽取到父类,这样能更好的支持多态,一提高代码的灵活性。
反过来用,我们不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来让子类去实现,以方便系统的扩展。
Animal类,但是由于猫和狗叫的声音不一样,于是我们在Animal类中将叫的行为写成抽象的。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public abstract class Animal { private String name;
public abstract void cry();
public String getName() { return name; }
public void setName(String name) { this.name = name; } }
|
接着写一个Animal的子类,Dog类。代码如下
1 2 3 4 5
| public class Dog extends Animal{ public void cry(){ System.out.println(getName() + "汪汪汪的叫~~"); } }
|
然后,再写一个Animal的子类,Cat类。代码如下
1 2 3 4 5
| public class Cat extends Animal{ public void cry(){ System.out.println(getName() + "喵喵喵的叫~~"); } }
|
最后,再写一个测试类,Test类。
1 2 3 4 5 6 7
| public class Test2 { public static void main(String[] args) { Animal a = new Dog(); a.cry(); } }
|
假设现在系统有需要加一个Pig类,也有叫的行为,这时候也很容易原有功能扩展。只需要让Pig类继承Animal,复写cry方法就行。
1 2 3 4 5 6
| public class Pig extends Animal{ @Override public void cry() { System.out.println(getName() + "嚯嚯嚯~~~"); } }
|
此时,创建对象时,让Animal接收Pig,就可以执行Pig的cry方法
1 2 3 4 5 6 7
| public class Test2 { public static void main(String[] args) { Animal a = new Pig(); a.cry(); } }
|
模板方法设计模式
模板方法模式主要解决方法中存在重复代码的问题
示例:
如我们所见,在A类和B类中存在很多相同的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class A { public void sing() { System.out.println("选一首你喜欢的歌"); System.out.println("我是一只小小小小鸟..."); System.out.println("播放完毕"); } }
public class B { public void sing() { System.out.println("选一首你喜欢的歌"); System.out.println("倘若那天..."); System.out.println("播放完毕"); } }
|
我们定义一个父类,同时将相同的代码都保留下来
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public abstract class C { public final void sing() { System.out.println("选一首你喜欢的歌"); doSing(); System.out.println("播放完毕"); } public abstract void doSing(); }
|
之后我们需要将A类B类继承C类,同时重写方法
1 2 3 4 5 6 7 8 9 10 11 12
| public class A extends C { @Override public void doSing() { System.out.println("我是一只小小小小鸟..."); } } public class B extends C { @Override public void doSing() { System.out.println("倘若那天..."); } }
|
扩展:java中DateFormat
类使用的是模板方法设计模式
接口
接口就是规范,定义的是一组规则。体现了现实世界中:“如果你是…就必须去…”的思想。比如:如果你是猎人,就必须去打猎。
Java提供了一个关键字interface,用这个关键字来定义接口这种特殊结构。格式如下
1 2 3 4
| public interface 接口名{ }
|
接口是用来被类实现(implements)的,我们称之为实现类。
1 2 3 4
| 修饰符 class 实现类 implements 接口1, 接口2, 接口3, ... {
}
|
一个类是可以实现多个接口的(接口可以理解成干爹),类实现接口必须重写所有接口的全部抽象方法,否则这个类也必须是抽象类
声明类的关键字是class
,声明接口的关键字是interface
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
public class UserServiceImpl implements UserService, TimeService{
@Override public void add(String addName) {
}
@Override public void delete(String deleteName) {
}
@Override public void update(String updateName) {
}
@Override public void search(String searchName) {
}
@Override public void timer() {
} }
public interface UserService {
int AGE = 22;
void add(String addName); void delete(String deleteName); void update(String updateName); void search(String searchName);
}
public interface TimeService { void timer(); }
|
接口的作用:
- 约束。
- 定义一些方法,让不同的人来使用。
- 在接口里面所定义的方法都是
public abstract
。
- 在接口里面定义的变量都是常量:
public static final
。
- 接口不能被直接实例化,接口中没有构造方法。
- 关键字
implements
可以实现多个接口。
- 必须要重写接口中的方法
@Override
。
接口的好处
- 弥补了类单继承的不足,一个类同时可以实现多个接口。
- 让程序可以面向接口编程,这样程序员可以灵活方便的切换各种业务实现。
- 一个类我们说可以实现多个接口,同样,一个接口也可以被多个类实现的。这样做的好处是我们的程序就可以面向接口编程了,这样我们程序员就可以很方便的灵活切换各种业务实现了。
现在要写一个A类,想让他既是老师,偶然也是司机能够开车,偶尔也是歌手能够唱歌。那我们代码就可以这样设计,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Teacher{
}
interface Driver{ void drive(); }
interface Singer{ void sing(); }
class A extends Teacher implements Driver, Singer{ @Override public void drive() {
}
@Override public void sing() {
} }
public class Test { public static void main(String[] args) { Singer s = new A(); s.sing(); Driver d = new A(); d.drive(); } }
|
综上所述:接口弥补了单继承的不足,同时可以轻松实现在多种业务场景之间的切换。
接口JDK8的新特性
随着JDK版本的升级,在JDK8版本以后接口中能够定义的成员也做了一些更新,从JDK8开始,接口中新增的三种方法形式。
我们看一下这三种方法分别有什么特点?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public interface A {
default void test1(){ System.out.println("===默认方法=="); test2(); }
private void test2(){ System.out.println("===私有方法=="); }
static void test3(){ System.out.println("==静态方法=="); }
void test4(); void test5(); default void test6(){
} }
|
定义一个B类来实现A接口,需要重写A类中的所有抽象方法。但是,其中默认方法default
不需要重写。
1 2 3 4 5 6 7 8 9 10 11
| public class B implements A{ @Override public void test4() {
}
@Override public void test5() {
} }
|
最后在主程序中,三种方法的调用如下:
1 2 3 4 5 6 7 8
| public class Test { public static void main(String[] args) { B b = new B(); b.test1(); A.test3(); } }
|
综上所述:JDK8对接口新增的特性,有利于对程序进行扩展。
接口的其他细节
一个接口可以继承多个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| interface A{ void test1(); } interface B{ void test2(); } interface C{}
interface D extends C, B, A{
}
class E implements D{ @Override public void test1() {
}
@Override public void test2() {
} }
|
综上所述:一个接口可以继承多个接口,接口同时也可以被类实现。
接口除了上面的多继承特点之外,在多实现、继承和实现并存时,有可能出现方法名冲突的问题,需要了解怎么解决(仅仅作为了解即可,实际上工作中几乎不会出现这种情况)
- 一个接口继承多个接口,如果多个接口中存在相同的方法声明,则此时不支持多继承
- 一个类实现多个接口,如果多个接口中存在相同的方法声明,则此时不支持多实现
- 一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会有且只使用父类的方法
- 一个类实现类多个接口,多个接口中有同名的默认方法,则这个类必须重写该方法。
内部类
- 内部类就是在一个类的内部在定义一个类,比如在A类中定义一个B类,那么B类就相对与A类来说就是内部类,而A类相对于B类来说就是外部类了。
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类[重要]
内部类是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类。
当一个类的内部,包含一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类。
比如:汽车、的内部有发动机,发动机是包含在汽车内部的一个完整事物,可以把发动机设计成内部类。
成员内部类
成员内部类就是类中的一个普通成员,类似于成员变量、成员方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class Outer { private int id = 207; public int age;
public void out(){ System.out.println("这是内部类的方法"); }
public class Inner{ private int id = 310; public void in(){ System.out.println("这是内部类"); }
public void getId(){ System.out.println("获得外部类的私有属性"); int id = 202; System.out.println(id); System.out.println(this.id); System.out.println(Outer.this.id); } } }
public class Application { public static void main(String[] args) { Outer outer =new Outer(); Outer.Inner inner = outer.new Inner(); inner.in(); inner.getId(); } }
|
总结一下内部类访问成员的特点
- 既可以访问内部类成员、也可以访问外部类成员
- 如果内部类成员和外部类成员同名,可以使用**
类名.this.成员
**区分
静态内部类
静态内部类,其实就是在成员内部类的前面加了一个static关键字。静态内部类属于外部类自己持有。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Outer { private int age = 99; public static String schoolName="黑马";
public static class Inner{ public void test(){ System.out.println(schoolName); } } }
|
静态内部类创建对象时,需要使用外部类的类名调用。
1 2 3
| Outer.Inner in = new Outer.Inner(); in.test();
|
局部内部类
局部内部类是定义在方法中的类,和局部变量一样,只能在方法中有效。所以局部内部类的局限性很强,一般在开发中是不会使用的。
类可以写在多个地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Outer { public void method(){
class Inner{ public static void main(String[] args) { System.out.println("在方法中也可以加入class"); } } } }
class Test{
}
|
匿名内部类
匿名内部类是一种特殊的局部内部类;所谓匿名,指的是程序员不需要为这个类声明名字。
下面就是匿名内部类的格式:
1 2 3 4
| new 父类/接口(参数值){ @Override 重写父类/接口的方法; }
|
匿名内部类本质上是一个没有名字的子类对象、或者接口的实现类对象。
这个是匿名内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Outer { public static void main(String[] args) { new Test().run();
new UserService(){ @Override public void say(){ System.out.println("say hello"); } }; } }
class Test{ public void run(){ System.out.println("run"); } }
interface UserService{ void say(); }
|
匿名内部类的作用:简化了创建子类对象、实现类对象的书写格式。
匿名内部类的应用场景
只有在调用方法时,当方法的形参是一个接口或者抽象类,为了简化代码书写,而直接传递匿名内部类对象给方法。这样就可以少写一个类。比如,看下面代码
1 2 3
| public interface Swimming { public void swim(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Test { public static void main(String[] args){ Swimming s1 = new Swimming(){ public void swim(){ System.out.println("狗游泳飞快"); } }; go(s1); Swimming s1 = new Swimming(){ public void swim(){ System.out.println("猴子游泳不太行"); } }; go(s1); } public static void go(Swimming s){ System.out.println("开始~~~~~~~~"); s.swim(); System.out.println("结束~~~~~~~~"); } }
|
我们还可以简化一下上面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Test { public static void main(String[] args){ go(new Swimming(){ public void swim(){ System.out.println("狗游泳飞快"); } }); go(new Swimming(){ public void swim(){ System.out.println("猫猫游不了泳"); } }); } public static void go(Swimming s){ System.out.println("开始~~~~~~~~"); s.swim(); System.out.println("结束~~~~~~~~"); } }
|
泛型
所谓泛型指的是,在定义类、接口、方法时,同时声明了一个或者多个类型变量(如:<E>
),称为泛型类、泛型接口、泛型方法、它们统称为泛型。
比如我们前面学过的ArrayList类就是一个泛型类,我们可以打开API文档看一下ArrayList类的声明。
ArrayList集合的设计者在定义ArrayList集合时,就已经明确ArrayList集合时给别人装数据用的,但是别人用ArrayList集合时候,装什么类型的数据他不知道,所以就用一个<E>
表示元素的数据类型。
当别人使用ArrayList集合创建对象时,new ArrayList<String>
就表示元素为String类型,new ArrayList<Integer>
表示元素为Integer类型。
我们总结一下泛型的作用、本质:
自定义泛型类
接下来我们学习一下自定义泛型类,但是有一些话需要给大家提前交代一下:泛型类,在实际工作中一般都是源代码中写好,我们直接用的,就是ArrayList这样的,自己定义泛型类是非常少的。
自定义泛型类的格式如下
1 2 3 4
| public class 类名 <T, W> { }
|
接下来,我们自己定义一个MyArrayList泛型类,模拟一下自定义泛型类的使用。注意这里重点仅仅只是模拟泛型类的使用,所以方法中的一些逻辑是次要的,也不会写得太严谨。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public class MyArrayList<E>{ private Object[] array = new Object[10]; private int index; public void add(E e){ array[index]=e; index++; } public E get(int index){ return (E)array[index]; } }
|
接下来,我们写一个测试类,来测试自定义的泛型类MyArrayList是否能够正常使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Test{ public static void main(String[] args){ MyArrayList<String> list = new MyArrayList<>(); list.add("张三"); list.add("李四"); MyArrayList<Integer> list1 = new MyArrayList<>(); list.add(100); list.add(200); } }
|
自定义泛型接口
泛型接口其实指的是在接口中把不确定的数据类型用<类型变量>
表示。定义格式如下:
1 2 3 4
| public interface 接口名<类型变量>{ }
|
比如,我们现在要做一个系统要处理学生和老师的数据,需要提供2个功能,保存对象数据、根据名称查询数据,要求:这两个功能处理的数据既能是老师对象,也能是学生对象。
首先我们得有一个学生类和老师类
1 2 3 4 5 6
| public class Teacher{
} public class Student{ }
|
我们定义一个Data<T>
泛型接口,T表示接口中要处理数据的类型。
1 2 3 4 5
| public interface Data<T>{ public void add(T t); public ArrayList<T> getByName(String name); }
|
接下来,我们写一个处理Teacher对象的接口实现类
1 2 3 4 5 6 7 8 9 10 11
|
public class TeacherData implements Data<Teacher>{ public void add(Teacher t){ } public ArrayList<Teacher> getByName(String name){ } }
|
接下来,我们写一个处理Student对象的接口实现类
1 2 3 4 5 6 7 8 9 10 11
|
public class StudentData implements Data<Student>{ public void add(Student t){ } public ArrayList<Student> getByName(String name){ } }
|
在实际工作中,一般也都是框架底层源代码把泛型接口写好,我们实现泛型接口就可以了。
泛型方法
下面就是泛型方法的格式
1 2 3
| public <泛型变量,泛型变量> 返回值类型 方法名(形参列表){ }
|
下面在返回值类型和修饰符之间有定义的才是泛型方法。
1 2 3 4 5 6 7 8
| public static <T> void test(T t){ }
public E get(int index) { return (E) arr[index]; }
|
泛型限定
通配符
就是?
,可以在使用泛型 的时候代表一切类型;ETKV是在定义泛型的时候使用。
泛型的上下限:
- 泛型上限:
? extends Car
:? 能接收的必须是Car或者其子类。
- 泛型下限:
? super Car
:? 能接收的必须是Car或者其父类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public class Test{ public static void main(String[] args){ String rs = test("test"); Dog d = test(new Dog()); } public static <T> test(T t){ return t; } ArrayList<People> peoples = new ArrayList<>(); peoples.add(new People()); peoples.add(new People()); ArrayList<Dog> dogs = new ArrayList<>(); dogs.add(new Dog()); ArrayList<Student> students = new ArrayList<>(); students.add(new Student()); test2(peoples); test2(students); test3(peoples); test3(dogs); public static <T extends People> void test2(ArrayList<T> t) { } public static void test3(ArrayList<?> t) { } public static void test3(ArrayList<? extends People> t) { } } public class Dog { }
public class People { }
public class Student extends People { }
|
泛型注意事项
泛型擦除,也就是说泛型只能编译阶段有效,一旦编译成字节码,字节码中是不包含泛型的。而且泛型只支持引用数据类型,不支持基本数据类型。
泛型不支持基本数据类型,只能支持引用数据类型。
后记
之后就是java中最重要的面向对象编程。加油!去吧,继续服务。 ( ゚ ▽ ゚)つ□乾杯~
参考链接
B站:狂神说java老师讲的真的非常好,有兴趣的话就去看看吧。
封面图来源:https://www.pixiv.net/artworks/87550307