首页 文章 精选 留言 我的

精选列表

搜索[基础搭建],共10000篇文章
优秀的个人博客,低调大师

[源码]Python Tkiner 写的截图小工具 实现了大部分基础功能

最近在写一些小工具,因为是给别人用的需要界面,刚开始使用pyQT5 功能强大,但是打包后一个小工具 就有几十兆 有点蛋疼而且还很消耗资源;后面改用TK,TK明显简洁很多,功能同样也若很多! 因为有截图需求,就抽空写了这个工具,全当练习。过程当中遇到过好多蛋疼的事情,功能基本实现后发出来给大家分享一下,我个人还是比较喜欢python这门语言的! 实现思路:3.1.新建一个最小化窗口,利用线程启动监听"ctrl+a"启动截图窗口3.2.截图窗口初始化时利用 pyautogui.screenshot 截取全屏(类似PRTSC)作为子窗口的背景(在这搞了好久,后面没办法只有利用canvas设置背景),子窗口最大化取消菜单栏3.3.在主窗口上的canvas上用绑定鼠标事件,根据鼠标选取截图范围,和鼠标操作相关功能3.4.保存功能是根据截图范围重新截图生成文件保存 代码 main_UI.py fromtkinter.messageboximportshowinfo fromSc_Tool.Sc_Winimport* importkeyboard import_thread scwd=None #新建窗口对象 win=Tk() #最小化主窗口 win.wm_state('icon') win.iconify() #启用热键‘Ctrl+a’截屏 defstar_sc(): globalscwd scwd=windSc(win) scwd.newwind() defexit_sc(): print("exit") scwd.exit_sc(scwd) defstartThread1(event=None): keyboard.add_hotkey('ctrl+a',star_sc,args=(),suppress=False) defstartThread2(event=None): keyboard.add_hotkey('esc',exit_sc,args=(),suppress=False) try: _thread.start_new_thread(startThread1,()) _thread.start_new_thread(startThread2,()) exceptExceptionase: print(e) #启动app win.mainloop() scwd=None #新建窗口对象 win=Tk() win.wm_state('icon') win.iconify() #启用热键‘Ctrl+a’截屏 defstar_sc(): globalscwd scwd=windSc(win) scwd.newwind() defexit_sc(): scwd.exit_sc(scwd) defctrl_z(): scwd.ctrl_z(scwd) defstartThread1(event=None): keyboard.add_hotkey('ctrl+a',star_sc,args=(),suppress=False) defstartThread2(event=None): keyboard.add_hotkey('esc',exit_sc,args=(),suppress=False) defstartThread3(event=None): keyboard.add_hotkey('ctrl+z',ctrl_z,args=(),suppress=False) try: _thread.start_new_thread(startThread1,()) _thread.start_new_thread(startThread2,()) _thread.start_new_thread(startThread3,()) exceptExceptionase: print(e) #启动app win.mainloop() Sc_Tool/Sc_Win.py importos fromtkinterimport* importtkinter importpyautoguiaspyautogui fromPILimportImage,ImageTk importtkinter.fontastkFont importtime classwindSc: win,file_path,im,winAppSc,mouEvent,canvas=None,None,None,None,None,None #窗口宽窗口高宽高截图起点x坐标截图起点y坐标绘图起点x坐标绘图起点y坐标 win_w,win_h,width,height,x,y,dx,dy=0,0,0,0,0,0,0,0 #截图结束点坐标 end_x,end_y=0,0 #开始添加图形的起始坐标 cr_x,cr_y=0,0 #记录按钮的状态、文本输入按钮状态 but1_stat,but_text_stat=False,False #记录按钮类型1矩形2圆形 but_type=0 #tag标记文本输入单击次数标记 i,p=1,0 #工具栏部件 but1,but2,but3,but4,buttext,text_lable=None,None,None,None,None,None #画笔大小,文本框行数 size,row=1,1 #画笔颜色 color="black" #字体 font=None #按钮图片 img1,img2,img3,imgs,imgp1,imgp2,imgp3=None,None,None,None,None,None,None imgred,imgwhite,imgblack,imgblue,imggreen,imgyellow,imgback=None,None,None,None,None,None,None #工具按钮列表 but_list=[] #图形id列表 ids=[] def__init__(self,win): self.win=win #获取屏幕分辨率 self.width,self.height=win.winfo_screenwidth(),win.winfo_screenheight() #窗口宽度和高度 self.win_w,self.win_h=self.width,self.height image=pyautogui.screenshot(region=[0,0,self.width,self.height]).resize((self.width,self.height)) self.im=ImageTk.PhotoImage(image) self.img1=tkinter.PhotoImage(file="Sc_Tool/icon/1.png") self.img2=tkinter.PhotoImage(file="Sc_Tool/icon/2.png") self.img3=tkinter.PhotoImage(file="Sc_Tool/icon/3.png") self.imgtext=tkinter.PhotoImage(file="Sc_Tool/icon/Text.png") self.imgs=tkinter.PhotoImage(file="Sc_Tool/icon/s.png") self.imgp1=tkinter.PhotoImage(file="Sc_Tool/icon/p1.png") self.imgp2=tkinter.PhotoImage(file="Sc_Tool/icon/p2.png") self.imgp3=tkinter.PhotoImage(file="Sc_Tool/icon/p3.png") self.imgblack=tkinter.PhotoImage(file="Sc_Tool/icon/black.png") self.imgwhite=tkinter.PhotoImage(file="Sc_Tool/icon/white.png") self.imgred=tkinter.PhotoImage(file="Sc_Tool/icon/red.png") self.imggreen=tkinter.PhotoImage(file="Sc_Tool/icon/green.png") self.imgblue=tkinter.PhotoImage(file="Sc_Tool/icon/blue.png") self.imgyellow=tkinter.PhotoImage(file="Sc_Tool/icon/yellow.png") self.imgback=tkinter.PhotoImage(file="Sc_Tool/icon/b.png") defnewwind(self,event=''): #最小化主窗口 self.win.wm_state('icon') self.win.iconify() self.winAppSc=Toplevel(self.win) #winAppSc.attributes("-alpha",1) #删除标题栏 self.winAppSc.overrideredirect(True) self.winAppSc.geometry("%dx%d+%d+%d"%(self.win_w,self.win_h,(self.width-self.win_w)/2,(self.height-self.win_h)/2)) self.winAppSc.config(bg="white") self.winAppSc.title('截屏') self.winAppSc.attributes("-topmost",True) canvas=tkinter.Canvas(self.winAppSc, width=self.win_w,#指定Canvas组件的宽度 height=self.win_h,#指定Canvas组件的高度 bg='red')#指定Canvas组件的背景色 canvas.create_image(0,0,image=self.im,anchor='nw',tag=('r','r0')) canvas.pack(fill='both',expand='yes') self.canvas=canvas #工具栏部件 self.but1=Button(self.winAppSc,text="保存",image=self.imgs) self.but2=Button(self.winAppSc,text="矩形",image=self.img1) self.but3=Button(self.winAppSc,text="圆形",image=self.img2) self.but4=Button(self.winAppSc,text="箭头",image=self.img3) self.buttext=Button(self.winAppSc,text="文本",image=self.imgtext) self.but5=Button(self.winAppSc,text="返回",image=self.imgback) #画笔大小 self.sizebut1=Button(self.winAppSc,text="1",image=self.imgp1) self.sizebut2=Button(self.winAppSc,text="5",image=self.imgp2) self.sizebut3=Button(self.winAppSc,text="10",image=self.imgp3) #画笔颜色 self.color_red=Button(self.winAppSc,text="red",image=self.imgred) self.color_green=Button(self.winAppSc,text="red",image=self.imggreen) self.color_blue=Button(self.winAppSc,text="red",image=self.imgblue) self.color_yellow=Button(self.winAppSc,text="red",image=self.imgyellow) self.color_black=Button(self.winAppSc,text="red",image=self.imgblack) self.color_white=Button(self.winAppSc,text="red",image=self.imgwhite) #文本框 self.text_entry=Text(self.winAppSc,bg=None,relief=None,height=self.row,width=4) canvas.bind('',lambdaevent:self.setxy(event,canvas)) canvas.bind('',lambdaevent:self.getxy(event,canvas)) canvas.bind('',lambdaevent:self.setBtn(event,self.winAppSc)) canvas.bind('',lambdaevent:self.clear(canvas)) self.but1.bind('',lambdaevent:self.bu_save(event)) self.but2.bind('',lambdaevent:self.bu_click(event,canvas,1)) self.but3.bind('',lambdaevent:self.bu_click(event,canvas,2)) self.but4.bind('',lambdaevent:self.bu_click(event,canvas,3)) self.buttext.bind('',lambdaevent:self.bu_click_text(event,canvas)) self.sizebut1.bind('',lambdaevent:self.sizebut_click(1)) self.sizebut2.bind('',lambdaevent:self.sizebut_click(5)) self.sizebut3.bind('',lambdaevent:self.sizebut_click(10)) self.color_red.bind('',lambdaevent:self.colorbut_click("red")) self.color_green.bind('',lambdaevent:self.colorbut_click("green")) self.color_blue.bind('',lambdaevent:self.colorbut_click("blue")) self.color_yellow.bind('',lambdaevent:self.colorbut_click("yellow")) self.color_white.bind('',lambdaevent:self.colorbut_click("white")) self.color_black.bind('',lambdaevent:self.colorbut_click("black")) self.text_entry.bind('',lambdaevent:self.entry(event)) self.but5.bind('',lambdaevent:self.ctrl_z()) #先清空but_list以防止下次截图时还保存有之前的按钮而之前的按钮的父窗口已经销毁 self.but_list.clear() self.but_list.append(self.but1) self.but_list.append(self.but2) self.but_list.append(self.but3) self.but_list.append(self.but4) self.but_list.append(self.buttext) self.but_list.append(self.sizebut1) self.but_list.append(self.sizebut2) self.but_list.append(self.sizebut3) self.but_list.append(self.color_red) self.but_list.append(self.color_green) self.but_list.append(self.color_blue) self.but_list.append(self.color_yellow) self.but_list.append(self.color_white) self.but_list.append(self.color_black) self.but_list.append(self.but5) #鼠标点击调用初始化绘制截图的起点位置 defsetxy(self,event,canvas): #清空上一次鼠标事件,并设置截图的起始位置 self.mouEvent=None if(self.but1_stat): #如果超出截图区域改变起始坐标为截图起始坐标 if(event.x>self.xandevent.x<self.end_x): self.dx=event.x if(event.y>self.yandevent.y<self.end_y): self.dy=event.y else: self.x,self.y=event.x,event.y forbutinself.but_list: but.place_forget() canvas.delete(self,'j0') #文本输入操作 if(self.but_text_statandself.but_type==4): self.p=self.p+1 if(self.p%2==1): self.text_entry.delete('1.0','end') self.font=tkFont.Font(family='宋体',size=(self.size+5)*3) self.text_entry.config(font=self.font,fg=self.color,width=4,height=1) self.text_entry.place(x=event.x,y=event.y) else: canvas.create_text(self.text_entry.winfo_x(),self.text_entry.winfo_y()+15,text=self.text_entry.get(1.0,END),font=self.font,justify=LEFT,fill=self.color,anchor=W) self.text_entry.place_forget() #获取所有图形对象 self.ids=list(canvas.find_all()) defPreventOutOfBounds(self,eventx,eventy): if(eventx<self.x): eventx=self.x elif(eventx>self.end_x): eventx=self.end_x if(eventy<self.y): eventy=self.y elif(eventy>self.end_y): eventy=self.end_y returneventx,eventy #鼠标按下左键拖动时调用绘制截图的终点位置 defgetxy(self,event,canvas): #记录上一次的鼠标事件,如果按钮有按下不消除选取框矩形没有按钮按下消除上一个选取矩形 if(self.but1_stat): ifself.but_type==1: #销毁上一个鼠标暂停点绘制的图形 canvas.delete(self,"j%d"%(self.i)) #防止图形画出界 event.x,event.y=self.PreventOutOfBounds(event.x,event.y) canvas.create_rectangle(self.dx,self.dy,event.x,event.y,width=self.size,outline=self.color,tag=('j',"j%d"%(self.i))) elifself.but_type==2: canvas.delete(self,"j%d"%(self.i)) event.x,event.y=self.PreventOutOfBounds(event.x,event.y) canvas.create_oval(self.dx,self.dy,event.x,event.y,width=self.size,outline=self.color,tag=('j',"j%d"%(self.i))) elifself.but_type==3: canvas.delete(self,"j%d"%(self.i)) event.x,event.y=self.PreventOutOfBounds(event.x,event.y) canvas.create_line(self.dx,self.dy,event.x,event.y,width=self.size,fill=self.color,arrow=tkinter.LAST,tag=('j',"j%d"%(self.i))) else: #新截图框 #print("销毁截图矩形") self.mouEvent=event #删除上一个矩形 canvas.delete(self,"j0") #绘制新矩形并设置结束点坐标 canvas.create_rectangle(self.x,self.y,event.x,event.y,tag=('j','j0')) self.end_x,self.end_y=event.x,event.y self.ids=list(canvas.find_all()) #鼠标释放的时候绘制工具按钮如果上次释放鼠标左键的上一个鼠标事件是拖动则显示按钮 defsetBtn(self,event,winAppSc): #获取截图区域大小如果长宽小于5个像素则不显示按钮 w,h=event.x-self.x,event.y-self.y if(self.mouEvent!=Noneandw>5andh>5): x,y=self.x,event.y forbutinself.but_list: but.place(x=x,y=y) x=x+35 #释放鼠标新的图形动作tags标记变更 self.i=self.i+1 defbu_click(self,event,canvas,but_name): #按钮按下记录其状态为True self.but1_stat=True self.i=self.i+1 self.but_type=but_name #清除文本框 self.text_entry.place_forget() #按下鼠标右键取消截图初始化参数 defclear(self,canvas): #print("取消截图") self.i=1 self.but1_stat=False #销毁所有对象 canvas.delete(ALL) #从新加载IMG对象,此时依旧是之前的截图背景 canvas.create_image(0,0,image=self.im,anchor='nw',tag=('r','r0')) canvas.pack(fill='both',expand='yes') #隐藏按钮删除截图框 forbutinself.but_list: but.place_forget() #设置画笔大小 defsizebut_click(self,size): self.but1_stat=True self.size=size self.font=tkFont.Font(family='宋体',size=(self.size+5)*3) self.text_entry.config(font=self.font) #设置画笔颜色 defcolorbut_click(self,color): self.but1_stat=True self.color=color self.text_entry.config(fg=self.color) #退出窗口 defexit_sc(self,scwd): try: scwd.winAppSc.destroy() exceptExceptionase: print(e) defbu_click_text(self,event,canvas): #输入文本框 self.but1_stat=True self.but_text_stat=True self.but_type=4 defentry(self,event): keysym=event.keysym ifkeysym=="Return": self.row=self.row+1 self.text_entry.config(height=self.row) elifkeysym=="BackSpace": text=self.text_entry.get("%d.0"%(self.row),"%d.end"%(self.row)) iflen(text)==0: self.row=self.row-1 ifself.row<1: self.row=1 self.text_entry.config(height=self.row) else: tmp=0 foriinrange(1,self.row+1): pattern="[\u4e00-\u9fa5]+" regex=re.compile(pattern) text=self.text_entry.get("%d.0"%(i),"%d.end"%(i)) ch=regex.findall(text) chstr=''.join(ch) text_len=len(text)+len(chstr) iftext_len>=tmp:tmp=text_len if(tmp>=2):self.text_entry.config(width=tmp+3) defbu_save(self,event): image=pyautogui.screenshot(region=[self.x+1,self.y+1,self.end_x-self.x-1,self.end_y-self.y-1]) save_dir="save" ifnotos.path.exists(save_dir): os.makedirs(save_dir) t=int(time.time()) image.save("%s/%d.jpg"%(save_dir,t)) #截图完成销毁窗口 self.winAppSc.destroy() defctrl_z(self): iflen(self.ids)>2: self.canvas.delete(self.ids[-1]) self.ids.pop() 截图 源码因为打包了python环境比较大 链接:https://pan.baidu.com/s/1hND-HARBPt6mumgnvvUtqA 提取码:WLWL

优秀的个人博客,低调大师

Spring Boot 2.x基础教程:默认数据源Hikari的配置详解

通过上一节的学习,我们已经学会如何应用Spring中的JdbcTemplate来完成对MySQL的数据库读写操作。接下来通过本篇文章,重点说说在访问数据库过程中的一个重要概念:数据源(Data Source),以及Spring Boot中对数据源的创建与配置。 基本概念 在开始说明Spring Boot中的数据源配置之前,我们先搞清楚关于数据访问的这些基本概念: 什么是JDBC? Java数据库连接(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。 JDBC API主要位于JDK中的java.sql包中(之后扩展的内容位于javax.sql包中),主要包括(斜体代表接口,需驱动程序提供者来具体实现): DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。 Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。 Connection:数据库连接,负责与进行数据库间通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。 Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。 CallableStatement:用以调用数据库中的存储过程。 SQLException:代表在数据库连接的建立和关闭和SQL语句的执行过程中发生了例外情况(即错误)。 什么是数据源? 可以看到,在java.sql中并没有数据源(Data Source)的概念。这是由于在java.sql中包含的是JDBC内核API,另外还有个javax.sql包,其中包含了JDBC标准的扩展API。而关于数据源(Data Source)的定义,就在javax.sql这个扩展包中。 实际上,在JDBC内核API的实现下,就已经可以实现对数据库的访问了,那么我们为什么还需要数据源呢?主要出于以下几个目的: 封装关于数据库访问的各种参数,实现统一管理 通过对数据库的连接池管理,节省开销并提高效率 在Java这个自由开放的生态中,已经有非常多优秀的开源数据源可以供大家选择,比如:DBCP、C3P0、Druid、HikariCP等。 而在Spring Boot 2.x中,对数据源的选择也紧跟潮流,采用了目前性能最佳的HikariCP。接下来,我们就来具体说说,这个Spring Boot中的默认数据源配置。 默认数据源:HikariCP 由于Spring Boot的自动化配置机制,大部分对于数据源的配置都可以通过配置参数的方式去改变。只有一些特殊情况,比如:更换默认数据源,多数据源共存等情况才需要去修改覆盖初始化的Bean内容。本节我们主要讲Hikari的配置,所以对于使用其他数据源或者多数据源的情况,在之后的教程中学习。 在Spring Boot自动化配置中,对于数据源的配置可以分为两类: 通用配置:以spring.datasource.*的形式存在,主要是对一些即使使用不同数据源也都需要配置的一些常规内容。比如:数据库链接地址、用户名、密码等。这里就不做过多说明了,通常就这些配置: spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver 数据源连接池配置:以spring.datasource.<数据源名称>.*的形式存在,比如:Hikari的配置参数就是spring.datasource.hikari.*形式。下面这个是我们最常用的几个配置项及对应说明: spring.datasource.hikari.minimum-idle=10 spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.idle-timeout=500000 spring.datasource.hikari.max-lifetime=540000 spring.datasource.hikari.connection-timeout=60000 spring.datasource.hikari.connection-test-query=SELECT 1 这些配置的含义: spring.datasource.hikari.minimum-idle: 最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size spring.datasource.hikari.maximum-pool-size: 最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值 spring.datasource.hikari.idle-timeout: 空闲连接超时时间,默认值600000(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。 spring.datasource.hikari.max-lifetime: 连接最大存活时间,不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短 spring.datasource.hikari.connection-timeout: 连接超时时间:毫秒,小于250毫秒,否则被重置为默认值30秒 spring.datasource.hikari.connection-test-query: 用于测试连接是否可用的查询语句 更多完整配置项可查看下表: name 描述 构造器默认值 默认配置validate之后的值 validate重置 autoCommit 自动提交从池中返回的连接 TRUE TRUE – connectionTimeout 等待来自池的连接的最大毫秒数 SECONDS.toMillis(30) = 30000 30000 如果小于250毫秒,则被重置回30秒 idleTimeout 连接允许在池中闲置的最长时间 MINUTES.toMillis(10) = 600000 600000 如果idleTimeout+1秒>maxLifetime 且 maxLifetime>0,则会被重置为0(代表永远不会退出);如果idleTimeout!=0且小于10秒,则会被重置为10秒 maxLifetime 池中连接最长生命周期 MINUTES.toMillis(30) = 1800000 1800000 如果不等于0且小于30秒则会被重置回30分钟 connectionTestQuery 如果您的驱动程序支持JDBC4,我们强烈建议您不要设置此属性 null null – minimumIdle 池中维护的最小空闲连接数 -1 10 minIdle<0或者minIdle>maxPoolSize,则被重置为maxPoolSize maximumPoolSize 池中最大连接数,包括闲置和使用中的连接 -1 10 如果maxPoolSize小于1,则会被重置。当minIdle<=0被重置为DEFAULT_POOL_SIZE则为10;如果minIdle>0则重置为minIdle的值 metricRegistry 该属性允许您指定一个 Codahale / Dropwizard MetricRegistry 的实例,供池使用以记录各种指标 null null – healthCheckRegistry 该属性允许您指定池使用的Codahale / Dropwizard HealthCheckRegistry的实例来报告当前健康信息 null null – poolName 连接池的用户定义名称,主要出现在日志记录和JMX管理控制台中以识别池和池配置 null HikariPool-1 – initializationFailTimeout 如果池无法成功初始化连接,则此属性控制池是否将 fail fast 1 1 – isolateInternalQueries 是否在其自己的事务中隔离内部池查询,例如连接活动测试 FALSE FALSE – allowPoolSuspension 控制池是否可以通过JMX暂停和恢复 FALSE FALSE – readOnly 从池中获取的连接是否默认处于只读模式 FALSE FALSE – registerMbeans 是否注册JMX管理Bean(MBeans) FALSE FALSE – catalog 为支持 catalog 概念的数据库设置默认 catalog driver default null – connectionInitSql 该属性设置一个SQL语句,在将每个新连接创建后,将其添加到池中之前执行该语句。 null null – driverClassName HikariCP将尝试通过仅基于jdbcUrl的DriverManager解析驱动程序,但对于一些较旧的驱动程序,还必须指定driverClassName null null – transactionIsolation 控制从池返回的连接的默认事务隔离级别 null null – validationTimeout 连接将被测试活动的最大时间量 SECONDS.toMillis(5) = 5000 5000 如果小于250毫秒,则会被重置回5秒 leakDetectionThreshold 记录消息之前连接可能离开池的时间量,表示可能的连接泄漏 0 0 如果大于0且不是单元测试,则进一步判断:(leakDetectionThreshold < SECONDS.toMillis(2) or (leakDetectionThreshold > maxLifetime && maxLifetime > 0),会被重置为0 . 即如果要生效则必须>0,而且不能小于2秒,而且当maxLifetime > 0时不能大于maxLifetime dataSource 这个属性允许你直接设置数据源的实例被池包装,而不是让HikariCP通过反射来构造它 null null – schema 该属性为支持模式概念的数据库设置默认模式 driver default null – threadFactory 此属性允许您设置将用于创建池使用的所有线程的java.util.concurrent.ThreadFactory的实例。 null null – scheduledExecutor 此属性允许您设置将用于各种内部计划任务的java.util.concurrent.ScheduledExecutorService实例 null null – 代码示例 本文的相关例子可以查看下面仓库中的chapter3-2目录: Github:https://github.com/dyc87112/SpringBoot-Learning/ Gitee:https://gitee.com/didispace/SpringBoot-Learning/ 如果您觉得本文不错,欢迎Star支持,您的关注是我坚持的动力! 参考资料 百度百科:JDBC HikariCP官方文档 Spring Boot默认HikariDataSource配置 欢迎关注我的公众号:程序猿DD,获得独家整理的学习资源和日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:didispace.com

优秀的个人博客,低调大师

MySQL基础篇(03):系统和自定义函数总结,触发器使用详解

本文源码:GitHub·点这里 || GitEE·点这里 一、系统封装函数 MySQL 有很多内置的函数,可以快速解决开发中的一些业务需求,大概包括流程控制函数,数值型函数、字符串型函数、日期时间函数、聚合函数等。以下列出了这些分类中常用的函数。 1、控制流程函数 case...when 根据值判断返回值,类比编程中的IF-ELSE判断。 -- DEMO 01 SELECT CASE DATE_FORMAT(NOW(),'%Y-%m-%d') WHEN '2019-12-29' THEN 'today' WHEN '2019-12-28' THEN 'yesterday' WHEN '2019-12-30' THEN 'tommor' ELSE 'Unknow' END; -- DEMO 02 SELECT (CASE WHEN 1>0 THEN 'true' ELSE 'false' END) AS result; if(expr1,expr2,expr3) 如果表达式 expr1 是TRUE,则 IF()的返回值为expr2; 否则返回值则为 expr3。 SELECT IF(1>2,'1>2','1<2') AS result ; SELECT IF(1<2,'yes ','no') AS result ; SELECT IF(STRCMP('test','test'),'no','yes'); ifnull(expr1,expr2) 如果表达式 expr1不为NULL,则返回值为expr1;否则返回值为 expr2。 SELECT IFNULL(NULL,'cicada'); SELECT IFNULL(1/1,'no'); 2、常用字符串函数 CHAR_LENGTH() 返回值为字符串的长度 。 SELECT CHAR_LENGTH(' c i c ') ;-- 包含空格 SELECT LENGTH(' S q l ') ; CONCAT(str1...) 拼接串联字符串。 SELECT CONCAT('My', 'S', 'ql'); SELECT CONCAT('My', NULL, 'QL'); -- 包含Null 则返回Null SELECT CONCAT("%", "Java", "%"); -- mybatis中拼接模糊查询 ELT(N,str1,str2,...) 若N = 1,则返回值为 str1 ,若N = 2,则返回值为 str2 ,以此类推,可以用来转换返回页面的状态。 SELECT ELT(1,'提交','审核中','规则通过') ; SELECT ELT(2,'提交','审核中','规则通过') ; FORMAT(X,D) 格式化数字类型。 SELECT FORMAT(3.1455,2) ; -- 四舍五入保留两位 SELECT TRUNCATE(3.1455,2) ; -- 直接截取两位 TRIM(str) 清空字符串空格。 SELECT LTRIM(' hel l o ') ;-- 清空左边 SELECT RTRIM(' hel l o ') ;-- 清空右边 SELECT TRIM(' hel l o ') ; -- 清空两边 SELECT REPLACE('M y S Q L',' ','') ; -- 替换掉全部空格 3、数值函数 FLOOR(X) 返回不大于X的最大整数值 。 SELECT FLOOR(1.23); -- 1 SELECT FLOOR(-1.23); -- -2 MOD(N,M) 模操作。返回N 被 M除后的余数。 SELECT MOD(29,9); -- 2 SELECT 29 MOD 9; -- 2 RAND() RAND(N) 返回一个随机浮点值,范围在0到1之间。若已指定一个整数参数 N ,则它被用作种子值,用来产生重复序列。 SELECT RAND(); -- 0.923 SELECT RAND(20) = RAND(20) ; -- TRUE 4、时间日期函数 ADDDATE(date,INTERVAL expr type) 给指定日期,以指定类型进行运算。 SELECT DATE_ADD('2019-12-29', INTERVAL 3 DAY); -- 2020-01-01 CURDATE() 将当前日期按照'YYYY-MM-DD' 或YYYYMMDD 格式的值返回,具体格式根据函数用在字符串或是数字语境中而定。 SELECT CURDATE(); -- '2019-12-29' 字符串 SELECT CURDATE() + 0; -- 20180725 数字 DATE(expr) 提取日期或时间日期表达式expr中的日期部分。 SELECT DATE('2019-12-31 01:02:03'); -- '2019-12-31' SELECT DATE('2019-12-31 01:02:03')+0; -- 20191231 DATE_FORMAT(date,format) 根据format 字符串进行 date 值的格式化。 SELECT DATE_FORMAT(NOW(), '%Y-%m-%d'); -- 2019-12-29 SELECT DATE_FORMAT(NOW(), '%Y年%m月%d日'); -- 2019年12月29日 5、聚合函数 AVG([distinct] expr) 求平均值 COUNT({*|[distinct] } expr) 统计行的数量 MAX([distinct] expr) 求最大值 MIN([distinct] expr) 求最小值 SUM([distinct] expr) 求累加和 二、自定义函数 1、概念简介 函数存储着一系列sql语句,调用函数就是一次性执行这些语句。所以函数可以降低语句重复。函数注重返回值,而触发器注重执行过程,所以一些语句无法执行。所以函数并不是单纯的sql语句集合。 2、使用方式 create function 函数名([参数列表]) returns 数据类型 begin sql语句; return 值; end; 参数列表的格式是: 变量名 数据类型。 无参案例 CREATE FUNCTION mysum1 () RETURNS INT RETURN (2+3)*2; SELECT mysum1 () ; 有参函数 表结构 CREATE TABLE t01_user ( id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', user_name varchar(20) DEFAULT NULL COMMENT '用户名称' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '用户表'; 函数用法 create function get_name(p_id INT) returns VARCHAR(20) begin declare userName varchar(20); select user_name from t01_user where id=p_id into userName; return userName; end; SELECT get_name(1) ; 3、函数查看 show create function get_name ; 4、删除函数 drop function get_name ; 5、函数注意事项 函数是事先经过编译,才能在服务器环境调用,所以MySQL集群环境需要同步编译;MySQL是多线程环境,所以要保证函数也是线程安全 。 三、触发器 1、触发器简介 触发器是特殊的存储过程,不同的是存储过程要用CALL来调用,而触发器不需要使用CALL。也不需要手工启动,只要当一个预定义的事件发生的时候,就会被MYSQL自动触发调用。 2、创建触发器 触发器语法 CREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_name FOR EACH ROW trigger_stmt trigger_name:触发器命名 ; trigger_time: 触发动作的时间 ; trigger_event: 激活触发器的语句类型 ; tbl_name: 触发器作用的表明,非临时表 ; trigger_stmt:触发程序执行的语句 ; 表数据同步 当向用户表 t01_user 写入数据时,同时向 t02_back 表写入一份备份数据。 -- 用户备份表 CREATE TABLE t02_back ( id int(11) NOT NULL PRIMARY KEY COMMENT '主键ID', user_name varchar(20) DEFAULT NULL COMMENT '用户名称' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '用户备份'; -- 触发器程序 DROP TRIGGER IF EXISTS user_back_trigger ; CREATE TRIGGER user_back_trigger AFTER INSERT ON t01_user FOR EACH ROW BEGIN INSERT INTO t02_back (id,user_name) VALUES (new.id,new.user_name); END ; -- 测试案例 INSERT INTO t01_user (user_name) VALUES ('smile'),('mysql') ; SELECT * FROM t02_back ; 3、查看触发器 查看触发器是指数据库中已存在的触发器的定义、状态、语法信息等。可以在TRIGGERS表中查看触发器信息。 SELECT * FROM `information_schema`.`TRIGGERS` WHERE `TRIGGER_NAME`='user_back_trigger'; 4、删除触发器 DROP TRIGGER语句可以删除MYSQL中已经定义的触发器,删除触发器的基本语法。 DROP TRIGGER [schema_name.]trigger_name 5、触发器注意事项 触发事件 对于相同的表,相同的事件只能创建一个触发器,比如对表t01_user创建两次AFTER INSERT触发器,就会报错。 执行效率 触发器可以减少应用端和数据库的通信次数和业务逻辑,但是基于行触发的逻辑,如果数据集非常大,效率会降低。 事务问题 触发器执行和原表的执行语句是否在同一个事务中,取决于触发表的存储引擎是否支持事务。 四、源代码地址 GitHub·地址 https://github.com/cicadasmile/mysql-data-base GitEE·地址 https://gitee.com/cicadasmile/mysql-data-base

优秀的个人博客,低调大师

JavaEE基础(06):Servlet整合C3P0数据库连接池

本文源码:GitHub·点这里 || GitEE·点这里 一、C3P0连接池 1、C3P0简介 C3P0是一个开源的JDBC连接池,应用程序根据C3P0配置来初始化数据库连接,可以自动回收空闲连接的功能。 2、核心依赖 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>${c3p0.version}</version> </dependency> 3、配置文件 配置文件位置:放在resources目录下,这样C3P0组件会自动加载该配置。 <?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <!-- 核心参数配置 --> <property name="jdbcUrl">jdbc:mysql://localhost:3306/servlet-jdbc</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="user">root</property> <property name="password">123</property> <!-- 池参数配置 --> <property name="acquireIncrement">3</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">2</property> <property name="maxPoolSize">10</property> </default-config> </c3p0-config> 4、编写工具类 该工具类用来获取数据库连接,和释放相关连接。 public class C3P0Pool { private static DataSource dataSource = new ComboPooledDataSource(); public static DataSource getDataSource() { return dataSource ; } /** * 获取连接 */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 释放连接 */ public static void close(ResultSet resultSet, PreparedStatement pst, Connection connection) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (pst != null) { try { pst.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } } 二、数据操作封装 1、新增数据 public class UserJdbcInsert { public static void insertUser (UserInfo userInfo){ try { Connection connection = C3P0Pool.getConnection(); String sql = "INSERT INTO user_info (user_name,user_age) VALUES (?,?)" ; PreparedStatement statement = connection.prepareStatement(sql); statement.setString(1,userInfo.getUserName()); statement.setString(2,userInfo.getUserAge().toString()); statement.execute() ; C3P0Pool.close(null, statement, connection); } catch (Exception e) { e.printStackTrace(); } } public static void batchInsertUser (List<UserInfo> userInfoList){ try { Connection connection = C3P0Pool.getConnection(); String sql = "INSERT INTO user_info (user_name,user_age) VALUES (?,?)" ; PreparedStatement statement = connection.prepareStatement(sql); for (UserInfo userInfo:userInfoList){ statement.setString(1,userInfo.getUserName()); statement.setString(2,userInfo.getUserAge().toString()); statement.addBatch(); } statement.executeBatch() ; C3P0Pool.close(null, statement, connection); } catch (Exception e) { e.printStackTrace(); } } } 2、查询数据 public class UserJdbcQuery { public static UserInfo queryUser (String userName){ UserInfo userInfo = null ; try { Connection connection = C3P0Pool.getConnection(); String sql = "SELECT * FROM user_info WHERE user_name=?" ; PreparedStatement statement = connection.prepareStatement(sql); statement.setString(1,userName); ResultSet resultSet = statement.executeQuery() ; while (resultSet.next()){ int id = resultSet.getInt("id"); String name = resultSet.getString("user_name"); int age = resultSet.getInt("user_age"); System.out.println("ID:"+id+";name:"+name+";age:"+age); userInfo = new UserInfo(name,age) ; } C3P0Pool.close(resultSet, statement, connection); } catch (Exception e) { e.printStackTrace(); } return userInfo ; } } 3、更新数据 public class UserJdbcUpdate { public static void updateUser (String name,Integer age,Integer id){ try { Connection connection = C3P0Pool.getConnection(); String sql = "UPDATE user_info SET user_name=?,user_age=? WHERE id=?" ; PreparedStatement statement = connection.prepareStatement(sql); statement.setString(1,name); statement.setInt(2,age); statement.setInt(3,id); statement.executeUpdate() ; C3P0Pool.close(null, statement, connection); } catch (Exception e) { e.printStackTrace(); } } } 4、删除数据 public class UserJdbcDelete { public static void deleteUser (Integer id){ try { Connection connection = C3P0Pool.getConnection(); String sql = "DELETE FROM user_info WHERE id=?" ; PreparedStatement statement = connection.prepareStatement(sql); statement.setInt(1,id); statement.executeUpdate() ; C3P0Pool.close(null, statement, connection); } catch (Exception e) { e.printStackTrace(); } } } 三、Servlet接口 public class JdbcServletImpl extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userName = request.getParameter("userName") ; UserInfo userInfo = UserJdbcQuery.queryUser(userName) ; response.setContentType("text/html;charset=utf-8"); response.getWriter().print("用户信息:"+userInfo); } } 测试访问: http://localhost:6003/jdbcServletImpl?userName=LiSi 页面打印: 用户信息:UserInfo{userName='LiSi', userAge=22}

优秀的个人博客,低调大师

夯实Java基础系列23:一文读懂继承、封装、多态的底层实现原理

本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下Star哈 文章首发于我的个人博客: www.how2playlife.com 从JVM结构开始谈多态 Java 对于方法调用动态绑定的实现主要依赖于方法表,但通过类引用调用和接口引用调用的实现则有所不同。总体而言,当某个方法被调用时,JVM 首先要查找相应的常量池,得到方法的符号引用,并查找调用类的方法表以确定该方法的直接引用,最后才真正调用该方法。以下分别对该过程中涉及到的相关部分做详细介绍。 JVM 的结构 典型的 Java 虚拟机的运行时结构如下图所示 图 1.JVM 运行时结构 此结构中,我们只探讨和本文密切相关的方法区 (method area)。当程序运行需要某个类的定义时,载入子系统 (class loader subsystem) 装入所需的 class 文件,并在内部建立该类的类型信息,这个类型信息就存贮在方法区。类型信息一般包括该类的方法代码、类变量、成员变量的定义等等。可以说,类型信息就是类的 Java 文件在运行时的内部结构,包含了改类的所有在 Java 文件中定义的信息。 注意到,该类型信息和 class 对象是不同的。class 对象是 JVM 在载入某个类后于堆 (heap) 中创建的代表该类的对象,可以通过该 class 对象访问到该类型信息。比如最典型的应用,在 Java 反射中应用 class 对象访问到该类支持的所有方法,定义的成员变量等等。可以想象,JVM 在类型信息和 class 对象中维护着它们彼此的引用以便互相访问。两者的关系可以类比于进程对象与真正的进程之间的关系。 Java 的方法调用方式 Java 的方法调用有两类,动态方法调用与静态方法调用。静态方法调用是指对于类的静态方法的调用方式,是静态绑定的;而动态方法调用需要有方法调用所作用的对象,是动态绑定的。类调用 (invokestatic) 是在编译时刻就已经确定好具体调用方法的情况,而实例调用 (invokevirtual) 则是在调用的时候才确定具体的调用方法,这就是动态绑定,也是多态要解决的核心问题。 JVM 的方法调用指令有四个,分别是 invokestatic,invokespecial,invokesvirtual 和 invokeinterface。前两个是静态绑定,后两个是动态绑定的。本文也可以说是对于 JVM 后两种调用实现的考察。 常量池(constant pool) 常量池中保存的是一个 Java 类引用的一些常量信息,包含一些字符串常量及对于类的符号引用信息等。Java 代码编译生成的类文件中的常量池是静态常量池,当类被载入到虚拟机内部的时候,在内存中产生类的常量池叫运行时常量池。 常量池在逻辑上可以分成多个表,每个表包含一类的常量信息,本文只探讨对于 Java 调用相关的常量池表。 CONSTANT_Utf8_info 字符串常量表,该表包含该类所使用的所有字符串常量,比如代码中的字符串引用、引用的类名、方法的名字、其他引用的类与方法的字符串描述等等。其余常量池表中所涉及到的任何常量字符串都被索引至该表。 CONSTANT_Class_info 类信息表,包含任何被引用的类或接口的符号引用,每一个条目主要包含一个索引,指向 CONSTANT_Utf8_info 表,表示该类或接口的全限定名。 CONSTANT_NameAndType_info 名字类型表,包含引用的任意方法或字段的名称和描述符信息在字符串常量表中的索引。 CONSTANT_InterfaceMethodref_info 接口方法引用表,包含引用的任何接口方法的描述信息,主要包括类信息索引和名字类型索引。 CONSTANT_Methodref_info 类方法引用表,包含引用的任何类型方法的描述信息,主要包括类信息索引和名字类型索引。 图 2. 常量池各表的关系 可以看到,给定任意一个方法的索引,在常量池中找到对应的条目后,可以得到该方法的类索引(class_index)和名字类型索引 (name_and_type_index), 进而得到该方法所属的类型信息和名称及描述符信息(参数,返回值等)。注意到所有的常量字符串都是存储在 CONSTANT_Utf8_info 中供其他表索引的。 方法表与方法调用 方法表是动态调用的核心,也是 Java 实现动态调用的主要方式。它被存储于方法区中的类型信息,包含有该类型所定义的所有方法及指向这些方法代码的指针,注意这些具体的方法代码可能是被覆写的方法,也可能是继承自基类的方法。 如有类定义 Person, Girl, Boy, 清单 1 class Person { public String toString(){ return "I'm a person."; } public void eat(){} public void speak(){} } class Boy extends Person{ public String toString(){ return "I'm a boy"; } public void speak(){} public void fight(){} } class Girl extends Person{ public String toString(){ return "I'm a girl"; } public void speak(){} public void sing(){} } 当这三个类被载入到 Java 虚拟机之后,方法区中就包含了各自的类的信息。Girl 和 Boy 在方法区中的方法表可表示如下: 图 3.Boy 和 Girl 的方法表 可以看到,Girl 和 Boy 的方法表包含继承自 Object 的方法,继承自直接父类 Person 的方法及各自新定义的方法。注意方法表条目指向的具体的方法地址,如 Girl 的继承自 Object 的方法中,只有 toString() 指向自己的实现(Girl 的方法代码),其余皆指向 Object 的方法代码;其继承自于 Person 的方法 eat() 和 speak() 分别指向 Person 的方法实现和本身的实现。 Person 或 Object 的任意一个方法,在它们的方法表和其子类 Girl 和 Boy 的方法表中的位置 (index) 是一样的。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。 如调用如下: 清单 2 class Party{ … void happyHour(){ Person girl = new Girl(); girl.speak(); … } } 当编译 Party 类的时候,生成girl.speak()的方法调用假设为: Invokevirtual #12 设该调用代码对应着 girl.speak(); #12 是 Party 类的常量池的索引。JVM 执行该调用指令的过程如下所示: 图 4. 解析调用过程 JVM 首先查看 Party 的常量池索引为 12 的条目(应为 CONSTANT_Methodref_info 类型,可视为方法调用的符号引用),进一步查看常量池(CONSTANT_Class_info,CONSTANT_NameAndType_info ,CONSTANT_Utf8_info)可得出要调用的方法是 Person 的 speak 方法(注意引用 girl 是其基类 Person 类型),查看 Person 的方法表,得出 speak 方法在该方法表中的偏移量 15(offset),这就是该方法调用的直接引用。 当解析出方法调用的直接引用后(方法表偏移量 15),JVM 执行真正的方法调用:根据实例方法调用的参数 this 得到具体的对象(即 girl 所指向的位于堆中的对象),据此得到该对象对应的方法表 (Girl 的方法表 ),进而调用方法表中的某个偏移量所指向的方法(Girl 的 speak() 方法的实现)。 接口调用 因为 Java 类是可以同时实现多个接口的,而当用接口引用调用某个方法的时候,情况就有所不同了。Java 允许一个类实现多个接口,从某种意义上来说相当于多继承,这样同样的方法在基类和派生类的方法表的位置就可能不一样了。 清单 3 interface IDance{ void dance(); } class Person { public String toString(){ return "I'm a person."; } public void eat(){} public void speak(){} } class Dancer extends Person implements IDance { public String toString(){ return "I'm a dancer."; } public void dance(){} } class Snake implements IDance{ public String toString(){ return "A snake."; } public void dance(){ //snake dance } } 图 5.Dancer 的方法表(查看大图) 可以看到,由于接口的介入,继承自于接口 IDance 的方法 dance()在类 Dancer 和 Snake 的方法表中的位置已经不一样了,显然我们无法通过给出方法表的偏移量来正确调用 Dancer 和 Snake 的这个方法。这也是 Java 中调用接口方法有其专有的调用指令(invokeinterface)的原因。 Java 对于接口方法的调用是采用搜索方法表的方式,对如下的方法调用 invokeinterface #13 JVM 首先查看常量池,确定方法调用的符号引用(名称、返回值等等),然后利用 this 指向的实例得到该实例的方法表,进而搜索方法表来找到合适的方法地址。 因为每次接口调用都要搜索方法表,所以从效率上来说,接口方法的调用总是慢于类方法的调用的。 执行结果如下:可以看到System.out.println(dancer);调用的是Person的toString方法。 继承的实现原理 Java 的继承机制是一种复用类的技术,从原理上来说,是更好的使用了组合技术,因此要理解继承,首先需要了解类的组合技术是如何实现类的复用的。 使用组合技术复用类假设现在的需求是要创建一个具有基本类型,String 类型以及一个其他非基本类型的对象。该如何处理呢? 对于基本类型的变量,在新类中成员变量处直接定义即可,但对于非基本类型变量,不仅需要在类中声明其引用,并且还需要手动初始化这个对象。 这里需要注意的是,编译器并不会默认将所有的引用都创建对象,因为这样的话在很多情况下会增加不必要的负担,因此,在合适的时机初始化合适的对象,可以通过以下几个位置做初始化操作: 在定义对象的地方,先于构造方法执行。在构造方法中。在正要使用之前,这个被称为惰性初始化。使用实例初始化。 class Soap { private String s; Soap() { System.out.println("Soap()"); s = "Constructed"; } public String tiString(){ return s; } } public class Bath { // s1 初始化先于构造函数 private String s1 = "Happy", s2 = "Happy", s3, s4; private Soap soap; private int i; private float f; public Both() { System.out.println("inSide Both"); s3 = "Joy"; f = 3.14f; soap = new Soap(); } { i = 88; } public String toString() { if(s4 == null){ s4 = "Joy" } return "s1 = " + s1 +"\n" + "s2 = " + s2 +"\n" + "s3 = " + s3 +"\n" + "s4 = " + s4 +"\n" + "i = " + i +"\n" + "f = " + f +"\n" + "soap = " + soap; } } 继承Java 中的继承由 extend 关键字实现,组合的语法比较平实,而继承是一种特殊的语法。当一个类继承自另一个类时,那么这个类就可以拥有另一个类的域和方法。 class Cleanser{ private String s = "Cleanser"; public void append(String a){ s += a; } public void apply(){ append("apply"); } public void scrub(){ append("scrub"); } public String toString(){ return s; } public static void main(String args){ Cleanser c = new Cleanser(); c.apply(); System.out.println(c); } } public class Deter extends Cleanser{ public void apply(){ append("Deter.apply"); super.scrub(); } public void foam(){ append("foam"); } public static void main(String args){ Deter d = new Deter(); d.apply(); d.scrub(); d.foam(); System.out.println(d); Cleanser.main(args); } } 上面的代码中,展示了继承语法中的一些特性: 子类可以直接使用父类中公共的方法和成员变量(通常为了保护数据域,成员变量均为私有)子类中可以覆盖父类中的方法,也就是子类重写了父类的方法,此时若还需要调用被覆盖的父类的方法,则需要用到 super 来指定是调用父类中的方法。子类中可以自定义父类中没有的方法。可以发现上面两个类中均有 main 方法,命令行中调用的哪个类就执行哪个类的 main 方法,例如:java Deter。继承语法的原理接下来我们将通过创建子类对象来分析继承语法在我们看不到的地方做了什么样的操作。 可以先思考一下,如何理解使用子类创建的对象呢,首先这个对象中包含子类的所有信息,但是也包含父类的所有公共的信息。 下面来看一段代码,观察一下子类在创建对象初始化的时候,会不会用到父类相关的方法。 class Art{ Art() { System.out.println("Art Construct"); } } class Drawing extends Art { Drawing() { System.out.println("Drawing Construct"); } } public class Cartoon extends Drawing { public Cartoon() { System.out.println("Cartoon construct"); } public void static main(String args) { Cartoon c = new Cartoon(); } } /*output: Art Construct Drawing Construct Cartoon construct */ 通过观察代码可以发现,在实例化Cartoon时,事实上是从最顶层的父类开始向下逐个实例化,也就是最终实例化了三个对象。编译器会默认在子类的构造方法中增加调用父类默认构造方法的代码。 因此,继承可以理解为编译器帮我们完成了类的特殊组合技术,即在子类中存在一个父类的对象,使得我们可以用子类对象调用父类的方法。而在开发者看来只不过是使用了一个关键字。 注意:虽然继承很接近组合技术,但是继承拥有其他更多的区别于组合的特性,例如父类的对象我们是不可见的,对于父类中的方法也做了相应的权限校验等。 那么,如果类中的构造方法是带参的,该如何操作呢?(使用super关键字显示调用) 见代码: class Game { Game(int i){ System.out.println("Game Construct"); } } class BoardGame extends Game { BoardGame(int j){ super(j); System.out.println("BoardGame Construct"); } } public class Chess extends BoardGame{ Chess(){ super(99); System.out.println("Chess construct"); } public static void main(String args) { Chess c = new Chess(); } } /*output: Game Construct BoardGame Construct Chess construc */ 重载和重写的实现原理 刚开始学习Java的时候,就了解了Java这个比较有意思的特性:重写 和 重载。开始的有时候从名字上还总是容易弄混。我相信熟悉Java这门语言的同学都应该了解这两个特性,可能只是从语言层面上了解这种写法,但是jvm是如何实现他们的呢 ? 重载官方给出的介绍: 一. overload:The Java programming language supports overloading methods, and Java can distinguish between methods with different method signatures. This means that methods within a class can have the same name if they have different parameter lists . Overloaded methods are differentiated by the number and the type of the arguments passed into the method. You cannot declare more than one method with the same name and the same number and type of arguments, because the compiler cannot tell them apart. The compiler does not consider return type when differentiating methods, so you cannot declare two methods with the same signature even if they have a different return type. 首先看一段代码,来看看代码的执行结果: public class OverrideTest { class Father{} class Sun extends Father {} public void doSomething(Father father){ System.out.println("Father do something"); } public void doSomething(Sun father){ System.out.println("Sun do something"); } public static void main(String [] args){ OverrideTest overrideTest = new OverrideTest(); Father sun = overrideTest.new Sun(); Father father = overrideTest.new Father(); overrideTest.doSomething(father); overrideTest.doSomething(sun); } } 看下这段代码的执行结果,最后会打印: Father do somethingFather do something 为什么会打印出这样的结果呢? 首先要介绍两个概念:静态分派和动态分派 静态分派:依赖静态类型来定位方法执行版本的分派动作称为静态分派 动态分派:运行期根据实际类型确定方法执行版本的分派过程。 他们的区别是: 1. 静态分派发生在编译期,动态分派发生在运行期; 2. private,static,final 方法发生在编译期,并且不能被重写,一旦发生了重写,将会在运行期处理。 3. 重载是静态分派,重写是动态分派 回到上面的问题,因为重载是发生在编译期,所以在编译期已经确定两次 doSomething 方法的参数都是Father类型,在class文件中已经指向了Father类的符号引用,所以最后会打印两次Father do something。 二. override:An instance method in a subclass with the same signature (name, plus the number and the type of its parameters) and return type as an instance method in the superclassoverridesthe superclass's method. The ability of a subclass to override a method allows a class to inherit from a superclass whose behavior is "close enough" and then to modify behavior as needed. The overriding method has the same name, number and type of parameters, and return type as the method that it overrides. An overriding method can also return a subtype of the type returned by the overridden method. This subtype is called acovariant return type. 还是上面那个代码,稍微改动下 public class OverrideTest { class Father{} class Sun extends Father {} public void doSomething(){ System.out.println("Father do something"); } public void doSomething(){ System.out.println("Sun do something"); } public static void main(String [] args){ OverrideTest overrideTest = new OverrideTest(); Father sun = overrideTest.new Sun(); Father father = overrideTest.new Father(); overrideTest.doSomething(); overrideTest.doSomething(); } } ​最后会打印: Father do something Sun do something 相信大家都会知道这个结果,那么这个结果jvm是怎么实现的呢? 在编译期,只会识别到是调用Father类的doSomething方法,到运行期才会真正找到对象的实际类型。 首先该方法的执行,jvm会调用invokevirtual指令,该指令会找栈顶第一个元素所指向的对象的实际类型,如果该类型存在调用的方法,则会走验证流程,否则继续找其父类。这也是为什么子类可以直接调用父类具有访问权限的方法的原因。简而言之,就是在运行期才会去确定对象的实际类型,根据这个实际类型确定方法执行版本,这个过程称为动态分派。override 的实现依赖jvm的动态分派。 参考文章 https://blog.csdn.net/dj_dengjian/article/details/80811348 https://blog.csdn.net/chenssy/article/details/12757911 https://blog.csdn.net/fan2012huan/article/details/51007517 https://blog.csdn.net/fan2012huan/article/details/50999777 https://www.cnblogs.com/serendipity-fly/p/9469289.html https://blog.csdn.net/m0_37264516/article/details/86709537 微信公众号 Java技术江湖 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号【Java技术江湖】一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发! Java工程师必备学习资源: 一些Java工程师常用学习资源,关注公众号后,后台回复关键字 “Java” 即可免费无套路获取。 个人公众号:黄小斜 作者是跨考软件工程的 985 硕士,自学 Java 两年,拿到了 BAT 等近十家大厂 offer,从技术小白成长为阿里工程师。 作者专注于 JAVA 后端技术栈,热衷于分享程序员干货、学习经验、求职心得和程序人生,目前黄小斜的CSDN博客有百万+访问量,知乎粉丝2W+,全网已有10W+读者。 黄小斜是一个斜杠青年,坚持学习和写作,相信终身学习的力量,希望和更多的程序员交朋友,一起进步和成长! 关注公众号【黄小斜】后回复【原创电子书】即可领取我原创的电子书《菜鸟程序员修炼手册:从技术小白到阿里巴巴Java工程师》 程序员3T技术学习资源: 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 “资料” 即可免费无套路获取。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册