Java知识记录
这是我曾经学Java的一点知识记录与理解。之前c语言学的还可以,所以java这里记录一点与c不同的东西
基本语法和基本操作
switch表达式。
很神奇,感觉是用来进行多种情况的判断赋值的,使用->符号,并且也不需要break
输入输出。
一般采取scanner读取控制台,或用JOptionPane来读取输入框
输出类似,用System。out就是控制台,用JOptionPane.MessageDialog
随机数
我是没感觉出随机数有多重要,但是既然老师都说了比较有用,那就姑且记一下。
Math.random这个只能生成0-1的浮点数
但是Random对象就不一样了,他可以对各种类型的变量进行随机赋值,bool,byte,int,double,以及部分数组,一个next就搞定 ,不过貌似都是primitive数据
Random的实例通常是配合currentTimeMillis来用的,seed为系统当前的毫秒时间
遍历。
java的成员遍历配合万能的var真的好用
字符串操作
Character包装类
charValue转为char类型,toString转为字符串,hashcode返回ascii值
其他一系列判断字符种类以及转换的方法。
String类
构造String类方法很多,常用的是用String.valueOf()来转换其他数据构成string。
也可以采用复制构造,new String(s);复制来源也可以是StringBuffer
将String转换为charArray,使用 toCharArray();
之后就可以采用c语言的风格去操作字符串。
静态和动态,static和final。
如何理解静态?静态就在那里,而不在堆上,你可以看到他,但是他看不到你(非静态)!
类比一下全局变量。
static
如果给变量加,他所属的类不会变成静态类。静态的仅仅是这个变量,这个变量的初始化可以用static{内有赋值语句}的 静态初始化块 来静态初始化。
如果给方法加,他所属的类会变成一个静态类,但是类不能加static标志(其实也没必要)
如果给类加static,那必然是内部类。
静态的类直接访问即可,可以实例化,但是不建议,为了避免被实例化,可以自己设定private的构造函数来拒绝构造。
final
值只能被赋值一次
方法不能被覆盖,告诉你不能被改变。
类不能被继承。就告诉你是叶节点。
从上面看,建立一个计数的静态变量完全没问题,不过建立对象数组需要全部初始化,用for的成员遍历比较好
类相关注意点
基本的。
基本东西
variable instance ,method,getter,setter,构造函数,至少把类的variable instance初始化了(其实这个都不用构造函数出手,只不过构造函数的优先级最高,可以无视前后顺序)但是无论是初始化,还是初始化块,还是构造函数初始化,用一个就行了。
继承的基本概念:
子类继承父类所有实例变量(复制一份)和方法并可以覆盖。
覆盖的时候可以标记@Override
多态的基本概念:
简单说就是大盒子可以装小东西,本质上就是小赋值大,所以在引用,返回,参数这三个方面都有多态(本质都是赋值)
多态的弊端,会丧失具体性,可以通过强制类型转换来具体化,但是在转换之前要用instanceof 关键字判断一下是否可行。
封装的基本概念:
一种思想,只暴露接口方法,所有实例变量都private,内部方法也私有。
设计类,子类,覆盖,抽象类,接口的理解与使用:
如果新类不通过IS-A测试,就设计新的类;如果通过IS-A测试,就extend继承;如果需要在一些地方上特殊化一下,就@Override或者新增;如果我当前的位置仅仅是一大堆子类的父类模板,这个模板不应该被实例化,我就需要做抽象类,把一部分方法加abstract;如果我连模板都不做了,仅仅是想对类进行一个除了继承树以外的交叉分类(用于多态),我可以定义一个接口。
类只能来自于单亲家庭(extend),但是可以是多重角色(implements)
只读类。
设计只读类,就采用构造器+不设setter(),如果要“修改”,则设置一个返回新的对象的调整函数,原对象被回收。这种的好处是,空间换性能,还有就是可以连续调用。
如string就是只读类,+会被变成concat函数,如果调用太多字符串拼接,就会进行很多新建对象的过程,会拖累速度,相比之下,如果仅仅是int的加,速度快百倍不止。或者可以用buffer(线程安全)+append方法/builder(线程不安全)
不是还有个final关键字嘛。
抽象类
为了解决继承中有的东西(指部分方法)不适合实现的问题,不适合在当前实现,就强制让子孙实现。
abstract public class Abs{ public int i = 1;//可以有实例 public void func1(){ //实现 } public abstract void func2();//不实现,抽象 } public class extend Abs{ //必须实现func2 }
在继承层次中,有的东西就仅仅是表达一种逻辑层次,不应该被实例化,就用abstract来声明。唯一的价值就是在继承关系中发光发热,抽象类必须被extend。
抽象类中可以有抽象方法,同样也是没有意义。
当然,抽象类中可以有非抽象方法和实例变量。
如果抽象类的子类没有完全重写父类的抽象方法,那么这仍然是个抽象类。
接口
基本概念:
目的是在多态中实现交叉分类的使用,描述一种不便于分类的特性。接口没有带来实现上的方便,仅仅是表明一种多样化的继承关系,但是可以用于多态,这就够了。
接口是纯抽象类,所以以不用abstrac声明接口了,直接 换成interface,方法也直接上类型即可。
里面有实例,但是都是static final的,因为这种特性应该是一种固有的,不应该被改变,而且可以直接调用。
接口可以不实现方法,但是如果不实现,那他就是个抽象类。
扩充接口:
自定义接口,去继承已有接口,然后自己加入新的特性方法。
函数式接口:
只有一个共有方法,常用于引用lambda表达式。
新特性:
私有方法,被默认方法调用,如果没有被子类重写,就直接继承
interface Pet{ void beFriendly(); //自带public abstract 含义 public abstract void play(); private void secret(){ //私有方法。 system.out.println("secret"); } default void defaultMethod{ secret(); { } public interface Robot{ void move(); } //接口是纯抽象的。 public class Dog extends Canine implements Pet, Robot{//可以implement多个接口 //实现接口 //进行你的操作 }
对象组合。
内部类,一种特殊的是在构造函数里实例化一个外部类,将其引用设为private,但外部类还可以被用。我们继续说一般的。
内部类是在一个类的功能较多的时候,用来实现区分,将一个类内部一些有关系的东西包装在一起。
成员内部类:
把类变成private的内部类,作为一个成员,只能自己用。这个private应该加,符合封装特性。
特点是,仅仅用来分割代码,而且是与实际的对象有关。
静态内部类:
和一般内部类(可以和外部类互相访问),静态内部类看不到动态部分。
特点是,一般用来封装不依赖外部条件的,比如数学函数。
本地内部类:
将内部类放入外部类的一个方法中,该方法内部new这个内部类,自己使用。
属于仅仅用于某个方法的类。
匿名内部类:
一般来说,我们是无法直接new一个接口类的,因为是抽象的。
我们需要再新建一个类去实现接口,在去new这个子类。
如果以后我们不复用这个类,而且这次使用就是在这个方法里,是不是就有点多余?于是有了临时的匿名内部类。
匿名内部类就可以将这两部结合起来。我也不知道这个子类是什么名字,反正有一个var的引用,引用了不知名类,这个类实现了接口的功能。
之后就可以愉快的用这个引用变量实现接口的一些功能了。
在拥有可视化界面的GUI应用中,大量使用匿名内部类。比如为按钮的点击编写事件响应代码。interface MyInterface { void func(); } class Main{ int ouberField = 0; public void InnerVisitOuterField() { var mi = new MyInterface() { //在方法里,创建一个匿名内部类,用mi引用 @Override public void func() {//实现接口 //访问外部类的字段 AnonymousInnerClassTest.this.outerField++; System.out.println(outerField); } }; mi.func(); } }
异常
方法抛出异常,try中包异常方法,catch中包处理异常,finally无论如何都要执行
异常有两种来源
public void do() throws Exception{ //表明方法可能会抛出异常 //可能会抛出异常的代码,比如 if() throw new Exception(); //可能会抛出异常的方法 }
多重异常:
多个抛出就用多个catch,但是要注意顺序,应该从小往大接,起到筛选作用。
之所以要筛选,是因为异常也是多态的,异常本质上是个对象。
处理异常的第二种方法——duck机制:
当前方法如果不处理异常,可以选择抛出异常,虽然main也可以throws,但是这就意味着可能会死机。
public void do() throws Exception{ }
函数式编程
常用于高并发,分布式场景。
特点:
- 函数没有副作用。不会对外面的值产生影响->传入和传出是单射关系,线程安全。
- 高阶函数。函数可以当参数,还可以返回函数配合lambda表达式使用,可以形成函数式接口变量,变量可以引用静态方法,实例方法。
类名::方法,类名.成员- 级联调用。
lambda表达式:
相当于一个可以复用的代码块,类型为接口类型。
节省了定义类的操作,甚至进一步可以节省定义引用的操作。
以上用法就是赋值给函数式接口变量,另一个就是直接当被调用的实参(实参类型是函数式接口类型)。
//使用匿名内部类来承载一个类实例,进而实现承载方法(方法本质上是代码块) Runnable race1 = new Runnable() { @Override public void run() { System.out.println("Hello world !"); } }; //使用lambda表达式更加简洁,使用一个接口类型变量来承载一个代码块 //省去了承载实例的多余步骤,直接承载代码块 Runnable race2 = () -> System.out.println("Hello world !"); race1.run(); race2.run();
函数式接口:
只定义一个自定义方法的接口,使用@FunctionalInterface的annotation
当然,默认和静态,还有Object类的公有方法不计入。
默认方法相当于给接口定义一种普遍会执行的方法,让接口不是那么没用。
静态方法也是类似,区别就在于,默认方法需要实例化,静态方法只需要类实现接口。
如此,还可以实现小幅度的接口扩充操作而不用担心已有的实现接口的类无法编译的问题。
@FunctionalInterface public interface Runnable{ public abstract void run(); }
方法引用:
lambda表达式可以作为形参,规定好方法格式,后面可以直接传入方法引用作为实参
类::方法(静态引用),对象::方法(一般引用)
这其实是lambda的一种简写。
线程
所谓多线程,实际上只是在不同线程之间来回切换。这种切换是不稳定的,完全可以在执行一个赋值的过程中被线程调度器停掉,另一个线程执行。
流程:
- 新建一个类,这里叫MyRunnable,实现Runnable接口,要实现的是public void run(),要执行的任务就在这里
- 用接口多态的引用去建立一个新的对象MyRunnable
- 新建Thread对象,参数取Runnable型引用
- 调用线程的start
package com; class MyRunnable implements Runnable { //实例化任务,实现Runnable接口 public void run() { //run必须实现 go(); } void go(){ new Count(); System.out.println(Thread.currentThread().toString() + Count.counter); } } public class Main { public static void main(String[] args) { Runnable myJob = new MyRunnable(); //用Runnable接口多态定义一个引用指向我自定义的任务 Thread threadOne = new Thread(myJob);//为线程安排任务 Thread threadTwo = new Thread(myJob); threadOne.start();//启动线程 threadTwo.start(); } }
线程控制:
setName()和currentThread()方法,可以实现对线程的监控
1 主动sleep,在需要执行另一个线程时,可以先把当前的sleep。这种主动控制更稳定。
try{ Thread.sleep(2000); } catch(InterruptedException e){ e.printStackTrace(); }
2 synchronized
用于,防止两个线程同时访问一个对象。
一般,synchronized关键词是给方法前面加的。
实际上还是给对象上了锁,而不是方法。
只不过,给方法加synchronised,是指定了哪种操作会触发锁,一旦触发,该对象整体无法被其他线程调用。
会出现死锁问题,如果互相在等。
数据库使用
java做的也不错,不管是什么数据库,都可以使用统一的操作来搞定,降低学习成本。
先记录一些技巧 1 可以用String对象保存要重复使用的sql语句 2 PreparedStatement 加强版Statement,可以储存一条预编译sql语句,保证在调用这条语句的时候的高效,适用于多次运行的,比如查询。 try{ 链接数据库:Connection connection = DriverManager.getConnection(URL:); 创建语句对象:Statement statement = connection.createStatement(); 执行语句: statement.executeUpdate(sql 语句); ResultSet rs = statement.executeQuery(sql);//返回一张表,可以用get方法来访问column //同时只能开一个ResultSet,所以Query下一个的时候,第一个被自动关闭 移动ResultSet的行指定,rs初次指定的是第一行的前面的东西: rs.next(); 提取ResultSet中的column: String string = rs.getString("string类名"); int i = rs.getInt("int类名"); } catch ( SQLException e){ System.err.println(e.getMessage()); System.err.println(e.getErrorCode()); System.err.println(e.getSQLState()); } finally{ 善后: try{ if(某!=null) 关闭 { catch{ { }
路径和文件
文件路径检查以及创建
从网上爬资源储存以及生成数据库和访问数据库,都是要用路径的,不然一大堆东西堆在一个文件夹下面,头都大了。
一般来说,我们发出去的程序都是jar包,无论是jar包还是有java文件的项目,都可以通过统一的命令获取当前项目路径(也就是根目录):
之后就要考虑到健壮性,所以要增加目录判断,如果目录不在,可以尝试一次性创建所有路径上的目录。
//获取当前路径 String path = System.getProperty("user.dir"); //判断路径安全性,没有就新建路径 path=path+"/你自己要加的路径2"; File file = new File(path); if(!file.exists()){ file.mkdirs(); //最好用mkdirs,因为这个会把路径上所有需要的文件夹都创建出来 System.out.println("success"); }
文件获取及保存
这是我封装的一个方法
//将指定url的内容下载到path,可以自动创建路径,name,这个的优势在于,无论任何格式的文件都可以) public static int download(String url, String path, String fileName){ try { //新建URL链接 URL urlObject = new URL(url); //打开链接 URLConnection con = urlObject.openConnection(); //创建输入流 InputStream is = con.getInputStream(); //创建数据缓冲,后续配合len来实现缓冲读取 byte[] buffer = new byte[1024]; int len; //创建输出文件,提前铺设路径 File folder = new File(path); if(!folder.exists()){ folder.mkdirs(); } File picture = new File(path+"/"+fileName); //创建文件输出流 FileOutputStream os = new FileOutputStream(picture, true); //开始读入网络流,写入文件输出流 while((len = is.read(buffer))!=-1){ os.write(buffer,0,len); } //关闭链接 os.close();; is.close(); return 1; } catch (IOException e) { e.printStackTrace(); return 0; } }
项目层次与组织,组件化开发
最基本的重用是搞一个方法,然后在一个模块的一个包里的几个类之间相互调用。
进一步,可以在同一个模块不同包之间调用,这个时候用import即可
再进一步,想在不同模块之间调用,这时就要添加模块依赖、
再进一步,想要用到别人的项目中,就要到处jar文件,在别人的项目中添加模块依赖。一般来说,我们项目中存放第三方包的位置在lib文件夹中。
Project(文件夹) src(文件夹) 一级包(一般是叫com) 二级包(就比如叫cyy) .java文件 classes(文件夹) 一级包(一般是叫com) 二级包 .class文件 manifest.txt //其实所谓的包还是文件夹,只不过被叫成了包
对应这个组织层次,我们需要为为类指定包,用package 包名;
package com.cyy; //声明当前类的包归属结构,否则后面包装和引用的时候,会找不到,类似于树的指针吧 //如果是嵌套包,就是 包名1.包名2.包名3; public class Main{ public static void main(String[] args){ ; } }
好,到这里就已经把组织结构弄清楚了。其实项目里面还有个模块,模块是什么?其实就相当于我们上面的project文件夹,说白了一个项目,应该有不同模块(modual),各负责一个大的功能,然后每一个模块都是上面的组织方式,而我们后面打包jar的时候,也是以模块为单位的。
1 定向编译
平常我们都是用javac 直接编译的,但是现在有了组织结构后,就要变一下了。
定向编译,新的文件会自动创建相关的结构,和src的结构是一种镜像关系。
cd src目录 javac -d ../classes com/cyy/Main.java 首先,需要进入src的目录,这个目录里有一级包 之后的命令。 -d 代表directory ,表明我是定向到某个文件夹里的,后面紧跟目标文件夹。 编译目标仍然是按文件夹层次,一般是编译所有java,所以会这么写 javac -d ../classes com/cyy/*.java
2 执行
同理,但是我们执行class文件时候,不是以文件夹结构写,而是包结构写
cd class目录 java com.cyy.Main //这里就可以看到,我们是用。来表示包结构,而不是像前面编译的时候用\
3 打包 和 解压
这个时候就用到前面的manifest文件了,说白了,区分项目和模块的方法就是,项目里面有多个主类,但是一个模块只能有一个主类,到时候jar包是整体运行的,而代表这整体的就是主类,而主类信息就记录在manifest.txt文件中。
cd class目录 //因为我们是要把类打包,而不是源文件 jar -cvmf manifest.txt cyy.jar com jar 我们这次用的是jar工具 cvmf 表示这是在进行抽取类打包jar 后面紧跟主类信息 写法为 Main-Class: com.cyy.Main.java 最后是一级包目录
最后打包出个什么东西呢?可以使用 jar -tf 命令列出里面的东西(table file)
cyy.jar //里面有被抽取的包结构,以及主类信息 一级包(一般是叫com) 二级包 .class文件 META_INF(文件夹) MANIFEST.MF(记录主类信息) //前面的manifest.txt文件没有被包括进来,我们只是读取了信息而已
解压就用 jar -xf ,(extract file)
4 直接执行(但是这里有个问题,如果没有jre,就无法执行)
java -jar cyy.jar //不管是什么,只要你执行,一定用java工具 //加-jar是表明我执行的是个jar
5 idea打包
其实idea里有图形界面和自动化的东西去加,我们学上面的知识只是为了更好的理解,出了问题自己能解决。
idea打包,先要在file里去构建project structure ,然后选artifact(产品),这样就可以去指定一个模块结构(一般是用我们现成构建好的文件结构)。
指定了以后,还没有编译呢,后面要用build(编译+打包)一条龙搞定。
6 idea模块依赖
模块依赖。
一般说,是在项目文件夹里构造好关系。
然后还是project structure,这次选module,去设置模块依赖(dependences)
之后在编写的时候就可以在模块间调用了,之后再build即可,build大模块的时候会自动打包被依赖项
补充:
jar是什么文件?本质上就是个遵循zip标准的压缩包,其中有manifest文件,存放信息清单
Maven&Grado
用于文件打包发布测试等的文件组织结构,Maven是web,Grado是Android。
基本的就是一个pom.xml文件,里面储存和版本,文件结构,依赖有关的信息。
项目初始化的时候会自动拉组件,如果没有拉完,重新装载一下。
项目下有两个文件夹和一个pom.xml文件。
pom.xml文件
储存版本相关信息,如果版本号不对,会编译失败。
resource文件夹
储存界面文件.fxml,形式类似于html
标签代表组件,属性代表字段值。
因为有html的关系,所以所有对象可以构成对象树,呈现嵌套控件。
控件大致上分成容器控件和叶子控件。
java文件夹
一般会自动创建包,也可以自己弄包结构。
JavaFX图形技术
Maven结构
一般会自动创建包,也可以自己弄包结构。
但是自己弄包结构需要改一下项目文件:module-info.java,里面要有这一行,如果没有的话,你自己写的contorller是无法访问到fxml文档的。
//这句opens package to fxml,代表package可以被所有.fxml文件反射访问 opens com.controller to javafx.fxml;
包里面应有一个controller包,包里存一些controller类,负责事件响应。
controller对应入口点的class,一般叫**Application。这种类都要extends Application,这种的结构一般是:先实现抽象类,用@Override覆盖start方法,编写你的图形框架。下面弄一个main入口,里面直接launch();即可,运行这个类的时候就会调用上面的start方法。
问题来了:Application,controlller,fxml是怎么联系起来的?
Application和controller没有直接联系,他们都是和fxml建立联系的。
Application新建FXMLLoader来导入fxml文档,这是Application和fxml。
fxml里面有一句fx:controller=“完整包路径”,这是fxml和contorller。
其实就是: application <- fxml <- contorller ,由controller控制fxml,然后application再将fxml显示,每次做出事件,也都是这个响应链条。
放一个具体的程序,里面打了很多注解
//controller部分 package com.controller; import javafx.fxml.FXML; import javafx.scene.control.Label; public class TestController { //@FXML代表你要去访问组件,然后创建一个同名实例即可 @FXML private Label id1; //创建同名方法就可以编写响应 @FXML protected void action(){ id1.setText("dasdfa"); } }
<!--标注xml文档的版本和编码--> <?xml version="1.0" encoding="UTF-8"?> <!--引入库--> <?import java.lang.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import javafx.geometry.Insets?> <!--按照html和css合并的的组织方式编写--> <!-- xmlns:fx,没看懂是在干嘛,大概是标注这是个javafx文档 fx:controller 这是关键,指向控制器 --> <VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml" fx:controller="com.controller.TestController"> <padding> <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"></Insets> </padding> <!--标注了fx:id才能用控制器访问--> <Label fx:id="id1"></Label> <!--onAction=“#action”,代表这个按钮绑定了一个叫action的事件--> <Button text="Hello" onAction="#action"></Button> </VBox>
package com.controller; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; //这里要继承Application类 public class TestApplication extends Application { //这里要覆盖start方法 抛出异常 @Override public void start(Stage stage) throws IOException { //加载资源,fxml文档属于一种资源,图片也可以存到jar包的resource里作为嵌入 //这里的路径比较有趣,是和java文件夹对应的,就可以直接用,再新建子目录就自己改路径 //但是根路径就是对应路径 //FXMLLoader对象代表一个对象树 FXMLLoader fxmlLoader = new FXMLLoader( TestApplication.class.getResource("test.fxml")); //Stage:窗体 Scene:场景 //通过FXMLLoader传来一个控件树,来装载场景,另外两个参数为宽高 Scene scene = new Scene(fxmlLoader.load(),320,240); //设置标题 stage.setTitle("这是title"); //将Scene放到stage上 stage.setScene(scene); //显示stage stage.show(); } //Application入口,运行launch方法 public static void main(String[] args) { launch(); } }
项目打包
JavaFX和sqlite3驱动都不是jdk包含的,所以最好打包到项目文件里。
在项目文件夹下创建lib文件夹,然后把外部依赖都放进去,比如jdbc-sqlite,JavaFX的bin文件等等,包进jar包就可以集成了。