记录一下这些天学习的一些关于JavaSE的基础, 第二篇 —— 面向对象篇。(知识的搬运工)
面向过程(POP),强调的是功能行为,以函数为最小单位,考虑怎么做。
面向对象(OOP),将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
面向对象更强调运用人类在日常的思维逻辑中采用的思想方法和原则,如抽象、分类、继承、聚合、多态等。
例 :人把大象装进冰箱
面向过程
1. 把冰箱打开
2. 把大象塞进冰箱
3. 关闭冰箱门
面向对象
人{
打开(冰箱) {
冰箱.开开();
}
抬起(大象){
大象.进入(冰箱);
}
关闭(冰箱){
冰箱.闭合();
}
}
冰箱{
开开();
闭合();
}
大象{
进入(冰箱){}
}
属性、方法、构造器、代码块、内部类
类和对象
类的成员
属性:对应类中的成员变量, 需要修饰符(可缺省)
public class Person{
private int age; //声明 private 变量 age
public String name = “Lila”; //声明 public 变量 name
}
区别于局部变量,在方法体外,类体内声明的变量称为成员变量,在方法体内部声明的变量称为局部变量。局部变量在栈空间中,没有初始化值;成员变量在堆空间或静态域内,有初始化值。
行为:对应类中的成员方法
类和对象的使用
public class OOPTes {
public static void main(String[] args) {
//创建Person类对象
Person p1 = new Person();
//调用对象结构:属性、方法
p1.name = "Tom";
p1.isMale = true;
System.out.println(p1.name);
p1.rap();
//实例化对象
Person p2 = new Person();
System.out.println(p2.name); //这里对象会存在默认初始化值
//没有创建实例化对象,而是指向了同一个地址
Person p3 = p1;
System.out.println(p3.name); //Tom
p3.age = 10;
System.out.println(p1.age); //10
}
}
//类
class Person {
//属性
String name;
int age = 28;
boolean isMale;
//方法
public void eat() {
System.out.println("吃饭");
}
public void rap() {
System.out.println("练习时长两年半");
}
}
对象的创建----内存解析
构造器的作用: 创建对象;给对象进行初始化
public class Animal {
private int legs
//构造器
public Animal() {
legs= 4
}
public Animal(int n) {
legs= n
}
}
Animal = new Animal(3); // new + 构造器
注 意:
封装性、继承性、多态性、(抽象性)
为什么需要封装
使用者对类内部定义的属性对象的成员变量 的直接操作会导致数据的错误、混乱或安全性问题。
程序设计追求“高内聚,低耦合”
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性 , 这就是封装性的设计思想。
体现
为什么要继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。(此处的多个类称为 子类 (派生类),单独的这个类称为 父类 (基 类或 超类)。
作用
规则
特别的, 父类中声明为private的属性和方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只是因为封装性的影响,使得子类不能直接调用父类的结构。
class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo() {}
}
/*Student 类继承了父类 Person 的所有属性和方法,并增加了一个属性 school 。 Person 中的属性和方法 ,Student 都可以使用 */
class Student extends Person {
public String school;
}
父类的引用指向子类的对象,可直接应用在抽象类和接口上
多态性的产生
//对象的多态性,左边Person是父类,右边是子类Men
Person p = new Men(); //父类的引用指向子类的对象
多态的使用
//当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法----- 虚拟方法调用
p.eat();
//子类特有方法earn(),调用时报错没有定义该方法,编译时看左边,即Person类,而不是Man类
p.earn();
例:招聘人员,可能招到男生或女生,招到男(女)生都会做其对应的工作。
虚拟方法调用(多态情况下)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。类似于动态绑定的效果
前提:父类中定义了 该方法,各个子类重写了该方法。
执行:多态的情况下,调用对象的 该方法,实际执行的是子类重写的方法
举例
//这是一个连接数据库的方法,由于数据库有mysql,Oracle等多种源,所以需要定义为它们的父类Connection,实际上传的参数可以是connection = new MySQLConnection();而不需要为每个连接源定义一个方法。即使传入不同的子类对象,也能够使用同一个方法进行操作。
public void doData(Connection connection) {
//定义通过步骤
}
对象的多态性只适用于方法,不适用与属性
例:
class Base {
int count = 10;
public void display() {
System.out.println(this count);
}
}
class Sub extends Base {
int count = 20; //同名属性
//重写方法
public void display() {
System.out.println(this count);
}
}
public class FieldMethodTest {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count); // 20
s.display();
Base b = s;//多态性
System.out.println( b == s);// true, "==":比较引用类型是比较地址
System.out.println( b.count);//10, 多态性只适用于方法,不适用于属性
b.display();//20 , 适用于方法,子类方法中重写了
}
}
从编译和运行的角度上看
重载,是指允许存在多个同名方法,而这些方法的参数不同。 编译器根据方法不同的参数 表, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成 了不同的方法。 它们 的调用地址在编译 期就 绑定了 。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为 “早绑定”或“静态绑定
而对于多态,只有等到方法调用的那一刻 解释运行 器 才会确定所要调用的具体方法,这称为 “晚绑定”或“动态绑定 。
多态的作用:提高了代码的通用性,常称作接口重用
补充:
**instanceof **操作符
x instanceof A :检验 x 是否为类 A 的对象,返回值为 boolean 型,一般转型之前使用。
要求 x 所属的类与类 A 必须是子类和父类的关系(直接或间接),否则编译错误。
对象类型转换(Casting)
基本数据类型的 Casting
自动类型转换 :小的数据类型可以自动转换成大的数据类型
如 long g= 20; double d= 12.0 f
强制类型转换: 可以把大的数据类型强制转换成小的数据类型
如 float f= (float) 12.0; int a= (int) 1200 L
对 Java 对象的强制类型转换称为造型
String objStr = (String) obj;
Object objPri = new Integer(5); //多态
//运行时引发 ClassCastException 异常,非法
String str = (String) objPri;
转型时需要是子父类关系
注:只创建了一个new的对象,其他只调用了构造器
方格表示构造器
Object是所有java类的父类(不需要使用extends指明,默认)
方法名称 | 类型 | 描述 | |
---|---|---|---|
1 | public Object() | 构造 | 构造器 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 取得 Hash 码 |
4 | public String toString() | 普通 | 对象打印时调用 |
…
==:基本类型比较值,引用类型比较地址(是否指向同一个对象)
equals 方法,所有类都继承了Object类,可重写equals方法, 没有重写等同于 “==”。
public boolean equals(Object obj) {
return (this == obj);
}
当比较引用类型,作用等同于 “ == ” , 用于比较地址,obj1.equals(obj2)
注: 当用 equals() 方法进行比较时 对类 File 、 String 、 Date 及包装类来说 是比较类型及内容而不考虑引用的是否是同一个对象 , 因为在这些类中已重写equals方法。
当自定义使用 equals() 时,可以重写 。 用于比较两个对象的 内容 是否都相等
任何情况下 x.equals(null) 永远返回是 false
//String类中重写的equals方法
public boolean equals(Object anObject) {
if (this == anObject) { //先判断地址是否一样,地址一样说明内容一定一样
return true;
}
if (anObject instanceof String) { // 判断是否是String类型再比较内容
String anotherString = (String)anObject;
int n = value.length; //当前字符串的长度
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//Object类toString() 方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
Date now=new Date()
System.out.println ("now="+ now); //相当于System.out.println (“now=" + now.toString);
注: print 实际调用了toString()方法
String = null;
System.out.println(s) // null,内部有一个判断是否为空的保护机制
System.out.println(s.toString()); //NullPointerException
基本数据类型包装成包装类的实例 —— 装箱
int i = 500;
Integer t = new Integer(i);
装箱:包装类使得一个基本数据类型的数据变成了类, 有了类的特点,可以调用类中的方法。
获得包装类对象中包装的基本类型变量 —— 拆箱
包装类的 parseXxx (String s) 静态方法可将字符串装换为基本数据类型
Float f = Float.parseFloat ("12.1");
字符串重载的 valueOf() 方法可将基本数据类型转换成字符串
String fstr = String.valueOf (2.34f);
注:包装类的默认值为null
//三目运算比较的是基本类型,在编译时自动拆箱为int,double。同时三目运算要求两种类型一致,所以int自动被提升为double,然后自动装箱为Object类
Object o1 = true ? new Integer(1) : new Double(2.0);
//输出时调用了toString()方法
System.out.println(o1); //1.0
注:Integer内部定义了IntegerCache结构,其中定义了Integer[], 保存了 -128 ~ 127范围内的整数,如果使用自动装箱的方法在该范围内的数,可以直接使用数组中的元素,不需要自己new,从而提高效率
概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
特点: 与返回值类型无关,只看参数列表,且参数列表必须不同。参数个数或参数类型 。调用时, 根据方法参数列表的不同来区别。
重载规则:
实现理论:
public void test (int x, int... i) {}
Java里方法的参数传递方式只有一种: 值传递 。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受 影响
为什么要用get 、 set方法,而不直接使用public
在类的设计中,会定义private变量,然后通过get()、set()方法控制变量,那为什么要用get 、 set方法,而不直接使用public呢?
子类与父类中同名同参数的方法必须同时声明为非 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 + "\nnage : "+ age+ "school : "+ school;
}
}
Person p1=new Person();
p1.getInfo(); //调用 Person 类的 getInfo 方法
Student s1=new Student();
s1.getInfo(); //调用 Student 类的 getInfo 方法
//考查多态的笔试题目:
public class InterviewTest1 {
public static void main(String[] args) {
Base base = new Sub();
//这里调用的是应该是子类中重写的方法,所以输出为sub_1
base.add(1, 2, 3);
Sub sub = (Sub) base;
base.add(1,2,3) //这里调用的的是类中确定性方法,输出sub_2
}
}
class Base {
public void add(int a, int... arr) { //可变形参
System.out.println("base");
}
}
class Sub extends Base {
//这个认为是父类方法的重写,所以会覆盖父类方法
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
//方法的重载
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}
//程序入口、静态方法
public static void main(String[] args ){
}
this、super、static、final、abstract、interface、package、import等
在方法内部使用,即这个方法所属对象的引用
在构造器内部使用,表示该构造器正在初始化的对象,
当 形参与成员变量同名时,若在方法或构造器内需使用成员变量,必须添加 this 表明该变量是类的成员变量
使用 this 访问属性和方法时,如果在本类中未找到,会从父类中查找
this(形参)必须声明在当前构造器的首行,且最多声明一个
this可理解为当前实例对象 或正在创建的对象
class Person{
Person{// 定义 Person 类
private String name ;
private int age ;
public Person(){
System.out.println("新对象实例化 ")
}
public Person(String name,int age){
this(); // 调用本类中的无 参构造器
this.name = name ; //不使用this时,会存在就近原则,导致无法找到name成员变量
this.age = age ;
}
public void getInfo(){
System.out.println("姓名 :" + name);
this.speak();
}
public void speak(){
System.out.println("年龄: :" + this.age);
}
}
包的作用:
使用 super 来调用父类中的指定操作:
注:1. 当子父类出现同名成员时,可以用 super 表明调用的是父类中的成员,没有显示使用则调用子类成员(就近原则);
2. super不仅限于直接父类,也可用于间接父类,所以调用父类中的重写方法时,需要显示使用super
3. 类似于this,this表示本类对象的引用,**super表示父类的内存空间标识 **,这里我理解为存放了父类的内存地址。
关于调用父类构造器
例:
public class Person {
private String name;
private int age;
public Person(String name , int age ){
this(name , age , null);
}
public String getInfo() {
return "Name: "+ name + "\n age: "+ age;
}
}
public class Student extends Person {
private String school;
public Student(String name , int age , String s ){
super(name , age); //显示调用父类构造器
school = s;
}
//编译出错 : no super() 系统将调用父类无参数的构造器。
public Student(String s ) {
//super(); //没有显示this和super时,默认有这个空参构造器
school = s;
}
//@Override 表示这是一个重写方法
@Override
public String getInfo() {
//调用父类成员方法
return super.getInfo()+ "\nschool: " + school;
}
}
this和super的区别
区别点 | this | super | |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
如果想让一个类的所有实例共享数据,就用类变量!不因对象的不同而改变
由于不需要创建对象就可以调用类方法 ,从而简化了方法的调用。
class Person{
public static int total = 0; // 可直接访问 Person.total,共享
}
System.out.println(); //out即static修饰,所以能直接调用
String 类 、 System 类 、 StringBuffer 类
final 标记的方法不能被子类重写。
Object的 getClass()方法
final 标记的变量 成员变量或局部变量 即称为常量 。 名称大写 且只能被赋值一次,不能被更改,必须手动初始化,通常和static一起 。
赋值位置:显示初始化、代码块中初始化、构造器中初始化
final int WIDTH = 0;
final int PI;
final int LEFT;
{
PI = 3.14;
}
public FinalTest(int m){
LEFT = m;
}
作用:对 Java 类或对象进行 初始化
class Person {
public static int total;
//静态代码块,类加载时加载并执行,只执行一次
static {
total = 100; //为 total 赋初值
System.out.println("in static");
}
//非静态代码块,随着对象实例的创建而执行,每创建一次,执行一次
{
total1 = 100;
}
…… //其它属性或方法声明
}
总结:程序中成员变量赋值的执行顺序,由父及子,静态先行
父类:更一般、更通用
子类:更具体
类的设计应该保证父类和子类能够共享特征。当一个父类设计得非常抽象( 没有具体的实例),这样的类叫做抽象类, 用abstract关键字修饰。
抽象方法 :只有方法的声明,没有方法的实现。以分号结束; 且含有抽象方法 的类必须被声明为抽象类。
public abstract void talk();
抽象 类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
abstract class A {
abstract void m1();
public void m2() {
System.out.println("A 类中定义的 m2 方法");
}
}
class B extends A {
void m1() {
System.out.println("B 类中定义的 m1 方法");
}
}
//例:
//定义交通工具类Vehicle类,包括两个子类Truck类和RiverBarge类,由于两种工具计算燃料效率和行驶距离的方法不一致,无法在父类提供计算方法,只能由各个子类去实现,所以要使用抽象类。
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency () {
//写出计算卡车的燃料效率的具体方法
}
public double calcTripDistance () {
//写出计算卡车行驶距离的具体方法
}
}
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency () {
//写出计算驳船的燃料效率的具体方法
}
public double calcTripDistance () {
//写出计算驳船行驶距离的具体方法
}
}
//创建匿名子类
Person p = new Person() { // Person是父类
@Override
public void eat(){
//重写的方法
}
}
接口 就是规范,定义的是一组规则,体现了现实世界中“如果你是 要 则必须能 …”的思想。 继承是一个 是不是 的关系,而接口实现则是 能不能的关系。
如数据库的操作,定义了各种规范,只提供抽象的方法,定义了大部分接口功能,使用时自己实现即可。
class SubClass extends SuperClass implements InterfaceA{
//接口中只能定义全局常量
public static final int MIN = 1;
int MAX = 9; //这里省略了public static final,但仍是全局常量 , 等同于public static final int MAX = 9;
//抽象方法
public abstract void fly();
//省略public abstract
void stop(); //同上
}
interface Runner { public void run();}
interface Swimmer {public double swim();}
class Creator{
public int eat(){…}
}
class Man extends Creator implements Runner ,Swimmer{
public void run() {
//……
}
public double swim() {
//……
}
public int eat() {
//……
}
}
interface AA {...}
interface BB {...}
interface CC extends AA,BB {...}
个人理解通常父类和子类存在某种所属关系,如猫科动物和狮子。而接口一般可以定义某种功能,如猫、狗都会跑(说的有点狭义,但可以这么用)
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内 部类。
在 Java 中,允许一个类的定义位于另一个类的内部,前者称为 内部类 ,后者称为 外部类
分类
内部类可以声明为private或protected, 可调用外部类的结构;若声明为static,则只能使用外部类中static的成员变量。
可以在内部定义属性 、 方法 、 构造器等结构
可以 声明为 abstract 类 因此可以被其它的内 部类 继承、可以声明为 final
class Outer {
private int s;
public class Inner {
private int s = 222;
public void mb(int s) {
System.out.println("在内部类 Inner 中 s=" + s); //局部变量
System.out.println(this.s);// 内部类对象的属性s
System.out.println( Outer.this.s ); // 外部类对象属性s
}
}
public static class InnerStatic {
//....
}
public void ma() {
Inner i = new Inner(); // 使用内部类
i.mb();
}
}
public class InnerTest{
public static void main(String args []){
Outer o = new Outer();//外部类
o.ma();
Outer.Inner b = O.new Inner();//创建非静态的内部类实施方法
b.mb(333);
Outer.InnerStatic c = new Outer.InnerStatic();//创建静态内部类
}
}
设计模式 是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式(类似于下棋有棋谱,就是套路)
所谓 类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法。
如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类 的 构造 器 的 访问权限设置为 private ,这样,就不能用 new 操作符在类的外部产生
类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能 调用该类的某个静态方法 以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的 该类对象的变量也必须定义成静态的 。
//饿汉式 线程安全
class Singleton {
//1. 私有化构造器
private Singleton() {
}
//2. 内部提供一个当前类的实例
//4. 此实例也必须静态化
private static Singleton single = new Singleton();
//3. 提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}
//懒汉式 啥时候用啥时候造 ,延迟对象的创建
class Singleton {
//1. 私有化构造器
private Singleton() {
}
//2. 内部提供一个当前类的实例
//4. 此实例也必须静态化
private static Singleton single = null;
//3. 提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null ){
single = new Singleton();
}
return single;
}
}
单例模式的优点:减少了系统性能开销
应用场景:网站 的计数器、应用程序 的日志应用、数据库连接池等
汇总图
MVC设计模式将整个程序分为三个层次: 视图模型层,控制器层,与数据模型层。 这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
模型层 model 主要处理数据
数据对象封装 model.bean/domain
数据库操作类 model.dao
数据库 model.db
控制层 controller 处理业务逻辑
应用界面相关 controller.activity
存放 fragment controller.fragment
显示列表的适配器 controller.adapter
服务相关的 controller.service
抽取的基类 controller.base
视图层 view 显示数据
相关工具类 view.utils
自定义 view view.ui
使用代理对象来控制对真实对象(real object)的访问,这样就可以在屏蔽直接访问原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
如:明星和经纪人的关系即可看做代理和被代理对象
public class ProxyTest {
public static void main(String[] args ){
Star star = new ProxyStar( new RealStar());
star.contract(); // 经纪人控制
}
}
//定义接口
interface Star {
public void contract();
}
//被代理类 (明星)
class RealStar implements Star{
@Override
public void contract() {
System.out.println("真正的明星在这") ;
}
}
//代理类 (经纪人)
class ProxyStar implements Star {
private Star star;
public ProxyStar(Star star ){
this.star = star;
}
public void check() {
System.out.println("经纪人检查合同");
}
public void contract() {
check();
star.contract();//经纪人先检查合同是否有误,在开始签约
}
}
javaBean 是一种 Java 语言写成的可重用组件,,是指符合如下标准的 Java类: