由于近期需要用到基础以及底层的一些知识点,所以借此机会找来了一些学习的资料,用于对 JAVA
面向对象相关的知识点进行复盘、巩固,并且在此将一些重点的知识点做相关的记录,在后续的实际开发工作中遇到这些知识点相关的内容都会在本篇笔记中进行记录、迭代。
“ 温故而知新 ”
对于 “面向对象” 章节的学习,划分为以下三个阶段
Java
类及类的成员:属性、方法、构造器:代码块、内部类this
、super
、static
、final
、abstract
、interface
、package
、import
等核心:掌握上述的概念、思想、关键字等如何在代码中进行具体实现与应用。
技巧:“大处着眼,小处着手”
具体涉及到的代码的需要特别细致的去写,否则一不小心就会出错,导致花费大量的时间去排错, 但也不能埋头去写,要关注前面以及后续的需要学习的一些知识点,区分哪些是重要的,哪些是不重要的,且如果遇到一些 "问题" 时,先花一些时间去考量这个问题的价值,如果需要花费大量的时间去完成,而到最后却不怎么用得上的话,那大可不必。
总结:抓重点去学习,将自己的时间和精力的价值最大化。
查阅资料的过程中发现的重点
面向过程(POP
)与面向对象的区别(OOP
)
两者都是一种编程思想,面向对象是由面向过程延伸而来的。
面向对象更加强调运用人类在日常生活中的思维逻辑所采用的思想方法以及原则,如抽象、分类、继承、聚合、多态等。
面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做,以下面的伪代码来举例
人{
打开(冰箱){
冰箱.开开();
}
抬起(大象){
大象.进入(冰箱);
}
关闭(冰箱){
冰箱.闭合();
}
}
//实体对象
冰箱{
//对象拥有的功能
开开(){}
闭合(){}
}
大象{
进入(冰箱){}
}
在 Java
语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化来使用类的具体功能。
Scanner
、String
、File
等涉及到 Java
语言与前端提交的数据或者后端的数据库交互时,在 Java
层面都体现为类、对象,例如在数据库中的每一个表都被看作为一个个的类,而每个表里面的字段都是这个类的属性。
引用类型的变量,只可能储存两类值:
null
例如,直接打印一个实例化后对象的值
Phone p = new Phone();
System.out.println(p)
得到的结果为
下图为对象数组在 JVM
的栈和堆中的内存的解析图
图片来源自宋红康老师课程的课件
创建两个匿名的对象,他们的内存空间是独立的,运行结束后地址会在内存中销毁(适合一次性使用的场景,无需在栈中储存该对象的内存地址)
new Phone().price = 1999;
new Phone().getPrice(); //0.0
匿名对象的使用
public class InstanceTest {
public static void main(String[] args) {
PhoneMall mall = new PhoneMall();
//匿名对象的使用
mall.show(new Phone());
}
}
class PhoneMall{
public void show(Phone phone){
//匿名对象通过形参的赋值,实现在方法中被多次调用
phone.sendEmail();
phone.playGame();
}
}
” 两同一不同 “
一个例子:
判断与 void show(int a,char b,double c){}
构成重载的有哪些?
void show(int x,char y,double z){} // no,参数的数量和类型相同
int show(int a,double c,char b){} // yes
void show(int a,double c,char b){} // yes
boolean show(int c,char b){} // yes
void show(double c){} // yes
double show(int x,char y,double z){} // 参数的数量和类型相同
void shows(){double c} // no ,方法名不同
总结
方法的重载需要重点关注相同方法名的参数的数量以及参数的类型
jdk 5.0 中新增的内容
使用过程中需要注意的:
一个例子:
public class MethodArgsTest {
public static void main(String[] args) {
MethodArgsTest test = new MethodArgsTest();
test.show(new String[]{"AA","BB","CC"});
// 可变形参也可以接收一个数组
test.show(new String[]{"AA","BB","CC"});
}
public void show(String ... strs){
for(int i = 0;i < strs.length;i++){
System.out.println(strs[i]);
}
}
}
形参:方法定义时,声明在小括号内的参数
实参:方法调用时,实际传递给形参的数据
int
、float
、double
等)此时实参赋给形参的是真是储存的数据值。
一些例子
例1:计算 1-100
之间所有自然数的和
public static void main(String[] args) {
RecursionTest test = new RecursionTest();
int sum = test.getSum(100);
System.out.println(sum);
}
public int getSum(int n) {// 3
if (n == 1) {
return 1;
} else {
return n + getSum(n - 1);
}
}
输出结果:5050
例2: 已知有一个数列:f(0) = 1,f(1) = 4, f(n+2) = 2 * f(n+1) + f(n)
,其中 n
是大于 0
的整数,求 f(10)
的值。
public static void main(String[] args) {
RecursionTest test = new RecursionTest();
int value = test.f(10);
System.out.println(value);
}
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);
}
}
运行结果:10497
为什么需要封装?
隐藏对象内部的复杂性,只对外公开简单的接口,便于外界的调用,从而提高系统的可扩展性、可维护性。
通俗的说,就是把该隐藏的都隐藏起来,该暴露的都暴露出来,这就是封装性的设计思想。
问题的引入
当我们创建一个类的对象以后,我们可以通过 "对象.属性
" 的方式对该对象的属性进行赋值。赋值操作要受到属性的数据类型和存储范围的制约。
但在实际问题中,我们给属性赋值时可能需要加入额外的限制条件,这个条件就不能在属性声明时体现,我们只能通过 方法 进行限制条件的添加,比如 setLegs()
同时,我们需要避免用户再使用 "对象.属性" 的方式对属性进行赋值。则需要将属性声明为私有的 (private
)
此时,针对于属性就体现了封装性。
封装性的体现
age
私有化 (private
),同时提供公共的 (public
) 方法来获取和设置此属性的值,例如: getAge()
和 setAge()
需要权限修饰符来配合
Java
规定的 4
种权限(权重从小到大排列):
private
、缺省
、protected
、public
这4 种权限可以用来修饰类及类的内部结构有:
以下是这四种权限修饰符的权限范围
如果对类进行修饰,只能使用:缺省
、public
总结
Java
提供了 4
种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
构造器的作用:
一些特点
权限修饰符 类名(参数列表){}
一个例子
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String n, int a, Date d) {
name = n;
age = a;
birthDate = d;
}
public Person(String n, int a) {
name = n;
age = a;
}
public Person(String n, Date d) {
name = n;
birthDate = d;
}
public Person(String n) {
name = n;
age = 30;
}
}
是什么?
this
可以修饰、调用类的属性、方法和构造器
为什么?怎么用?
我们可以用 this
来区分属性和局部变量。
比如: this.name = name;
我们可以使用 "this.属性
" 或 "this.方法
" 的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略 "this.
"。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用 "this.变量
" 的方式,表明此变量是属性,而非形参。
但是,通常情况下,我们都选择省略 "this.
",再特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用 "this.变量
" 的方式,表明此变量是属性,而非形参。
我们可以使用 this
调用构造器,案例如下
class Person{ // 定义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 ;
}
}
需要注意以下几点
this(形参列表)
" 方式调用自己n - 1
构造器中使用了 "this(形参列表
)"this(形参列表)
" 必须声明在当前构造器的首行this(形参列表)
" 用来调用其他的构造器是什么?
java
中提供了包的概念
package
声明类或者接口所属的包,需要声明在源文件的首行。
package
属于标识符,遵循标识符的命名规范,例如需要以小写进行命名,需要做到 "见名知意" 的效果,每 "." 一次,就代表一层文件目录
为什么,怎么用?
命名示例:package com.codeyee.javabase.object;
JDK中主要的包介绍
包名 | 描述 |
---|---|
java.lang | 包含一些Java语言的核心类, 如String、 Math、 Integer、 System和Thread, 提供常用功能 |
java.net | 包含执行与网络相关的操作的类和接口 |
java.io | 包含能提供多种输入/输出功能的类。 |
java.util | 包含一些实用工具类, 如定义系统特性、 接口的集合框架类、 使用与日期日历相关的函数。 |
java.text | 包含了一些java格式化相关的类 |
java.sql | 包含了java进行JDBC数据库编程的相关类/接口 |
是什么?为什么?
继承性的好处
但是不要仅为了获取其他类中某个功能而去继承
怎么用?
继承性的使用格式
class A extends B{}
A: 子类、派生类、subclass
B: 父类、超类、基类、superclass
如何体现?
一旦子类A继承父类 B
以后,子类A中就获取了父类B中声明的所有的属性和方法。
我们可以通过下面的例子来理解
需要注意的是,父类中声明为 private
的属性或方法,子类继承父类以后,仍然获取了父类中私有的结构,只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
子类继承父类以后,还可以声明自己特有的属性或方法,从而实现功能的拓展。
我们用下面的一个例子中体现上述的内容
public class Test{
public static void main(String[] args) {
Student stu = new Student();
stu.eat() //可以正常继承并且调用
stu.sleep() //由于该方法在父类中使用private进行修饰,无法调用
}
}
public class Person{
String name;
private int age;
private void sleep(){ //私有的
System.out.println("睡觉");
}
public void eat(){ //公共的
System.out.println("吃饭");
}
}
public class Student extends Person{
String major;
public void study(){
System.out.println("学习");
}
}
在Java中关于继承性的规定
Java
中类的单继承性:一个类只能有一个父类如果我们没有显式的声明一个类的父类的话,则此类继承于 java.lang.Object
类,所有的java类(除 java.lang.Object
类之外)都直接或间接的继承于java.lang.Object
类,意味着所有的 java
类具有 java.lang.Object
类声明的功能。
定义
在子类中可以根据需要对从父类中继承来的方法进行改造, 也称 为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
要求
方法名称
、 参数列表
不能大于
父类被重写的方法的 返回值类型
不能小于
父类被重写的方法的 访问权限
private
权限的方法需要注意
子类与父类中同名同参数的方法必须同时声明为非 static
的(即为重写),或者同时声明为
static
的(不是重写),因为 static
方法是属于类的,子类无法覆盖父类的方法。
下面我们来看一个例子
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
public class Student extends Person {
public String school;
public String getInfo() { //重写方法
return "Name: "+ name + "\nage: "+ age
+ "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo());
}
}
输出结果:Name:Bob age:20 school:school2
从输出的结果我们可以看出 Student
类重写了 Person
类中的 getInfo()
方法
Person p1=new Person();
//调用Person类的getInfo()方法
p1.getInfo();
Student s1=new Student();
//调用Student类的getInfo()方法
s1.getInfo();
这是一种 “多态性”,同名的方法,用不同的对象来区分调用的是哪一个方法。
对于多态性的应用场景我们在后续的章节中详细的说道
在 Java
类中使用 super
来调用父类中的指定操作:
super
可用于访问父类中定义的属性super
可用于调用父类中定义的成员方法super
可用于在子类构造器中调用父类的构造器注意
尤其当子父类出现同名成员时, 可以用 super
表明调用的是父类中的成员
super
的追溯不仅限于直接父类super
和 this
的用法相像, this
代表本类对象的引用, super
代表父类的内存空间的标识一些例子
1、关键字 super
举例:使用 super
调用父类的方法
class protected Person {
String name = "张三";
protected int age;
public String getInfo() {
return "Name: " + name + "\nage: " + age;
}
}
class Student extends Person {
protected String name = "李四";
private String school = "New Oriental";
public String getSchool() {
return school;
}
public String getInfo() {
//使用super调用父类的方法
return super.getInfo() + "\nschool: " + school;
}}
public class StudentTest {
public static void main(String[] args) {
Student st = new Student();
System.out.println(st.getInfo());
}
}
2、调用父类的构造器的例子
this (参数列表)
或者 super (参数列表)
语句指定调用本类或者父类中相应的构造器。 同时, 只能 ”二选一” 且必须放在构造器的首行public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d;
}
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}
}
public class Student extends Person {
private String school;
public Student(String name, int age, String s{
super(name, age);
school = s;
}
public Student(String name, String s) {
super(name);
school = s;
}
// 编译出错: no super(),系统将调用父类无参数的构造器。
public Student(String s) {
school = s;
}
}
关键字 this
和 super
有哪些区别 ?
从结果上来看(继承性)
从过程上来看
java.lang.Object
类中的空参构造器为止
虽然创建子类对象是时调用了父类的构造器,但是自始至终只创建过一个对象,即为new的子类对象。
一个例子
class Creature {
public Creature() {
System.out.println("Creature无参数的构造器");
}
}
class Animal extends Creature {
public Animal(String name) {
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}
public Animal(String name, int age) {
this(name);
System.out.println("Animal带两个参数的构造器,其age为" + age);
}
}
public class Wolf extends Animal {
public Wolf() {
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
}
}
输出结果如下
是什么?
何为多态性?在 java
中的多态性通常指的是对象的多态性,父类的引用指向子类的对象(或者子类的对象赋给父类的引用)也可以理解为一个事物的多种形态。
为什么?
提高了代码的通用性,常称作接口的重用
怎么用?
多态性的使用前提:
有了对象的多态性以后,我们在编译期只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法,如下代码
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
//多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法(虚拟方法调用)
p2.eat();
p2.walk();
巧记多态性:编译时候看左边,运行时看右边
但是需要注意,虽然将子类 Man
的对象赋给了父类的对象实现多态,但是无法通过该对象调用子类特有的属性和方法(未经过重写的)
我们再来看一些多态性的例子,加深对多态性的理解
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
}
public void func(Animal animal){
//Animal animal = new Dog();
animal.eat();
animal.shout();
}
}
class Animal{
public void eat(){
System.out.println("动物:进食");
}
public void shout(){
System.out.println("动物:叫");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪!汪!汪!");
}
public void watchDoor(){
System.out.println("看门");
}
}
运行上面的代码
我们可以从上面的例子中看到,虽然 func
方法接收的参数是 Animal
类,但是由于 Dog
类继承了 Animail
类,并且重写父类的了 eat()
和 shuout()
方法,所以我们可以执行向 func
方法传入一个 Dog
类型的对象,而最终执行的也是 Dog
类重写后的方法。
这样做的好处是,可以根据我们传入不同的子类,来执行子类重写后的功能(方法)来实现多态,例如下面我们再构建一个 Cat
类,并且将 Cat 类的对象作为参数传入 func
方法
构建 Cat
类
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵!喵!喵!");
}
}
将 Cat
类型的对象作为参数传入 func
方法
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
System.out.println("==================分割线====================");
test.func(new Cat());
}
执行结果如下
我们再来看一个更贴近我们日常应用的例子:数据库连接
class Driver{
public void doData(Connection conn){
conn.method1(); //数据库连接的一些相关操作的方法(假设)
conn.method2();
conn.method3();
}
}
我们使用的数据库可能是 Mysql
或者 Oracle
假设 Mysql
的连接类为 MySQlConnection
,Oracle
的连接类为 OracleConnection
,这两种数据库的连接类都继承于 Connection
,并且根据各自的特点重写了 method1
、method2
、method3
这三个方法(假设这三个方法是提供的是对数据库的连接操作)
如果我们现在需要连接到 Mysql
的数据库,只需要将 MySQlConnection
类对象作为 doData
的方法传入,就能实现对 Mysql
数据库连接相关的一些功能,例如
Driver driver = new Driver();
driver.doData(new MySQlConnection());
如果需求变更,需要我们完成对 Oracle
数据库的连接操作,这时我们只需传入一个 OracleConnection
类型的对象,就可以完成相关的操作,而无需再写额外的代码,例如
Driver driver = new Driver();
driver.doData(new OracleConnection());
e
为 Person
类型,而方法的调用是在运行时确定的,所以调用的是Student
类的 getInfo()
方法,称为动态绑定。
我们从编译和运行的角度看:
方法重载,是指允许存在多个同名方法,而这些方法的参数不同。 编译器根据方法不
同的参数列表
, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。 它们的调用地址在编译期就绑定了。 Java
的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以: 对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,
这称为 “早绑定
” 或 “静态绑定
” ;
而对于多态,只有等到方法调用的那一刻, 解释运行器才会确定所要调用的具体
方法,这称为 “晚绑定
” 或 “动态绑定
” ,而 方法重写 就属于动态绑定的范畴。
引用一句 Bruce Eckel
的话:
“不要犯傻,如果它不是晚绑定, 它就不是多态。”
我们先来看下面的例子
import java.util.Random;
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat ();
case 1:
return new Dog ();
default:
return new Sheep ();
}
}
public static void main(String[] args) {shii
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
}
我们从上述的代码例子中可以看到, 实例化哪个子类的对象是由变量 key
的值来决定的,而 key
的值由是在运行的时候才通过 Random
类来随机生成,所以由此可知,多态是 运行时
的行为。
对象的多态性,只适用于方法,而不适用于属性(编译和运行都看左边的类型)
例如我们在父类 Person
和 子类 Man
中都定义一个属性 id
,并且构建一个 Man
类型的对象赋值给 Person
类型的对象,再输出该对象的属性 id
的值,得到的为 Person
中所定义的属性值,如下代码
public class PersonTest {
public static void main(String[] args) {
Person p2 = new Man();
System.out.println(p2.id); //1001
}
}
public class Person {
int id = 1001;
}
public class Man {
int id = 1002;
}
为什么?
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
那么 如何才能调用子类特有的属性和方法?
向下转型:使用强制类型转换符
我们来看一组代码
Person p2 = new Man();
Man m1 = (Man)p2;
m1.earnMoney();
m1.isSmoking = true;
如上代码所示,我们可通过使用 (Man)
来将 Person
对象强制转为 Man
类型,这是因为 Person
为 Man
的父类,并且在构建 Person
对象时实例化的是 Man
类,所以可以通过强制类型转换符进行 向下转型
我们再来看一个例子
Woman w1 = (Woman)p2; //编译通过
w1.goShopping(); //执行时出错,出现ClassCastException的异常。
但是在实际编码的过程中,使用强制转换可能会出现 ClassCastException
的异常,例如我们将对象 p2
强制转换为 Woman
类型,实际上是将包含 Man
类型的一些特有的方法和属性的 Person
对象强制转换为 Woman
类型,转换后得到了对象 w1
此时编译不会有任何错误,但是当通过对象 w1
调用 Woman
类的特有的方法 goShopping()
时,却抛出了异常,我们可以理解为两个各自有着自己不同特点的对象不能进行相互转换。此时我们的 instanceof
关键字就派上了用场。
是什么?怎么用?
instanceof 关键字的具体使用
使用格式:a instanceof A
: 判断对象 a
是否是类 A
的实例。如果是,返回 true
;如果不是,返回 false
使用情境:为了避免在向下转型时出现ClassCastException
的异常,我们在向下转型之前,先 进行 instanceof
的判断,一旦返回 true
,就进行向下转型。如果返回 false
,不进行向下转型。
对该关键字进行了解之后,我们将该关键字使用到上面的例子当中
if(p2 instanceof Woman){
Woman w1 = (Woman)p2;
w1.goShopping();
System.out.println("******Woman******");
}
if(p2 instanceof Man){
Man m2 = (Man)p2;
m2.earnMoney();
System.out.println("******Man******");
}
基本的数据类型转换与多态性下的对象转换的对比
是什么?
java.lang.Object
类Object
类是所有 Java
类的根父类extends
关键字指明其父类,则默认父类为java.lang.Object
类Object
类中的功能(属性、方法) 就具有通用性。一些常见的方法
NO. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Object() | 构造 | 构造器 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 取得Hash码 |
4 | public String toString() | 普通 | 对象打印时调用 |
1、回顾 ==
的使用:
==
:运算符
如果比较的是引用数据类型变量,实际比较的是两个对象的内存地址值是否相同,即两个引用是否指向同一个对象实体。
补充:
==
符号使用时,必须保证符号左右两边的变量类型一致。
2、equals()
方法的使用:
Object
类中 equals()
的定义:
public boolean equals(Object obj) { return (this == obj); }
说明:Object
类中定义的 equals()
和 ==
的作用是相同的
String
、Date
、File
、包装类等都重写了 Object
类中的equals()
方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的 "值(实体内容)" 是否相同,如下代码
//基本数据类型 int i = 10; int j = 10; double d = 10.0; System.out.println(i == j);//true System.out.println(i == d);//true,i的类型会自动提升为double //引用类型: Customer cust1 = new Customer("Tom",21); Customer cust2 = new Customer("Tom",21); System.out.println(cust1 == cust2); //false String str1 = new String("atguigu"); String str2 = new String("atguigu"); System.out.println(str1 == str2); //false System.out.println(cust1.equals(cust2)); //false System.out.println(str1.equals(str2)); //true Date date1 = new Date(32432525324L); Date date2 = new Date(32432525324L); System.out.println(date1.equals(date2)); //true
equals()
的话,也通常是比较两个对象的 "值(实体内容)" 是否相同。那么,我们就需要对 Object
类中equals()
进行重写。
重写的原则:比较两个对象的实体内容是否相同.
3、重写 equals()
通常情况下,我们自定义的类如果使用 equals()
的话也是想对比两个对象的值(实体内容)是否相同,所以我们需要对 Object
类中的 equals()
进行重写
public class Customer {
private String name;
private int age;
//...... 这里省略get set方法以及构造方法
//重写的原则:比较两个对象的实体内容(即:name和age)是否相同
//手动实现equals()的重写
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if(obj instanceof Customer){
Customer cust = (Customer) obj;
//对比每个属性的值
return this.age == cust.age && this.name.equals(cust.name);
}else{
return false;
}
}
在实际开发的过程中,我们会使用 eclipse
或者 idea
等开发工具去自动生成 equals()
的代码。
4、重写 equals()
方法的原则
x.equals(y)
返回是 true
, 那么y.equals(x)也应该返回是 true
。x.equals(x)
必须返回是 true
。x.equals(y)
返回是 true
, 而且 y.equals(z)
返回是 true
,那么 z.equals(x)
也应该返回是 true
。x.equals(y)
返回是 true
, 只要 x
和 y
内容一直不变, 不管你重复 x.equals(y)
多少次, 返回都是 true
。x.equals(null)
, 永远返回是 false
;
x.equals(和x不同类型的对象)
永远返回是false
。为了防止出现空指针异常,在对象调用
equals()
方法之前,先判断该对象的值是否为null
1、toString()
的基本使用
toString()
Object
类中 toString()
的定义:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
String
、Date
、File
、包装类等都重写了Object
类中的toString()
方法。使得在调用对象的 toString()
时,返回 "实体内容" 信息,如下代码所示
public class ToStringTest { public static void main(String[] args) { Customer cust1 = new Customer("Tom",21); System.out.println(cust1.toString()); //输出结果:com.atguigu.java1.Customer@15db9742 System.out.println(cust1); //输出结果:com.atguigu.java1.Customer@15db9742 String str = new String("MM"); System.out.println(str); //输出结果:MM Date date = new Date(4534534534543L); System.out.println(date.toString()); //输出结果:Mon Sep 11 08:55:34 GMT+08:00 2113 } }
toString()
方法,当调用此方法时,返回对象的 "实体内容" ,如下代码,我们在自定义类中重写 toString()
方法后再尝试输出对象的值
public class Customer { // ... 省略属性等代码 @Override public String toString() { return "Customer [name=" + name + ", age=" + age + "]"; } }
再次输出该对象的值,观察输出的结果
System.out.println(cust1.toString()); //输出结果:Customer[name = Tom,age = 21] System.out.println(cust1); //输出结果:Customer[name = Tom,age = 21]
java
提供了 8
种基本数据类型对应的类,使得基本数据类型的变量具有类的特征,针对这八种基本数据类型定义相应的引用类型,这称作为包装类(封装类),有了类的特点,就可以调用类中的方法, 这样的 Java
才是真正的面向对象 。
本章节需要掌握的内容:基本数据类型、包装类、String
三者之间的相互转换
**装箱:基本数据类型包装成包装类的实例 **
拆箱:获得包装类对象中包装的基本类型变量
.xxxValue()
方法
boolean b = bObj.booleanValue();
JDK1.5之后,支持自动装箱,自动拆箱。但类型必须匹配。
字符串转换成基本数据类型
parseXxx(String s)
静态方法:
Float f = Float.parseFloat(“12.1”);
基本数据类型转换成字符串
valueOf()
方法:
String fstr = String.valueOf(2.34f);
一些练习
1、如下两份代码输出结果相同吗?各是什么?为什么?
代码一
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);
代码二
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);
答案是不相同
代码一的执行结果为 1.0
,代码二的执行结果为 1
,题目考察的知识点为 类型提升 ,在代码1中使用了三元运算符,在 java
中涉及到预算的操作都需要保证左边与右边的类型存在继承关系或者相同,而 Integer 比 Double 类型小,所以被 向上转型
为 Double
类型,所以代码一的输出结果为 1.0
。
2、解释一下以下程序的输出结果
@Test
public void test3() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false
Integer m = 1;
Integer n = 1;
System.out.println(m == n);//true
Integer x = 128; //相当于new了一个Integer对象
Integer y = 128; //相当于new了一个Integer对象
System.out.println(x == y);//false
}
解析:由于 Integer
内部定义了 IntegerCache
结构,IntegerCache
中定义了 Integer
类型的数组,并保存了从 -128~127
范围的整数。如果我们使用自动装箱的方式给 Integer
赋值的范围在 -128~127
范围内时,可以直接使用数组中的元素,不用再去 new
了 。
目的:提高效率
1、如何实现向下转型,需要注意什么问题?如何解决此问题
使用 (类型)
进行强制类型转换,可能会出现 ClassCastException
异常,使用 instanceof
在向下转型之前进行判断对象与类是否为同一类型。
2、== 和 equels() 有什么区别?
使用 ==
在值类型是对比两个变量的值,而在引用类型是对比两个变量的内存地址,使用 equels()
如果没有重写 Object
类的 equels()
的情况下,与 ==
的功能是相同的,String
和 Date
等类性默认重写了equels()
方法,其功能是对比两个实体的类型、属性是否相等,在自定义类需要重写 equels()
方法时也可以参考上述的逻辑,代码如下
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if(obj instanceof Customer){
Customer cust = (Customer) obj;
//对比每个属性的值
return this.age == cust.age && this.name.equals(cust.name);
}else{
return false;
}
}
static
关键字可以用来修饰:属性、方法、代码块、内部类
使用 static 修饰属性:静态变量(或类变量)
属性,按是否使用 static
修饰,又分为:静态属性(变量) vs 非静态属性
如下例子
public class StaticTest {
public static void main(String[] args) {
Chinese.nation = "中国";
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
c1.nation = "CHN";
Chinese c2 = new Chinese();
c2.name = "马龙";
c2.age = 30;
c2.nation = "CHINA"; //使用c2调用该属性并修改值
System.out.println(c1.nation); //使用c1对象输出该属性的值,结果为CHINA
}
}
class Chinese{
String name;
int age;
static String nation;
}
static
修饰属性的其他说明:
类.静态变量
" 的方式进行调用
类变量 vs 实例变量内存解析
使用static修饰方法:静态方法
随着类的加载而加载,可以通过"类.静态方法
"的方式进行调用
调用类型 | 静态方法 | 非静态方法 |
---|---|---|
类 | yes | no |
对象 | yes | yes |
例如
public class StaticTest {
public static void main(String[] args) {
Chinese.show();
//Chinese.eat(); //编译不通过
//Chinese.info();
}
}
class Chinese{
String name;
int age;
static String nation;
public void eat(){
System.out.println("中国人吃中餐");
//调用非静态结构
this.info();
System.out.println("name :" +name);
//调用静态结构
walk();
System.out.println("nation : " + nation);
}
public static void show(){
System.out.println("我是一个中国人!");
//不能调用非静态的结构
// eat();
// name = "Tom";
//可以调用静态的结构
System.out.println(Chinese.nation);
walk();
}
}
还需要注意
this
关键字、super
关键字开发中,如何确定一个属性是否要声明为static的?
static
开发中,如何确定一个方法是否要声明为static的?
static
的
static
的。 比如:Math
、Arrays
、Collections
什么是设计模式?
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。 设计模免去我们自己再思考和摸索。就像是经典 的棋谱,不同的棋局,我们用不同的棋谱。 ”套路”
那么什么是单例设计模式?
private
,这样,就不能用 new
操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。如何实现?
饿汉式单例模式的实现
public class SingletonTest1 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2); //同一个对象,所以结果为true
}
}
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创建类的对象,要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
1、声明私有化的类的构造器
2、内部创建类的对象,要求此对象也必须声明为静态的
3、声明 public
、static
的方法,返回当前类的对象
“懒汉式” 单例模式的实现
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);
}
}
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化,此对象也必须声明为static的
private static Order instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
1、私有化类的构造器
2、声明当前类对象,没有初始化,此对象也必须声明为 static
的
3、声明 public
、static
的返回当前类对象的方法
区分饿汉式 和 懒汉式
饿汉式:
懒汉式:
代码块的作用:初始化类、对象
如果要对代码块有修饰的话,自能使用 static
代码块分为两种:静态代码块、非静态代码块
我们来开一组代码,结合输出的内容加深对上述知识点的了解
public class BlockTest {
public static void main(String[] args) {
String desc = Person.desc;
System.out.println(desc);
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.age);
Person.info();
}
}
class Person{
//属性
String name;
int age;
static String desc = "我是一个人";
//构造器
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//非static的代码块
{
System.out.println("hello, block - 2");
}
{
System.out.println("hello, block - 1");
//调用非静态结构
age = 1;
eat();
//调用静态结构
desc = "我是一个爱学习的人1";
info();
}
//static的代码块
static{
System.out.println("hello,static block-2");
}
static{
System.out.println("hello,static block-1");
//调用静态结构
desc = "我是一个爱学习的人";
info();
//不可以调用非静态结构
// eat();
// name = "Tom";
}
//方法
public void eat(){
System.out.println("吃饭");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public static void info(){
System.out.println("我是一个快乐的人!");
}
}
输出结果:
hello,static block-2
hello,static block-1
我是一个快乐的人!
我是一个爱学习的人
hello, block - 2
hello, block - 1
吃饭
我是一个快乐的人!
hello, block - 2
hello, block - 1
吃饭
我是一个快乐的人!
1
我是一个快乐的人!
代码块的使用场景举例
我们通过一个获取数据库连接的案例来理解一下代码块的应用
public class JDBCUtils {
private static DataSource dataSource = null;
static{
InputStream is = null;
try {
is = DBCPTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
Properties pros = new Properties();
pros.load(is);
//调用BasicDataSourceFactory的静态方法,获取数据源。
dataSource = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}finally{
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//使用DBCP数据库连接池实现数据库的连接
public static Connection getConnectionAccess() throws SQLException{
Connection conn = dataSource.getConnection();
System.out.println(conn);
return conn;
}
}
我们在获取数据库的连接对象之前,需要构建一个数据库连接池,但是为了节省系统的开销,我们一个类里面只构建一个数据库,这就可以使用惊天代码块的特性:只在类加载时创建一次,在这之后我们每调用一次getConnectionAccess()
即可从连接池中获取一个连接对象,然后对数据库进行相关的操作
对于属性可以赋值的位置
执行的先后顺序:① --> ②或⑤ --> ③ --> ④
final:最终的
final
关键字可以用于修饰的结构有:类、方法、变量
String
类、System
类、StringBuffer
类使用 final
关键字来修饰方法:
Object
类的 getClass();
我们发现 getClass()
方法中还用到一个关键字 native
,使用该关键字表示该方法的具体使用调用的是调用了 c/c++
的实现,而非 java
实现。
使用 final
关键字来修饰变量(属性)
final
修饰的属性可以被初始化的位置
例如
public class FinalTest { final int WIDTH = 0; final int LEFT; final int RIGHT; { LEFT = 1; } public FinalTest(){ RIGHT = 2; } public FinalTest(int n){ RIGHT = n; } }
如果类中存在多个构造器,则每个构造器中都需要对被 final
所修饰的未初始化的属性进行赋值。
final
修饰方法的形参
表示此形参是一个常量,当我们调用此方法时,给常量赋一个实参,一旦赋值后,就只能在方法体内使用此形参,但不能进行重写赋值。
使用 static final
用来修饰:全局常量
常见的一些排错情况:
上述的代码 ++x
是一个错误的举例,无法通过编译,原因是对常量 x
进行了自增操作,但 x+1
只是将 x
+ 1
后的值进行返回,而没有对 x
本身进行赋值操作
在上述的代码当中,对象 o
只是常量,但是该对象的属性 i
是一个普通的属性,可以被修饰,但是如果执行 o = new Other()
则是对该对象进行了修改,编译则不通过。
abstract: 抽象的
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做 抽象类。
abstract
关键字可以用来修饰的结构有:类、方法
abstract
关键字修饰类:抽象类
abstract
修饰方法:抽象方法
abstract
关键字修饰abstract
使用时需要注意的几个点
abstract
不能用来修饰:属性、构造器等结构abstract
不能用来修饰:私有方法、静态方法、final
的方法、final
的类如下例子
abstract class Creature{
public abstract void breath();
}
abstract class Person extends Creature{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//不是抽象方法:
// public void eat(){
//
// }
//抽象方法
public abstract void eat();
public void walk(){
System.out.println("人走路");
}
}
class Student extends Person{
public Student(String name,int age){
super(name,age);
}
public Student(){
}
public void eat(){
System.out.println("学生多吃有营养的食物");
}
@Override
public void breath() {
System.out.println("学生应该呼吸新鲜的没有雾霾的空气");
}
}
抽象类的应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
Vehicle
(车辆)类需要定义两个方法分别计算运输工具的燃料效率和行驶距离。如下图
卡车(Truck
)和驳船 (RiverBarge
) 的燃料效率和行驶距离的计算方法完全不
同。 Vehicle
类不能提供计算方法,但子类可以,但又需要子类又必须拥有这些计算方法,所以需要用到 abstract
关键字。
Java
允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽类。
抽象类的匿名子类
作用:无需再造一个具体的子类去实现抽象类的方法
语法格式
new 抽象类名(){
@Overrride
public void 方法1(){
}
@Overrride
public void 方法2(){
}
}
具体例子
/*
* 抽象类的匿名子类
*
*/
public class PersonTest {
public static void main(String[] args) {
method(new Student());//匿名对象
Worker worker = new Worker();
method1(worker);//非匿名的类非匿名的对象
method1(new Worker());//非匿名的类匿名的对象
System.out.println("********************");
//创建了一匿名子类的对象:p
Person p = new Person(){
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
};
method1(p);
System.out.println("********************");
//创建匿名子类的匿名对象
method1(new Person(){
@Override
public void eat() {
System.out.println("吃好吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸新鲜空气");
}
});
}
public static void method1(Person p){
p.eat();
p.breath();
}
public static void method(Student s){
}
}
class Worker extends Person{
@Override
public void eat() {}
@Override
public void breath() {}
}
思考一些问题
1、为什么抽象类不可以使用 final
关键字声明?
抽象类必须要求用子类去继承和实例化,使用 final
则无法被继承
2、一个抽象类中可以定义构造器吗?
可以,子类在实例化的时候还是会调用父类的构造器
抽象类的体现就是一种 模板模式的设计模式 ,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
我们来看一下具体的例子,来体会以上上述的内容
//抽象类的应用:模板方法的设计模式
public class TemplateMethodTest {
public static void main(String[] args) {
BankTemplateMethod btm = new DrawMoney();
btm.process();
BankTemplateMethod btm2 = new ManageMoney();
btm2.process();
}
}
abstract class BankTemplateMethod {
// 具体方法
public void takeNumber() {
System.out.println("取号排队");
}
public abstract void transact(); // 办理具体的业务 //钩子方法
public void evaluate() {
System.out.println("反馈评分");
}
// 模板方法,把基本操作组合到一起,子类一般不能重写
public final void process() {
this.takeNumber();
this.transact();// 像个钩子,具体执行时,挂哪个子类,就执行哪个子类的实现代码
this.evaluate();
}
}
//业务子类,实现各自业务的办理流程
class DrawMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要取款!!!");
}
}
class ManageMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要理财!我这里有2000万美元!!");
}
}
从上述的代码中,我们把银行的业务抽象成了一个 BankTemplateMethod
类,由于每种业务的办理流程都不同,所以定义了一个抽象的方法 transact
用于给不同的 “业务子类” 去实现具体的业务办理流程,在父类中再定义一个 process
方法去规范化银行业务办理的流程,并且该方法使用 final
去修饰,使子类无法重写该方法。
一个类可以实现多个接口,在一定程度上解决了我们类的单继承性的局限性
Java
不支持多重继承。 有了接口, 就可以得到多重继承的效果。is-a
(继承)的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、 MP3机、手机、数码相机、移动硬盘等都支持 USB
连接。接口 (interface) 是抽象方法和常量值定义的集合。
接口的一些特点:
interface
来定义,与 class
同级 。public static final
修饰的。public abstract
修饰的。接口定义的举例如下:
public interface Runner {
int ID = 1;
void start();
public void run();
void stop();
}
在上述的定义当中,系统默认会将接口中定义的属性修饰为常量,而方法会修饰为抽象的方法,我们在手动修饰时也必须遵守这一规则。
public interface Runner {
public static final int ID = 1;
public abstract void start();
public abstract void run();
public abstract void stop();
}
通过下面的几个图,我们来进一步了解接口的作用
从上图总我们可以看出,运动员和学生的子类都共同拥有学习的技能,但是由于学生和运动员不存在继承的关系,所以我们就可以使用接口来实现。
飞机、子弹、风筝、热气球都共同拥有 “飞” 的技能,但 "子弹" 在拥有 "飞" 的技能的同时还拥有了 "攻击性" 这个技能。
代码示例如下
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
System.out.println(Flyable.MIN_SPEED);
// Flyable.MIN_SPEED = 2;
Plane plane = new Plane();
plane.fly();
}
}
interface Flyable{
//全局常量
public static final int MAX_SPEED = 7900;//第一宇宙速度
int MIN_SPEED = 1;//省略了public static final
//抽象方法
public abstract void fly();
//省略了public abstract
void stop();
}
interface Attackable{
void attack();
}
class Plane implements Flyable{
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止");
}
}
abstract class Kite implements Flyable{
@Override
public void fly() {
}
}
class Bullet extends Object implements Flyable,Attackable,CC{
@Override
public void attack() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void method1() {
// TODO Auto-generated method stub
}
@Override
public void method2() {
// TODO Auto-generated method stub
}
}
接口的基本使用总结
Java
类的语法格式: 先写 extends
,后写 implements
class SubClass extends SuperClass implements InterfaceA{ }
Java
单继承的局限性。
class AA extends BB implements CC,DD,EE
java
开发中,接口通过让类去实现(implements)的方式来使用。
JDK7
以前:只能定义全局常量和抽象方法JDK8
:除了可以定义全局常量和抽象方法之外,还可以定义静态方法,默认方法。我们可以把 JAVA
中的接口比作我们日常生活中的USB接口
我们通过下面的一个例子,来更深入的了解接口的使用
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//2. 创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
//3. 创建了接口的匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
com.transferData(phone);
//4. 创建了接口的匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("mp3开始工作");
}
@Override
public void stop() {
System.out.println("mp3结束工作");
}
});
}
}
class Computer{
public void transferData(USB usb){//USB usb = new Flash();
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
上述的代码当中,需要在电脑(Computer)类中传输数据的闪存设备(Flash)需要实现(implements) USB
接口的规范,手机(Phone)、MP3设备使用 “匿名实现类“ 的方式来实现 USB
接口的规范。
概述
代理模式是 Java
开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
举一个生活中的例子
比如大学生在毕业的时候需要去租房子,这是一类 “具体的功能“,
我们再来通过一个代码例子来进一步的理解什么是代理模式
/*
* 接口的应用:代理模式
*
*/
public class NetWorkTest {
public static void main(String[] args) {
BrowseGoogle browseGoogle = new BrowseGoogle();
BrowseFireFox browseFireFox = new BrowseFireFox();
//通过谷歌浏览器访问网络
ProxyBrowse proxyBrowseGoogle = new ProxyBrowse(browseGoogle);
proxyBrowseGoogle.browse();
//通过浏览器B访问网络
ProxyBrowse proxyBrowseFireFox = new ProxyBrowse(browseFireFox);
proxyBrowseFireFox.browse();
}
}
interface NetWork{
//浏览网页
public void browse();
}
//被代理类A:服务提供商
class BrowseGoogle implements NetWork{
@Override
public void browse() {
System.out.println("谷歌浏览器:正在访问网页");
}
}
//被代理类B:服务提供商
class BrowseFireFox implements NetWork{
@Override
public void browse() {
System.out.println("火狐浏览器:正在访问网页");
}
}
//代理类:需求方
class ProxyBrowse implements NetWork{
private NetWork work;
public ProxyBrowse(NetWork work){
this.work = work;
}
public void check(){
System.out.println("网上冲浪之前的检查工作");
}
public void end(){
System.out.println("关机,结束网上冲浪");
}
@Override
public void browse() {
this.check();
work.browse();
this.end();
}
}
运行结果如下
网上冲浪之前的检查工作
谷歌浏览器:正在访问网页
关机,结束网上冲浪
网上冲浪之前的检查工作
火狐浏览器:正在访问网页
关机,结束网上冲浪
在上面的代码当中,“代理类”(需求方)通过 “被代理类”(服务提供方)来实现浏览网页的行为。
应用场景:
比如你要开发一个大文档查看软件, 大文档中有大的图片, 有可能一个图片有 100MB
, 在打开文件时, 不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
分类
接口和抽象类之间的对比
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
Java 8
中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完
全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
Collection/Collections
或者 Path/Paths
这样成对的接口和类。
Collection 为 jdk8 之后 Collections 的过渡版本
default
关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
比如: java 8 API
中对Collection
、 List
、 Comparator
等接口提供了丰富的默认方法。
通过下面的代码,我们来理解几个知识点
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// s.method1();
// SubClass.method1();
//知识点1
CompareA.method1();
//知识点2
s.method2();
//知识点3、知识点4
s.method3();
}
}
class SubClass extends SuperClass implements CompareA,CompareB{
public void method2(){
System.out.println("SubClass:上海");
}
public void method3(){
System.out.println("SubClass:深圳");
}
//知识点5
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
}
public class SuperClass {
public void method3(){
System.out.println("SuperClass:北京");
}
}
public interface CompareA {
//静态方法
public static void method1(){
System.out.println("CompareA:北京");
}
//默认方法
public default void method2(){
System.out.println("CompareA:上海");
}
default void method3(){
System.out.println("CompareA:上海");
}
}
public interface CompareB {
default void method3(){
System.out.println("CompareB:上海");
}
}
在jdk1.8之后,接口在保留原有的特性的同时,拥有了更多类的特征
接口新特性的应用举例
interface Filial {// 孝顺的
default void help() {
System.out.println("老妈,我来救你了");
}
}
interface Spoony {// 痴情的
default void help() {
System.out.println("媳妇,别怕,我来了");
}
}
class Father{
public void help(){
System.out.println("儿子,救我媳妇!");
}
}
class Man extends Father implements Filial, Spoony {
@Override
public void help() {
System.out.println("我该救谁呢?"); //自定义的
Filial.super.help(); //救你的老妈
Spoony.super.help(); //救媳妇
}
public static void main(String[] args) {
Man man = new Man();
man.help(); //如果Man未实现help方法,则默认调用父类的同名方法
}
}
当某一个类的内部,还需要有一部分需要以一个完整的结构进行描述时,而这部分结构又只为外部事物提供服务,那么这个结构最好使用 “内部类” 的结构进行定义。
java
中,如果类 A
中如果还定义了 ”类 B
“,那么后者类 B
称为内部类,类 A
成为外部类。Inner class
一般用在定义它的类或者语句块之内,在外部引用它时必须给出完整的名称。 Inner class
的名字不能与包含它的外部类的类名相同内部类的分类:
static
成员内部类和非 static
成员内部类)成员内部类作为类的成员的角色:
Inner class
还可以声明为 private
或 protected
;Inner class
可以声明为 static
的, 但此时就不能再使用外层类的非 static
的成员
变量成员内部类作为类的角色:
abstract
类 , 因此可以被其它的内部类继承
final
OuterClass$InnerClass.class
字节码文件( 也适用于局部内部类)
通过例子来理解
public class Outer {
private int s = 111;
public class Inner {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(Outer.this.s); // 外部类对象属性s
}
}
public static void main(String args[]) {
Outer a = new Outer();
Outer.Inner b = a.new Inner();
b.mb(333);
}
}
如何声明局部内部类 ?
class 外部类{
方法(){
class 局部内部类{
}
} {
class 局部内部类{
}
}
}
如何使用局部内部类 ?
public class InnerClassTest1 {
//开发中很少见
public void method(){
//局部内部类
class AA{
}
}
//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
//创建一个实现了Comparable接口的类:局部内部类
//方式一:
// class MyComparable implements Comparable{
//
// @Override
// public int compareTo(Object o) {
// return 0;
// }
//
// }
//
// return new MyComparable();
//方式二:
return new Comparable(){
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
局部内部类的一些特点
.class
文件,但
是前面冠以外部类的类名和 $
符号,以及数字编号。final
的。 由局部内部类和局
部变量的声明周期不同所致。public
、protected
缺省为 private
static
修饰,因此也不能包含静态成员new
的后面,用其隐含实现一个接口或实现一个类。
interface A{
public abstract void fun1();
}
public class Outer{
public static void main(String[] args) {
new Outer().callInner(new A(){
//接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名
public void fun1() {
System.out.println(“implement for fun1");
}
});// 两步写成一步了
}
public void callInner(A a) {
a.fun1();
}
}
}
通过本次复习和整理,再次巩固了 JAVA
面向对象开发相关的知识点。
学习过程中受到的一些启发