从Android到Java (一)

2017-05-30  本文已影响191人  IAM四十二
Java

相信很多的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吗?

结合上面关于引用变量的赋值解释,这个问题就很容易理解了。

而当我们创建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

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.

这段话应该说的很清楚了。

上一篇 下一篇

猜你喜欢

热点阅读