这是一个先学了python再学java 的憨憨笔记,基本基于java核心编程第一部分写的。
数据类型
基本数据类型
一个字节是8位
int
4字节, short
2, long
8, byte
1
float
4, double
8
char
单字符 ,2字节,能够转换成数字
java区分引号,单引号是字符,双引号是字符串。
上述类型,小字节可以向大字节隐式转换,类型无所谓。同等大小则低精度向高精度转换。例如,byte
可以转为double
,int
会转为float
。
此外还能强制转换,不过会丢失精度,因为是直接截取低位。同时可能导致正数变负数
int a=989; //0000 0011 1101 1101
long b = a; //b=989
byte c=(byte)a; //c=-35 二进制为1101 1101,最高位为1,所以是负数
下述类型,不可转换
boolean
: true
, false
enmu
枚举类型
String
字符串(实际上是类),字符串是不可变类型
null
空,类似python的None
变量在使用之前需要赋值,否则报错。
极大数值
非常大的数需要使用到java.math的 Biglnteger 和 BigDecimal类
大数的运算需要使用类方法,而不是运算符。
Biglnteger a = Biglnteger.valueOf(100); //将普通数值转化为大数值
Biglnteger c = a.add(b); // c = a + b
Biglnteger d = c.multipiy(b.add(Biglnteger.valueOf(2))); // d = c *( b + 2 )
java.math.Biglnteger
Biglnteger add( Biglnteger other )
Biglnteger subtract( Biglnteger other )
Biglnteger multipiy( Biginteger other )
Biglnteger divide( Biglnteger other )
Biglnteger mod( Biglnteger other )
返冋这个大整数和另一个大整数 other的和、 差 、积 、 商以及余数。int compareTo( Biglnteger other )
如果这个大整数与另一个大整数 other 相等,返回 0; 如果这个大整数小于另一个大整数 other, 返回负数;否则,返回正数。static Biglnteger valueOf( long x )
返回值等于 x 的大整数。
java.math.BigDecimal
BigDecimal add( BigDecimal other )
BigDecimal subtract( BigDecimal other )
BigDecimal multipiy( BigDecimal other )
BigDecimal divide( BigDecimal other RoundingMode mode )
返回这个大实数与另一个大实数 other 的和、差、积、商。 要想计算商,必须给出舍入方式(rounding mode)。RoundingMode.HALF_UP 是在学校中学习的四舍五入方式(即,数值0到4舍去,数值5到9进位)。它适用于常规的计算。有关其他的舍入方式请参看 Api 文档。int compareTo( BigDecimal other )
如果这个大实数与另一个大实数相等,返回 0; 如果这个大实数小于另一个大实数,返回负数;否则,返回正数。static BigDecimal valueOf( 1 ong x )
static BigDecimal valueOf( 1 ong x, int scale )
返回值为 X 或 x / 10 scale 的一个大实数。
String类
String 的创建
-
直接赋值。
String a="123"
,不过这样赋值是将一个指针指向字符串常量,若字符串常量相同,通常不会复制新的字符串,这代表多个指针指向同一个字符串。String a = "123"; String b = "123"; String c = "123"; //a,b,c指向同一个字符串常量,也就是a==b,a==c,b==c
-
实例赋值。
char[] ch= {'1','2','3','4','5'}; String a = new String(ch); String b = new String(ch); //由于是实例化,所以每个new的都是新的对象指针,所以a不等于b String c = new String(ch,2,2); // String构造器还提供参数,这个表明是从第二个字符开始往后数4个字符,所以c是"34"
String的特点
-
String可以和其他类型相加,相加的时候会调用toString 方法,将类型转为字符串,然后相加。不能转换的就报错。
"12"+2+2.3+"a"+0.7="1222.3a0.7"
-
关于格式化,System.out.printf是一个格式化输出函数。字符串变量要格式话则是String.format方法。它一个参数提供格式化字符,一个参数提供值。
String a =String.format("今天买了%d个桃子花了%.2f",50/4,10/3);
此外关于日期格式化自行百度,个人感觉不是很重要(面试也基本不问)
-
java中正则表达式也是纯字符串,需要套用专用的方法才会解析为正则表达式。
注意在java中,字符串中的所有特殊符号都要自行转义,所以反斜杠为
\\
所以,正则表达式的\d
在java中是\\d
。类似的正则表达式掺入反斜杠则是\\\\
。
String字符串常用方法
所有方法详情请看文档
java.lang模块无需import
String 支持使用+
来拼接字符串,但java自身不支持运算符的重载
java.lang.String
-
char charAt( int index )
返回给定位置的代码单元。 除非对底层的代码单元感兴趣,否则不需要调用这个方法 -
int codePointAt( int Index )
返回从给定位置开始的码点。 -
int offsetByCodePoints( int startIndex, int cpCount )
返回从 startIndex 代码点开始, 位移 cpCount 后的码点索引。 -
int compareTo( String other )
按照字典顺序,如果字符串位于 other 之前,返回一个负数;如果字符串位于other 之后,返回一个正数;如果两个字符串相等,返回0。 -
IntStream codePoints()
将这个字符串的码点作为一个流返回。调用 toArray 将它们放在一个数组中。 -
new String( int[] codePoints, int offset, int count )
用数组中从 offset 开始的 count 个码点构造一个字符串。 -
boolean equals( Object other )
如果字符串与 other 相等,返回 true。 -
boolean equalsIgnoreCase( String other )
如果字符串与 other 相等(忽略大小写),返回 true。 -
boolean startsWith( String prefix )
boolean endsWith( String suffix )
如果字符串以 suffix 开头或结尾,则返回 true。 -
int indexOf( String str )
int indexOf( String str, int fromIndex )
int indexOf( int cp )
int indexOf( int cp, int fromIndex )
返回与字符串 str 或代码点 cp 匹配的第一个子串的开始位置。这个位置从索引0 或fromIndex 开始计算。如果在原始串中不存在str,返回 -1 -
int lastIndexOf( String str )
int lastIndexOf( String str, int fromIndex )
int lastindexOf( int cp )
int lastindexOf( int cp, int fromIndex )
返回与字符串 str 或代码点 cp 匹配的最后一个子串的开始位置。 这个位置从原始串尾端或 fromIndex 开始计算。 -
int length()
返回字符串的长度。 -
int codePointCount( int startIndex, int endIndex )
返回 startIndex 和 endludex - l 之间的代码点数量。 没有配成对的代用字符将计入代码点。 -
String replace( CharSequence oldString, CharSequence newString )
返回一个新字符串。 这个字符串用 newString代替原始字符串中所有的 oldString。 可以用String 或 StringBuilder 对象作为 CharSequence 参数。 -
String substring( int beginIndex )
String substring( int beginIndex, int endIndex )
返回一个新字符串。 这个字符串包含原始字符串中从beginIndex 到串尾或 endIndex - 1的所有代码单元。 -
String toLowerCase()
String toUpperCase()
返回一个新字符串。 这个字符串将原始字符串中的大写字母改为小写 ,或者将原始字符串中的所有小写字母改成了大写字母。 -
String trim()
返回一个新字符串。 这个字符串将删除了原始字符串头部和尾部的空格。 -
String join( CharSequence delimiter, CharSequence... elements )
返回一个新字符串,用给定的定界符连接所有元素。
-
Boolean matches(String regex)
验证字符串是否符合正则表达式,符合返回true
-
String replaceAll(String regex, String replacement)
String replaceFirst(String regex, String replacement)
根据正则表达式替换字符串,All是全部替换,First是替换第一个匹配的结果
-
String[] split(String regex)
String[] split(String regex, int limit)
根据正则表达式分割字符,正则表达式匹配的结果不会加入到结果中。
Java String Builder字符串构造类
仅开辟一个空间储存字符,在需要时再转为字符串。效率的字符串构建方式,单线程工作
若要在多线程工作的话,其替代类为StringBuffer,但效率略低。
简单讲,普通字符串由于是固定的长度,若要添加需要用用加号添加,这样的操作时间实际上比较长的。
若要频繁动态修改字符串,那么,就可以用StringBuilder。
java.lang.StringBuilder
StringBuilder()
构造一个空的字符串构建器。 注意它的第一个字母是大写int length()
返回构建器或缓冲器中的代码单元数量。StringBuilder append( String str )
追加一个字符串并返回 this。StringBuilder append( char c )
追加一个代码单元并返回 this。StringBuilder appendCodePoint( int cp )
追加一个代码点, 并将其转换为一个或两个代码单元并返回 this。void setCharAt( int i, char c )
将第 i 个代码单元设置为 c。- ``StringBuilder insert( int offset, String str )`
在 offset 位置插入一个字符串并返回 this。 StringBuilder insert( int offset, Char c )
在 offset 位置插入一个代码单元并返回 this。StringBuilder delete( int startindex, int endIndex )
删除偏移量从 startindex 到 - endIndex - 1 的代码单元并返回 this。String toString()
返回一个与构建器或缓冲器内容相同的字符串
数组
初始化
数据的定义方式有两种
int[] a; //java风格,推荐
int b[]; //c风格
但如果要使用数组的话,还需要对它进行赋值,它的赋值就是指定长度
a = new int[100] //长度必须指定
指定长度后,数字数组元素初始化0,布尔数组初始化为false。对象数组初始化为null
初始化具体数值有两种方法。
//方法1
int[] a = {1,2,3};
//方法2
int[] a;
a = new int[] {1,2,3};
其中new int[] {1,2,3}
被称为匿名数组。
引用与拷贝
若有一个已存在数组变量,赋值给另一个数组变量,则两个数组变量引用的是同一个数组。
若要拷贝数组给新的数组变量,则需要用到java.util.Array的copyOf方法
a = new int[] {1,2,3};
int[] b = a; //b和a引用同一个数组
import java.util.*
int[] c = Array.copyOf(a,a.length); //c是一个与a相同的新数组
此外,基本数据类型的赋值,确实是赋值行为,而且基本数据类型的变量是实实在在获得了一个值,而不是引用(虽然效果和python的引用自动复制相同)。
命令行参数
main函数的String args[]会读取命令行的参数。
比如命令行输入
java xx.java -a hello -b cccc
那么xx.java程序的arg会有如下数据
args[0]: "-a"
args[1]: "hello"
args[2]: "-b"
args[3]: "cccc"
多维数组
创建数组时添加多个[]即可创建多维数组。
int[][][] a={{{1,2},{1,2}},{{1,2},{1,2}}};
实际上,java只有一维数组,多维数组实际上是构建一个一维的引用数组,数组内的元素引用某个数组,然后这个数组又引用某个数组这样。
因为这种特性,所以可以构建不规则的数组
int[][] a = {{1,2,3},{1,2},{1,2,3,4}};
数组的其他使用
数组的很多使用都在java.util.Arrays类中,它提供很多实用方法。
java.util.Arrays
static String toString( type[] a )
返回包含 a 中数据元素的字符串,这些数据元素被放在括号内,并用逗号分隔。
参数:a
: 类型为 int、long、short、char、byte、boolean、float或 double 的数组。
static String deepToString( type[] a )
返回包含 a 中多维数据元素的字符串,这些数据元素被放在括号内,并用逗号分隔。
参数:a
: 类型为 int、long、short、char、byte、boolean、float或 double 的多维数组。
static type copyOf( type[] a, int length )
static type copyOfRange( type[] a, int start, int end )
返回与 a 类型相同的一个数组,其长度为 length 或者 end-start,数组元素为 a 的值。
参数 :- a 类型为 int、long、short、char、byte、boolean、float或 double 的数组。
- start 起始下标(包含这个值)
- end 终止下标(不包含这个值)。这个值可能大于a.length。在这种情况下,结果为0或false。
- length 拷贝的数据元素长度。如果 length 值大于 a.length ,结果为 0 或 false ;否则,数组中只有前面 length 个数据元素的拷贝值。
static void sort( type[] a )
采用优化的快速排序算法对数组进行排序。
参数:- a 类型为 int、long、short、char、byte、boolean、float或 double 的数组。
static int binarySearch( type[] a, type v )
static int binarySearch( type[] a, int start, int end, type v )
采用二分搜索算法查找值 v。 如果查找成功 ,则返回相应的下标值 ; 否则,返回一个负数值 r。 - r - 1是为保持 a 有序 v 应插入的位置。
参数:- a 类型为 int、long、short、char、byte、boolean、float 或 double 的有序数组。
- start 起始下标(包含这个值)。
- end 终止下标(不包含这个值)。
- v 同 a 的数据元素类型相同的值。
static void fi11( type[] a, type v )
将数组的所有数据元素值设置为 V。
参数:- a 类型为 int、long、short、char、byte、boolean、float 或 double 的数组。
- v 与 a 数据元素类型相同的一个值。
static boolean equals( type[] a, type[] b )
如果两个数组大小相同,并且下标相同的元素都对应相等,返回 true。
参数 :- a、b 类型为 int、long、short、char、byte、boolean、float 或 double 的两个数组。
输入输出
标准输入输出
java输出的函数有三种
System.out.println()
输出一行,会添加一个换行符结束
System.out.print()
输出,不添加换行
System.out.printf()
支持格式化的输出
java的键盘输入略微麻烦一点,其过程为
import java.util.*; //导入Scanner
Scanner in = new Scanner(System.in);
String in1 = in.nextline(); //输入一行,回车结束
String in2 = in.next(); //输入一个单词,任何空白字符结束
int in3 = in.nextInt(); //输入一个整数
此外Scanner的输入时是可见的,若要不可见,可以使用Console类
import java.io.*; //导如Console
Console cons = System.console();
String username = cons.readLine("Username:");
char[] passwd = cons.readPassword("Password:"); //此时输入的内容是不可见的
java.util.Scanner
Scanner(InputStream in)
用给定的输人流创建一个 Scanner 对象。String nextLine()
读取输入的下一行内容。String next()
读取输入的下一个单词(以空格作为分隔符)。int nextlnt()
double nextDouble()
读取并转换下一个表示整数或浮点数的字符序列。boolean hasNext()
检测输人中是否还有其他单词。boolean hasNextInt()
boolean hasNextDouble()
检测是否还有表示整数或浮点数的下一个字符序列。
java.io.Console
static char [] readPassword(String prompt, Object ... args)
static String readLine(String prompt, Object ... args)
显示字符串 prompt 并且读取用户输入, 直到输入行结束。 args参数可以用来提供输人格式。
文件输入输出
文件输入也是一样使用Scanner
import java.nio.file.* // Paths.get 处于java.nio.file模块中
Scanner in = new Scanner(Paths.get("miyflle.txt"), "UTF-8");
//必须要使用get获取文件流,直接使用文件名会报错
读取的方法和控制台输入一致
文件输出则需要使用PrintWriter类
import java.nio.file.*
PrintWriter out = new PrintWriter("myfile.txt", "UTF-8");
输出的方法也是print
, printf
, println
控制流程
块
使用{}即可构造块,在块内的参数无法作用到块外,块外参数可作用到块内。
块内外的参数名不能相同,否则会报错。
{
int out=1;
{
int in=2; //in 仅作用在内部
System.out.printf(out); //out 可作用在内部
int out=3; //会报错
}
System.out.printf(in); //会报错
}
块的这种特性在下面的所有语句中都适用。
条件语句
if(condition) statement
else if statement2
else statement3 //statement 通常为一个块
while 循环
while(condition)
statement //statement 通常为一个块
类似的还有do while
do
statement //statement 通常为一个块
while(conditon)
for 循环
for循环和C++一致。
for(var;condition;varChange)
statement //statement 通常为一个块
需要注意的是,for内部定义的var作用域为statement,在外部是无法使用的,若需要使用,则需要在for之前定义var
{
for(int i=1;i<10;i++){}
System.out.printf(i); //报错
}
{
int i=0;
for(i=1;i<10;i++){}
System.out.printf(i); //没有问题
}
for迭代
java也有用于迭代数组的for循环。
for(var:iterator) //类似于python的 for var in iterator
statement
上述代码中的iterator通常都为数组。实际上任何迭代器都行。关于这部分可能要去看java核心编程第二部分
switch 选择
switch(condition)
{ //块,下面的冒号后面可以不加花括号
case x:
...
break; //不添加break则会顺序运行下面的case
...
default:
... //不匹配任何x则运行此块
break;
}
标签
定义标签可以利用break用来跳出循环,但要注意标签需要放在需要跳出的块的前一行
while(1){ //外层
label:
while(1){ //中层
while(1){ //内层
break label; //直接跳出中层循环(虽然没有意义),但继续外层循环
}
}
statement; //跳出后会执行该statement
}
同时标签也可以用goto操作,但不推荐
同样continue也可以搭配标签,和break不同的是,它会跳到标签标注的循环的第一行
while(1){ //外层
label:
while(1){ //中层
statement; //跳出后会执行该statement
while(1){ //内层
continue label; //直接跳回中层循环(虽然没有意义)开头
}
}
}
类概述
这里的面向对象概念就不说了,只提供基本知识。
main方法类
main函数处于一个类中,该类是程序的入口,且mian函数所在文件的文件名与mian函数所在类的类名一致。
public class MainClass //文件名为MainClass.java
{
public static void main(String[] args)
{
... //主函数代码在这
}
}
实际上,文件名要和public 类的名字相匹配,一个文件只能有一个public类
创建类
class ClassName
{
fields //通常用于定义类变量以及一些操作
...
constructors //构造函数,可以有多个
...
methods //方法
}
构造器
class A
{
A(){ //构造器
...
}
}
- 构造器与类同名
- 每个类可以有一个以上的构造器(重载)
- 构造器可以有 0 个、1 个或多个参数
- 构造器没有返回值
- 构造器总是伴随着 new 操作一起调用
- 若不显式的编写构造器则系统自动生成一个构造器,该构造器会把所有未赋值的参数赋值为默认值
创建类对象
创建对象有两种方式
classType var = new classCreateFun(); //使用构造函数创建
classType var = class.factoryFun(); //使用工厂函数创建
需要注意的是,构造函数以及工厂函数会生成类实例。但是classType var
创建的是指向对象的引用变量,不赋值给该变量前它不包含任何类实例。
你可以直接使用类实例如Fun(new classCreateFun())
,但不可以在赋值前使用类引用变量。
只访问类但不修改类的方法称为访问器方法,修改的方法叫做更改器方法。
访问器不应该返回可变对象
析构器
实际上java会自动进行垃圾回收,所以java不支持显式的创建析构器。但可以在任何类中添加finalize方法,他会在垃圾回收之前运行。但是已经不推荐使用了
java的垃圾回收比较深,而且牵扯到java的内存堆与栈,细节还是自行百度吧。
类细节
this指针
在方法中定义的变量为局部变量,在类中fields中定义的变量为内部变量,为了区别两种变量。java提供了this引用,它指向内部变量。(this就是python的self)
class A{
private int a=10;
public A(){ this.a++; }
public int fun(int b){
int c = 5;
return c+b+this.a;
}
}
pubilc,private域
pubilc和private的基本概念就不多讲了 。一句话就是一个是公共的,一个私有的。
注意类的方法可以访问属于同一个类的另一个实例的private 数据(私有域)
calss A{
private int a=10;
public void fun(A b){
System.out.print(this.a > b.a);
}
}
final域
还有一种是final域,即最终常量域。它需要在创建时就初始化,在类中的话就是必须要在构造器中初始化。并且该域在初始化之后无法再被修改。
但是若是可变类的话,类内部的数据还是可变的,只是引用该类的引用名不能指向其他对象。实际上不推荐final指向可变对象
class A{
private final string name="123";
private int data=1;
public A(){};
public void funa(string a){this.name=a;} //报错,不能赋值
public void funb(int a){this.data=a;}
}
final A test = new A();
A testb = new A();
test.funb(123); //可用,改变了final对象内部数据
test=testb; //报错,不能指向新对象
static静态域与静态方法
定义static域之后,域中的变量所有类共享。可以有多个实例,但是只会有一个静态域。简单讲,多个实例对象指向同一个静态域。
class A{
static int a = 0;
public A(){a++;}
public void show(){System.out.print(this.a);}
public void add(){this.a++;}
}
A a1 = new A();
A a2 = new A();
a1.show(); //输出5
a2.show(); //输出5
a1.add();
a2.show(); //输出6
上述是静态变量,还有种常用的是静态常量,常用于定义某种常量,和C++的DEFINE很类似。(通常为public static final
)
对应静态域,还有静态方法。所谓静态方法就是存在于类中,但是无法操作类的实例域,没有this指针,所有类、实例共享的方法。也可以说是存在于类中的普通函数。但是静态方法可以访问类中的静态域。 实际上,由于java是完全面向对象,所以没有正常函数一说,所以提出了静态方法。
class A{
static int a =0;
public A(){}
public static int fun(){return a;}
}
System.out.print(A.fun()); //输出0
A a = new A();
System.out.print(a.fun()) //输出1
注意main函数也是静态方法,而每一个public类都可以有一个mian方法。和python的if __name__ == '__main__'
一样,只有在运行该文件时才会自动启动main。
方法参数
java中所有的方法参数都是进行复制操作,也就是全部都是形参。
calss A
public A(){}
public fun(int a){ a= a*3;}
}
int test=10;
A b = new A();
b.fun(test);
System.out.print(test); //输出10
上述代码 test 的值复制给 a ,此时 test 和 a 指向不同的具体对象(基本数据类型的特点)。于是a的操作不会影响test。
但是若对于可变对象,数组等引用效果就不一样了。虽然也是复制,但是它所进行的复制是赋值引用对象的引用变量(也就是指针)。
class A{
private int a=0;
public A(){}
public void fun(){this.a+=10;}
public void geta(){return this.a;}
}
class B{
public B(){}
public void fun(A a){a.fun();}
}
A testa=new A();
B testb=new B();
B.fun(testa);
System.out.print(testa.geta()); //输出10
虽然是复制,但是类B.fun中的a和变量test都引用同一个对象,所以最后a对于对象的操作会反映到test中。
多参数
python 的多参数有 *args 和 **kws 。java也有类似的表现形式,它使用三个省略号...
来表示可以接受多个参数。
不过需要注意的是,接受的参数类型都是要指定的。
实际上这样做的话就是讲多个参数转化一个数组而已。
class A{
public A(){}
public int fun(int... a){for (int q:a){System.out.print(q);}}
}
this特殊用法
this是指实例自己,类似于python的self。但this单独调用的时候会调用this所属类的构造器。
class A{
private int a;
public A(int a){ this.a = a;}
public A(){this(10);} //无参的构造器使用this调用有参数的构造器
}
A test = new A() //调用A()构造器后会自动调用 A(10)构造器
初始化块
java提供一个初始化块,该块在任何构造器构造之前都会运行,可以存放公用初始化的代码。
class A{
private int a;
private static int id=0;
{
id++; //调用任何构造器都会运行
}
public A(){this.a=10;}
public A(int a){this.a=a;}
public static int getId(){return id;}
}
A test1=new A();
A test2=new A(2);
System.out.print(A.getId()); //输出2
包
java 和 python 一样,提供很多包来提供很多不同的功能。
导入包
使用一个包中的某一个类,最直白的方法是将类所属的包的完整名称写出来。
java.time.LocalDate today = java.time.LocalDate.now();
但是这样很繁琐,所以需要简单方式,也就是导入包。
和python一样,导入一个包需要使用import
关键字。
导入一个包中的所有类的方式是使用通配符 * ,例如import java.time.*
。这样就能导入time包中的所有类了。
但需要注意的是import
一次只能导入一个包,且该包不能包含子包,仅能包含类。否则会报错
import java.time.*;
import java.*; //包含多个包,错误
impoer java.*.*; //包含多个包,错误
LocalDate to = LocalDate.now();
同时与python不同的是,import导入某特定类后,可以直接使用类名。
import java.time.LocalDate;
LocalDate to = LocalDate.now(); //没有问题
而java的内置包都存在java包以及javax包中。
命名空间的冲突
若几个包中都有名称相同的类时,用通配符导入时不会有问题,但是使用类时则会报错
import java.util.*;
import java.sql.*;
Date test = new Date(); //报错,两个包都有Date类
解决这个方法有两种,第一种是使用类的全名,例如java.util.Date
。第二种是若只是用某一个包的类时,可以import导入特定的类,例如import java.util.Date
。
静态导入
由于java是完全基于类的语言,他没有所谓的函数一说,所有的都是类方法。但是那种通用的函数,例如输出函数print,也是存在于java.lang.System类中的。
这种通用函数在java中也就是类中的静态方法。
为了简化静态方法的导入(即不用每次都写所在包与所在类)java提供了静态导入import static
。静态导入之后就可以直接使用静态方法名了。
import static java.lang.System.*;
out.print("hello"); //直接调用静态域out的print方法
exit(0); //即静态方法System.exit
当然也可以静态导入特定静态方法或者静态域。
构建自己的包
要构建自己的包,只需要在文件开头加入package
关键字与包名即可。
package com.spring.java
public class A{ //A类现在存在于com.spring.java包中
...
}
实际上,在没有加入package
时,文件的类也会放入到一个默认的包中。这个包没有名字,也就没法导入。
此外,包所在的源文件应该放在对应包名的文件夹目录中。例如上述的代码的源文件名称为A.class
和A.java
。它所在的完整路径应该是com/spring/java/A.class
和com/spring/java/A.java
。文件树如下
.(根目录)
└─ com/
└─ spring/
└─ java/
├─ A.java
└─ A.class
在根目录放入java文件并导入包后,java会自动找寻包中的类
此外还有就是jar的包,这个是目录包文件的封装,它的细节将会在后面讨论。
包作用域
public 的 变量和类能够被任何其他类使用;
private 的 变量 只能被定义该变量的类使用,且private不修饰类;
未标记public或private的变量或类能够被同一个包中的其他类使用,未定义包则是默认包。
包路径
和python类似,java搜寻包的路径默认个有java根目录,当前目录。当然用户是可以添加包的搜索路径的。方法有两种
- 使用
java -classpath path
指令设置,注意path中可以有多个路径,unix使用:
分开,windows使用;
分开。 - 添加环境变量CLASSPATH
包路径所在的问题
在源文件中导入的包名会和文件目录关联,而且包的搜索路径仅在默认的包路径以及工作路径中搜索的。
举个例子,System
包存在于默认包路径中。而自己构建的包则需要放到工作路径当中,否则会找不到包。
例如下列文件树
.(根目录)
└─ com/
├─ spring/
│ └─ java/
│ ├─ A.java
│ └─ A.class
└─ hello/
├─ Hello.java
└─ Hello.class
这种情况下,仍然要从根目录运行 java com/hello/Hello.java
。否则com.spring.java包将会找不到。
编译器编译文件,解释器加载类
实际上若不使用包中的类的话即使导入不存在的包也不会报错,但是一旦使用不存在的包中的类就会报错
java注释工具javadoc
自行百度,不重要
继承
类的继承的基本概念不再赘述。
java 的继承都是公有继承。
java 的继承使用 extend
关键字
class A {...}
class B extend A {...} //B 继承与 A。 A是超类,B是子类
重写方法
重写方法就是在子类中使用同名方法,即可重写方法。java和python一样,自动处理多态。
若是要在子类中调用超类的方法的话,需要使用super
关键字,它和this
类似。
super
能够指向超类的方法与构造函数。
class A{
public A(){}
public void fun(){}
}
class B extend A{
public B(){
super() //调用超类的构造器
}
public void fun(){
super.fun() //调用超类的方法
}
}
类型指定与转换
超类的引用变量可以引用子类实例,但超类的变量调用子类方法且该方法不存在于超类时会报错
java方法调用的过程是列出所有候选方法然后搜索,具体细节请百度
当然因此,类型转换也可以使用在有继承的类中。超类可以直接引用子类,但是子类引用超类就需要进行强制类型转换。不过java不提倡这个强制类型转换。并且这种类的强制类型转化仅存在于继承类之间。
class A{}
class B extend A{}
A testA;
B testB;
testA = new B(); //超类引用子类没有任何问题
testB = (B) new A(); //将超类A转化为子类B
testB = (B) new String(); //String类不属于AB继承中,会报错
java 提供了检测是否属于某类的运算关键字instanceof
,和python的isinstance函数类似
抽象方法与抽象类
java 提供了abstract 关键字来设置某个类或者某个方法为抽象形式。
在某类中定义抽象方法后,该类也必须定义成抽象类。
抽象方法不需要提供实现,只要定义即可。继承抽象类的子类需要实现抽象方法。
若子类仍然存在抽象方法,那么子类仍然是抽象类。直到所有抽象方法都实现了。
此外抽象类是无法实例化的。
abstract class A{
public A(){}
public abstract void fun();
}
class B extend A{
public B(){}
public void fun(){}
}
A a= new A(); //报错,抽象类不能实例
final 和 protect
在类添加前缀final
会使得该类不能被继承。
或者在类方法添加final
,会使得子类无法被重写。实际上,在类前添加final会使得类内的所有方法添加final前缀。
class A{
public final fun(){}
}
final class B extend A{
public fun(){} //报错,无法重写
}
class C extend B{} //报错,无法继承
在方法或者属性前添加protected
关键字,会使得该方法或者属性能够被子类的方法访问。但是实例无法访问。
class A{
protected int a=10;
protected void fun(){}
}
class B extend A{
protected void fun(){
super.a=15; //可以访问
super.fun(); //可以访问
}
}
A testA = new A();
B testB = new B();
testA.a; //报错,不能访问
testB.a; //报错,不能访问
testB.fun();
元祖类Object
所有没有指定extend的类都默认继承于元祖类Object。
Object有几个很重要的方法。
Object.equal()
:判定应用变量与本引用的类是否是同一引用。是的话返回falseObject.hashCode()
:返回类的散列值,要求相同的对象返回相同的散列值。Object.toString()
:将对象的属性转化为字符串并返回。通常println函数会自动调用对象的该方法并输出。和python类似
类细节杂项
列表ArrayList
数组由于需要制定大小并不方便。所以java后面又提供了动态数组–列表。
和python类似,它是一个动态的有序数组。但不同的是,java列表需要指定列表类型。
而且java的列表是种泛型类,泛型类的具体介绍会在后面介绍。
列表指定类型需要使用<>来指定,且类型必须是对象。
class A{}
ArrayList<A> c = new ArrayList<A> //自定类A的列表
ArrayList<A> d = new ArrayList<>()
//javase7提供简化的构造方式
当然列表也可以初始大小,但是初始大小内部的元素是空的,而不是有默认值。
简单讲就是ArrayList会开辟内存空间,而不进行默认赋值
ArrayList<A> a = new ArrayList<A>(100) //指定列表大小为100
ArrayList支持迭代方法。
ArrayList常用方法有:
.add(item)
添加元素,和python的append类似。此外add(int index,item)
能够在特定的位置添加元素.ensureCapacity(int length)
固定大小,使用方法后会使列表会创建大小为为所指长度的内存空间。和构造列表时的方式类似。.size()
返回列表大小(元素的数量).trimToSize()
调整列表的内存空间为列表的实际大小,回收多余内存。.set(int index,item)
由于列表是一个类,所以不能用[]方式设置元素。该方法等价于a[index]=item。.get(int index)
等价于a[index].toArray(0)
将列表转化为数组.remove(int index)
删除特定位置的元素
当方法需要使用列表时,只能定义为ArrayList而不能再指定类型。这样会导致所有的列表传递后会变为原始列表(无类型)。这样再传递给有类型的列表时会产生警告(不是错误)。若认为这个警告没有问题的话可以加上修饰@SupperssWarnings("unchecked")
来取消警告。实际上泛型类都会哟这种状况。
class A{
public void funa(ArrayList list){...}
public ArrayList funb(){...}
}
ArrayList<A> test = new ArrayList<>();
A a = new A();
a.funa(test); //传入时不会有警告,只有在运算发现类型错误时才会报错
ArrayList<int> test2 = a.funb();
//会有警告,而不是报错
@SuppressWarnings("unchecked") ArrayList<int> test3 = a.funb(); //不再有警告
对象包装器与自动装箱
包装器指的是能够将对象,数值包装成某种特定状态,类型的一种类。通常包装器是不可变类,并且大多支持自动装箱和拆箱。
所谓自动装箱指的是在调用包装器,但不进行显示包装时,它能够自动的将对象打包成包装器的特定状态。
自动拆箱类似,表示在使用包装好的对象时,它会自动拆解对象变成能够使用的状态。
java中自带的,常用的包装器有Integer
、Long
、Float
、Double
、Short
、Byte
、Character
、Void
和 Boolean
。它们对应那些基本数据类型。
在使用列表时,由于类型指定必须是类,所以ArrayList是不成立的。这个时候内置包装器就十分有用了。
ArrayList<Integer> a = new ArrayList<Integer>() //Integer能够自动将传入数据包装成int类型
a.add(3) //实际上包装器自动运行了.valueOf()方法,即这句话等价于a.add(Integer.valueOf(3))
//这就是自动装箱
int b = a.get(0); //等价于 a.get(0).intValue() ,自动拆箱
当然,包装器在其他运算中作用也是一致的,自动装箱和拆箱在基本运算中也会自动运行。并且包装器会自动类型转换,并且是低精度向高精度转换。
Integer n = 3;
n++; //使用起来和int没有区别
Double m = n; //n的值自动拆箱,然后被Double包装器自动装箱,所以这样运算不会有问题
包装器的通用方法:
.toString
转为字符串 , parse
将字符串转化为对应类型 , valueOf
将对象尽可能的转化为对应类型
枚举类
实际上emnu类型是一个类,而且它比较特别,枚举内部的一个对象就是一个枚举实例。而且因为枚举是个类,所以可以在枚举中添加一个方法变量。
public enum Test{
A(1),B(2),C(3); //构建了3个实例对象,而且每一个都是一个构造函数来构建的
//枚举实例括号内的值就是对应对象的值。
private int num=3;
public int getNum(){return this.int}
}
实际上枚举类和python的字典有点类似。
反射
反射说的是java提供的一个库(java.lang.reflect),叫做反射库,它里面提供了很多实用的工具类与方法。之所以称为反射,是因为该库提供的类能够显示(反射)出一个类的各种详细信息。
Class类
需要将关键字 class 和类 Class 区分开
java中的每一个类都包含了很多信息与属性,而同时java提供了一个Class类用于获取一个类,实例的相信信息。
Class类相当于记录类信息的类,从Class里可以获取到变量的数量,类的名称,构造函数信息,等等等等。
而Object类也提供了一个.getClass()
方法来返回类的Class类,同时还提供.class
属性返回Class类。
class A{}
A a = new A();
Class test = a.getClass();
test.getName(); //Class提供很多有用的方法,getName就能获取实例所属类的完整名称(包含包路径)。
而且Class类可以获取类型的信息,例如Class a = int.class
会得到一个int类型的Class类。
实际上Class类时个泛型类,所以任何类型都可以返回。
其他类
除了Class类,反射还提供 Filed,Method 和 Constructor 类,分别能够获取一个类的域信息,方法信息以及构造函数信息
反射还提供Array类,用来专门获取数组信息以及操作数组的。
反射中的类通常都有一个setAccessible(boolean choice)
方法,设置它可以让反射类查看类中的private变量,方法等。
类的所有具体方法请查看文档。
接口
所谓接口,就是一种只定义方法与常量。但不做实现的抽象类,但是不使用class关键字而是使用interface关键字,这是为了区别类和接口
接口是不能使用与实例化的,它必须由其他类来实现(关键字 implements
)接口内部定义的方法。并且实现中方法名,参数数量类型都要完全和接口中定义的一致。
interface A{ //接口A
int M=10;
void fun(int a);
}
class B implements A{
public void fun(int a){return a;}
} //类B实现了接口A
接口特性
首先接口不是类,它是为了将定义和实现完全区分开而出现的。
一个类只能继承一个类,但是能够实现多个接口。 并且继承和实现能够同时出现。
硬要按类的角度来说的话,接口就是比抽象类更加抽象的类(实际上接口出现后抽象类用的就少了)
接口的变量和方法可以省去修饰前缀,因为所有的接口变量都是public static final的,而方法都是public的。
接口变量的修饰不能改成其他,否则会报错。方法可以添加static和final。
最新的javaSE让接口的方法可以提供一个默认行为,只要添加关键字default
,这样在类实现的时候可以不用实现所有方法。这个在GUI回调事件时很有用。
public interface A{
public void atestfun(int a , String b);
default void btestfun(int a){return a;}
}
public interface B{}
class C{}
class D extend C implements A,B{
public void atestfun(int a, String b){return a;}
}
接口冲突
当然多个接口的方法名称有相同时,且两个方法都有默认行为,并且实现的类不实现接口(使用默认方法),那么会产生冲突。若一个有默认一个没有,那么有默认的接口会覆盖没默认的。而冲突需要程序员自己决定调用哪个默认接口,或者实现该接口。此外,当超类和接口有相同的方法时,超类的方法会覆盖接口(类优先)。
interface A{
default int a(){return 12;}
}
interface B{
default int a(){return 15;}
}
class testA implements A,B{
int a(){A.a()} //A,B都有a方法,这里指定使用哪个默认方法,不产生冲突
class test implements A,B{
int a(){return 12;} //重新实现接口不产生冲突
}
接口实例
接口的一个实例就是克隆。由于java是全引用语言,所以指向可变类时,多个变量是指向同一个对象。那么这个时候需要指向新对象就需要clone方法了。
java的Object类提供clone方法,但是它的赋值是浅拷贝,若类中还有对象指向新对象,这一块的复制是无效的。
因此,对象还需要进行深拷贝。而java提供了Cloneable接口,它里面有方法clone()。用户可以自己设计clone方法来进行深拷贝。此外,clone是泛型方法。
同时,
class A{
public A clone() throws CloneNotSupportException //必须有这个异常处理
{
... //拷贝内容,由于类的克隆要返回类本身的类型,所以clone接口是泛型
//但是在实现上需要指定类型
}
}
lambda 表达式
java的lambda和py的差别很大。
lambda 表达式的格式如下:
(参数类型 参数名,...) -> {函数内容} //完整形式,函数内容可以有多行,单行内容可以省去花括号
(参数名,...) -> {...} //多个参数的类型可以推断得到时可以省略参数类型
参数名 -> {...} //一个参数时,参数类型可以推断那么可以省略括号
() -> {...} //不需要参数时小括号不能省略
此外,如果lambda的内容是调用某个类的某个方法时,有以下简化书写形式
System.out::print //相当于 x -> System.out.print(x)
Math::pow //相当于 (x,y) -> Math.pow(x,y)
//该书写形式称作方法引用,不过它是一种语法糖,简化操作而已。同时该书写形式支持三种类型
// object::instanceMethod
// Class::staticMethod
// Class::instanceMethod
方法引用中可以引用new,表示引用类的构造函数。
三种类型中前两种方法需要多少参数则lambda就要传入多少参数。而对于第三种情况,第一个参数会认作类的实例。 举个例子:
class A{
public A(int a){}
public method(int a,int b){}
}
A::method; //等价于 (a,b,c) -> a.method(b,c)
A::new; //等价于 (a) -> return new A(a)
lambda 用途
lambda是一个匿名函数,java中它大多是用在函数回调之中。
调用回调的方法中,总是需要传入一个接口,然后在之后调用接口中的方法。
那么lambda就可以不需要实现一个类来直接实现一个接口。
interface A{
void fun(int a,int b);
}
A test = (a,b) -> System.out.print(a+b); //定义lambda函数,实现了接口A的方法
C.fun(test); //设置lambda作为回调(假定C.fun中定义了接口A.fun的回调)
通常来说,lambda实现的接口有且只有一个抽象方法,同时java8中称这种接口为函数式接口。
同时,java8提供了简化的调用形式
interface A{
void fun(int a,int b);
}
C.fun((a,b) -> System.out.print(a+b)); //直接在函数中定义
通常来说,大多时候lambda都是实现函数式接口的,在其他场合中用的比较少。(大概)
lambda的变量作用域
在一个回调的实现中,可能会有额外的参数,例如下面的方法
interface A{}
public static void testFun(int a,int b){
A action = () -> System.out.println(a+b);
C.fun(action);
}
那么在调用action的时候 , 函数中的a和b可能已经消失了。不过java的lambda会储存它的参数,即使消失了参数也会存在,直到下一次回调储存新参数。这个在java中叫做闭包。
但是闭包不是什么情况都可以的。闭包有如下2个限制,
- 闭包的参数不能在lambda中修改
- 闭包的参数不能再lambda外改变
具体来说是,lambda的闭包参数总是final的,虽然不一定添加final修饰,但是传入的一刻起就不能再进行修改了。
interface A{}
public static void testFun(int a,int b){
a++; //报错,不能在外部修改
A action = () -> {
a++; //报错,不能在内部修改
System.out.println(a+b);
}
C.fun(action);
}
此外,lambda可以调用this指针,lambda的this指针指向的是lambda所在的类实例
interface B{int a=22;}
class A{
private int a=15;
void fun(){
B action = () -> System.out.println(this.a); //this 指向A而不是B
}
}
回调编写
编写回调的过程具体来说就是:
- 编写函数式接口
- 编写调用回调的方法
不过,java为了减少冗余的接口编写,它提供了通用的常用函数式接口。包含了有参数的接口,无参的接口,有返回的,没返回的等等。常用的接口有
接口名 | 参数类型 | 返回类型 | 抽象方法名 | 描述 | 其他方法 |
---|---|---|---|---|---|
Runnable | 无 | void | run | 作为无参无返回的动作运行 | |
Supplier | 无 | T | get | 提供一个T类型的值 | |
Consumer | T | void | accept | 处理一个T类型的值 | andThen |
BiConsumer<T,U> | T,U | void | accept | 处理T和U类型的值 | andThen |
Function<T,R> | T | R | apply | 有一个T类型参数的函数 | compose , andThen , identity |
BiFunction<T,U,R> | T,U | R | apply | 有T和U类型参数的函数 | andThen |
UnaryOperator | T | T | apply | 类型T上的一元操作符 | compose , andThen , identity |
BinaryOperator | T,T | T | apply | 类型T上的二元操作符 | andThen , max By , minBy |
Predicate | T | boolean | test | 布尔值函数 | and , or , negate , isEqual |
BiPredicate<T,U> | T,U | boolean | test | 两个参数的布尔值函数 | and , or , negate |
当然,当上述接口不能满足时,那就需要自己编写接口了。
同时,java有一条标准的回调接口命名规范,在编写回调接口时推荐按照规范编写。
接口名 | 参数类型 | 返回类型 | 抽象方法名 |
---|---|---|---|
BooleanSupplier | 无 | boolean | getAsBoolean |
PSupplier | 无 | p | getAsP |
PConsumer | p | void | accept |
ObjPConsumer | T,p | void | accept |
PFunction | p | T | apply |
PToQFunction | p | q | applyAsQ |
ToPFunction | T | p | applyAsP |
ToPBiFunction<T,U> | T,U | p | applyAsP |
PUnaryOperator | p | p | applyAsP |
PBinaryOperator | p,p | p | applyAsP |
PPredicate | p | boolean | test |
其中p,q为 int ,long, double;P,Q为Int,Long,Double
内部类
内部类指的就是在类内再定义类
class A{
private int a=10;
class innerA(){
public void fun(print(a));
}
}
内部类可以获取外部类的所有域,它内部操作是在构造函数中添加外部类的引用。
在使用this时,需要指定是指哪个类的this。即A.this
或B.this
。
类似的,在外部类new内部类时,完整的写法是OuterObj.new InnerClass()
,例如在A中B b = this.new B()
局部内部类
有了内部类之后,在方法中也可以定义类,这个称为局部内部类。
局部类不能用public和private说明,它只存在于方法中,外部无法看到它的存在
同时局部类的参数应该都为final(不必加修饰)
class A{
public void fun(){
class B{} //B为局部内部类
}
}
匿名内部类
当然为了方便,java又提供了匿名内部类。这个类在java8之前常被使用,因为它是回调的常用设置方式。但在java8之后被lambda以及函数式接口代替了。
匿名内部类的定义方式如下
class A {
B test = new B(){ //B就是一个匿名内部类
public void fun(){}
}
}
静态内部类
若内部类仅仅是为了隐藏在外部类之内,内部类并不引用外部类对象,那么可以将内部类设为静态。
在内部类前加static修饰的话就变成了静态内部类。静态内部类不产生外部类的引用,可以减少内存消耗,在某些时候很有用。
class A{
static class B{
public void fun(){print(123);}
}
}
代理
java的高级话题,对于应用开发程序员来说使用的很少。
等学完java基础再过来补充
异常
java的所有异常都继承于超类 Trowable ,该超类有两个子类 Error 和 Exception。
Error 表示程序运行发生错误,这种错误是程序内部引起,与编程无关,所以这类错误也很少见。
Exception 表示程序发生的异常,是编程导致,也是程序员要处理的东西。
基本的格式如下
class A throws XXXException,XXX{ //表明类可能会出现的异常
...
throw new XXXException(...) //异常是类,所以需要构造
try{...} //try不多说
catch(XXXExcepton | XXX | XXX e){....} //捕获异常并处理,多个异常相同处理可以用|分开
finally{} //finally无论发不发生都运行
try(FileClass res){...} //和py的with类似,对于那种资源对象会自动处理关闭
}
断言
pass
日志
pass
泛型
也就是能够设定类型的类,其基本表达为
class A <T>
{
private T a;
private int b;
public T fun(){return a;}
}
而且单独的方法也支持泛型,泛型方法能够存在于普通类中
class A{
public <T> T fun(){} //在普通类中的泛型方法需要加入<T>来表明该方法是泛型方法
}
此外接口也支持泛型,而接口在实现时就可以指定类型,或者不指定。
interface A<T>{
T fun();
}
class B implements A<Integer>{ //实现整型接口
public Integer fun(){...}
}
class C<T> implements A<T>{ //实现接口的类仍然是泛型
public T fun(){...}
}
泛型限定
因为类型的不一定,在某些情况下,需要限定泛型支持的类型。这个时候就需要进行泛型的限定,其方式是继承能够支持的类或接口。
同时继承的类还能够进行进一步限定
class A{
public void fun(){}
}
interface B{
void fun2();
}
class C<T>{}
class D <T extends A & B>{ //泛型限定为类A和接口B,它需要支持类A的方法与设定
public T fun3(){...} //同时需要支持接口B的方法。
}
通配符
有时候设定了一个泛型,对于其的子类或者超类都无法使用
class A{void put}
class B extends A{}
class C<T>{}
C<A> a = new C<B>(); //报错,不属于同一泛型
这样导致每一个继承都需要写一个泛型,为了简化,java提供了通配符<? extends xx>
, <? super xx>
以及<?>
其中extend表示支持该类以及其子类,super表示该类以及其超类,?表示支持Object以及其所有子类。
而<?>和的区别在于T是指定一个特定的类型,并且在代码中可以使用,而?是不能用于申明类以及方法的。它只能定义好的泛型。
class A<T>{}
class B<?>{} //报错,不能使用
A<?> a = new A<String>() //通配符的一种常用方式。
实际上?用的很少,更多使用的是extends和super