容量指的是表示的数据范围大小,而不是所占空间的大小。
long表示8个字节,float表示4个字节,但是float表示的数据范围却远大于long,因为浮点数用一部分空间存储10的多少次方
当byte,char,short三种类型的变量做运算时,结果为int型
强制类型转换是自动类型转换的逆过程,从容量大的转换为容量小的,会发生精度损失
java中的输入操作
Java中输入一般是通过Scanner类来实现的:
具体步骤如下:
(1)创建Scanner对象,接受从控制台输入
Scanner input=new Scanner(System.in);
(2) 接受String类型
String str=new input.next();
(3)接受int类型
int n=input.nextInt();
以enter为分隔读取输入
String str=new input.nextLine();
该对象的next()方法和nextLine()方法的区别:
在java中,next()方法是不接收空格的,在接收到有效数据前,所有的空格或者tab键等输入被忽略,若有有效数据,则遇到这些键退出。nextLine()可以接收空格或者tab键,其输入应该以enter键结束。
(4)输出结果
System.out.println(str);
System.out.println(n);
连续输入多个数:
public class Demo59 {
public static void main(String[] args) {
/*
//创建Scanner对象,接受从控制台输入
Scanner input=new Scanner(System.in);
//接受String类型
String str=input.next();
//输出结果
System.out.println(str);
*/
Scanner input=new Scanner(System.in);
//输入数组的长度
int len=input.nextInt();
//创建一个数组
int[] array=new int[len];
//遍历数组,并给其赋值
for(int i=0;i<len;i++){
array[i]=input.nextInt();
}
//打印数组
for(int i:array){
System.out.print(i+" ");
}
}
}
整型常量默认类型为int型
浮点型常量默认类型为double型
public class study {
public static void main(String[] args)
{
long l=12345;//可以正常运行,数字后没有加L,默认为int类型,自动转换成long
long l2=1234156748978654;//错误,默认为int类型,但这个数超过了int的表达范围
long l3=1564163564156416L;//correct
float f1=12.3;//错误,没有加f,默认为double类型,但double类型比float的容量大,无法自动转换成float
float f2=(float)12.3;//正确
float f3=12.3f;//正确
byte b=12;
byte b1=b+1;//wrong,b是byte,1是int,相加后的结果为int,而int无法向byte进行自动类型转换
float ff=b+12.3;//wrong,相加的结果为double,无法进行自动类型转换
}
由于java中基本数据类型之外的所有数据类型(即引用数据类型:数组,类)的初始化都要使用new,所以引用数据类型在赋值或传参数到方法中时,传递的都是地址。
在java中,所有的引用数据类型(string,类,数组)的对象的内容实际上都是地址,这个地址所指向的内容(堆空间)才是这个数据类型实际的内容!!!
java中的类内的静态成员属性,在类外被使用不需要通过这个类,可以直接使用
继承
:
一旦A类继承了父类B类后,A就获得了B中的所有属性和方法。
父类中声明为private的属性或方法,子类继承父类后,仍然认为获取了父类中的private的结构,只是由于封装性的影响,子类无法调用继承的结构
一个方法可以访问所属类的所有对象的的私有属性
私有数据只能在类内访问,即便是子类也不能访问从父类继承的私有属性
如果构造子类对象时需要初始化从父类继承来的属性,则必须(最好)在子类的构造函数后显示的调用父类的构造函数(即调用super)
如果继承的属性非private,也可以直接调用属性手动初始化(如下),但仍然会默认调用super(),所以在这种情况下,如果父类中没有默认构造函数就会报错。
多态
我们必须区分一个表达式的静态类型和动态类型,静态类型在编译时是已知的,它是变量声明的类型,动态类型是变量或表达式表示的内存中的对象的类型,直到运行时才知道。
所以动态绑定之后也无法调用子类特有的属性或方法,因为此时对象的类型为基类。
多态的使用前提:
1.类的继承关系
2.方法的重写
多态性只适用于方法,不适用于属性,最后调用的是哪个类中的属性取决于静态类型
多态对属性不起作用,调用哪个属性取决于声明的是什么类型,而不是动态绑定后的实际类型
==
==能通过的前提是两边的类型要可以相互转换,比如bool和其他的数据类型就不行
equals属于java.lang.Object类里面的方法**,如果该方法没有被重写过默认也是==**;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点
数组也作为object的子类出现,可以调用object中的方法
重写equals
tostring
单元测试
包装类
static
因为静态方法随着类的加载而加载,而非静态方法和属性创建对象时才加载,所以在静态方法中非静态的属性和方法可能还没有生成,所以在静态方法中,只能调用静态的方法或属性
。
由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的
在main方法中,不能直接调用同一个类中的非静态方法或属性,就是因为main方法是静态的,只能调用静态的方法或属性。要想在main中调用同一个类中的非静态方法或属性,必须创建一个本类的对象,然后通过对象去调用。
。
单例设计模式
final
abstract
有一些方法,父类无法确定全部实现,只能由其子类提供具体实现,对于这些方法,在没有abstract关键字之前,我们只能是在父类的方法体中随便写什么,在子类中再去重写该方法,完成具体的实现。
而abstract关键字(抽象方法)就是用来模型化这些方法,定义为abstract的方法没有方法体,在非抽象的子类中必须重写,就相当于一个占位符,满足了以上情况的要求
同时,由于抽象方法没有方法体,为了防止被调用,所以:①抽象方法所在的类也必须是抽象的,必须被abstract修饰,这样的类无法实例化对象,也就无法调用方法;②抽象方法也不能是static的。
基于同样的规则,抽象类的子类要么被定义为抽象类,要么就重写继承的所有抽象方法
。
匿名对象,匿名子类
接口interface
。
内部类
当一个类只在某一个类或方法的内部需要被使用时,我们就可以把这个类声明为内部类
局部内部类的使用
返回了一个非匿名类的匿名对象
返回了一个匿名类的匿名对象
异常
catch中的异常类型如果满足父子类关系,子类必须放在父类上面。
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常。
。
进程与线程
。
run()方法中的所有内容就是一条线程,main()方法中的所有内容是主线程
class MyThread extends Thread{
@Override
public void run() {
System.out.println(this.getName());//与下面代码的效果相同,也可以省略this,直接调用
System.out.println(Thread.currentThread().getName());
}
public MyThread(String name){
super(name);
}
}
public class study {
public static void main(String[] args) {
MyThread myThread=new MyThread("线程一");
myThread.start();
System.out.println(Thread.currentThread().getName());//在main中只能使用这一种方法
}
}
在实现runnable接口创建多线程的方法中,由于Thread构造器使用的都是同一个对象,所以可以考虑使用this作同步监视器
在继承Thread类创建多线程的方法中,不要使用this作同步监视器,可以使用当前类.class作同步监视器
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
既然CPU同一时间只能执行一个线程,为什么存在并发问题!!!!
CPU的时间是按时间片分的,而不是一个时间点,并发问题是由于CPU线程切换导致的。
现在假设有一段代码
if(i == 1) {
i++; //断点1
system.out.print(i);
} //断点2
有两个线程A,B同时执行这一段代码,假设A线程先被CPU调度,然而A线程在断点1处,时间片到期了,此时A线程的代码并没有执行完,但是CPU此时会调度B线程,并不会管A线程是不是执行完了这一段代码。
再接着假设B线程现在执行完了这一段代码(当然也可能没有执行完),CPU 现在就又会调度A线程,并且从A线程的断点1处继续执行(注意不是重新执行,CPU切换的时候保存了线程的上下文)
总结一下:CPU切换线程并不会管你线程是否将代码执行完,而是和分给线程的时间片是否到期有关,时间片到期了就会切换线程,并发也就由此产生了。
CPU在某一个时间点上确实只能执行一个线程,但是多线程不是由于多核或者双核才叫多线程。是由于,很多个线程在并行执行的时候,CPU根据一定的线程调度算法,频繁的进行线程切换,当正在执行的一个线程需要进行IO操作或者需要访问内存的时候,CPU完全可以放弃该线程,转而调度线程就绪队列上的其他线程,被放弃的线程则进入阻塞状态,IO操作或者访问内存操作结束之后,该线程可以进入线程就绪队列上。
人们通常意义上的多线程指的是,由于CPU根据一定的线程调度算法来切换线程,所以在一个时间段上,可以看做很多线程在并发执行。其实还是在某一个时间点上只有一个线程在运行罢了。
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
sychronized的作用:
防止其他的线程执行被synchronized包起来的代码,而不阻止cpu执行别的线程。在synchronized中的代码的被cpu分配的执行时间结束后,就会失去cpu的执行权,cpu会执行其他的线程,只是其他的线程无法执行被synchronized包围和被之前代码抢占的锁锁住的所有代码。
一个线程一进入synchronized,拿到了锁A后,其他线程不仅无法进入线程一所在的代码块,而且也无法进入所有锁A的代码块。
String
在实际项目中,如果想要把数组中的内容打印出来,直接使用toString方法只会打印出数组的地址(因为数组没有重写toString()方法),因此需要使用Arrays的toString方法,可以从其内部实现中看出来,该方法支持入参可以是long,float,double,int,boolean,byte,object型的数组。
打印数组的内容用Arrays.toString()
打印数组的内容用Arrays.toString()
打印数组的内容用Arrays.toString()
打印数组的内容用Arrays.toString()
打印数组的内容用Arrays.toString()
打印数组的内容用Arrays.toString()
java比较器
自然排序通常在自定义的类中定义,对实现该类的所有对象进行整体排序
而定制排序,指的就是给一次排序行为单独定制排序方式。通常以Comparator的匿名实现类的匿名对象的形式实现,放在如Arrays.sort()这样的方法中作为参数,精确控制单次排序
class Good implements Comparable{
private int price;
private String name;
public Good(int price, String name) {
this.price = price;
this.name = name;
}
@Override
public String toString() {
return "Good{" +
"price=" + price +
", name='" + name + '\'' +
'}';
}
@Override
public int compareTo(Object o) {
if(o instanceof Good){
Good good=(Good)o;
if(this.price<good.price){
return -1;
}else if(this.price==good.price){
return 0;
}else {
return 1;
}
}
else
throw new RuntimeException("数据类型不匹配");
}
}
public class compare {
@Test
public void test(){
String[] strings={"ef","ab","cd","gh"};
Arrays.sort(strings);
System.out.println(Arrays.toString(strings));
}
public static void main(String[] args) {
String a="abc";
String b="bcd";
Good good1=new Good(100,"luo");
Good good2=new Good(120,"xie");
Good good3=new Good(130,"i");
Good good4=new Good(140,"love");
Good good5=new Good(150,"you");
Good[] goods=new Good[]{good1,good2,good3,good4,good5};
Arrays.sort(goods);//自然排序,在sort中调用Good中重写的compareto方法
System.out.println(Arrays.toString(goods));
Arrays.sort(goods, new Comparator() {//定制排序,给goods重新指定排序方式,单独定制排序方式
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Good&&o2 instanceof Good){
Good g1=(Good)o1;
Good g2=(Good)o2;
return -g1.compareTo(g2);
}
else throw new RuntimeException("fuckyou");
}
});
System.out.println(Arrays.toString(goods));
}
}
枚举类
注解
集合
List
Set
hashcode()定义在object中,所以所有类都有hashcode()方法,该方法随机计算出一个值,作为对象在堆中的存储地址,每个对象的地址都不同,所以存储在hashset中的对象的类如果没有重写hashcode(),即使两个对象内容完全相同,也会被随机计算出完全不同的hash值,放在hashset中的不同位置,也就无法调用equals()。
无序指的是插入无序,数组中的对象在数组中存放的位置无序。
定制排序
1、set的remove方法通过被remove对象的hash值找到该对象的位置,然后remove,而这里p1的值被修改过了,p1的hash值也随之改变,remove找到的并不是p1的位置,也就无法remove p1
结果为
2、p1此时虽然name变成了CC,但p1的hash值还是由AA计算出来的,与CC不同,所以可以成功add
结果为
3、此时添加的对象的hash值与p1的相同,所以需要调用equals比较两个对象的内容,但是p1的值已经被改成了CC,所以不相等,成功添加,和p1连成链表。
Map
jdk7
jdk8
泛型
泛型和多态使用目的的区别:
最主要的区别在于容器的使用:容器中使用泛型意味着所有类型的对象都可以使用这个容器,但是对每一个容器来说,它内部的所有对象的类型必须相同
容器中使用多态意味着即便是同一个容器,它也可以装不同类型的对象
而在类和方法中泛型和多态的区别则不明显,都可以实现接口的复用性,比如同一个方法可以提供给不同类型的对象使用,但是仍然存在差异,使用泛型可以避免频繁的类型转换,Which甚至很多时候会导致精度损失
IO流
如果是在main方法中,相对路径是在当前的project下,如果单元测试方法中,相对路径是在当前的module下
由于io流需要对文件进行操作,所以定义了一个File类,每一个文件都是一个File对象,File 能新建、删除、重命名文件和目录,可以访问文件的属性(比如大小,最后修改时间),但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。File对象常作为参数传递到流的构造器中
File的构造器无法创建文件!File的构造器无法创建文件!File的构造器无法创建文件!File的构造器无法创建文件!
读入的文件一定要存在!读入的文件一定要存在!
@Test
public void test() {
File file = new File("test.txt");
FileReader fileReader = null;
try {
fileReader = new FileReader(file);
int data = fileReader.read();
while (data != -1) {
System.out.println((char) data);
data = fileReader.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileReader != null){
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
缓冲流
转换流
网络编程基础
1.客户端发送内容给服务端,服务端将内容打印到控制台上。
2.客户端发送文件给服务端,服务端将文件保存在本地。
在输入流读取的时候,会出现阻塞的现象,即当读取的内容没有显式结束时,读取操作不会退出,无法执行后面的代码。比如服务器和客户端通信时(如上面图片中的例子),服务器端先启动,然后就会阻塞在socket输入流读取操作,然后客户端再启动,输出内容到服务器,服务器同步读取内容,客户端输出完成后,执行方法中后面的代码,退出方法体,输出才显式结束,此时服务器端才结束读取操作(即read),执行方法中后面的代码。
所以如果要实现服务器和客户端的相互多次通信(比如客户端输出到服务端后又要从服务端读取消息),一定要在每一方输出操作结束后显式关闭输出。只有这样,另一方才能结束读取,执行后面的操作。否则就会出现类似死锁的情况。
UDP编程
UDP无需建立连接,客户端直接发送,服务器直接接受即可
反射
程序经过javac.exe命令后,会生成一个或多个字节码文件(.class结尾),接着使用java.exe命令对某个字节码文件进行解释运行,将字节码文件加载到内存中,此过程叫类的加载;加载到内存中的类叫运行时类,这个运行时类就是Class的一个实例’
一个Class实例对应着一个运行时类
修改和调用类的属性
调用类的方法
调用静态属性同理,使用类去调用