以下是一名java初学者在自学过程中所整理的笔记,欢迎大家浏览并留言,若有错误的地方请大家指正。
为了让计算机可以表示现实世界中的文字,需要人提前制定好“文字”和“二进制”之间的对照关系,这种对照转换关系被称为“字符编码”。
说明:整数默认为int,浮点数默认为double,布尔类型默认为false
byte、short、char-->int-->long-->float-->double
注:为什么long比float所占字节长度更大却是long转换为float呢?
因为它们底层的存储结构不同,float表示的数据范围要比long的范围更大。
格式如下:
/*动态初始化*/
数组类型[] 数组名 = new 数组类型[元素个数(数组长度)];
/*静态初始化*/
数组类型[] 数组名 = {
元素1, 元素2, 元素3, .....} = new 数组类型[]{
元素1, 元素2, 元素3, .....};
举例:
String[] array1 = {
"abc", "a", "ad"};//字符串数组中不能有字符('')
char[] array2 = {
'A', 'b'};//字符数组中不能有字符串("")
int[] array3 = {
23, 12, 90};
说明:以上例子中可以把“[ ]”放在数组名后面(array1[]),但是Java中不建议这样,放在数组名前面更易于理解。
格式如下:
/*动态初始化*/
写法一:数据类型[][] 数组名 = new 数据类型[m][n];//m表示这个二维数组有m个一维数组, n表示每一个一维数组有n个元素
写法二:数据类型 数组名[][] = new 数据类型[m][n];
写法三:数据类型[] 数组名[] = new 数据类型[m][n];
数据类型[][] 数组名 = new 数据类型[m][];//如果没有给出每个一维数组的元素个数,就表示这个二维数组是变化的,但不能没有一维数组的个数
/*静态初始化*/
数据类型[][] 数组名 = {
{
元素...}, {
元素...}, {
元素...}......} = new 数据类型[][] {
{
元素...}, {
元素...}, {
元素...}......};
/*注明*/
int[] x, y[];//x是一维数组,y是二维数组
public static String toString(int[] array);
public static void sort(int[] array);
public static int binarySearch(int[] array, int key);
步骤如下:
import java.util.Scanner;
Scanner input = new Scanner(System.in);
int n = input.nextInt();
注:
整型:input.nextInt();
单精度浮点型:input.nextFloat();
双精度浮点型:input.nextDouble();
字符串类型:input.nextLine();//这种写法可以得到带空格的字符串
input.next();//此写法在读取内容时会过滤掉有效字符前面的无效字符,所以这种写法不能得到带空格的字符串
短整型:input.nextShort();
字节型:input.nextByte();
字符型:input.next().charAt(i);//i是指从用户输入的字符串中选取第i个单个字符输入到内存中,i从0开始
在计算机内,有符号数据有三种表示法:原码、反码、补码,所有数据的运算都是采用补码进行的
面向过程:主要关注实现的具体过程,语句之间是因果关系
面向对象:它是基于面向过程的编程思想,也是一种思考问题的方式,主要关注对象能完成哪些功能
注:采用面向对象的方式开发一个软件的生命周期
对一个事物的描述:
一个类可以使用所属包中的所有类,以及其他包中的公共类,有两种方式访问另一个包中的公共类:
类名 对象名 = new 类名();
对象名.变量名
对象名.方法名(参数...)
以下代码在内存中做了哪些事情?
Student s = new Student();
分五步:
对上述语句的解析:
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三步来实现对这个类进行的加载
加载:将.class文件读入内存,并为之创建一个class对象,任何类被使用时系统都会建立一个class对象
连接:
什么时候类会加载?
匿名对象是指没有名字的对象,只创建对象但是不用变量来接收
//匿名对象举例
new student();
new student().show();
匿名对象的应用场景:
匿名对象的优点:匿名对象创建的方式能够减少栈帧的分配和指向,且在调用完毕后能够被GC机制(垃圾回收机制)快速的回收
Employee e = new Employee("xl"...);
e.raiseSalary(5);
class Employee
{
...
public Date getHireDay()
{
return (Date)hireDay.clone();
}
...
}
按值传递:值传递是指在调用方法时将实际参数复制一份传递到方法中,这样在方法中如果对参数进行修改,将不会影响到实际参数
按引用传递:引用传递就是直接把内存地址传过去,也就是说引用传递时,操作的其实都是源数据,有可能影响原数据,除了基本类型的参数以外,其它的都是引用传递,比如:Object,二维数组,List,Map等
java中所有的参数传递都是传递变量所代表的值的副本,因此,Java中的对象引用还是按值传递的,并不是按引用传递
public static void main(String[] args)
{
.....
}
在构造器中防止初始化字段为空,有两种方法:
//构造方法
public Employee(String name)
{
this.name = Objects.requireNonNullElse(name, "空名字");
}
public Employee(String name)
{
Objects.requireNonNUll(name, "名字不能为空");
this.name = name;
}
两者区别:
注:局部变量名可以与实例变量名相同,在方法被调用时采用就近原则
访问权限修饰符 | 功能 |
---|---|
public | 对外部完全可见 |
protected | 对本包和所有子类可见 |
缺省(不写修饰符) | 对本包可见 |
private | 仅对本类可见 |
注:当变量被final修饰后,这个变量就变成了常量,既然是常量,那么它在内存中存储的就只是数值了,与之前的变量内存就无关系了,即当变量消失时,常量不会消失,依旧是那个数值在运算,所以,若想某个数据不会因变量消失而消失,就将它修饰为常量
在Java中使用{ }括起来的代码被称为代码块,根据其位置和声明的不同可以分为:局部代码块、构造代码块、静态代码块、同步代码块
以上代码块执行顺序:
静态代码块-->构造代码块-->构造方法-->局部代码块
继承:多个类中存在相同属性(成员变量)和行为(成员方法)时,将这些内容抽取到单独一个类中,那么多个类就无需再定义这些属性和行为了,只要继承那个类即可
通过extends关键字可以实现类与类的继承
class 子类名 extends 父类名{
}
class A{
}
class B extends A{
}
class C extends B{
}
在子类中访问一个变量的执行顺序:首先在子类的局部范围中找,然后在子类成员范围中找,最后在父类成员范围中找(不能访问到父类的局部范围),如果还是没有就报错
super.成员变量
super(参数)
super.成员方法
注:
采用假设法:如果有两个类A、B,只要它们符合A是B的一种或者B是A的一种(A is B)或(B is A)这样的关系,就可以考虑使用继承
父类名 f = new 子类名();
fu f = new zi();
zi z = (zi)f;//要求该f必须是能够转换为zi的
注:在进行以上两种转型之前应当对两对象之间进行检查,这是编程的好习惯,instanceof操作符:检查对象之间是否能成功地进行转换
例:
/*
Manager类继承了Employee类
*/
Employee[] staff = new Employee[3];
Manager boss = new Manager();
if(staff[1] instanceof Manager)
{
boss = (Manager)staff[1];
}
public abstract void eat(){
...}//这叫空方法体,会报错
public abstract void eat();//这才叫无方法体
public interface 接口名{
}
class 类名 implements 接口名{
}
public interface 接口1 extends 接口2, 接口3{
}
注:开发中实现接口的类的类名命名格式:接口名+Impi
类的修饰符可有:public、缺省、final、abstract
注:成员内部类可有private和static修饰符,其余的普通类不行,因为成员内部类可以看作是外部类的成员
成员变量的修饰符可有:private、缺省、protected、public、static、final
构造方法的修饰符可有:private、缺省、protected、public
成员方法的修饰符可有:private、缺省、protected、public、static、final、abstract
直接访问内部类成员的语法格式:
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
外部类名.this.成员变量
外部类名.this.成员方法
注:一般来说,匿名内部类用于继承其他类或实现接口,并不需要增加额外的方法,只是对继承方法的实现或重写,语法格式:
new 类名或者接口名(){
重写方法}//可以理解为一个对象
静态内部类的唯一访问语法格式:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
注:现在考虑这个情况:外部类并没有实例化,内部类还没有加载,这时候如果调用内部类的静态成员或方法,内部类还没有加载却试图在内存中创建该内部类的静态成员,这是明显矛盾的,所以非静态内部类不能有静态成员变量或静态方法(这里就和非静态方法不一样了)
//以下两者的区别
String s1 = new String("hello");//创建2个或1个对象,new操作符是一定要创建对象的
String s2 = "hello";//创建1个或0个对象
三者使用场景
以下说明用StringBuilder类举例,对于StringBuffer类同等适用
StringBuilder的构造方法:
StringBuilder();
StringBuilder(int size);//指定初始容量的字符串缓冲区(不一定是实际长度)
StringBuilder(String str);//指定字符串内容的字符串缓冲区
StringBuilder类的常见功能(除截取功能外,其余功能返回StringBuilder本身):
1、添加功能
public StringBuilder append(String str);//可以把任意类型添加到字符串缓冲区里
public StringBuilder insert(int offset, String str);//在指定位置把任意类型的数据插入到字符串缓冲区里
2、删除功能
public StringBuilder deleteCharAt(int index);//删除指定位置的字符
public StringBuilder delete(int start, int end);//删除从指定位置开始到指定位置结束的这一范围的内容
3、替换功能
public StringBuilder replace(int start, int end, String str);
4、翻转功能
public StringBuilder reverse();
5、截取功能
public String subString(int start);//从start开始到末尾
public String subString(int start, int end);//从start到end
注:StingBuilder类中的截取方法不再是返回本身了,而是重新开辟空间
String与StringBuilder相互转换
1、通过构造方法
StringBuilder s1 = new StringBuilder("字符串");
2、通过append()
StringBuilder s3 = new StringBuilder();
s3.append("字符串");
StringBuilder builder = new StringBuilder("字符串");
1、通过构造方法
String str = new String(builder);
2、通过toString()
String str = builder.toString();
String和int的相互转换
Integer.parseInt("数字字符串");
String.valueof(int型数据);
Java中的增强for(for each循环)是一种很强的循环结构,可以用来依次处理数组中的每一个元素,不必考虑下标的问题
for(int element : a)//这里的a是一个数组名或集合
{
System.out.println(element);//输出每一个元素
}
基本类型 | 引用类型(类) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
Integer类中常用的方法:
1、String转int
public static int parseInt(String s);//字符串s必须为由数字组成的字符串,字母的不行
2、进制转换(radix的范围:2<= radix <= 36)
public static String toString(int i, int radix);//i表示被转换的数(十进制数),radix表示进制数
注:
Character类中常用的方法:
1、判断给定的字符大小写
public static boolean isUpperCase(char ch);//大写返回true,小写返回false
public static boolean isLowerCase(char ch);//大写返回false,小写返回true
2、判断给定的字符是否是数字
public static boolean isDigit(char ch);
3、把给定的字符转成大小写
public static char toUpperCase(char ch);
public static char toLowerCase(char ch);
Math类常用方法:
1、取绝对值
public static int abs(int a);
2、向上取整
public static double ceil(double a);
3、向下取整
public static double floor(double a);
4、取最大值
public static int max(int a, int b);
5、a的b次幂
public static double pow(double a, double b);
6、取随机值
public static double random();//返回值范围[0.0, 1.0)
7、四舍五入
public static int round(float a);
8、正平方根(开方)
public static double sqrt(double a);
Random类的构造方法:
Random();//默认种子,每次产生的随机数不同
Random(long seed);//指定种子,每次种子相同,随机数就相同
Random类常用方法:
int nextInt();//返回int范围内的随机数
int nextInt(int n);//返回[0, n)范围内的随机数
int nextDouble();//随机获得一个0-1的double类型数据,括号里不填
int nextFloat();//随机获得一个0-1的float的类型数据
int nextBoolean();//随机获得true或false
System类常用方法:
1、运行垃圾回收器
public static void gc();
2、退出JVM
public static void exit(int status);//status - 退出状态。
3、获取当前时间的毫秒值
public static long currentTimeMillis();
4、复制数组
public static void arraycopy(Object src, int srcpos, Object dest, int destpos, int length);
参数:
src:源数组
srcpos:源数组中的起始位置
dest:目标数组
destpos:目标数组中的起始位置
length:要复制的数组元素的数量
public BigInteger(String val);//字符串val必须是数字字符串
1、加法
public BigInteger add(BigInteger val);
2、减法
public BigInteger subtract(BigInteger val);
3、乘法
public BigInteger multiply(BigInteger val);
4、除法
public BigInteger divide(BigInteger val);
5、返回商和余数的数组,该数组中只有两个元素:商array[0]、余数array[1]
public BigInteger[] divideAndRemainder(BigInteger val);
public BigDecimal(String val);//字符串val必须是数字字符串
1、加法
public BigDecimal add(BigDecimal augend);
2、减法
public BigDecimal subtract(BigDecimal subtrahend);
3、乘法
public BigDecimal multiply(BigDecimal multiplicand);
4、除法
public BigDecimal divide(BigDecimal divisor);
5、对除法运算结果的操作
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode);
参数:
divisor:被除数
scale:小数点后位数
roundingMode:舍取模式,一般用四舍五入模式(BigDecimal.ROUND_HALF_UP)
public Date();//根据当前的默认毫秒值创建对象
public Date(long date);//根据给定的毫秒值创建对象,data是毫秒值,日期是从1970年1月1日0:0:0开始计算,data加上这个时间即可
1、获取时间,以毫秒为单位返回
public long getTime();
2、设置时间
public void setTime(long time);
public SimpleDateFormat(String pattern);//指定日期的格式(如:yyyy-MM-dd HH:mm:ss)
public SimpleDateFormat();//默认格式表示日期
1、Date转String(格式化)
public final String format(Date date);
2、String转Date(解析)
public Date parse(String source);
1、获取一个日历对象
Calendar rightNow = Calendar.getIntstance();
2、返回给定日历字段的值,日历字段中的每个日历字段都是静态的成员变量,并且是int型
public int get(int field);
3、根据给定的日历字段和对应的时间来对当前的日历进行操作
public void add(int field, int amount);
4、设置当前日历的年月日
public final void set(int year, int month, int date);
数组和集合类同是容器,区别在于:1、数组虽然也可以存储对象(对象数组),但长度是固定的,集合长度是可变的;2、数组可存储基本数据类型,但集合只能存储对象的内存地址(引用),且集合可存储不同类型的对象
1、添加功能
boolean add(Object obj);//添加一个元素
boolean addAll(Collection c);//添加一个集合的元素
2、删除功能
void clear();//移除所有元素
boolean remove(Object o);//移除一个元素
boolean removeAll(Collection c);//移除一个集合的元素,只要有一个元素被移除就返回true
3、判断功能
boolean contains(Object o);//判断集合中是否包含指定元素
boolean containsAll(Collection c);//判断集合中是否包含指定的集合元素,只有包含所有元素才叫包含,才返回true
boolean isEmpty();//判断集合是否为空
4、长度功能
int size();//这里获取的是元素的实际个数,不是集合的容量
5、交集功能
/*
设有两个集合A、B
A.retainAll(B);
若A、B之间有交集就把交集保存到集合A中,若无交集,那么集合A就为空,
至于返回结果则取决于保存交集的A集与保存之前的集合内容是否有变化,
有变化就返回true,没有变化就返回false
*/
boolean retainAll(Collection c);
6、集合转数组
Object[] toArray();//转型后的数组中每一个元素都是Object类型的
集合的迭代也叫集合的遍历,集合的迭代是通过迭代器完成的,迭代器是依赖于集合而存在的,也就是有集合才有迭代器
Iterator<E> iterator();
Object next();//获取元素之后自动后移
boolean hasNext();//判断是否还有元素,如果仍有元素可以迭代就返回true
Collection c = new ArrayList();
方式一:
Iterator it = c.iterator();//通过集合对象获取迭代器对象
while(it.hasNext())
{
System.out.println(it.next());
}
方式二:
for(Iterator it = c.iterator(); it.hasNext();)
{
System.out.println(it.next());
}
总:方式一结构清晰;方式二效率更高,运行更快,因为it循环之后就变成垃圾
问:迭代器为什么不定义成一个类,而定义成一个接口?
答:Java中提供了很多的集合类,而这些集合类的数据结构是不同的,意味着它们的存储方式和遍历方式也各不相同,遍历一个集合应当具备判断功能和获取功能,因每种集合的方式不太一样,所以把这两个功能提取出来,这种方式就是接口,这个接口的实现类是以内部类的方式体现的
List c = new ArrayList();
for(int i = 0; i < c.size(); i++)
{
System.out.println(c.get(i));
}
列表迭代器:ListIterator listIterator();
该迭代器继承了Iterator迭代器,所以也可直接使用hasNext()和next()
列表迭代器特有的功能:
逆向遍历:
Object previous();//获取上一个元素
boolean hasprevious();//判断是否有元素
正向遍历:
Object next();
boolean hasNext();
注:ListIterator可以实现逆向遍历,但是必须同一迭代器正向遍历之后才能进行逆向遍历,无实际意义,一般不使用
1、添加功能
public void addFirst(Object o);//开头添加
public void addLast(Object o);//结尾添加
2、获取功能
public Object getFirst();//获取开头元素
public Object getLast();//获取结尾元素
3、删除功能
public Object removeFirst();//删除开头元素并返回被删除元素
public Object removeLast();//删除结尾元素并返回被删除元素
1、添加功能
/*
(key表示键,value表示值)如果键是第一次存储就直接存储元素并返回null,
如果键不是第一次存储就用现在的值把以前的值替换掉并返回以前的值
*/
v put(k key, v value);
2、删除功能
void clear();//移除所有的键值对元素
v remove(Object key);//根据键删除键值对元素并把值返回
3、判断功能
boolean containsKey(Object key);//判断集合中是否包含指定的键
boolean containValue(Object value);//判断集合是否包含指定的值
boolean isEmpty();//判断集合是否为空
4、长度功能
int size();//返回集合中键值对的对数
5、获取功能
v get(Object key);//根据键来获取值
Set<K> keySet();//获取集合中所有键的集合
Collection<V> values();//获取集合中所有值的集合
Set<Map.Entry<K,V>> entrySet();//返回的是键值对对象的集合
Hashtable与HashMap的区别:
TreeSet集合底层实际上是一个TreeMap,也就是一个二叉树,放到TreeSet集合中的元素等同于放到TreeMap集合的Key(键)部分了,TreeSet集合中的元素无序不重复,可以按照元素的大小顺序自动排序
重点:
TreeSet集合自动排序的特点是通过实现java.lang包下Comparable接口或java.util包下的Comparator接口来完成的,即要指定比较规则。如String、Integer等已经默认实现了Comparable接口,所以TreeSet中的String、Integer就不用再指定比较规则了,而放到TreeSet或TreeMap集合key部分的自定义元素要想做到排序就必须指定比较规则,有如下两种方式:
自定义元素实现Comparable接口中的compareTo()方法,在compareTo()方法中指定比较规则,这时TreeSet或TreeMap集合的构造方法是无参构造
在构造TreeSet或TreeMap集合时,用匿名内部类的方式实现比较器Comparator接口中的compare()方法(也可以单独建一个比较器类实现Comparator接口),在compare()方法中指定规则,这时TreeSet或TreeMap集合的构造方法是带参构造
说明:compareTo()方法的返回值:
问:Comparable和Comparator怎么选择
答:当比较规则不会发生改变的时候,或者说当比较规则只有一个的时候建议使用Comparable接口,如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口
Collections是针对集合进行操作的工具类,其中的方法都是静态的
Collection与Collections的区别:
Collections类中的常用方法:
1、排序,默认情况下是自然排序
public static <T> void sort(List<T> list);
2、二分查找
public static <T> int binarySearch(List<?> list,T key);
3、最大值(根据元素的自然顺序)
public static <T> T max(Collection<?> coll);
4、最小值
public static <T> T min(Collection<?> coll);
5、随机转换
public static void shuffle(List<?> list);
6、反转
public static void reverse(List<?> list);
7、将线程不安全的List转换成线程安全的List(Set和Map也有类似的方法)
public static <T> List<T> synchronizedList(List<T> list);
泛型是一种把数据类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型,也叫参数化类型,把类型当作参数一样传递
<数据类型> 注:这里的数据类型只能是引用类型
例:ArrayList<String> array = new ArrayList<String>();//array这个集合中所有元素都是String类型的,且只能是String类型的
Collection<?> c1 = new ArrayList<Object>();
Collection<?> c2 = new ArrayList<Animal>();
Collection<?> c3 = new ArrayList<Dog>();
Collection<? extends Animal> c = new ArrayList<Dog>();//Dog继承类Animal
可以直接导入到静态方法的级别
import static 包名.类名.方法名;
import static java.lang.System.out;
out.println("java");
public int sum(int...a);//这里必须是三个点
public int sum(int b, int c, int...a);
枚举(enum)是一种引用数据类型,编译之后生成class文件,枚举中的每一个值可以看作常量
枚举语法格式;
enum 枚举类型名
{
枚举值1, 枚举值2, 枚举值3...
}
说明:结果只有两种情况的建议使用布尔类型,结果超过两种并且可以一枚一枚列举出来的建议使用枚举类型,如颜色、四季、星期等都可以使用枚举类型
try…catch…语法格式:
try{
//可能出现异常的代码
}catch(异常类名 变量名){
//针对异常的提示处理
}finally{
//不管try内有没有异常出现,finally语句块中的语句一定执行
//这里一般是资源的释放和关闭
}
注:
1、虽然说所有的异常类名都可写Exception,但异常类名能明确就明确,方便调错
2、try语句块中的某一行出现异常该行后面的代码不会执行,只有在try...catch...捕捉异常后,后续代码才可执行
3、当try语句块中有return时,finally中的语句也会执行,且return语句在finally之后执行,也就是最后执行
4、如果在执行finally之前JVM退出了(System.exit(0)),那么就不会再执行finally里的语句了
出现多个异常时:
方式一:针对每一个异常写一个try...catch...
方式二:将异常集中在一个try中,多个catch
try{
...
}catch(异常类名1 变量名){
...
}catch(异常类名2 变量名){
...
}
注:平级关系的异常谁前谁后无所谓,如果出现父子关系,那么父必须出现在子的后面,否则会报错
JDK8的一个新异常处理方式
try{
...
}catch(异常类名1 | 异常类名2 | ... 变量名){
...
}
注:这个方法很简洁,特点是处理方式一致,且多个异常间必须是平级关系
异常对象有两个重要的方法
1、获取异常简单的描述信息
public String getMessage();
2、打印异常追踪的堆栈信息
public void printStackTrace();
语法格式:
throws 异常类名(可以跟多个异常类名)
public static void myException() throws ClassNotFoundException
{
Class.forName("文件路径");
//因为异常只是被上抛,没有被处理,所以以下代码不会执行
System.out.println("Hello");
}
如果方法内部可以将异常处理就用try,如果内部处理不了就用throws上抛
分两步:
public class MyException extends Exception //编译时异常
{
public MyException()
{
}
public MyException(String s)
{
super(s);
}
}
IO流是数据传输的通道/管道,是实现数据输入和输出的基础,通过IO流可以完成硬盘文件的读和写
按照流的方向进行分类(以内存作为参照物):
按照数据的读取方式进行分类:
总:在Java中只要类名以“Stream”结尾的都是字节流,以“Reader/Writer”结尾的都是字符流
注:1、以上所有的流都实现了Closeable接口,表明所有的流都可以关闭,都有close()方法,切记用完流之后一定要关闭流。2、所有的输出流都实现了Flushable接口,表明所有的输出流都是可以刷新的,都有flush()方法,输出流在最终输出完后一定要记得flush()方法刷新一下,这个刷新表示将通道当中剩余未输出的数据强行输出完(清空通道),如果没有flush()方法可能为导致数据丢失
常用方法
1、返回流当中剩余的没有读到的字节数量
public int available();
2、跳过n个字节不读
public long skip(long n);
3、返回读到的字节值,每次读一个字节,读完自动后移,文件读完返回-1
public int read();
4、将读到的内容存入字节数组中,且最多读入b.length个字节,返回读到的字节总数,文件读完返回-1
public int read(byte[] b);
常用方法
1、将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流
public void write(byte[] b, int off, int len);
用字节流完成文本复制
FileInputStream fis = null;
FileOutputStream fos = null;
try{
//创建字节输入流对象
fis = new FileInputStream("src/lianxi_io/myio_1");
//创建字节输出流对象,true表示追加
fos = new FileOutputStream("src/lianxi_io/myio_2", true);
byte[] bytes = new byte[1024];
int rcount = 0;
while((rcount = fis.read(bytes)) != -1)
{
fos.write(bytes, 0, rcount);
}
//输出流刷新(刷新缓冲区)
fos.flush();
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
} finally {
//关闭流
if(fis != null)
{
try{
fis.close();
} catch (IOException e){
e.printStackTrace();
}
}
if(fos != null)
{
try{
fos.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
通过构造方法将字节输入流转换为字符输入流
public InputStreamReader(InputStream in);
通过构造方法将字节输出流转换为字符输出流
public OutputStreamWriter(OutputStream out);
注:当一个流的构造方法中需要一个流的时候,这个被传进来的流叫作节点流,外部负责包装的这个流叫包装流或处理流,只需关闭包装流就行,当包装流关闭后节点流会自动关闭
缓冲流是为了提高数据的读写速度而存在的,因为缓冲流自带缓冲区,不需要再定义数组
注:其中BufferedReader的特殊方法
一次读取一个文本行,包含该行内容的字符串,不包含任何行终止符,如果已到达文本末尾,则返回 null
public String readLine();
数据流中DateOutputStream可以将数据连同数据的类型一并写入文件中,这个文件不是普通的文本文件,用记事本打不开,只能使用DateInputStream去读,并且读的时候需要与写的顺序一致才可以正常取出数据
标准的输出流默认输出到控制台
System.out.println("Hello");
改变输出方向,输入到指定文本中
PrintStream ps = new PrintStream(new FileOutputStream("src/lianxi_io/myio_1", true));
//改变输出方向
System.setOut(ps);
private static final long serialVersionUID= 1L;
实例:
序列化
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class IoTest
{
public static void main(String[] args)
{
ObjectOutputStream oos = null;
try{
//序列化
oos = new ObjectOutputStream(new FileOutputStream("src/lianxi_io/myio_1"));
oos.writeObject(new Student("xss", 23));
oos.writeObject(new Student("xxx", 34));
oos.flush();
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
} finally {
try{
oos.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
}
class Student implements Serializable
{
//手动固定序列化版本号
private static final long serialVersionUID = 2L;
private String name;
private int age;
private String address;
public Student()
{
}
public Student(String name, int age)
{
super();
this.name = name;
this.age = age;
}
public String toString()
{
return "Student [name=" + name + ", age=" + age + "]";
}
}
反序列化
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class lianxi
{
public static void main(String[] args)
{
ObjectInputStream ois = null;
try{
//反序列化
ois = new ObjectInputStream(new FileInputStream("src/lianxi_io/myio_1"));
Object obj1 = ois.readObject();
System.out.println(obj1);
Object obj2 = ois.readObject();
System.out.println(obj2);
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e){
e.printStackTrace();
} finally {
try{
ois.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
}
1、根据一个路径得到FIle对象
public File(String pathname);
2、根据一个目录和一个子文件/目录得到File对象
public File(String parent, String child);
3、根据一个父File对象和一个子文件/目录得到对象
public File(File parent, String child);
1、创建文件,如果已存在该文件就不再创建并返回false
public boolean createNewFile();
2、创建文件夹,如果已存在该文件就不再创建并返回false
public boolean mkdir();
3、创建文件夹,且是多级创建,如果父文件夹不存在就创建
public boolean mkdirs()
删除文件或文件夹
public boolean delete();
public boolean renameTo(File dest);
1、判断是否是目录/文件夹
public boolean isDirectory()
2、判断是否是文件
public boolean isFile();
3、判断是否存在
public boolean exists();
4、判断是否可读
public boolean canRead();
5、判断是否可写
public boolean canWrite();
6、判断是否隐藏
public boolean isHidden();
1、获取绝对路径
public String getAbsolutePath();
2、获取相对路径
public String getPath();
3、获取名称
public String getName();
4、获取文件大小
public long length();
5、获取最后一次修改时间,毫秒值
public long lastModified();
1、获取指定目录下的所有文件或文件夹的名称数组
public String[] list();
2、获取指定目录下的所有文件或文件夹的File数组
public File[] listFiles();
注:
方式一:编写一个类,直接继承Thread类并重写run()方法
public class lianxi_02
{
public static void main(String[] args)
{
//创建线程对象
MyThread mt = new MyThread();
mt.start();//启动线程
}
}
//自定义类继承Thread类
class MyThread extends Thread
{
//在自定义类中重写run()方法
public void run()
{
//被线程执行的代码
...
}
}
方式二:编写一个类,实现Runnable接口并重写run()方法
public class lianxi_02
{
public static void main(String[] args)
{
//创建线程对象
MyThread mt = new MyThread();
//通过线程对象创建Thread对象
Thread t = new Thread(mt);
t.start();//启动线程
}
}
//自定义类实现Runnable接口
class MyThread implements Runnable
{
//在自定义类中重写run()方法
public void run()
{
//被线程执行的代码
}
}
在上述两种方式中方式二较为常用,因为方式二避免了Java单继承带来的局限性,适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,较好的体现了面向对象的设计思想(低耦合、高内聚)
1、返回当前正在执行的线程对象
public static Thread currentThread();
2、获取线程对象的名字
public final String getName();
3、更改线程对象的名字
public final void setName(String name);
方式三:实现Callable接口(JDK8新特性)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 实现线程的第三种方式:实现Callable接口
*
* 这种方式的优点是:可以获取到线程的执行结果
* 这种方式的缺点是:在获取线程结果时容易引起当前线程阻塞,效率较低
*/
public class Lianxi_02
{
public static void main(String[] args)
{
MyThread1 mt = new MyThread1();
//创建一个“未来类”对象
FutureTask task = new FutureTask(mt);
//创建线程对象
Thread t = new Thread(task);
t.start();
try{
//通过get()方法获取线程返回值
System.out.println(task.get());
} catch (InterruptedException e){
e.printStackTrace();
} catch (ExecutionException e){
e.printStackTrace();
}
//get()方法会导致当前线程阻塞,所以此方法效率比较低
//因为get()方法需要等线程结束后拿到线程返回值
//所以main()方法这里的代码需要等get()方法结束才能执行,也就是要等以上线程结束后才执行
System.out.println("结束了");
}
}
//实现Callable接口
class MyThread1 implements Callable<String>
{
//这里的call()方法就相当于run()方法
public String call() throws Exception
{
String str = "hhhh";
Thread.sleep(5000);
System.out.println("2222");
Thread.sleep(5000);
System.out.println("3333");
return str;
}
}
线程的生命周期总共有五个状态:
public static void sleep(long millis);
public void interrupt();
public static void yield();
public class ThreadTest
{
public static void main(String[] args)
{
MyThread2 mt = new MyThread2();
Thread th = new Thread(mt);
th.start();
//过5秒后
try{
Thread.sleep(5000);
} catch (InterruptedException e){
e.printStackTrace();
}
mt.run = false;//改变run属性,通过run的值来控制线程执行
System.out.println("终止成功");
}
}
class MyThread2 implements Runnable
{
// 打一个布尔标记
boolean run = true;
public void run()
{
for (int i = 0; i < 10; i++)
{
if (run)
{
System.out.println(Thread.currentThread().getName() + "---->" + i);
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
} else//如果run属性为false就结束run()方法,意味着线程结束了
{
return;
}
}
}
}
public final void join();
在以下代码,合并线程的作用是:t1线程进入阻塞状态,t2线程执行,直到t2线程结束,t1线程才可以正常执行
public class Lianxi_02
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Myth1());
t1.setName("t1");
t1.start();
}
}
class Myth1 implements Runnable
{
public void run()
{
Thread t2 = new Thread(new Myth2());
t2.setName("t2");
//启动t2线程
t2.start();
//合并t2
try{
t2.join();//t1进入阻塞,t2执行
} catch (InterruptedException e){
e.printStackTrace();
}
//t1的线程代码
for(int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName() + "---->" + i);
}
}
}
class Myth2 implements Runnable
{
public void run()
{
//t2的线程代码
for(int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName() + "---->" + i);
}
}
}
执行结果:
t2---->0
t2---->1
t2---->2
t2---->3
t2---->4
t1---->0
t1---->1
t1---->2
t1---->3
t1---->4
1、返回线程对象的优先级
public final int getPriority();
2、更改线程的优先级
public final void setPriority(int newPriority);
线程优先级高仅仅表示线程获取到CPU时间片的概率高
判断一个程序是否可能会有线程安全问题:
可以理解为:当有多个线程同时操作同一数据对象时(线程并发),就容易导致数据状态错误的情况,这时的数据就不安全了
以下代码体现了线程安全问题
/**
* 线程安全示例:
* 多个用户同时从同一个账户中取钱
*
*/
public class Lianxi_02
{
public static void main(String[] args)
{
//创建一个账户对象(共享数据)
Account ac = new Account(10000);
//创建两个线程对象(多线程环境)
Thread t1 = new Thread(new User(ac, "用户1"));
Thread t2 = new Thread(new User(ac, "用户2"));
t1.start();
t2.start();
}
}
//账户类
class Account
{
private double money;
public Account(double money)
{
this.money = money;
}
public double getMoney()
{
return this.money;
}
public void setMoney(double money)
{
this.money = money;
}
//取款(操作同一账户)
public void Withdrawal(double money)
{
//取之前的余额
double before = this.money;
//取之后的余额
double after = before - money;
//这里让线程睡眠1s,就一定会出现线程安全问题
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
//设置取之后的余额
this.setMoney(after);
}
}
//用户类
class User implements Runnable
{
private Account ac;
private String name;
public User(Account ac, String name)
{
this.name = name;
this.ac = ac;
}
public void run()
{
ac.Withdrawal(5000);
System.out.println(this.name + "已取款完成,余额为" + ac.getMoney());
}
}
Java提供了线程同步机制(synchronized)用于解决线程安全问题,其思想是:把多条语句操作共享数据的代码包装成一个整体,让某个线程在执行的时候,其他线程不能执行
线程同步实际上就是让线程不能并发了,必须排队执行
synchronized的三种用法:
synchronized(这里填的是想要同步的线程(也就是想要排队的线程)所共享的对象) {
//需要同步的代码块(这部分的代码块越少程序执行效率就越高)
}
//取款(操作同一账户)
public void Withdrawal(double money)
{
synchronized(this) {
//这里的锁对象是账户对象,在这个类中就是this
//取之前的余额
double before = this.getMoney();
//取之后的余额
double after = before - money;
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
//设置取之后的余额
this.setMoney(after);
}
}
//取款(操作同一账户)
public synchronized void Withdrawal(double money)
{
//取之前的余额
double before = this.getMoney();
//取之后的余额
double after = before - money;
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
//设置取之后的余额
this.setMoney(after);
}
注:
synchronized在开发中最好不要嵌套使用,可能会导致死锁(指两个或两个以上的线程在执行的过程中,因争夺资源产生的一种相互等待现象),
以下代码是死锁示例
public class Lianxi_02
{
public static void main(String[] args)
{
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Myth1(o1, o2);
Thread t2 = new Myth2(o1, o2);
t1.start();
t2.start();
}
}
class Myth1 extends Thread
{
private Object o1;
private Object o2;
public Myth1(Object o1, Object o2)
{
this.o1 = o1;
this.o2 = o2;
}
public void run()
{
synchronized(o1) {
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
synchronized(o2) {
}
}
}
}
class Myth2 extends Thread
{
private Object o1;
private Object o2;
public Myth2(Object o1, Object o2)
{
this.o1 = o1;
this.o2 = o2;
}
public void run()
{
synchronized(o2) {
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
synchronized(o1) {
}
}
}
解决线程安全问题的方案:
Java语言中线程分为两大类:
守护线程的特点:一般的守护线程是一个死循环,所有的用户线程只要结束,守护线程就自动结束
将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,Java 虚拟机退出。
java.lang.Thread中的方法
public final void setDaemon(boolean on);//true表示守护线程,false表示用户线程
定时器的作用:间隔特定的时间,执行特定的程序
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class ThreadTest
{
public static void main(String[] args)
{
//创建定时器对象
Timer ti = new Timer();
//创建定时任务对象
TimerTask task = new MyThread();
//任务第一次执行的时间
Date firstTime = new Date();
//任务task从时间date开始执行,每隔2000ms执行一次
ti.schedule(task, firstTime, 2000);
}
}
//定时任务类
class MyThread extends TimerTask
{
//指定定时任务run()代码块
public void run()
{
System.out.println("hhhh");
}
}
wait()和notify()不是线程对象的方法,是Java中任何一个Java对象都有的方法,因为这两个方法是Object类中自带的
public final void wait();
public final void notify();
public final void notifyAll();
注:
sleep()与wait()的区别:sleep()只能通过线程对象调用,且必须指定睡眠时间,不释放锁。wait()方法可以通过任意对象调用,且可指定时间,也可不指定时间,释放锁
采用以下的代码可以拿到一个文件的绝对路径,前提是文件必须在当前类路径下(在当前src下),这种写法无论在哪个系统上都可获得绝对路径,是通用的
String path = Thread.currentThread().getContextClassLoader()
.getResource("从当前类路径开始的文件路径(相对路径)").getPath();
//Thread.currentThread();表示获取当前线程对象
//getContextClassLoader();表示获取当前线程的类加载器对象
//getResource();获取资源,当前线程的类加载器默认从类的根路径下加载资源
直接返回流
InputStream path = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("从当前类路径开始的文件路径(相对路径)");
Java.util包下提供了一个资源绑定器,便于获取属性配置文件(.properties)中的内容,使用以下方法的前提是配置文件必须在当前类路径下,且此方法只适用于配置文件,注意:在写路径时路径后的扩展名不能写
ResourceBundle bundle = ResourceBundle.getBundle("db");//不能写成db.properties
String driver = bundle.getString("driver");
反射就是通过字节码文件对象把java类中的各种成分(变量、方法、构造方法)映射成一个个java对象,实际上是通过class对象反向获取该类的信息
获取Class对象的三种方式(在运行期间,一个类只有一个class对象产生):
方式一:会导致类加载,类加载静态代码块执行
Class c = Class.forName("带有包名的完整类名");
方式二:
Class c = 对象.getClass();
方式三:java语言中任何一种类型,包括基本数据类型都有.class属性
Class c = 任何类型.class;
以下所有方法中若要获取私有成员时就在Declared之后调用解除私有限定(安全检查管理器)方法
public void setAccessible(boolean flag);
flag为true时表示关闭检查,可调用私有
flag为false时表示打开检查,不可调用私有
通过class对象获取构造方法对象
java.lang.reflect.Constructor<T>
获取批量的构造方法对象
public Constructor<?>[] getConstructors();//所有公有的构造方法
public Constructor<?>[] getDeclaredConstructors();//获取所有的构造方法(包括私有、受保护、默认、公有)
获取单个指定的构造方法对象
//Class>... parameterTypes表示参数类型的字节码,如String.class、int.class
public Constructor<T> getConstructor(Class<?>... parameterTypes);//获取单个指定的公有构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);//获取单个指定不限修饰符的构造方法
创建新对象
public T newInstance(Object... initargs);//括号里的是填实际参数
通过class对象获取成员变量对象
java.lang.reflect.Field
获取批量的成员变量对象
public Field[] getFields();//获取公有的成员变量
public Field[] getDeclaredFields();//获取所有成员变量,包括:私有、受保护、默认、公有
通过变量名获取单个指定的成员变量对象
public Field getField(String name);//获取指定公有的成员变量
public Field getDeclaredField(String name);//获取单个指定成员变量、不限修饰符
获取变量的值,如果是私有就需要关闭安全检查
public Object get(Object obj);//括号里表示对象,返回该对象的某个变量的值
给获取的变量赋值
public void set(Object obj, Object value);//obj表示变量所在的对象,value表示要为变量赋的值
通过class对象获取成员方法对象
java.lang.reflect.Method
获取批量的成员方法对象
public Method[] getMethods();//获取公有的成员方法,包含了父类的公有成员方法
public Method[] getDeclaredMethods();//获取所有的成员方法,包含了私有成员方法,不包括继承的
获取单个指定成员方法对象
public Method getMethod(String name, Class<?>... parameterTypes);//获取公有的指定成员方法,name是方法名,后面填的是形参类型字节码
public Method getDeclaredMethod(String name, Class<?>... parameterTypes);//获取所有的成员方法
调用该成员方法
public Object invoke(Object obj, Object... args);//obj表示要调用方法的对象,args表示调用方法时传递的实际参数
获取类的父类对象
public Class<? super T> getSuperclass();
获取类实现的所有接口对象
public Class<?>[] getInterfaces();
注解Annotation是一种引用数据类型,编译后生成.class文件
注解定义语法
[修饰符列表] @interface 注解类型名{
}
注解使用语法
@ 注解类型名
@Target(ElementType.METHOD)//被标注的元素只能出现在方法上
@Target(ElementType.ANNOTATION_TYPE)//被标注的元素只能出现在注解类型上
@Target(ElementType.TYPE)//被标注的元素只能出现在类上
@Target(ElementType.FIELD)//被标注的元素只能出现在字段上
@Target(ElementType.PARAMETER)//被标注的元素只能出现在参数上
@Target(ElementType.CONSTRUCTOR)//被标注的元素只能出现在构造方法上
@Target(ElementType.LOCAL_VARIABLE)//被标注的元素只能出现在局部变量上
@Target(ElementType.PACKAGE)//被标注的元素只能出现在包上
@Target(ElementType. MODULE)//被标注的元素只能出现在模块上
@Retention(RetentionPolicy.SOURCE)//被标注的元素只被保存在java源文件中
@Retention(RetentionPolicy.CLASS)//被标注的元素只被保存在class文件中
@Retention(RetentionPolicy.RUNTIME)//被标注的元素只被保存在class文件中,并且可以被反射机制所读取
注解中定义属性
属性类型 属性名();//表示该属性名只能被赋值该属性类型的数据
public @interface MyAnnotation
{
int value();
String name();
String address() default "";//给address属性赋默认值,在该注解使用时可省略不写
}
注解使用
@ 注解类型名(属性名=属性值, 属性名=属性值......)
@MyAnnotation(value = 9, name = "xxx", address = "dddd")
class Person
{
@MyAnnotation(value = 9, name = "xxx" )//因属性address有默认值可省略
public void run()
{
}
}
反射注解
判断这个注解是否在此类上
//括号里填注解类型的字节码,
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);//返回true表示在此类上有此注解,false表示没有
获取该注解对象
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);//返回该注解类型,可能需强转
这些设计原则都是为了提高代码的维护性、扩展性、复用性、低耦合高内聚
(此处归纳比较琐碎的知识点)
System.out.println("ab" + "c" + 4 + 6);//结果是:abc46
System.out.println(4 + 6 + "abc");//结果是:10abc
float f1 = (float)12.345;
float f2 = 12.345F;
byte b1 = 3, b2 = 4, b3;
b3 = b1 + b2;//这是错误的,应改为b3 = (byte)(b1 + b2);
b3 = 3 + 4;//这是对的,因为3和4是常量,常量运算是先把结果计算出来,然后看是否在byte的范围内(-128~+127),如果在就不报错
int a = 10;
int b = 20;
System.out.println(a^b^b);//10
System.out.println(a^b^a);//20
wc : for(int i = 0; i < 3; i++)
{
nc : for(int j = 0; j < 4; j++)
{
if(j == 2)
{
break wc;
}
}
}
var a = 10;
等价于
int a = 10;
import java.util.Date;
System.out.printf("%tc", new Date());
main + Alt + /
syso + Alt + /
Alt + /
Ctrl + shift + f
Ctrl + shift + o
单行注释:Ctrl + /
多行注释:Ctrl + shift + /
取消多行注释:Ctrl + shift + \
Alt + shift + s + c
Alt + shift + s + o
Alt + shift + s + r
以上就是我对JavaSE部分的大致总结了,后续会有一定补充,那经过对这部分的学习呢,其实感觉Java入门并不难(虽然只学了一丢丢,哈哈哈),只是它的知识体系有点复杂,需要注意的细节蛮多,只要有足够的耐心和毅力,Java入门冒得问题的,纪念一下(2021.1.23)第一次写博客(在这方面需要学习的东西还有很多),加加油!最后感谢大家的浏览,蟹蟹。