java 核心技术卷I II

第三章 java基本程序设置结构

java基本数据类型

整形:

类型存取需求取值范围
int4字节-2 147 483 648 ~ 2 147 483 647
short2字节-32 768 ~ 32767
long8字节-9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807
byte1字节-128 ~ 127

浮点型:

类型存取需求取值范围
float4字节大约 ±3.402 823 47E+38F(有效位为 6~7位)
double8字节大约 ±1.797 693 134 862 315 70E+308(有效位为15位)

char类型,和boolean类型

strictpf关键字

对于使用strictpf关键字标记的方法必须使用严格的浮点计算来生成可再生的结果。如果将一个类标记为strictpf,这个类中的所有方法都要使用严格的浮点运算。

数值类型之间的转换

当两个数值进行二元操作时候,先要将两个操作数类型转为同一种类型,然后进行计算。
如果两个操作数仲有一个是double类型,则另一个操作数就会转为double
否则如果其中一个操作数是float类型,则另一个操作数就会转为float
否则如果其中一个操作数是long类型,则另一个操作数就会转为long
否则,两个操作数都将被转换位int类型

第四章 对象于类

对象

要想使用oop,一定要清楚对象的三个主要特征
对象的行为——可以对对象世家那些操作,或可以对对象施加哪些方法?
对象的状态——当施加那些方法时,对象如何响应?
对象的表示——如何辨别具有相同行为与状态的不同对象?

方法参数

java程序设置语言总是采用按值调用的。如果是基本数据类型就是复制该值,如果是引用类型就复制该引用。例子如下

    public static void main(String[] args) {
        Boolean flag1 = new Boolean(false);
        Boolean flag2 = new Boolean(true);
        swap(flag1,flag2);
        System.out.println(flag1);//false
        System.out.println(flag2);//true
    }
    public static void swap(Object obj1, Object obj2) {
        Object temp = obj1;
        obj1 = obj2;
        obj2 = temp;
    }
总结一下java中方法参数的使用情况:
一个方法不能修改一个基本数据类型的参数(既数值或者布尔型)
   int x = 3;
   increment(x);
   System.out.println(x); //3;

:一个方法可以改变一个对象参数的状态

   StringBuilder sb = new StringBuilder("发达了!");
   increment(sb);
   System.out.println(sb.toString()); //发达了!做梦;

:一个方法不能让对象参数引用一个新的对象
如之前的代码

通过this的方式对构造器进行重载

在这里插入图片描述
为什么我要写这个?这个继承当中的super用法一样。只不过super指向是父类中的this。当然这只是一个简单的类比。实际上: super不是一个对象的引用,不能将super复制给另一个对象的变量,他只是一个指示编译器调用超类方法的特殊关键字

继承

理解方法的调用

   public void say(int a){};
    public void say(String){};
    X x = new X();
    x.say("加油");

上述的 .say调用方法的执行步骤

1.编译器查看对象的声明类型和方法名。如图x.say(“加油”),则隐式参数x声明为X类的对象。需要注意的是:有可能存在多个名字say的方法,但是参数类型不一样。编译器将会一一列于所有X类名为say的方法和超类中的访问属性为public且名为say的方法。至此,编译器已经获取所有可能被调用的候选方法

2.接下来,编译器将查看调用方法时提供的参数类型。如果在所有名say的方法中存在一个与提供参数类型完全匹配,就选择这个方法。这个过程被称为重载解析由于允许类型转变的存在。参数向上转型可被视为安全。所以如果找不到自身的参数类型,也会去寻找能自身能向上转化的类型的方法。如果都没找到报错。

3.如是是private方法、static方法、final方法或者构造器(其实也是static final方法)。那么编译器将会可以准确的知道应该调用哪个方法。我们称为调用方式的静态绑定

4.当程序运行,并且采用动态绑定调用方法(一个是方式,一个是方法。),虚拟机一定会调用与x所引用对象的实际类型最合适的那个类的方法。假设 Father x = new X(); 那么x的实际类型是X,他是Father的子类。如果X类定义了say(String)方法,直接调用,获取去父类找,以此类推。
每次调用方法都需要进行动态搜索,时间开销相当的大。因此,虚拟机预先为每个类创建一个方法表 (我想应该在jvm加载类的时候创建) 其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候,虚拟机查表就可以了。(题外话,cglib的代理模式,也是运用了类似这种方法起到快速找到对应的方法,好像叫fastmethod)

5.首先,虚拟机提取x的实际类型的方法表

6.接下来,搜索定义的方法say(String)签名的类。此时,已经确定了调用方法

7调用

final修饰方法一个远古误区

在早期,有些程序员为了避免动态绑定带来的系统开销,多使用final修饰方法。如1.0版本的Vector。不过我看了下已经修改了。如果一个方法没有被覆盖并且很短,编译器就能够对他进行优化处理,这个过程称为内联。例如,内联调用e.getName()将被替换为e.name域

接口、lambda表达式与内部类

解决默认方法的冲突

public interface Named {
    default String getName() {
        return "123";
    }
}
public interface Person {
    default String getName() {
        return "123";
    }
}
public abstract People{
    public String getName() {
        return "123";
    }
}
public class Student extend People implements Named,Person{1}

public class Student implements Named,Person{2@Override
    public String getName() {
        return Named.super.getName(); //or Person.super.getName()//
         }
}

: 【1】超类优先,如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。这里有一个很奇怪的一点如果我把People getName方法修饰符改成不是public那么它会在这里插入图片描述
意思是:试图分配较弱的访问权限(“受保护”);是“公开的”
也就是说超类优先,是编译器默认的规则,它比较了接口和超类对应的方法,然后默认实现超类的方法。隐式覆盖了该冲突方法为什么这么说呢? 如果超类的该方法不是public。他就会导致如上述图中的问题。这个问题出在重载父类或接口的方法的时候,不能修改其访问域。显而易见,子类在超类和接口默认方法冲突的时候,默认选择了父类的方式,其实可以想象成。子类继承父类拥有了父类全部的东西。所以子类中也有getName方法,如果把它变成不是public就会导致冲突

:【2】接口冲突,如果一个超接口提供了一个默认方法,另一个接口提供一个同名而且参数类型(不论是否是默认参数)相同的方法,必须覆盖这个方法来解决冲突。

lambda表达式

在这里插入图片描述
lambda包含三样东西 代码块,参数,自由变量值(这是指非参数而且不在代码中定义的变量)

lambda表达式的代码可能会在调用返回后很久才运行,而那时这个参数变量已经不存在了。如何保存变量?
闭包,可以想象成一个lambda表达式就是一个包含一个方法的对象,这个自由变量的值就会复制到这个对象的实例变量当中。

为什么lambda中的自由变量值需要是不可变

明显我的代码是破坏了lambda的规范,是不合法的。在并发的情况下。一个接口函数可能同时被调用。这会导致自由变量的状态值不稳定。所有在lambda引用外围的变量时候,一般都是最终变量(初始化之后就不会再为它赋新值),甚至更好是一个不可变变量。

内部类

在这里插入图片描述

异常

在这里插入图片描述
在这里插入图片描述
父类的抽象方法,子类可以选择不抛出异常。因为抽象方法根本不知道是如何到底被实现的。
父类的实例方法,子类可以抛出该异常的子类异常,让异常更具体化。这个没有问题的

 try {
   ...
 }catch(FileNotFoundException | UnknownHostException e) {
    ...
 }catch(IOException e) {
   ...
 }

当捕获异常类型彼此之间不存在子类关系时候可以写在一起。==注意:==同时捕获多个异常时,异常变量隐含为final变量。所以只能给一个e赋值。

异常开发技巧

   try {
    access the database
  }catch(SQLException e) {
  throw new ServletException("database error:" +e.getMessage());
}

   try {
    access the database
  }catch(SQLException e) {
   Throwable se = new ServletException("database error");
   se.initCause(e);
   throw se;
}

第一种方法会导致异常链丢失。建议使用第二种包装技术

强烈建议解耦合try/catch和try/finally语句块

   InputStream in = 。。。;
   try {
      try {
          code that might throw exceptions
      } finally {
          in.close();
       }
   }catch(IOException e) {
   }

这种设计方法不仅清楚,而且还将捕获finally子句中出现的错误

泛型程序设计

在这里插入图片描述

上述中的代码,编译器会将他们自动打包参数为1个Double和2个Integer对象,然后寻找这些类的共同超类型。找到了2个这样的超类型:Number 和 Comparable接口

类型擦除

**无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。**原始类型的名字就是删去类型参数后的泛型类型名字。

T  t => Object t;
ArrayList< String > list => ArrayList list;

public class Interval< T extends Comparable & Serializable > implements Serializable {
   private T var1;
   private T var2;
}
转变为:
public class Interval implements Serializable {
   private Comparable  var1;
   private Comparable  var2;
}

原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Object替换

如果调换SerializableComparable  的位置
public class Interval< T extends Serializable & Comparable > implements Serializable {
   private T var1;
   private T var2;
}
转变为:
public class Interval implements Serializable {
   private Serializable var1;
   private Serializable var2;
}

如果这样做,原始类型用了Serializable替换T,而编译器在必要时要向Comparable插入强制类型转换。为了提高效率,应该将标签接口(既没有方法的接口)放在边界列表的末尾

Java泛型中的桥方法(Bridge Method)

链接: 点击查看.

如何生成泛型数组

在这里插入图片描述

泛型类的静态上下文中类型变量无效

   public class Singleton<T>
   {
    private static T singleInstance;
    public static T getSingleInstance()
     {
       if(singleInstance == null) ...create new instance of T
       return sigleInstacne
     }
   }

如果这个程序能运行,就能声明多个Singleton< A >,Singleton< B >。共享不一样的数据,但因为擦拭的原因,只剩下Singleton类,和一个 Ojbect singleInstace 的域。因此,禁止!!!

可以消除对受查异常的检查

。。。不懂
https://www.codeleading.com/article/29174191128/

通配符

<? extends Father>

   List<? extends Father> list= new ArrayList();
   list.add(new Father()); //illegal
   list.add(new Son()); //illegal
   list.add(null);//OK 
   Father fa = list.get(0);//ok

ArrayList中add源码如下

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    因为泛型的原因会变成
      public boolean add( ? extends Father e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = ? extends Father;
        return true;
    }

编译器只知道需要某个Father的子类型,但不知道具体是什么类型。它拒绝传递任何特定的类型。毕竟?不能用来匹配
ArrayList中get源码如下

   public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }
          因为泛型的原因会变成
      public ? extends Father get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }
    }

然后get却不存在这个问题:因为返回赋值给Father的引用完全合理。

<? super Father>

   List<? super Father> list= new ArrayList();
   list.add(new Father()); //OK 
   list.add(new Son()); //OK 
   list.add(null);//OK 
   Object fa = list.get(0);

正好与extends相反,

集合

集合框架接口图

在这里插入图片描述

集合框架中的类

在这里插入图片描述
可以把这些抽象类看成是骨架类,避免了每个实现类都需要实现接口中的全部方法。

集合类型描述
ArrayList一种可以动态增长和缩减的索引序列
LinkedList一种可以在任何位置进行高效地插入和删除操作的有序序列
ArrayDeque一种循环数组实现的双端队列
HashSet一种没有重复元素的无序集合
TreeSet一种有序集
EnumSet一种包含枚举类型值的集
LinkedHashSet一种可以己住元素插入次序的集
PriorityQueue一种允许高效删除最小元素的集合(由于习惯上将1设为”最高“优先级,所以会将最小的元素删除)
HashMap一种存储键值关联的数据结构
TreeMap一种键值有序排列的映射表
EnumMap一种键值属于枚举类型的映射表
LinkedHashMap一种可以记住键值项添加次序的映射表
WeakHashMap一种其值无用武之地后可以被垃圾回收器回收的映射表
IdentityHashMap一种用==而不用equal比较键值的映射表

Map函数新方法

merge

key存在会且值不为null通过函数改写val的值,key不存在或key的val为null,不执行函数。直接用val替换旧值,put进去,如果return null 则会删除现有条目

      String xd = map.merge("1", "XD", (a, b) -> {
            System.out.println();
            System.out.println(a);//"a"
            System.out.println(b);//"XD"
            return a + b;
        });

compute

     String compute = map.compute("1", (a, b) -> {
           System.out.println();
        System.out.println(a);
         System.out.println(b);
            return a + b;
        });

如果key存在,那么a为的值,b为val的值。如果key不存在。a为key b为null,同样都执行函数put(key,compute),如果return null 则会删除现有条目

computeIfAbsent

           String computeIfAbsent = map.computeIfAbsent("1", (a) -> {
            System.out.println();
            System.out.println(a); //1 key
            return a + 1;
        });

computeIfAbsent:如果缺失就编译,顾名思义如果key存在且值不为null。那就啥也不做。返回val,如果key不存在或val为null。执行函数。返回put(key,函数结果)

computeIfPresent

           String computeIfAbsent = map.computeIfPresent("1", (a,b) -> {
            System.out.println();
           System.out.println(a);//key
           System.out.println(b);//val
            return a + 1;
      });

computeIfPresent:如果存在就编译。顾名思义如果key存在且值不为null,执行函数,a为key,b为val。put(key,computeIfAbsent).反之啥也不做

replaceAll

   map.replaceAll((a,b)->{
                System.out.println(a);
                System.out.println(b);
                return a +b;
            });

遍历map函数,a为key,b为val

并发

中断线程

在这里插入图片描述

线程状态

线程有如下6种状态 NEW(新创建),Runnable(可运行/就绪),Blocked(阻塞),Waiting(等待),Timed waiting(计时等待),Terminated(被终止)

新建线程

当new操作符创建一个新线程时,如new Thread(Runnable),该线程还没有开始运行。这意味着他的状态是new。当一个线程处理新创建转台时,程序还没有开始运行线程的代码。在线程运行之前还有一些基础工作要做。

可运行线程

一但调用start方法,线程处于runnalbe状态。一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间。(Java的规范说明没有将它作为一个单独状态。一个正在运行中的线程仍然处于可运行状态)

被阻塞线程和等待线程

当线程处于被阻塞或等待状态时,它暂时不活动。它不运行任何代码且消耗最少的资源。直到线程调度器重新激活它。细节取决于它是怎样达到非活动状态的。

  1. 当一个线程试图获取一个内部的对象锁(而不是concurrent库中的锁),而该锁被其他线程持有,则该线程进入阻塞状态。当所有其他线程释放了该锁,并且线程调度器允许本线程持有它的时候,该线程将变为非阻塞状态
  2. 当线程等待另一个线程通知调度器一个条件时,它自己进去等待状态。在调用Object.wait方法或Thread.join方法,或者是等待concurrent库中的Lock或Condition时,就会出现这种情况。实际上,被阻塞状态与等待状态有很大不同
  3. 有几个方法有一个超时参数。调用他们导致线程进去了计时等待(time waiting)状态。这一状态将一直保持到超时期满或者接收到适当的通知。带有超时参数的方法有Threa.sleep 和 Object.wait,Thread.join,Lock.tryLock以及Condition.await的记时版。
    在这里插入图片描述

被阻塞:当争夺不到锁的时候线程无法继续执行下去,就会导致阻塞;

等待:每个class都会继承来自Object的wait,notify,notifyAll方法。这些方法构成了内置的条件队列API。对象的内置锁(synchronizd)与内部条件队列是相互关联的,要调用对象X中条件队列的任何一个方法,都必须持有对象X上的锁。换言之,持有锁了才能执行这些API。Object.wait会自动释放锁,并请求操作系统关起当前线程。从而使其他线程获得该锁修改对象状态,发出notify,notifyAll,唤醒被挂起的线程。

计时等待:Thread.sleep(Time time),该方法是不会释放锁的,其目的就想让释放锁的操作回到程序员的控制范围内。sleep只是让出CPU的调度,不要浪费时间片,让CPU去执行其他的操作。

notify,notify的区别

notify,和notifyAll存在区别。notify对单独一个线程进行唤醒。我们说过这些api只有获得锁的线程才能调用。然后当对单独一个线程进行唤醒后。他将在返回之前重新获得锁。notifyAll,唤醒全部线程,只有获取到锁的线程才能从wait状态中返回
注意:多数情况下都会使用notifyAll来唤醒全部等待的线程,避免类似于信号丢失。举个例子。假设线程A在条件队列上等待条件谓语PA,同时线程B在同一个条件队列上等待条件谓语PB,现在,假设PB变为真,并且线程C执行了一个notify;JVM将从它拥有的众多线程中选择一个并唤醒。如果选择了A线程,那么它被唤醒,但是看到PA尚未为真,因此继续等待。同时,线程B本可以执行。却没有被唤醒。当然notifyAll也存在这种情况,在多个条件谓语存在的时候

只有同时满足以下两个条件采用notify:
1:所有等待线程的类型相同。只有一个条件谓词与条件队列相关,并且每个线程从wait返回后执行相同的操作
2:单进单出。在条件变量上的每次通知,最多只能唤醒一个线程来执行

线程安全集合

安全的映射集合:ConcurrentHashMap,ConcurrentSkipListMap,ConcurrentSkipListSet,ConcurrentLinkedQueue

对于这四种集合,有一个统一得特点。迭代时返回弱一致性得迭代器。意味着迭代器不一定能放映出他们被构造之后得所有修改,但是,它们不会将用一个值返回两次,也不会抛出ConcurrentModificationException

   CuncurrentHashMap<String,Long> map = new ConcurrentHashMap;
   map.put("a",1L);
   while(true){ 
   new Thread(() -> {
      Long oldValue = map.get("a");
      Long newValue = oldValue +1;
      map.put("a",newValue)
     }).start();
   };

该方法并不是线程安全的。有程序员很奇怪为什么原本线程安全得数据结构会允许非线程安全的操作。不过有两种完全不同的情况。如果多个线程修改一个普通的HashMap,它们会破坏内部结构。有些链接可能会丢失,或者甚至会构成循环,使得这个数据结构不再可用。对于ConcurrentHashMap绝对不会发生这种情况。在上面的例子中,get和put代码不会破坏数据结构。==不过,==由于操作不是原子的,所以结果不可预知。

ConcurrentHashMap中不允许有null值。有很多方法都是用null值来指数映射中某个给定的键不存在

对于并发散列映射得批操作

有三种不同的操作

1:搜索(search)为每个键或值提供一个函数,直到函数生成一个非null的结果。然后搜索终止,返回这个函数结果
2:归约(reduce)组合所有键或值,这里要使用所提供的一个累加函数。
3:forEach 为所有键或值提供一个函数

每个操作都有4个版本

1:xxxKeys:处理键
2:xxxValues:处理值
3:xxx:处理键值
4:xxxEntries:处理Map.Entry对象

对于上述操作,都需要指定一个参数阈值(parallelism threshold)。如果映射包含的元素多余这个阈值,就会并发完成批操作。如果喜欢批操作在一个线程运行,就可以使用Long_Max_VALUE,相反则用1

Callable与Future

Future保存异步计算的结果。可以启动一个计算,将Future对象交给某个线程,然后忘掉它。Future对象的所有者在结果计算好之后就可以获得它。

  public interface Future<V> {
   V get() throws ...; //调用此方法,在任务未完成前堵塞
   V get(long timeout, TimeUnit unit) throws...;//调用超时,抛出TimeoutException异常
  //上述两者,如果运算该计算的线程被中断,两个方法都将抛出InterruptedException
   void cancal (boolean mayInterrupt);
   boolean isCancelled();
   //上述两者打配合,如果用cancel方法取消该计算。
   //如果计算还没开启,被取消且不再开始。如果计算处于运行之中,那么如果mayInterrupt参数为true,才被中断。
   boolean isDone();//任务完成返回true,进行中/未进行皆返回false

}

Callable转Future 和 Runnable

在这里插入图片描述

执行器

方法描述
newCachedThreadPool必要时创建新新线程;空闲线程会被保留60秒
newFixedThreadPool该池包含固定数量的线程;空闲的线程会一直被保留
newSingleThreadExector只有一个线程的‘池’,该线程顺序执行每一个提交的任务
newScheduledThreadPool用于预定执行而构建的固定线程池,代替java.util.Timer
newSingleThreadScheduledExecutorl用于预定执行而构建的单线程池

流的创建

   Stream.of(...T t);
   Stream.of({"1","2","3"});
   String a = "a,b,c,d"
   Stream.of(a.split(","));
   Arrays.stream(array,from,to);(from包括,to不包括);
   Stream.empty();//创建空流
   //创建无现流的静态方法
   Stream.generate(Supplier<T> s);// 通常传入构造函数方法引用
   //第一个seed是种子函数,第一个传进去UnaryOperator<T> f的参数。
   Stream.iterate(final T seed, final UnaryOperator<T> f);

输入与输出

完整的流家族

输入流和输出流的层次结构

在这里插入图片描述

Reader和Writer的层次结构

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值