我写的一系列“Android 内功心法”着重讲到android中经常使用的设计模式。那么如果有些程序员刚刚接触设计模式,那就有必要确定一下自己面对对象编程的基础是否牢固了。
因为这直接关系到你阅读设计模式的速度和理解质量。
接下来我将简单介绍java中面对对象编程的一些基础知识。
1,类和修饰符
public class ClassTest{
public ClassTest{
}
public void test(){
}
}
其中类的定义是以“class”来决定的。class关键字就表示类。
类的命名规范“骆驼命名法”,首字母大写,如果名字由多个单词,则每个单词首字母大写。
“public ”则是修饰符。
public 代表所有包的类都可以访问。(公有)
protected 代表同包下的类可以访问。
private 代表只能当前类才可以访问。(私有)
如何使用类中的方法呢?
若想使用就要”实例化“这个类。实例化类用到关键字”new“
ClassTest classTest;
classTest=new ClassTest();
2,构造方法
也可以称之为”构造函数“。对类初始化的方法。和类同名,没有返回返回值,不需要”void“。在类被实例化的时候自动被调用。
public class ClassTest{
public ClassTest{//构造方法
}
}
所有的类都有构造方法,如果程序员不写,系统也会默认生成一个空的构造方法。若程序员自己定义了,则默认的构造方法就失效了。
3,方法重载
方法重载的规则就是相同的方法名可以有多个方法,但是必须保证参数不同,简称”同名不同参“。
public class ClassTest{
public ClassTest{//构造方法
}
//方法重载
public void test(int id){
}
public void test(String name){
}
public void test(double money){
}
}
方法重载可以在不改变原来方法的基础上,新增功能。
4,变量和属性
类中可以有多个变量而变量可以有多个属性。
public class ClassTest{
String mName;//不加修饰符的变量默认protected
private int mId;//类中声明的变量
public void setMyId(int id){//变量拥有的方法(属性)
mId=id;
}
public int getMyId(){//变量拥有的方法(属性)
return mId;
}
}
其中,mName如果不添加修饰符,则java中默认protected。
5,封装
封装属于一种思想。一个类中声明一个方法,这个方法在传入规定的参数后就可以给你返回一个想要的结果。而方法中的各种逻辑和计算其他类不用管。
这种代码方式被称为”封装“。
public class ClassTest{
public String checkMoneySize(double money) {
if (money >= 100) {
return "百元以上";
} else if (money >= 1000) {
return "千元以上";
} else {
return "";
}
}
}
那么,外部调用的话只需要调用方法,传入参数就可以得到相应的结果。
封装的优点:
1,减少耦合性
2,内部自由修改
3,清晰的传入参数和返回结果(对外的接口)
6,继承和重写(重构)
继承(extends)代表了一种”强耦合“的关系。
子类继承父类,拥有了父类的所有特性之外还可以拥有自己的特性。
子类还可以重写父类的所有非”private“的方法。
public class ClassTest {
public String checkMoneySize(double money) {
if (money >= 100) {
return "百元以上";
} else if (money >= 1000) {
return "千元以上";
} else {
return "";
}
}
}
public class ClassTestTow extends ClassTest {
@Override
public String checkMoneySize(double money) {
//return super.checkMoneySize(money);
if (money >= 100) {
return "百元以上";
} else if (money >= 1000) {
return "千元以上";
} else if (money >= 10000) {
return "万元以上";
} else {
return "";
}
}
}
继承的关键字”extends “
重写父类方法的关键字是在重写父类方法后在其开始部位写上”@Override“
还有一种情况,就是需要保留父类方法的情况下重写。
那么就要加上”super.checkMoneySize(money);“
使用”super.”关键字+父类方法名。
这段代码放置位置也决定执行顺序,如果在重写方法里,super放在重写的方法之前,那么父类的方法则会优先执行,如果在子类所有重写代码之后加上super则子类的方法会优先执行。
继承的缺点在于”父类变,子类跟着变“,父类的实现细节暴露给了子类。
当两个类之间相似度很高的时候就可以考虑使用继承。
7,多态
多态是面向对象编程的三大特性之一。(封装,继承和多态)
多态特点在于子类以父类的身份去完成逻辑,用自己的逻辑去完成父类的目的。子类特有的属性不可以使用。
同时,为了使子类能够完全代替父类工作,常常使用”重写(@Override)“来表示父类的方法或者类。
//父类
public class ClassTest {
public String checkMoneySize(double money){
//父类的方法取决于子类使用不使用
if (money >= 10000)
return "你的钱够10000了";
else
return "你的钱不够";
}
}
//实现其父类的具体类
public class ClassTestOne extends ClassTest {
//实现父方法
@Override
public String checkMoneySize(double money) {
if (money >= 1000)
return "你的钱够1000了";
else
return "你的钱不够";
}
}
//实现其父类的具体类
public class ClassTestTow extends ClassTest {
//实现父方法
@Override
public String checkMoneySize(double money) {
if (money >= 100)
return "你的钱够100了";
else
return "你的钱不够";
}
}
//测试方法
public void test() {
ClassTest classTestOne = new ClassTestTow();
String a = classTestOne.checkMoneySize(1000);
ClassTest classTestTow = new ClassTestOne();
String b = classTestTow.checkMoneySize(100);
}
通过代码可以看出,ClassTest 类他就有了两种状态,一个状态是ClassTestOne还有一个就是ClassTestTow.
不同的对象可以执行相同的动作,但是需要自己来实现。
多态的原理就是当父类方法被调用的时候,父类方法会被子类实现,去执行子类中的逻辑而非父类的。那么这个父类可以有多个子类,那么也就有了多种状态。
8,抽象类
抽象类不能实例化,因为它没有”意义“。
抽象类中的抽象方法必须被子类重写。
抽象方法没有具体的实现内容。
如果一个类中有抽象方法,那么这个类必须是抽象类。
抽象类存在的意义在于尽可能多的整合代码和逻辑,尽可能少的存放数据。
抽象类的存在就是为了被继承。所以,父类是抽象类的时候,子类可以是具体类也可以是抽象类但是末尾的子类必须是具体类要么这个抽象类的存在就没有意义。
//抽象类
public abstract class ClassTest {
public abstract String checkMoneySize(double money);
}
//实现其抽象类的具体类
public class ClassTestOne extends ClassTest {
//实现抽象方法
@Override
public String checkMoneySize(double money) {
if (money >= 1000)
return "你的钱够1000了";
else
return "你的钱不够";
}
}
//实现其抽象类的具体类
public class ClassTestTow extends ClassTest {
//实现抽象方法
@Override
public String checkMoneySize(double money) {
if (money >= 100)
return "你的钱够100了";
else
return "你的钱不够";
}
}
//测试方法
public void test() {
ClassTest classTestOne = new ClassTestTow();
String a = classTestOne.checkMoneySize(1000);
ClassTest classTestTow = new ClassTestOne();
String b = classTestTow.checkMoneySize(100);
}
ClassTestOne 和ClassTestTow 类都是具体类,都继承了抽象类ClassTest,并且重写了抽象方法。
ClassTest抽象类存在的意义就是ClassTestTow 和ClassTestOne都有一个判断金钱的方法checkMoneySize(double money)。
那么就可以实现一个抽象类来管理这个方法,而子类去实现。
9,接口
接口(interface)的目的就是为了把公共的方法和属性封装起来提供一个特定的接口,让外界去调用。
一个类如何实现了某个接口,那么这个接口下的所有方法都必须全部实现。
接口和抽象类比较相似,但是不同的是:
1,接口不能提供任何一个方法的执行方式
2,接口不能实例化不能有构造方法和变量也不能有修饰符(private等)。不能声抽象方法和静态方法。
一个类可以支持多个接口,多个类可以支持相同的接口。
接口的用法也不同,现在来看第一种
//定义接口
public interface ITest {
String checkMoneySize(double money);
}
//实现其接口的具体类
public class ClassTestThree implements ITest {
//实现接口方法
@Override
public String checkMoneySize(double money) {
if (money >= 100)
return "你的钱够100了";
else
return "你的钱不够";
}
}
//测试方法
public void test() {
ClassTestThree classTestThree=new ClassTestThree();
String a=classTestThree.checkMoneySize(100);
}
当一个类实现了某个接口,那么它就必须实现接口中的所有方法。
外部调用某个类(ClassTestThree )中的某个接口(ITest )的方法(checkMoneySize)的时候,类已经将接口方法做了逻辑处理。外部只需要传入参数和获得接口。
第二种接口使用方法:
//实现其接口的具体类
public class ClassTestFour {
private ITest iTest;
private void checkMoneySize(ITest iTest) {
this.iTest = iTest;
try {
Thread.sleep(2000);//睡2s,模拟耗时操作
String a=iTest.checkMoneySize(100);//实现接口的时候,类传入处理的值
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//测试方法
public void test() {
ClassTestFour classTestFour=new ClassTestFour();
classTestFour.checkMoneySize(new ITest() {
@Override
public String checkMoneySize(double money) {
//调用类中的接口的方法的时候,获得类处理过的值 即获得money
if (money >= 100)
return "你的钱够100了";
else
return "你的钱不够";
}
});
}
这种使用方式是类做一系列的逻辑处理,这个处理可能是耗时的 ,可能是异步的。
那么实现方法(test)在调用的时候就可以new这个类中的接口
当类处理完了接口返回的结果自然返回test类中的checkMoneySize方法的接口中,完成一次异步的调用。
接口的第二种用法就不为了异步的获得结果。
接口和抽象类是相反的生成原因。
抽象类是发现多个类有相同的逻辑代码,今儿生成抽象类。而接口是预先知道逻辑而定制的接口。
10,集合
java中的集合都继承于Collection
而list集合中包含:Vector;LinkedList;ArrayList.
Set集合中包含:HashSet;LinkedHashSet;SortedSet;
Vector : 基于Array的List,其实就是封装了Array所不具备的一些功能方便我们使用,它受Array的限制。性能也就不可能超越Array。所以,在可能的情况下,我们要多运用Array。另外很重要的一点就是Vector 是线程同步的,这个也是Vector和ArrayList的唯一的区别。
ArrayList:同Vector一样是一个基于Array上的链表,但是不同的是ArrayList不是同步的。所以在性能上要比Vector优越一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。
不同线程同时添加arraylist就可能会出现位置冲突。
比较:所以,当你的list需要在多线程下添加删除的话,建议使用Vector;一般情况下使用arraylist;
LinkedList:LinkedList不同于前面两种List,它不是基于Array的,所以不受Array性能的限制。它每一个节点(Node)都包含两方面的内容:1.节点本身的数据(data);2.下一个节点的信息(nextNode)。所以当对LinkedList做添加,删除动作的时候就不用像基于Array的List一样,必须进行大量的数据移动。只要更改nextNode的相关信息就可以实现了。这就是LinkedList的优势。
比较:那么,LinkedList相对于Vector和ArrayList来说的优势就是不需要移动数据,所以,add和remove操作更加快捷。
但是,对于数据的读取和写入(get,set),LinkedList就不如前两种了。
List总结:
- 所有的List中只能容纳单个不同类型的对象组成的表,而不是Key-Value键值对。例如:[ tom,1,c ];
- 所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ];
- 所有的List中可以有null元素,例如[ tom,null,1 ];
- 基于Array的List(Vector,ArrayList)适合查询,而LinkedList(链表)适合添加,删除操作。
**HashSet:**List基本上都是以Array为基础。但是Set则是在HashMap的基础上来实现的。
**LinkedHashSet:**HashSet的一个子类,一个链表。
**TreeSet:**SortedSet的子类,它不同于HashSet的根本就是TreeSet是有序的。它是通过SortedMap来实现的。
可以看出set型集合中,HashSet和LinkedHashSet的差别在于基于HashMao的HashSet适合查询,而LinkedHashSet(链表)适合添加,删除操作。
而TreeSet和前两种不同在于它是有序的,适合查询而添加/删除较弱。
11,泛型
泛型的作用是作为参数的占位符。
例如List,其中,T就是泛型,就是arraylist的数据类型占位符。
当实例化一个arraylist的时候,我们可以这样做:
List list=new Arraylist<>();
其中的泛型就被具体参数类型所代替。
泛型的使用在设计android框架的时候运用广泛。
例如,listview的适配器adapter中需要优化,就必不可少的用到ViewHolder。
加入我们需要向这个ViewHolder中传入view以及数据。那么我们怎么做才能让这个ViewHolder能够适配所有类型的数据?
public class BaseAdapter<T extends ViewHolder>{
}
public class ViewHolder(){
public ViewHolder(View root){
}
}
那么当list集合每次调用程序中的BaseaAdapter时就能够给他传入一个数据类型
private class TestAdapter extends BaseAdapter<TestViewHolder>{
//BaseAdapter<T>中的泛型就被TestViewHolder
}
同样的我们再写一个不同的TestTowViewHolder也同样能够替换T这个泛型。
可能有人会问,代码中的泛型T extends ViewHolder是为什么?
这就是更加灵活的运用泛型的例子。
我们的适配器(BaseAdapter)只能传入ViewHolder,其他的类型不能接受。接收了适配器会解析不出来。
这个时候就有这么个需求,既要传进来的数据多样化,又要给这个多样化确定一个范围。那么就让这个T泛型 extends ViewHolder这个基类。
那么,泛型的作用就在于给一个集合体传入一个数据类型,而这个数据类型必不可少,也不能定死。这个时候就用泛型来充当占位符。
外界必须传入一种类型而内部也可以直接用占位符一直做逻辑处理。
12,委托与事件
java的是没有委托的。但是事件在android中最好的体现就是各种监听事件。比如:点击事件监听,checkedchange监听。
委托是对函数的封装。可以理解为当前方法特征指定给一个名称,而事件就是委托的一种特殊形式。当发生事件的时候,事件就会处理通知过程。
我们来模仿一个android的点击事件:
//仿android点击事件
public abstract class OnClickListener {
private View v;
public abstract void onClickListener(View v);
private OnClickListener(View v) {
this.v = v;
if ("这里是view点击处理的逻辑代码".equals("用户点击的响应")) {
onClickListener(v);
}
}
}
//测试
private void test() {
View v=findviewByid(R.id.test);
new OnClickListener(v) {
@Override
public void onClickListener(View v) {
//用户点击了该view就会把事件传递到这里
}
};
}
这就相当于我们写一个点击事件,并声明一个view,将这个点击事件赋予view。当view做点击操作的时候,这个点击事件就会被触发。
总结
java或者android中面对对象编程的基础知识就包括这15点。当然还有更多知识点。
其中,三大思想是”封装,继承,多态“;
只有完全了解和运用了面对对象的基础,才能更好的去理解设计模式。
面对对象编程是更好理解设计模式的前提。
无前者不后者