相信很多的Android开发者和我一样,当初学习Android开发时,对Java的学习并不是非常深入;大致了解了类和对象是怎么回事,对多线程及网络编程有了一个简单的了解之后,便投入到了Android开发中;感觉当时了解的东西就够用了,一些比较偏的点,遇到了在网上找一下就能解决问题了,总的来说不影响日常工作。
但是,随着时间的流逝,慢慢感觉自己遇到了瓶颈,基础的东西都会了;尝试去学习一些进阶的东西,却发现非常的难;由于不了解注解和反射,第一次使用Retrofit框架的时候,完全就是一脸懵逼,搞不懂@是干什么用的;尝试去解读Glide的源码,由于缺乏对泛型及设计模式的了解,连Glide底层的网络请求时在哪里实现都找不到;基础不牢,写代码总是挖坑……。
总之应了那句话,出来混总是要还的。想在这条道上长远的走下去,曾经欠下的东西都得补回来。所以,这段时间对恶补了一写Java基础,总结了一些之前理解有偏差或错误的点,在这里权当笔记记录一下,之后又新的心得体会会持续更新。
基础
基础数据类型的范围
类型 | 位数 | 值域 |
---|---|---|
boolean | JVM 决定 | true/false |
char | 16bit | 0~65535 |
byte | 8bit | -128~127 |
short | 16bit | -32768~32767 |
int | 32bit | -2147483648~2147483648 |
long | 64bit | 很大 |
float | 32bit | 范围可变 |
double | 64bit | 范围可变 |
引用变量
People man=new People();
People women=new People();
People boy=man;
man,women,boy应该称为引用变量,它保存的是存取对象的方法;引用变量并不是对象的容器,而是类似指向对象的指针。
以上赋值代码表达的意思,一个People类型的变量man引用到堆上创建的一个People对象。
"==" 和 "equals"
== 两个引用变量是否引用到堆上的同一个对象。或者是基础数据类型(int,long等)的变量是否相等。
equals 两个对象的内容是否一样
关于"=" 产生的一个低级bug。
之前写代码的时候,就关于变量赋值产生过一个很经(di)典(ji)的bug。这里来分享一下。背景很简单,就是做RecyclerView的下拉刷新,代码如下,很简单。
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
private List<String> datas = new ArrayList<>();
private MyAdaptetr mMyAdaptetr;
SwipeRefreshLayout mSwipeRefreshLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeLayout);
mSwipeRefreshLayout.setOnRefreshListener(this);
RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
datas = getData();
mMyAdaptetr = new MyAdaptetr(datas);
mRecyclerView.setAdapter(mMyAdaptetr);
}
private List<String> getData() {
List<String> datas = new ArrayList<>();
for (int i = 0; i < 100; i++) {
datas.add("item " + i);
}
return datas;
}
@Override
public void onRefresh() {
datas.clear();
datas = getData();
mMyAdaptetr.notifyDataSetChanged();
mSwipeRefreshLayout.setRefreshing(false);
}
}
private class MyAdaptetr extends RecyclerView.Adapter<MyAdaptetr.MyHolder> {
private List<String> datas;
public MyAdaptetr(List<String> datas) {
this.datas = datas;
}
@Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new MyHolder(mView);
}
@Override
public void onBindViewHolder(MyHolder holder, int position) {
holder.text.setText(datas.get(position));
}
@Override
public int getItemCount() {
return datas.size();
}
class MyHolder extends RecyclerView.ViewHolder {
TextView text;
public MyHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(R.id.text);
}
}
}
实际场景代码要比这复杂很多,这里为了说明问题,写了一个简易的demo,但所要表达的问题一致,onRefresh里的代码是有问题的,你发现了吗?
乍一看,这代码貌似没问题,但是执行下拉刷新后,列表直接被清空了,一条数据都显示不出来。记得当初为了找原因,对MyAdapter各种修改,甚至怀疑自己是不是发现了一个Android系统的bug。完全没有去考虑datas=getData()这行代码的意义。直到后来打断点发现,mMyAdaptetr.notifyDataSetChanged() 执行后,再次去Adapter的onBindViewHolder方法中查看时,居然发现的列表的size变成了0。这下可是完全懵逼了。
后来做了如下修改,问题得以解决:
@Override
public void onRefresh() {
datas.clear();
//datas = getData();
datas.addAll(getData());
mMyAdaptetr.notifyDataSetChanged();
mSwipeRefreshLayout.setRefreshing(false);
}
当时,虽然把问题解决了,但是非常的不理解。为什么第一执行datas=getData()时就可以,第二次执行就不行了呢?datas=getData() 和 datas.addAll(getData())有区别吗?不都是把新建的列表赋给datas吗?
结合上面关于引用变量的赋值解释,这个问题就很容易理解了。
- 在onCreate()方法中,第一次执行datas=getData()时,在内存中创建了一段数据空间,同时datas这个变量指向了这段空间。
- datas.clear(),清空了datas所指向的这段内容空间的数据。
- datas=getData(),让datas 指向了一段新的内存空间
- datas.addAll(getData()) 向datas第一次指向的内容空间,重新填充数据。
而当我们创建MyAdapter对象时,由于MyAdapter只会执行一次
public MyAdaptetr(List<String> datas) {
this.datas = datas;
}
因此,MyAdapter内部的datas指向的永远都是我们第一次创建的那块存储区域。
到这里,我们就很容易理解这个bug的本质了。
** "=", 不是的赋值这么简单 !**
多态
引用变量的类型可以是实际对象类型的父类
//Man 继承自People类
People mPeople=new Man();
方法的覆盖,参数,返回值类型,均不能改变,存取权限不能降低
方法的重载,参数不同,返回值类型,存取权限可以改变,与多态无关。
People mPeople=new People();
Object o=mPeople;
int code=o.hashCode();
o.toString();
o.eat();
编译器是根据引用类型来判断有哪些method可以调用,而不是根据引用所指向的对象类型来判断。因此,上述o.eat()将 无法执行,即便People类这个方法,但是对于Object来说是未知的。
Java关键字
this 和 super
-
this 关键字是类内部当中对自己的一个引用,可以方便类中方法访问自己的属性
-
要从子类调用父类的方法可以使用super关键字来引用
super.onResume()
构造函数之this() 和 super()
使用this()来从某个构造函数调用同一个类的另外一个构造函数。this()只能用在构造函数中,并且必须是第一行被执行的语句。
super() 用来调用父类的构造函数,必须是第一个被执行的语句,因此,super()和this() 不能共存。
以后自定义View的时候,构造函数该怎么写,终于有谱了。
abstract
被abstract标记的类称为抽象类,无法被实例化;
抽象类任然可以作为引用变量的类型。
被abstract标记的方法被声明时没有内容,以分号结束;非抽象子类必须实现此方法。
如果有一个类当中有任意一个抽象的方法,那么这个类也必须是抽象的;当然这个抽象类当中,同时可以包括其他非抽象的方法。
如果要限制一个类被实例化,除了使用abstract标记为抽象类之外,还可以将其构造函数标记为private。
static
被static标记的方法,静态方法,可以不需要具体的对象,可直接由类调用。
静态方法不能调用非静态的变量,因为他无法得知是那个实例变量。同理可得,静态方法不能调用非静态的方法。
静态变量是共享的,同一类的所有实例共享一份静态变量,它会在该类的任何静态方法 执行之前就初始化,没有被赋值时,会被设定为该变量所属的默认值。
Math.random();
Math.min(1,2);
因此,带有静态方法的类,一般来说可以是抽象的,不必要被初始化;但不是必须的。
final
static final double PI=3.1415925
final 类型的静态变量为常量
final 类型的变量一旦被赋值就不能再更改
final 类型的方法不能被覆盖
final 类型的类不能被继承
synchronized
防止两个线程同时进入同一对象的同一个方法
public class TestSync implements Runnable {
private static final int COUNT = 500000;
private int count;
@Override
public void run() {
for (int i = 0; i < COUNT; i++) {
increment();
System.err.println("count=" + count);
}
}
private synchronized void increment() {
count = count + 1;
}
public static void main(String[] args) {
TestSync mRunnable = new TestSync();
Thread a = new Thread(mRunnable);
Thread b = new Thread(mRunnable);
a.start();
b.start();
}
}
上面的代码中,当a,b 两个线程同时开始执行run方法时,在缺少synchronized的情况下,两个线程将由虚拟机的调度器控制执行,因此当a线程完成count+1时,还没来得及赋值操作,就被切换到了b线程,b线程再次执行count+1操作时,就会丢掉a完成的工作,最终会导致结果不正确,两个线程内各自经历COUNT次循环后,并没有使最终的值达到COUNT*2 的结果。
只有increment()方法被synchronized修饰后,就可以保证在每次在a线程完整了执行完了increment方法后,b线程才可以执行该方法,反之亦然。这样就可以保证最终的执行结果的正确性
需要注意的是,synchronized(锁)并不是加在方法上,而是配在对象上。某个对象都有一把“锁”和一把“钥匙”存在,大部分时候并没有实际意义,只有对方法使用synchronized(同步化)之后,锁才会变得有意义。当同时有多个线程需要执行同步化方法时,只有取得当前对象锁的钥匙的线程才能进入该同步化方法。其他线程只有在获得钥匙的线程完整的执行完毕同步化方法时,才会获得钥匙从而进入同步化方法,否则,就只能等待。因此,使用synchronized会消耗额外的资源,会使方法执行变慢,因此要谨慎使用。同时,使用不当会导致死锁问题的产生。
当对象有多个同步化的方法时,锁和钥匙还是只有一把。获得钥匙的某个线程进入到该对象的同步化方式时,其他线程也无法进入该对象其他的同步化方法。此时,唯一能做的事情就是等待。
未完待续。。。。
关于Kotlin
Google I/O 大会之后Kotlin很火,那么我们是否意味着在Android开发中他会取代Java呢?
Google’s Java-centric Android mobile development platform is adding the Kotlin language as an officially supported development language, and will include it in the Android Studio 3.0 IDE.
这段话应该说的很清楚了。
网友评论